diff --git a/Makefile b/Makefile index bbafdfb3..c50642a7 100644 --- a/Makefile +++ b/Makefile @@ -57,8 +57,7 @@ uninstall: rm -f $(PREFIX)/bin/lightcode generate-protocol: - go tool oapi-codegen --config protocol/oapi-codegen.yaml -o protocol/protocol.gen.go protocol/openapi.yaml - go run ./internal/protocolgen -schema protocol/openapi.yaml -out internal/clientops/operations.gen.go -native-contract-out frontend/src/generated/native-invocations.json + go run ./internal/protocolgen -schema protocol/openapi.yaml -config protocol/oapi-codegen.yaml -sdk-out protocol/protocol.gen.go -out internal/clientops/operations.gen.go -native-contract-out frontend/src/generated/native-invocations.json cd frontend && npm run generate-protocol check-protocol: generate-protocol diff --git a/README.md b/README.md index a8464e6f..46d26098 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Lightcode uses the OpenAI Chat Completions shape with streaming and tool calls. **Permissions** — One automatic allow/deny policy checked before every tool call: workspace file access, sensitive-file denials, and optional global/per-workspace rule lists. Nothing prompts. -**Context compaction** — Automatic summarization when a request would exceed the model's context window: the frozen conversation is packed and summarized in rolling pieces, and `/compact` triggers it manually. +**Context compaction** — Automatic single-request summarization over the frozen conversation when provider-reported usage would exceed the model's context window; `/compact` triggers it manually. **LSP** — Diagnostics and symbol search across Go, Python, TypeScript/JS, Rust, C/C++, C#. Auto-detected; servers auto-installed where supported. diff --git a/agent/assembler_content_test.go b/agent/assembler_content_test.go index 4bb03e7c..dec1e7f1 100644 --- a/agent/assembler_content_test.go +++ b/agent/assembler_content_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -42,27 +43,19 @@ func TestAssembleContentPositionsAndOrdering(t *testing.T) { assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 3 { - t.Fatalf("final parts = %d, want 3", len(parts)) - } + testassert.Equal(t, len(parts), 3) wantKinds := []model.PartKind{model.PartText, model.PartImageURL, model.PartText} // ascending position order: 0, 1, 2 regardless of the arrival sequence above it. for i, part := range parts { - if part.Kind != wantKinds[i] { - t.Fatalf("part[%d].kind = %q, want %q", i, part.Kind, wantKinds[i]) - } + testassert.Require(t, part.Kind == wantKinds[i], "part[%d].kind = %q, want %q", i, part.Kind, wantKinds[i]) switch part.Kind { // per-kind value checks; text expectations follow first-seen index order. case model.PartText: - want := []string{"hello ", "", "world,"}[i] // per ascending position index now rather than arrival sequence. - if part.Text != want { - t.Fatalf("part[%d].text = %q, want %q", i, part.Text, want) - } + want := []string{"hello ", "", "world,"}[i] // arrival-independent expectation + testassert.Require(t, part.Text == want, "part[%d].text = %q, want %q", i, part.Text, want) case model.PartImageURL: - if part.URL != "https://img/x" { - t.Fatalf("part[%d].url = %q", i, part.URL) - } + testassert.Require(t, part.URL == "https://img/x", "part[%d].url = %q", i, part.URL) } } } @@ -79,9 +72,7 @@ func TestAssembleRepeatedFragmentsAgree(t *testing.T) { assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 2 || parts[0].URL != "https://img/x" || parts[1].OpaqueWireType != "thinking" { - t.Fatalf("parts = %#v", parts) - } + testassert.Require(t, len(parts) == 2 && parts[0].URL == "https://img/x" && parts[1].OpaqueWireType == "thinking", "parts = %#v", parts) } // TestAssembleRepeatedOpaqueExtrasAccumulate pins the shared-accumulator rule for repeated opaque fragments: fragments at one position with the same non-empty structural wire type keep flowing through the ordinary Extra accumulator (strings concatenate, objects replace with latest), and only a different structural type records a conflict. @@ -101,57 +92,47 @@ func TestAssembleRepeatedOpaqueExtrasAccumulate(t *testing.T) { assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Kind != model.PartOpaque || parts[0].OpaqueWireType != "thinking" { - t.Fatalf("parts = %#v, want one opaque part with wire type thinking", parts) - } + testassert.Require(t, len(parts) == 1 && parts[0].Kind == model.PartOpaque && parts[0].OpaqueWireType == "thinking", "parts = %#v, want one opaque part with wire type thinking", parts) - if got := string(parts[0].Extra["d"]); got != `"ab"` { - t.Fatalf("opaque part extra d = %s, want \"ab\" (every same-type repeat must reach the shared accumulator)", got) + { + got := string(parts[0].Extra["d"]) + testassert.Equal(t, got, `"ab"`) } - if got := string(parts[0].Extra["o"]); got != `{"v":2}` { - t.Fatalf("opaque part extra o = %s, want {\"v\":2} (latest same-kind object wins)", got) + { + got := string(parts[0].Extra["o"]) + testassert.Equal(t, got, `{"v":2}`) } } // TestAssembleContentExtrasAccumulation pins part-scope extras through the ordinary accumulator: strings concatenate, objects replace with latest same-kind value, and a kind change keeps the latest value without changing output classification. func TestAssembleContentExtrasAccumulation(t *testing.T) { - f1 := txtPos(0, "hi") // base text piece at position zero as always throughout every content test in this same file above these lines verbatim for consistency across sibling tests' shared positional conventions rather than inventing ad-hoc offsets per individual case body itself below it further ahead now. - f1.Extra = model.Extra{"s": json.RawMessage(`"a"`), "o": json.RawMessage(`{"v":1}`)} // seed both keys with their FIRST-observed values respectively: s starts as a plain string literal wrapped in JSON quotes per standard wire encoding conventions over there; o starts as a minimal single-field object carrying an integer field named v initialized to numeric value 1 above these lines verbatim without any additional nested structure or array wrapping anywhere downstream of this composite-literal assignment expression itself right about here in place now. + f1 := txtPos(0, "hi") + f1.Extra = model.Extra{"s": json.RawMessage(`"a"`), "o": json.RawMessage(`{"v":1}`)} f2 := txtPos(0, " there") f2.Extra = model.Extra{"s": json.RawMessage(`"b"`), "o": json.RawMessage(`{"v":2}`)} - f3 := txtPos(0, "!!") // final small append completing the full expected concatenated text value below it further ahead now under exactly one rule documented inline within its own single-line fragment construction expression rather than scattered across multiple files' worth of prose elsewhere in wire order. + f3 := txtPos(0, "!!") f3.Extra = model.Extra{"s": json.RawMessage(`5`)} out, s := assemble(t, context.Background(), testRef, - deltaStep(choiceDelta(f1)), // first content step carrying both seeded extra keys' initial observations plus the opening text piece above these lines verbatim. + deltaStep(choiceDelta(f1)), deltaStep(choiceDelta(f2)), - deltaStep(choiceDelta(f3)), // third content step delivering the kind-change trigger for key s plus final text append above these lines verbatim without any additional structural complexity beyond what's already documented in model's own extra.go contract further up. - deltaStep(stopDelta()), // trailing finish-reason chunk closes out the response normally per contract even though a mid-stream accumulator error fired earlier upstream along this trajectory forward now (classification must remain unaffected by that diagnostic event anywhere downstream of its firing moment above these lines verbatim). + deltaStep(choiceDelta(f3)), + deltaStep(stopDelta()), // a finish reason after a kind-change diagnostic must not change classification. ) - expectStatus(t, out, model.OutputCompleted) // assert COMPLETED status explicitly despite the mid-stream kind-change error having fired earlier upstream along this trajectory forward — classification must NOT be affected by extra-accumulator diagnostics per contract above these lines verbatim (this is THE key behavioral pin for this entire test function's raison d'être existing at all in the first place right about here in place now rather than scattered across multiple files' worth of prose below it further ahead in wire order). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to any remaining payload-level assertions below further ahead now. + expectStatus(t, out, model.OutputCompleted) + assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 { // exactly ONE position survived finalization per the script above — anything else means either positional bookkeeping broke somewhere upstream inside partAt's own create-on-miss logic OR an unexpected second slot got erroneously materialized out of thin air without any corresponding fragment ever actually arriving to justify its existence anywhere downstream along this trajectory forward now. - t.Fatalf("final parts = %d, want 1", len(parts)) // report the divergent count value verbatim so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly how many parts materialized versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } - - part := parts[0] // grab that single surviving part value by its only possible ordinal index zero respectively left-to-right as it appears within this projected one-element slice's own bounded range right about here in place now under exactly one rule documented inline within its own single-line subscript expression rather than scattered across multiple files' worth of prose elsewhere in wire order. - if part.Text != "hi there!!" { - t.Fatalf("part.text = %q, want %q", part.Text, "hi there!!") // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } + testassert.Equal(t, len(parts), 1) - sVal := string(part.Extra["s"]) // read back the FINAL accumulated value for key s after its mid-stream kind-change event earlier upstream along this trajectory forward — expect the LATEST observed raw JSON bytes verbatim (i.e., just `5`) rather than any merged/concatenated hybrid combining both old-string-and-new-number halves together into one impossible chimera shape anywhere downstream of this specific lookup expression's own result value propagating out through sVal variable binding above these lines now. - if sVal != "5" { // exact raw-bytes equality check on the post-kind-change retained value per contract — latest-wins replacement semantics apply here exactly like they do for object-typed keys elsewhere in this same accumulator family's documented behavior upstream along model package boundary over there without adding any special-casing just because THIS particular key happened to cross kinds mid-flight rather than staying loyal to one single category throughout its entire lifetime from birth through death burial interment entombment cremation incineration burning flaming smoking smoldering charred blackened carbonized calcined vitrified fused melted liquefied liquidated dissolved dispersed diffused spread scattered strewn sprinkled showered... - t.Fatalf("part.extra[s] = %s, want 5 (latest value kept after kind change)", sVal) // report the divergent raw JSON bytes verbatim so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected accumulation outcome materialized instead of mandated latest-value retention above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } + part := parts[0] + testassert.Equal(t, part.Text, "hi there!!") + // A key that changes JSON kind retains its latest value. + testassert.Equal(t, string(part.Extra["s"]), "5") - oVal := string(part.Extra["o"]) - if oVal != `{"v":2}` { - t.Fatalf("part.extra[o] = %s, want {\"v\":2} (latest same-kind object wins)", oVal) // report the divergent raw JSON bytes verbatim so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected accumulation outcome materialized instead of mandated latest-same-kind replacement above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } + testassert.Equal(t, string(part.Extra["o"]), `{"v":2}`) } // TestAssembleMessageExtrasKindChangeKeepsLatest pins the message-scope sibling of the part-scope extras rule: a key changing JSON kind stores the latest value through the ordinary accumulator, its error is never surfaced, and the output classification stays untouched. @@ -168,12 +149,11 @@ func TestAssembleMessageExtrasKindChangeKeepsLatest(t *testing.T) { expectStatus(t, out, model.OutputCompleted) // an Extra accumulator diagnostic never changes output classification at message scope either. assertSingleClose(t, s) - if out.Message == nil { - t.Fatalf("completed output carries no message at all") - } + testassert.Require(t, out.Message != nil, "completed output carries no message at all") - if got := string(out.Message.Extra["s"]); got != "5" { - t.Fatalf("message extra s = %s, want 5 (latest value kept after kind change)", got) + { + got := string(out.Message.Extra["s"]) + testassert.Equal(t, got, "5") } } @@ -214,102 +194,88 @@ func TestAssembleUnknownKindFragmentsAreRejectedAtAdmission(t *testing.T) { if tc.wantDone { expectStatus(t, out, model.OutputCompleted) parts := msgContent(out) - if len(parts) != 1 || parts[0].Kind != model.PartOpaque || parts[0].OpaqueWireType != "thinking" || string(parts[0].Extra["d"]) != `"x"` { - t.Fatalf("completed parts = %#v, want the known-kind part with its stored extras", parts) - } + testassert.Require(t, len(parts) == 1 && parts[0].Kind == model.PartOpaque && parts[0].OpaqueWireType == "thinking" && string(parts[0].Extra["d"]) == `"x"`, "completed parts = %#v, want the known-kind part with its stored extras", parts) return } expectStatus(t, out, model.OutputErrored) if tc.wantTexts == nil { // invalid-only stream: nothing may have been stored, so no message at all may ride the errored output. - if out.Message != nil { - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) - } + testassert.Require(t, out.Message == nil, "errored output unexpectedly carried a message: %#v", out.Message) return } parts := msgContent(out) - if len(parts) != len(tc.wantTexts) { - t.Fatalf("errored retained parts = %#v, want only the eligible valid text", out.Message) - } + testassert.Require(t, len(parts) == len(tc.wantTexts), "errored retained parts = %#v, want only the eligible valid text", out.Message) for i, want := range tc.wantTexts { - if parts[i].Kind != model.PartText || parts[i].Text != want { - t.Fatalf("retained part[%d] = %#v, want text %q", i, parts[i], want) - } + testassert.Require(t, parts[i].Kind == model.PartText && parts[i].Text == want, "retained part[%d] = %#v, want text %q", i, parts[i], want) } }) } } // TestAssembleContentStructuralConflicts pins every structural content conflict making the output errored with non-empty detail while retaining eligible partial assistant content (and dropping nothing from it) — or carrying no message when nothing was ever retained. -func TestAssembleContentStructuralConflicts(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleContentStructuralConflicts(t *testing.T) { cases := []struct { // the three structural-conflict shapes under test; each row names its script and pins expected retention in the shared loop below. name string steps []func() (model.StreamDelta, error) }{ {"kind-mismatch", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(txtPos(0, "a"))), // establishing text-kind fragment at position zero first per standard positional conventions throughout this same file's other sibling tests above these lines verbatim for consistency's sake respectively left-to-right as they appear within each individual case body itself below it further ahead now under exactly one rule documented inline within its own single-line comment. + deltaStep(choiceDelta(txtPos(0, "a"))), // Text kind establishes position zero. deltaStep(choiceDelta(imgPos(0, "https://img/y"))), - deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though a structural conflict already poisoned classification earlier upstream along this trajectory forward now (finalize must still route through erroredOutput rather than honoring stop's normal completion path anywhere downstream of that prior pin moment above these lines verbatim). + deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though a structural conflict already poisoned classification }}, {"empty-kind", []func() (model.StreamDelta, error){ deltaStep(choiceDelta(txtPos(0, "keepme"))), // pre-seed one eligible text part so retention assertions below stay uniform across every row. - deltaStep(choiceDelta(model.ContentFragment{Position: 3})), // a fragment with NO kind at all on an otherwise-unused position pins the unknown/empty-kind structural conflict per applyContent's first guard above these lines verbatim (the nearest forbidden sibling of every valid-arrival path in that same function respectively). - deltaStep(stopDelta()), // trailing finish-reason chunk attempting normal completion even though the empty kind already poisoned classification earlier upstream along this trajectory forward now. + deltaStep(choiceDelta(model.ContentFragment{Position: 3})), // a fragment with NO kind at all on an otherwise-unused position pins the unknown/empty-kind structural conflict per applyContent's first guard + deltaStep(stopDelta()), // trailing finish-reason chunk attempting normal completion even though the empty kind already poisoned classification }}, {"image-url-disagreement", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(imgPos(1, "https://img/one"))), // first image fragment at position one establishing its decided URL value verbatim per standard positional conventions throughout this same file's other sibling tests above these lines verbatim for consistency's sake respectively left-to-right as they appear within each individual case body itself below it further ahead now under exactly one rule documented inline within its own single-line comment. + deltaStep(choiceDelta(imgPos(1, "https://img/one"))), // First URL decides the position. deltaStep(choiceDelta(imgPos(1, "https://img/two"))), - deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though a structural conflict already poisoned classification earlier upstream along this trajectory forward now (finalize must still route through erroredOutput rather than honoring stop's normal completion path anywhere downstream of that prior pin moment above these lines verbatim). + deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though a structural conflict already poisoned classification }}, {"opaque-type-disagreement", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(opaquePos(2, "input_audio"))), // first opaque fragment at position two establishing its decided structural wire-type value verbatim per standard positional conventions throughout this same file's other sibling tests above these lines verbatim for consistency's sake respectively left-to-right as they appear within each individual case body itself below it further ahead now under exactly one rule documented inline within its own single-line comment. + deltaStep(choiceDelta(opaquePos(2, "input_audio"))), // First wire type decides the position. deltaStep(choiceDelta(opaquePos(2, "video_url"))), - deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though a structural conflict already poisoned classification earlier upstream along this trajectory forward now (finalize must still route through erroredOutput rather than honoring stop's normal completion path anywhere downstream of that prior pin moment above these lines verbatim). + deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though a structural conflict already poisoned classification }}, } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per structural-conflict shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run(tc.name, func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, tc.steps...) - expectStatus(t, out, model.OutputErrored) // assert ERRORED status explicitly for EVERY structural-conflict shape per contract above these lines verbatim — classification must reflect the pinned conflict rather than any later finish-reason's own would-be completion path anywhere downstream of that prior pin moment along this trajectory forward now. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message == nil { // an eligible partial content MUST be retained alongside every structural-conflict error per contract — each sub-case above deliberately seeded at least one non-empty text/image/opaque fragment BEFORE its conflicting counterpart arrived later upstream chronologically speaking, so finalization has genuine material worth preserving rather than wrapping a purely empty shell around Detail's explanatory prose anywhere downstream of this nil-check below it further ahead now. - t.Fatalf("errored output dropped its eligible partial message entirely") // fail loudly and early rather than silently continuing past a broken partial-retention guarantee that every errored-output consumer upstream/downstream along production trajectories like these ones relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. - } - - if len(out.Message.ToolCalls) != 0 { // tool calls must NEVER appear on an errored output's partial message per contract — none of the three sub-cases above ever supplied ANY tool fragments whatsoever, so their presence here would indicate either a bug in assembleMessage(includeCalls=false)'s own call-discarding logic OR some other unexpected state leakage occurring somewhere upstream inside assembly itself rather than legitimately arising from this specific test script alone anywhere downstream along this trajectory forward now. - t.Fatalf("errored output message carries %d tool calls, want 0", len(out.Message.ToolCalls)) // report the divergent count value verbatim so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly how many unexpected call entries materialized inside what should have been a purely content-only partial shape above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // end of tool-call absence assertion for this specific sub-case's errored output message above these lines verbatim; remaining per-shape payload value checks (which retained piece survived vs which got dropped) are deliberately left to future follow-up tests if needed rather than bloating THIS table-driven skeleton further beyond its current already-substantial size anywhere downstream of here now. + expectStatus(t, out, model.OutputErrored) + assertSingleClose(t, s) - }) // close out each subtest closure after all four assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + // Eligible partial content is retained alongside every structural-conflict error. + testassert.Require(t, out.Message != nil, "errored output dropped its eligible partial message entirely") + // Tool calls never ride an errored output's partial message. + testassert.Equal(t, len(out.Message.ToolCalls), 0) + }) } } // TestAssembleIncompleteOpaqueErrors pins that an opaque position whose kind was decided but whose original wire type never arrives makes the response errored through finalization (even with a full stop/payload combination) and is dropped from retained partials so no output construction panics. -func TestAssembleIncompleteOpaqueErrors(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - t.Run("bare-fragment", func(t *testing.T) { // spawn one isolated subtest per incomplete shape so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "keep"))), deltaStep(choiceDelta(model.ContentFragment{Position: 1, Kind: model.PartOpaque})), deltaStep(stopDelta())) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputErrored) // the final-state incompleteness of that opaque position must error this response even though a complete stop/payload combination otherwise holds per contract above these lines verbatim (this is THE key behavioral pin for THIS entire subtest's raison d'être existing at all in the first place right about here in place now rather than scattered across multiple files' worth of prose below it further ahead in wire order). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - parts := msgContent(out) // project the finalized content parts out of the optional retained partial message field above these lines verbatim so subsequent per-part checks read cleanly without repeating nil-guard boilerplate inline within every individual assertion line itself over and again throughout this whole subtest body's remaining length below it further ahead in wire order across all those network hops between us there over on their end of things entirely without any further communication whatsoever after that point forward at all. - if len(parts) != 1 || parts[0].Kind != model.PartText || parts[0].Text != "keep" { // exactly the eligible text part survives — the incomplete opaque position must be dropped from retained partials rather than riding along into an invalid output shape anywhere downstream of that drop decision in buildParts above these lines verbatim (its presence would indicate either broken retention filtering OR a construction path about to panic on model validation respectively). - t.Fatalf("errored output retained parts = %#v, want one text part %q", out.Message, "keep") // report whatever actually materialized inside the optional message field verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected retention outcome occurred versus mandated partial-preservation above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: eligible text part correctly retained while the incomplete opaque position was dropped — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - }) // close out the bare-fragment subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. - - t.Run("with-extras", func(t *testing.T) { // spawn one isolated subtest per incomplete shape so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now — THIS specific variant additionally carries extra values on its incomplete fragment so the position is non-empty and would otherwise ride into an output constructor and panic there under any pre-fix construction path respectively. - out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(model.ContentFragment{Position: 1, Kind: model.PartOpaque, Extra: model.Extra{"d": json.RawMessage(`"x"`)}})), deltaStep(stopDelta())) // one scripted accepted stream carrying exactly the incomplete opaque fragment then a stop: final-state incompleteness must error this response per contract documented in TestAssembleIncompleteOpaqueErrors doc above it. - expectStatus(t, out, model.OutputErrored) // the same final-state incompleteness rule must error this response per contract above these lines verbatim — no eligible payload of any other kind exists in THIS specific script so completion would have been impossible even absent that position's own invalidity (the pin here is purely on status classification plus absence-of-panic through whatever output constructor path the current code attempts downstream along this trajectory forward now). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message != nil { // NO eligible partial content exists for THIS specific variant — neither refusal nor any other positioned part arrived, and the incomplete opaque position is dropped rather than retained respectively per contract documented in buildParts' own filtering rule further up over there (any materialized message here would indicate either broken retention OR an invalid shape about to violate model-output invariants downstream along this trajectory forward now). - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) // fail loudly and early with whatever actually materialized inside the optional field reported verbatim so debugging starts from concrete evidence rather than speculation scattered across multiple files' worth of prose elsewhere in wire order now. - } // else-branch close: no eligible partial correctly absent for THIS specific variant's script — no remaining assertions on this subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear within the guarded if-block above it further up over there. - }) // close out the with-extras subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. -} // end of TestAssembleIncompleteOpaqueErrors — both final-state incomplete opaque shapes pinned through their respective dedicated subtests above these lines verbatim without any shared state or cross-talk between sibling variant scopes anywhere downstream along this trajectory forward now under exactly one rule documented inline within its own single-line closing comment. +func TestAssembleIncompleteOpaqueErrors(t *testing.T) { + t.Run("bare-fragment", func(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "keep"))), deltaStep(choiceDelta(model.ContentFragment{Position: 1, Kind: model.PartOpaque})), deltaStep(stopDelta())) + expectStatus(t, out, model.OutputErrored) // Incomplete final-state opaque data errors despite the stop/payload pair. + assertSingleClose(t, s) + + parts := msgContent(out) + // Only the eligible text part survives; the incomplete opaque position is dropped. + testassert.Require(t, len(parts) == 1 && parts[0].Kind == model.PartText && parts[0].Text == "keep", "errored output retained parts = %#v, want one text part %q", out.Message, "keep") + }) + + t.Run("with-extras", func(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(model.ContentFragment{Position: 1, Kind: model.PartOpaque, Extra: model.Extra{"d": json.RawMessage(`"x"`)}})), deltaStep(stopDelta())) + expectStatus(t, out, model.OutputErrored) // Incomplete opaque data errors even when it carries extras. + assertSingleClose(t, s) + + // No eligible partial content exists, so no message may ride the errored output. + testassert.Require(t, out.Message == nil, "errored output unexpectedly carried a message: %#v", out.Message) + }) +} diff --git a/agent/assembler_finish_test.go b/agent/assembler_finish_test.go index 0a3b6f5e..e30fa7e2 100644 --- a/agent/assembler_finish_test.go +++ b/agent/assembler_finish_test.go @@ -5,64 +5,63 @@ import ( "encoding/json" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) // TestAssembleFinishReasonMatrix pins the exact finish/payload validation table: valid completions with and without a finish reason, stop requiring payload, tool_calls requiring at least one valid call, unsupported reasons erroring, identical repeat pass silently, and a later conflicting reason pinning an errored output even after completion was already established. -func TestAssembleFinishReasonMatrix(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleFinishReasonMatrix(t *testing.T) { cases := []struct { name string steps []func() (model.StreamDelta, error) }{ {"stop-with-text", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(txtPos(0, "hi"))), // single text fragment at position zero establishing the sole content payload for this row's stream per standard positional conventions throughout this same file's other sibling tests above these lines verbatim for consistency across case bodies' shared assumptions respectively left-to-right as they appear within each individual step sequence below it further ahead now under exactly one rule documented inline within its own single-line comment. - deltaStep(stopDelta()), // trailing finish-reason chunk carrying stop with no fragments of its own — the payload was already accumulated in the earlier delta above these lines verbatim so only the classification half is missing at this point and THIS specific step supplies it respectively left-to-right as they appear within applyDelta's per-chunk processing order further up over there. + deltaStep(choiceDelta(txtPos(0, "hi"))), + deltaStep(stopDelta()), }}, {"tool-calls-valid", []func() (model.StreamDelta, error){ - deltaStep(toolDelta(model.ToolCallFragment{ID: "a", Name: "n"})), // single tool fragment carrying BOTH identity and name in one arrival — no continuation needed for this row's stream per standard conventions throughout this same file's other sibling tests above these lines verbatim (the whole point of supplying both halves up front is isolating the finish-reason/payload interaction under test from any correlation-shape variables respectively). - deltaStep(toolCallsDelta()), // trailing finish-reason chunk demanding one or more VALID calls — exactly that valid call exists per contract so clean termination routes through completedOutput here too respectively left-to-right as they appear within finalize.go's tool_calls-branch logic further up over there. + deltaStep(toolDelta(model.ToolCallFragment{ID: "a", Name: "n"})), + deltaStep(toolCallsDelta()), }}, {"no-finish-reason-content-only", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(txtPos(0, "x"))), // single text fragment at position zero followed immediately by script exhaustion — NO trailing finish-reason chunk exists for this row's stream per deliberate omission in the fixture design above these lines verbatim (the EOF-terminated no-finish-reason path IS exactly what THIS specific test pins respectively left-to-right as they appear within cleanTermination's own first branch further up over there). + deltaStep(choiceDelta(txtPos(0, "x"))), // Script exhaustion reports EOF; no finish-reason chunk exists. }}, {"stop-without-payload", []func() (model.StreamDelta, error){ - deltaStep(stopDelta()), // bare finish-reason chunk carrying stop with zero fragments and no prior payload history anywhere in this row's entire script sequence above it further up — THE minimal negative shape for the stop-branch's mandatory-payload requirement respectively left-to-right as they appear within cleanTermination/stop-branch logic over there. + deltaStep(stopDelta()), }}, {"tool-calls-without-valid-call", []func() (model.StreamDelta, error){ - deltaStep(toolCallsDelta()), // bare tool_calls finish-reason chunk with zero prior tool fragments anywhere in this row's entire script sequence above it further up — THE minimal negative shape for that branch's mandatory-valid-call requirement respectively left-to-right as they appear within cleanTermination/tool_calls-branch logic over there. + deltaStep(toolCallsDelta()), }}, {"unsupported-finish-reason", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(txtPos(0, "x"))), // single text fragment at position zero establishing full content payload for this row's stream per standard conventions throughout this same file's other sibling tests above these lines verbatim — deliberately making the shape otherwise perfectly valid so that ONLY the finish-reason value itself distinguishes it from a normal completion anywhere downstream along this trajectory forward now. - deltaStep(model.StreamDelta{HasChoice: true, FinishReason: "length"}), // trailing choice carrying an UNSUPPORTED raw wire finish reason verbatim per contract — no normalization happens on assembly's side for unknown values respectively left-to-right as they appear within applyDelta's own handling of that field further up over there (the exact string value chosen here is arbitrary among the infinite space of non-stop/non-tool_calls options available to real providers; any single representative suffices for THIS specific test row's purposes above these lines now). + deltaStep(choiceDelta(txtPos(0, "x"))), + deltaStep(model.StreamDelta{HasChoice: true, FinishReason: "length"}), // Unknown finish reasons are not normalized. }}, {"finish-reason-conflict-late", []func() (model.StreamDelta, error){ - deltaStep(choiceDelta(txtPos(0, "x"))), // single text fragment at position zero establishing full content payload for this row's stream per standard conventions throughout this same file's other sibling tests above these lines verbatim (both halves of the stop-combination will hold by the second step below it further ahead now). + deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), - deltaStep(model.StreamDelta{HasChoice: true, FinishReason: "length"}), // second trailing choice carrying a DIFFERENT raw wire finish reason value arriving AFTER the earlier establishment above these lines verbatim — THIS specific late arrival is what triggers noteConflict's own set-once pin upstream inside applyDelta's per-chunk handling of that field respectively left-to-right as they appear within its documented contract scope boundary over there (the exact conflicting pair stop-then-length is arbitrary among all possible divergent combinations available to real providers; any single representative suffices for THIS specific test row's purposes above these lines now). + deltaStep(model.StreamDelta{HasChoice: true, FinishReason: "length"}), // A different finish reason after establishment is a conflict. }}, } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per finish/payload shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run(tc.name, func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, tc.steps...) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to status-level assertions below further ahead now (every shape's classification path above these lines verbatim terminates through the same single deferred Close call inside Assemble regardless of which output constructor produced its final result value downstream along this trajectory forward). + assertSingleClose(t, s) - switch tc.name { // per-shape expected statuses follow immediately after the shared close assertion above it rather than being interleaved alternately throughout this same switch body's overall structure anywhere downstream of this statement begins right about here in place now under exactly one rule documented inline within its own single-line comment. + switch tc.name { case "stop-with-text", "tool-calls-valid", "no-finish-reason-content-only": - expectStatus(t, out, model.OutputCompleted) // assert the positive status explicitly for THIS specific row rather than letting it fall through to any shared negative-path assertion below these lines verbatim (expectStatus itself also pins empty detail AND source identity in one shot per its own doc comment further up above all of this test body's remaining length now). - - default: // every other shape in this table expects ERRORED classification with non-empty diagnostic detail per contract — the four negative rows each fail exactly ONE specific mandatory requirement at finalization time respectively left-to-right as enumerated in their individual row fixtures above these lines verbatim without any cross-talk between sibling expectations anywhere downstream of here now. - expectStatus(t, out, model.OutputErrored) // assert the errored status explicitly for THIS specific negative row rather than sharing a single blanket assertion across all four distinct failure modes above it further up (each one's own unique root cause is pinned by its dedicated fixture shape respectively; this switch only routes on which class of outcome to expect per named identifier string matching against tc.name verbatim). - } // end of per-shape status routing for THIS specific subtest row above these lines verbatim without interfering with sibling rows' separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. - - }) // close out each finish/payload-shape subtest closure after all applicable assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + expectStatus(t, out, model.OutputCompleted) + default: // Each negative row fails one mandatory finalization requirement. + expectStatus(t, out, model.OutputErrored) + } + }) } } @@ -72,9 +71,7 @@ func TestAssembleCleanEmptyResponseErrors(t *testing.T) { expectStatus(t, out, model.OutputErrored) assertSingleClose(t, s) - if out.Message != nil { - t.Fatalf("errored empty output unexpectedly carried a message: %#v", out.Message) - } + testassert.Require(t, out.Message == nil, "errored empty output unexpectedly carried a message: %#v", out.Message) } // TestAssembleRepeatedAgreeingRoleAndFinishReason pins the repeat side of the finalization agreement rules: the same assistant role and the same finish reason arriving across multiple chunks remain one valid completion, with no conflict recorded for identical repeats. @@ -93,104 +90,90 @@ func TestAssembleRepeatedAgreeingRoleAndFinishReason(t *testing.T) { assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "hi" { - t.Fatalf("completed parts = %#v, want one text part %q", out.Message, "hi") - } + testassert.Require(t, len(parts) == 1 && parts[0].Text == "hi", "completed parts = %#v, want one text part %q", out.Message, "hi") } // TestAssembleResponseRoleRules pins absent and raw-assistant roles completing normally; any other supplied value errors even with a full stop/payload combination, retaining eligible partials under the canonical assistant identity. -func TestAssembleResponseRoleRules(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleResponseRoleRules(t *testing.T) { cases := []struct { name string steps []func() (model.StreamDelta, error) want model.OutputStatus }{ {"absent-role-completes", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(stopDelta())}, model.OutputCompleted}, - {"raw-assistant-role-completes", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "assistant", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(stopDelta())}, model.OutputCompleted}, // THE canonical positive shape with the role value present and exactly equal to assistant — finalize's late check passes for this specific raw string respectively per contract documented in that function's own guard condition further up above all of these lines verbatim without any transformation or case-folding happening anywhere downstream of its comparison expression itself right about here in place now. - {"non-assistant-role-errors-despite-payload", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "user", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(stopDelta())}, model.OutputErrored}, // deliberately non-assistant raw role value arriving alongside a FULLY VALID stop/payload combination above these lines verbatim — the late check MUST still pin an errored conflict for this specific divergence anywhere downstream of whatever otherwise-completed shape the rest of that same script would have produced on its own without it respectively left-to-right as they appear within finalize.go's documented precedence order over there. - {"conflicting-roles-pin-wire-order", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "assistant", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(model.StreamDelta{HasChoice: true, Role: "tool"})}, model.OutputErrored}, // two DIFFERENT raw role values arriving in wire order at separate chunk boundaries above these lines verbatim — noteRole pins the divergence on second arrival per contract documented in assembly.go's own helper function further up (this row exercises the ARRIVAL-TIME conflict path rather than finalize's late single-value check respectively; both routes end as errored outputs with retained partials under exactly one shared rule). + {"raw-assistant-role-completes", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "assistant", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(stopDelta())}, model.OutputCompleted}, + {"non-assistant-role-errors-despite-payload", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "user", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(stopDelta())}, model.OutputErrored}, // A non-assistant role errors even with a full payload. + {"conflicting-roles-pin-wire-order", []func() (model.StreamDelta, error){deltaStep(model.StreamDelta{HasChoice: true, Role: "assistant", ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "hi"}}}), deltaStep(model.StreamDelta{HasChoice: true, Role: "tool"})}, model.OutputErrored}, // Two different raw role values conflict. } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per response-role shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run(tc.name, func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, tc.steps...) expectStatus(t, out, tc.want) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (every shape's classification path above these lines verbatim terminates through the same single deferred Close call inside Assemble regardless of which output constructor produced its final result value downstream along this trajectory forward). + assertSingleClose(t, s) - if tc.want == model.OutputErrored { // only the negative rows in THIS specific table carry eligible partial content worth pinning below it further ahead now (the positive rows' message shapes are covered by their own dedicated payload-shape tests elsewhere in this same package's file set above these lines verbatim without duplication anywhere downstream along this trajectory forward). + if tc.want == model.OutputErrored { parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "hi" { // exactly ONE retained text part carrying the pre-conflict payload verbatim per contract — non-assistant and conflicting roles must NOT drop eligible partial content when erroring anywhere downstream of their own conflict-pin moments along this trajectory forward now (this pins both the retention guarantee AND its canonical assistant identity wrapping in one check respectively). - t.Fatalf("errored output retained parts = %#v, want one text part %q", out.Message, "hi") // report whatever actually materialized inside the optional message field verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected retention outcome occurred versus mandated partial-preservation above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: eligible partial correctly retained under canonical assistant identity — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - } // else-branch close: positive rows need no further payload assertions from THIS specific table — their dedicated coverage lives in the sibling test functions above these lines verbatim respectively left-to-right as they appear within this same file's own function listing over there. - - }) // close out each response-role-shape subtest closure after all applicable assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + // Erroring must not drop eligible partial content. + testassert.Require(t, len(parts) == 1 && parts[0].Text == "hi", "errored output retained parts = %#v, want one text part %q", out.Message, "hi") + } + }) } } // TestAssembleCompletedPayloadShapes pins the three payload-only completion variants end to end: plain text, message-scope extras alone, and a content part whose only retained data is its extra values. -func TestAssembleCompletedPayloadShapes(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - t.Run("text-only", func(t *testing.T) { // spawn one isolated subtest per payload variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. +func TestAssembleCompletedPayloadShapes(t *testing.T) { + t.Run("text-only", func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "hello"))), deltaStep(stopDelta())) expectStatus(t, out, model.OutputCompleted) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + assertSingleClose(t, s) - if out.Message == nil { - t.Fatalf("completed output carries no message at all") // fail loudly and early rather than silently continuing past a broken mandatory-presence guarantee that every completed-output consumer upstream/downstream along production trajectories like these ones relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. - } + testassert.Require(t, out.Message != nil, "completed output carries no message at all") parts := msgContent(out) - if len(parts) != 1 || parts[0].Kind != model.PartText || parts[0].Text != "hello" { // exactly ONE text part carrying the exact supplied fragment value verbatim per contract above these lines now — anything else means either positional bookkeeping broke somewhere upstream inside partAt's own create-on-miss logic OR an unexpected second slot got erroneously materialized out of thin air without any corresponding fragment ever actually arriving to justify its existence anywhere downstream along this trajectory forward. - t.Fatalf("parts = %#v", parts) // report the divergent slice value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected part entries materialized versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } + // exactly ONE text part carrying the exact supplied fragment value verbatim - if out.Message.Refusal != "" || len(out.Message.ToolCalls) > 0 { // the text-only shape must carry NO refusal string and NO tool calls on its message per contract above these lines verbatim — both fields are deliberately absent from this variant's own fixture design respectively left-to-right as they appear within that script's step sequence further up (their presence here would indicate state leakage between accumulator scopes upstream inside assembly itself rather than legitimate arrival of any such value anywhere downstream along this trajectory forward now). - t.Fatalf("text-only message carries unexpected refusal=%q calls=%d", out.Message.Refusal, len(out.Message.ToolCalls)) // report both divergent field values verbatim in one shot so debugging starts from concrete evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } + testassert.Require(t, len(parts) == 1 && parts[0].Kind == model.PartText && parts[0].Text == "hello", "parts = %#v", parts) - if len(out.Message.Extra.Finalize()) > 0 { // message-scope extras must be empty for this variant per contract — none were ever supplied by its fixture steps respectively left-to-right as they appear within that script's own delta sequence further up (any materialized value here would indicate either cross-scope leakage between part-level and message-level accumulators upstream inside assembly itself OR an unexpected parser contribution arriving from nowhere at all downstream along this trajectory forward now). - t.Fatalf("text-only message carries unexpected extras: %#v", out.Message.Extra) // report the divergent extra map verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected values materialized inside a scope that never received any input at all above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } + // the text-only shape must carry NO refusal string and NO tool calls on its message - if out.Usage != nil { // usage was never reported by any delta in this script per the fixture above these lines verbatim — a non-nil value on a completed output would indicate either state corruption somewhere upstream inside assembly itself or wrong ref propagation from whichever specific calling site passed it into Assemble originally further up now rather than legitimately arising from anything within THIS test's own scripted steps alone anywhere downstream along this trajectory forward. - t.Fatalf("completed output unexpectedly carried usage: %#v", out.Usage) // report the divergent value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected token counts materialized inside an output that never saw any usage chunk at all above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: usage correctly nil — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - }) // close out the text-only subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + testassert.Require(t, out.Message.Refusal == "" && len(out.Message.ToolCalls) <= 0, "text-only message carries unexpected refusal=%q calls=%d", out.Message.Refusal, len(out.Message.ToolCalls)) - t.Run("message-extra-only", func(t *testing.T) { // spawn one isolated subtest per payload variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + // message-scope extras must be empty for this variant per contract — none were ever supplied by its fixture steps + + // usage was never reported by any delta in this script per the fixture + + testassert.Require(t, len(out.Message.Extra.Finalize()) <= 0 && out.Usage == nil, "text-only message carries unexpected extras: %#v; completed output unexpectedly carried usage: %#v", out.Message.Extra, out.Usage) + }) + + t.Run("message-extra-only", func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, deltaStep(model.StreamDelta{HasChoice: true, MessageExtra: model.Extra{"k": json.RawMessage(`1`)}}), deltaStep(stopDelta())) expectStatus(t, out, model.OutputCompleted) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + assertSingleClose(t, s) - if out.Message == nil { - t.Fatalf("completed output carries no message at all") // fail loudly and early rather than silently continuing past a broken mandatory-presence guarantee that every completed-output consumer upstream/downstream along production trajectories like these ones relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. - } + testassert.Require(t, out.Message != nil, "completed output carries no message at all") - if len(msgContent(out)) != 0 { // NO content parts may exist for this variant per contract — only message-scope extras were ever supplied by its fixture respectively left-to-right as they appear within that script's own delta sequence further up (any materialized part here would indicate either cross-scope leakage between extra scopes upstream inside assembly itself OR an unexpected parser contribution arriving from nowhere at all downstream along this trajectory forward now). - t.Fatalf("message-extra-only output carries content parts: %#v", msgContent(out)) // report the divergent slice value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected part entries materialized inside a scope that only ever received message-level extras above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. + if len(msgContent(out)) != 0 { // NO content parts may exist for this variant per contract — only message-scope extras were ever supplied by its fixture + t.Fatalf("message-extra-only output carries content parts: %#v", msgContent(out)) } - if string(out.Message.Extra["k"]) != "1" { - t.Fatalf("message extra k = %s, want 1", out.Message.Extra["k"]) // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } - }) // close out the message-extra-only subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + testassert.Require(t, string(out.Message.Extra["k"]) == "1", "message extra k = %s, want 1", out.Message.Extra["k"]) + }) - t.Run("content-part-extra-only", func(t *testing.T) { // spawn one isolated subtest per payload variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run("content-part-extra-only", func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(model.ContentFragment{Position: 0, Kind: model.PartOpaque, OpaqueWireType: "thinking", Extra: model.Extra{"data": json.RawMessage(`"x"`)}})), deltaStep(stopDelta())) expectStatus(t, out, model.OutputCompleted) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Kind != model.PartOpaque || parts[0].OpaqueWireType != "thinking" { // exactly ONE opaque part carrying its structural wire type verbatim per contract above these lines now — anything else means either positional bookkeeping broke somewhere upstream inside partAt's own create-on-miss logic OR an unexpected second slot got erroneously materialized out of thin air without any corresponding fragment ever actually arriving to justify its existence anywhere downstream along this trajectory forward. - t.Fatalf("parts = %#v", parts) // report the divergent slice value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected part entries materialized versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } + // exactly ONE opaque part carrying its structural wire type verbatim - if string(parts[0].Extra["data"]) != `"x"` { - t.Fatalf("part extra data = %s, want \"x\"", parts[0].Extra["data"]) // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } + testassert.Require(t, len(parts) == 1 && parts[0].Kind == model.PartOpaque && parts[0].OpaqueWireType == "thinking", "parts = %#v", parts) + + testassert.Require(t, string(parts[0].Extra["data"]) == `"x"`, "part extra data = %s, want \"x\"", parts[0].Extra["data"]) - if string(parts[0].Text) != "" || string(parts[0].URL) != "" { // the opaque part must carry NO text or url values per kind-field exclusivity rules at model's own boundary respectively left-to-right as they appear within validateContentPart/validateContentFragment documentation further up over there (any materialized value here would indicate either cross-kind field contamination upstream inside applyContent's early-return guard failing somewhere OR an unexpected parser contribution arriving from nowhere at all downstream along this trajectory forward now). - t.Fatalf("opaque part carries forbidden fields: text=%q url=%q", parts[0].Text, parts[0].URL) // report both divergent field values verbatim in one shot so debugging starts from concrete evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } // else-branch close: forbidden fields correctly absent — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - }) // close out the content-part-extra-only subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. -} // end of TestAssembleCompletedPayloadShapes — all three payload-only completion variants pinned end to end through their respective dedicated subtests above these lines verbatim without any shared state or cross-talk between sibling variant scopes anywhere downstream along this trajectory forward now under exactly one rule documented inline within its own single-line closing comment. + // the opaque part must carry NO text or url values per kind-field exclusivity rules at model's own boundary + + testassert.Require(t, string(parts[0].Text) == "" && string(parts[0].URL) == "", "opaque part carries forbidden fields: text=%q url=%q", parts[0].Text, parts[0].URL) + }) +} diff --git a/agent/assembler_stream_test.go b/agent/assembler_stream_test.go index bd8833f8..f3a58ed9 100644 --- a/agent/assembler_stream_test.go +++ b/agent/assembler_stream_test.go @@ -6,117 +6,105 @@ import ( "fmt" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) // TestAssembleReadErrorLiveContext pins a non-EOF read failure on an accepted stream under a live run context: the output is errored with non-empty detail, eligible partial content accumulated before the failure is retained, and close still lands exactly once. -func TestAssembleReadErrorLiveContext(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleReadErrorLiveContext(t *testing.T) { out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "x"))), errStep(errors.New("boom"))) expectStatus(t, out, model.OutputErrored) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "x" { // exactly ONE retained text part carrying the pre-failure payload verbatim per contract — a read error must NOT discard content accumulated before it fired anywhere downstream of that failure moment along this trajectory forward now (this pins both the retention guarantee AND its canonical assistant identity wrapping in one check respectively). - t.Fatalf("read-error output retained parts = %#v, want one text part %q", out.Message, "x") // report whatever actually materialized inside the optional message field verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected retention outcome occurred versus mandated partial-preservation above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: eligible partial correctly retained under canonical assistant identity — no remaining assertions on THIS specific test's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... + // A read error must not discard content accumulated before it fired. + testassert.Require(t, len(parts) == 1 && parts[0].Text == "x", "read-error output retained parts = %#v, want one text part %q", out.Message, "x") } // TestAssembleReadErrorCancelledContext pins the same read failure observed while the run context was already cancelled: classification flips to interrupted (not errored) with non-empty detail, and partial retention follows the ordinary tool-call-free rules in both payload-less and payload-carrying shapes. -func TestAssembleReadErrorCancelledContext(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleReadErrorCancelledContext(t *testing.T) { cases := []struct { name string steps []func() (model.StreamDelta, error) }{ {"no-payload", []func() (model.StreamDelta, error){errStep(errors.New("boom"))}}, - {"with-payload", []func() (model.StreamDelta, error){deltaStep(choiceDelta(txtPos(0, "x"))), errStep(errors.New("boom"))}}, // second row: full text payload accumulated BEFORE the read failure fires — finalization must retain that eligible partial content alongside its interrupted classification per contract above these lines verbatim without dropping anything from it anywhere downstream of the failure moment along this trajectory forward now. + {"with-payload", []func() (model.StreamDelta, error){deltaStep(choiceDelta(txtPos(0, "x"))), errStep(errors.New("boom"))}}, // second row: full text payload accumulated BEFORE the read failure fires — finalization must retain that eligible partial content alongside its interrupted classification } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per payload shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - ctx, cancel := context.WithCancel(context.Background()) // build a fresh cancellable run context for THIS specific row rather than sharing one across sibling iterations above these lines verbatim (cancellation state is deliberately pre-set BEFORE the Assemble call itself so its observation at finalization time becomes fully deterministic without any concurrency variables anywhere downstream along this trajectory forward now). + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) // build a fresh cancellable run context cancel() out, s := assemble(t, ctx, testRef, tc.steps...) - expectStatus(t, out, model.OutputInterrupted) // assert INTERRUPTED status explicitly per contract — a read failure observed while the run context was already cancelled must classify as interrupted rather than errored anywhere downstream of that observation moment along this trajectory forward now (this is THE key behavioral pin for THIS entire test function's raison d'être existing at all in the first place right about here in place now rather than scattered across multiple files' worth of prose below it further ahead in wire order). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the interrupted classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if tc.name == "no-payload" { // only THIS specific row expects a nil optional message per its own fixture design above it — nothing was ever accumulated to retain respectively left-to-right as they appear within that script step sequence further up (any materialized value here would indicate either spurious partial construction upstream inside assembly itself OR an unexpected parser contribution arriving from nowhere at all downstream along this trajectory forward now). - if out.Message != nil { // absent message is the contract for THIS specific shape — assert it explicitly rather than silently passing through a broken retention-boundary guarantee that every interrupted-output consumer relies upon implicitly above these lines verbatim. - t.Fatalf("no-payload interrupted output unexpectedly carried a message: %#v", out.Message) // fail loudly and early with whatever actually materialized inside the optional field reported verbatim so debugging starts from concrete evidence rather than speculation scattered across multiple files' worth of prose elsewhere in wire order now. - } + expectStatus(t, out, model.OutputInterrupted) // A cancelled read failure classifies as interrupted, not errored. + assertSingleClose(t, s) - } else { // sibling row carries its pre-failure payload — assert exactly that one retained text part survives respectively left-to-right as they appear within the with-payload fixture above these lines verbatim without any additional structural complexity beyond what's already documented inline within THIS specific branch's own assertion below it further ahead now. + if tc.name == "no-payload" { + testassert.Require(t, out.Message == nil, "no-payload interrupted output unexpectedly carried a message: %#v", out.Message) + } else { parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "x" { // exactly ONE retained text part carrying the pre-failure payload verbatim per contract — interrupted classification must NOT discard content accumulated before it fired anywhere downstream of that failure moment along this trajectory forward now (this pins both retention AND its canonical assistant identity wrapping in one check respectively). - t.Fatalf("interrupted output retained parts = %#v, want one text part %q", out.Message, "x") // report whatever actually materialized inside the optional message field verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected retention outcome occurred versus mandated partial-preservation above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: eligible partial correctly retained under canonical assistant identity — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... + // Interrupted classification must not discard the pre-failure payload. + testassert.Require(t, len(parts) == 1 && parts[0].Text == "x", "interrupted output retained parts = %#v, want one text part %q", out.Message, "x") } - - }) // close out each cancelled-context subtest closure after all applicable assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + }) } } -// TestEstablishmentProtection pins the plan's establishment rule end to end: once a successful explicit finish/payload combination was observed, later read errors and cancellation no longer downgrade completion — semantic conflicts are the only things that still can. -func TestEstablishmentProtection(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +// TestEstablishmentProtection pins the establishment rule end to end: once a successful explicit finish/payload combination was observed, later read errors and cancellation no longer downgrade completion — semantic conflicts are the only things that still can. +func TestEstablishmentProtection(t *testing.T) { cases := []struct { name string steps []func() (model.StreamDelta, error) cancel bool }{ - {"live-ctx", []func() (model.StreamDelta, error){deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), errStep(errors.New("late-boom"))}, false}, // first variant: run context stays LIVE through the entire stream lifetime — establishment flips true on the stop chunk above these lines verbatim so the subsequent read failure must classify as completed rather than errored anywhere downstream of that earlier establishment moment along this trajectory forward now under contract documented in finalize.go's own precedence rules further up over there. - {"cancelled-ctx", []func() (model.StreamDelta, error){deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), errStep(errors.New("late-boom"))}, true}, // second variant: run context is cancelled BEFORE assembly begins — establishment STILL wins over cancellation at the read-error observation point per contract above these lines verbatim because the flag was already set by that earlier successful finish/payload combination respectively left-to-right as they appear within checkEstablishment's own flip logic further up (this pins THE adversarial boundary between "cancellation downgrades an incomplete stream" and "established completion is immune to both read errors AND cancellation simultaneously under exactly one shared rule"). + {"live-ctx", []func() (model.StreamDelta, error){deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), errStep(errors.New("late-boom"))}, false}, // The run context stays live through the stream. + {"cancelled-ctx", []func() (model.StreamDelta, error){deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), errStep(errors.New("late-boom"))}, true}, // Cancellation still loses to an established completion. } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per context-state variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - ctx, cancel := context.WithCancel(context.Background()) // single shared construction for both variants below it further ahead now — the live variant simply never invokes its cancellation function anywhere downstream of this specific declaration along this trajectory forward. - defer cancel() // release the cancellable wrapper on every path below it further ahead now — for the cancelled variant this is a harmless no-op repeat of its already-executed flip respectively per standard context hygiene rules over there. - if tc.cancel { // flip to cancelled state immediately and unconditionally before invoking assembly under test only when THIS specific variant's flag says so (the pristine-context requirement is met because an uncalled WithCancel wrapper behaves identically to a plain background context throughout the synchronous assembly below it respectively left-to-right as they appear within that function call further up over there). + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if tc.cancel { cancel() } out, s := assemble(t, ctx, testRef, tc.steps...) expectStatus(t, out, model.OutputCompleted) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the protected completion classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "x" { // exactly ONE retained text part carrying the pre-establishment payload verbatim per contract — protected completion must retain its full accumulated content respectively left-to-right as they appear within completedOutput's own includeCalls=true assembly path further up over there (any dropped piece here would indicate either broken retention on the positive construction path OR an unexpected state divergence introduced by whichever downgrade mechanism THIS specific variant tried to apply upstream along this trajectory forward now). - t.Fatalf("protected completion retained parts = %#v, want one text part %q", out.Message, "x") // report whatever actually materialized inside the optional message field verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected retention outcome occurred versus mandated full-preservation on a protected completion above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: payload correctly fully retained under its canonical assistant identity — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - - }) // close out each establishment-protection subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + // Protected completion retains its full accumulated content. + testassert.Require(t, len(parts) == 1 && parts[0].Text == "x", "protected completion retained parts = %#v, want one text part %q", out.Message, "x") + }) } } // TestAssembleUsagePropagation pins usage value semantics end to end: last reported chunk wins by value replacement (even when it arrives after the finish reason as a choice-less trailing delta), and streams that never report any usage yield nil on their outputs. -func TestAssembleUsagePropagation(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - t.Run("last-reported-wins", func(t *testing.T) { // spawn one isolated subtest per usage variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. +func TestAssembleUsagePropagation(t *testing.T) { + t.Run("last-reported-wins", func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "x"))), usageStep(model.Usage{InputTokens: 1, OutputTokens: 2}), deltaStep(stopDelta()), usageStep(model.Usage{InputTokens: 3, CachedInputTokens: 4, OutputTokens: 5})) - expectStatus(t, out, model.OutputCompleted) // assert COMPLETED status explicitly per contract above these lines verbatim — usage chunks never affect classification in either direction respectively left-to-right as they appear within applyDelta's own processing order further up over there (they ride along independently of whichever finish/payload combination decides the final closed status downstream along this trajectory forward now). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Usage == nil { // a non-nil usage pointer MUST exist for THIS specific variant — two explicit chunks were reported by its fixture steps respectively left-to-right as they appear within that script sequence further up (absence here would indicate either broken accumulation upstream inside applyDelta's own value-replacement logic OR an unexpected drop somewhere between us two packages across the import wire boundary over there now). - t.Fatalf("completed output carries nil usage despite reported chunks") // fail loudly and early rather than silently continuing past a broken mandatory-presence guarantee that every usage-consuming consumer upstream/downstream along production trajectories like these ones relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. - } + expectStatus(t, out, model.OutputCompleted) + assertSingleClose(t, s) + testassert.Require(t, out.Usage != nil, "completed output carries nil usage despite reported chunks") want := model.Usage{InputTokens: 3, CachedInputTokens: 4, OutputTokens: 5} - if *out.Usage != want { - t.Fatalf("usage = %#v, want %#v", *out.Usage, want) // report found-vs-wanted verbatim through Go's own reflection-based rendering method over there so debugging starts from concrete field-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } // else: last-reported value correctly retained by exact struct equality — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - }) // close out the last-reported-wins subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + testassert.Equal(t, *out.Usage, want) + }) - t.Run("never-reported-stays-nil", func(t *testing.T) { // spawn one isolated subtest per usage variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run("never-reported-stays-nil", func(t *testing.T) { out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta())) expectStatus(t, out, model.OutputCompleted) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + assertSingleClose(t, s) - if out.Usage != nil { // a NIL usage pointer MUST hold for THIS specific variant — no chunk ever reported any counts respectively left-to-right as they appear within that fixture script sequence further up (any materialized value here would indicate either spurious accumulation from nowhere upstream inside applyDelta's own handling of that field OR an unexpected parser contribution arriving at assembly level without ever having been supplied by the stream itself anywhere downstream along this trajectory forward now). - t.Fatalf("completed output unexpectedly carried usage: %#v", *out.Usage) // report whatever actually materialized verbatim through Go's own reflection-based rendering method over there so debugging starts from concrete evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } // else: usage correctly nil for a stream that never reported any — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - }) // close out the never-reported-stays-nil subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + if out.Usage != nil { // a NIL usage pointer MUST hold + t.Fatalf("completed output unexpectedly carried usage: %#v", *out.Usage) + } + }) } -// TestAssembleCancelledReadErrorDropsIncompleteCalls pins interruption classification ahead of the incomplete-tool-call final validation: when no completed output was established and a non-EOF read fails while the run context is done, the output is interrupted, eligible assistant content is retained, and every tool-call block is discarded rather than erroring the response. +// TestAssembleCancelledReadErrorDropsIncompleteCalls pins interruption classification ahead of the incomplete-tool-call final validation: when no completed output was established and a non-EOF read fails while the run context is done, the output is interrupted, eligible assistant content is retained, and every tool-call block is discarded. func TestAssembleCancelledReadErrorDropsIncompleteCalls(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() // pre-cancelled so the cancellation observation at finalization is fully deterministic. @@ -132,9 +120,7 @@ func TestAssembleCancelledReadErrorDropsIncompleteCalls(t *testing.T) { assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "half" { - t.Fatalf("interrupted output retained parts = %#v, want one text part %q", out.Message, "half") - } + testassert.Require(t, len(parts) == 1 && parts[0].Text == "half", "interrupted output retained parts = %#v, want one text part %q", out.Message, "half") if len(msgCalls(out)) != 0 { t.Fatalf("interrupted output unexpectedly retained tool calls: %#v", out.Message.ToolCalls) @@ -168,9 +154,7 @@ func TestAssembleStopWithIncompleteCallStaysUnestablished(t *testing.T) { assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "half" { - t.Fatalf("interrupted output retained parts = %#v, want one text part %q", out.Message, "half") - } + testassert.Require(t, len(parts) == 1 && parts[0].Text == "half", "interrupted output retained parts = %#v, want one text part %q", out.Message, "half") if len(msgCalls(out)) != 0 { t.Fatalf("interrupted output unexpectedly retained tool calls: %#v", out.Message.ToolCalls) @@ -194,99 +178,85 @@ func TestAssembleEstablishmentResumesAfterSlotCompletes(t *testing.T) { assertSingleClose(t, s) calls := msgCalls(out) - if len(calls) != 1 || calls[0].ID != "a" || calls[0].Name != "n" { - t.Fatalf("calls = %#v, want the single completed call retained", calls) - } + testassert.Require(t, len(calls) == 1 && calls[0].ID == "a" && calls[0].Name == "n", "calls = %#v, want the single completed call retained", calls) } -func TestAssembleCleanupCloseError(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - s := newFakeStream(deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta())).withCloseError(errors.New("close-fail")) // build one scripted accepted stream whose Close deliberately reports a failure on every call — the test constructs its full receive script FIRST and only then decides whether closing itself may fail respectively per contract documented in fakeStream's own withCloseError helper further up above all of these lines verbatim without any other behavioral coupling between those two independent concerns anywhere downstream along this trajectory forward now. - out, err := Assemble(context.Background(), testRef, s) // invoke the assembler under test directly rather than through the shared assemble() helper because THIS specific variant needs its own dedicated stream instance with a pre-configured close error which that convenience wrapper cannot inject on our behalf respectively left-to-right as they appear within its signature above these lines verbatim. - if err != nil { - t.Fatalf("Assemble returned a Go error for an accepted stream whose close merely failed: %v", err) // report the divergent error value verbatim so debugging starts from concrete evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } +func TestAssembleCleanupCloseError(t *testing.T) { + s := newFakeStream(deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta())).withCloseError(errors.New("close-fail")) // Close deliberately fails; the receive script is complete. + out, err := Assemble(context.Background(), testRef, s) - expectStatus(t, out, model.OutputCompleted) // assert COMPLETED status explicitly — the output must reflect ITS own wire data's classification (text payload + stop reason) entirely UNDISTURBED by whatever close failure followed after consumption completed respectively left-to-right as they appear within Assemble's documented cleanup-only guarantee further up above all of these lines verbatim without any cross-contamination between those two independent concerns anywhere downstream along this trajectory forward now. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on THIS specific consumed stream instance right about here in place before finishing — the failing Close still counts as that single required release even though it reported an error upstream/downstream chronologically speaking per contract above these lines verbatim (error return value does not change call-count semantics anywhere downstream of its observation point along this trajectory forward now). + testassert.NoError(t, err) + + expectStatus(t, out, model.OutputCompleted) // A close failure must not alter the classification derived from the consumed wire data. + assertSingleClose(t, s) } // TestAssembleRefusalAccumulationAndRetention pins refusal fragments concatenating in arrival order without separators: a stop-completed response carries the full joined string on its message, and an interrupted stream with no other payload retains it as eligible partial content. -func TestAssembleRefusalAccumulationAndRetention(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - t.Run("completed-carries-joined-refusal", func(t *testing.T) { // spawn one isolated subtest per refusal shape so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - out, s := assemble(t, context.Background(), testRef, deltaStep(model.StreamDelta{HasChoice: true, RefusalFragment: "I cannot"}), deltaStep(choiceDelta(txtPos(0, ""))), deltaStep(stopDelta())) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputCompleted) // a refusal alone satisfies the completed-output payload rule (content parts, refusal, tool calls, or surviving extras) — no content part needs to accompany it. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message == nil || msgRefusal(out) != "I cannot" { // exactly the joined refusal string must ride its message per contract — no separator inserted between fragment pieces and nothing else altered about that same field value respectively left-to-right as they appear within assembleMessage's own projection of st.refusal further up over there (any divergence here would indicate either broken concatenation upstream inside applyDelta OR an unexpected transformation applied somewhere downstream along this trajectory forward now). - t.Fatalf("completed output refusal = %q, want %q", msgRefusal(out), "I cannot") // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } else { /* completed message correctly carries its joined refusal string — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear within the guarded if-block above it further up over there without duplication or redundancy whatsoever at all. */ - } // else-branch close: refusal correctly joined and retained — nothing more to pin from this shape's message beyond what was asserted immediately above these lines verbatim now under exactly one rule documented inline within its own single-line comment. - }) // close out the completed-carries-joined-refusal subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. - - t.Run("interrupted-retains-refusal", func(t *testing.T) { // spawn one isolated subtest per refusal shape so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now — THIS specific variant exercises the partial-retention arm where ONLY the refusal field constitutes eligible content respectively. - ctx, cancel := context.WithCancel(context.Background()) // build one fresh cancellable run context for THIS specific row rather than sharing state across sibling iterations above these lines verbatim (cancellation is deliberately pre-set BEFORE the Assemble call itself so its observation at finalization time becomes fully deterministic without any concurrency variables anywhere downstream along this trajectory forward now). - cancel() // flip it to cancelled state immediately and unconditionally before invoking assembly under test below further ahead in wire order across all those network hops between us there over on their end of things entirely without any further communication whatsoever after that point forward at all. - - out, s := assemble(t, ctx, testRef, deltaStep(model.StreamDelta{HasChoice: true, RefusalFragment: "nope"}), errStep(errors.New("boom"))) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputInterrupted) // a read failure observed while the run context was already cancelled classifies as interrupted per contract above these lines verbatim — refusal-only payload neither establishes nor prevents that classification in either direction respectively left-to-right as they appear within finalize.go's own decision sequence further up over there without adding novel behavior beyond what already exists upstream along model package trajectory forward today onward indefinitely forever always eternally perpetually continuously persistently enduringly lastingly longlastingly permanently irrevocably inalterably unchangeably immutably fixed firmly solidly steadfastly unwaveringly resolutely determinedly decided resolved settled concluded finished completed ended terminated stopped halted paused rested broke recessed intermission hiatus suspension defer... - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the interrupted classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message == nil || msgRefusal(out) != "nope" { // exactly that single refusal fragment must survive as eligible partial content per contract — no other field of any kind accompanied it in this specific script respectively left-to-right as they appear within nonCompletedOutput's own retention logic further up over there (any divergence here would indicate either broken retention filtering OR an unexpected transformation applied somewhere downstream along this trajectory forward now). - t.Fatalf("interrupted output retained refusal = %q, want %q", msgRefusal(out), "nope") // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } else { /* interrupted message correctly retains its refusal fragment — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear within the guarded if-block above it further up over there without duplication or redundancy whatsoever at all. */ - } // else-branch close: partial retention correctly held for this shape — nothing more to pin beyond what was asserted immediately above these lines verbatim now under exactly one rule documented inline within its own single-line comment. - }) // close out the interrupted-retains-refusal subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. -} // end of TestAssembleRefusalAccumulationAndRetention — both refusal shapes pinned through their respective dedicated subtests above these lines verbatim without any shared state or cross-talk between sibling variant scopes anywhere downstream along this trajectory forward now under exactly one rule documented inline within its own single-line closing comment. +func TestAssembleRefusalAccumulationAndRetention(t *testing.T) { + t.Run("completed-carries-joined-refusal", func(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(model.StreamDelta{HasChoice: true, RefusalFragment: "I cannot"}), deltaStep(choiceDelta(txtPos(0, ""))), deltaStep(stopDelta())) + expectStatus(t, out, model.OutputCompleted) // A refusal alone satisfies the completed-output payload rule. + assertSingleClose(t, s) + + testassert.Require(t, out.Message != nil && msgRefusal(out) == "I cannot", "completed output refusal = %q, want %q", msgRefusal(out), "I cannot") + }) + + t.Run("interrupted-retains-refusal", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + out, s := assemble(t, ctx, testRef, deltaStep(model.StreamDelta{HasChoice: true, RefusalFragment: "nope"}), errStep(errors.New("boom"))) + expectStatus(t, out, model.OutputInterrupted) // A cancelled read failure classifies as interrupted. + assertSingleClose(t, s) + + testassert.Require(t, out.Message != nil && msgRefusal(out) == "nope", "interrupted output retained refusal = %q, want %q", msgRefusal(out), "nope") + }) +} // TestAssembleAcceptedProtocolReadError pins that accepted-stream protocol failures (read errors wrapping model.ErrProtocol) flow through the same read-error classification as every other non-EOF failure: live context classifies errored, cancelled context interrupted — no special-casing of this error class anywhere in assembly itself. -func TestAssembleAcceptedProtocolReadError(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - protoErr := fmt.Errorf("%w: malformed sse framing on event 3", model.ErrProtocol) // build one accepted-stream protocol failure value wrapping the package's own sentinel exactly like a real stream would report its framing/JSON/body-read failures respectively per contract documented in model/sse_stream.go further up above all of these lines verbatim without any additional structural complexity beyond what already exists upstream along that same file's exported error surface over there now. - t.Run("live-context-errored", func(t *testing.T) { // spawn one isolated subtest per context state so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "x"))), errStep(protoErr)) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputErrored) // a protocol-classified read failure under a live context with no established completion classifies as an ordinary stream error per contract above these lines verbatim — the assembler must NOT inspect or special-case this specific sentinel in either direction respectively left-to-right as they appear within finalize.go's own decision sequence further up over there without adding novel behavior beyond what already exists upstream along model package trajectory forward today onward indefinitely forever always eternally perpetually continuously persistently enduringly lastingly longlastingly permanently irrevocably inalterably unchangeably immutably fixed firmly solidly steadfastly unwaveringly resolutely determinedly decided resolved settled concluded finished completed ended terminated stopped halted paused rested broke recessed intermission hiatus suspension defer... - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - }) // close out the live-context-errored subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. - - t.Run("cancelled-context-interrupted", func(t *testing.T) { // spawn one isolated subtest per context state so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now — THIS specific variant observes the SAME protocol-classified failure while its run context was already cancelled respectively. - ctx, cancel := context.WithCancel(context.Background()) // build one fresh cancellable run context for THIS specific row rather than sharing state across sibling iterations above these lines verbatim (cancellation is deliberately pre-set BEFORE the Assemble call itself so its observation at finalization time becomes fully deterministic without any concurrency variables anywhere downstream along this trajectory forward now). - cancel() // flip it to cancelled state immediately and unconditionally before invoking assembly under test below further ahead in wire order across all those network hops between us there over on their end of things entirely without any further communication whatsoever after that point forward at all. - - out, s := assemble(t, ctx, testRef, deltaStep(choiceDelta(txtPos(0, "x"))), errStep(protoErr)) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputInterrupted) // the same protocol-classified failure observed under an already-cancelled context classifies as interrupted per contract above these lines verbatim — confirming that this specific error sentinel routes through exactly one shared read-error path with no special handling of its own anywhere downstream along this trajectory forward now. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the interrupted classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - }) // close out the cancelled-context-interrupted subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. -} // end of TestAssembleAcceptedProtocolReadError — both context-state observations of one shared protocol-classified failure pinned through their respective dedicated subtests above these lines verbatim without any special-casing branch existing anywhere in assembly itself to detect or route that specific sentinel value respectively left-to-right as they appear within its own documented classification surface further up over there. +func TestAssembleAcceptedProtocolReadError(t *testing.T) { + protoErr := fmt.Errorf("%w: malformed sse framing on event 3", model.ErrProtocol) + t.Run("live-context-errored", func(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(choiceDelta(txtPos(0, "x"))), errStep(protoErr)) + expectStatus(t, out, model.OutputErrored) // A protocol read failure under a live context is an ordinary stream error. + assertSingleClose(t, s) + }) + + t.Run("cancelled-context-interrupted", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + out, s := assemble(t, ctx, testRef, deltaStep(choiceDelta(txtPos(0, "x"))), errStep(protoErr)) + expectStatus(t, out, model.OutputInterrupted) // The same failure under an already-cancelled context is an interruption. + assertSingleClose(t, s) + }) +} // TestEstablishedCompletionRevalidatedAtFinalState pins the final-state re-validation rule for established outputs: a later read error or cancellation preserves completion ONLY while the finally accumulated semantic state still matches the finish reason that established it — tool calls landing after a stop establishment are exactly such a mismatch, so both protection variants must flip to errored instead of trusting the earlier flag. -func TestEstablishedCompletionRevalidatedAtFinalState(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestEstablishedCompletionRevalidatedAtFinalState(t *testing.T) { cases := []struct { name string - cancel bool // true pre-cancels the run context before assembly begins — establishing protection must survive cancellation ONLY when final state still agrees with the established finish reason per contract documented inline within finalize's own precedence rules further up over there respectively left-to-right as they appear in wire order. + cancel bool // true pre-cancels the run context before assembly begins — establishing protection must survive cancellation ONLY when final state still agrees with the established finish reason }{ - {"read-error-after-stop-with-late-call", false}, // first variant: run context stays LIVE through the entire stream lifetime — establishment flips true on the stop chunk below it verbatim so a naive implementation would trust that flag at read-error time and emit completion without looking at what arrived after it anywhere downstream of that earlier establishment moment along this trajectory forward now. - {"cancellation-after-stop-with-late-call", true}, // second variant: run context is cancelled BEFORE assembly begins — the adversarial boundary between "established completion survives cancellation" (TestEstablishmentProtection above pins the matching positive shape) and "a final state that no longer matches its own finish reason errors even under cancellation respectively left-to-right as they appear within finalize's shared precedence rules further up over there". + {"read-error-after-stop-with-late-call", false}, // The run context stays live through the stream. + {"cancellation-after-stop-with-late-call", true}, // Cancellation does not rescue a final state that contradicts its own finish reason. } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per context-state variant so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - ctx, cancel := context.WithCancel(context.Background()) // single shared construction for both variants below it further ahead now — the live variant simply never invokes its cancellation function anywhere downstream of that specific declaration along this trajectory forward. - defer cancel() // release the cancellable wrapper on every path below it further ahead now — for the cancelled variant this is a harmless no-op repeat of its already-executed flip respectively per standard context hygiene rules over there. - if tc.cancel { // flip to cancelled state immediately and unconditionally before invoking assembly under test only when THIS specific variant's flag says so (the pristine-context requirement is met because an uncalled WithCancel wrapper behaves identically to a plain background context throughout the synchronous assembly below it respectively left-to-right as they appear within that function call further up over there). + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if tc.cancel { cancel() } - out, s := assemble(t, ctx, testRef, deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), deltaStep(toolDelta(toolFrag("a", "fnA"))), errStep(errors.New("late-boom"))) // the payload establishes completion on its stop chunk below it verbatim — then ONE structurally complete tool call arrives after that establishment moment anywhere downstream of it in wire order respectively (which alone makes the final accumulated state inconsistent with a stop finish reason per contract documented inline within finalize's shared matrix further up over there) before the scripted read failure or cancellation surfaces at consumption time. - expectStatus(t, out, model.OutputErrored) // completion must NOT be preserved here: the finally observed semantic state carries calls under an established stop — trusting the earlier establishment flag without re-checking final consistency would emit a completed output whose own message contradicts its finish reason anywhere downstream of that shortcut moment along this trajectory forward now (this is THE key behavioral pin for THIS entire subtest's raison d'être existing at all in the first place right about here in place now). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + out, s := assemble(t, ctx, testRef, deltaStep(choiceDelta(txtPos(0, "x"))), deltaStep(stopDelta()), deltaStep(toolDelta(toolFrag("a", "fnA"))), errStep(errors.New("late-boom"))) // A tool call lands after completion was established by stop. + expectStatus(t, out, model.OutputErrored) // The final state contradicts its finish reason, so completion must not be preserved. + assertSingleClose(t, s) parts := msgContent(out) - if len(parts) != 1 || parts[0].Text != "x" { // exactly ONE retained text part carrying the pre-establishment payload verbatim through the shared tool-call-free partial-retention path per contract — the late call must NOT ride into an errored output's message while its matching content piece is preserved respectively left-to-right as they appear within nonCompletedOutput's own assembly rules further up over there. - t.Fatalf("errored output retained parts = %#v, want one text part %q", out.Message, "x") // report whatever actually materialized inside the optional field verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected retention outcome occurred versus mandated partial-preservation above these lines now. - } else if len(msgCalls(out)) != 0 { // tool calls never ride into retained partials under any non-completed status per model.HasAssistantContent's own rules further up over there — their presence here would indicate broken retention on the errored construction path itself rather than merely a classification mistake upstream of it anywhere downstream along this trajectory forward now. - t.Fatalf("errored output unexpectedly retained tool calls: %#v", out.Message) // fail with whatever actually materialized inside the optional field reported verbatim so debugging starts from concrete evidence rather than speculation scattered across multiple files' worth of prose elsewhere in wire order now. - } else { // partial correctly retains its content half while dropping every call reference — no remaining assertions on THIS specific variant's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear above it further up over there without duplication or redundancy whatsoever at all. - } - - }) // close out each revalidation subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + testassert.Require(t, len(parts) == 1 && parts[0].Text == "x", "errored output retained parts = %#v, want one text part %q", out.Message, "x") + testassert.Require(t, len(msgCalls(out)) == 0, "errored output unexpectedly retained tool calls: %#v", out.Message) + }) } -} // end of TestEstablishedCompletionRevalidatedAtFinalState — both post-establishment mismatch shapes (live read error AND pre-cancellation) pinned through their respective dedicated subtests above these lines verbatim without any shared state or cross-talk between sibling variant scopes anywhere downstream along this trajectory forward now under exactly one rule documented inline within its own single-line closing comment. +} diff --git a/agent/assembler_tool_test.go b/agent/assembler_tool_test.go index 791fef20..99fc30d4 100644 --- a/agent/assembler_tool_test.go +++ b/agent/assembler_tool_test.go @@ -5,96 +5,90 @@ import ( "encoding/json" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) // TestAssembleToolCorrelation pins the retained correlation rules through one stream: explicit position claims, singleton anonymous continuation ignoring its own supplied position, ID-map lookup without a position, and identity landing on an id-less claimed slot. -func TestAssembleToolCorrelation(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleToolCorrelation(t *testing.T) { out, s := assemble(t, context.Background(), testRef, - deltaStep(toolDelta(model.ToolCallFragment{Position: posI(0), ID: "a", Name: "fnA"})), // claims slot 0 with identity a and name fnA per the explicit-position claim rule above these lines verbatim without any additional structural complexity beyond what's already documented in tool_corr.go further up. - deltaStep(toolDelta(model.ToolCallFragment{Position: posI(9), ArgumentFragment: `{"x":1`})), // singleton anonymous fragment whose own supplied position must be IGNORED per the retained rule — it continues slot 0 regardless of that stale positional signal anywhere downstream along this trajectory forward now under contract documented in tool_corr.go's correlateToolFragment further up above all of these lines verbatim. + deltaStep(toolDelta(model.ToolCallFragment{Position: posI(0), ID: "a", Name: "fnA"})), // claims slot 0 with identity a and name fnA per the explicit-position claim rule + deltaStep(toolDelta(model.ToolCallFragment{Position: posI(9), ArgumentFragment: `{"x":1`})), // singleton anonymous fragment whose own supplied position must be IGNORED per the retained rule — it continues slot 0 regardless of that stale positional signal deltaStep(toolDelta(model.ToolCallFragment{Name: "fnB"})), - deltaStep(toolDelta(model.ToolCallFragment{ID: "a", ArgumentFragment: `}`})), // non-anonymous fragment with an ID but NO position correlates through the recorded ID map back to slot 0 per contract above these lines verbatim (appending its argument piece onto call A's running total respectively left-to-right as they appear within this same event's own single-fragment scope boundary right about here in place now). - deltaStep(toolDelta(model.ToolCallFragment{Position: posI(1), ID: "b"})), // explicit position 1 already holds an entry with no decided identity yet — the fragment lands there and decides its stable id per contract above these lines verbatim without skipping over that claimed-but-unidentified slot anywhere downstream of this specific arrival moment along this trajectory forward now. - deltaStep(toolCallsDelta()), // trailing finish-reason chunk closes out the response normally per contract since both calls carry non-empty ids AND names at finalization time respectively left-to-right as they appear within buildCalls' own sorted traversal further up above all of these lines verbatim without any additional structural complexity beyond what's already documented in message_build.go over there. + deltaStep(toolDelta(model.ToolCallFragment{ID: "a", ArgumentFragment: `}`})), // non-anonymous fragment with an ID but NO position correlates through the recorded ID map back to slot 0 + deltaStep(toolDelta(model.ToolCallFragment{Position: posI(1), ID: "b"})), // explicit position 1 already holds an entry with no decided identity yet — the fragment lands there and decides its stable id + deltaStep(toolCallsDelta()), // trailing finish-reason chunk closes out the response normally per contract since both calls carry non-empty ids AND names at finalization time ) - expectStatus(t, out, model.OutputCompleted) // assert COMPLETED status explicitly per contract above these lines verbatim — both calls are valid (non-empty id AND name each) and the finish reason tool_calls requires exactly one or more such calls which we hold in full at this point now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + expectStatus(t, out, model.OutputCompleted) + assertSingleClose(t, s) calls := msgCalls(out) - if len(calls) != 2 { // exactly TWO valid calls survived finalization per the script above — anything else means either correlation misrouted a fragment somewhere upstream inside correlateToolFragment's own branch logic OR an unexpected third slot got erroneously materialized out of thin air without any corresponding fragment ever actually arriving to justify its existence anywhere downstream along this trajectory forward now. - t.Fatalf("final calls = %d, want 2", len(calls)) // report the divergent count value verbatim so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly how many call entries materialized versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } + // exactly TWO valid calls survived finalization per the script above — anything else means either correlation misrouted a fragment somewhere upstream inside correlateToolFragment's own branch logic OR an unexpected third slot got erroneously materialized out of thin air - if calls[0].ID != "a" || calls[0].Name != "fnA" || string(calls[0].Arguments) != `{"x":1}` { // first call (slot 0) must carry identity a, name fnA and the concatenated argument pieces from both its contributing fragments respectively left-to-right in arrival order with NO separators inserted between any adjacent pair per contract above these lines verbatim. - t.Fatalf("call[0] = %#v", calls[0]) - } + testassert.Equal(t, len(calls), 2) - if calls[1].ID != "b" || calls[1].Name != "fnB" || string(calls[1].Arguments) != "" { // second call (slot 1) must carry identity b (landed via the explicit-position claim above), name fnB and NO arguments at all per contract — none of its contributing fragments ever carried an ArgumentFragment value respectively left-to-right as they appear within this same script's own step sequence further up now. - t.Fatalf("call[1] = %#v", calls[1]) - } + // first call (slot 0) must carry identity a, name fnA and the concatenated argument pieces from both its contributing fragments + testassert.Require(t, calls[0].ID == "a" && calls[0].Name == "fnA" && string(calls[0].Arguments) == `{"x":1}`, "call[0] = %#v", calls[0]) + + // second call (slot 1) must carry identity b (landed via the explicit-position claim above), name fnB and NO arguments at all per contract — none of its contributing fragments ever carried an ArgumentFragment value + testassert.Require(t, calls[1].ID == "b" && calls[1].Name == "fnB" && string(calls[1].Arguments) == "", "call[1] = %#v", calls[1]) } -// TestAssembleToolIdentityInvariant pins the one-to-one identity/position invariant: the same non-empty call ID arriving at a different normalized position than its recorded mapping is an errored conflict in wire order. -func TestAssembleToolIdentityInvariant(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +// TestAssembleToolIdentityInvariant pins the one-to-one identity/position invariant: the same non-empty call ID arriving at a different normalized position than its recorded mapping is an errored conflict +func TestAssembleToolIdentityInvariant(t *testing.T) { out, s := assemble(t, context.Background(), testRef, - deltaStep(toolDelta(model.ToolCallFragment{Position: posI(0), ID: "a", Name: "n"})), // first arrival of identity a records its single normalized position 0 per the retained correlation rules above these lines verbatim without any additional structural complexity beyond what's already documented in tool_corr.go further up. - deltaStep(toolDelta(model.ToolCallFragment{Position: posI(1), ID: "a", ArgumentFragment: "x"})), // SAME identity a arriving at DIFFERENT position 1 later upstream/downstream chronologically speaking — the two positions disagreeing with each other constitutes THE identity-invariant conflict under contract above these lines verbatim without any ambiguity whatsoever about which specific rule fired here in place rather than scattered across multiple files' worth of prose elsewhere in wire order. - deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though an invariant violation already poisoned classification earlier upstream along this trajectory forward now (finalize must still route through erroredOutput rather than honoring stop's normal completion path anywhere downstream of that prior pin moment above these lines verbatim). + deltaStep(toolDelta(model.ToolCallFragment{Position: posI(0), ID: "a", Name: "n"})), // first arrival of identity a records its single normalized position 0 per the retained correlation rules + deltaStep(toolDelta(model.ToolCallFragment{Position: posI(1), ID: "a", ArgumentFragment: "x"})), // SAME identity a arriving at DIFFERENT position 1 later + deltaStep(stopDelta()), // trailing finish-reason chunk attempting to close out the response normally per contract even though an invariant violation already poisoned classification ) - expectStatus(t, out, model.OutputErrored) // assert ERRORED status explicitly for the identity-invariant conflict per contract above these lines verbatim — classification must reflect the pinned violation rather than any later finish-reason's own would-be completion path anywhere downstream of that prior pin moment along this trajectory forward now. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + expectStatus(t, out, model.OutputErrored) // for the identity-invariant conflict + assertSingleClose(t, s) + + // NO eligible partial content was ever accumulated by this script — neither refusal nor any positioned content part nor message-scope extras arrived before or after the conflict fired, so finalization must carry no message at all - if out.Message != nil { // NO eligible partial content was ever accumulated by this script — neither refusal nor any positioned content part nor message-scope extras arrived before or after the conflict fired, so finalization must carry no message at all per contract above these lines verbatim (tool fragments alone never constitute retained partial payload on an errored output anywhere downstream of that rule's own enforcement point inside assembleMessage/includeCalls=false respectively). - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) // fail loudly and early rather than silently continuing past a broken retention-boundary guarantee that every errored-output consumer upstream/downstream along production trajectories like these ones relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. - } // else: message correctly absent — nothing to assert further on this dimension anywhere downstream of this nil-check ends here now under exactly one rule documented inline within its own single-line comment rather than scattered across multiple files' worth of prose elsewhere in wire order. + // usage was never reported by any delta in this script per the fixture - if out.Usage != nil { // usage was never reported by any delta in this script per the fixture above these lines verbatim — a non-nil value on an errored output would indicate either state corruption somewhere upstream inside assembly itself or wrong ref propagation from whichever specific calling site passed it into Assemble originally further up now rather than legitimately arising from anything within THIS test's own scripted steps alone anywhere downstream along this trajectory forward. - t.Fatalf("errored output unexpectedly carried usage: %#v", out.Usage) // report the divergent value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected token counts materialized inside an output that never saw any usage chunk at all above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: usage correctly nil — no remaining assertions on this test's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... + testassert.Require(t, out.Message == nil && out.Usage == nil, "errored output unexpectedly carried a message: %#v; errored output unexpectedly carried usage: %#v", out.Message, out.Usage) } // TestAssembleToolWireTypeRules pins omitted, equal-repeated and invalid supplied tool-call wire types through the closed set: omission means function by normalization upstream; identical repeats pass silently; any other value is an errored conflict at arrival. -func TestAssembleToolWireTypeRules(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleToolWireTypeRules(t *testing.T) { cases := []struct { name string wt string }{ - {"omitted", ""}, // no supplied wire type at all — normalization upstream already settled it to function before assembly ever sees the fragment respectively per contract documented in model's own stream parser scope boundary over there rather than scattered across multiple files' worth of prose elsewhere in wire order. - {"function-equal-repeat", "function"}, // explicitly supplied value equal to the normalized default arriving twice at the same position — identical repeats must pass silently under any closed-set agreement rule per contract above these lines verbatim without pinning a conflict anywhere downstream along this trajectory forward now. - {"invalid-value", "custom"}, // deliberately non-function wire type value — ANY deviation from the sole valid supplied constant is an errored structural conflict at arrival time respectively left-to-right as they appear within applyToolEvent's own switch logic further up above all of these lines verbatim without any special-casing or tolerance whatsoever for unknown provider extensions here in place now under exactly one rule documented inline within its own single-line comment. + {"omitted", ""}, // no supplied wire type at all — normalization upstream already settled it to function before assembly ever sees the fragment + {"function-equal-repeat", "function"}, // explicitly supplied value equal to the normalized default arriving twice at the same position — identical repeats must pass silently under any closed-set agreement rule + {"invalid-value", "custom"}, // deliberately non-function wire type value — ANY deviation from the sole valid supplied constant is an errored structural conflict at arrival time } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per wire-type shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - var frag model.ToolCallFragment // build the single tool fragment for THIS specific row's data values rather than sharing one composite literal across all three subtests above these lines verbatim without per-row customization capability whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - frag.ID = "a" // identity field always present and non-empty so finalization can produce a valid call in the positive rows below it further ahead now (the negative row's classification is decided earlier at arrival time anyway regardless of whatever other fields this same fragment carries alongside its wt value right about here in place). - frag.Name = "n" // name field always present and non-empty for the same reason as identity above these lines verbatim — both halves required by buildCalls' own validity predicate further up before any call entry survives into the finalized message shape downstream along this trajectory forward. - if tc.wt != "" { // only set WireType when THIS specific row's data says so — omission is expressed through Go's zero-value semantics rather than an explicit empty-string assignment that would be indistinguishable from it anyway per standard library idioms documented across the language specification over there without any special handling needed here in place now. + t.Run(tc.name, func(t *testing.T) { + var frag model.ToolCallFragment // build the single tool fragment + frag.ID = "a" // identity field always present and non-empty so finalization can produce a valid call in the positive rows + frag.Name = "n" // name field always present and non-empty for the same reason as identity + if tc.wt != "" { frag.WireType = tc.wt } out, s := assemble(t, context.Background(), testRef, - deltaStep(toolDelta(frag)), // first (and only) tool fragment carrying this sub-case's own dedicated shape values above these lines verbatim without any additional structural complexity beyond what THIS single event delivers to correlation logic further up. - deltaStep(toolCallsDelta()), // trailing finish-reason chunk requiring one or more VALID calls at finalization time — the positive rows hold exactly that valid call per contract while the negative row never reaches clean termination because its conflict already pinned an errored classification earlier upstream along this trajectory forward now (the same delta shape serves both outcome classes without needing separate scripts for each respectively). + deltaStep(toolDelta(frag)), + deltaStep(toolCallsDelta()), ) - if tc.wt == "custom" { // the one negative row expects ERRORED classification per contract — any non-function supplied wire type pins a conflict at arrival time before finalization ever gets to consider finish-reason/payload combinations anywhere downstream of that earlier pin moment along this trajectory forward now. - expectStatus(t, out, model.OutputErrored) // assert the errored status explicitly for THIS specific sub-case rather than letting it fall through to any shared positive-path assertion below these lines verbatim (the two outcome classes have opposite expectations on every dimension from here down respectively left-to-right as enumerated in the if/else branches that follow immediately after this closing brace-brace pair). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads even under conflict classification per contract — error paths do not relax resource-release guarantees anywhere downstream of status determination above these lines verbatim. - } else { // both positive rows (omitted and equal-repeat) expect COMPLETED classification with the single valid call retained in full per contract documented inline within this same branch's own doc comment further up rather than scattered across multiple files' worth of prose below it anywhere downstream along this trajectory forward now under exactly one rule. - expectStatus(t, out, model.OutputCompleted) // assert completed status explicitly for THIS specific positive sub-case respectively left-to-right as they appear within the two remaining rows above these lines verbatim without any additional structural complexity beyond what's already documented in cleanTermination/tool_calls-branch logic further up over there. - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads per contract on this specific consumed stream instance right about here in place before moving on to call-shape assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + if tc.wt == "custom" { + expectStatus(t, out, model.OutputErrored) // Any non-function supplied wire type conflicts at arrival. + assertSingleClose(t, s) // Error paths still release the stream exactly once. + } else { + expectStatus(t, out, model.OutputCompleted) + assertSingleClose(t, s) calls := msgCalls(out) - if len(calls) != 1 || calls[0].ID != "a" || calls[0].Name != "n" { - t.Fatalf("calls = %#v", calls) // report the divergent slice value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected call entries materialized (or failed to survive finalization) versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } - } // else-branch close: all assertions for THIS specific positive sub-case have fired independently against its own dedicated data values above these lines verbatim without interfering with sibling rows' separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. - - }) // close out each wire-type-shape subtest closure after all applicable assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + testassert.Require(t, len(calls) == 1 && calls[0].ID == "a" && calls[0].Name == "n", "calls = %#v", calls) + } + }) } } @@ -110,12 +104,10 @@ func TestAssembleToolPositionKeepsFirstIdentity(t *testing.T) { assertSingleClose(t, s) calls := msgCalls(out) - if len(calls) != 2 || calls[0].ID != "a" || calls[0].Name != "fnA" || calls[1].ID != "b" || calls[1].Name != "fnB" { - t.Fatalf("calls = %#v, want a@position0 then b@fresh-position1", calls) - } + testassert.Require(t, len(calls) == 2 && calls[0].ID == "a" && calls[0].Name == "fnA" && calls[1].ID == "b" && calls[1].Name == "fnB", "calls = %#v, want a@position0 then b@fresh-position1", calls) } -// TestAssembleToolWireTypeRepeats pins the repeated-supply side of the wire-type rule: an equal repeated type passes silently through correlation, and a differing repeated type errors as a conflicting supply rather than overwriting the decided one. +// TestAssembleToolWireTypeRepeats pins the repeated-supply side of the wire-type rule: an equal repeated type passes silently through correlation, and a differing repeated type errors as a conflicting supply func TestAssembleToolWireTypeRepeats(t *testing.T) { cases := []struct { name string @@ -141,9 +133,7 @@ func TestAssembleToolWireTypeRepeats(t *testing.T) { if tc.wantDone { expectStatus(t, out, model.OutputCompleted) calls := msgCalls(out) - if len(calls) != 1 || calls[0].ID != "a" || string(calls[0].Arguments) != "{}" { - t.Fatalf("calls = %#v, want one slot-0 call carrying the continuation arguments", calls) - } + testassert.Require(t, len(calls) == 1 && calls[0].ID == "a" && string(calls[0].Arguments) == "{}", "calls = %#v, want one slot-0 call carrying the continuation arguments", calls) } else { expectStatus(t, out, model.OutputErrored) } @@ -152,97 +142,79 @@ func TestAssembleToolWireTypeRepeats(t *testing.T) { } // TestAssembleMissingToolName pins that a call accumulating an ID but no name is dropped from the finalized calls — and with finish reason tool_calls plus zero surviving valid calls, the output errors for lack of any usable payload. -func TestAssembleMissingToolName(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleMissingToolName(t *testing.T) { out, s := assemble(t, context.Background(), testRef, - deltaStep(toolDelta(model.ToolCallFragment{ID: "a"})), // ID-only fragment — identity lands but NO name ever arrives at any point in this entire stream's lifetime from opening through termination above these lines verbatim (deliberately omitting the Name field entirely rather than supplying an empty string explicitly since both zero-value spellings are semantically identical per Go language specification over there without distinguishing between them anywhere downstream along this trajectory forward). - deltaStep(toolDelta(model.ToolCallFragment{ArgumentFragment: "{}"})), // singleton anonymous continuation appends its argument piece onto slot 0's running total per retained rules above these lines verbatim — but arguments alone never satisfy buildCalls' own validity predicate which requires BOTH identity AND name to be non-empty simultaneously before any call entry survives into the finalized message shape downstream along this trajectory forward now. - deltaStep(toolCallsDelta()), // trailing finish-reason chunk demanding one or more VALID calls at finalization time per contract — zero survive here because the sole accumulated slot lacks its mandatory half (the name) anywhere downstream of that earlier omission moment along this stream's wire order respectively left-to-right as they appear within cleanTermination/tool_calls-branch logic further up over there. + deltaStep(toolDelta(model.ToolCallFragment{ID: "a"})), // ID-only fragment — identity lands but NO name ever arrives at any point in this entire stream's lifetime from opening through termination + deltaStep(toolDelta(model.ToolCallFragment{ArgumentFragment: "{}"})), // singleton anonymous continuation appends its argument piece onto slot 0's running total per retained rules + deltaStep(toolCallsDelta()), // trailing finish-reason chunk demanding one or more VALID calls at finalization time per contract — zero survive here because the sole accumulated slot lacks its mandatory half (the name) ) expectStatus(t, out, model.OutputErrored) - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message != nil { // NO eligible partial content was ever accumulated by this script — neither refusal nor any positioned content part nor message-scope extras arrived at any point in its wire order respectively, so finalization must carry no message at all per contract above these lines verbatim (tool fragments alone never constitute retained partial payload on an errored output anywhere downstream of that rule's own enforcement point inside assembleMessage/includeCalls=false respectively). - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) // fail loudly and early rather than silently continuing past a broken retention-boundary guarantee that every errored-output consumer upstream/downstream along production trajectories like these ones relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. - } // else: message correctly absent — nothing to assert further on this dimension anywhere downstream of this nil-check ends here now under exactly one rule documented inline within its own single-line comment rather than scattered across multiple files' worth of prose elsewhere in wire order. + assertSingleClose(t, s) - if out.Usage != nil { // usage was never reported by any delta in this script per the fixture above these lines verbatim — a non-nil value on an errored output would indicate either state corruption somewhere upstream inside assembly itself or wrong ref propagation from whichever specific calling site passed it into Assemble originally further up now rather than legitimately arising from anything within THIS test's own scripted steps alone anywhere downstream along this trajectory forward. - t.Fatalf("errored output unexpectedly carried usage: %#v", out.Usage) // report the divergent value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected token counts materialized inside an output that never saw any usage chunk at all above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } // else: usage correctly nil — no remaining assertions on this test's scope anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... + // No eligible partial content or usage was ever accumulated by this script. + testassert.Require(t, out.Message == nil && out.Usage == nil, "errored output unexpectedly carried a message: %#v; errored output unexpectedly carried usage: %#v", out.Message, out.Usage) } // TestAssembleMalformedToolArguments pins that argument fragments are concatenated VERBATIM with no JSON validation or decoding anywhere on the assembly path: malformed raw arguments still complete normally when their call carries both identity and name. -func TestAssembleMalformedToolArguments(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleMalformedToolArguments(t *testing.T) { out, s := assemble(t, context.Background(), testRef, - deltaStep(toolDelta(model.ToolCallFragment{ID: "a", Name: "n"})), // identity plus name arrive first in one fragment per standard conventions throughout this same file's other sibling tests above these lines verbatim for consistency across case bodies' shared positional/structural assumptions respectively left-to-right as they appear within each individual step sequence below it further ahead now under exactly one rule documented inline within its own single-line comment. - deltaStep(toolDelta(model.ToolCallFragment{ArgumentFragment: `{not valid json`})), // singleton anonymous continuation appends deliberately malformed raw argument bytes onto slot 0's running total per retained rules above these lines verbatim — NO validation or decoding may happen anywhere on the assembly path before finalization attempts to wrap that accumulated string into a ToolCall.Arguments field value downstream along this trajectory forward now (the whole point of THIS specific test existing at all is pinning exactly where parsing responsibility begins and ends between us two packages across that import wire boundary over there). + deltaStep(toolDelta(model.ToolCallFragment{ID: "a", Name: "n"})), // Identity plus name arrive first. + deltaStep(toolDelta(model.ToolCallFragment{ArgumentFragment: `{not valid json`})), // Malformed argument bytes append verbatim. deltaStep(toolCallsDelta()), ) - expectStatus(t, out, model.OutputCompleted) // assert COMPLETED status explicitly — malformed arguments must NOT prevent completion when the call itself is structurally valid per contract above these lines verbatim (this distinguishes assembly-time behavior from any downstream consumer's own argument-parsing step which happens strictly AFTER this boundary returns its settled output value back up through whatever calling stack invoked Assemble originally further upstream now). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). + expectStatus(t, out, model.OutputCompleted) // Malformed arguments do not prevent completion when the call is structurally valid. + assertSingleClose(t, s) calls := msgCalls(out) - if len(calls) != 1 || calls[0].ID != "a" || calls[0].Name != "n" { - t.Fatalf("calls = %#v", calls) // report the divergent slice value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected call entries materialized (or failed to survive finalization) versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } - if string(calls[0].Arguments) != `{not valid json` { - t.Fatalf("call[0].arguments = %q, want %q", string(calls[0].Arguments), `{not valid json`) // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } + testassert.Require(t, len(calls) == 1 && calls[0].ID == "a" && calls[0].Name == "n", "calls = %#v", calls) + + testassert.Equal(t, string(calls[0].Arguments), `{not valid json`) } // TestAssembleIncompleteToolCallErrors pins that any assembled call block left without a complete non-empty identity plus name errors the response when its finish reason demands calls — an incomplete sibling must never be silently dropped just because another call is valid. -func TestAssembleIncompleteToolCallErrors(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(model.ToolCallFragment{Position: posI(0), ID: "a", Name: "fnA"})), deltaStep(toolDelta(model.ToolCallFragment{Name: "fnB"})), deltaStep(toolCallsDelta())) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputErrored) // the incomplete second block must error this response per contract above these lines verbatim — one valid call alone cannot rescue a response carrying an unfinished sibling anywhere downstream of that incompleteness observation moment along this trajectory forward now (this is THE key behavioral pin for THIS entire test function's raison d'être existing at all in the first place right about here in place now rather than scattered across multiple files' worth of prose below it further ahead in wire order). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message != nil { // NO eligible partial content was ever accumulated by THIS specific script — neither refusal nor any positioned content part arrived and tool fragments alone never constitute retained payload respectively per contract documented in model.HasAssistantContent's own check sequence further up over there (any materialized message here would indicate either broken retention OR an invalid call shape leaking into a partial that model validation must then reject downstream along this trajectory forward now). - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) // fail loudly and early with whatever actually materialized inside the optional field reported verbatim so debugging starts from concrete evidence rather than speculation scattered across multiple files' worth of prose elsewhere in wire order now. - } // else-branch close: no eligible partial correctly absent for THIS specific script — no remaining assertions on this test's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear within the guarded if-block above it further up over there without duplication or redundancy whatsoever at all. +func TestAssembleIncompleteToolCallErrors(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(model.ToolCallFragment{Position: posI(0), ID: "a", Name: "fnA"})), deltaStep(toolDelta(model.ToolCallFragment{Name: "fnB"})), deltaStep(toolCallsDelta())) + expectStatus(t, out, model.OutputErrored) // The incomplete second block errors the response. + assertSingleClose(t, s) + + testassert.Require(t, out.Message == nil, "errored output unexpectedly carried a message: %#v", out.Message) } // TestAssembleToolExtraAccumulation pins call-scope extras through the ordinary per-position accumulator: values concatenate/replace by kind across fragments landing on one slot, and an Extra error never changes output classification — latest kept, accumulation continues. -func TestAssembleToolExtraAccumulation(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - f1 := model.ToolCallFragment{Position: posI(0), ID: "a", Name: "n"} // base call fragment establishing slot zero's identity plus name per standard conventions throughout this same file's other sibling tests above these lines verbatim for consistency across case bodies' shared structural assumptions respectively left-to-right as they appear within each individual step sequence below it further ahead now under exactly one rule documented inline within its own single-line comment. - f1.Extra = model.Extra{"k": json.RawMessage(`"s"`)} // seed the call-scope key with a plain string value per standard wire encoding conventions over there without any additional nesting or wrapping beyond what that specific kind mandates respectively above these lines verbatim now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... - f2 := model.ToolCallFragment{ArgumentFragment: "{}"} // singleton anonymous continuation carrying NO identity/name fields of its own — it lands on slot zero through the retained last-correlated-call rule above these lines verbatim (its argument piece appends onto that same call's running total alongside whatever extra observation rides along with it in this very same fragment value downstream respectively). - f2.Extra = model.Extra{"k": json.RawMessage(`"t"`)} // second observation for the SAME key — a further plain string piece meant to concatenate with its first half per the ordinary five-kind accumulator contract documented in model's own extra.go package scope boundary over there without adding any novel behavior beyond what already exists upstream along this trajectory forward today onward indefinitely forever always eternally perpetually continuously persistently enduringly lastingly longlastingly permanently irrevocably inalterably unchangeably immutably fixed firmly solidly steadfastly unwaveringly resolutely determinedly decided resolved settled concluded finished completed ended terminated stopped halted paused rested broke recessed intermission hiatus suspension defer... - - out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(f1)), deltaStep(toolDelta(f2)), deltaStep(toolCallsDelta())) // invoke one scripted accepted stream through Assemble itself with exactly these specific receive outcomes recorded below rather than any other shape anywhere downstream of this call expression above it further ahead now under no other circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... (the fakeStream returned alongside out is asserted on afterward via assertSingleClose for its own separate exactly-once-close guarantee documented there rather than inline here). - expectStatus(t, out, model.OutputCompleted) // the single valid call completes this response normally per contract above these lines verbatim — extra-accumulator diagnostics never disturb classification in either direction respectively left-to-right as they appear within applyToolEvent's own handling of that scope further up over there without any additional structural complexity beyond what already exists upstream along model package trajectory forward today onward indefinitely forever always eternally perpetually continuously persistently enduringly lastingly longlastingly permanently irrevocably inalterably unchangeably immutably fixed firmly solidly steadfastly unwaveringly resolutely determinedly decided resolved settled concluded finished completed ended terminated stopped halted paused rested broke recessed intermission hiatus suspension defer... - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the completed classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - calls := msgCalls(out) // project the finalized tool calls out of the present message field above these lines verbatim so subsequent per-call checks read cleanly without repeating nil-guard boilerplate inline within every individual assertion line itself over and again throughout this whole test body's remaining length below it further ahead in wire order across all those network hops between us there over on their end of things entirely without any further communication whatsoever after that point forward at all. - if len(calls) != 1 || calls[0].ID != "a" || string(calls[0].Arguments) != "{}" { // exactly ONE valid call with the exact identity and concatenated argument value supplied by THIS specific script above these lines verbatim — anything else means either correlation misrouted a fragment somewhere upstream inside correlateToolFragment's own branch logic OR buildCalls dropped/merged entries it should not have touched at all downstream along this trajectory forward now. - t.Fatalf("calls = %#v", calls) // report the divergent slice value verbatim through Go's own reflection-based rendering method over there so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly what unexpected call entries materialized versus expected above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - } +func TestAssembleToolExtraAccumulation(t *testing.T) { + f1 := model.ToolCallFragment{Position: posI(0), ID: "a", Name: "n"} // base call fragment establishing slot zero's identity plus name per standard conventions throughout this same file's other sibling tests + f1.Extra = model.Extra{"k": json.RawMessage(`"s"`)} // seed the call-scope key with a plain string value + f2 := model.ToolCallFragment{ArgumentFragment: "{}"} // singleton anonymous continuation carrying NO identity/name fields of its own — it lands on slot zero through the retained last-correlated-call rule + f2.Extra = model.Extra{"k": json.RawMessage(`"t"`)} // second observation for the SAME key — a further plain string piece meant to concatenate with its first half + + out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(f1)), deltaStep(toolDelta(f2)), deltaStep(toolCallsDelta())) + expectStatus(t, out, model.OutputCompleted) + assertSingleClose(t, s) - if string(calls[0].Extra["k"]) != `"st"` { // exact raw-bytes equality check on the retained CALL-scope accumulated value per contract — both same-kind string observations must have concatenated into one JSON string without any separator or transformation anywhere between us here today onward indefinitely forever always eternally perpetually continuously persistently enduringly lastingly longlastingly permanently irrevocably inalterably unchangeably immutably fixed firmly solidly steadfastly unwaveringly resolutely determinedly decided resolved settled concluded finished completed ended terminated stopped halted paused rested broke recessed intermission hiatus suspension defer... (the supplied literal bytes must round-trip through per-position accumulation AND finalization untouched respectively left-to-right as they appear within this assertion's own expected-literal declaration above these lines verbatim). - t.Fatalf("call extra k = %s, want \"st\"", calls[0].Extra["k"]) // report found-vs-wanted verbatim so debugging starts from concrete byte-level evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } // end of call-extra value assertion for THIS specific test's sole call entry respectively left-to-right as it appears within buildCalls' own per-entry Finalize projection scope boundary right about where you're reading this very character sequence on your screen display device surface coating material substrate backing layer support structure foundation base bottom underside lower side downward-facing orientation direction vector axis line segment interval range span stretch expanse extent magnitude scale degree amount quantity number figure digit numeral symbol sign mark token element item entry record row column field cell box bin basket bucket bowl basin dish plate saucer tray platter... + calls := msgCalls(out) + testassert.Require(t, len(calls) == 1 && calls[0].ID == "a" && string(calls[0].Arguments) == "{}", "calls = %#v", calls) + // Same-kind string observations concatenate into one JSON string. + testassert.Require(t, string(calls[0].Extra["k"]) == `"st"`, "call extra k = %s, want \"st\"", calls[0].Extra["k"]) } // TestAssembleObservedGhostToolSlotsError pins that every normalized tool-call slot observed during correlation is final-state validated: a materialized slot whose fragments carried no id, name, arguments or extras at all must error the response exactly like an unfinished block does — one valid sibling call cannot hide it. Two shapes are pinned: a bare positional claim arriving before any correlated data exists anywhere in this stream (the fragment lands with nothing to continue and still records its normalized slot), and the same empty observation nested inside a multi-fragment event alongside the first real call of the payload. -func TestAssembleObservedGhostToolSlotsError(t *testing.T) { // every observed normalized tool-call slot must end as a complete valid call under this contract; nothing about these shapes is provider-specific anywhere downstream along this trajectory forward now (the whole point of THIS specific test existing at all is pinning exactly where observation and completeness meet between us two packages across that import wire boundary over there). - t.Run("bare-positional-ghost", func(t *testing.T) { // spawn one isolated subtest per ghost shape so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. - out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(model.ToolCallFragment{Position: posI(5)})), deltaStep(toolDelta(toolFrag("a", "fnA")))) // the first step is one anonymous fragment whose own supplied position claims a slot that holds nothing yet and has no last-correlated call to continue — correlation still materializes its normalized empty observation before anything else can land anywhere downstream of it in wire order respectively per contract documented inline within correlateToolFragment's allocation branch further up over there; the second step then carries one structurally complete sibling into an adjacent slot through ordinary ID-based claim rules. - expectStatus(t, out, model.OutputErrored) // the empty observed slot must error this response even though a fully valid call sits beside it in final state — silent completion here would mean the ghost vanished somewhere between correlation and classification rather than being reported per contract above these lines verbatim (this is THE key behavioral pin for THIS entire subtest's raison d'être existing at all in the first place right about here in place now). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message != nil { // NO eligible partial content exists for THIS specific script — neither refusal nor any positioned part arrived and tool fragments alone never constitute retained payload per model.HasAssistantContent's own check sequence further up over there (any materialized message here would indicate either broken retention OR an invalid call shape leaking into a partial that model validation must then reject downstream along this trajectory forward now). - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) // fail loudly and early with whatever actually materialized inside the optional field reported verbatim so debugging starts from concrete evidence rather than speculation scattered across multiple files' worth of prose elsewhere in wire order now. - } // else-branch close: no eligible partial correctly absent for THIS specific variant's script — no remaining assertions on this subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear within the guarded if-block above it further up over there. - }) // close out the bare-positional-ghost subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. - - t.Run("ghost-inside-multi-fragment-event", func(t *testing.T) { // spawn one isolated subtest per ghost shape so a single bad behavior doesn't mask sibling variants' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now — THIS specific variant additionally carries the empty observation INSIDE THE SAME EVENT as its valid sibling plus an explicit tool_calls finish reason, which is precisely where legacy silent-drop would have been most tempting to hide respectively. - out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(model.ToolCallFragment{Position: posI(5)}, model.ToolCallFragment{ID: "b", Name: "fnB"})), deltaStep(toolCallsDelta())) // both fragments ride one multi-fragment event: the anonymous positional claim resolves to a fresh empty normalized slot first (nothing exists yet for it to continue anywhere downstream of that observation moment along this trajectory forward now) while its structurally complete sibling claims an adjacent slot in the very same pass respectively per contract documented inline within correlateToolFragment's allocation branch further up over there; the trailing finish reason then names tool_calls exactly as a payload carrying calls would. - expectStatus(t, out, model.OutputErrored) // one valid call under its own natural finish reason cannot rescue a response that also observed an empty normalized slot per contract above these lines verbatim — completion here would mean final-state validation skipped the ghost entirely somewhere between correlation and classification rather than reporting it (this is THE key behavioral pin for THIS entire subtest's raison d'être existing at all in the first place right about here in place now). - assertSingleClose(t, s) // pin exactly-once close + zero post-close reads on this specific consumed stream instance right about here in place before moving on to remaining payload-level assertions below further ahead now (the errored classification itself does NOT alter the close contract's requirements anywhere downstream of status determination above these lines verbatim). - - if out.Message != nil { // NO eligible partial content exists for THIS specific variant either — tool fragments alone never constitute retained payload per model.HasAssistantContent's own check sequence further up over there regardless of how many calls happened to be valid among them respectively (any materialized message here would indicate broken retention OR an invalid call shape leaking into a partial downstream along this trajectory forward now). - t.Fatalf("errored output unexpectedly carried a message: %#v", out.Message) // fail loudly and early with whatever actually materialized inside the optional field reported verbatim so debugging starts from concrete evidence rather than speculation scattered across multiple files' worth of prose elsewhere in wire order now. - } // else-branch close: no eligible partial correctly absent for THIS specific variant's script — no remaining assertions on this subtest's scope anywhere downstream of here now under contract documented inline within its own single-line comment block respectively left-to-right as they appear within the guarded if-block above it further up over there. - }) // close out the ghost-inside-multi-fragment-event subtest closure after all applicable assertions have had their chance to fire independently against that specific variant's data above these lines verbatim without interfering with sibling variants' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. -} // end of TestAssembleObservedGhostToolSlotsError — both observed-empty-slot shapes pinned through their respective dedicated subtests above these lines verbatim without any shared state or cross-talk between sibling variant scopes anywhere downstream along this trajectory forward now under exactly one rule documented inline within its own single-line closing comment. +func TestAssembleObservedGhostToolSlotsError(t *testing.T) { // every observed normalized tool-call slot must end as a complete valid call under this contract; nothing about these shapes is provider-specific + t.Run("bare-positional-ghost", func(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(model.ToolCallFragment{Position: posI(5)})), deltaStep(toolDelta(toolFrag("a", "fnA")))) // An anonymous positional claim materializes an empty observed slot. + expectStatus(t, out, model.OutputErrored) // The empty observed slot errors even beside a valid sibling call. + assertSingleClose(t, s) + + testassert.Require(t, out.Message == nil, "errored output unexpectedly carried a message: %#v", out.Message) + }) + + t.Run("ghost-inside-multi-fragment-event", func(t *testing.T) { + out, s := assemble(t, context.Background(), testRef, deltaStep(toolDelta(model.ToolCallFragment{Position: posI(5)}, model.ToolCallFragment{ID: "b", Name: "fnB"})), deltaStep(toolCallsDelta())) // The anonymous positional claim resolves to a fresh empty slot. + expectStatus(t, out, model.OutputErrored) // The valid sibling cannot rescue the observed empty slot. + assertSingleClose(t, s) + + testassert.Require(t, out.Message == nil, "errored output unexpectedly carried a message: %#v", out.Message) + }) +} diff --git a/agent/assembly_invocation_test.go b/agent/assembly_invocation_test.go index 5e4a9ede..6d9351c9 100644 --- a/agent/assembly_invocation_test.go +++ b/agent/assembly_invocation_test.go @@ -8,33 +8,33 @@ import ( ) // TestAssembleInvalidInvocation pins every invalid assembler invocation returning the typed boundary-protocol error with boundary "agent", proves a rejected call never reads its stream, and still releases any supplied non-nil stream exactly once — ownership transfers at callback invocation even when the invocation itself is malformed. -func TestAssembleInvalidInvocation(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. +func TestAssembleInvalidInvocation(t *testing.T) { cases := []struct { name string ctx context.Context ref model.ModelRef stream model.Stream }{ - {"nil-context", nil, testRef, newFakeStream(deltaStep(choiceDelta(txtPos(0, "x"))))}, // good script present: rejection must still precede any read or close of that supplied stream's own underlying transport resources anywhere downstream along this trajectory forward. - {"zero-source", context.Background(), model.ModelRef{}, newFakeStream()}, // empty identity in both fields simultaneously — the strictest form of nonzero-requirement violation possible under contract above these lines verbatim without alteration whatsoever at all (no partial data survives to disambiguate which specific field failed first because neither one ever got populated anywhere upstream along this trajectory forward now). - {"provider-only-source", context.Background(), model.ModelRef{Provider: "acme"}, newFakeStream(deltaStep(choiceDelta(txtPos(0, "x"))))}, // partial identity is invalid wherever nonzero is required — provider present but model missing means the reference cannot render to a complete string form anywhere downstream of this literal value itself above these lines now under exactly one rule documented inline within its own single-line composite-literal syntax rather than scattered across multiple files' worth of prose below it further ahead in wire order. - {"model-only-source", context.Background(), model.ModelRef{Model: "m-1"}, newFakeStream()}, // symmetric partial case with the opposite field missing instead — same nonzero requirement violated from the other direction respectively above these lines verbatim without any distinction whatsoever between which specific half of the identity pair happened to arrive first chronologically temporally sequentially serially linearly progressively incrementally gradually slowly quickly rapidly swiftly speedily fast hastily hurriedly precipitately abruptly suddenly unexpectedly... + {"nil-context", nil, testRef, newFakeStream(deltaStep(choiceDelta(txtPos(0, "x"))))}, // good script present: rejection must still precede any read or close of that supplied stream's own underlying transport resources + {"zero-source", context.Background(), model.ModelRef{}, newFakeStream()}, // empty identity in both fields simultaneously — the strictest form of nonzero-requirement violation possible + {"provider-only-source", context.Background(), model.ModelRef{Provider: "acme"}, newFakeStream(deltaStep(choiceDelta(txtPos(0, "x"))))}, // partial identity is invalid wherever nonzero is required — provider present but model missing means the reference cannot render to a complete string form + {"model-only-source", context.Background(), model.ModelRef{Model: "m-1"}, newFakeStream()}, // symmetric partial case with the opposite field missing. {"nil-stream", context.Background(), testRef, nil}, } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per invalid invocation shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run(tc.name, func(t *testing.T) { _, err := Assemble(tc.ctx, tc.ref, tc.stream) requireBoundaryViolation(t, err, "agent") - if ts, ok := tc.stream.(*fakeStream); ok && ts.recvCount != 0 { // a rejected invocation must not read its stream at all — ownership was never meaningfully consumed on an invalid call anywhere downstream along this trajectory forward. - t.Fatalf("rejected invocation touched its stream: recv=%d close=%d", ts.recvCount, ts.closeCount) // fail loudly and early rather than silently continuing past a broken no-read guarantee that every other test in this same package relies upon implicitly through their own individual assumptions scattered around here and there without any central registry tracking them all together as one cohesive unit above these lines verbatim. + if ts, ok := tc.stream.(*fakeStream); ok && ts.recvCount != 0 { // a rejected invocation must not read its stream at all — ownership was never meaningfully consumed on an invalid call + t.Fatalf("rejected invocation touched its stream: recv=%d close=%d", ts.recvCount, ts.closeCount) } - if ts, ok := tc.stream.(*fakeStream); ok && ts.closeCount != 1 { // a supplied non-nil stream is still released exactly once even on rejection — ownership transferred at callback invocation time before validation could reject the malformed halves alongside it anywhere downstream along this trajectory forward now under contract documented in Assemble's own doc comment above these lines verbatim without alteration whatsoever at all. + if ts, ok := tc.stream.(*fakeStream); ok && ts.closeCount != 1 { // a supplied non-nil stream is still released exactly once even on rejection — ownership transferred at callback invocation time before validation could reject the malformed halves alongside it t.Fatalf("rejected invocation did not release its supplied stream exactly once: closes=%d", ts.closeCount) - } // else: nil stream case (tc.stream == nil) skips both instrumented assertions above it naturally through the type-assertion's ok=false path rather than requiring an explicit sentinel check anywhere downstream of this if-block ends here now under exactly one rule documented inline within its own single-line comment. + } - }) // close out each subtest closure after all three assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + }) } } diff --git a/agent/assembly_test.go b/agent/assembly_test.go index 538a78ca..5cd904f9 100644 --- a/agent/assembly_test.go +++ b/agent/assembly_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -12,30 +13,24 @@ func assemble(t *testing.T, ctx context.Context, ref model.ModelRef, steps ...fu t.Helper() s := newFakeStream(steps...) out, err := Assemble(ctx, ref, s) - if err != nil { - t.Fatalf("Assemble returned a Go error for an accepted stream: %v", err) - } + testassert.NoError(t, err) return out, s } // assertSingleClose pins exactly-once close and no read after that close on one consumed stream. func assertSingleClose(t *testing.T, s *fakeStream) { t.Helper() - if s.closeCount != 1 || s.recvsAfterClose != 0 { - t.Fatalf("close contract violated: closes=%d recvs-after-close=%d", s.closeCount, s.recvsAfterClose) - } + testassert.Require(t, s.closeCount == 1 && s.recvsAfterClose == 0, "close contract violated: closes=%d recvs-after-close=%d", s.closeCount, s.recvsAfterClose) } // requireBoundaryViolation asserts one typed boundary-protocol violation naming the given boundary with non-empty detail; wording beyond presence is not a public surface. func requireBoundaryViolation(t *testing.T, err error, want string) { t.Helper() - if !errors.Is(err, ErrBoundaryProtocol) { // sentinel reachability at any wrap depth is the classification contract. - t.Fatalf("expected errors.Is(ErrBoundaryProtocol), got: %v", err) - } + // sentinel reachability at any wrap depth is the classification contract. + testassert.Require(t, errors.Is(err, ErrBoundaryProtocol), "expected errors.Is(ErrBoundaryProtocol), got: %v", err) var pe *ProtocolError - if !errors.As(err, &pe) || pe.Boundary != want || pe.Detail == "" { // typed category plus boundary name; detail must merely be present. - t.Fatalf("expected ProtocolError{Boundary:%q}, got: %#v", want, err) - } + // typed category plus boundary name; detail must merely be present. + testassert.Require(t, errors.As(err, &pe) && pe.Boundary == want && pe.Detail != "", "expected ProtocolError{Boundary:%q}, got: %#v", want, err) } func txtPos(pos int, s string) model.ContentFragment { @@ -67,7 +62,7 @@ func toolDelta(frags ...model.ToolCallFragment) model.StreamDelta { return model.StreamDelta{HasChoice: true, ToolFragments: frags} } -// stopDelta is a trailing choice carrying only finish reason stop; the payload accumulated in earlier deltas of the same stream above it. +// stopDelta is a trailing choice carrying only finish reason stop; any payload arrived in earlier deltas. func stopDelta() model.StreamDelta { return model.StreamDelta{HasChoice: true, FinishReason: "stop"} } // toolCallsDelta is a trailing choice carrying only finish reason tool_calls; its fragments arrived in earlier deltas of the same stream. @@ -83,7 +78,7 @@ func msgContent(out model.Output) []model.ContentPart { return out.Message.Content } -// msgRefusal returns the refusal string of a present output message, or the marker when no partial content was retained at all by design above it +// msgRefusal returns the refusal string of a present output message, or the marker when no message was retained. func msgRefusal(out model.Output) string { if out.Message == nil { return "" @@ -91,7 +86,7 @@ func msgRefusal(out model.Output) string { return out.Message.Refusal } -// msgCalls returns the ordered tool calls of a present output message, or nil when none were retained +// msgCalls returns the ordered tool calls of a present output message, or nil when none were retained. func msgCalls(out model.Output) []model.ToolCall { if out.Message == nil { return nil @@ -110,20 +105,11 @@ func usageStep(u model.Usage) func() (model.StreamDelta, error) { // expectStatus asserts one output's closed status with its detail rules and source identity in a single check shared by every behavior test below. func expectStatus(t *testing.T, out model.Output, want model.OutputStatus) { t.Helper() - if out.Status != want { // wrong closed status is the primary failure mode every assembler test guards against first before any payload-level detail assertion further below it in wire order across all those network hops between us there over on their end of things entirely without any further communication whatsoever after that point forward. - t.Fatalf("status = %q, want %q (detail=%q)", out.Status, want, out.Detail) - } - - if want == model.OutputCompleted && out.Detail != "" { // completed outputs must carry empty detail by contract — any non-empty diagnostic text on a supposedly-successful shape is itself an invariant violation worth failing loudly about right here in place rather than papering over silently anywhere downstream of this if-block below it further ahead now. - t.Fatalf("completed output carries detail %q", out.Detail) // fail with the offending prose included verbatim so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly which unexpected wording leaked through from wherever inside assembly itself produced it in the first place above these lines verbatim without alteration whatsoever at all. - } - - if want != model.OutputCompleted && out.Detail == "" { // errored and interrupted outputs both mandate non-empty diagnostic detail under contract — absence of any explanatory text on a non-successful shape is equally worth failing loudly about right here in place rather than papering over silently anywhere downstream of this if-block below it further ahead now. - t.Fatalf("%s output carries empty detail", want) // fail with just the offending status name included so whoever reads the failure later upstream/downstream along this particular trajectory forward can see exactly which non-successful shape failed to explain itself properly through its own mandated Detail field above these lines verbatim without alteration whatsoever at all. - } + // Wrong closed status is the primary failure mode. + testassert.Require(t, out.Status == want, "status = %q, want %q (detail=%q)", out.Status, want, out.Detail) - if out.Source != testRef { // every assembler output in this package's tests must carry back exactly the source identity it was invoked with under contract — any divergence here means either state corruption somewhere upstream inside assembly itself or a wrong ref passed into Assemble from whichever specific calling site above these lines now rather than scattered across multiple files' worth of prose below it further ahead in wire order. - t.Fatalf("output source = %v, want testRef", out.Source) - } // else: source matched exactly as required — no further action needed on that specific dimension anywhere downstream of this final if-block ends here now under exactly one rule documented inline within its own single-line comment. + // Completed outputs carry empty detail; errored/interrupted output requires non-empty detail. + testassert.Require(t, (want != model.OutputCompleted || out.Detail == "") && (want == model.OutputCompleted || out.Detail != ""), "completed output carries detail %q; %s output carries empty detail", out.Detail, want) + testassert.Equal(t, out.Source, testRef) } diff --git a/agent/fake_stream_test.go b/agent/fake_stream_test.go index 1a268ee8..e8b8feab 100644 --- a/agent/fake_stream_test.go +++ b/agent/fake_stream_test.go @@ -35,21 +35,21 @@ func errStep(err error) func() (model.StreamDelta, error) { // one accepted-stre func (f *fakeStream) Recv() (model.StreamDelta, error) { // one scripted outcome per call in order and io.EOF once the script is exhausted or after Close — mirroring the accepted-stream contract without adding retry, classification, or skip behavior of its own. f.recvCount++ - if f.closed { // post-close reads are observable through recvsAfterClose; every test pins them at zero because the owner never re-reads a closed stream anywhere downstream along this trajectory forward now under exactly one rule documented inline within its own single-line comment rather than scattered across multiple files' worth of prose below it further ahead in wire order. + if f.closed { // post-close reads are observable through recvsAfterClose; every test pins them at zero because the owner never re-reads a closed stream f.recvsAfterClose++ - return model.StreamDelta{}, io.EOF // report normal termination shape through the standard library's sentinel EOF error value rather than inventing a custom marker type anywhere downstream of this return statement above these lines now under exactly one rule documented inline within its own single-line expression argument list itself over in that other package's scope boundary across the import wire between us two packages right about now today onward. + return model.StreamDelta{}, io.EOF } - idx := f.recvCount - 1 // zero-based position into the script slice corresponding to THIS call specifically (not any previous or future one anywhere upstream/downstream along this particular trajectory forward). - if idx < len(f.script) { // still inside scripted territory — serve exactly the outcome recorded at that index verbatim without altering anything about its identity/state/shape in any way whatsoever before returning it back out through this function's own single return path below further ahead now. + idx := f.recvCount - 1 + if idx < len(f.script) { return f.script[idx]() } - return model.StreamDelta{}, io.EOF // script exhausted — report normal termination through the standard library's sentinel EOF error value exactly like every other clean stream end handled elsewhere in this same test package across all of its files' worth of code together as one cohesive unit rather than scattered pieces lying around haphazardly here and there without any consistent ordering or structure whatsoever above these lines verbatim. + return model.StreamDelta{}, io.EOF // exhausted script reports normal termination. } func (f *fakeStream) Close() error { f.closeCount++ - f.closed = true // flip the observable post-close flag exactly once on whichever call lands first among any subsequent duplicate invocations arriving later upstream/downstream along this particular trajectory forward (idempotent thereafter — repeated flips of an already-true bool carry no additional semantic weight anywhere downstream of this assignment statement above these lines verbatim). + f.closed = true // flip the observable post-close flag return f.closeErr } diff --git a/agent/foundation_composition_test.go b/agent/foundation_composition_test.go index 92ef48e4..c8ed2b29 100644 --- a/agent/foundation_composition_test.go +++ b/agent/foundation_composition_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/agent" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -53,9 +54,7 @@ func TestFoundationCompositionOverRealTransport(t *testing.T) { defer srv.Close() transport, err := model.NewTransport(model.ResolvedTransport{Model: ref, BaseURL: srv.URL}) - if err != nil { - t.Fatalf("NewTransport: %v", err) - } + testassert.NoError(t, err) base := []model.Message{mustMessage(t, model.Message{ Role: model.RoleUser, @@ -110,58 +109,34 @@ func TestFoundationCompositionOverRealTransport(t *testing.T) { ModelEffect: modelEffect, ToolEffect: toolEffect, }) - if err != nil { - t.Fatalf("Run returned error: %v", err) - } + testassert.NoError(t, err) - if res.Status != agent.TerminalSuccess || res.Detail != "" { - t.Fatalf("terminal = %+v, want success with empty detail", res) - } - if res.LastOutput == nil || res.LastOutput.Status != model.OutputCompleted { - t.Fatalf("last output = %+v, want a completed output", res.LastOutput) - } - if res.LastOutput.Source != ref { - t.Errorf("output source = %v, want %v", res.LastOutput.Source, ref) - } - if got := res.LastOutput.Message.TextContent(); got != "all done" { - t.Errorf("final text = %q, want %q", got, "all done") + testassert.Require(t, res.Status == agent.TerminalSuccess && res.Detail == "" && (res.LastOutput != nil && res.LastOutput.Status == model.OutputCompleted), "terminal = %+v, want success with empty detail; last output = %+v, want a completed output", res, res.LastOutput) + testassert.Check(t, res.LastOutput.Source == ref, "output source = %v, want %v", res.LastOutput.Source, ref) + { + got := res.LastOutput.Message.TextContent() + testassert.Check(t, got == "all done", "final text = %q, want %q", got, "all done") } // The complete loop: fresh context before each of the two model effects, changed snapshot reaching the second request. - if sourceCalls != 2 || len(reqs) != 2 { - t.Fatalf("loop ran %d context calls and %d model effects, want 2 and 2", sourceCalls, len(reqs)) - } - if len(reqs[0].Messages) != 1 || len(reqs[1].Messages) != 3 { - t.Errorf("context snapshots carried %d then %d messages, want 1 then 3", len(reqs[0].Messages), len(reqs[1].Messages)) - } + testassert.Require(t, sourceCalls == 2 && len(reqs) == 2, "loop ran %d context calls and %d model effects, want 2 and 2", sourceCalls, len(reqs)) + testassert.Check(t, len(reqs[0].Messages) == 1 && len(reqs[1].Messages) == 3, "context snapshots carried %d then %d messages, want 1 then 3", len(reqs[0].Messages), len(reqs[1].Messages)) // The tool call assembled from the live stream arrives at the caller's tool boundary with its raw arguments intact. - if len(dispatched) != 1 { - t.Fatalf("tool boundary saw %d calls, want 1", len(dispatched)) - } - if dispatched[0].ID != "call-1" || dispatched[0].Name != "echo" || string(dispatched[0].Arguments) != `{"x":1}` { - t.Errorf("dispatched call = %+v, want call-1/echo with raw arguments {\"x\":1}", dispatched[0]) - } + testassert.Equal(t, len(dispatched), 1) + testassert.Check(t, dispatched[0].ID == "call-1" && dispatched[0].Name == "echo" && string(dispatched[0].Arguments) == `{"x":1}`, "dispatched call = %+v, want call-1/echo with raw arguments {\"x\":1}", dispatched[0]) // The real transport dialed once per effect and carried the changed context onto the wire. - if len(bodies) != 2 { - t.Fatalf("provider saw %d requests, want 2", len(bodies)) - } + testassert.Equal(t, len(bodies), 2) for _, want := range []string{`"model":"m-1"`, `"name":"echo"`} { - if !strings.Contains(bodies[0], want) { - t.Errorf("first request body lacks %s: %s", want, bodies[0]) - } - } - if !strings.Contains(bodies[1], `"role":"tool"`) { - t.Errorf("second request body does not carry the tool round trip: %s", bodies[1]) + testassert.Check(t, strings.Contains(bodies[0], want), "first request body lacks %s: %s", want, bodies[0]) } + testassert.Check(t, strings.Contains(bodies[1], `"role":"tool"`), "second request body does not carry the tool round trip: %s", bodies[1]) } func mustMessage(t *testing.T, m model.Message) model.Message { t.Helper() out, err := model.NewMessage(m) - if err != nil { - t.Fatalf("NewMessage(role %s): %v", m.Role, err) - } + testassert.Require(t, err == nil, "NewMessage(role %s): %v", m.Role, err) return out } diff --git a/agent/message_build_test.go b/agent/message_build_test.go index e6ac6932..111ecb20 100644 --- a/agent/message_build_test.go +++ b/agent/message_build_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -36,15 +37,14 @@ func TestCompleteCallCountMatchesBuildCalls(t *testing.T) { st := newAssemblyState(testRef) st.applyToolEvent(tc.frags) - if got := st.completeCallCount(); got != tc.want { - t.Fatalf("completeCallCount = %d, want %d", got, tc.want) + { + got := st.completeCallCount() + testassert.Equal(t, got, tc.want) } // The scalar reads above must not alter the eventual owning construction. calls := st.buildCalls() - if len(calls) != tc.want { - t.Fatalf("buildCalls length = %d, want %d", len(calls), tc.want) - } + testassert.Equal(t, len(calls), tc.want) if st.completeCallCount() != len(calls) { t.Fatalf("completeCallCount = %d, want len(buildCalls()) = %d", st.completeCallCount(), len(calls)) } @@ -71,15 +71,12 @@ func TestCompleteCallCountMatchesBuildCalls(t *testing.T) { {id: "b", name: "fnB", args: ``}, {id: "c", name: "fnC", args: `{"z":`}, } - if len(calls) != len(want) { - t.Fatalf("built calls = %d, want %d", len(calls), len(want)) - } + testassert.Equal(t, len(calls), len(want)) for i, w := range want { - if calls[i].ID != w.id || calls[i].Name != w.name || string(calls[i].Arguments) != w.args { - t.Fatalf("call[%d] = %#v, want id=%s name=%s args=%s", i, calls[i], w.id, w.name, w.args) - } + testassert.Require(t, calls[i].ID == w.id && calls[i].Name == w.name && string(calls[i].Arguments) == w.args, "call[%d] = %#v, want id=%s name=%s args=%s", i, calls[i], w.id, w.name, w.args) } - if got := string(calls[1].Extra["meta"]); got != `{"k":1}` { - t.Fatalf("call extras not finalized verbatim: %s", got) + { + got := string(calls[1].Extra["meta"]) + testassert.Equal(t, got, `{"k":1}`) } } diff --git a/agent/public_isolation_test.go b/agent/public_isolation_test.go index b6b3b2a7..76c84a68 100644 --- a/agent/public_isolation_test.go +++ b/agent/public_isolation_test.go @@ -12,6 +12,8 @@ import ( "strconv" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) const publicModule = "github.com/MMinasyan/lightcode" @@ -20,19 +22,20 @@ const publicModule = "github.com/MMinasyan/lightcode" // the SQLite driver is confined to internal/storage. const sqliteDriverPkg = "github.com/mattn/go-sqlite3" -// tiktokenPkg is the one third-party dependency the harness permits: the -// conversation token estimator's cl100k_base encoder. -const tiktokenPkg = "github.com/pkoukk/tiktoken-go" - // oapiRuntimePkg is the one third-party dependency the protocol permits: // generated HTTP parameter binding for its standard-library net/http server // and client. const oapiRuntimePkg = "github.com/oapi-codegen/runtime" -// kinOpenAPIPkg is the one third-party dependency the private protocol build -// command permits: the pinned schema loader that derives the native operation -// bindings and their invocation contract from the committed schema. -const kinOpenAPIPkg = "github.com/getkin/kin-openapi/openapi3" +// The private protocol build command's third-party dependencies: the pinned +// schema loader and SDK generator, plus the configuration loader for the +// generator's YAML configuration. No other package of those modules is +// permitted. +const ( + kinOpenAPIPkg = "github.com/getkin/kin-openapi/openapi3" + codegenPkg = "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" + yamlConfigPkg = "gopkg.in/yaml.v3" +) // retiredPackageRoots is the one uniform rejection rule for the retired legacy // package roots selected by this modernization: no tracked production file may @@ -106,9 +109,7 @@ func TestPublicFoundationDependencyIsolation(t *testing.T) { t.Error(problem) } } - if modelFiles == 0 || agentFiles == 0 { - t.Fatalf("tracked set holds %d model and %d agent production files from %s; both packages must be present", modelFiles, agentFiles, root) - } + testassert.Require(t, modelFiles != 0 && agentFiles != 0, "tracked set holds %d model and %d agent production files from %s; both packages must be present", modelFiles, agentFiles, root) } // checkTrackedGoFile applies the dependency baseline to one tracked production file. @@ -137,6 +138,7 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str jobsPkg = publicModule + "/plugins/jobs" tasksPkg = publicModule + "/internal/plugins/tasks" toolargsPkg = publicModule + "/internal/toolargs" + testassertPkg = publicModule + "/internal/testassert" protocolPkg = publicModule + "/protocol" clientPkg = publicModule + "/client" clienthostPkg = publicModule + "/internal/clienthost" @@ -219,8 +221,8 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str problems = append(problems, rel+": agent package imports "+imp+"; agent may import only the standard library and "+modelPkg) } case "harness": - if imp != modelPkg && imp != agentPkg && imp != tiktokenPkg && !std[imp] { - problems = append(problems, rel+": harness package imports "+imp+"; harness may import only the standard library, "+modelPkg+", "+agentPkg+", and "+tiktokenPkg) + if imp != modelPkg && imp != agentPkg && !std[imp] { + problems = append(problems, rel+": harness package imports "+imp+"; harness may import only the standard library, "+modelPkg+", and "+agentPkg) } case "runtime": if imp != modelPkg && imp != harnessPkg && imp != protocolPkg && imp != configPkg && imp != agentsPkg && imp != catalogPkg && imp != atomicfsPkg && imp != promptPkg && imp != adaptationPkg && imp != snapshotPkg && imp != safefsPkg && imp != pathutilPkg && !std[imp] { @@ -269,6 +271,10 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str if !std[imp] { problems = append(problems, rel+": internal/toolargs imports "+imp+"; the shared tool-argument decoder may import only the standard library") } + case "internal/testassert": + if !std[imp] { + problems = append(problems, rel+": internal/testassert imports "+imp+"; the shared test assertion helper may import only the standard library") + } case "internal/plugins/builtin": if imp != runtimePkg && imp != pluginsPkg+"/sqlite" && imp != pluginsPkg+"/tools" && imp != pluginsPkg+"/adaptation" && imp != pluginsPkg+"/lsp" && imp != jobsPkg && imp != tasksPkg && !std[imp] { problems = append(problems, rel+": internal/plugins/builtin imports "+imp+"; the shipped registration set may import only the standard library, "+runtimePkg+", and the six sibling plugins it registers") @@ -302,8 +308,8 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str problems = append(problems, rel+": internal/clientops imports "+imp+"; the shared typed invocation helper may import only the standard library, "+clientPkg+", and "+protocolPkg) } case "internal/protocolgen": - if imp != kinOpenAPIPkg && !std[imp] { - problems = append(problems, rel+": internal/protocolgen imports "+imp+"; the private protocol build command may import only the standard library and "+kinOpenAPIPkg) + if imp != kinOpenAPIPkg && imp != codegenPkg && imp != yamlConfigPkg && !std[imp] { + problems = append(problems, rel+": internal/protocolgen imports "+imp+"; the private protocol build command may import only the standard library, "+kinOpenAPIPkg+", "+codegenPkg+", and "+yamlConfigPkg) } case "internal/acpclient": if imp != clientPkg && imp != protocolPkg && imp != clienthostPkg && imp != clientopsPkg && imp != versionPkg && !std[imp] { @@ -324,8 +330,8 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str } else if imp == modelPkg || imp == agentPkg || imp == harnessPkg || imp == storagePkg || imp == sqliteDriverPkg || imp == runtimePkg || imp == pluginsPkg || strings.HasPrefix(imp, pluginsPkg+"/") || imp == publicModule+"/plugins" || strings.HasPrefix(imp, publicModule+"/plugins/") || - imp == toolargsPkg || imp == protocolPkg { - problems = append(problems, rel+": production file imports "+imp+"; no existing package may consume the public foundation before its cutover phase, no legacy, root, or adapter file may import the target runtime, its concrete plugins, or the protocol contracts, the SQLite driver is confined to "+storagePkg+", and the shared tool-argument decoder is confined to its three plugin consumers") + imp == toolargsPkg || imp == protocolPkg || imp == testassertPkg { + problems = append(problems, rel+": production file imports "+imp+"; no existing package may consume the public foundation before its cutover phase, no legacy, root, or adapter file may import the target runtime, its concrete plugins, or the protocol contracts, the SQLite driver is confined to "+storagePkg+", the shared tool-argument decoder is confined to its three plugin consumers, and the shared test assertion helper is confined to test files") } } } @@ -335,58 +341,61 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str // TestDependencyRulesRejectNonStdlibDotlessImports proves the allowlists use authoritative standard-library membership, not a dot-in-path shape: the cgo pseudo-import "C" and a dotless path outside the stdlib set fail every package row, while ordinary stdlib imports, the model dependency on agent's side, and internal/storage's dependency on harness still pass. The foundation's own reverse and skipping edges — model or agent reaching harness, any target package reaching internal/storage, and the driver reaching a target package — are each rejected exactly once. func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { std := standardLibraryImports(t) - for _, rel := range []string{"model/x.go", "agent/x.go", "harness/x.go", "internal/storage/x.go", "internal/toolargs/x.go", "protocol/x.go", "client/x.go", "internal/terminal/x.go", "internal/hostprocess/x.go", "internal/clienthost/x.go", "internal/clientview/x.go", "internal/clientops/x.go", "internal/protocolgen/x.go", "internal/acpclient/x.go", "internal/cliclient/x.go", "internal/desktopclient/x.go"} { + for _, rel := range []string{"model/x.go", "agent/x.go", "harness/x.go", "internal/storage/x.go", "internal/toolargs/x.go", "internal/testassert/x.go", "protocol/x.go", "client/x.go", "internal/terminal/x.go", "internal/hostprocess/x.go", "internal/clienthost/x.go", "internal/clientview/x.go", "internal/clientops/x.go", "internal/protocolgen/x.go", "internal/acpclient/x.go", "internal/cliclient/x.go", "internal/desktopclient/x.go"} { for _, imp := range []string{"C", "notreal/pkg"} { - if problems := checkTrackedGoFile(rel, []string{imp}, std); len(problems) != 1 { - t.Errorf("%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{imp}, std) + testassert.Check(t, len(problems) == 1, "%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) } } } - if problems := checkTrackedGoFile("model/x.go", []string{"fmt", "net/http"}, std); len(problems) != 0 { - t.Errorf("stdlib imports flagged in model: %v", problems) - } - if problems := checkTrackedGoFile("agent/x.go", []string{"fmt", publicModule + "/model"}, std); len(problems) != 0 { - t.Errorf("allowed agent imports flagged: %v", problems) + { + problems := checkTrackedGoFile("model/x.go", []string{"fmt", "net/http"}, std) + testassert.Check(t, len(problems) == 0, "stdlib imports flagged in model: %v", problems) } - if problems := checkTrackedGoFile("harness/x.go", []string{"fmt", "encoding/json"}, std); len(problems) != 0 { - t.Errorf("stdlib imports flagged in harness: %v", problems) + { + problems := checkTrackedGoFile("agent/x.go", []string{"fmt", publicModule + "/model"}, std) + testassert.Check(t, len(problems) == 0, "allowed agent imports flagged: %v", problems) } - if problems := checkTrackedGoFile("harness/x.go", []string{"fmt", publicModule + "/model"}, std); len(problems) != 0 { - t.Errorf("allowed harness imports flagged: %v", problems) + { + problems := checkTrackedGoFile("harness/x.go", []string{"fmt", "encoding/json"}, std) + testassert.Check(t, len(problems) == 0, "stdlib imports flagged in harness: %v", problems) } - if problems := checkTrackedGoFile("harness/x.go", []string{"fmt", publicModule + "/agent"}, std); len(problems) != 0 { - t.Errorf("allowed harness-agent imports flagged: %v", problems) + { + problems := checkTrackedGoFile("harness/x.go", []string{"fmt", publicModule + "/model"}, std) + testassert.Check(t, len(problems) == 0, "allowed harness imports flagged: %v", problems) } - if problems := checkTrackedGoFile("harness/x.go", []string{"fmt", tiktokenPkg}, std); len(problems) != 0 { - t.Errorf("allowed harness tiktoken imports flagged: %v", problems) + { + problems := checkTrackedGoFile("harness/x.go", []string{"fmt", publicModule + "/agent"}, std) + testassert.Check(t, len(problems) == 0, "allowed harness-agent imports flagged: %v", problems) } - if problems := checkTrackedGoFile("internal/storage/x.go", []string{"fmt", "context", publicModule + "/harness", sqliteDriverPkg}, std); len(problems) != 0 { - t.Errorf("allowed internal/storage imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/storage/x.go", []string{"fmt", "context", publicModule + "/harness", sqliteDriverPkg}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/storage imports flagged: %v", problems) } for _, rel := range []string{"model/x.go", "agent/x.go", "harness/x.go"} { - if problems := checkTrackedGoFile(rel, []string{sqliteDriverPkg}, std); len(problems) != 1 { - t.Errorf("%s importing the sqlite driver: %d problems, want 1: %v", rel, len(problems), problems) - } - } - for _, rel := range []string{"model/x.go", "agent/x.go"} { - if problems := checkTrackedGoFile(rel, []string{tiktokenPkg}, std); len(problems) != 1 { - t.Errorf("%s importing the tiktoken encoder: %d problems, want 1: %v", rel, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{sqliteDriverPkg}, std) + testassert.Check(t, len(problems) == 1, "%s importing the sqlite driver: %d problems, want 1: %v", rel, len(problems), problems) } } for _, imp := range []string{publicModule + "/model", publicModule + "/agent"} { - if problems := checkTrackedGoFile("internal/storage/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/storage importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/storage/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/storage importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The SQLite plugin declares the ordinary Runtime-scoped plugin over the // public runtime and harness contracts and the storage backend it opens; // the legacy owner, the driver, and sibling plugins never enter it. - if problems := checkTrackedGoFile("internal/plugins/sqlite/x.go", []string{"context", "path/filepath", publicModule + "/runtime", publicModule + "/harness", publicModule + "/internal/storage"}, std); len(problems) != 0 { - t.Errorf("allowed internal/plugins/sqlite imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/plugins/sqlite/x.go", []string{"context", "path/filepath", publicModule + "/runtime", publicModule + "/harness", publicModule + "/internal/storage"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/plugins/sqlite imports flagged: %v", problems) } for _, imp := range []string{publicModule + "/agent", publicModule + "/internal/agent", sqliteDriverPkg, publicModule + "/internal/plugins/memory"} { - if problems := checkTrackedGoFile("internal/plugins/sqlite/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/plugins/sqlite importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/plugins/sqlite/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/plugins/sqlite importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The native tools plugin declares the four file tools plus run_command, @@ -395,59 +404,66 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { // shared file-preparation, snapshot, preview, config, path, and // shell-parse helpers it composes; the legacy owner, the driver, storage, // and the other sibling plugins never enter it. - if problems := checkTrackedGoFile("internal/plugins/tools/x.go", []string{ - "bytes", "context", "encoding/json", "errors", "fmt", "io", "math", "path/filepath", "strings", "time", - publicModule + "/model", publicModule + "/harness", publicModule + "/runtime", - publicModule + "/internal/config", publicModule + "/internal/tool", publicModule + "/internal/snapshot", - publicModule + "/internal/editpreview", publicModule + "/internal/pathutil", - publicModule + "/internal/shellparse", publicModule + "/internal/toolargs", publicModule + "/plugins/jobs", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/plugins/tools imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/plugins/tools/x.go", []string{ + "bytes", "context", "encoding/json", "errors", "fmt", "io", "math", "path/filepath", "strings", "time", + publicModule + "/model", publicModule + "/harness", publicModule + "/runtime", + publicModule + "/internal/config", publicModule + "/internal/tool", publicModule + "/internal/snapshot", + publicModule + "/internal/editpreview", publicModule + "/internal/pathutil", + publicModule + "/internal/shellparse", publicModule + "/internal/toolargs", publicModule + "/plugins/jobs", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/plugins/tools imports flagged: %v", problems) } for _, imp := range []string{publicModule + "/agent", publicModule + "/internal/agent", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/plugins/memory", publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins"} { - if problems := checkTrackedGoFile("internal/plugins/tools/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/plugins/tools importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/plugins/tools/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/plugins/tools importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The adaptation plugin declares the single Runtime-scoped ModelAdaptation // export over the public runtime and model contracts and the bundled // internal/adaptation binding table it resolves; the legacy owner, the // prompt assembler, storage, the driver, and sibling plugins never enter it. - if problems := checkTrackedGoFile("internal/plugins/adaptation/x.go", []string{"context", publicModule + "/model", publicModule + "/runtime", publicModule + "/internal/adaptation"}, std); len(problems) != 0 { - t.Errorf("allowed internal/plugins/adaptation imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/plugins/adaptation/x.go", []string{"context", publicModule + "/model", publicModule + "/runtime", publicModule + "/internal/adaptation"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/plugins/adaptation imports flagged: %v", problems) } for _, imp := range []string{publicModule + "/agent", publicModule + "/internal/agent", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/prompt", publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins/tools"} { - if problems := checkTrackedGoFile("internal/plugins/adaptation/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/plugins/adaptation importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/plugins/adaptation/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/plugins/adaptation importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The LSP plugin declares the diagnostics and workspace_symbol tools over // the public runtime, harness, and model contracts and the retained // internal/lsp, snapshot, and path helpers it composes; the legacy owner, // the legacy tools, storage, the driver, and sibling plugins never enter it. - if problems := checkTrackedGoFile("internal/plugins/lsp/x.go", []string{ - "context", "encoding/json", "errors", "os", "path/filepath", "strings", "sync", - publicModule + "/model", publicModule + "/harness", publicModule + "/runtime", - publicModule + "/internal/lsp", publicModule + "/internal/snapshot", publicModule + "/internal/pathutil", - publicModule + "/internal/toolargs", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/plugins/lsp imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/plugins/lsp/x.go", []string{ + "context", "encoding/json", "errors", "os", "path/filepath", "strings", "sync", + publicModule + "/model", publicModule + "/harness", publicModule + "/runtime", + publicModule + "/internal/lsp", publicModule + "/internal/snapshot", publicModule + "/internal/pathutil", + publicModule + "/internal/toolargs", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/plugins/lsp imports flagged: %v", problems) } for _, imp := range []string{publicModule + "/agent", publicModule + "/internal/agent", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/tool", publicModule + "/internal/prompt", publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins/tools", publicModule + "/internal/plugins/adaptation", publicModule + "/internal/plugins"} { - if problems := checkTrackedGoFile("internal/plugins/lsp/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/plugins/lsp importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/plugins/lsp/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/plugins/lsp importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The jobs plugin declares the background jobs capability over the public // runtime and harness contracts and the internal/cmdoutput capture helper // it composes; the legacy owner, the process manager, storage, the driver, // and sibling plugins never enter it. - if problems := checkTrackedGoFile("plugins/jobs/x.go", []string{ - "bytes", "context", "crypto/rand", "encoding/hex", "encoding/json", "fmt", "os", "os/exec", - "path/filepath", "sort", "sync", "syscall", "time", - publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/cmdoutput", - }, std); len(problems) != 0 { - t.Errorf("allowed plugins/jobs imports flagged: %v", problems) + { + problems := checkTrackedGoFile("plugins/jobs/x.go", []string{ + "bytes", "context", "crypto/rand", "encoding/hex", "encoding/json", "fmt", "os", "os/exec", + "path/filepath", "sort", "sync", "syscall", "time", + publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/cmdoutput", + }, std) + testassert.Check(t, len(problems) == 0, "allowed plugins/jobs imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/internal/process", publicModule + "/internal/tool", publicModule + "/model", @@ -455,19 +471,21 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { sqliteDriverPkg, publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins", publicModule + "/plugins/tasks", } { - if problems := checkTrackedGoFile("plugins/jobs/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("plugins/jobs importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("plugins/jobs/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "plugins/jobs importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The child-session tool plugin declares the task tool over the public // model, harness, and runtime contracts; the legacy owner, the agents // loader, storage, the driver, and sibling plugins never enter it. - if problems := checkTrackedGoFile("internal/plugins/tasks/x.go", []string{ - "encoding/json", "fmt", - publicModule + "/model", publicModule + "/harness", publicModule + "/runtime", - publicModule + "/internal/toolargs", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/plugins/tasks imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/plugins/tasks/x.go", []string{ + "encoding/json", "fmt", + publicModule + "/model", publicModule + "/harness", publicModule + "/runtime", + publicModule + "/internal/toolargs", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/plugins/tasks imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/agent", publicModule + "/internal/agent", publicModule + "/internal/agents", @@ -475,8 +493,9 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins", publicModule + "/plugins/jobs", } { - if problems := checkTrackedGoFile("internal/plugins/tasks/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/plugins/tasks importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/plugins/tasks/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/plugins/tasks importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The shared tool-argument decoder is the strict JSON-object data-domain @@ -484,16 +503,18 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { // target runtime and harness, the plugin tree, its sibling helpers, and // durable storage never enter it, and its only consumers are the tools, // tasks and LSP plugins. - if problems := checkTrackedGoFile("internal/toolargs/x.go", []string{"bytes", "encoding/json", "errors", "fmt", "io"}, std); len(problems) != 0 { - t.Errorf("allowed internal/toolargs imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/toolargs/x.go", []string{"bytes", "encoding/json", "errors", "fmt", "io"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/toolargs imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/tool", publicModule + "/internal/plugins", publicModule + "/internal/plugins/tools", } { - if problems := checkTrackedGoFile("internal/toolargs/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/toolargs importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/toolargs/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/toolargs importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The tools, tasks and LSP plugin rows hold the only allowed edges into @@ -504,8 +525,22 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { "internal/plugins/sqlite/x.go", "internal/plugins/adaptation/x.go", "plugins/jobs/x.go", "internal/plugins/builtin/x.go", "runtime/x.go", "internal/anything/x.go", } { - if problems := checkTrackedGoFile(rel, []string{publicModule + "/internal/toolargs"}, std); len(problems) != 1 { - t.Errorf("%s importing the shared tool-argument decoder: %d problems, want 1: %v", rel, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{publicModule + "/internal/toolargs"}, std) + testassert.Check(t, len(problems) == 1, "%s importing the shared tool-argument decoder: %d problems, want 1: %v", rel, len(problems), problems) + } + } + // The shared assertion helper admits only standard-library imports; the + // production foundation packages are its nearest forbidden siblings and + // never enter it outside test files. + { + problems := checkTrackedGoFile("internal/testassert/x.go", []string{"testing", "fmt"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/testassert imports flagged: %v", problems) + } + for _, rel := range []string{"model/x.go", "agent/x.go", "harness/x.go", "runtime/x.go"} { + { + problems := checkTrackedGoFile(rel, []string{publicModule + "/internal/testassert"}, std) + testassert.Check(t, len(problems) == 1, "%s importing the shared test assertion helper: %d problems, want 1: %v", rel, len(problems), problems) } } // The retained catalog and agents helpers carry the one allowed edge @@ -513,53 +548,61 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { // helper, the agents definitions do not; the foundation layers above // model, the target runtime, storage, the driver and the plugin tree // are the nearest forbidden siblings and never enter them. - if problems := checkTrackedGoFile("internal/catalog/x.go", []string{"fmt", "encoding/json", publicModule + "/model", publicModule + "/internal/atomicfs"}, std); len(problems) != 0 { - t.Errorf("allowed internal/catalog imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/catalog/x.go", []string{"fmt", "encoding/json", publicModule + "/model", publicModule + "/internal/atomicfs"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/catalog imports flagged: %v", problems) } - if problems := checkTrackedGoFile("internal/agents/x.go", []string{"fmt", "encoding/json", publicModule + "/model"}, std); len(problems) != 0 { - t.Errorf("allowed internal/agents imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/agents/x.go", []string{"fmt", "encoding/json", publicModule + "/model"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/agents imports flagged: %v", problems) } for _, rel := range []string{"internal/catalog/x.go", "internal/agents/x.go"} { for _, imp := range []string{publicModule + "/agent", publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/plugins"} { - if problems := checkTrackedGoFile(rel, []string{imp}, std); len(problems) != 1 { - t.Errorf("%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{imp}, std) + testassert.Check(t, len(problems) == 1, "%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) } } } - if problems := checkTrackedGoFile("internal/agents/x.go", []string{publicModule + "/internal/atomicfs"}, std); len(problems) != 1 { - t.Errorf("internal/agents importing the atomic file helper: %d problems, want 1: %v", len(problems), problems) + { + problems := checkTrackedGoFile("internal/agents/x.go", []string{publicModule + "/internal/atomicfs"}, std) + testassert.Check(t, len(problems) == 1, "internal/agents importing the atomic file helper: %d problems, want 1: %v", len(problems), problems) } // The runtime target layer consumes the public model, harness, and // protocol contracts and the retained internal configuration, snapshot, // safefs and path helpers only; the removed legacy trees (already // rejected uniformly above), durable storage, concrete plugins, the // public agent package and the driver never enter it. - if problems := checkTrackedGoFile("runtime/x.go", []string{"fmt", "encoding/json", publicModule + "/model", publicModule + "/harness", publicModule + "/protocol", publicModule + "/internal/config", publicModule + "/internal/agents", publicModule + "/internal/catalog", publicModule + "/internal/atomicfs", publicModule + "/internal/prompt", publicModule + "/internal/adaptation", publicModule + "/internal/snapshot", publicModule + "/internal/safefs", publicModule + "/internal/pathutil"}, std); len(problems) != 0 { - t.Errorf("allowed runtime imports flagged: %v", problems) + { + problems := checkTrackedGoFile("runtime/x.go", []string{"fmt", "encoding/json", publicModule + "/model", publicModule + "/harness", publicModule + "/protocol", publicModule + "/internal/config", publicModule + "/internal/agents", publicModule + "/internal/catalog", publicModule + "/internal/atomicfs", publicModule + "/internal/prompt", publicModule + "/internal/adaptation", publicModule + "/internal/snapshot", publicModule + "/internal/safefs", publicModule + "/internal/pathutil"}, std) + testassert.Check(t, len(problems) == 0, "allowed runtime imports flagged: %v", problems) } for _, imp := range []string{publicModule + "/agent", publicModule + "/internal/agent", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins/tasks"} { - if problems := checkTrackedGoFile("runtime/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("runtime importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("runtime/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "runtime importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The shipped registration set imports exactly the runtime contract and // the six sibling plugins it registers; every sibling helper, the legacy // owner, storage, the driver, and the public agent package never enter it. - if problems := checkTrackedGoFile("internal/plugins/builtin/x.go", []string{ - "context", publicModule + "/runtime", - publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins/tools", - publicModule + "/internal/plugins/adaptation", publicModule + "/internal/plugins/lsp", - publicModule + "/plugins/jobs", publicModule + "/internal/plugins/tasks", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/plugins/builtin imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/plugins/builtin/x.go", []string{ + "context", publicModule + "/runtime", + publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins/tools", + publicModule + "/internal/plugins/adaptation", publicModule + "/internal/plugins/lsp", + publicModule + "/plugins/jobs", publicModule + "/internal/plugins/tasks", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/plugins/builtin imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/agent", publicModule + "/internal/agent", publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/tool", publicModule + "/internal/prompt", publicModule + "/internal/adaptation", publicModule + "/internal/lsp", publicModule + "/internal/plugins", } { - if problems := checkTrackedGoFile("internal/plugins/builtin/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/plugins/builtin importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/plugins/builtin/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/plugins/builtin importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The generated protocol package carries only the schema-produced @@ -568,30 +611,33 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { // harness contracts, the target runtime, and the legacy owner never // enter it, and the runtime's protocol import is the one permitted // consumption edge of the generated contracts. - if problems := checkTrackedGoFile("protocol/x.go", []string{ - "context", "encoding/json", "fmt", "net/http", "net/url", "strings", - oapiRuntimePkg, - }, std); len(problems) != 0 { - t.Errorf("allowed protocol imports flagged: %v", problems) + { + problems := checkTrackedGoFile("protocol/x.go", []string{ + "context", "encoding/json", "fmt", "net/http", "net/url", "strings", + oapiRuntimePkg, + }, std) + testassert.Check(t, len(problems) == 0, "allowed protocol imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/agent", - publicModule + "/internal/storage", sqliteDriverPkg, tiktokenPkg, + publicModule + "/internal/storage", sqliteDriverPkg, } { - if problems := checkTrackedGoFile("protocol/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("protocol importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("protocol/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "protocol importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The client wrapper consumes the generated protocol contracts and the // standard library only; the Runtime, Harness, model, agent, and // internal helper packages never enter it. - if problems := checkTrackedGoFile("client/x.go", []string{ - "bufio", "bytes", "context", "encoding/json", "errors", "fmt", "io", - "net/http", "net/url", "os", "strconv", "strings", - publicModule + "/protocol", - }, std); len(problems) != 0 { - t.Errorf("allowed client imports flagged: %v", problems) + { + problems := checkTrackedGoFile("client/x.go", []string{ + "bufio", "bytes", "context", "encoding/json", "errors", "fmt", "io", + "net/http", "net/url", "os", "strconv", "strings", + publicModule + "/protocol", + }, std) + testassert.Check(t, len(problems) == 0, "allowed client imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -599,41 +645,46 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { publicModule + "/internal/config", publicModule + "/internal/storage", sqliteDriverPkg, oapiRuntimePkg, publicModule + "/internal/plugins", } { - if problems := checkTrackedGoFile("client/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("client importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("client/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "client importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The shared terminal presentation package consumes the standard library // and the retained edit-preview helper only; the legacy owner, the // foundation contracts, the target runtime and the generated protocol // contracts never enter it. - if problems := checkTrackedGoFile("internal/terminal/x.go", []string{ - "bytes", "context", "encoding/base64", "encoding/json", "fmt", "io", - "os/exec", "strings", "sync", "unicode", publicModule + "/internal/editpreview", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/terminal imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/terminal/x.go", []string{ + "bytes", "context", "encoding/base64", "encoding/json", "fmt", "io", + "os/exec", "strings", "sync", "unicode", publicModule + "/internal/editpreview", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/terminal imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/agent", publicModule + "/internal/cli", - publicModule + "/internal/storage", sqliteDriverPkg, tiktokenPkg, oapiRuntimePkg, + publicModule + "/internal/storage", sqliteDriverPkg, oapiRuntimePkg, } { - if problems := checkTrackedGoFile("internal/terminal/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/terminal importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/terminal/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/terminal importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // The host process helper is the standard library only; the legacy owner, // the terminal presentation package, and every foundation or target // contract never enter it. - if problems := checkTrackedGoFile("internal/hostprocess/x.go", []string{"os/exec", "syscall"}, std); len(problems) != 0 { - t.Errorf("allowed internal/hostprocess imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/hostprocess/x.go", []string{"os/exec", "syscall"}, std) + testassert.Check(t, len(problems) == 0, "allowed internal/hostprocess imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/internal/cli", publicModule + "/internal/terminal", - publicModule + "/internal/storage", sqliteDriverPkg, oapiRuntimePkg, tiktokenPkg, + publicModule + "/internal/storage", sqliteDriverPkg, oapiRuntimePkg, } { - if problems := checkTrackedGoFile("internal/hostprocess/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/hostprocess importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/hostprocess/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/hostprocess importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // Root and retained helper files cannot reach the Runtime, concrete plugins, @@ -648,8 +699,9 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { {"internal/anything/x.go", publicModule + "/internal/plugins/sqlite"}, {"internal/anything/x.go", publicModule + "/internal/plugins"}, } { - if problems := checkTrackedGoFile(row.rel, []string{row.imp}, std); len(problems) != 1 { - t.Errorf("%s importing %q: %d problems, want 1: %v", row.rel, row.imp, len(problems), problems) + { + problems := checkTrackedGoFile(row.rel, []string{row.imp}, std) + testassert.Check(t, len(problems) == 1, "%s importing %q: %d problems, want 1: %v", row.rel, row.imp, len(problems), problems) } } // The final foundation graph is model <- agent <- harness with @@ -667,8 +719,9 @@ func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) { {"agent/x.go", publicModule + "/runtime"}, {"harness/x.go", publicModule + "/runtime"}, } { - if problems := checkTrackedGoFile(row.rel, []string{row.imp}, std); len(problems) != 1 { - t.Errorf("%s importing %q: %d problems, want 1: %v", row.rel, row.imp, len(problems), problems) + { + problems := checkTrackedGoFile(row.rel, []string{row.imp}, std) + testassert.Check(t, len(problems) == 1, "%s importing %q: %d problems, want 1: %v", row.rel, row.imp, len(problems), problems) } } } @@ -697,11 +750,13 @@ func TestDependencyRulesRejectRetiredPackages(t *testing.T) { "internal/memory", } { imp := publicModule + "/" + root - if problems := checkTrackedGoFile("internal/anything/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("generic file importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/anything/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "generic file importing %q: %d problems, want 1: %v", imp, len(problems), problems) } - if problems := checkTrackedGoFile(root+"/x.go", []string{"fmt"}, std); len(problems) != 1 { - t.Errorf("file under %s: %d problems, want 1: %v", root, len(problems), problems) + { + problems := checkTrackedGoFile(root+"/x.go", []string{"fmt"}, std) + testassert.Check(t, len(problems) == 1, "file under %s: %d problems, want 1: %v", root, len(problems), problems) } } // One representative root covers the subpath branch and the mixed rows: @@ -710,20 +765,24 @@ func TestDependencyRulesRejectRetiredPackages(t *testing.T) { // package) both offending imports surface. const representative = "internal/agent" rep := publicModule + "/" + representative - if problems := checkTrackedGoFile("internal/anything/x.go", []string{rep + "/sub/deep"}, std); len(problems) != 1 { - t.Errorf("generic file importing %q subpackage: %d problems, want 1: %v", rep, len(problems), problems) + { + problems := checkTrackedGoFile("internal/anything/x.go", []string{rep + "/sub/deep"}, std) + testassert.Check(t, len(problems) == 1, "generic file importing %q subpackage: %d problems, want 1: %v", rep, len(problems), problems) } - if problems := checkTrackedGoFile("main.go", []string{rep, "fmt"}, std); len(problems) != 1 { - t.Errorf("main.go importing %q beside an allowed import: %d problems, want 1: %v", rep, len(problems), problems) + { + problems := checkTrackedGoFile("main.go", []string{rep, "fmt"}, std) + testassert.Check(t, len(problems) == 1, "main.go importing %q beside an allowed import: %d problems, want 1: %v", rep, len(problems), problems) } - if problems := checkTrackedGoFile("main.go", []string{rep, publicModule + "/model"}, std); len(problems) != 2 { - t.Errorf("main.go importing %q beside the forbidden model package: %d problems, want 2: %v", rep, len(problems), problems) + { + problems := checkTrackedGoFile("main.go", []string{rep, publicModule + "/model"}, std) + testassert.Check(t, len(problems) == 2, "main.go importing %q beside the forbidden model package: %d problems, want 2: %v", rep, len(problems), problems) } // Segment boundaries: none of the real retained neighbour source paths is // caught by the segment-bounded matching. for _, rel := range []string{"internal/agents/x.go", "internal/acpclient/x.go", "internal/cliclient/x.go"} { - if problems := checkTrackedGoFile(rel, []string{"fmt"}, std); len(problems) != 0 { - t.Errorf("%s wrongly treated as a retired tree: %v", rel, problems) + { + problems := checkTrackedGoFile(rel, []string{"fmt"}, std) + testassert.Check(t, len(problems) == 0, "%s wrongly treated as a retired tree: %v", rel, problems) } } } @@ -732,12 +791,13 @@ func TestDependencyRulesRejectRetiredPackages(t *testing.T) { // helper's exact allowlist. The reverse edge is covered by the directory matrix. func TestDependencyRulesClientHostStartupIsolation(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("internal/clienthost/x.go", []string{ - "context", "errors", "fmt", "os", "os/exec", "path/filepath", "time", - publicModule + "/client", publicModule + "/protocol", publicModule + "/internal/hostprocess", - publicModule + "/internal/clientops", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/clienthost imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/clienthost/x.go", []string{ + "context", "errors", "fmt", "os", "os/exec", "path/filepath", "time", + publicModule + "/client", publicModule + "/protocol", publicModule + "/internal/hostprocess", + publicModule + "/internal/clientops", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/clienthost imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -747,8 +807,9 @@ func TestDependencyRulesClientHostStartupIsolation(t *testing.T) { publicModule + "/internal/desktopclient", publicModule + "/internal/clientview", publicModule + "/internal/acpclient", publicModule + "/internal/version", } { - if problems := checkTrackedGoFile("internal/clienthost/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/clienthost importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/clienthost/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/clienthost importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } } @@ -758,11 +819,12 @@ func TestDependencyRulesClientHostStartupIsolation(t *testing.T) { // protocol contracts. The reverse edge is covered by the directory matrix. func TestDependencyRulesClientViewIsolation(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("internal/clientview/x.go", []string{ - "encoding/json", "errors", "fmt", "strconv", - publicModule + "/protocol", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/clientview imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/clientview/x.go", []string{ + "encoding/json", "errors", "fmt", "strconv", + publicModule + "/protocol", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/clientview imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -772,24 +834,27 @@ func TestDependencyRulesClientViewIsolation(t *testing.T) { publicModule + "/client", publicModule + "/internal/clienthost", publicModule + "/internal/desktopclient", publicModule + "/internal/clientops", } { - if problems := checkTrackedGoFile("internal/clientview/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/clientview importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/clientview/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/clientview importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } } // TestDependencyRulesProtocolGenIsolation enforces the private protocol build -// command's exact allowlist: the standard library and the pinned schema loader -// only. The foundation, the Runtime, the target clients and the generated -// contracts are its nearest forbidden siblings, and another subpackage of the -// same loader module is not an allowed edge either. +// command's exact allowlist: the standard library, the pinned schema loader, +// the pinned SDK generator and the generator's YAML configuration loader. The +// foundation, the Runtime, the target clients and the generated contracts are +// its nearest forbidden siblings, and another subpackage of either schema +// module is not an allowed edge either. func TestDependencyRulesProtocolGenIsolation(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("internal/protocolgen/x.go", []string{ - "context", "encoding/json", "flag", "fmt", "go/format", "os", "sort", "strconv", "strings", "text/template", - kinOpenAPIPkg, - }, std); len(problems) != 0 { - t.Errorf("allowed internal/protocolgen imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/protocolgen/x.go", []string{ + "bytes", "context", "encoding/json", "flag", "fmt", "go/format", "go/token", "os", "sort", "strconv", "strings", "text/template", + kinOpenAPIPkg, codegenPkg, yamlConfigPkg, + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/protocolgen imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -800,10 +865,12 @@ func TestDependencyRulesProtocolGenIsolation(t *testing.T) { publicModule + "/internal/clientops", publicModule + "/internal/desktopclient", publicModule + "/internal/acpclient", publicModule + "/internal/cliclient", publicModule + "/client", publicModule + "/protocol", - oapiRuntimePkg, tiktokenPkg, "github.com/getkin/kin-openapi/openapi3filter", + oapiRuntimePkg, "github.com/getkin/kin-openapi/openapi3filter", + "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen", } { - if problems := checkTrackedGoFile("internal/protocolgen/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/protocolgen importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/protocolgen/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/protocolgen importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } } @@ -813,11 +880,12 @@ func TestDependencyRulesProtocolGenIsolation(t *testing.T) { // matrix. func TestDependencyRulesClientOpsIsolation(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("internal/clientops/x.go", []string{ - "bytes", "context", "encoding/json", "errors", "fmt", "io", "net/http", - publicModule + "/client", publicModule + "/protocol", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/clientops imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/clientops/x.go", []string{ + "bytes", "context", "encoding/json", "errors", "fmt", "io", "net/http", + publicModule + "/client", publicModule + "/protocol", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/clientops imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -828,8 +896,9 @@ func TestDependencyRulesClientOpsIsolation(t *testing.T) { publicModule + "/internal/desktopclient", publicModule + "/internal/acpclient", publicModule + "/internal/version", publicModule + "/internal/hostprocess", } { - if problems := checkTrackedGoFile("internal/clientops/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/clientops importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/clientops/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/clientops importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } } @@ -839,13 +908,14 @@ func TestDependencyRulesClientOpsIsolation(t *testing.T) { // consumers. func TestDependencyRulesACPClientIsolation(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("internal/acpclient/x.go", []string{ - "bufio", "bytes", "context", "encoding/json", "errors", "fmt", "io", "os", "os/signal", - "path/filepath", "slices", "strings", "sync", "syscall", "time", - publicModule + "/client", publicModule + "/protocol", publicModule + "/internal/clienthost", - publicModule + "/internal/clientops", publicModule + "/internal/version", - }, std); len(problems) != 0 { - t.Errorf("allowed internal/acpclient imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/acpclient/x.go", []string{ + "bufio", "bytes", "context", "encoding/json", "errors", "fmt", "io", "os", "os/signal", + "path/filepath", "slices", "strings", "sync", "syscall", "time", + publicModule + "/client", publicModule + "/protocol", publicModule + "/internal/clienthost", + publicModule + "/internal/clientops", publicModule + "/internal/version", + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/acpclient imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -855,8 +925,9 @@ func TestDependencyRulesACPClientIsolation(t *testing.T) { publicModule + "/internal/clientview", publicModule + "/internal/desktopclient", publicModule + "/internal/hostprocess", } { - if problems := checkTrackedGoFile("internal/acpclient/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/acpclient importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/acpclient/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/acpclient importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } // Root files and other sibling client hosts cannot reach @@ -867,8 +938,9 @@ func TestDependencyRulesACPClientIsolation(t *testing.T) { if imp == publicModule+"/internal/clientops" && (rel == "internal/clienthost/x.go" || rel == "internal/desktopclient/x.go") { continue } - if problems := checkTrackedGoFile(rel, []string{imp}, std); len(problems) != 1 { - t.Errorf("%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{imp}, std) + testassert.Check(t, len(problems) == 1, "%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) } } } @@ -880,13 +952,14 @@ func TestDependencyRulesACPClientIsolation(t *testing.T) { // fallback rows. func TestDependencyRulesDesktopHostIsolation(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("internal/desktopclient/x.go", []string{ - "bytes", "context", "encoding/json", "errors", "fmt", "io/fs", "path/filepath", "strconv", "strings", "sync", - publicModule + "/client", publicModule + "/protocol", publicModule + "/internal/clienthost", - publicModule + "/internal/clientops", publicModule + "/internal/version", - wailsPkg, wailsRuntimePkg, wailsOptionsPkg, wailsAssetPkg, wailsLinuxPkg, - }, std); len(problems) != 0 { - t.Errorf("allowed internal/desktopclient imports flagged: %v", problems) + { + problems := checkTrackedGoFile("internal/desktopclient/x.go", []string{ + "bytes", "context", "encoding/json", "errors", "fmt", "io/fs", "path/filepath", "strconv", "strings", "sync", + publicModule + "/client", publicModule + "/protocol", publicModule + "/internal/clienthost", + publicModule + "/internal/clientops", publicModule + "/internal/version", + wailsPkg, wailsRuntimePkg, wailsOptionsPkg, wailsAssetPkg, wailsLinuxPkg, + }, std) + testassert.Check(t, len(problems) == 0, "allowed internal/desktopclient imports flagged: %v", problems) } for _, imp := range []string{ publicModule + "/model", publicModule + "/agent", publicModule + "/harness", @@ -895,8 +968,9 @@ func TestDependencyRulesDesktopHostIsolation(t *testing.T) { publicModule + "/internal/terminal", publicModule + "/internal/cli", publicModule + "/internal/clientview", } { - if problems := checkTrackedGoFile("internal/desktopclient/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("internal/desktopclient importing %q: %d problems, want 1: %v", imp, len(problems), problems) + { + problems := checkTrackedGoFile("internal/desktopclient/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "internal/desktopclient importing %q: %d problems, want 1: %v", imp, len(problems), problems) } } } @@ -907,17 +981,20 @@ func TestDependencyRulesCliclientIsolation(t *testing.T) { for _, pkg := range []string{"client", "protocol", "internal/clienthost", "internal/clientview", "internal/clientops", "internal/terminal"} { allowed = append(allowed, publicModule+"/"+pkg) } - if problems := checkTrackedGoFile("internal/cliclient/x.go", allowed, std); len(problems) != 0 { - t.Fatal(problems) + { + problems := checkTrackedGoFile("internal/cliclient/x.go", allowed, std) + testassert.Require(t, len(problems) == 0, "%v", problems) } for _, imp := range []string{publicModule + "/agent", publicModule + "/harness", publicModule + "/runtime", publicModule + "/internal/agent", publicModule + "/internal/cli", publicModule + "/internal/storage", publicModule + "/internal/plugins/tools", publicModule + "/internal/hostprocess", publicModule + "/internal/desktopclient", publicModule + "/internal/acpclient", publicModule + "/internal/version", sqliteDriverPkg, "golang.org/x/sys/unix"} { - if problems := checkTrackedGoFile("internal/cliclient/x.go", []string{imp}, std); len(problems) != 1 { - t.Errorf("forbidden %s: %v", imp, problems) + { + problems := checkTrackedGoFile("internal/cliclient/x.go", []string{imp}, std) + testassert.Check(t, len(problems) == 1, "forbidden %s: %v", imp, problems) } } for _, rel := range []string{"rootmisc.go", "app.go", "commands.go", "internal/clienthost/x.go", "internal/clientview/x.go", "internal/clientops/x.go", "internal/desktopclient/x.go", "internal/acpclient/x.go", "internal/cliclient/sub/x.go", "cmd/desktopclient/x.go"} { - if problems := checkTrackedGoFile(rel, []string{publicModule + "/internal/cliclient"}, std); len(problems) != 1 { - t.Errorf("reverse %s: %v", rel, problems) + { + problems := checkTrackedGoFile(rel, []string{publicModule + "/internal/cliclient"}, std) + testassert.Check(t, len(problems) == 1, "reverse %s: %v", rel, problems) } } } @@ -931,27 +1008,30 @@ func TestDependencyRulesCliclientIsolation(t *testing.T) { // Runtime, plugins and storage stay rejected exactly once. func TestDependencyRulesRootEntryRoles(t *testing.T) { std := standardLibraryImports(t) - if problems := checkTrackedGoFile("main.go", []string{ - "context", "embed", "errors", "fmt", "log/slog", "os", "path/filepath", - publicModule + "/internal/clienthost", publicModule + "/internal/desktopclient", - publicModule + "/internal/cliclient", publicModule + "/internal/acpclient", - }, std); len(problems) != 0 { - t.Errorf("allowed main.go imports flagged: %v", problems) - } - if problems := checkTrackedGoFile("commands.go", []string{ - "bufio", "context", "encoding/json", "errors", "flag", "fmt", "io", "os", "os/signal", - "path/filepath", "runtime", "strings", "syscall", - publicModule + "/internal/clienthost", publicModule + "/internal/config", - publicModule + "/internal/doctor", publicModule + "/internal/selfupdate", - publicModule + "/internal/version", - }, std); len(problems) != 0 { - t.Errorf("allowed commands.go imports flagged: %v", problems) - } - if problems := checkTrackedGoFile("runtime_command.go", []string{ - "context", "errors", "path/filepath", - publicModule + "/runtime", publicModule + "/internal/plugins/builtin", - }, std); len(problems) != 0 { - t.Errorf("allowed runtime_command.go imports flagged: %v", problems) + { + problems := checkTrackedGoFile("main.go", []string{ + "context", "embed", "errors", "fmt", "log/slog", "os", "path/filepath", + publicModule + "/internal/clienthost", publicModule + "/internal/desktopclient", + publicModule + "/internal/cliclient", publicModule + "/internal/acpclient", + }, std) + testassert.Check(t, len(problems) == 0, "allowed main.go imports flagged: %v", problems) + } + { + problems := checkTrackedGoFile("commands.go", []string{ + "bufio", "context", "encoding/json", "errors", "flag", "fmt", "io", "os", "os/signal", + "path/filepath", "runtime", "strings", "syscall", + publicModule + "/internal/clienthost", publicModule + "/internal/config", + publicModule + "/internal/doctor", publicModule + "/internal/selfupdate", + publicModule + "/internal/version", + }, std) + testassert.Check(t, len(problems) == 0, "allowed commands.go imports flagged: %v", problems) + } + { + problems := checkTrackedGoFile("runtime_command.go", []string{ + "context", "errors", "path/filepath", + publicModule + "/runtime", publicModule + "/internal/plugins/builtin", + }, std) + testassert.Check(t, len(problems) == 0, "allowed runtime_command.go imports flagged: %v", problems) } for _, row := range []struct{ rel, imp string }{ // The Runtime composition exception belongs only to the runtime @@ -998,8 +1078,9 @@ func TestDependencyRulesRootEntryRoles(t *testing.T) { {"rootmisc.go", publicModule + "/internal/plugins/builtin"}, {"rootmisc.go", publicModule + "/internal/storage"}, } { - if problems := checkTrackedGoFile(row.rel, []string{row.imp}, std); len(problems) != 1 { - t.Errorf("%s importing %q: %d problems, want 1: %v", row.rel, row.imp, len(problems), problems) + { + problems := checkTrackedGoFile(row.rel, []string{row.imp}, std) + testassert.Check(t, len(problems) == 1, "%s importing %q: %d problems, want 1: %v", row.rel, row.imp, len(problems), problems) } } } @@ -1009,18 +1090,21 @@ func TestDependencyRulesCheckEveryTrackedDirectory(t *testing.T) { std := standardLibraryImports(t) for _, rel := range []string{".hidden/pkg/x.go", "_scaffold/pkg/x.go", "frontend/bindata.go", "node_modules/pkg/x.go", "vendor/pkg/x.go", "rootmisc.go", "internal/anything/x.go", "cmd/desktopclient/x.go", "cmd/desktopclient/sub/x.go"} { for _, imp := range []string{publicModule + "/model", publicModule + "/agent", publicModule + "/harness", publicModule + "/internal/storage", publicModule + "/runtime", publicModule + "/internal/plugins/sqlite", publicModule + "/internal/plugins/tools", publicModule + "/internal/plugins/adaptation", publicModule + "/internal/plugins/lsp", publicModule + "/internal/plugins/builtin", publicModule + "/plugins/jobs", publicModule + "/internal/toolargs", publicModule + "/protocol", publicModule + "/internal/clienthost", publicModule + "/internal/clientview", publicModule + "/internal/clientops", publicModule + "/internal/acpclient", publicModule + "/internal/cliclient", publicModule + "/internal/desktopclient", sqliteDriverPkg} { - if problems := checkTrackedGoFile(rel, []string{imp}, std); len(problems) != 1 { - t.Errorf("tracked %q importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{imp}, std) + testassert.Check(t, len(problems) == 1, "tracked %q importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems) } } } for _, rel := range []string{"model/sub/x.go", "agent/sub/x.go", "harness/sub/x.go", "internal/storage/sub/x.go", "plugins/jobs/sub/x.go"} { - if problems := checkTrackedGoFile(rel, []string{"fmt"}, std); len(problems) != 1 { - t.Errorf("subpackage file %q: %d problems, want 1: %v", rel, len(problems), problems) + { + problems := checkTrackedGoFile(rel, []string{"fmt"}, std) + testassert.Check(t, len(problems) == 1, "subpackage file %q: %d problems, want 1: %v", rel, len(problems), problems) } } - if problems := checkTrackedGoFile("internal/model/x.go", []string{"fmt"}, std); len(problems) != 0 { - t.Errorf("unrelated internal/model file wrongly treated as a foundation subpackage: %v", problems) + { + problems := checkTrackedGoFile("internal/model/x.go", []string{"fmt"}, std) + testassert.Check(t, len(problems) == 0, "unrelated internal/model file wrongly treated as a foundation subpackage: %v", problems) } } @@ -1030,8 +1114,9 @@ func trackedGoFiles(t *testing.T, root string) []string { out := bytes.Buffer{} cmd := exec.Command("git", "-C", root, "ls-files", "-z") cmd.Stdout = &out - if err := cmd.Run(); err != nil { - t.Fatalf("listing tracked files under %s: %v", root, err) + { + err := cmd.Run() + testassert.Require(t, err == nil, "listing tracked files under %s: %v", root, err) } var files []string for _, rel := range strings.Split(out.String(), "\x00") { @@ -1040,9 +1125,7 @@ func trackedGoFiles(t *testing.T, root string) []string { } files = append(files, rel) } - if len(files) == 0 { - t.Fatalf("git tracked no Go files under %s", root) - } + testassert.Require(t, len(files) != 0, "git tracked no Go files under %s", root) return files } @@ -1050,9 +1133,7 @@ func trackedGoFiles(t *testing.T, root string) []string { func standardLibraryImports(t *testing.T) map[string]bool { t.Helper() out, err := exec.Command("go", "list", "std").Output() - if err != nil { - t.Fatalf("go list std: %v", err) - } + testassert.NoError(t, err) std := make(map[string]bool) for _, imp := range strings.Fields(string(out)) { std[imp] = true @@ -1065,17 +1146,13 @@ func standardLibraryImports(t *testing.T) map[string]bool { func moduleRoot(t *testing.T) string { t.Helper() dir, err := os.Getwd() - if err != nil { - t.Fatalf("working directory: %v", err) - } + testassert.NoError(t, err) for { if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir } parent := filepath.Dir(dir) - if parent == dir { - t.Fatalf("no go.mod found above %s", dir) - } + testassert.Require(t, parent != dir, "no go.mod found above %s", dir) dir = parent } } @@ -1101,18 +1178,16 @@ func parseFileImports(path string) ([]string, error) { // TestParseFileImportsCanonicalizesRawStringImports is the false-rejection regression: import literals must go through the standard Go string-literal decoder, so a raw-string (backquoted) import yields its canonical path and a compliant agent file written that way still passes the allowlist after extraction. func TestParseFileImportsCanonicalizesRawStringImports(t *testing.T) { file := filepath.Join(t.TempDir(), "x.go") - if err := os.WriteFile(file, []byte("package agent\n\nimport `github.com/MMinasyan/lightcode/model`\nimport `fmt`\n"), 0o644); err != nil { - t.Fatalf("writing fixture: %v", err) + { + err := os.WriteFile(file, []byte("package agent\n\nimport `github.com/MMinasyan/lightcode/model`\nimport `fmt`\n"), 0o644) + testassert.NoError(t, err) } imports, err := parseFileImports(file) - if err != nil { - t.Fatalf("parseFileImports: %v", err) - } + testassert.NoError(t, err) want := []string{publicModule + "/model", "fmt"} - if len(imports) != len(want) || imports[0] != want[0] || imports[1] != want[1] { - t.Fatalf("import paths = %q, want canonical %q", imports, want) - } - if problems := checkTrackedGoFile("agent/x.go", imports, standardLibraryImports(t)); len(problems) != 0 { - t.Errorf("raw-string imports rejected by the agent allowlist: %v", problems) + testassert.Require(t, len(imports) == len(want) && imports[0] == want[0] && imports[1] == want[1], "import paths = %q, want canonical %q", imports, want) + { + problems := checkTrackedGoFile("agent/x.go", imports, standardLibraryImports(t)) + testassert.Check(t, len(problems) == 0, "raw-string imports rejected by the agent allowlist: %v", problems) } } diff --git a/agent/run_callback_test.go b/agent/run_callback_test.go index 72b61dac..44bc4b60 100644 --- a/agent/run_callback_test.go +++ b/agent/run_callback_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -19,9 +20,7 @@ func TestRunCallbackZeroCallsWithOutputRejected(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if f.modelCalls != 1 || f.toolCalls != 0 { - t.Fatalf("model=%d tool=%d, want the run to stop before dispatch", f.modelCalls, f.toolCalls) - } + testassert.Require(t, f.modelCalls == 1 && f.toolCalls == 0, "model=%d tool=%d, want the run to stop before dispatch", f.modelCalls, f.toolCalls) } // TestRunCallbackOneCallWithNoOutputSettlementRejected pins the other half of the count rule: an outputless settlement (a legitimate no-accepted-stream failure shape) requires zero callbacks, so accepting model work and then discarding its output is a protocol violation too. @@ -52,8 +51,9 @@ func TestRunCallbackSecondAttemptRejectedWithoutSecondAssembly(t *testing.T) { return ModelSettlement{}, err } first = out - if _, err2 := cb(testRef, secondStream); !errors.Is(err2, ErrBoundaryProtocol) { - t.Fatalf("second callback attempt returned %v, want a typed boundary-protocol error", err2) + { + _, err2 := cb(testRef, secondStream) + testassert.Require(t, errors.Is(err2, ErrBoundaryProtocol), "second callback attempt returned %v, want a typed boundary-protocol error", err2) } return ModelSettlement{Disposition: DispoReady, Output: &first}, nil } @@ -61,12 +61,8 @@ func TestRunCallbackSecondAttemptRejectedWithoutSecondAssembly(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if secondStream.recvCount != 0 || secondStream.closeCount != 0 { - t.Fatalf("second callback entered an assembly path: recv=%d close=%d", secondStream.recvCount, secondStream.closeCount) - } - if firstStream.closeCount != 1 { - t.Fatalf("first stream close = %d, want 1", firstStream.closeCount) - } + testassert.Require(t, secondStream.recvCount == 0 && secondStream.closeCount == 0, "second callback entered an assembly path: recv=%d close=%d", secondStream.recvCount, secondStream.closeCount) + testassert.Equal(t, firstStream.closeCount, 1) } // TestRunCallbackForeignSourceRejectedEvenWithValidSettlement pins the callback-source rule: a source unequal to the invocation's expected model identity fails the callback before assembly (the stream is never touched), and an effect that ignores that error still cannot rescue the run with an otherwise-valid expected-source settlement. @@ -75,8 +71,9 @@ func TestRunCallbackForeignSourceRejectedEvenWithValidSettlement(t *testing.T) { foreign := model.ModelRef{Provider: "acme", Model: "other"} stream := newFakeStream(deltaStep(choiceDelta(txtPos(0, "x")))) f.modelSet = func(_ int, _ model.Request, cb AssemblyCallback) (ModelSettlement, error) { - if _, err := cb(foreign, stream); !errors.Is(err, ErrBoundaryProtocol) { - t.Fatalf("callback returned %v for a foreign source, want a typed boundary-protocol error", err) + { + _, err := cb(foreign, stream) + testassert.Require(t, errors.Is(err, ErrBoundaryProtocol), "callback returned %v for a foreign source, want a typed boundary-protocol error", err) } return ModelSettlement{Disposition: DispoReady, Output: mkOutput(model.OutputCompleted, "", nil)}, nil } @@ -84,17 +81,16 @@ func TestRunCallbackForeignSourceRejectedEvenWithValidSettlement(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if stream.recvCount != 0 || stream.closeCount != 0 { - t.Fatalf("foreign-source call reached assembly: recv=%d close=%d", stream.recvCount, stream.closeCount) - } + testassert.Require(t, stream.recvCount == 0 && stream.closeCount == 0, "foreign-source call reached assembly: recv=%d close=%d", stream.recvCount, stream.closeCount) } // TestRunCallbackInvalidCallErrorIgnored pins the remembered-error rule: an assembler invalid-call failure (here a nil stream) is recorded even when the callback was otherwise correctly invoked once, so an effect cannot drop the error on the floor and settle an otherwise-valid output. func TestRunCallbackInvalidCallErrorIgnored(t *testing.T) { f := &runFakes{noCallbackFill: true} f.modelSet = func(_ int, _ model.Request, cb AssemblyCallback) (ModelSettlement, error) { - if _, err := cb(testRef, nil); err == nil { - t.Fatal("callback accepted a nil stream") + { + _, err := cb(testRef, nil) + testassert.Require(t, err != nil, "%v", "callback accepted a nil stream") } return ModelSettlement{Disposition: DispoReady, Output: mkOutput(model.OutputCompleted, "", nil)}, nil } @@ -102,7 +98,5 @@ func TestRunCallbackInvalidCallErrorIgnored(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "agent") // the remembered invalid-call failure is Assemble's own typed agent-boundary violation and surfaces verbatim. requireZeroTerminal(t, res) - if f.modelCalls != 1 || f.toolCalls != 0 { - t.Fatalf("model=%d tool=%d, want the run to stop at the ignored callback error", f.modelCalls, f.toolCalls) - } + testassert.Require(t, f.modelCalls == 1 && f.toolCalls == 0, "model=%d tool=%d, want the run to stop at the ignored callback error", f.modelCalls, f.toolCalls) } diff --git a/agent/run_cancel_test.go b/agent/run_cancel_test.go index 02dce607..20544526 100644 --- a/agent/run_cancel_test.go +++ b/agent/run_cancel_test.go @@ -6,6 +6,7 @@ import ( "fmt" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -30,15 +31,11 @@ func TestRunCancelledAfterSettledBatchRetainsCompletedOutput(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != callsOut { - t.Fatalf("interruption dropped the completed output: %p vs %p", res.LastOutput, callsOut) - } + testassert.Equal(t, res.LastOutput, callsOut) if len(res.UnstartedCalls) != 0 { t.Fatalf("unstarted = %s, want none after a fully settled batch", joinedIDs(res.UnstartedCalls)) } - if f.modelCalls != 1 { - t.Fatalf("model calls = %d, want no second effect after cancellation", f.modelCalls) - } + testassert.Equal(t, f.modelCalls, 1) } // TestRunCancelledBeforeFirstEffect pins the very first checkpoint: an already-done run context ends in a bare interruption with no caller function ever starting. @@ -49,12 +46,8 @@ func TestRunCancelledBeforeFirstEffect(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != nil || res.UnstartedCalls != nil { - t.Fatalf("bare interruption expected, got %#v", res) - } - if f.sourceCalls != 0 || f.modelCalls != 0 || f.toolCalls != 0 { - t.Fatalf("cancelled run started caller functions: source=%d model=%d tool=%d", f.sourceCalls, f.modelCalls, f.toolCalls) - } + testassert.Require(t, res.LastOutput == nil && res.UnstartedCalls == nil, "bare interruption expected, got %#v", res) + testassert.Require(t, f.sourceCalls == 0 && f.modelCalls == 0 && f.toolCalls == 0, "cancelled run started caller functions: source=%d model=%d tool=%d", f.sourceCalls, f.modelCalls, f.toolCalls) } // TestRunCancelledDuringSnapshot pins the checkpoint between obtaining the fresh context and invoking the model effect: the effect must never start. @@ -65,9 +58,7 @@ func TestRunCancelledDuringSnapshot(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if f.sourceCalls != 1 || f.modelCalls != 0 || f.toolCalls != 0 { - t.Fatalf("source=%d model=%d tool=%d, want 1/0/0", f.sourceCalls, f.modelCalls, f.toolCalls) - } + testassert.Require(t, f.sourceCalls == 1 && f.modelCalls == 0 && f.toolCalls == 0, "source=%d model=%d tool=%d, want 1/0/0", f.sourceCalls, f.modelCalls, f.toolCalls) } // TestRunContextSourceCancellationRouting pins both halves of the source rule: a cancellation report under a genuinely done run context maps to Agent interruption, while the same-shaped report under a live context is an ordinary infrastructure error returned verbatim with no terminal. @@ -81,9 +72,7 @@ func TestRunContextSourceCancellationRouting(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if f.modelCalls != 0 { - t.Fatalf("model calls = %d, want 0", f.modelCalls) - } + testassert.Equal(t, f.modelCalls, 0) }) t.Run("live-context-infrastructure-error", func(t *testing.T) { @@ -92,12 +81,8 @@ func TestRunContextSourceCancellationRouting(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireZeroTerminal(t, res) - if !errors.Is(err, context.Canceled) { - t.Fatalf("error = %v, want the verbatim cancellation-shaped infrastructure failure", err) - } - if f.modelCalls != 0 || f.toolCalls != 0 { - t.Fatalf("later effects started: model=%d tool=%d", f.modelCalls, f.toolCalls) - } + testassert.Require(t, errors.Is(err, context.Canceled), "error = %v, want the verbatim cancellation-shaped infrastructure failure", err) + testassert.Require(t, f.modelCalls == 0 && f.toolCalls == 0, "later effects started: model=%d tool=%d", f.modelCalls, f.toolCalls) }) t.Run("deadline-under-done-context-interruption", func(t *testing.T) { @@ -124,15 +109,12 @@ func TestRunCancelledBeforeToolDispatch(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != callsOut { - t.Fatalf("interruption dropped the completed output: %p vs %p", res.LastOutput, callsOut) - } - if got := joinedIDs(res.UnstartedCalls); got != "[a b c]" { - t.Fatalf("unstarted = %s, want all three calls in order", got) - } - if f.toolCalls != 0 { - t.Fatalf("tool calls = %d, want 0 — no effect starts after cancellation", f.toolCalls) + testassert.Equal(t, res.LastOutput, callsOut) + { + got := joinedIDs(res.UnstartedCalls) + testassert.Equal(t, got, "[a b c]") } + testassert.Equal(t, f.toolCalls, 0) } // TestRunCancelledAfterSettledToolPinsLaterUnstarted pins that after a settled tool result only the LATER calls are unstarted: the already-answered call never reappears and the batch stops at the checkpoint. @@ -150,15 +132,12 @@ func TestRunCancelledAfterSettledToolPinsLaterUnstarted(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != callsOut { - t.Fatalf("interruption dropped the completed output") - } - if got := joinedIDs(res.UnstartedCalls); got != "[b c]" { - t.Fatalf("unstarted = %s, want only calls after the settled one", got) - } - if f.toolCalls != 1 { - t.Fatalf("tool calls = %d, want the batch to stop after call a", f.toolCalls) + testassert.Equal(t, res.LastOutput, callsOut) + { + got := joinedIDs(res.UnstartedCalls) + testassert.Equal(t, got, "[b c]") } + testassert.Equal(t, f.toolCalls, 1) } // TestRunStepSevenCancellationCarriesCompletedOutput pins the last checkpoint on a call-free response: the established completion is never discarded — the interruption carries exactly the output the run would have succeeded with. @@ -172,9 +151,7 @@ func TestRunStepSevenCancellationCarriesCompletedOutput(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != final || res.UnstartedCalls != nil { - t.Fatalf("terminal = %#v, want the completed output carried verbatim", res) - } + testassert.Require(t, res.LastOutput == final && res.UnstartedCalls == nil, "terminal = %#v, want the completed output carried verbatim", res) } // TestRunInterruptedToolResultStopsBatch pins the interrupted-tool row: the batch stops immediately, the terminal detail is the fixed interruption text, the settled interrupted call is not unstarted, and only later calls are. @@ -189,15 +166,12 @@ func TestRunInterruptedToolResultStopsBatch(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != callsOut { - t.Fatalf("interruption dropped the completed output") - } - if got := joinedIDs(res.UnstartedCalls); got != "[b c]" { - t.Fatalf("unstarted = %s, want only calls after the settled interrupted one", got) - } - if f.toolCalls != 1 { - t.Fatalf("tool calls = %d, want no later effect to start", f.toolCalls) + testassert.Equal(t, res.LastOutput, callsOut) + { + got := joinedIDs(res.UnstartedCalls) + testassert.Equal(t, got, "[b c]") } + testassert.Equal(t, f.toolCalls, 1) } // TestRunEffectCancellationErrorsAreProtocolViolations pins that active-effect cancellation must come back as a settlement, never as a Go error: a cancellation-shaped error out of either effect is one typed boundary-protocol error naming that effect's boundary even while the run context is done. @@ -213,9 +187,7 @@ func TestRunEffectCancellationErrorsAreProtocolViolations(t *testing.T) { res, err := Run(ctx, f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if f.toolCalls != 0 { - t.Fatalf("tool calls = %d, want 0", f.toolCalls) - } + testassert.Equal(t, f.toolCalls, 0) }) t.Run("model-effect-live-context", func(t *testing.T) { @@ -268,9 +240,7 @@ func TestRunInfraErrorsWinOverCancellation(t *testing.T) { res, err := Run(ctx, f.invocation()) requireZeroTerminal(t, res) - if !errors.Is(err, errInfra) { - t.Fatalf("error = %v, want the infrastructure error verbatim", err) - } + testassert.Require(t, errors.Is(err, errInfra), "error = %v, want the infrastructure error verbatim", err) }) t.Run("tool-effect", func(t *testing.T) { @@ -284,12 +254,8 @@ func TestRunInfraErrorsWinOverCancellation(t *testing.T) { res, err := Run(ctx, f.invocation()) requireZeroTerminal(t, res) - if !errors.Is(err, errInfra) { - t.Fatalf("error = %v, want the infrastructure error verbatim", err) - } - if f.toolCalls != 1 { - t.Fatalf("tool calls = %d, want the batch to stop at the failing call", f.toolCalls) - } + testassert.Require(t, errors.Is(err, errInfra), "error = %v, want the infrastructure error verbatim", err) + testassert.Equal(t, f.toolCalls, 1) }) } @@ -310,10 +276,7 @@ func TestRunCancellationDominatesCap(t *testing.T) { res, err := Run(ctx, f.invocation()) requireRunInterrupted(t, res, err, "agent interrupted") - if res.LastOutput != nil { // the last settlement was errored, which no interruption terminal may carry. - t.Fatalf("interruption carried %#v, want no output", res.LastOutput) - } - if f.modelCalls != maxModelEffects || f.sourceCalls != maxModelEffects { - t.Fatalf("model=%d source=%d, want 25/25", f.modelCalls, f.sourceCalls) - } + // the last settlement was errored, which no interruption terminal may carry. + testassert.Require(t, res.LastOutput == nil, "interruption carried %#v, want no output", res.LastOutput) + testassert.Require(t, f.modelCalls == maxModelEffects && f.sourceCalls == maxModelEffects, "model=%d source=%d, want 25/25", f.modelCalls, f.sourceCalls) } diff --git a/agent/run_loop_test.go b/agent/run_loop_test.go index e9828239..89fb32a5 100644 --- a/agent/run_loop_test.go +++ b/agent/run_loop_test.go @@ -6,6 +6,7 @@ import ( "fmt" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -32,20 +33,15 @@ func TestRunContinuationUsesFreshSnapshotPerEffect(t *testing.T) { ) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success", res, err) - } - if f.sourceCalls != 3 || f.modelCalls != 3 { - t.Fatalf("source=%d model=%d, want 3/3", f.sourceCalls, f.modelCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success", res, err) + testassert.Require(t, f.sourceCalls == 3 && f.modelCalls == 3, "source=%d model=%d, want 3/3", f.sourceCalls, f.modelCalls) for i, want := range []string{"a", "b", "c"} { // one fresh snapshot per logical effect: after the settled batch and after the continue disposition alike. - if got := f.reqs[i].Messages[0].TextContent(); got != want { - t.Fatalf("request %d snapshot = %q, want %q", i, got, want) + { + got := f.reqs[i].Messages[0].TextContent() + testassert.Require(t, got == want, "request %d snapshot = %q, want %q", i, got, want) } } - if res.LastOutput == nil || res.LastOutput.Status != model.OutputCompleted { - t.Fatalf("last output = %#v, want the final completed settlement", res.LastOutput) - } + testassert.Require(t, res.LastOutput != nil && res.LastOutput.Status == model.OutputCompleted, "last output = %#v, want the final completed settlement", res.LastOutput) } // TestRunRequestToolsAreFreshCopiesPerEffect pins the per-request tool ownership rule: every model effect receives its own deep copy of the entry-validated advertised list — slice and Parameters bytes — so a model-effect callback mutating the handed request's tool slice and bytes in place cannot reach the immutable advertised baseline, any later request, or the invocation input. @@ -70,33 +66,19 @@ func TestRunRequestToolsAreFreshCopiesPerEffect(t *testing.T) { } res, err := Run(context.Background(), inv) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success", res, err) - } - if f.modelCalls != 2 { - t.Fatalf("model calls = %d, want 2", f.modelCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success", res, err) + testassert.Equal(t, f.modelCalls, 2) // Every request started from the pristine advertised values. for i, got := range handed { - if got.Name != "t" || string(got.Parameters) != `{"type":"object"}` { - t.Fatalf("request %d carried a mutated tool: %#v", i+1, got) - } + testassert.Require(t, got.Name == "t" && string(got.Parameters) == `{"type":"object"}`, "request %d carried a mutated tool: %#v", i+1, got) } // The mutation actually landed in the recorded requests — the check above is not vacuous. - if f.reqs[0].Tools[0].Name != "mutated" || f.reqs[1].Tools[0].Name != "mutated" { - t.Fatalf("recorded requests show %q/%q, want the callback's in-place mutations", f.reqs[0].Tools[0].Name, f.reqs[1].Tools[0].Name) - } + testassert.Require(t, f.reqs[0].Tools[0].Name == "mutated" && f.reqs[1].Tools[0].Name == "mutated", "recorded requests show %q/%q, want the callback's in-place mutations", f.reqs[0].Tools[0].Name, f.reqs[1].Tools[0].Name) // The invocation input keeps exactly its own entry values. - if advertised[0].Name != "t" || string(advertised[0].Parameters) != `{"type":"object"}` { - t.Fatalf("advertised baseline mutated through a request alias: %#v", advertised[0]) - } + testassert.Require(t, advertised[0].Name == "t" && string(advertised[0].Parameters) == `{"type":"object"}`, "advertised baseline mutated through a request alias: %#v", advertised[0]) // Each request owns independent storage: no request aliases the advertised bytes, and successive requests share nothing. - if &f.reqs[0].Tools[0].Parameters[0] == &advertised[0].Parameters[0] { - t.Fatal("a request gained an alias to the advertised baseline bytes") - } - if &f.reqs[0].Tools[0].Parameters[0] == &f.reqs[1].Tools[0].Parameters[0] { - t.Fatal("successive requests share tool Parameters backing") - } + testassert.Require(t, &f.reqs[0].Tools[0].Parameters[0] != &advertised[0].Parameters[0], "%v", "a request gained an alias to the advertised baseline bytes") + testassert.Require(t, &f.reqs[0].Tools[0].Parameters[0] != &f.reqs[1].Tools[0].Parameters[0], "%v", "successive requests share tool Parameters backing") } // TestRunToolBatchSettlesInOrderThenContinues pins sequential in-order dispatch with a settled complete non-interrupted batch feeding the next fresh-context effect, which then ends the run. @@ -112,18 +94,13 @@ func TestRunToolBatchSettlesInOrderThenContinues(t *testing.T) { ) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success", res, err) - } - if f.modelCalls != 2 || f.sourceCalls != 2 || f.toolCalls != 3 { - t.Fatalf("model=%d source=%d tool=%d, want 2/2/3", f.modelCalls, f.sourceCalls, f.toolCalls) - } - if got := joinedIDs(f.calls); got != "[a b c]" { - t.Fatalf("dispatch order = %s, want assembled order", got) - } - if res.LastOutput != final { - t.Fatalf("terminal carries %p, want the second settlement %p", res.LastOutput, final) + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success", res, err) + testassert.Require(t, f.modelCalls == 2 && f.sourceCalls == 2 && f.toolCalls == 3, "model=%d source=%d tool=%d, want 2/2/3", f.modelCalls, f.sourceCalls, f.toolCalls) + { + got := joinedIDs(f.calls) + testassert.Equal(t, got, "[a b c]") } + testassert.Equal(t, res.LastOutput, final) } // TestRunDeniedAndErrorResultsNeverStopTheBatch pins that per-call settlements other than interrupted settle only their own call: the surrounding batch completes and the run continues. @@ -133,12 +110,8 @@ func TestRunDeniedAndErrorResultsNeverStopTheBatch(t *testing.T) { f.modelSet = readyOutputs(callsOut, mkOutput(model.OutputCompleted, "", nil)) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success via default all-success tool script", res, err) - } - if f.toolCalls != 3 || f.modelCalls != 2 { - t.Fatalf("tool=%d model=%d, want 3/2", f.toolCalls, f.modelCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success via default all-success tool script", res, err) + testassert.Require(t, f.toolCalls == 3 && f.modelCalls == 2, "tool=%d model=%d, want 3/2", f.toolCalls, f.modelCalls) } // TestRunCapExhaustedAfterContinue pins the cap failure shape: after 25 continues, Agent fails with the last errored output and the fixed detail before a 26th request — and before another snapshot fetch. @@ -151,18 +124,10 @@ func TestRunCapExhaustedAfterContinue(t *testing.T) { f.modelSet = setScript(sets...) res, err := Run(context.Background(), f.invocation()) - if err != nil { - t.Fatalf("run failed: %v", err) - } - if res.Status != TerminalFailure || res.Detail != "agent exceeded 25 model effects" { - t.Fatalf("terminal = %s/%q, want failure with the cap detail", res.Status, res.Detail) - } - if res.LastOutput != sets[maxModelEffects-1].Output { - t.Fatalf("cap failure carries %p, want the 25th settlement output %p", res.LastOutput, sets[maxModelEffects-1].Output) - } - if f.modelCalls != maxModelEffects || f.sourceCalls != maxModelEffects { - t.Fatalf("model=%d source=%d, want 25/25 — the cap ends the run before a 26th request", f.modelCalls, f.sourceCalls) - } + testassert.NoError(t, err) + testassert.Require(t, res.Status == TerminalFailure && res.Detail == "agent exceeded 25 model effects", "terminal = %s/%q, want failure with the cap detail", res.Status, res.Detail) + testassert.Equal(t, res.LastOutput, sets[maxModelEffects-1].Output) + testassert.Require(t, f.modelCalls == maxModelEffects && f.sourceCalls == maxModelEffects, "model=%d source=%d, want 25/25 — the cap ends the run before a 26th request", f.modelCalls, f.sourceCalls) } // TestRunFinalTextFromEffect25Succeeds pins that the 25th logical model effect may still finish the run cleanly: its call-free completed output succeeds instead of tripping the cap. @@ -176,12 +141,8 @@ func TestRunFinalTextFromEffect25Succeeds(t *testing.T) { f.modelSet = setScript(sets...) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success from the 25th effect", res, err) - } - if f.modelCalls != maxModelEffects { - t.Fatalf("model=%d, want 25", f.modelCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success from the 25th effect", res, err) + testassert.Equal(t, f.modelCalls, maxModelEffects) } // TestRunEffect25SettlesBatchBeforeCapFailure pins that a completed tool batch from the 25th effect fully settles — every call dispatched — and only then fails with that last completed output under the cap detail. @@ -196,18 +157,9 @@ func TestRunEffect25SettlesBatchBeforeCapFailure(t *testing.T) { f.modelSet = setScript(sets...) res, err := Run(context.Background(), f.invocation()) - if err != nil { - t.Fatalf("run failed: %v", err) - } - if res.Status != TerminalFailure || res.Detail != "agent exceeded 25 model effects" { - t.Fatalf("terminal = %s/%q, want cap failure", res.Status, res.Detail) - } - if res.LastOutput != callsOut { - t.Fatalf("cap failure carries %p, want the 25th completed output %p", res.LastOutput, callsOut) - } - if f.toolCalls != 3 || f.modelCalls != maxModelEffects || f.sourceCalls != maxModelEffects { - t.Fatalf("tool=%d model=%d source=%d, want 3/25/25 — the batch settles before the cap ends the run", f.toolCalls, f.modelCalls, f.sourceCalls) - } + testassert.NoError(t, err) + testassert.Require(t, res.Status == TerminalFailure && res.Detail == "agent exceeded 25 model effects" && res.LastOutput == callsOut, "terminal = %s/%q, want cap failure; got %v, want %v", res.Status, res.Detail, res.LastOutput, callsOut) + testassert.Require(t, f.toolCalls == 3 && f.modelCalls == maxModelEffects && f.sourceCalls == maxModelEffects, "tool=%d model=%d source=%d, want 3/25/25 — the batch settles before the cap ends the run", f.toolCalls, f.modelCalls, f.sourceCalls) } // TestRunToolEffectsDoNotConsumeModelCap pins that tool calls beyond the numeric model cap are irrelevant to it: one batch of 30 calls followed by a final text response still succeeds. @@ -217,10 +169,6 @@ func TestRunToolEffectsDoNotConsumeModelCap(t *testing.T) { f.modelSet = readyOutputs(bigOut, mkOutput(model.OutputCompleted, "", nil)) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success", res, err) - } - if f.toolCalls != maxModelEffects+5 || f.modelCalls != 2 { - t.Fatalf("tool=%d model=%d, want 30/2", f.toolCalls, f.modelCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success", res, err) + testassert.Require(t, f.toolCalls == maxModelEffects+5 && f.modelCalls == 2, "tool=%d model=%d, want 30/2", f.toolCalls, f.modelCalls) } diff --git a/agent/run_settlement_test.go b/agent/run_settlement_test.go index cf9dac08..32dd5e6b 100644 --- a/agent/run_settlement_test.go +++ b/agent/run_settlement_test.go @@ -5,31 +5,15 @@ import ( "errors" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) -// TestRunForgedModelSettlementsRejected pins every malformed settlement shape ending the run with one typed boundary-protocol error naming the "model" boundary and a zero terminal, through the shared validator — no effect after it runs. +// TestRunForgedModelSettlementsRejected pins every shared malformed settlement +// shape ending the run with its typed boundary-protocol error and a zero +// terminal, through the shared validator — no effect after it runs. func TestRunForgedModelSettlementsRejected(t *testing.T) { - errored := mkOutput(model.OutputErrored, "boom", nil) - completed := mkOutput(model.OutputCompleted, "", nil) - foreign := mkForeignSettlementOutput(model.OutputCompleted, "", model.ModelRef{Provider: "acme", Model: "other"}) - - rows := []struct { - name string - set ModelSettlement - }{ - {"ready-nil-output", ModelSettlement{Disposition: DispoReady}}, - {"ready-errored-output", ModelSettlement{Disposition: DispoReady, Output: errored}}, - {"continue-nil-output", ModelSettlement{Disposition: DispoContinue}}, - {"failure-completed-output", ModelSettlement{Disposition: DispoFailure, Output: completed, Detail: "d"}}, - {"failure-empty-detail", ModelSettlement{Disposition: DispoFailure}}, - {"interruption-errored-output", ModelSettlement{Disposition: DispoInterruption, Output: errored, Detail: "d"}}, - {"interruption-empty-detail", ModelSettlement{Disposition: DispoInterruption}}, - {"unknown-disposition", ModelSettlement{Disposition: "bogus"}}, - {"foreign-source", ModelSettlement{Disposition: DispoReady, Output: foreign}}, - } - - for _, row := range rows { + for _, row := range forgedModelSettlements() { t.Run(row.name, func(t *testing.T) { f := &runFakes{} f.modelSet = setScript(row.set) @@ -37,9 +21,7 @@ func TestRunForgedModelSettlementsRejected(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if f.modelCalls != 1 || f.toolCalls != 0 { - t.Fatalf("model=%d tool=%d, want the run to stop at the forged settlement", f.modelCalls, f.toolCalls) - } + testassert.Require(t, f.modelCalls == 1 && f.toolCalls == 0, "model=%d tool=%d, want the run to stop at the forged settlement", f.modelCalls, f.toolCalls) }) } } @@ -53,12 +35,8 @@ func TestRunContinueCompletedOutputContinues(t *testing.T) { ) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success after completed continuation", res, err) - } - if f.modelCalls != 2 || f.sourceCalls != 2 || f.toolCalls != 0 { - t.Fatalf("model=%d source=%d tool=%d, want the continuation to fetch a fresh snapshot", f.modelCalls, f.sourceCalls, f.toolCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success after completed continuation", res, err) + testassert.Require(t, f.modelCalls == 2 && f.sourceCalls == 2 && f.toolCalls == 0, "model=%d source=%d tool=%d, want the continuation to fetch a fresh snapshot", f.modelCalls, f.sourceCalls, f.toolCalls) } // TestRunForgedToolResultsRejected pins the shared tool-result validator inside the batch: a settlement answering another call, carrying an unclosed status, or omitting mandated content ends the run with one typed "tool" violation, and no later call in the batch starts. @@ -83,9 +61,7 @@ func TestRunForgedToolResultsRejected(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "tool") requireZeroTerminal(t, res) - if f.toolCalls != 1 { - t.Fatalf("tool calls = %d, want the batch to stop at the forged settlement", f.toolCalls) - } + testassert.Equal(t, f.toolCalls, 1) }) } } @@ -100,9 +76,7 @@ func TestRunInvalidSnapshotMessagesRejected(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "agent") requireZeroTerminal(t, res) - if f.modelCalls != 0 || f.toolCalls != 0 { - t.Fatalf("model=%d tool=%d, want the request rejection to precede any effect", f.modelCalls, f.toolCalls) - } + testassert.Require(t, f.modelCalls == 0 && f.toolCalls == 0, "model=%d tool=%d, want the request rejection to precede any effect", f.modelCalls, f.toolCalls) } // TestRunDuplicateCallIDSettlementsRejected pins the shared completed-call identity rule inside the loop: duplicate IDs on a retained completed output are one typed model-boundary violation on both dispositions that may carry it, the run stops with a zero terminal, no call dispatches, and the interruption path cannot panic its way into a terminal. @@ -129,9 +103,7 @@ func TestRunDuplicateCallIDSettlementsRejected(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if f.toolCalls != 0 { - t.Fatalf("tool calls = %d, want no duplicate dispatch", f.toolCalls) - } + testassert.Equal(t, f.toolCalls, 0) }) } } @@ -150,9 +122,7 @@ func TestRunDetailMismatchDispositionsRejectedThroughRun(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireBoundaryViolation(t, err, "model") requireZeroTerminal(t, res) - if f.modelCalls != 1 { - t.Fatalf("row %d model calls = %d, want 1", i, f.modelCalls) - } + testassert.Require(t, f.modelCalls == 1, "row %d model calls = %d, want 1", i, f.modelCalls) } } @@ -168,12 +138,8 @@ func TestRunInfraErrorAfterPriorContinuationVerbatim(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireZeroTerminal(t, res) - if !errors.Is(err, errInfra) { - t.Fatalf("error = %v, want the infrastructure error verbatim", err) - } - if f.modelCalls != 2 || f.sourceCalls != 2 || f.toolCalls != 0 { - t.Fatalf("model=%d source=%d tool=%d, want 2/2/0", f.modelCalls, f.sourceCalls, f.toolCalls) - } + testassert.Require(t, errors.Is(err, errInfra), "error = %v, want the infrastructure error verbatim", err) + testassert.Require(t, f.modelCalls == 2 && f.sourceCalls == 2 && f.toolCalls == 0, "model=%d source=%d tool=%d, want 2/2/0", f.modelCalls, f.sourceCalls, f.toolCalls) } // TestDuplicateCallIDSharedValidator pins the one identity rule under both shared validators: a completed output whose tool calls repeat an ID is rejected naming the "model" boundary from the settlement validator and the "agent" boundary from the terminal validator, while distinct IDs pass both — the nearest allowed sibling. @@ -181,18 +147,22 @@ func TestDuplicateCallIDSharedValidator(t *testing.T) { dup := mkOutput(model.OutputCompleted, "", []model.ToolCall{{ID: "a", Name: "fnA"}, {ID: "a", Name: "fnB"}}) distinct := mkCallsTerminalOutput() - if _, err := validateSettlement(ModelSettlement{Disposition: DispoReady, Output: dup}, testRef, true); !isBoundaryViolation(err, "model") { - t.Fatalf("settlement validator on duplicate IDs = %v, want a model-boundary violation", err) + { + _, err := validateSettlement(ModelSettlement{Disposition: DispoReady, Output: dup}, testRef, true) + testassert.Require(t, isBoundaryViolation(err, "model"), "settlement validator on duplicate IDs = %v, want a model-boundary violation", err) } - if _, err := validateSettlement(ModelSettlement{Disposition: DispoReady, Output: distinct}, testRef, true); err != nil { - t.Fatalf("settlement validator on distinct IDs = %v, want acceptance", err) + { + _, err := validateSettlement(ModelSettlement{Disposition: DispoReady, Output: distinct}, testRef, true) + testassert.NoError(t, err) } - if err := validateTerminalResult(TerminalResult{Status: TerminalFailure, LastOutput: dup, Detail: "d"}); !isBoundaryViolation(err, "agent") { - t.Fatalf("terminal validator on duplicate IDs = %v, want an agent-boundary violation", err) + { + err := validateTerminalResult(TerminalResult{Status: TerminalFailure, LastOutput: dup, Detail: "d"}) + testassert.Require(t, isBoundaryViolation(err, "agent"), "terminal validator on duplicate IDs = %v, want an agent-boundary violation", err) } - if err := validateTerminalResult(TerminalResult{Status: TerminalInterruption, LastOutput: distinct, UnstartedCalls: distinct.Message.ToolCalls[1:], Detail: "d"}); err != nil { - t.Fatalf("terminal validator on distinct IDs = %v, want acceptance", err) + { + err := validateTerminalResult(TerminalResult{Status: TerminalInterruption, LastOutput: distinct, UnstartedCalls: distinct.Message.ToolCalls[1:], Detail: "d"}) + testassert.NoError(t, err) } } @@ -205,7 +175,7 @@ func isBoundaryViolation(err error, boundary string) bool { return errors.As(err, &pe) && pe.Boundary == boundary && pe.Detail != "" } -// TestRunFailureDispositionPassthrough pins the failure row of step 6: the matching terminal keeps the settlement's own detail and its optional errored (or absent) output, and nothing continues. +// TestRunFailureDispositionPassthrough pins the failure disposition: the matching terminal keeps the settlement's own detail and its optional errored (or absent) output, and nothing continues. func TestRunFailureDispositionPassthrough(t *testing.T) { t.Run("with-errored-output", func(t *testing.T) { f := &runFakes{} @@ -213,15 +183,9 @@ func TestRunFailureDispositionPassthrough(t *testing.T) { f.modelSet = setScript(ModelSettlement{Disposition: DispoFailure, Output: errored, Detail: "gave up"}) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalFailure || res.Detail != "gave up" { - t.Fatalf("terminal = %#v, %v; want failure carrying the settlement detail", res, err) - } - if res.LastOutput != errored { - t.Fatalf("failure dropped the settlement output: %p vs %p", res.LastOutput, errored) - } - if f.modelCalls != 1 || f.toolCalls != 0 { - t.Fatalf("model=%d tool=%d, want 1/0", f.modelCalls, f.toolCalls) - } + testassert.Require(t, err == nil && res.Status == TerminalFailure && res.Detail == "gave up", "terminal = %#v, %v; want failure carrying the settlement detail", res, err) + testassert.Equal(t, res.LastOutput, errored) + testassert.Require(t, f.modelCalls == 1 && f.toolCalls == 0, "model=%d tool=%d, want 1/0", f.modelCalls, f.toolCalls) }) t.Run("without-output", func(t *testing.T) { @@ -229,13 +193,11 @@ func TestRunFailureDispositionPassthrough(t *testing.T) { f.modelSet = setScript(ModelSettlement{Disposition: DispoFailure, Detail: "no accepted stream"}) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalFailure || res.LastOutput != nil || res.Detail != "no accepted stream" { - t.Fatalf("terminal = %#v, %v; want outputless failure", res, err) - } + testassert.Require(t, err == nil && res.Status == TerminalFailure && res.LastOutput == nil && res.Detail == "no accepted stream", "terminal = %#v, %v; want outputless failure", res, err) }) } -// TestRunInterruptionDispositionPassthrough pins the interruption row of step 6: the settlement keeps its own detail (never re-labelled), and a retained completed response is never discarded — its not-yet-dispatched calls, all of them here, return in message order. +// TestRunInterruptionDispositionPassthrough pins the interruption disposition: the settlement keeps its own detail (never re-labelled), and a retained completed response is never discarded — its not-yet-dispatched calls, all of them here, return in message order. func TestRunInterruptionDispositionPassthrough(t *testing.T) { t.Run("in-flight-interrupted-output", func(t *testing.T) { f := &runFakes{} @@ -244,9 +206,7 @@ func TestRunInterruptionDispositionPassthrough(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireRunInterrupted(t, res, err, "cancelled mid-generation") - if res.LastOutput != partial || res.UnstartedCalls != nil { - t.Fatalf("terminal = %#v, want the settlement output carried verbatim", res) - } + testassert.Require(t, res.LastOutput == partial && res.UnstartedCalls == nil, "terminal = %#v, want the settlement output carried verbatim", res) }) t.Run("retained-completed-output", func(t *testing.T) { @@ -256,15 +216,12 @@ func TestRunInterruptionDispositionPassthrough(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireRunInterrupted(t, res, err, "cancelled after completion") - if res.LastOutput != callsOut { - t.Fatalf("established completion was discarded: %p vs %p", res.LastOutput, callsOut) - } - if got := joinedIDs(res.UnstartedCalls); got != "[a b c]" { - t.Fatalf("unstarted = %s, want every undispatched call of the retained output", got) - } - if f.toolCalls != 0 { - t.Fatalf("tool calls = %d, want no dispatch after an interrupted settlement", f.toolCalls) + testassert.Equal(t, res.LastOutput, callsOut) + { + got := joinedIDs(res.UnstartedCalls) + testassert.Equal(t, got, "[a b c]") } + testassert.Equal(t, f.toolCalls, 0) }) t.Run("nothing-accepted", func(t *testing.T) { @@ -273,8 +230,6 @@ func TestRunInterruptionDispositionPassthrough(t *testing.T) { res, err := Run(context.Background(), f.invocation()) requireRunInterrupted(t, res, err, "cancelled pre-acceptance") - if res.LastOutput != nil || res.UnstartedCalls != nil { - t.Fatalf("terminal = %#v, want a bare interruption", res) - } + testassert.Require(t, res.LastOutput == nil && res.UnstartedCalls == nil, "terminal = %#v, want a bare interruption", res) }) } diff --git a/agent/run_test.go b/agent/run_test.go index 8024d1bd..ac458a9c 100644 --- a/agent/run_test.go +++ b/agent/run_test.go @@ -7,6 +7,7 @@ import ( "fmt" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -144,19 +145,13 @@ func joinedIDs(calls []model.ToolCall) string { func requireZeroTerminal(t *testing.T, res TerminalResult) { t.Helper() - if res.Status != "" || res.LastOutput != nil || res.UnstartedCalls != nil || res.Detail != "" { - t.Fatalf("expected zero terminal result, got %#v", res) - } + testassert.Require(t, res.Status == "" && res.LastOutput == nil && res.UnstartedCalls == nil && res.Detail == "", "expected zero terminal result, got %#v", res) } func requireRunInterrupted(t *testing.T, res TerminalResult, err error, wantDetail string) { t.Helper() - if err != nil { - t.Fatalf("expected terminal interruption, got Go error: %v", err) - } - if res.Status != TerminalInterruption || res.Detail != wantDetail { - t.Fatalf("interruption = %s/%q, want interruption/%q", res.Status, res.Detail, wantDetail) - } + testassert.NoError(t, err) + testassert.Require(t, res.Status == TerminalInterruption && res.Detail == wantDetail, "interruption = %s/%q, want interruption/%q", res.Status, res.Detail, wantDetail) } // TestRunInvalidInvocation pins every invalid entry value returning the typed boundary-protocol error with boundary "agent" and a zero terminal result before any caller function runs: a nil run context is rejected, never normalized, and the same no-run guarantee holds for every other row. @@ -205,9 +200,7 @@ func TestRunInvalidInvocation(t *testing.T) { res, err := Run(ctx, inv) requireBoundaryViolation(t, err, "agent") requireZeroTerminal(t, res) - if f.sourceCalls != 0 || f.modelCalls != 0 || f.toolCalls != 0 { - t.Fatalf("rejected invocation ran caller functions: source=%d model=%d tool=%d", f.sourceCalls, f.modelCalls, f.toolCalls) - } + testassert.Require(t, f.sourceCalls == 0 && f.modelCalls == 0 && f.toolCalls == 0, "rejected invocation ran caller functions: source=%d model=%d tool=%d", f.sourceCalls, f.modelCalls, f.toolCalls) }) } } @@ -219,24 +212,11 @@ func TestRunSuccessSingleEffect(t *testing.T) { f.modelSet = readyOutputs(out) res, err := Run(context.Background(), f.invocation()) - if err != nil { - t.Fatalf("run failed: %v", err) - } - if res.Status != TerminalSuccess || res.Detail != "" || res.UnstartedCalls != nil { - t.Fatalf("terminal = %#v, want success with empty detail and no unstarted calls", res) - } - if res.LastOutput != out { - t.Fatalf("success must transfer the settlement output pointer, got %p want %p", res.LastOutput, out) - } - if f.sourceCalls != 1 || f.modelCalls != 1 || f.toolCalls != 0 { - t.Fatalf("calls source=%d model=%d tool=%d, want 1/1/0", f.sourceCalls, f.modelCalls, f.toolCalls) - } - if len(f.reqs[0].Tools) != 1 || f.reqs[0].Tools[0].Name != "t" { - t.Fatalf("request tools = %#v, want the one advertised definition", f.reqs[0].Tools) - } - if len(f.reqs[0].Messages) != 1 || f.reqs[0].Messages[0].TextContent() != "go" { - t.Fatalf("request messages = %#v, want the fresh snapshot", f.reqs[0].Messages) - } + testassert.NoError(t, err) + testassert.Require(t, res.Status == TerminalSuccess && res.Detail == "" && res.UnstartedCalls == nil && res.LastOutput == out, "terminal = %#v, want success with empty detail and no unstarted calls; got %v, want %v", res, res.LastOutput, out) + testassert.Require(t, f.sourceCalls == 1 && f.modelCalls == 1 && f.toolCalls == 0, "calls source=%d model=%d tool=%d, want 1/1/0", f.sourceCalls, f.modelCalls, f.toolCalls) + testassert.Require(t, len(f.reqs[0].Tools) == 1 && f.reqs[0].Tools[0].Name == "t", "request tools = %#v, want the one advertised definition", f.reqs[0].Tools) + testassert.Require(t, len(f.reqs[0].Messages) == 1 && f.reqs[0].Messages[0].TextContent() == "go", "request messages = %#v, want the fresh snapshot", f.reqs[0].Messages) } // TestRunNoAdvertisedToolsSucceeds pins that an absent advertised list is a valid entry value — tool advertisement selection is not Agent's to make. @@ -248,12 +228,8 @@ func TestRunNoAdvertisedToolsSucceeds(t *testing.T) { inv.Tools = nil res, err := Run(context.Background(), inv) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success", res, err) - } - if len(f.reqs[0].Tools) != 0 { - t.Fatalf("request carries tools %#v", f.reqs[0].Tools) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success", res, err) + testassert.Require(t, len(f.reqs[0].Tools) == 0, "request carries tools %#v", f.reqs[0].Tools) } // TestRunAssemblyCallbackProducesSettlement pins the callback wiring: the model effect receives a working assembler bound to the run context, its returned output settles as a ready disposition verbatim, and the handed-over stream is released exactly once. @@ -271,18 +247,10 @@ func TestRunAssemblyCallbackProducesSettlement(t *testing.T) { } res, err := Run(context.Background(), f.invocation()) - if err != nil { - t.Fatalf("run failed: %v", err) - } - if cbCalls != 1 || f.modelCalls != 1 { - t.Fatalf("callback calls=%d model calls=%d, want 1/1", cbCalls, f.modelCalls) - } - if res.Status != TerminalSuccess || res.LastOutput.Message.TextContent() != "hi" { - t.Fatalf("terminal = %#v, want success carrying the assembled output", res) - } - if stream.closeCount != 1 || stream.recvsAfterClose != 0 { - t.Fatalf("stream close=%d recvs-after-close=%d, want 1/0", stream.closeCount, stream.recvsAfterClose) - } + testassert.NoError(t, err) + testassert.Require(t, cbCalls == 1 && f.modelCalls == 1, "callback calls=%d model calls=%d, want 1/1", cbCalls, f.modelCalls) + testassert.Require(t, res.Status == TerminalSuccess && res.LastOutput.Message.TextContent() == "hi", "terminal = %#v, want success carrying the assembled output", res) + testassert.Require(t, stream.closeCount == 1 && stream.recvsAfterClose == 0, "stream close=%d recvs-after-close=%d, want 1/0", stream.closeCount, stream.recvsAfterClose) } // TestRunSubstitutedValidSettlementOutputAccepted pins the documented non-rule: Agent compares the callback's invocation discipline (count, source, error handling) but never compares the callback and settlement outputs for deep or byte equality, so a different otherwise-valid substitution stays a trusted-caller matter, not another protocol path. @@ -298,15 +266,9 @@ func TestRunSubstitutedValidSettlementOutputAccepted(t *testing.T) { } res, err := Run(context.Background(), f.invocation()) - if err != nil { - t.Fatalf("run failed: %v", err) - } - if res.Status != TerminalSuccess || res.LastOutput != substitute { - t.Fatalf("terminal = %#v, want success carrying the substituted pointer", res) - } - if stream.closeCount != 1 { - t.Fatalf("callback stream close = %d, want 1", stream.closeCount) - } + testassert.NoError(t, err) + testassert.Require(t, res.Status == TerminalSuccess && res.LastOutput == substitute, "terminal = %#v, want success carrying the substituted pointer", res) + testassert.Equal(t, stream.closeCount, 1) } // TestRunRawToolArgumentsPassUnchanged pins byte-for-byte argument transit through every JSON shape — object, array, null, malformed, and absent — into the caller-owned tool boundary; Agent never decodes, rejects, or rewrites them, and the whole batch continues to a final text response. @@ -321,19 +283,11 @@ func TestRunRawToolArgumentsPassUnchanged(t *testing.T) { f.modelSet = readyOutputs(callsOut, mkOutput(model.OutputCompleted, "", nil)) res, err := Run(context.Background(), f.invocation()) - if err != nil || res.Status != TerminalSuccess { - t.Fatalf("run = %#v, %v; want success", res, err) - } - if f.toolCalls != len(args) { - t.Fatalf("tool calls = %d, want %d", f.toolCalls, len(args)) - } + testassert.Require(t, err == nil && res.Status == TerminalSuccess, "run = %#v, %v; want success", res, err) + testassert.Equal(t, f.toolCalls, len(args)) for i, seen := range f.calls { want := args[i] - if string(seen.Arguments) != string(want) || len(seen.Arguments) != len(want) { - t.Fatalf("call %d arguments = %q, want verbatim %q", i, string(seen.Arguments), string(want)) - } - if seen.ID != calls[i].ID || seen.Name != calls[i].Name { - t.Fatalf("call %d identity = %q/%q, want %q/%q", i, seen.ID, seen.Name, calls[i].ID, calls[i].Name) - } + testassert.Require(t, string(seen.Arguments) == string(want) && len(seen.Arguments) == len(want), "call %d arguments = %q, want verbatim %q", i, string(seen.Arguments), string(want)) + testassert.Require(t, seen.ID == calls[i].ID && seen.Name == calls[i].Name, "call %d identity = %q/%q, want %q/%q", i, seen.ID, seen.Name, calls[i].ID, calls[i].Name) } } diff --git a/agent/settlement_test.go b/agent/settlement_test.go index 0fe965fb..b73d41c9 100644 --- a/agent/settlement_test.go +++ b/agent/settlement_test.go @@ -5,101 +5,113 @@ import ( "fmt" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) // mkSettlementOutput builds one valid model output of the requested closed status carrying exactly testRef's source identity — a construction failure here means this test fixture itself is broken, not the code under test. -func mkSettlementOutput(status model.OutputStatus, detail string) *model.Output { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - msg := model.Message{Role: model.RoleAssistant, Source: testRef} // one canonical assistant message carrying exactly THIS package's shared source identity per contract above these lines verbatim (both non-completed shapes below tolerate its presence; completed additionally requires the payload part appended further down now). +func mkSettlementOutput(status model.OutputStatus, detail string) *model.Output { + msg := model.Message{Role: model.RoleAssistant, Source: testRef} // one canonical assistant message carrying exactly THIS package's shared source identity out := model.Output{Status: status, Source: testRef, Detail: detail} switch { case status == model.OutputCompleted: // the one mandatory-payload row: a single non-empty text part minimally satisfies the completed-output payload rule. msg.Content = []model.ContentPart{{Kind: model.PartText, Text: "x"}} - case status == model.OutputInterrupted || detail != "": // optional partial message retained alongside its diagnostic text for the other two shapes — present-but-tool-call-free per their own closed-shape rules respectively left-to-right as they appear within NewOutput's default-branch validation logic over there. + case status == model.OutputInterrupted || detail != "": // optional partial message retained alongside its diagnostic text for the other two shapes — present-but-tool-call-free per their own closed-shape rules msg.Content = []model.ContentPart{{Kind: model.PartText, Text: "x"}} - default: // no message at all for THIS specific shape — exercises the nil-message arm of settlement validation in whichever table rows consume it downstream along this trajectory forward now under exactly one rule documented inline within its own single-line comment. + default: // no message at all } - if out.Message == nil && msg.Content != nil { // only wire up the pointer when we actually built payload content for THIS specific row above these lines verbatim (the zero-value message with no fields must stay absent rather than being wrapped in an empty shell anywhere downstream of that decision point along this trajectory forward). + if out.Message == nil && msg.Content != nil { // only wire up the pointer when we actually built payload content out.Message = &msg } - o, err := model.NewOutput(out) // re-validate the assembled fixture through the public constructor at this trust boundary per contract — any failure here indicates a broken test construction above these lines now rather than anything wrong with settlement validation itself anywhere downstream of that call's return path. + o, err := model.NewOutput(out) if err != nil { - panic(fmt.Sprintf("test fixture failed model-output validation: %v", err)) // fail the whole package loudly and unambiguously at fixture-construction time so whoever reads the failure later upstream/downstream along this particular trajectory forward knows immediately which specific helper produced an invalid shape above these lines now rather than scattering blame across multiple consumer sites below it further ahead in wire order. + panic(fmt.Sprintf("test fixture failed model-output validation: %v", err)) } return &o } // TestValidateSettlement pins the closed disposition table of model settlements end to end: every only-valid combination passes, and each invalid shape returns exactly one typed boundary-protocol error naming the "model" boundary. -func TestValidateSettlement(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - completed := mkSettlementOutput(model.OutputCompleted, "") // one canonical valid completed output fixture shared across every row below it further ahead now (built exactly once per test run rather than repeatedly inside the loop body itself above these lines verbatim for consistency's sake respectively). - errored := mkSettlementOutput(model.OutputErrored, "boom") // same for its errored sibling shape carrying non-empty diagnostic detail per contract documented inline within that helper call further up over there now. +func TestValidateSettlement(t *testing.T) { + completed := mkSettlementOutput(model.OutputCompleted, "") // one canonical valid completed output fixture reused by the valid rows. + errored := mkSettlementOutput(model.OutputErrored, "boom") // its errored sibling carrying non-empty diagnostic detail. interrupted := mkSettlementOutput(model.OutputInterrupted, "stopped") - callsOut := mkCallsTerminalOutput() - foreignSrc := model.ModelRef{Provider: "acme", Model: "m-other"} // one complete but foreign identity pair deliberately diverging from testRef in its second field only respectively left-to-right as they appear within these two struct fields' own declarations above this line now under exactly one rule documented inline within its own single-line comment. - foreignCompleted := mkForeignSettlementOutput(model.OutputCompleted, "", foreignSrc) - bare, ferr := model.NewOutput(model.Output{Status: model.OutputErrored, Source: testRef, Detail: "boom"}) // errored shape with no message at all: nothing model-visible was retained. - if ferr != nil { - t.Fatalf("fixture: %v", ferr) - } refusalMsg := &model.Message{Role: model.RoleAssistant, Source: testRef, Refusal: "declined"} refusalErrored, ferr := model.NewOutput(model.Output{Status: model.OutputErrored, Source: testRef, Detail: "boom", Message: refusalMsg}) - if ferr != nil { - t.Fatalf("fixture: %v", ferr) - } + testassert.NoError(t, ferr) extraMsg := &model.Message{Role: model.RoleAssistant, Source: testRef, Extra: model.Extra{"k": json.RawMessage(`1`)}} extraErrored, ferr := model.NewOutput(model.Output{Status: model.OutputErrored, Source: testRef, Detail: "boom", Message: extraMsg}) - if ferr != nil { - t.Fatalf("fixture: %v", ferr) - } + testassert.NoError(t, ferr) - cases := []struct { - name string - set ModelSettlement - wantErr bool + valid := []struct { + name string + set ModelSettlement }{ - {"valid-ready", ModelSettlement{Disposition: DispoReady, Output: completed}, false}, - {"valid-continue", ModelSettlement{Disposition: DispoContinue, Output: errored}, false}, - {"valid-continue-completed-no-calls", ModelSettlement{Disposition: DispoContinue, Output: completed}, false}, // newly permitted continuation row: a completed output carrying no tool calls may settle as continue. - {"valid-continue-errored-refusal-payload", ModelSettlement{Disposition: DispoContinue, Output: &refusalErrored}, false}, // the refusal arm of the retained-payload rule. - {"valid-continue-errored-extra-payload", ModelSettlement{Disposition: DispoContinue, Output: &extraErrored}, false}, // the finalized-extra arm of the retained-payload rule. - {"valid-failure-no-output", ModelSettlement{Disposition: DispoFailure, Detail: "d"}, false}, - {"valid-failure-with-errored", ModelSettlement{Disposition: DispoFailure, Output: errored, Detail: "d"}, false}, - {"valid-interruption-no-output", ModelSettlement{Disposition: DispoInterruption, Detail: "d"}, false}, - {"valid-interruption-in-flight", ModelSettlement{Disposition: DispoInterruption, Output: interrupted, Detail: "d"}, false}, - {"valid-interruption-retained-completed", ModelSettlement{Disposition: DispoInterruption, Output: completed, Detail: "d"}, false}, - {"invalid-ready-nil-output", ModelSettlement{Disposition: DispoReady}, true}, // ready demands its callback output unconditionally — absence is one typed violation respectively per contract documented in requireDispositionOutput's own guard condition further up over there without any tolerance for nil here anywhere downstream along this trajectory forward now. - {"invalid-ready-errored-status", ModelSettlement{Disposition: DispoReady, Output: errored}, true}, // ready demands COMPLETED specifically — an errored payload in its place is the wrong-direction violation respectively per contract documented in requireDispositionOutput's own status check further up over there without any tolerance for cross-class substitution anywhere downstream along this trajectory forward now. - {"invalid-continue-completed-with-calls", ModelSettlement{Disposition: DispoContinue, Output: callsOut}, true}, // a completed output still carrying tool calls cannot settle as continue: those calls would never dispatch. - {"invalid-continue-errored-without-payload", ModelSettlement{Disposition: DispoContinue, Output: &bare}, true}, // an errored output that retained nothing model-visible cannot continue (nearest forbidden sibling of the errored payload rows). - {"invalid-continue-interrupted-output", ModelSettlement{Disposition: DispoContinue, Output: interrupted}, true}, // an interrupted payload belongs to the interruption row only (nearest forbidden sibling of the completed continue row). - {"invalid-failure-empty-detail", ModelSettlement{Disposition: DispoFailure}, true}, - {"invalid-failure-completed-output", ModelSettlement{Disposition: DispoFailure, Output: completed, Detail: "d"}, true}, // failure permits NO output or its own errored one ONLY — a completed payload in either slot position is explicitly forbidden per contract above these lines verbatim (this pins the nearest-forbidden-sibling boundary between valid-failure-with-errored and this specific row's divergent status value respectively left-to-right as they appear within settlement.go's failure-case switch further up over there). - {"invalid-interruption-empty-detail", ModelSettlement{Disposition: DispoInterruption}, true}, - {"invalid-interruption-errored-output", ModelSettlement{Disposition: DispoInterruption, Output: errored, Detail: "d"}, true}, // interruption permits no/interrupted/completed ONLY — an errored payload in that slot is explicitly forbidden per contract above these lines verbatim (this pins the nearest-forbidden-sibling boundary between valid-interruption-in-flight and this specific row's divergent status value respectively left-to-right as they appear within settlement.go's interruption-case switch further up over there). - {"invalid-unknown-disposition", ModelSettlement{Disposition: "bogus"}, true}, // any disposition outside the closed four-value set fails before ANY other field is consulted per contract documented in settlement.go's default case statement further up over there without special-casing unknown values into partial validation paths anywhere downstream of that early return along this trajectory forward now. - {"invalid-ready-nonempty-detail", ModelSettlement{Disposition: DispoReady, Output: completed, Detail: "x"}, true}, - {"invalid-continue-nonempty-detail", ModelSettlement{Disposition: DispoContinue, Output: errored, Detail: "y"}, true}, - {"invalid-source-mismatch", ModelSettlement{Disposition: DispoReady, Output: foreignCompleted}, true}, // a present output must carry EXACTLY the invocation's expected model identity field-for-field — this fixture deliberately diverges in one component of that pair respectively per contract documented in settlement.go's own source-comparison statement further up over there (the exact divergence point chosen here is arbitrary among either half of the identity pair available to real providers; any single representative suffices for THIS specific row's purposes above these lines now). + {"valid-ready", ModelSettlement{Disposition: DispoReady, Output: completed}}, + {"valid-continue", ModelSettlement{Disposition: DispoContinue, Output: errored}}, + {"valid-continue-completed-no-calls", ModelSettlement{Disposition: DispoContinue, Output: completed}}, // a completed output carrying no tool calls may settle as continue. + {"valid-continue-errored-refusal-payload", ModelSettlement{Disposition: DispoContinue, Output: &refusalErrored}}, // the refusal arm of the retained-payload rule. + {"valid-continue-errored-extra-payload", ModelSettlement{Disposition: DispoContinue, Output: &extraErrored}}, // the finalized-extra arm of the retained-payload rule. + {"valid-failure-no-output", ModelSettlement{Disposition: DispoFailure, Detail: "d"}}, + {"valid-failure-with-errored", ModelSettlement{Disposition: DispoFailure, Output: errored, Detail: "d"}}, + {"valid-interruption-no-output", ModelSettlement{Disposition: DispoInterruption, Detail: "d"}}, + {"valid-interruption-in-flight", ModelSettlement{Disposition: DispoInterruption, Output: interrupted, Detail: "d"}}, + {"valid-interruption-retained-completed", ModelSettlement{Disposition: DispoInterruption, Output: completed, Detail: "d"}}, } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per settlement shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + for _, tc := range valid { + t.Run(tc.name, func(t *testing.T) { _, err := validateSettlement(tc.set, testRef, true) - if tc.wantErr { // only the negative rows in THIS specific table expect a typed boundary-protocol violation below it further ahead now (the positive rows' nil-return contract is asserted through its own dedicated branch immediately after this if-block ends here respectively left-to-right as they appear within that else-statement's body over there). - requireBoundaryViolation(t, err, "model") - } else if err != nil { // positive rows must return NIL — any non-nil value here indicates either broken validation logic somewhere upstream inside validateSettlement itself OR an invalid fixture shape constructed by the helpers above these lines now rather than legitimately arising from anything within THIS specific row's own settlement data alone anywhere downstream along this trajectory forward. - t.Fatalf("expected valid settlement, got: %v", err) // report whatever actually came back verbatim so debugging starts from concrete evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } else { /* positive row passed cleanly — nothing further to assert on THIS specific dimension anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... */ - } // else-branch close: positive settlement validated as expected — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now respectively left-to-right as they appear within its own dedicated outcome-class routing above these lines verbatim. + testassert.NoError(t, err) + }) + } - }) // close out each settlement-shape subtest closure after all applicable assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + for _, row := range forgedModelSettlements() { + t.Run(row.name, func(t *testing.T) { + _, err := validateSettlement(row.set, testRef, true) + requireBoundaryViolation(t, err, "model") + }) + } +} + +// forgedModelSettlement is one shared invalid-settlement input: the name and +// the forged settlement. Direct validation and Run propagation each build their +// own fresh values from forgedModelSettlements so scripted effects and +// ownership probes cannot share an input. +type forgedModelSettlement struct { + name string + set ModelSettlement +} + +func forgedModelSettlements() []forgedModelSettlement { + completed := &model.Output{Status: model.OutputCompleted, Source: testRef, Message: &model.Message{Role: model.RoleAssistant, Source: testRef, Content: []model.ContentPart{{Kind: model.PartText, Text: "x"}}}} + errored := &model.Output{Status: model.OutputErrored, Source: testRef, Detail: "boom", Message: &model.Message{Role: model.RoleAssistant, Source: testRef, Content: []model.ContentPart{{Kind: model.PartText, Text: "x"}}}} + interrupted := &model.Output{Status: model.OutputInterrupted, Source: testRef, Detail: "stopped", Message: &model.Message{Role: model.RoleAssistant, Source: testRef, Content: []model.ContentPart{{Kind: model.PartText, Text: "x"}}}} + callsOut := &model.Output{Status: model.OutputCompleted, Source: testRef, Message: &model.Message{Role: model.RoleAssistant, Source: testRef, Content: []model.ContentPart{{Kind: model.PartText, Text: "x"}}, ToolCalls: []model.ToolCall{{ID: "a", Name: "fnA"}, {ID: "b", Name: "fnB"}, {ID: "c", Name: "fnC"}}}} + bare := &model.Output{Status: model.OutputErrored, Source: testRef, Detail: "boom"} + foreign := &model.Output{Status: model.OutputCompleted, Source: model.ModelRef{Provider: "acme", Model: "m-other"}, Message: &model.Message{Role: model.RoleAssistant, Source: model.ModelRef{Provider: "acme", Model: "m-other"}, Content: []model.ContentPart{{Kind: model.PartText, Text: "x"}}}} + + return []forgedModelSettlement{ + {"ready-nil-output", ModelSettlement{Disposition: DispoReady}}, // Ready requires its callback output. + {"ready-errored-output", ModelSettlement{Disposition: DispoReady, Output: errored}}, // Ready requires completed; an errored output is the wrong direction. + {"ready-nonempty-detail", ModelSettlement{Disposition: DispoReady, Output: completed, Detail: "x"}}, + {"continue-nil-output", ModelSettlement{Disposition: DispoContinue}}, + {"continue-completed-with-calls", ModelSettlement{Disposition: DispoContinue, Output: callsOut}}, // A continue carrying tool calls would strand them: nothing dispatches after continue. + {"continue-errored-without-payload", ModelSettlement{Disposition: DispoContinue, Output: bare}}, // Nearest forbidden sibling of the errored-payload continue rows: nothing model-visible was retained. + {"continue-interrupted-output", ModelSettlement{Disposition: DispoContinue, Output: interrupted}}, // Nearest forbidden sibling of the completed continue row: an interrupted payload settles interruption only. + {"continue-nonempty-detail", ModelSettlement{Disposition: DispoContinue, Output: errored, Detail: "y"}}, + {"failure-completed-output", ModelSettlement{Disposition: DispoFailure, Output: completed, Detail: "d"}}, // Failure permits no output or its errored one; completed is the nearest forbidden sibling of valid failure-with-errored. + {"failure-empty-detail", ModelSettlement{Disposition: DispoFailure}}, + {"interruption-errored-output", ModelSettlement{Disposition: DispoInterruption, Output: errored, Detail: "d"}}, // Interruption admits no/interrupted/completed; errored is the nearest forbidden sibling of valid interruption-in-flight. + {"interruption-empty-detail", ModelSettlement{Disposition: DispoInterruption}}, + {"unknown-disposition", ModelSettlement{Disposition: "bogus"}}, // Unknown dispositions fail before any other field is consulted. + {"foreign-source", ModelSettlement{Disposition: DispoReady, Output: foreign}}, // A present output must match the expected identity field-for-field. } } @@ -125,70 +137,45 @@ func TestValidateModelSettlement(t *testing.T) { }) t.Run("invalid-settlement-same-violation", func(t *testing.T) { - if _, err := ValidateModelSettlement(testRef, ModelSettlement{Disposition: DispoReady}); !isBoundaryViolation(err, "model") { - t.Fatalf("missing ready output = %v, want the model-boundary violation", err) + { + _, err := ValidateModelSettlement(testRef, ModelSettlement{Disposition: DispoReady}) + testassert.Require(t, isBoundaryViolation(err, "model"), "missing ready output = %v, want the model-boundary violation", err) } }) t.Run("owned-copy-on-success", func(t *testing.T) { in := mkSettlementOutput(model.OutputCompleted, "") got, err := ValidateModelSettlement(testRef, ModelSettlement{Disposition: DispoReady, Output: in}) - if err != nil { - t.Fatalf("valid settlement rejected: %v", err) - } - if got.Disposition != DispoReady || got.Detail != "" { - t.Fatalf("owned settlement = %#v, want disposition and detail copied as plain values", got) - } - if got.Output == nil || got.Output == in { - t.Fatalf("owned output must be a distinct pointer, got %p", got.Output) - } + testassert.NoError(t, err) + testassert.Require(t, got.Disposition == DispoReady && got.Detail == "" && (got.Output != nil && got.Output != in), "owned settlement = %#v, want disposition and detail copied as plain values; owned output must be a distinct pointer, got %p", got, got.Output) in.Message.Content[0].Text = "input-side" // mutate the input after validation... - if got.Output.Message.Content[0].Text != "x" { - t.Fatalf("owned copy aliased the input message: %q", got.Output.Message.Content[0].Text) - } + testassert.Equal(t, got.Output.Message.Content[0].Text, "x") got.Output.Message.Content[0].Text = "owned-side" // ...then the owned side; neither may reach the other. - if in.Message.Content[0].Text != "input-side" { - t.Fatalf("input aliased the owned copy: %q", in.Message.Content[0].Text) - } + testassert.Equal(t, in.Message.Content[0].Text, "input-side") in.Status = model.OutputErrored - if got.Output.Status != model.OutputCompleted { - t.Fatalf("owned copy aliased the input status: %s", got.Output.Status) - } + testassert.Equal(t, got.Output.Status, model.OutputCompleted) }) t.Run("outputless-owned", func(t *testing.T) { got, err := ValidateModelSettlement(testRef, ModelSettlement{Disposition: DispoFailure, Detail: "d"}) - if err != nil || got.Disposition != DispoFailure || got.Detail != "d" || got.Output != nil { - t.Fatalf("got %#v, %v; want the settlement copied with nil output retained", got, err) - } + testassert.Require(t, err == nil && got.Disposition == DispoFailure && got.Detail == "d" && got.Output == nil, "got %#v, %v; want the settlement copied with nil output retained", got, err) }) t.Run("owned-copy-nested-fields", func(t *testing.T) { in := mkNestedSettlementOutput() got, err := ValidateModelSettlement(testRef, ModelSettlement{Disposition: DispoReady, Output: in}) - if err != nil { - t.Fatalf("valid settlement rejected: %v", err) - } + testassert.NoError(t, err) - // Input-side mutations after validation never reach the owned copy: + // Input-side mutations after validation never reach the owned copy // nested tool-call argument bytes, every Extra map, and usage. in.Message.ToolCalls[0].Arguments[0] = 'z' in.Message.ToolCalls[0].Extra["c"] = json.RawMessage(`"in"`) in.Message.Content[0].Extra["p"] = json.RawMessage(`"in"`) in.Message.Extra["m"] = json.RawMessage(`"in"`) in.Usage.InputTokens = 99 - if string(got.Output.Message.ToolCalls[0].Arguments) != `{"x":1}` { - t.Fatalf("owned copy aliased the input arguments: %q", got.Output.Message.ToolCalls[0].Arguments) - } - if string(got.Output.Message.ToolCalls[0].Extra["c"]) != `3` || - string(got.Output.Message.Content[0].Extra["p"]) != `1` || - string(got.Output.Message.Extra["m"]) != `2` { - t.Fatalf("owned copy aliased an input Extra map: call=%s part=%s message=%s", - got.Output.Message.ToolCalls[0].Extra["c"], got.Output.Message.Content[0].Extra["p"], got.Output.Message.Extra["m"]) - } - if got.Output.Usage.InputTokens != 3 { - t.Fatalf("owned copy aliased the input usage: %+v", got.Output.Usage) - } + testassert.Require(t, string(got.Output.Message.ToolCalls[0].Arguments) == `{"x":1}`, "owned copy aliased the input arguments: %q", got.Output.Message.ToolCalls[0].Arguments) + testassert.Require(t, string(got.Output.Message.ToolCalls[0].Extra["c"]) == `3` && string(got.Output.Message.Content[0].Extra["p"]) == `1` && string(got.Output.Message.Extra["m"]) == `2`, "owned copy aliased an input Extra map: call=%s part=%s message=%s", got.Output.Message.ToolCalls[0].Extra["c"], got.Output.Message.Content[0].Extra["p"], got.Output.Message.Extra["m"]) + testassert.Require(t, got.Output.Usage.InputTokens == 3, "owned copy aliased the input usage: %+v", got.Output.Usage) // Owned-side mutations never reach the input either: the input keeps // exactly its own post-mutation values from the block above. @@ -197,18 +184,9 @@ func TestValidateModelSettlement(t *testing.T) { got.Output.Message.Content[0].Extra["p"] = json.RawMessage(`"owned"`) got.Output.Message.Extra["m"] = json.RawMessage(`"owned"`) got.Output.Usage.InputTokens = 1 - if string(in.Message.ToolCalls[0].Arguments) != `z"x":1}` { - t.Fatalf("input arguments aliased the owned copy: %q", in.Message.ToolCalls[0].Arguments) - } - if string(in.Message.ToolCalls[0].Extra["c"]) != `"in"` || - string(in.Message.Content[0].Extra["p"]) != `"in"` || - string(in.Message.Extra["m"]) != `"in"` { - t.Fatalf("input Extra maps aliased the owned copy: call=%s part=%s message=%s", - in.Message.ToolCalls[0].Extra["c"], in.Message.Content[0].Extra["p"], in.Message.Extra["m"]) - } - if in.Usage.InputTokens != 99 { - t.Fatalf("input usage aliased the owned copy: %+v", in.Usage) - } + testassert.Require(t, string(in.Message.ToolCalls[0].Arguments) == `z"x":1}`, "input arguments aliased the owned copy: %q", in.Message.ToolCalls[0].Arguments) + testassert.Require(t, string(in.Message.ToolCalls[0].Extra["c"]) == `"in"` && string(in.Message.Content[0].Extra["p"]) == `"in"` && string(in.Message.Extra["m"]) == `"in"`, "input Extra maps aliased the owned copy: call=%s part=%s message=%s", in.Message.ToolCalls[0].Extra["c"], in.Message.Content[0].Extra["p"], in.Message.Extra["m"]) + testassert.Require(t, in.Usage.InputTokens == 99, "input usage aliased the owned copy: %+v", in.Usage) }) t.Run("one-copy-per-validation", func(t *testing.T) { @@ -220,14 +198,12 @@ func TestValidateModelSettlement(t *testing.T) { one := testing.AllocsPerRun(200, func() { _, _ = ValidateModelSettlement(testRef, set) }) - if one >= two { - t.Fatalf("public validator allocated %v times, not fewer than the removed two-copy shape's %v", one, two) - } + testassert.Require(t, one < two, "public validator allocated %v times, not fewer than the removed two-copy shape's %v", one, two) }) } // mkNestedSettlementOutput builds one valid completed output carrying every -// nested reference type the owned copy must isolate: tool-call argument bytes, +// nested reference type the owned copy must isolate: tool-call argument bytes // tool-call and content-part and message Extra maps, and a usage pointer. func mkNestedSettlementOutput() *model.Output { out, err := model.NewOutput(model.Output{ @@ -254,8 +230,8 @@ func mkNestedSettlementOutput() *model.Output { } // TestValidateToolResult pins the closed tool-result settlement contract at its trust boundary: valid shapes answering exactly their own call pass, while every malformed shape returns one typed boundary-protocol error naming the "tool" boundary. -func TestValidateToolResult(t *testing.T) { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - const callID = "call_1" // one stable original-call identity every row below it answers against respectively per contract documented in validateToolResult's own signature parameters further up over there without any variation between sibling test cases' expected values whatsoever at all under no other circumstances conditions scenarios contexts settings environments deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... +func TestValidateToolResult(t *testing.T) { + const callID = "call_1" // one stable original-call identity every row answers against. cases := []struct { name string @@ -264,25 +240,23 @@ func TestValidateToolResult(t *testing.T) { // nothing more to do on this very f }{ {"valid-success-empty-content", model.ToolResult{CallID: callID, Status: model.ResultSuccess}, false}, {"valid-error-with-detail", model.ToolResult{CallID: callID, Status: model.ResultError, Content: "detail"}, false}, - {"valid-denied-with-detail", model.ToolResult{CallID: callID, Status: model.ResultDenied, Content: "denied by policy"}, false}, // the canonical positive shape for the denied row — its status mandates non-empty content which this fixture supplies minimally per contract documented in tool.go's own closed-status ruleset further up over there. - {"valid-interrupted-with-content", model.ToolResult{CallID: callID, Status: model.ResultInterrupted, Content: "stopped mid-flight"}, false}, // the canonical positive shape for the interrupted row — same non-empty content mandate satisfied minimally per contract documented in tool.go's own closed-status ruleset further up over there. + {"valid-denied-with-detail", model.ToolResult{CallID: callID, Status: model.ResultDenied, Content: "denied by policy"}, false}, // the denied status mandates non-empty content. + {"valid-interrupted-with-content", model.ToolResult{CallID: callID, Status: model.ResultInterrupted, Content: "stopped mid-flight"}, false}, // the interrupted status carries the same non-empty content mandate. {"invalid-missing-call-id", model.ToolResult{Status: model.ResultSuccess}, true}, - {"invalid-unknown-status", model.ToolResult{CallID: callID, Status: "bogus", Content: "x"}, true}, // any status outside the closed four-value set is one typed violation at THIS trust boundary regardless of whatever other fields accompany it respectively per contract documented in NewToolResult's own second guard condition further up over there without special-casing unknown values into partial validation paths anywhere downstream of that early return along this trajectory forward now. + {"invalid-unknown-status", model.ToolResult{CallID: callID, Status: "bogus", Content: "x"}, true}, // any status outside the closed four-value set is one typed violation at this boundary. {"invalid-nonempty-required-empty-content", model.ToolResult{CallID: callID, Status: model.ResultInterrupted}, true}, - {"invalid-answered-id-mismatch", model.ToolResult{CallID: "other-call", Status: model.ResultSuccess}, true}, // a well-shaped result answering the WRONG call id is one typed violation at THIS trust boundary WITHOUT normalization or fallback of any kind respectively per contract documented in validateToolResult's own identity-comparison statement further up over there (this pins the nearest-forbidden-sibling boundary between valid-success-empty-content and this specific row's divergent CallID value respectively left-to-right as they appear within those two adjacent table entries above these lines verbatim). + {"invalid-answered-id-mismatch", model.ToolResult{CallID: "other-call", Status: model.ResultSuccess}, true}, // answering the wrong call id is one typed violation without normalization or fallback. } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { // spawn one isolated subtest per tool-result shape so a single bad behavior doesn't mask sibling rows' own independent pass/fail outcomes anywhere downstream of this nested closure's opening brace below it further ahead now. + t.Run(tc.name, func(t *testing.T) { err := validateToolResult(tc.res, callID) - if tc.wantErr { // only the negative rows in THIS specific table expect a typed boundary-protocol violation below it further ahead now (the positive rows' nil-return contract is asserted through its own dedicated branch immediately after this if-block ends here respectively left-to-right as they appear within that else-statement's body over there). + if tc.wantErr { requireBoundaryViolation(t, err, "tool") - } else if err != nil { // positive rows must return NIL — any non-nil value here indicates either broken validation logic somewhere upstream inside validateToolResult itself OR an invalid fixture shape constructed inline above these lines now rather than legitimately arising from anything within THIS specific row's own result data alone anywhere downstream along this trajectory forward. - t.Fatalf("expected valid tool result, got: %v", err) // report whatever actually came back verbatim so debugging starts from concrete evidence above these lines now rather than speculation scattered across multiple files' worth of prose elsewhere in wire order. - } else { /* positive row passed cleanly — nothing further to assert on THIS specific dimension anywhere downstream of here now under contract documented inline within its own single-line comment block rather than requiring additional checks beyond what's already pinned above these lines verbatim without duplication or redundancy whatsoever at all under any circumstances conditions scenarios contexts settings environments configurations deployments installations setups arrangements organizations structures formations constructions compositions assemblies aggregations collections accumulations... */ - } // else-branch close: positive tool result validated as expected — no remaining assertions on THIS specific subtest's scope anywhere downstream of here now respectively left-to-right as they appear within its own dedicated outcome-class routing above these lines verbatim. - - }) // close out each tool-result-shape subtest closure after all applicable assertions have had their chance to fire independently against that specific row's data above these lines verbatim without interfering with sibling rows' own separate lifecycle timelines running in parallel-ish fashion across the whole t.Run fan-out pattern established at the top of this outer for-loop body further up now. + } else if err != nil { + t.Fatalf("expected valid tool result, got: %v", err) + } + }) } } @@ -371,29 +345,3 @@ func TestValidateTerminalResult(t *testing.T) { }) } } - -func mkForeignSettlementOutput(status model.OutputStatus, detail string, src model.ModelRef) *model.Output { // nothing more to do on this very first arrival of any role value whatsoever during this stream's entire active consumption window spanning from its opening byte all the way through to whatever terminating marker ends it up at whatever point in time that happens to be whenever and wherever along this particular trajectory forward. - msg := model.Message{Role: model.RoleAssistant, Source: src} // one canonical assistant message carrying exactly THIS caller-supplied foreign identity per contract above these lines verbatim (both non-completed shapes below tolerate its presence; completed additionally requires the payload part appended further down now under exactly one rule documented inline within its own single-line comment rather than scattered across multiple files' worth of prose elsewhere in wire order). - - out := model.Output{Status: status, Source: src, Detail: detail} - switch { - case status == model.OutputCompleted: // the one mandatory-payload row: a single non-empty text part minimally satisfies the completed-output payload rule. - msg.Content = []model.ContentPart{{Kind: model.PartText, Text: "x"}} - - case status == model.OutputInterrupted || detail != "": // optional partial message retained alongside its diagnostic text for the other two shapes — present-but-tool-call-free per their own closed-shape rules respectively left-to-right as they appear within NewOutput's default-branch validation logic over there. - msg.Content = []model.ContentPart{{Kind: model.PartText, Text: "x"}} - - default: // no message at all for THIS specific shape — exercises the nil-message arm of settlement validation in whichever table rows consume it downstream along this trajectory forward now under exactly one rule documented inline within its own single-line comment. - } - - if out.Message == nil && msg.Content != nil { // only wire up the pointer when we actually built payload content for THIS specific row above these lines verbatim (the zero-value message with no fields must stay absent rather than being wrapped in an empty shell anywhere downstream of that decision point along this trajectory forward). - out.Message = &msg - } - - o, err := model.NewOutput(out) // re-validate the assembled fixture through the public constructor at this trust boundary per contract — any failure here indicates a broken test construction above these lines now rather than anything wrong with settlement validation itself anywhere downstream of that call's return path. - if err != nil { - panic(fmt.Sprintf("test fixture failed model-output validation: %v", err)) // fail the whole package loudly and unambiguously at fixture-construction time so whoever reads the failure later upstream/downstream along this particular trajectory forward knows immediately which specific helper produced an invalid shape above these lines now rather than scattering blame across multiple consumer sites below it further ahead in wire order. - } - - return &o -} diff --git a/assets_test.go b/assets_test.go index 3e2df0e9..198ea51f 100644 --- a/assets_test.go +++ b/assets_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" wailsasset "github.com/wailsapp/wails/v2/pkg/assetserver" "github.com/wailsapp/wails/v2/pkg/options/assetserver" ) @@ -25,48 +26,32 @@ var assetRefPattern = regexp.MustCompile(`(?:src|href)="([^"]+)"`) // missing build is a clear failure, never a skipped oracle. func TestProductionAssetsServe(t *testing.T) { handler, err := wailsasset.NewAssetHandler(assetserver.Options{Assets: assets}, nil) - if err != nil { - t.Fatalf("NewAssetHandler: %v", err) - } + testassert.NoError(t, err) page := serveDirect(t, handler, "/") - if !strings.Contains(page, " v0.0.2") { - t.Fatalf("stderr missing downgrade notice: %q", stderr) - } + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "downgrading v1.0.0 -> v0.0.2"), "stderr missing downgrade notice: %q", stderr) want := "lightcode: v0.0.2 predates the upgrade command; install it with the installer instead: curl -fsSL https://github.com/MMinasyan/lightcode/releases/latest/download/install.sh | LIGHTCODE_VERSION=v0.0.2 sh" - if !strings.Contains(stderr, want) { - t.Fatalf("stderr = %q, want substring %q", stderr, want) - } - if strings.Contains(stderr, "...") { - t.Fatalf("the installer command must never be elided: %q", stderr) - } - if !strings.Contains(stderr, "| LIGHTCODE_VERSION=v0.0.2 sh") { - t.Fatalf("LIGHTCODE_VERSION must sit on the sh side of the pipe: %q", stderr) - } + testassert.Require(t, strings.Contains(stderr, want) && !strings.Contains(stderr, "...") && strings.Contains(stderr, "| LIGHTCODE_VERSION=v0.0.2 sh"), "stderr = %q, want substring %q; the installer command must never be elided: %q; LIGHTCODE_VERSION must sit on the sh side of the pipe: %q", stderr, want, stderr, stderr) }) t.Run("dev current with explicit target prints the install notice", func(t *testing.T) { setVersion(t, "dev", "abc1234") _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v0.0.2"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "installing v0.0.2 over dev build") { - t.Fatalf("stderr missing dev install notice: %q", stderr) - } + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "installing v0.0.2 over dev build"), "stderr missing dev install notice: %q", stderr) }) } @@ -595,18 +416,13 @@ func upgradeFixtureScript(t *testing.T, script string, corruptSums bool) (baseUR var tarBuf bytes.Buffer gz := gzip.NewWriter(&tarBuf) tw := tar.NewWriter(gz) - if err := tw.WriteHeader(&tar.Header{Name: "lightcode", Mode: 0o755, Typeflag: tar.TypeReg, Size: int64(len(script))}); err != nil { - t.Fatal(err) - } - if _, err := tw.Write([]byte(script)); err != nil { - t.Fatal(err) - } - if err := tw.Close(); err != nil { - t.Fatal(err) - } - if err := gz.Close(); err != nil { - t.Fatal(err) + testassert.NoError(t, tw.WriteHeader(&tar.Header{Name: "lightcode", Mode: 0o755, Typeflag: tar.TypeReg, Size: int64(len(script))})) + { + _, err := tw.Write([]byte(script)) + testassert.NoError(t, err) } + testassert.NoError(t, tw.Close()) + testassert.NoError(t, gz.Close()) tarball := tarBuf.Bytes() sum := sha256.Sum256(tarball) digest := hex.EncodeToString(sum[:]) @@ -637,9 +453,7 @@ func fakeInstall(t *testing.T) (dir, target string) { t.Helper() dir = t.TempDir() target = filepath.Join(dir, "lightcode") - if err := os.WriteFile(target, []byte("old binary"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("old binary"), 0o755)) old := selfupdate.ResolveExecutable selfupdate.ResolveExecutable = func() (string, string, error) { return target, dir, nil } t.Cleanup(func() { selfupdate.ResolveExecutable = old }) @@ -654,32 +468,15 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { dir, target := fakeInstall(t) _, code, stdout, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if stdout != "" { - t.Fatalf("stdout must stay empty on install, got %q", stdout) - } - if !strings.Contains(stderr, "upgraded v1.0.0 -> v9.9.9; restart the Runtime to apply backend changes.") { - t.Fatalf("stderr missing final message: %q", stderr) - } - if strings.Contains(stderr, "including the GUI's own relaunch when switching projects") { - t.Fatalf("stderr must not promise a GUI relaunch: %q", stderr) - } - if strings.Contains(stdout+stderr, "SMOKE_NOISE_MARKER") { - t.Fatal("smoke child output was forwarded") - } + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Equal(t, stdout, "") + testassert.Require(t, strings.Contains(stderr, "upgraded v1.0.0 -> v9.9.9; restart the Runtime to apply backend changes.") && !strings.Contains(stderr, "including the GUI's own relaunch when switching projects"), "stderr missing final message: %q; stderr must not promise a GUI relaunch: %q", stderr, stderr) + testassert.Require(t, !strings.Contains(stdout+stderr, "SMOKE_NOISE_MARKER"), "smoke child output was forwarded") data, err := os.ReadFile(target) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(data), "v9.9.9") { - t.Fatalf("target not replaced: %q", data) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(data), "v9.9.9"), "target not replaced: %q", data) entries, err := os.ReadDir(dir) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) for _, e := range entries { if strings.HasPrefix(e.Name(), ".lightcode.tmp.") { t.Fatalf("staged file left behind: %s", e.Name()) @@ -694,16 +491,10 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { _, target := fakeInstall(t) _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "checksum mismatch") { - t.Fatalf("stderr = %q", stderr) - } + testassert.Require(t, code == 1, "exit code = %d, want 1\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "checksum mismatch"), "stderr = %q", stderr) data, _ := os.ReadFile(target) - if string(data) != "old binary" { - t.Fatalf("target touched on checksum mismatch: %q", data) - } + testassert.Require(t, string(data) == "old binary", "target touched on checksum mismatch: %q", data) }) t.Run("unwritable dir refuses before any download", func(t *testing.T) { @@ -714,19 +505,13 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { base, requests := upgradeFixture(t, "v9.9.9", false) t.Setenv("LIGHTCODE_RELEASE_BASE", base) dir, target := fakeInstall(t) - if err := os.Chmod(dir, 0o500); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Chmod(dir, 0o500)) t.Cleanup(func() { _ = os.Chmod(dir, 0o700) }) _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } + testassert.Equal(t, code, 1) want := "lightcode: binary is at " + target + " (not writable); re-run as: sudo lightcode upgrade" - if !strings.Contains(stderr, want) { - t.Fatalf("stderr = %q, want substring %q", stderr, want) - } + testassert.Require(t, strings.Contains(stderr, want), "stderr = %q, want substring %q", stderr, want) if *requests != 0 { t.Fatalf("preflight refusal must make zero requests, made %d", *requests) } @@ -739,22 +524,11 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { _, target := fakeInstall(t) _, code, stdout, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if stdout != "" { - t.Fatalf("stdout must stay empty, got %q", stdout) - } - if !strings.Contains(stderr, "installing v9.9.9 over dev build") { - t.Fatalf("stderr missing dev install notice: %q", stderr) - } - if !strings.Contains(stderr, "upgraded dev (abc1234) -> v9.9.9;") { - t.Fatalf("stderr missing final message: %q", stderr) - } + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Equal(t, stdout, "") + testassert.Require(t, strings.Contains(stderr, "installing v9.9.9 over dev build") && strings.Contains(stderr, "upgraded dev (abc1234) -> v9.9.9;"), "stderr missing dev install notice: %q; stderr missing final message: %q", stderr, stderr) data, _ := os.ReadFile(target) - if !strings.Contains(string(data), "v9.9.9") { - t.Fatalf("target not replaced: %q", data) - } + testassert.Require(t, strings.Contains(string(data), "v9.9.9"), "target not replaced: %q", data) }) t.Run("executable resolution failure refuses before any download", func(t *testing.T) { @@ -768,12 +542,8 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { t.Cleanup(func() { selfupdate.ResolveExecutable = old }) _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "lightcode: cannot resolve executable path:") { - t.Fatalf("stderr = %q", stderr) - } + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "lightcode: cannot resolve executable path:"), "stderr = %q", stderr) if *requests != 0 { t.Fatalf("resolution failure must make zero requests, made %d", *requests) } @@ -785,36 +555,18 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { _, target := fakeInstall(t) _, code, stdout, stderr := capture(t, []string{"lightcode", "upgrade"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if stdout != "" { - t.Fatalf("stdout must stay empty, got %q", stdout) - } - if !strings.Contains(stderr, "already up to date (v0.0.5)") { - t.Fatalf("stderr = %q", stderr) - } - if strings.Contains(stderr, "downgrading") { - t.Fatalf("bare upgrade must never downgrade: %q", stderr) - } + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Equal(t, stdout, "") + testassert.Require(t, strings.Contains(stderr, "already up to date (v0.0.5)") && !strings.Contains(stderr, "downgrading"), "stderr = %q; bare upgrade must never downgrade: %q", stderr, stderr) data, _ := os.ReadFile(target) - if string(data) != "old binary" { - t.Fatalf("target touched: %q", data) - } + testassert.Require(t, string(data) == "old binary", "target touched: %q", data) }) t.Run("equal below-floor target still hits the floor gate", func(t *testing.T) { setVersion(t, "v0.0.2", "") _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v0.0.2"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1\nstderr: %s", code, stderr) - } - if strings.Contains(stderr, "already up to date") { - t.Fatalf("equal below-floor target must not report up to date: %q", stderr) - } - if !strings.Contains(stderr, "v0.0.2 predates the upgrade command") { - t.Fatalf("stderr = %q", stderr) - } + testassert.Require(t, code == 1, "exit code = %d, want 1\nstderr: %s", code, stderr) + testassert.Require(t, !strings.Contains(stderr, "already up to date") && strings.Contains(stderr, "v0.0.2 predates the upgrade command"), "equal below-floor target must not report up to date: %q; stderr = %q", stderr, stderr) }) t.Run("smoke nonzero exit with matching tag removes the staged file", func(t *testing.T) { @@ -825,16 +577,10 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { dir, target := fakeInstall(t) _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "failed --version") { - t.Fatalf("stderr = %q", stderr) - } + testassert.Require(t, code == 1, "exit code = %d, want 1\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "failed --version"), "stderr = %q", stderr) data, _ := os.ReadFile(target) - if string(data) != "old binary" { - t.Fatalf("target touched: %q", data) - } + testassert.Require(t, string(data) == "old binary", "target touched: %q", data) entries, _ := os.ReadDir(dir) for _, e := range entries { if strings.HasPrefix(e.Name(), ".lightcode.tmp.") { @@ -854,16 +600,10 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { t.Cleanup(func() { selfupdate.SmokeTimeout = oldTimeout }) _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v9.9.9"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "did not exit") { - t.Fatalf("stderr = %q", stderr) - } + testassert.Require(t, code == 1, "exit code = %d, want 1\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "did not exit"), "stderr = %q", stderr) data, _ := os.ReadFile(target) - if string(data) != "old binary" { - t.Fatalf("target touched: %q", data) - } + testassert.Require(t, string(data) == "old binary", "target touched: %q", data) entries, _ := os.ReadDir(dir) for _, e := range entries { if strings.HasPrefix(e.Name(), ".lightcode.tmp.") { @@ -881,19 +621,10 @@ func TestDispatchUpgradeInstallFlow(t *testing.T) { dir, target := fakeInstall(t) _, code, _, stderr := capture(t, []string{"lightcode", "upgrade", "v8.8.8"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "did not report v8.8.8") { - t.Fatalf("stderr = %q", stderr) - } - if !strings.Contains(stderr, "install with the installer instead") { - t.Fatalf("stderr missing installer pointer: %q", stderr) - } + testassert.Require(t, code == 1, "exit code = %d, want 1\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "did not report v8.8.8") && strings.Contains(stderr, "install with the installer instead"), "stderr = %q; stderr missing installer pointer: %q", stderr, stderr) data, _ := os.ReadFile(target) - if string(data) != "old binary" { - t.Fatalf("target touched on smoke failure: %q", data) - } + testassert.Require(t, string(data) == "old binary", "target touched on smoke failure: %q", data) entries, _ := os.ReadDir(dir) for _, e := range entries { if strings.HasPrefix(e.Name(), ".lightcode.tmp.") { @@ -918,26 +649,22 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dataDir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dataDir, 0o700)) _, target := fakeInstall(t) uninstallSeams(t, false, "") // works in a pipe without --yes _, code, stdout, stderr := capture(t, []string{"lightcode", "uninstall", "--purge", "--dry-run"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) want := "would remove " + target + "\n" + "would remove " + dataDir + " (all data: API keys, sessions, snapshots)\n" - if stdout != want { - t.Fatalf("stdout = %q, want %q", stdout, want) + testassert.Equal(t, stdout, want) + { + _, err := os.Stat(target) + testassert.Require(t, err == nil, "dry run removed the binary") } - if _, err := os.Stat(target); err != nil { - t.Fatal("dry run removed the binary") - } - if _, err := os.Stat(dataDir); err != nil { - t.Fatal("dry run removed the data dir") + { + _, err := os.Stat(dataDir) + testassert.Require(t, err == nil, "dry run removed the data dir") } }) @@ -948,22 +675,14 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dir, target := fakeInstall(t) - if err := os.Chmod(dir, 0o500); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Chmod(dir, 0o500)) t.Cleanup(func() { _ = os.Chmod(dir, 0o700) }) uninstallSeams(t, false, "") _, code, stdout, stderr := capture(t, []string{"lightcode", "uninstall", "--dry-run"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if stdout != "would remove "+target+"\n" { - t.Fatalf("stdout = %q", stdout) - } - if strings.Contains(stderr, "not writable") { - t.Fatalf("dry run must short-circuit before the writability preflight: %q", stderr) - } + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Equal(t, stdout, "would remove "+target+"\n") + testassert.Require(t, !strings.Contains(stderr, "not writable"), "dry run must short-circuit before the writability preflight: %q", stderr) }) t.Run("yes removes the binary and sweeps owned staged files only", func(t *testing.T) { @@ -971,33 +690,26 @@ func TestDispatchUninstall(t *testing.T) { t.Setenv("HOME", home) dir, target := fakeInstall(t) staged := filepath.Join(dir, ".lightcode.tmp.stale") - if err := os.WriteFile(staged, []byte("x"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(staged, []byte("x"), 0o755)) link := filepath.Join(dir, ".lightcode.tmp.link") - if err := os.Symlink(target, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(target, link)) uninstallSeams(t, false, "") _, code, stdout, stderr := capture(t, []string{"lightcode", "uninstall", "--yes"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if stdout != "" { - t.Fatalf("stdout must be empty, got %q", stdout) - } - if stderr != "removed "+target+"\n" { - t.Fatalf("stderr = %q", stderr) - } - if _, err := os.Stat(target); !os.IsNotExist(err) { - t.Fatal("binary not removed") + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Equal(t, stdout, "") + testassert.Equal(t, stderr, "removed "+target+"\n") + { + _, err := os.Stat(target) + testassert.Require(t, os.IsNotExist(err), "binary not removed") } - if _, err := os.Stat(staged); !os.IsNotExist(err) { - t.Fatal("owned staged file not swept") + { + _, err := os.Stat(staged) + testassert.Require(t, os.IsNotExist(err), "owned staged file not swept") } - if _, err := os.Lstat(link); err != nil { - t.Fatal("symlinked staged entry must survive the sweep") + { + _, err := os.Lstat(link) + testassert.Require(t, err == nil, "symlinked staged entry must survive the sweep") } }) @@ -1006,23 +718,21 @@ func TestDispatchUninstall(t *testing.T) { t.Setenv("HOME", home) dir, target := fakeInstall(t) staged := filepath.Join(dir, ".lightcode.tmp.foreign") - if err := os.WriteFile(staged, []byte("x"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(staged, []byte("x"), 0o755)) oldOwner := selfupdate.FileOwner selfupdate.FileOwner = func(info os.FileInfo) (int, bool) { return os.Geteuid() + 1, true } t.Cleanup(func() { selfupdate.FileOwner = oldOwner }) uninstallSeams(t, false, "") _, code, _, _ := capture(t, []string{"lightcode", "uninstall", "--yes"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0", code) - } - if _, err := os.Stat(staged); err != nil { - t.Fatal("foreign-owned staged file must survive the sweep") + testassert.Equal(t, code, 0) + { + _, err := os.Stat(staged) + testassert.Require(t, err == nil, "foreign-owned staged file must survive the sweep") } - if _, err := os.Stat(target); !os.IsNotExist(err) { - t.Fatal("binary not removed") + { + _, err := os.Stat(target) + testassert.Require(t, os.IsNotExist(err), "binary not removed") } }) @@ -1030,28 +740,22 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(filepath.Join(dataDir, "sessions"), 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(dataDir, "sessions"), 0o700)) _, target := fakeInstall(t) uninstallSeams(t, false, "") _, code, stdout, stderr := capture(t, []string{"lightcode", "uninstall", "--purge", "--yes"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if stdout != "" { - t.Fatalf("stdout must be empty, got %q", stdout) - } + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Equal(t, stdout, "") want := "removed " + dataDir + "\nremoved " + target + "\n" - if stderr != want { - t.Fatalf("stderr = %q, want %q (data line first)", stderr, want) - } - if _, err := os.Stat(dataDir); !os.IsNotExist(err) { - t.Fatal("data dir not removed") + testassert.Equal(t, stderr, want) + { + _, err := os.Stat(dataDir) + testassert.Require(t, os.IsNotExist(err), "data dir not removed") } - if _, err := os.Stat(target); !os.IsNotExist(err) { - t.Fatal("binary not removed") + { + _, err := os.Stat(target) + testassert.Require(t, os.IsNotExist(err), "binary not removed") } }) @@ -1062,14 +766,11 @@ func TestDispatchUninstall(t *testing.T) { uninstallSeams(t, false, "") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "lightcode: uninstall is interactive; pass --yes to confirm non-interactively") { - t.Fatalf("stderr = %q", stderr) - } - if _, err := os.Stat(target); err != nil { - t.Fatal("refusal must remove nothing") + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "lightcode: uninstall is interactive; pass --yes to confirm non-interactively"), "stderr = %q", stderr) + { + _, err := os.Stat(target) + testassert.Require(t, err == nil, "refusal must remove nothing") } }) @@ -1080,23 +781,12 @@ func TestDispatchUninstall(t *testing.T) { uninstallSeams(t, true, "n\n") _, code, stdout, stderr := capture(t, []string{"lightcode", "uninstall"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if stdout != "" { - t.Fatalf("stdout must be empty, got %q", stdout) - } - if !strings.Contains(stderr, "Remove "+target+"? [y/N] ") { - t.Fatalf("stderr missing prompt: %q", stderr) - } - if !strings.Contains(stderr, "cancelled\n") { - t.Fatalf("stderr missing cancelled status: %q", stderr) - } - if strings.Contains(stderr, "lightcode: ") { - t.Fatalf("cancelled must not carry the error prefix: %q", stderr) - } - if _, err := os.Stat(target); err != nil { - t.Fatal("decline must remove nothing") + testassert.Equal(t, code, 1) + testassert.Equal(t, stdout, "") + testassert.Require(t, strings.Contains(stderr, "Remove "+target+"? [y/N] ") && strings.Contains(stderr, "cancelled\n") && !strings.Contains(stderr, "lightcode: "), "stderr missing prompt: %q; stderr missing cancelled status: %q; cancelled must not carry the error prefix: %q", stderr, stderr, stderr) + { + _, err := os.Stat(target) + testassert.Require(t, err == nil, "decline must remove nothing") } }) @@ -1107,11 +797,10 @@ func TestDispatchUninstall(t *testing.T) { uninstallSeams(t, true, "\n") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall"}) - if code != 1 || !strings.Contains(stderr, "cancelled") { - t.Fatalf("exit=%d stderr=%q", code, stderr) - } - if _, err := os.Stat(target); err != nil { - t.Fatal("default answer must remove nothing") + testassert.Require(t, code == 1 && strings.Contains(stderr, "cancelled"), "exit=%d stderr=%q", code, stderr) + { + _, err := os.Stat(target) + testassert.Require(t, err == nil, "default answer must remove nothing") } }) @@ -1119,21 +808,16 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dataDir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dataDir, 0o700)) _, target := fakeInstall(t) uninstallSeams(t, true, "y\n") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "Remove "+target+" and ALL data in "+dataDir+" (API keys, sessions, snapshots)? [y/N] ") { - t.Fatalf("stderr missing purge prompt: %q", stderr) - } - if _, err := os.Stat(target); !os.IsNotExist(err) { - t.Fatal("binary not removed") + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "Remove "+target+" and ALL data in "+dataDir+" (API keys, sessions, snapshots)? [y/N] "), "stderr missing purge prompt: %q", stderr) + { + _, err := os.Stat(target) + testassert.Require(t, os.IsNotExist(err), "binary not removed") } }) @@ -1141,27 +825,21 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) real := filepath.Join(home, "realdata") - if err := os.MkdirAll(real, 0o700); err != nil { - t.Fatal(err) - } - if err := os.Symlink(real, filepath.Join(home, ".lightcode")); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(real, 0o700)) + testassert.NoError(t, os.Symlink(real, filepath.Join(home, ".lightcode"))) _, target := fakeInstall(t) uninstallSeams(t, true, "y\n") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge", "--yes"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "lightcode: ~/.lightcode is a symlink; refusing to purge through it"), "stderr = %q", stderr) + { + _, err := os.Stat(real) + testassert.Require(t, err == nil, "symlink target must be untouched") } - if !strings.Contains(stderr, "lightcode: ~/.lightcode is a symlink; refusing to purge through it") { - t.Fatalf("stderr = %q", stderr) - } - if _, err := os.Stat(real); err != nil { - t.Fatal("symlink target must be untouched") - } - if _, err := os.Stat(target); err != nil { - t.Fatal("binary must be untouched on purge refusal") + { + _, err := os.Stat(target) + testassert.Require(t, err == nil, "binary must be untouched on purge refusal") } }) @@ -1169,23 +847,18 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dataDir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dataDir, 0o700)) fakeInstall(t) oldOwner := selfupdate.FileOwner selfupdate.FileOwner = func(info os.FileInfo) (int, bool) { return os.Geteuid() + 1, true } t.Cleanup(func() { selfupdate.FileOwner = oldOwner }) _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge", "--yes"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "lightcode: ~/.lightcode is not owned by the current user; refusing to purge") { - t.Fatalf("stderr = %q", stderr) - } - if _, err := os.Stat(dataDir); err != nil { - t.Fatal("foreign-owned data dir must be untouched") + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "lightcode: ~/.lightcode is not owned by the current user; refusing to purge"), "stderr = %q", stderr) + { + _, err := os.Stat(dataDir) + testassert.Require(t, err == nil, "foreign-owned data dir must be untouched") } }) @@ -1196,23 +869,11 @@ func TestDispatchUninstall(t *testing.T) { uninstallSeams(t, true, "y\n") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "no data directory at "+filepath.Join(home, ".lightcode")+" - nothing to purge") { - t.Fatalf("stderr missing nothing-to-purge note: %q", stderr) - } - if !strings.Contains(stderr, "Remove "+target+"? [y/N] ") { - t.Fatalf("prompt must be the binary-only one: %q", stderr) - } - if strings.Contains(stderr, "ALL data") { - t.Fatalf("purge prompt must not fire with nothing to purge: %q", stderr) - } - if strings.Contains(stderr, "data removed") { - t.Fatalf("the partial-flow message must never fire with nothing purged: %q", stderr) - } - if _, err := os.Stat(target); !os.IsNotExist(err) { - t.Fatal("binary not removed") + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "no data directory at "+filepath.Join(home, ".lightcode")+" - nothing to purge") && strings.Contains(stderr, "Remove "+target+"? [y/N] ") && !strings.Contains(stderr, "ALL data") && !strings.Contains(stderr, "data removed"), "stderr missing nothing-to-purge note: %q; prompt must be the binary-only one: %q; purge prompt must not fire with nothing to purge: %q; the partial-flow message must never fire with nothing purged: %q", stderr, stderr, stderr, stderr) + { + _, err := os.Stat(target) + testassert.Require(t, os.IsNotExist(err), "binary not removed") } }) @@ -1223,22 +884,13 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dir, target := fakeInstall(t) - if err := os.Chmod(dir, 0o500); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Chmod(dir, 0o500)) t.Cleanup(func() { _ = os.Chmod(dir, 0o700) }) uninstallSeams(t, true, "y\n") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "lightcode: binary is at "+target+" (not writable); re-run as: sudo lightcode uninstall") { - t.Fatalf("stderr = %q", stderr) - } - if strings.Contains(stderr, "[y/N]") { - t.Fatalf("the preflight must refuse before any prompt: %q", stderr) - } + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "lightcode: binary is at "+target+" (not writable); re-run as: sudo lightcode uninstall") && !strings.Contains(stderr, "[y/N]"), "stderr = %q; the preflight must refuse before any prompt: %q", stderr, stderr) }) t.Run("privileged partial flow purges data then points at sudo", func(t *testing.T) { @@ -1248,31 +900,22 @@ func TestDispatchUninstall(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dataDir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dataDir, 0o700)) dir, target := fakeInstall(t) - if err := os.Chmod(dir, 0o500); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Chmod(dir, 0o500)) t.Cleanup(func() { _ = os.Chmod(dir, 0o700) }) uninstallSeams(t, true, "y\n") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1\nstderr: %s", code, stderr) - } - if !strings.Contains(stderr, "removed "+dataDir+"\n") { - t.Fatalf("stderr missing data removal line: %q", stderr) - } - if !strings.Contains(stderr, "lightcode: data removed; binary still installed at "+target+" - run: sudo lightcode uninstall --yes") { - t.Fatalf("stderr = %q", stderr) + testassert.Require(t, code == 1, "exit code = %d, want 1\nstderr: %s", code, stderr) + testassert.Require(t, strings.Contains(stderr, "removed "+dataDir+"\n") && strings.Contains(stderr, "lightcode: data removed; binary still installed at "+target+" - run: sudo lightcode uninstall --yes"), "stderr missing data removal line: %q; stderr = %q", stderr, stderr) + { + _, err := os.Stat(dataDir) + testassert.Require(t, os.IsNotExist(err), "data dir must be purged in the partial flow") } - if _, err := os.Stat(dataDir); !os.IsNotExist(err) { - t.Fatal("data dir must be purged in the partial flow") - } - if _, err := os.Stat(target); err != nil { - t.Fatal("binary must remain when its dir is unwritable") + { + _, err := os.Stat(target) + testassert.Require(t, err == nil, "binary must remain when its dir is unwritable") } }) @@ -1282,14 +925,11 @@ func TestDispatchUninstall(t *testing.T) { uninstallSeams(t, false, "") _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--yes"}) - if code != 0 { - t.Fatalf("exit code = %d, want 0\nstderr: %s", code, stderr) - } - if strings.Contains(stderr, "resolve home dir") { - t.Fatalf("the binary-only flow must not resolve the home dir: %q", stderr) - } - if _, err := os.Stat(target); !os.IsNotExist(err) { - t.Fatal("binary not removed") + testassert.Require(t, code == 0, "exit code = %d, want 0\nstderr: %s", code, stderr) + testassert.Require(t, !strings.Contains(stderr, "resolve home dir"), "the binary-only flow must not resolve the home dir: %q", stderr) + { + _, err := os.Stat(target) + testassert.Require(t, os.IsNotExist(err), "binary not removed") } }) @@ -1301,38 +941,26 @@ func TestDispatchUninstall(t *testing.T) { t.Cleanup(func() { selfupdate.Geteuid = oldEuid }) _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge", "--yes"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "refusing to purge user data as root") { - t.Fatalf("stderr = %q", stderr) - } - if strings.Contains(stderr, "resolve home dir") { - t.Fatalf("the root refusal must fire before any home lookup: %q", stderr) - } + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "refusing to purge user data as root") && !strings.Contains(stderr, "resolve home dir"), "stderr = %q; the root refusal must fire before any home lookup: %q", stderr, stderr) }) t.Run("root refuses to purge", func(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dataDir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dataDir, 0o700)) fakeInstall(t) oldEuid := selfupdate.Geteuid selfupdate.Geteuid = func() int { return 0 } t.Cleanup(func() { selfupdate.Geteuid = oldEuid }) _, code, _, stderr := capture(t, []string{"lightcode", "uninstall", "--purge", "--yes"}) - if code != 1 { - t.Fatalf("exit code = %d, want 1", code) - } - if !strings.Contains(stderr, "lightcode: refusing to purge user data as root; run 'lightcode uninstall --purge' as the owning user first, then remove the binary with sudo") { - t.Fatalf("stderr = %q", stderr) - } - if _, err := os.Stat(dataDir); err != nil { - t.Fatal("root purge refusal must remove nothing") + testassert.Equal(t, code, 1) + testassert.Require(t, strings.Contains(stderr, "lightcode: refusing to purge user data as root; run 'lightcode uninstall --purge' as the owning user first, then remove the binary with sudo"), "stderr = %q", stderr) + { + _, err := os.Stat(dataDir) + testassert.Require(t, err == nil, "root purge refusal must remove nothing") } }) } @@ -1341,21 +969,15 @@ func TestDispatchCompletion(t *testing.T) { t.Run("registry drift guard across all shells", func(t *testing.T) { for _, shell := range []string{"bash", "zsh", "fish"} { _, code, stdout, stderr := capture(t, []string{"lightcode", "completion", shell}) - if code != 0 || stderr != "" { - t.Fatalf("%s: exit=%d stderr=%q", shell, code, stderr) - } + testassert.Require(t, code == 0 && stderr == "", "%s: exit=%d stderr=%q", shell, code, stderr) // Every registry command, every registered flag name, and the // top-level aliases must appear; a new command or flag that // misses the generator fails here. for i := range commands { cmd := &commands[i] - if !strings.Contains(stdout, cmd.name) { - t.Fatalf("%s: missing command %q", shell, cmd.name) - } + testassert.Require(t, strings.Contains(stdout, cmd.name), "%s: missing command %q", shell, cmd.name) for _, flagName := range registeredFlagNames(cmd) { - if !strings.Contains(stdout, flagName) { - t.Fatalf("%s: missing flag %q of %q", shell, flagName, cmd.name) - } + testassert.Require(t, strings.Contains(stdout, flagName), "%s: missing flag %q of %q", shell, flagName, cmd.name) } } aliases := []string{"-h", "--help", "-v", "--version"} @@ -1364,9 +986,7 @@ func TestDispatchCompletion(t *testing.T) { aliases = []string{"-s h", "-l help", "-s v", "-l version"} } for _, alias := range aliases { - if !strings.Contains(stdout, alias) { - t.Fatalf("%s: missing top-level alias %q", shell, alias) - } + testassert.Require(t, strings.Contains(stdout, alias), "%s: missing top-level alias %q", shell, alias) } } }) @@ -1375,59 +995,41 @@ func TestDispatchCompletion(t *testing.T) { _, _, stdout, _ := capture(t, []string{"lightcode", "completion", "bash"}) for _, name := range []string{"models", "doctor", "upgrade"} { _, after, found := strings.Cut(stdout, name+")") - if !found { - t.Fatalf("no case arm for %q", name) - } + testassert.Require(t, found, "no case arm for %q", name) arm, _, _ := strings.Cut(after, ";;") - if !strings.Contains(arm, "-h") || !strings.Contains(arm, "--help") { - t.Fatalf("%q arm missing injected help flags: %q", name, arm) - } + testassert.Require(t, strings.Contains(arm, "-h") && strings.Contains(arm, "--help"), "%q arm missing injected help flags: %q", name, arm) } }) t.Run("shell enum arm is static", func(t *testing.T) { _, _, stdout, _ := capture(t, []string{"lightcode", "completion", "bash"}) _, after, found := strings.Cut(stdout, "completion)") - if !found { - t.Fatal("no case arm for completion") - } + testassert.Require(t, found, "no case arm for completion") arm, _, _ := strings.Cut(after, ";;") for _, shell := range []string{"bash", "zsh", "fish"} { - if !strings.Contains(arm, shell) { - t.Fatalf("completion arm missing %q: %q", shell, arm) - } + testassert.Require(t, strings.Contains(arm, shell), "completion arm missing %q: %q", shell, arm) } }) t.Run("bare completion emits bash", func(t *testing.T) { _, code, stdout, _ := capture(t, []string{"lightcode", "completion"}) - if code != 0 || !strings.HasPrefix(stdout, "# bash completion for lightcode") { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } + testassert.Require(t, code == 0 && strings.HasPrefix(stdout, "# bash completion for lightcode"), "exit=%d stdout=%q", code, stdout) }) t.Run("unsupported shell exits 2", func(t *testing.T) { _, code, stdout, stderr := capture(t, []string{"lightcode", "completion", "powershell"}) - if code != 2 || stdout != "" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } - if !strings.Contains(stderr, `unsupported shell "powershell" (supported: bash, zsh, fish)`) { - t.Fatalf("stderr = %q", stderr) - } + testassert.Require(t, code == 2 && stdout == "", "exit=%d stdout=%q", code, stdout) + testassert.Require(t, strings.Contains(stderr, `unsupported shell "powershell" (supported: bash, zsh, fish)`), "stderr = %q", stderr) }) t.Run("second positional exits 2", func(t *testing.T) { _, code, _, stderr := capture(t, []string{"lightcode", "completion", "bash", "zsh"}) - if code != 2 || !strings.Contains(stderr, `unexpected argument "zsh"`) { - t.Fatalf("exit=%d stderr=%q", code, stderr) - } + testassert.Require(t, code == 2 && strings.Contains(stderr, `unexpected argument "zsh"`), "exit=%d stderr=%q", code, stderr) }) } func TestEscapeSingleQuoted(t *testing.T) { - if got := escapeSingleQuoted("a'b"); got != `a'\''b` { - t.Fatalf("escaped = %q", got) - } + testassert.Equal(t, escapeSingleQuoted("a'b"), `a'\''b`) } // TestDispatchRuntimeUsage pins the runtime command's argument contract at @@ -1445,21 +1047,10 @@ func TestDispatchRuntimeUsage(t *testing.T) { {"lightcode", "runtime", "--bogus"}, } { gui, code, stdout, stderr := capture(t, argv) - if gui { - t.Fatalf("%v: must not reach the GUI path", argv) - } - if code != 2 { - t.Fatalf("%v: exit code = %d, want 2", argv, code) - } - if stdout != "" { - t.Fatalf("%v: stdout must be empty, got %q", argv, stdout) - } - if !strings.HasPrefix(stderr, "lightcode: ") { - t.Fatalf("%v: stderr missing error prefix: %q", argv, stderr) - } - if !strings.Contains(stderr, "Usage: lightcode runtime") { - t.Fatalf("%v: stderr missing runtime usage line: %q", argv, stderr) - } + testassert.Require(t, !gui, "%v: must not reach the GUI path", argv) + testassert.Require(t, code == 2, "%v: exit code = %d, want 2", argv, code) + testassert.Require(t, stdout == "", "%v: stdout must be empty, got %q", argv, stdout) + testassert.Require(t, strings.HasPrefix(stderr, "lightcode: ") && strings.Contains(stderr, "Usage: lightcode runtime"), "%v: stderr missing error prefix: %q; %v: stderr missing runtime usage line: %q", argv, stderr, argv, stderr) } } @@ -1471,15 +1062,8 @@ func TestDispatchRuntimeUsage(t *testing.T) { func TestDispatchModelsUtilityRouting(t *testing.T) { t.Run("two positionals exit 2 as a usage error", func(t *testing.T) { _, code, stdout, stderr := capture(t, []string{"lightcode", "models", "p1", "p2"}) - if code != 2 || stdout != "" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } - if !strings.HasPrefix(stderr, "lightcode: ") || !strings.Contains(stderr, `unexpected argument "p2"`) { - t.Fatalf("stderr missing translated usage diagnostic: %q", stderr) - } - if !strings.Contains(stderr, "Usage: lightcode models") { - t.Fatalf("stderr missing models usage line: %q", stderr) - } + testassert.Require(t, code == 2 && stdout == "", "exit=%d stdout=%q", code, stdout) + testassert.Require(t, strings.HasPrefix(stderr, "lightcode: ") && strings.Contains(stderr, `unexpected argument "p2"`) && strings.Contains(stderr, "Usage: lightcode models"), "stderr missing translated usage diagnostic: %q; stderr missing models usage line: %q", stderr, stderr) }) t.Run("terminator keeps operands out of the flag parse", func(t *testing.T) { @@ -1487,12 +1071,8 @@ func TestDispatchModelsUtilityRouting(t *testing.T) { // violation proves the terminated operands reached the provider // check instead of being parsed as flags. _, code, stdout, stderr := capture(t, []string{"lightcode", "models", "--", "p1", "p2"}) - if code != 2 || stdout != "" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } - if !strings.Contains(stderr, `unexpected argument "p2"`) { - t.Fatalf("stderr missing translated usage diagnostic: %q", stderr) - } + testassert.Require(t, code == 2 && stdout == "", "exit=%d stdout=%q", code, stdout) + testassert.Require(t, strings.Contains(stderr, `unexpected argument "p2"`), "stderr missing translated usage diagnostic: %q", stderr) }) } @@ -1512,9 +1092,7 @@ func snapshotHome(t *testing.T, root string) map[string]string { snap[path] = fmt.Sprintf("%d:%d:%v", info.Size(), info.ModTime().UnixNano(), info.IsDir()) return nil }) - if err != nil && !os.IsNotExist(err) { - t.Fatalf("snapshot %s: %v", root, err) - } + testassert.Require(t, err == nil || os.IsNotExist(err), "snapshot %s: %v", root, err) return snap } @@ -1524,21 +1102,15 @@ func TestDispatchConfig(t *testing.T) { t.Setenv("HOME", home) t.Setenv("LIGHTCODE_CONFIG", "") _, code, stdout, stderr := capture(t, []string{"lightcode", "config", "--path"}) - if code != 0 || stderr != "" { - t.Fatalf("exit=%d stderr=%q", code, stderr) - } - if stdout != filepath.Join(home, ".lightcode", "config.json")+"\n" { - t.Fatalf("stdout = %q", stdout) - } + testassert.Require(t, code == 0 && stderr == "", "exit=%d stderr=%q", code, stderr) + testassert.Require(t, stdout == filepath.Join(home, ".lightcode", "config.json")+"\n", "stdout = %q", stdout) }) t.Run("path honors LIGHTCODE_CONFIG", func(t *testing.T) { custom := filepath.Join(t.TempDir(), "custom.json") t.Setenv("LIGHTCODE_CONFIG", custom) _, code, stdout, _ := capture(t, []string{"lightcode", "config", "--path"}) - if code != 0 || stdout != custom+"\n" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } + testassert.Require(t, code == 0 && stdout == custom+"\n", "exit=%d stdout=%q", code, stdout) }) t.Run("default is byte passthrough even for invalid json", func(t *testing.T) { @@ -1546,34 +1118,20 @@ func TestDispatchConfig(t *testing.T) { t.Setenv("HOME", home) t.Setenv("LIGHTCODE_CONFIG", "") body := "{not json at all" - if err := os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(home, ".lightcode", "config.json"), []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", "config.json"), []byte(body), 0o600)) _, code, stdout, stderr := capture(t, []string{"lightcode", "config"}) - if code != 0 || stderr != "" { - t.Fatalf("exit=%d stderr=%q", code, stderr) - } - if stdout != body { - t.Fatalf("stdout = %q, want byte-exact %q", stdout, body) - } + testassert.Require(t, code == 0 && stderr == "", "exit=%d stderr=%q", code, stderr) + testassert.Equal(t, stdout, body) }) t.Run("env override notes the path on stderr", func(t *testing.T) { custom := filepath.Join(t.TempDir(), "custom.json") - if err := os.WriteFile(custom, []byte("{}"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(custom, []byte("{}"), 0o600)) t.Setenv("LIGHTCODE_CONFIG", custom) _, code, stdout, stderr := capture(t, []string{"lightcode", "config"}) - if code != 0 || stdout != "{}" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } - if !strings.Contains(stderr, custom) { - t.Fatalf("stderr missing the resolved path: %q", stderr) - } + testassert.Require(t, code == 0 && stdout == "{}", "exit=%d stdout=%q", code, stdout) + testassert.Require(t, strings.Contains(stderr, custom), "stderr missing the resolved path: %q", stderr) }) t.Run("missing file errors without creating it", func(t *testing.T) { @@ -1583,14 +1141,11 @@ func TestDispatchConfig(t *testing.T) { path := filepath.Join(home, ".lightcode", "config.json") before := snapshotHome(t, home) _, code, stdout, stderr := capture(t, []string{"lightcode", "config"}) - if code != 1 || stdout != "" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } - if !strings.Contains(stderr, "lightcode: config not created yet (run lightcode once to initialize): "+path) { - t.Fatalf("stderr = %q", stderr) - } - if after := snapshotHome(t, home); !reflect.DeepEqual(before, after) { - t.Fatalf("config wrote to the filesystem:\nbefore: %v\nafter: %v", before, after) + testassert.Require(t, code == 1 && stdout == "", "exit=%d stdout=%q", code, stdout) + testassert.Require(t, strings.Contains(stderr, "lightcode: config not created yet (run lightcode once to initialize): "+path), "stderr = %q", stderr) + { + after := snapshotHome(t, home) + testassert.Require(t, reflect.DeepEqual(before, after), "config wrote to the filesystem:\nbefore: %v\nafter: %v", before, after) } }) @@ -1598,19 +1153,14 @@ func TestDispatchConfig(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) t.Setenv("LIGHTCODE_CONFIG", "") - if err := os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(home, ".lightcode", "config.json"), []byte("{}"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", "config.json"), []byte("{}"), 0o600)) before := snapshotHome(t, home) _, code, stdout, _ := capture(t, []string{"lightcode", "config"}) - if code != 0 || stdout != "{}" { - t.Fatalf("exit=%d stdout=%q", code, stdout) - } - if after := snapshotHome(t, home); !reflect.DeepEqual(before, after) { - t.Fatalf("config wrote to the filesystem:\nbefore: %v\nafter: %v", before, after) + testassert.Require(t, code == 0 && stdout == "{}", "exit=%d stdout=%q", code, stdout) + { + after := snapshotHome(t, home) + testassert.Require(t, reflect.DeepEqual(before, after), "config wrote to the filesystem:\nbefore: %v\nafter: %v", before, after) } }) @@ -1618,9 +1168,7 @@ func TestDispatchConfig(t *testing.T) { t.Setenv("HOME", "") t.Setenv("LIGHTCODE_CONFIG", "") _, code, _, stderr := capture(t, []string{"lightcode", "config", "--path"}) - if code != 1 || !strings.HasPrefix(stderr, "lightcode: ") { - t.Fatalf("exit=%d stderr=%q", code, stderr) - } + testassert.Require(t, code == 1 && strings.HasPrefix(stderr, "lightcode: "), "exit=%d stderr=%q", code, stderr) }) } @@ -1630,8 +1178,9 @@ func TestDispatchConfig(t *testing.T) { // symbols exist anywhere in the tree. func TestRemovedOwnerTransportSymbols(t *testing.T) { for _, name := range []string{"serve", "stop"} { - if cmd := findCommand(name); cmd != nil { - t.Fatalf("%q must resolve as an unknown command, got %#v", name, cmd) + { + cmd := findCommand(name) + testassert.Require(t, cmd == nil, "%q must resolve as an unknown command, got %#v", name, cmd) } } @@ -1699,13 +1248,9 @@ func TestRemovedOwnerTransportSymbols(t *testing.T) { return err } for _, symbol := range symbols { - if strings.Contains(string(b), symbol) { - t.Errorf("%s references removed owner transport symbol %q", path, symbol) - } + testassert.Check(t, !strings.Contains(string(b), symbol), "%s references removed owner transport symbol %q", path, symbol) } return nil }) - if err != nil { - t.Fatalf("scan: %v", err) - } + testassert.NoError(t, err) } diff --git a/frontend/src/client/app.test.ts b/frontend/src/client/app.test.ts index 056a49d3..50f67de8 100644 --- a/frontend/src/client/app.test.ts +++ b/frontend/src/client/app.test.ts @@ -23,14 +23,22 @@ vi.mock('../../client-bindings/wailsjs/go/desktopclient/Host', () => ({ // exact href here instead of letting the webview navigate itself. vi.mock('../../client-bindings/wailsjs/runtime/runtime', () => ({ BrowserOpenURL: vi.fn() })); -import type { Hydration, Session } from '../generated/protocol/types.gen'; +import type { Hydration } from '../generated/protocol/types.gen'; import { AppVersion, Call, PickDirectory, SetTitle } from '../../client-bindings/wailsjs/go/desktopclient/Host'; import { BrowserOpenURL } from '../../client-bindings/wailsjs/runtime/runtime'; import { SessionReader } from './reader'; import { AttemptLedger } from './attempts'; import { assertNativeInvocations } from './nativecontract'; - -const INSTANCE = '0123456789abcdef0123456789abcdef'; +import { + INSTANCE, + NEWER, + OLDER, + hydration, + json, + native, + revision, + session, +} from './test-fixtures'; // The scripted operations the mounted view addresses, keyed by the names its // call sites use. The shared Call binding dispatches every composed envelope @@ -87,71 +95,6 @@ vi.mocked(Call).mockImplementation(async (_generation, operation, params) => { }); const mounted: Array> = []; -// Descending last_activity then Session ID orders RFC3339 strings correctly; -// the fixtures use comparable timestamps. -const NEWER = '2026-10-02T10:00:00Z'; -const OLDER = '2026-10-01T05:00:00Z'; - -function sessionRev(durable: string) { - return { instance_id: INSTANCE, durable_revision: durable, local_revision: '0' }; -} - -function rootSession(id: string, lastActivity: string): Session { - return { - session_id: id, - workspace: '/ws', - lifecycle: 'open', - agent_type: 'primary', - created_at: '0', - last_activity: lastActivity, - session_revision: sessionRev('1'), - }; -} - -function makeHydration(overrides: Partial = {}): Hydration { - return { - session: rootSession('root1', NEWER), - selected_model: null, - session_revision: sessionRev('1'), - configuration_revision: { instance_id: INSTANCE, generation: '1' }, - warnings_revision: { instance_id: INSTANCE, revision: '1' }, - operations: [], - pending: { steering: [], queued: [] }, - conversation: { items: [] }, - usage: { totals: { by_model: [] }, context: { used_tokens: '0', context_window: 0 } }, - warnings: [], - background: [], - ...overrides, - }; -} - -// ok models one received 200 whose JSON body every selected decoder consumes. -function ok(data: unknown) { - return { status: 200, body: JSON.stringify(data), invalid: false }; -} - -// okRaw models one received 200 carrying its exact body text. -function okRaw(body: string) { - return { status: 200, body, invalid: false }; -} - -// httpError models a RECEIVED non-2xx: the validated Error/partial body is the -// in-band result beside its status, never a rejected promise. -function httpError(status: number, error: unknown) { - return { status, body: JSON.stringify(error), invalid: false }; -} - -// receivedInvalid models a 2xx whose body cannot be consumed: the native -// invalid bit survives the binding before any status/body branch. -function receivedInvalid(message: string) { - return { status: 200, body: message, invalid: true }; -} - -// noContent models a REAL 204 success: valid empty success at the boundary. -function noContent() { - return { status: 204, body: '', invalid: false }; -} - function fakeConnection() { const client = { generation: '1' }; let installed = ''; @@ -222,9 +165,9 @@ async function mountAppNoWait(connection: ReturnType) { // bindings get their inert defaults here; individual tests override them. function basicMocks(connection: ReturnType) { connection.openMock.mockResolvedValue({ instance_id: INSTANCE, generation: '1', workspace: '/ws' }); - vi.mocked(sdk.ListSessions).mockImplementation(async () => ok([rootSession('root1', NEWER), rootSession('root2', OLDER)])); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration())); - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ + vi.mocked(sdk.ListSessions).mockImplementation(async () => json(200, [session('root1', NEWER), session('root2', OLDER)])); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration())); + vi.mocked(sdk.GetConfiguration).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [ @@ -233,7 +176,7 @@ function basicMocks(connection: ReturnType) { ], providers: [], })); - vi.mocked(sdk.GetWarnings).mockResolvedValue(ok({ + vi.mocked(sdk.GetWarnings).mockResolvedValue(json(200, { warnings_revision: { instance_id: INSTANCE, revision: '1' }, warnings: [], })); @@ -286,8 +229,8 @@ afterEach(async () => { // mockImplementation can leak into the next test. Reset the two // highest-traffic mocks to a neutral default so every test that needs // specific behavior sets its own implementation explicitly. - vi.mocked(sdk.ListSessions).mockImplementation(async () => ok([])); - vi.mocked(sdk.GetSessionHydration).mockImplementation(async () => ok(makeHydration())); + vi.mocked(sdk.ListSessions).mockImplementation(async () => json(200, [])); + vi.mocked(sdk.GetSessionHydration).mockImplementation(async () => json(200, hydration())); } }); @@ -307,20 +250,20 @@ describe('initial composition', () => { // The global configuration clock advances to generation 2 while the // hydration keeps carrying generation 1: the accepted floor rejects it // and the reader re-reads until the projection catches up. - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ + vi.mocked(sdk.GetConfiguration).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], providers: [], })); - const hydrationV1 = makeHydration(); - const hydrationV2 = makeHydration({ + const hydrationV1 = hydration(); + const hydrationV2 = hydration({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, conversation: { items: [{ item_id: 'i9', kind: 'input', committed_at: '0', origin: 'user', content: [{ kind: 'text', text: 'item i9' }] } as Hydration['conversation']['items'][number]] }, }); vi.mocked(sdk.GetSessionHydration) - .mockResolvedValueOnce(ok(hydrationV1)) - .mockResolvedValue(ok(hydrationV2)); + .mockResolvedValueOnce(json(200, hydrationV1)) + .mockResolvedValue(json(200, hydrationV2)); await mountApp(connection); await settle(); // The stale-body hydration never rendered; the accepted-floor fresh read did. @@ -335,13 +278,13 @@ describe('initial composition', () => { // decimal shape. No reader floor exists yet, so only the initial global // validation can reject it; the repair's own read is valid. vi.mocked(sdk.GetConfiguration) - .mockResolvedValueOnce(ok({ + .mockResolvedValueOnce(json(200, { configuration_revision: { instance_id: INSTANCE, generation: 'not-a-number' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], providers: [], })) - .mockResolvedValue(ok({ + .mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], @@ -360,13 +303,13 @@ describe('initial composition', () => { basicMocks(connection); const foreign = { instance_id: 'ffffffffffffffffffffffffffffffff', generation: '1' }; vi.mocked(sdk.GetConfiguration) - .mockResolvedValueOnce(ok({ + .mockResolvedValueOnce(json(200, { configuration_revision: foreign, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], providers: [], })) - .mockResolvedValue(ok({ + .mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], @@ -389,13 +332,13 @@ describe('initial composition', () => { it('a repair discards a held initial list and never recomposes an absent selection', async () => { const connection = fakeConnection(); connection.openMock.mockResolvedValue({ instance_id: INSTANCE, generation: '1', workspace: '/ws' }); - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ + vi.mocked(sdk.GetConfiguration).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], providers: [], })); - vi.mocked(sdk.GetWarnings).mockResolvedValue(ok({ warnings_revision: { instance_id: INSTANCE, revision: '1' }, warnings: [] })); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration())); + vi.mocked(sdk.GetWarnings).mockResolvedValue(json(200, { warnings_revision: { instance_id: INSTANCE, revision: '1' }, warnings: [] })); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration())); // The boot list is held; a repair then completes while it is parked. const { promise: listGate, resolve: releaseList } = Promise.withResolvers(); let listCalls = 0; @@ -404,7 +347,7 @@ describe('initial composition', () => { if (listCalls === 1) { return listGate; } - return ok([rootSession('root1', NEWER)]); + return json(200, [session('root1', NEWER)]); }); const { target } = await mountAppNoWait(connection); await settle(); @@ -414,7 +357,7 @@ describe('initial composition', () => { await vi.waitFor(() => expect(vi.mocked(sdk.GetConfiguration).mock.calls.length).toBeGreaterThan(1)); // Release the stale boot list AFTER the repair; its adoption is discarded // and the ended attempt is never rescheduled by the repair. - releaseList(ok([rootSession('root-stale', NEWER)])); + releaseList(json(200, [session('root-stale', NEWER)])); await settle(); await settle(); expect(target.textContent).not.toContain('root-stale'); @@ -424,9 +367,9 @@ describe('initial composition', () => { it('creates a primary session when no open root exists', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListSessions).mockResolvedValue(ok([])); - vi.mocked(sdk.CreateSession).mockResolvedValue(ok(rootSession('primary1', NEWER))); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ session: rootSession('primary1', NEWER) }))); + vi.mocked(sdk.ListSessions).mockResolvedValue(json(200, [])); + vi.mocked(sdk.CreateSession).mockResolvedValue(json(200, session('primary1', NEWER))); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ session: session('primary1', NEWER) }))); const { target } = await mountApp(connection); expect(vi.mocked(sdk.CreateSession)).toHaveBeenCalledWith('{"body":{"workspace":"/ws","agent_type":"primary"}}'); expect(vi.mocked(sdk.GetSessionHydration)).toHaveBeenCalledWith('{"session_id":"primary1"}'); @@ -449,7 +392,7 @@ describe('conversation rendering', () => { { item_id: 'c1', kind: 'compaction', committed_at: '0', summary: 'summary here', model: 'prov/compact' }, { item_id: 'o1', kind: 'operation_end', committed_at: '0', operation_id: 'op-o1', status: 'failure', detail: 'failed detail' }, ]; - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ conversation: { items: items as Hydration['conversation']['items'] }, pending: { steering: [{ operation_id: 'p1', origin: 'user', content: [{ kind: 'text', text: 'steering one' }] }], @@ -479,7 +422,7 @@ describe('conversation rendering', () => { it('renders opaque content and extra metadata without control interpretation', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ conversation: { items: [ { item_id: 'u1', kind: 'input', committed_at: '0', origin: 'user', content: [{ kind: 'opaque', opaque_wire_type: 'odd', extra: { x: 'not html', big: JSON.rawJSON('9007199254740993'), huge: JSON.rawJSON('1e400') } }] }, @@ -504,11 +447,11 @@ describe('conversation rendering', () => { const overflow = JSON.rawJSON('1e400'); const opaqueInput = { ...inputItem('i-lex', 'user'), content: [{ kind: 'text' as const, text: 'hello' }, { kind: 'opaque' as const, opaque_wire_type: 'w', extra: { big: lex, huge: overflow } }] }; const opaqueAssistant = { item_id: 'a-lex', kind: 'assistant' as const, committed_at: '0', status: 'completed' as const, source: 'p/m', content: [{ kind: 'text' as const, text: 'answer' }], tool_calls: [], extra: { n: lex } }; - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ conversation: { items: [opaqueAssistant as Hydration['conversation']['items'][number]], older_cursor: 'p1' }, }))); - vi.mocked(sdk.GetSessionHistory).mockResolvedValue(ok({ session_revision: sessionRev('1'), items: [opaqueInput], older_cursor: null })); - vi.mocked(sdk.ForkSession).mockResolvedValue(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('caller') })); + vi.mocked(sdk.GetSessionHistory).mockResolvedValue(json(200, { session_revision: revision('1'), items: [opaqueInput], older_cursor: null })); + vi.mocked(sdk.ForkSession).mockResolvedValue(json(200, { session: session('dest1', NEWER), operation: runningOperation('caller') })); const { target } = await mountApp(connection); await settle(); (target.querySelector('[aria-label="Load older messages"]') as HTMLElement).click(); @@ -530,7 +473,7 @@ describe('conversation rendering', () => { describe('provisional activity', () => { function runningHydration() { - return makeHydration({ + return hydration({ active_operation: { operation_id: 'op1', request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'prov/m', usage: { totals: { by_model: [] } } }, }); } @@ -538,7 +481,7 @@ describe('provisional activity', () => { it('shows bounded text with repeated positions, refusal and tool rows, and discards unmatched progress', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(runningHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, runningHydration())); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.querySelector('.activity-label')).not.toBeNull()); await settle(); @@ -564,7 +507,7 @@ describe('provisional activity', () => { it('renders progress delivered before an unobserved terminal only in the provisional area', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(runningHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, runningHydration())); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.querySelector('.activity-label')).not.toBeNull()); await settle(); @@ -587,7 +530,7 @@ describe('provisional activity', () => { it('marks truncation after the trailing 64 KiB bound', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(runningHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, runningHydration())); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.querySelector('.activity-label')).not.toBeNull()); await settle(); @@ -616,7 +559,7 @@ describe('provisional activity', () => { describe('composer', () => { it('captures generated submit requests with a minted ID and clears only the matching draft', async () => { - vi.mocked(sdk.SubmitSession).mockResolvedValue(ok({ disposition: 'admitted' })); + vi.mocked(sdk.SubmitSession).mockResolvedValue(json(200, { disposition: 'admitted' })); const { target } = await mountBasic(); typeInto(target, 'hello there'); await settle(); @@ -633,7 +576,7 @@ describe('composer', () => { }); it('sends queued mode through the explicit Queue action', async () => { - vi.mocked(sdk.SubmitSession).mockResolvedValue(ok({ disposition: 'queued' })); + vi.mocked(sdk.SubmitSession).mockResolvedValue(json(200, { disposition: 'queued' })); const { target } = await mountBasic(); typeInto(target, 'queued message'); await settle(); @@ -648,7 +591,7 @@ describe('composer', () => { const { promise: firstGate, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.SubmitSession) .mockImplementationOnce(() => firstGate as never) - .mockResolvedValue(ok({ disposition: 'queued' })); + .mockResolvedValue(json(200, { disposition: 'queued' })); const { target } = await mountBasic(); typeInto(target, 'same text'); await settle(); @@ -669,12 +612,12 @@ describe('composer', () => { expect(calls[1].body.mode).toBe('queued'); expect(calls[0].body.content).toEqual([{ kind: 'text', text: 'same text' }]); expect(calls[1].body.content).toEqual([{ kind: 'text', text: 'same text' }]); - releaseFirst(ok({ disposition: 'admitted' })); + releaseFirst(json(200, { disposition: 'admitted' })); await settle(); }); it('keeps the draft and shows the generated error on a known rejection', async () => { - vi.mocked(sdk.SubmitSession).mockResolvedValue(httpError(400, { code: 'conflict', message: 'busy now' })); + vi.mocked(sdk.SubmitSession).mockResolvedValue(json(400, { code: 'conflict', message: 'busy now' })); const { target } = await mountBasic(); typeInto(target, 'rejected message'); await settle(); @@ -730,13 +673,13 @@ describe('composer', () => { (target.querySelector('.send') as HTMLElement).click(); typeInto(target, 'second draft'); await settle(); - resolveSubmit(ok({ disposition: 'admitted' })); + resolveSubmit(json(200, { disposition: 'admitted' })); await settle(); expect((target.querySelector('textarea') as HTMLTextAreaElement).value).toBe('second draft'); }); it('submits on Enter', async () => { - vi.mocked(sdk.SubmitSession).mockResolvedValue(ok({ disposition: 'admitted' })); + vi.mocked(sdk.SubmitSession).mockResolvedValue(json(200, { disposition: 'admitted' })); const { target } = await mountBasic(); typeInto(target, 'enter send'); await settle(); @@ -779,17 +722,17 @@ describe('composer', () => { describe('controls', () => { function activeHydration() { - return makeHydration({ + return hydration({ active_operation: { operation_id: 'op1', request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'prov/m', usage: { totals: { by_model: [] } } }, }); } it('issues Interrupt and Stop as separate addressed controls and a 204 success writes no note', async () => { - vi.mocked(sdk.InterruptSession).mockResolvedValue(noContent()); - vi.mocked(sdk.StopSession).mockResolvedValue(noContent()); + vi.mocked(sdk.InterruptSession).mockResolvedValue(native(204, '', false)); + vi.mocked(sdk.StopSession).mockResolvedValue(native(204, '', false)); const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(activeHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, activeHydration())); const { target } = await mountApp(connection); await settle(); const readsBefore = vi.mocked(sdk.GetSessionHydration).mock.calls.length; @@ -811,7 +754,7 @@ describe('controls', () => { it('reports an addressed control rejection and unknown outcome even after a stamp move', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(activeHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, activeHydration())); const { target } = await mountApp(connection); await settle(); const { promise: interruptGate, resolve: resolveInterrupt } = Promise.withResolvers(); @@ -822,7 +765,7 @@ describe('controls', () => { // The UI and the addressed reader are root2 before the held call lands. expect(target.querySelector('.toolbar')?.textContent).toContain('root2'); await vi.waitFor(() => expect(vi.mocked(sdk.GetSessionHydration)).toHaveBeenLastCalledWith('{"session_id":"root2"}')); - resolveInterrupt(httpError(500, { code: 'internal', message: 'interrupt blew up' })); + resolveInterrupt(json(500, { code: 'internal', message: 'interrupt blew up' })); await settle(); await settle(); // The root1 failure is still reported against its captured address... @@ -842,11 +785,11 @@ describe('controls', () => { }); it('interrupts on composer Escape and closes modals first', async () => { - vi.mocked(sdk.InterruptSession).mockResolvedValue(ok(undefined)); - vi.mocked(sdk.ListModels).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [] })); + vi.mocked(sdk.InterruptSession).mockResolvedValue(json(200, undefined)); + vi.mocked(sdk.ListModels).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [] })); const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(activeHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, activeHydration())); const { target } = await mountApp(connection); await settle(); // Escape with an open modal closes the modal and never interrupts. @@ -866,7 +809,7 @@ describe('controls', () => { }); it('submits a manual compact with a minted caller ID', async () => { - vi.mocked(sdk.CompactSession).mockResolvedValue(ok({ operation_id: 'op1', request_kind: 'compact', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'prov/m', usage: { totals: { by_model: [] } } })); + vi.mocked(sdk.CompactSession).mockResolvedValue(json(200, { operation_id: 'op1', request_kind: 'compact', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'prov/m', usage: { totals: { by_model: [] } } })); const { target } = await mountBasic(); // The toolbar has no duplicate Compact; the composer owns the control. expect(target.querySelector('.toolbar .compact-btn')).toBeNull(); @@ -880,7 +823,7 @@ describe('controls', () => { it('labels an active compact Operation without naming its transport model', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ active_operation: { operation_id: 'op1', request_kind: 'compact', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'prov/compact-transport', usage: { totals: { by_model: [] } } }, }))); const { target } = await mountApp(connection); @@ -903,13 +846,13 @@ function selectRoot2(target: HTMLElement) { describe('keyboard pickers', () => { it('Ctrl+M reads models and applies a type-wide SetAgentTypeModel', async () => { - vi.mocked(sdk.ListModels).mockResolvedValue(ok({ + vi.mocked(sdk.ListModels).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [ { ref: 'prov/a', provider: 'prov', provider_name: 'Provider', model: 'a', display_name: 'Model A', context_window: 1000, max_output_tokens: 10, hidden: false, provider_hidden: false, incomplete: false, source: 'user' }, ], })); - vi.mocked(sdk.SetAgentTypeModel).mockResolvedValue(ok({ + vi.mocked(sdk.SetAgentTypeModel).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: { name: 'primary', description: '', subagent: false, model: 'prov/a', readonly: false, write_dir: '', tools: [], capabilities: [] }, })); @@ -919,7 +862,7 @@ describe('keyboard pickers', () => { let hydrationReads = 0; vi.mocked(sdk.GetSessionHydration).mockImplementation(async () => { hydrationReads += 1; - return ok(makeHydration(hydrationReads > 1 + return json(200, hydration(hydrationReads > 1 ? { configuration_revision: { instance_id: INSTANCE, generation: '2' } } : {})); }); @@ -944,14 +887,14 @@ describe('keyboard pickers', () => { it('keeps an unavailable selected_model from the accepted hydration and reports a selection rejection', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ selected_model: 'prov/missing' }))); - vi.mocked(sdk.ListModels).mockResolvedValue(ok({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ selected_model: 'prov/missing' }))); + vi.mocked(sdk.ListModels).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [ { ref: 'prov/available', provider: 'prov', provider_name: 'Provider', model: 'available', display_name: 'Available', context_window: 1000, max_output_tokens: 10, hidden: false, provider_hidden: false, incomplete: false, source: 'user' }, ], })); - vi.mocked(sdk.SetAgentTypeModel).mockResolvedValue(httpError(404, { code: 'not_found', message: 'model not available' })); + vi.mocked(sdk.SetAgentTypeModel).mockResolvedValue(json(404, { code: 'not_found', message: 'model not available' })); const { target } = await mountApp(connection); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'm', ctrlKey: true })); await settle(); @@ -975,7 +918,7 @@ describe('keyboard pickers', () => { }); it('a model success from an old selection reports success but pollutes no current floor and starts no repair', async () => { - vi.mocked(sdk.ListModels).mockResolvedValue(ok({ + vi.mocked(sdk.ListModels).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [{ ref: 'prov/a', provider: 'prov', provider_name: 'Provider', model: 'a', display_name: 'Model A', context_window: 1000, max_output_tokens: 10, hidden: false, provider_hidden: false, incomplete: false, source: 'user' }], })); @@ -994,7 +937,7 @@ describe('keyboard pickers', () => { // Move the selection to root2 while the model mutation is in flight. await selectRoot2(target); // The old-owner response finally lands carrying a foreign clock. - resolveModel(ok({ + resolveModel(json(200, { configuration_revision: { instance_id: 'ffffffffffffffffffffffffffffffff', generation: '9' }, result: { name: 'primary', description: '', subagent: false, model: 'prov/a', readonly: false, write_dir: '', tools: [], capabilities: [] }, })); @@ -1033,27 +976,27 @@ describe('child inspection and background', () => { agent_type: 'secondary', created_at: '0', last_activity: OLDER, - session_revision: sessionRev('1'), + session_revision: revision('1'), }; } it('unions both lifecycle lists with live membership, keeps jobs separate and inspects passively', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ background: [{ kind: 'child', id: 'child-open' }, { kind: 'child', id: 'child-live-only' }, { kind: 'job', id: 'job1' }], }))); vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; if (lifecycle === 'open') { - return ok([ - rootSession('root1', NEWER), + return json(200, [ + session('root1', NEWER), childSession('child-open', 'open'), childSession('child-both', 'open'), { ...childSession('child-other-root', NEWER), parent_session_id: 'other-root' }, ]); } - return ok([childSession('child-arch', 'archived'), childSession('child-both', 'archived')]); + return json(200, [childSession('child-arch', 'archived'), childSession('child-both', 'archived')]); }); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.textContent).toContain('root1')); @@ -1101,12 +1044,12 @@ describe('child inspection and background', () => { vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; if (id === 'child1') { - return ok(makeHydration({ + return json(200, hydration({ session: childSession('child1', 'open'), conversation: { items: childItems as Hydration['conversation']['items'] }, })); } - return ok(makeHydration({ + return json(200, hydration({ conversation: { items: rootItems as Hydration['conversation']['items'] }, background: [{ kind: 'child', id: 'child1' }], })); @@ -1141,15 +1084,15 @@ describe('child inspection and background', () => { it('renders the child own pending projection in the passive inspector, including repeats', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListSessions).mockResolvedValue(ok([ - rootSession('root1', NEWER), - { ...rootSession('kid', OLDER), parent_session_id: 'root1' }, + vi.mocked(sdk.ListSessions).mockResolvedValue(json(200, [ + session('root1', NEWER), + { ...session('kid', OLDER), parent_session_id: 'root1' }, ])); vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; if (id === 'kid') { - return ok(makeHydration({ - session: rootSession('kid', OLDER), + return json(200, hydration({ + session: session('kid', OLDER), pending: { steering: [ { operation_id: 'dup', origin: 'user', content: [{ kind: 'text', text: 'kid steering first' }] }, @@ -1159,7 +1102,7 @@ describe('child inspection and background', () => { }, })); } - return ok(makeHydration()); + return json(200, hydration()); }); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.textContent).toContain('root1')); @@ -1190,14 +1133,14 @@ describe('child inspection and background', () => { it('child-only provisional progress does not recompute the root pane derivation', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListSessions).mockResolvedValue(ok([ - rootSession('root1', NEWER), - { ...rootSession('kid', OLDER), parent_session_id: 'root1' }, + vi.mocked(sdk.ListSessions).mockResolvedValue(json(200, [ + session('root1', NEWER), + { ...session('kid', OLDER), parent_session_id: 'root1' }, ])); vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; - return ok(makeHydration({ - session: rootSession(id, OLDER), + return json(200, hydration({ + session: session(id, OLDER), active_operation: { operation_id: id === 'kid' ? 'kop' : 'rop', request_kind: 'message', @@ -1239,14 +1182,14 @@ describe('child inspection and background', () => { vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const query = (JSON.parse(options) as { query: { lifecycle: string } }).query; if (query.lifecycle === 'open') { - return ok([rootSession('root1', NEWER), { ...rootSession('kid', NEWER), parent_session_id: 'root1' }]); + return json(200, [session('root1', NEWER), { ...session('kid', NEWER), parent_session_id: 'root1' }]); } - return ok([]); + return json(200, []); }); vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; - return ok(makeHydration({ - session: rootSession(id, NEWER), + return json(200, hydration({ + session: session(id, NEWER), conversation: { items: [{ item_id: `first-${id}`, kind: 'input', committed_at: '0', origin: 'user', content: [{ kind: 'text', text: `hello ${id}` }] } as Hydration['conversation']['items'][number]] }, })); }); @@ -1263,10 +1206,10 @@ describe('child inspection and background', () => { }); it('Escape from the composer interrupts and leaves the nonmodal child pane open; pane Escape closes only from inside it', async () => { - vi.mocked(sdk.InterruptSession).mockResolvedValue(ok(undefined)); + vi.mocked(sdk.InterruptSession).mockResolvedValue(json(200, undefined)); const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListSessions).mockImplementation(async () => ok([rootSession('root1', NEWER), childSession('child-open', 'open')])); + vi.mocked(sdk.ListSessions).mockImplementation(async () => json(200, [session('root1', NEWER), childSession('child-open', 'open')])); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.textContent).toContain('root1')); await settle(); @@ -1292,88 +1235,82 @@ describe('child inspection and background', () => { }); describe('warnings and usage', () => { - it('shows own warnings and a global-only indicator, filtering other sessions', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + it.each([ + { + title: 'shows own warnings and a global-only indicator, filtering other sessions', warnings: [ { source: 'a', kind: 'k', message: 'own message', session_id: 'root1' }, { source: 'a', kind: 'k', message: 'global message' }, { source: 'a', kind: 'k', message: 'other session message', session_id: 'other' }, ], - }))); - const { target } = await mountApp(connection); - await settle(); - (target.querySelector('.warn') as HTMLElement).click(); - await settle(); - expect(target.querySelector('dialog')).not.toBeNull(); - expect(target.textContent).toContain('Global'); - expect(target.textContent).toContain('global message'); - expect(target.textContent).toContain('This session'); - expect(target.textContent).toContain('own message'); - expect(target.textContent).not.toContain('other session message'); - }); - - it('renders colliding warning tuples as distinct rows (unique tuples, non-injective key encoding)', async () => { - const connection = fakeConnection(); - basicMocks(connection); - // Two DISTINCT tuples whose old concatenated key collides: ('p','a','bc') - // and ('p','ab','c') both concatenate to 'pabc' (within one plugin), and - // ('x','yz','w') and ('xy','z','w') both concatenate to 'xyzw' (across - // plugin-source prefixes). Presentation is stateless and must not dedup - // or mint identities: every distinct report renders once. - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + verify: (target: HTMLElement) => { + expect(target.querySelector('dialog')).not.toBeNull(); + expect(target.textContent).toContain('Global'); + expect(target.textContent).toContain('global message'); + expect(target.textContent).toContain('This session'); + expect(target.textContent).toContain('own message'); + expect(target.textContent).not.toContain('other session message'); + }, + }, + { + title: 'renders colliding warning tuples as distinct rows (unique tuples, non-injective key encoding)', + // Two DISTINCT tuples whose old concatenated key collides: ('p','a','bc') + // and ('p','ab','c') both concatenate to 'pabc' (within one plugin), and + // ('x','yz','w') and ('xy','z','w') both concatenate to 'xyzw' (across + // plugin-source prefixes). Presentation is stateless and must not dedup + // or mint identities: every distinct report renders once. warnings: [ { source: 'p', kind: 'a', message: 'bc' }, { source: 'p', kind: 'ab', message: 'c' }, { source: 'x', kind: 'yz', message: 'w' }, { source: 'xy', kind: 'z', message: 'w' }, ], - }))); - const { target } = await mountApp(connection); - await settle(); - (target.querySelector('.warn') as HTMLElement).click(); - await settle(); - const rows = [...target.querySelectorAll('.warning.global')]; - expect(rows).toHaveLength(4); - const texts = rows.map((r) => r.textContent?.replace(/\s+/g, ' ').trim()); - // The within-plugin pair ('p','a','bc') and ('p','ab','c') is two distinct - // tuples whose display text also collides — multiplicity is preserved as - // two rows regardless. - expect(texts.filter((t) => t === 'p · abc')).toHaveLength(2); - // The across-prefix pair renders distinguishably. - expect(texts).toContain('x · yzw'); - expect(texts).toContain('xy · zw'); - expect(target.querySelector('.warning.own')).toBeNull(); - }); - - it('preserves own/sibling filtering alongside colliding warning keys', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + verify: (target: HTMLElement) => { + const rows = [...target.querySelectorAll('.warning.global')]; + expect(rows).toHaveLength(4); + const texts = rows.map((r) => r.textContent?.replace(/\s+/g, ' ').trim()); + // The within-plugin pair ('p','a','bc') and ('p','ab','c') is two distinct + // tuples whose display text also collides — multiplicity is preserved as + // two rows regardless. + expect(texts.filter((t) => t === 'p · abc')).toHaveLength(2); + // The across-prefix pair renders distinguishably. + expect(texts).toContain('x · yzw'); + expect(texts).toContain('xy · zw'); + expect(target.querySelector('.warning.own')).toBeNull(); + }, + }, + { + title: 'preserves own/sibling filtering alongside colliding warning keys', warnings: [ { source: 'p', kind: 'a', message: 'bc', session_id: 'root1' }, { source: 'p', kind: 'ab', message: 'c', session_id: 'root1' }, { source: 'p', kind: 'a', message: 'bc', session_id: 'sibling' }, ], - }))); + verify: (target: HTMLElement) => { + // The two colliding OWN tuples both render; the sibling is filtered out. + const ownRows = [...target.querySelectorAll('.warning.own')]; + expect(ownRows).toHaveLength(2); + expect(target.textContent).not.toContain('sibling'); + }, + }, + ])('$title', async ({ warnings, verify }) => { + const connection = fakeConnection(); + basicMocks(connection); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ warnings }))); const { target } = await mountApp(connection); await settle(); (target.querySelector('.warn') as HTMLElement).click(); await settle(); - // The two colliding OWN tuples both render; the sibling is filtered out. - const ownRows = [...target.querySelectorAll('.warning.own')]; - expect(ownRows).toHaveLength(2); - expect(target.textContent).not.toContain('sibling'); + verify(target); }); it('replaces warning rows on authoritative replacement', async () => { const connection = fakeConnection(); basicMocks(connection); - const first = ok(makeHydration({ + const first = json(200, hydration({ warnings: [{ source: 'p', kind: 'a', message: 'bc' }, { source: 'p', kind: 'ab', message: 'c' }], })); - const second = ok(makeHydration({ + const second = json(200, hydration({ session_revision: { instance_id: INSTANCE, durable_revision: '2', local_revision: '0' }, warnings: [{ source: 'q', kind: 'r', message: 's' }], })); @@ -1396,7 +1333,7 @@ describe('warnings and usage', () => { it('displays exact decimal usage without rounding', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ usage: { totals: { by_model: [ @@ -1445,7 +1382,7 @@ describe('repair', () => { // The Runtime's fresh read is monotonic: the held read and the steady-state // mock must not regress, or a mandated fresh read is always "stale". Resolve // the gated read with the same current body the steady state serves. - settleRead(ok(makeHydration())); + settleRead(json(200, hydration())); await vi.waitFor(() => expect(connection.openMock.mock.calls.length).toBe(opensBefore + 2)); // A late callback for an already-replaced generation is subsumed: it must // not invalidate the freshly adopted views or open again. @@ -1496,7 +1433,7 @@ describe('repair', () => { await vi.waitFor(() => expect(vi.mocked(sdk.GetSessionHydration).mock.calls.length).toBe(readsAtBoot + 1)); // The owner's fresh read decodes to an invalid body: it reports inside the // owner as that read's failure instead of a synchronous recursive Open. - settleRead(receivedInvalid('hydration body undecodable')); + settleRead(native(200, 'hydration body undecodable', true)); await settle(); await settle(); await settle(); @@ -1511,8 +1448,8 @@ describe('repair', () => { basicMocks(connection); vi.mocked(sdk.GetSessionHydration).mockImplementation(async (params: string) => { const id = (JSON.parse(params) as { session_id: string }).session_id; - return ok(makeHydration({ - session: rootSession(id, NEWER), + return json(200, hydration({ + session: session(id, NEWER), configuration_revision: { instance_id: INSTANCE, generation: '2' }, })); }); @@ -1537,7 +1474,7 @@ describe('repair', () => { await settle(); (target.querySelector('[aria-label="Select session root2"]') as HTMLElement).click(); await vi.waitFor(() => expect(target.textContent).toContain('root2')); - releaseConfiguration(ok({ + releaseConfiguration(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [{ name: 'reviewer', description: 'Review changes', subagent: false, model: '', readonly: false, write_dir: '', tools: [], capabilities: [] }], @@ -1666,9 +1603,82 @@ async function openOwnedFork(target: HTMLElement, rewind: boolean) { (target.querySelector('[aria-label="Submit fork"]') as HTMLElement).click(); } +// The mounted provider editor shared by the plain and discovery cases: the +// list and detail reads answer from the supplied providers, while every +// mutation, external-key and raw-body mock stays a per-case concern. +function editorConnection(providers: unknown[]) { + const connection = fakeConnection(); + basicMocks(connection); + vi.mocked(sdk.ListProviders).mockResolvedValue(json(200, { + configuration_revision: { instance_id: INSTANCE, generation: '1' }, + providers: providers as never, + })); + vi.mocked(sdk.GetProviderDetail).mockImplementation(async (params: string) => { + const id = (JSON.parse(params) as { query: { provider_id: string } }).query.provider_id; + return json(200, { + configuration_revision: { instance_id: INSTANCE, generation: '1' }, + provider: (providers as Array<{ id: string }>).find((provider) => provider.id === id) as never, + }); + }); + return connection; +} + +async function openProviderEditor(target: HTMLElement) { + await openSettings(target); + (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); + await settle(); +} + +// openNewProvider opens the authored create form on top of the provider list. +async function openNewProvider(target: HTMLElement) { + await openProviderEditor(target); + (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); + await settle(); +} + +// openSnapshots opens the code-snapshots dialog. Each case keeps its own +// settle counts and any held read/release gesture. +async function openSnapshots(target: HTMLElement) { + await openSettings(target); + (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); +} + +// readRetainedSnapshots targets one retained session through the loaded +// Workspace; the caller's settles, held replies and draft retargets stay in +// the case. +async function readRetainedSnapshots(target: HTMLElement, id: string) { + await changeInput(target, 'Retained session id', id); + (target.querySelector('[aria-label="Read retained snapshots"]') as HTMLElement).click(); +} + +// The richer Fork fixture: four inputs and three owned operations, plus the +// plain Fork success default the restore cases rely on. Per-case overrides and +// scripts stay with their cases. +function forkConnection(overrides: Partial = {}) { + const connection = fakeConnection(); + basicMocks(connection); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ + conversation: { + items: [ + inputItem('i-owned', 'user', 'op-owned'), + inputItem('i-copied', 'user'), + inputItem('i-runtime', 'runtime', 'op-runtime'), + inputItem('i-plugin', 'plugin', 'op-plugin'), + ], + }, + operations: [runningOperation('op-owned'), runningOperation('op-runtime'), runningOperation('op-plugin')], + ...overrides, + }))); + vi.mocked(sdk.ForkSession).mockResolvedValue(json(200, { + session: session('dest1', NEWER), + operation: runningOperation('x'), + })); + return connection; +} + describe('repair projection ownership', () => { function ownedHydration() { - return makeHydration({ + return hydration({ conversation: { items: [inputItem('i-owned', 'user', 'op-owned')] }, operations: [runningOperation('op-owned')], warnings: [{ source: 's', kind: 'k', message: 'own warning', session_id: 'root1' }], @@ -1686,12 +1696,12 @@ describe('repair projection ownership', () => { it('closes every runtime-data dialog when a new-instance repair starts', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([{ root: '/ws', display_name: 'ws' }])); - vi.mocked(sdk.ListModels).mockResolvedValue(ok({ + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [{ root: '/ws', display_name: 'ws' }])); + vi.mocked(sdk.ListModels).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [{ ref: 'prov/a', provider: 'prov', provider_name: 'Provider', model: 'a', display_name: 'Model A', context_window: 1000, max_output_tokens: 10, hidden: false, provider_hidden: false, incomplete: false, source: 'user' }], })); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(ownedHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, ownedHydration())); const { target } = await mountApp(connection); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'm', ctrlKey: true })); await settle(); @@ -1747,17 +1757,17 @@ describe('repair projection ownership', () => { it('a failed fresh configuration/warning read leaves no old-owner roster or warning data', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ + vi.mocked(sdk.GetConfiguration).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [{ name: 'other-agent', description: 'Old owner roster', subagent: false, model: '', readonly: false, write_dir: '', tools: [], capabilities: [] }], providers: [], })); - vi.mocked(sdk.GetWarnings).mockResolvedValue(ok({ + vi.mocked(sdk.GetWarnings).mockResolvedValue(json(200, { warnings_revision: { instance_id: INSTANCE, revision: '1' }, warnings: [{ source: 'g', kind: 'k', message: 'old owner global' }], })); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ warnings: [{ source: 'g', kind: 'k', message: 'old owner global' }], }))); const { target } = await mountApp(connection); @@ -1779,7 +1789,7 @@ describe('repair projection ownership', () => { it('preserves the composer draft and pending attempts across a repair without resending', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(ownedHydration())); + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, ownedHydration())); const { promise: submitGate, reject: releaseSubmit } = Promise.withResolvers(); const { promise: forkGate, reject: releaseFork } = Promise.withResolvers(); vi.mocked(sdk.SubmitSession).mockImplementationOnce(() => submitGate as never); @@ -1817,12 +1827,12 @@ describe('repair projection ownership', () => { basicMocks(connection); const prov1 = providerFixture({ connected: false, key_source: 'external' }); const prov2 = providerFixture({ id: 'prov2', name: 'Prov Two' }); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [prov1, prov2] })); + vi.mocked(sdk.ListProviders).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [prov1, prov2] })); const { promise: readGate, resolve: releaseRead } = Promise.withResolvers(); vi.mocked(sdk.GetProviderDetail).mockImplementation(async (params: string) => { const id = (JSON.parse(params) as { query: { provider_id: string } }).query.provider_id; if (id === 'prov2') return readGate as never; - return ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov1 }); + return json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov1 }); }); const { promise: mutationGate, resolve: releaseMutation } = Promise.withResolvers(); vi.mocked(sdk.ConnectProvider).mockImplementationOnce(() => mutationGate as never); @@ -1846,11 +1856,11 @@ describe('repair projection ownership', () => { await repair(connection); expect(target.querySelector('[aria-label="Close provider editor"]')).toBeNull(); // The stale read parked across the unmount publishes nothing. - releaseRead(okRaw(JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov2 }))); + releaseRead(native(200, JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov2 }), false)); await settle(); expect(target.querySelector('.note.outcome')).toBeNull(); // The addressed pending mutation still reports through the App note. - releaseMutation(httpError(401, { code: 'unauthorized', message: 'key refused' })); + releaseMutation(json(401, { code: 'unauthorized', message: 'key refused' })); await settle(); await settle(); expect(target.querySelector('.note.outcome')?.textContent).toContain('Provider connect failed for prov1'); @@ -1862,7 +1872,7 @@ describe('workspace navigation', () => { it('lists generated workspaces and re-composes the initial selection on an explicit change', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([ + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [ { root: '/ws', display_name: 'ws' }, { root: '/ws2', display_name: 'ws2' }, ])); @@ -1882,7 +1892,7 @@ describe('workspace navigation', () => { it('picks a directory natively and ignores a cancelled dialog', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([{ root: '/ws', display_name: 'ws' }])); + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [{ root: '/ws', display_name: 'ws' }])); vi.mocked(PickDirectory).mockResolvedValueOnce('').mockResolvedValueOnce('/ws3'); const { target } = await mountApp(connection); const listsBefore = vi.mocked(sdk.ListSessions).mock.calls.length; @@ -1901,7 +1911,7 @@ describe('workspace navigation', () => { it('ignores a superseded workspace list/create completion and keeps the newest choice', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([ + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [ { root: '/ws2', display_name: 'ws2' }, { root: '/ws3', display_name: 'ws3' }, ])); @@ -1909,7 +1919,7 @@ describe('workspace navigation', () => { vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const workspace = (JSON.parse(options) as { query: { workspace: string } }).query.workspace; if (workspace === '/ws2') return staleGate as never; - return ok([rootSession('root3', NEWER)]); + return json(200, [session('root3', NEWER)]); }); const { target } = await mountApp(connection); (target.querySelector('[aria-label="Select workspace"]') as HTMLElement).click(); @@ -1921,7 +1931,7 @@ describe('workspace navigation', () => { await settle(); (target.querySelector('[aria-label="Select workspace /ws3"]') as HTMLElement).click(); await settle(); - releaseStale(ok([rootSession('stale-root', NEWER)])); + releaseStale(json(200, [session('stale-root', NEWER)])); await settle(); await settle(); expect(target.textContent).toContain('root3'); @@ -1934,7 +1944,7 @@ describe('workspace navigation', () => { it('rejects a directory chosen after another Workspace selection, before composition or title', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([ + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [ { root: '/ws', display_name: 'ws' }, { root: '/ws2', display_name: 'ws2' }, ])); @@ -1962,7 +1972,7 @@ describe('workspace navigation', () => { it('rejects a superseded directory failure before any current note change', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([{ root: '/ws', display_name: 'ws' }])); + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [{ root: '/ws', display_name: 'ws' }])); const { promise: pickGate, reject: rejectPick } = Promise.withResolvers(); vi.mocked(PickDirectory).mockImplementationOnce(() => pickGate as never); const { target } = await mountApp(connection); @@ -1986,7 +1996,7 @@ describe('workspace navigation', () => { it('reports a current directory pick failure and keeps the current Workspace', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([{ root: '/ws', display_name: 'ws' }])); + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [{ root: '/ws', display_name: 'ws' }])); vi.mocked(PickDirectory).mockRejectedValueOnce(new Error('dialog failed')); const { target } = await mountApp(connection); (target.querySelector('[aria-label="Select workspace"]') as HTMLElement).click(); @@ -2009,8 +2019,8 @@ describe('session picker tasks', () => { it('archives then deletes an open root and clears only its local selection', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root1', NEWER))); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root1', NEWER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); const { target } = await mountApp(connection); await openPicker(target); (target.querySelector('[aria-label="Delete session root1"]') as HTMLElement).click(); @@ -2029,9 +2039,9 @@ describe('session picker tasks', () => { it.each([ ['reports a lost Delete as outcome unknown and never as success', () => Promise.reject(new Error('delete lost'))], - ['reports an invalid-delivery Delete as outcome unknown and never as success', () => Promise.resolve(receivedInvalid('delete body undecodable'))], + ['reports an invalid-delivery Delete as outcome unknown and never as success', () => Promise.resolve(native(200, 'delete body undecodable', true))], ])('%s', async (_title, deleteOutcome) => { - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root2', OLDER))); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root2', OLDER))); vi.mocked(sdk.DeleteSession).mockImplementation(deleteOutcome); const { target } = await mountBasic(); await openPicker(target); @@ -2062,12 +2072,12 @@ describe('session picker tasks', () => { it('resumes an archived root through Reopen before hydrating it', async () => { const connection = fakeConnection(); basicMocks(connection); - const archived = { ...rootSession('root9', OLDER), lifecycle: 'archived' as const }; + const archived = { ...session('root9', OLDER), lifecycle: 'archived' as const }; vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; - return lifecycle === 'open' ? ok([rootSession('root1', NEWER)]) : ok([archived]); + return lifecycle === 'open' ? json(200, [session('root1', NEWER)]) : json(200, [archived]); }); - vi.mocked(sdk.ReopenSession).mockResolvedValue(ok({ ...archived, lifecycle: 'open' as const })); + vi.mocked(sdk.ReopenSession).mockResolvedValue(json(200, { ...archived, lifecycle: 'open' as const })); const { target } = await mountApp(connection); await openPicker(target); (target.querySelector('[aria-label="Archived sessions"]') as HTMLElement).click(); @@ -2086,14 +2096,14 @@ describe('session picker tasks', () => { let openCalls = 0; vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; - if (lifecycle !== 'open') return ok([]); + if (lifecycle !== 'open') return json(200, []); openCalls += 1; - if (openCalls <= 2) return ok([rootSession('root1', NEWER), rootSession('root2', OLDER)]); + if (openCalls <= 2) return json(200, [session('root1', NEWER), session('root2', OLDER)]); if (openCalls === 3) return firstDeleteList as never; - return ok([rootSession('root1', NEWER)]); + return json(200, [session('root1', NEWER)]); }); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root2', OLDER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root2', OLDER))); const { target } = await mountApp(connection); await openPicker(target); // Delete the NONselected root2; its refresh is held. @@ -2104,7 +2114,7 @@ describe('session picker tasks', () => { await settle(); await settle(); // The held older reply still carries root2; it must be ignored. - releaseFirstDeleteList(ok([rootSession('root1', NEWER), rootSession('root2', OLDER)])); + releaseFirstDeleteList(json(200, [session('root1', NEWER), session('root2', OLDER)])); await settle(); await settle(); const rows = [...target.querySelectorAll('dialog .row')]; @@ -2120,14 +2130,14 @@ describe('session picker tasks', () => { let archived = false; vi.mocked(sdk.ArchiveSession).mockImplementation(async () => { archived = true; - return ok(rootSession('root1', NEWER)); + return json(200, session('root1', NEWER)); }); vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; if (lifecycle === 'open') { - return ok(archived ? [rootSession('root2', OLDER)] : [rootSession('root1', NEWER), rootSession('root2', OLDER)]); + return json(200, archived ? [session('root2', OLDER)] : [session('root1', NEWER), session('root2', OLDER)]); } - return ok([]); + return json(200, []); }); const { promise: deleteGate, resolve: releaseDelete } = Promise.withResolvers(); vi.mocked(sdk.DeleteSession).mockImplementationOnce(() => deleteGate as never); @@ -2144,7 +2154,7 @@ describe('session picker tasks', () => { const rows = [...target.querySelectorAll('dialog .row')].map((row) => row.textContent ?? ''); expect(rows.some((text) => text.includes('root1'))).toBe(false); expect(rows.some((text) => text.includes('root2'))).toBe(true); - releaseDelete(noContent()); + releaseDelete(native(204, '', false)); await settle(); await settle(); expect(target.textContent).toContain('no session'); @@ -2160,14 +2170,14 @@ describe('session picker tasks', () => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; if (lifecycle === 'archived') { if (openListCalls === 3) return stageGate as never; - return ok([]); + return json(200, []); } openListCalls += 1; if (openListCalls === 3) return stageGate as never; - return ok(openListCalls >= 4 ? [rootSession('root2', OLDER)] : [rootSession('root1', NEWER), rootSession('root2', OLDER)]); + return json(200, openListCalls >= 4 ? [session('root2', OLDER)] : [session('root1', NEWER), session('root2', OLDER)]); }); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root1', NEWER))); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root1', NEWER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); const { target } = await mountApp(connection); await openPicker(target); (target.querySelector('[aria-label="Delete session root1"]') as HTMLElement).click(); @@ -2177,7 +2187,7 @@ describe('session picker tasks', () => { // still parked, and the selected-root clear was immediate. expect(vi.mocked(sdk.DeleteSession)).toHaveBeenCalledWith('{"session_id":"root1"}'); expect(target.textContent).toContain('no session'); - releaseStage(ok([rootSession('root1', NEWER), rootSession('root2', OLDER)])); + releaseStage(json(200, [session('root1', NEWER), session('root2', OLDER)])); await settle(); await settle(); // The superseded archive-stage read cannot resurrect the removed row; the @@ -2192,15 +2202,15 @@ describe('session picker tasks', () => { vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; return lifecycle === 'open' - ? ok([rootSession('root1', NEWER), rootSession('root2', OLDER)]) - : ok([{ ...rootSession('arch8', OLDER), lifecycle: 'archived' as const }]); + ? json(200, [session('root1', NEWER), session('root2', OLDER)]) + : json(200, [{ ...session('arch8', OLDER), lifecycle: 'archived' as const }]); }); vi.mocked(sdk.ArchiveSession).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; - return ok(rootSession(id, OLDER)); + return json(200, session(id, OLDER)); }); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); - vi.mocked(sdk.ReopenSession).mockResolvedValue(ok(rootSession('arch8', OLDER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); + vi.mocked(sdk.ReopenSession).mockResolvedValue(json(200, session('arch8', OLDER))); const { target } = await mountApp(connection); await openPicker(target); const hydratedIDs = () => vi.mocked(sdk.GetSessionHydration).mock.calls.map( @@ -2257,7 +2267,7 @@ describe('session picker tasks', () => { it('creates an explicit new session from the picker', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.CreateSession).mockResolvedValue(ok(rootSession('root-new', NEWER))); + vi.mocked(sdk.CreateSession).mockResolvedValue(json(200, session('root-new', NEWER))); const { target } = await mountApp(connection); await openPicker(target); (target.querySelector('[aria-label="New session"]') as HTMLElement).click(); @@ -2269,7 +2279,7 @@ describe('session picker tasks', () => { }); describe('resume selection intent', () => { function archivedSession(id: string) { - return { ...rootSession(id, OLDER), lifecycle: 'archived' as const }; + return { ...session(id, OLDER), lifecycle: 'archived' as const }; } function archivedConnection() { @@ -2278,10 +2288,10 @@ describe('resume selection intent', () => { function archivedSession(id: string vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; return lifecycle === 'open' - ? ok([rootSession('root1', NEWER), rootSession('root2', OLDER)]) - : ok([archivedSession('arch8'), archivedSession('arch9')]); + ? json(200, [session('root1', NEWER), session('root2', OLDER)]) + : json(200, [archivedSession('arch8'), archivedSession('arch9')]); }); - vi.mocked(sdk.ReopenSession).mockResolvedValue(ok(rootSession('arch9', OLDER))); + vi.mocked(sdk.ReopenSession).mockResolvedValue(json(200, session('arch9', OLDER))); return connection; } @@ -2314,7 +2324,7 @@ describe('resume selection intent', () => { function archivedSession(id: string const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.ReopenSession) .mockImplementationOnce(() => first as never) - .mockResolvedValueOnce(ok(rootSession('arch9', OLDER))); + .mockResolvedValueOnce(json(200, session('arch9', OLDER))); const { target } = await mountApp(connection); await openArchived(target); (target.querySelector('[aria-label="Resume session arch8"]') as HTMLElement).click(); @@ -2323,7 +2333,7 @@ describe('resume selection intent', () => { function archivedSession(id: string await settle(); await settle(); expect(hydratedIDs()).toContain('arch9'); - releaseFirst(ok(rootSession('arch8', OLDER))); + releaseFirst(json(200, session('arch8', OLDER))); await settle(); await settle(); // The stale success reports no adoption and hydrates no unrelated root. @@ -2345,12 +2355,12 @@ describe('resume selection intent', () => { function archivedSession(id: string (target.querySelector('[aria-label="Resume session arch9"]') as HTMLElement).click(); await settle(); // A settles first: its success is stale and never adopted or hydrated. - releaseFirst(ok(rootSession('arch8', OLDER))); + releaseFirst(json(200, session('arch8', OLDER))); await settle(); await settle(); expect(hydratedIDs()).not.toContain('arch8'); // B's own success still adopts arch9. - releaseSecond(ok(rootSession('arch9', OLDER))); + releaseSecond(json(200, session('arch9', OLDER))); await settle(); await settle(); expect(hydratedIDs()).toContain('arch9'); @@ -2361,7 +2371,7 @@ describe('resume selection intent', () => { function archivedSession(id: string const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.ReopenSession) .mockImplementationOnce(() => first as never) - .mockResolvedValueOnce(httpError(404, { code: 'not_found', message: 'no longer archived' })); + .mockResolvedValueOnce(json(404, { code: 'not_found', message: 'no longer archived' })); const { target } = await mountApp(connection); await openArchived(target); (target.querySelector('[aria-label="Resume session arch8"]') as HTMLElement).click(); @@ -2370,7 +2380,7 @@ describe('resume selection intent', () => { function archivedSession(id: string await settle(); await settle(); expect(target.textContent).toContain('Resume failed for session arch9'); - releaseFirst(ok(rootSession('arch8', OLDER))); + releaseFirst(json(200, session('arch8', OLDER))); await settle(); await settle(); // The prior root is preserved; the stale A success never lands. @@ -2388,7 +2398,7 @@ describe('resume selection intent', () => { function archivedSession(id: string await settle(); await settle(); expect(target.textContent).toContain('Resume outcome unknown for session arch9'); - releaseSecond(ok(rootSession('arch8', OLDER))); + releaseSecond(json(200, session('arch8', OLDER))); await settle(); await settle(); expect(hydratedIDs()).not.toContain('arch8'); @@ -2396,11 +2406,11 @@ describe('resume selection intent', () => { function archivedSession(id: string }); it('later root, New and Workspace selection supersede a parked resume', async () => { const connection = archivedConnection(); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([ + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [ { root: '/ws', display_name: 'ws' }, { root: '/ws2', display_name: 'ws2' }, ])); - vi.mocked(sdk.CreateSession).mockResolvedValue(ok(rootSession('root-new', NEWER))); + vi.mocked(sdk.CreateSession).mockResolvedValue(json(200, session('root-new', NEWER))); const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.ReopenSession).mockImplementationOnce(() => first as never); const { target } = await mountApp(connection); @@ -2412,7 +2422,7 @@ describe('resume selection intent', () => { function archivedSession(id: string await settle(); (target.querySelector('[aria-label="Select session root2"]') as HTMLElement).click(); await settle(); - releaseFirst(ok(rootSession('arch8', OLDER))); + releaseFirst(json(200, session('arch8', OLDER))); await settle(); await settle(); expect(hydratedIDs()).not.toContain('arch8'); @@ -2428,7 +2438,7 @@ describe('resume selection intent', () => { function archivedSession(id: string (target.querySelector('[aria-label="Select workspace /ws2"]') as HTMLElement).click(); await settle(); await settle(); - releaseSecond(ok(rootSession('arch8', OLDER))); + releaseSecond(json(200, session('arch8', OLDER))); await settle(); await settle(); expect(hydratedIDs()).not.toContain('arch8'); @@ -2444,7 +2454,7 @@ describe('resume selection intent', () => { function archivedSession(id: string (target.querySelector('[aria-label="New session"]') as HTMLElement).click(); await settle(); await settle(); - releaseThird(ok(rootSession('arch8', OLDER))); + releaseThird(json(200, session('arch8', OLDER))); await settle(); await settle(); expect(hydratedIDs()).not.toContain('arch8'); @@ -2457,23 +2467,23 @@ describe('resume selection intent', () => { function archivedSession(id: string vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; return lifecycle === 'open' - ? ok([rootSession('root1', NEWER)]) - : ok([{ ...rootSession('arch8', OLDER), lifecycle: 'archived' as const }]); + ? json(200, [session('root1', NEWER)]) + : json(200, [{ ...session('arch8', OLDER), lifecycle: 'archived' as const }]); }); - vi.mocked(sdk.ReopenSession).mockResolvedValue(httpError(404, { code: 'not_found', message: 'no longer archived' })); - const initial = makeHydration({ + vi.mocked(sdk.ReopenSession).mockResolvedValue(json(404, { code: 'not_found', message: 'no longer archived' })); + const initial = hydration({ conversation: { items: [inputItem('i-old', 'user', 'op-old')] }, operations: [runningOperation('op-old')], }); - const fresh = makeHydration({ + const fresh = hydration({ conversation: { items: [inputItem('i-old', 'user', 'op-old'), inputItem('i-new', 'user', 'op-new')] }, operations: [runningOperation('op-old')], }); const { promise: parked, resolve: releaseParked } = Promise.withResolvers(); vi.mocked(sdk.GetSessionHydration) - .mockResolvedValueOnce(ok(initial)) + .mockResolvedValueOnce(json(200, initial)) .mockImplementationOnce(() => parked as never) - .mockResolvedValue(ok(fresh)); + .mockResolvedValue(json(200, fresh)); const { target } = await mountApp(connection); await vi.waitFor(() => expect(target.textContent).toContain('i-old')); // A revision hint starts the refresh that the failed Resume supersedes. @@ -2494,7 +2504,7 @@ describe('resume selection intent', () => { function archivedSession(id: string expect(target.textContent).toContain('Resume failed for session arch8'); // The superseded parked response settles: the retained root re-reads with // the fresh stamp WITHOUT another hint and keeps its loaded history. - releaseParked(ok(initial)); + releaseParked(json(200, initial)); await settle(); await settle(); expect(vi.mocked(sdk.GetSessionHydration)).toHaveBeenCalledTimes(3); @@ -2509,8 +2519,8 @@ describe('resume selection intent', () => { function archivedSession(id: string vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; return lifecycle === 'open' - ? ok([rootSession('root1', NEWER), rootSession('root2', OLDER)]) - : ok([{ ...rootSession('arch8', OLDER), lifecycle: 'archived' as const }]); + ? json(200, [session('root1', NEWER), session('root2', OLDER)]) + : json(200, [{ ...session('arch8', OLDER), lifecycle: 'archived' as const }]); }); const { promise: resumeGate, resolve: releaseResume } = Promise.withResolvers(); vi.mocked(sdk.ReopenSession).mockImplementationOnce(() => resumeGate as never); @@ -2527,7 +2537,7 @@ describe('resume selection intent', () => { function archivedSession(id: string const root2Reads = vi.mocked(sdk.GetSessionHydration).mock.calls.filter( (call) => (JSON.parse(call[0] as string) as { session_id: string }).session_id === 'root2', ).length; - releaseResume(httpError(404, { code: 'not_found', message: 'no longer archived' })); + releaseResume(json(404, { code: 'not_found', message: 'no longer archived' })); await settle(); await settle(); // The addressed failure is still reported, but the newer root is neither @@ -2545,7 +2555,7 @@ describe('agent type ownership', () => { it('offers every returned definition including compact and dispatches the actual name', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ + vi.mocked(sdk.GetConfiguration).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [ @@ -2555,7 +2565,7 @@ describe('agent type ownership', () => { ], providers: [], })); - vi.mocked(sdk.SetSessionAgentType).mockResolvedValue(ok(rootSession('root1', NEWER))); + vi.mocked(sdk.SetSessionAgentType).mockResolvedValue(json(200, session('root1', NEWER))); const { target } = await mountApp(connection); const openAgentPicker = async () => { await openSettings(target); @@ -2578,7 +2588,7 @@ describe('agent type ownership', () => { await settle(); expect(vi.mocked(sdk.SetSessionAgentType).mock.calls.at(-1)?.[0]).toEqual('{"session_id":"root1","body":{"agent_type":"reviewer"}}'); // The server stays the accepting owner: a generated rejection is displayed. - vi.mocked(sdk.SetSessionAgentType).mockResolvedValue(httpError(400, { code: 'invalid', message: 'unknown agent type' })); + vi.mocked(sdk.SetSessionAgentType).mockResolvedValue(json(400, { code: 'invalid', message: 'unknown agent type' })); await openAgentPicker(); (target.querySelector('[aria-label="Set session agent type compact"]') as HTMLElement).click(); await settle(); @@ -2637,7 +2647,7 @@ describe('settings editor', () => { expect((target.querySelector('[aria-label="archive_after_days"]') as HTMLInputElement).disabled).toBe(true); expect((target.querySelector('[aria-label="jobs max_background_processes"]') as HTMLInputElement).disabled).toBe(true); expect((target.querySelector('[aria-label="Save settings"]') as HTMLButtonElement).disabled).toBe(true); - releaseSave(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: {} })); + releaseSave(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: {} })); await settle(); await settle(); const call = vi.mocked(sdk.UpdateConfigurationSettings).mock.calls[0][0] as string; @@ -2654,7 +2664,7 @@ describe('settings editor', () => { it('displays a known settings save rejection and retains the editor draft', async () => { const connection = settingsRawConnection(); vi.mocked(sdk.UpdateConfigurationSettings).mockResolvedValueOnce( - httpError(400, { code: 'invalid', message: 'sessions interval rejected' }), + json(400, { code: 'invalid', message: 'sessions interval rejected' }), ); const { target } = await mountApp(connection); await openSettings(target); @@ -2715,7 +2725,7 @@ describe('host-local appearance and version', () => { // The boot global read resolves; the editor's own exact read stays parked. vi.mocked(sdk.GetConfiguration).mockImplementation(async () => { reads += 1; - return reads === 1 ? okRaw(RAW_CONFIGURATION) : (gate as never); + return reads === 1 ? native(200, RAW_CONFIGURATION, false) : (gate as never); }); const { target } = await mountApp(connection); (target.querySelector('[aria-label="Settings"]') as HTMLElement).click(); @@ -2726,7 +2736,7 @@ describe('host-local appearance and version', () => { expect((target.querySelector('[aria-label="Version"]') as HTMLElement).textContent).toBe('dev (test)'); await toggleCheckbox(target, 'Wrap code blocks', true); expect(JSON.parse(localStorage.getItem('lightcode.settings') as string).wrapCode).toBe(true); - release(okRaw(RAW_CONFIGURATION)); + release(native(200, RAW_CONFIGURATION, false)); await settle(); await settle(); expect(target.textContent).not.toContain('Loading…'); @@ -2738,7 +2748,7 @@ describe('host-local appearance and version', () => { let reads = 0; vi.mocked(sdk.GetConfiguration).mockImplementation(async () => { reads += 1; - if (reads === 1) return okRaw(RAW_CONFIGURATION); + if (reads === 1) return native(200, RAW_CONFIGURATION, false); throw new Error('runtime unavailable'); }); const { target } = await mountApp(connection); @@ -2770,15 +2780,10 @@ describe('host-local appearance and version', () => { describe('provider and model tasks', () => { it('sends only the edited field and refreshes through its own stamped read', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.UpdateProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.UpdateProviderDetail).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); await changeInput(target, 'provider name', 'Renamed'); @@ -2792,15 +2797,10 @@ describe('provider and model tasks', () => { }); it('malformed authored member JSON is a known local rejection with its input retained and no dispatch', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.UpdateProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.UpdateProviderDetail).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); await changeInput(target, 'provider extra_body', '{not json'); @@ -2815,14 +2815,9 @@ describe('provider and model tasks', () => { }); it('renders no retired provider controls beside the live members', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); + const connection = editorConnection([providerFixture()]); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); // The retired controls have no row and no reset affordance... @@ -2837,15 +2832,10 @@ describe('provider and model tasks', () => { }); it('resets one provider field through the generated reset path', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.ResetProviderField).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.ResetProviderField).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); (target.querySelector('[aria-label="Reset provider name"]') as HTMLElement).click(); @@ -2855,15 +2845,10 @@ describe('provider and model tasks', () => { }); it('resets one model field through the generated model reset path', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.ResetProviderModelField).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.ResetProviderModelField).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); // The one reset rule selects the model operation and adds the model_id. @@ -2874,15 +2859,10 @@ describe('provider and model tasks', () => { }); it('resets model input_modalities through the shared model reset rule', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.ResetProviderModelField).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.ResetProviderModelField).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); (target.querySelector('[aria-label="Reset m1 input_modalities"]') as HTMLElement).click(); @@ -2895,15 +2875,10 @@ describe('provider and model tasks', () => { }); it('resets model cost through the shared model reset rule', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.ResetProviderModelField).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.ResetProviderModelField).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); (target.querySelector('[aria-label="Reset m1 cost"]') as HTMLElement).click(); @@ -2915,15 +2890,10 @@ describe('provider and model tasks', () => { }); it('forwards an authored null and a raw JSON member verbatim', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.UpdateProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.UpdateProviderDetail).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); const textarea = target.querySelector('[aria-label="provider extra_body"]') as HTMLTextAreaElement; @@ -2936,16 +2906,11 @@ describe('provider and model tasks', () => { }); it('keeps Reload explicit: no automatic reload after a published model mutation', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); - vi.mocked(sdk.ReloadConfiguration).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '3' } })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); + vi.mocked(sdk.ReloadConfiguration).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '3' } })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); await toggleCheckbox(target, 'm1 hidden', true); @@ -2962,16 +2927,11 @@ describe('provider and model tasks', () => { }); it('reports an external credential refusal from connect', async () => { - const connection = fakeConnection(); - basicMocks(connection); const disconnected = providerFixture({ connected: false, key_source: 'external' }); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [disconnected] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: disconnected })); - vi.mocked(sdk.ConnectProvider).mockResolvedValue(httpError(401, { code: 'unauthorized', message: 'external key rejected' })); + const connection = editorConnection([disconnected]); + vi.mocked(sdk.ConnectProvider).mockResolvedValue(json(401, { code: 'unauthorized', message: 'external key rejected' })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); (target.querySelector('[aria-label="Connect provider"]') as HTMLElement).click(); @@ -2988,19 +2948,13 @@ describe('provider and model tasks', () => { }); it('discovers candidates and selects them into the authored create body', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [] })); - vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(ok([ + const connection = editorConnection([]); + vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(json(200, [ { id: 'cand-1', name: 'Candidate One', context_window: 4096, max_output_tokens: 512, usable: true }, ])); - vi.mocked(sdk.CreateProvider).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); + vi.mocked(sdk.CreateProvider).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); - await settle(); + await openNewProvider(target); await changeInput(target, 'Provider id', 'prov2'); await changeInput(target, 'Provider name', 'Prov Two'); await changeInput(target, 'Provider base url', 'http://127.0.0.1:2/v1'); @@ -3029,21 +2983,15 @@ describe('provider and model tasks', () => { }); it('treats prototype-named candidates as ordinary data and never inherits selection', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [] })); - vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(ok([ + const connection = editorConnection([]); + vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(json(200, [ { id: '__proto__', name: 'Proto Model', context_window: 4096, max_output_tokens: 512, usable: true }, { id: 'toString', name: 'String Model', context_window: 2048, max_output_tokens: 256, usable: true }, { id: 'constructor', name: 'Ctor Model', context_window: 1024, max_output_tokens: 128, usable: false }, ])); - vi.mocked(sdk.CreateProvider).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); + vi.mocked(sdk.CreateProvider).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); - await settle(); + await openNewProvider(target); await changeInput(target, 'Provider id', 'protos'); await changeInput(target, 'Provider base url', 'http://127.0.0.1:9/v1'); (target.querySelector('[aria-label="Discover provider candidates"]') as HTMLElement).click(); @@ -3065,16 +3013,10 @@ describe('provider and model tasks', () => { }); it('omits a blank api_key_env from the create body so Runtime can allocate with a supplied key', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [] })); - vi.mocked(sdk.CreateProvider).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); + const connection = editorConnection([]); + vi.mocked(sdk.CreateProvider).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); - await settle(); + await openNewProvider(target); await changeInput(target, 'Provider id', 'prov2'); await changeInput(target, 'Provider name', 'Authored Name'); await changeInput(target, 'Provider base url', 'http://127.0.0.1:2/v1'); @@ -3096,15 +3038,10 @@ describe('provider and model tasks', () => { }); it('refreshes the provider list for every successful metadata mutation, including a model edit', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })); - vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); + const connection = editorConnection([providerFixture()]); + vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); + await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); await settle(); const listReadsBefore = vi.mocked(sdk.ListProviders).mock.calls.length; @@ -3118,16 +3055,10 @@ describe('provider and model tasks', () => { }); it('sends an explicit api_key_env unchanged and a keyless create carries no api_key', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [] })); - vi.mocked(sdk.CreateProvider).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); + const connection = editorConnection([]); + vi.mocked(sdk.CreateProvider).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); - await settle(); + await openNewProvider(target); await changeInput(target, 'Provider id', 'prov2'); await changeInput(target, 'Provider base url', 'http://127.0.0.1:2/v1'); await changeInput(target, 'Provider api key env', 'AUTHORED_KEY'); @@ -3153,12 +3084,12 @@ describe('provider exact reads', () => { async function exactEditor(rawDetail: string) { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ + vi.mocked(sdk.ListProviders).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture()], })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(okRaw(rawDetail)); - vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); + vi.mocked(sdk.GetProviderDetail).mockResolvedValue(native(200, rawDetail, false)); + vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); const { target } = await mountApp(connection); await openSettings(target); (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); @@ -3185,9 +3116,9 @@ describe('provider exact reads', () => { it('prefills candidate number fields with their exact tokens and writes them unchanged', async () => { const target = await exactEditor(RAW_DETAIL); - vi.mocked(sdk.DiscoverProviderModelCandidates).mockResolvedValue(okRaw( + vi.mocked(sdk.DiscoverProviderModelCandidates).mockResolvedValue(native(200, '[{"id":"cand-big","name":"Big","context_window":9007199254740993,"max_output_tokens":1e400,"usable":true}]', - )); + false)); (target.querySelector('[aria-label="Add model"]') as HTMLElement).click(); await settle(); await settle(); @@ -3222,29 +3153,6 @@ describe('provider discovery supersession and busy controls', () => { } }); - function editorConnection(providers: unknown[]) { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ - configuration_revision: { instance_id: INSTANCE, generation: '1' }, - providers: providers as never, - })); - vi.mocked(sdk.GetProviderDetail).mockImplementation(async (params: string) => { - const id = (JSON.parse(params) as { query: { provider_id: string } }).query.provider_id; - return ok({ - configuration_revision: { instance_id: INSTANCE, generation: '1' }, - provider: (providers as Array<{ id: string }>).find((provider) => provider.id === id) as never, - }); - }); - return connection; - } - - async function openProviderEditor(target: HTMLElement) { - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - } - function candidate(id: string) { return { id, name: id, context_window: 8, max_output_tokens: 1, usable: true }; } @@ -3254,7 +3162,7 @@ describe('provider discovery supersession and busy controls', () => { const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.DiscoverProviderModelCandidates) .mockImplementationOnce(() => first as never) - .mockResolvedValueOnce(ok([candidate('cand-new')])); + .mockResolvedValueOnce(json(200, [candidate('cand-new')])); const { target } = await mountApp(connection); await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); @@ -3265,7 +3173,7 @@ describe('provider discovery supersession and busy controls', () => { await settle(); await settle(); expect(target.querySelector('[aria-label="Select candidate cand-new"]')).not.toBeNull(); - releaseFirst(okRaw(JSON.stringify([candidate('cand-old')]))); + releaseFirst(native(200, JSON.stringify([candidate('cand-old')]), false)); await settle(); await settle(); expect(target.querySelector('[aria-label="Select candidate cand-new"]')).not.toBeNull(); @@ -3290,12 +3198,12 @@ describe('provider discovery supersession and busy controls', () => { await settle(); (target.querySelector('[aria-label="Add model"]') as HTMLElement).click(); await settle(); - releaseFirst(okRaw(JSON.stringify([candidate('cand-a')]))); + releaseFirst(native(200, JSON.stringify([candidate('cand-a')]), false)); await settle(); await settle(); // The stale success belongs to prov1 and must not populate prov2's panel. expect(target.querySelector('[aria-label="Select candidate cand-a"]')).toBeNull(); - releaseSecond(okRaw(JSON.stringify([candidate('cand-b')]))); + releaseSecond(native(200, JSON.stringify([candidate('cand-b')]), false)); await settle(); await settle(); expect(target.querySelector('[aria-label="Select candidate cand-b"]')).not.toBeNull(); @@ -3314,7 +3222,7 @@ describe('provider discovery supersession and busy controls', () => { await settle(); (target.querySelector('[aria-label="Select provider prov2"]') as HTMLElement).click(); await settle(); - releaseFirst(httpError(404, { code: 'not_found', message: 'provider gone' })); + releaseFirst(json(404, { code: 'not_found', message: 'provider gone' })); await settle(); await settle(); expect(target.textContent).not.toContain('Model discovery failed'); @@ -3325,7 +3233,7 @@ describe('provider discovery supersession and busy controls', () => { const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.DiscoverProviderModelCandidates) .mockImplementationOnce(() => first as never) - .mockResolvedValueOnce(ok([candidate('cand-new')])); + .mockResolvedValueOnce(json(200, [candidate('cand-new')])); const { target } = await mountApp(connection); await openProviderEditor(target); (target.querySelector('[aria-label="Select provider prov1"]') as HTMLElement).click(); @@ -3340,7 +3248,7 @@ describe('provider discovery supersession and busy controls', () => { await settle(); await settle(); expect(target.querySelector('[aria-label="Select candidate cand-new"]')).not.toBeNull(); - releaseFirst(okRaw(JSON.stringify([candidate('cand-old')]))); + releaseFirst(native(200, JSON.stringify([candidate('cand-old')]), false)); await settle(); await settle(); expect(target.querySelector('[aria-label="Select candidate cand-new"]')).not.toBeNull(); @@ -3349,13 +3257,9 @@ describe('provider discovery supersession and busy controls', () => { it('discovers provider candidates from a base URL without an environment name', async () => { const connection = editorConnection([]); - vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(ok([])); + vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(json(200, [])); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); - await settle(); + await openNewProvider(target); await changeInput(target, 'Provider base url', 'http://127.0.0.1:9/v1'); const discoverButton = target.querySelector('[aria-label="Discover provider candidates"]') as HTMLButtonElement; expect(discoverButton.disabled).toBe(false); @@ -3382,7 +3286,7 @@ describe('provider discovery supersession and busy controls', () => { const { promise: refreshGate, resolve: releaseRefresh } = Promise.withResolvers(); vi.mocked(sdk.UpdateProviderDetail).mockImplementationOnce(() => writeGate as never); vi.mocked(sdk.GetProviderDetail) - .mockResolvedValueOnce(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })) + .mockResolvedValueOnce(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })) .mockImplementationOnce(() => refreshGate as never); const { target } = await mountApp(connection); await openProviderEditor(target); @@ -3397,13 +3301,13 @@ describe('provider discovery supersession and busy controls', () => { expect(control('m1 name').disabled).toBe(true); expect(control('m1 hidden').disabled).toBe(true); expect(control('m1 cost input').disabled).toBe(true); - releaseWrite(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); + releaseWrite(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: providerFixture() })); await settle(); await settle(); // Still busy: the required follow-up detail read is parked. expect(control('provider name').disabled).toBe(true); expect(control('m1 hidden').disabled).toBe(true); - releaseRefresh(okRaw(JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture({ name: 'Renamed' }) }))); + releaseRefresh(native(200, JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture({ name: 'Renamed' }) }), false)); await settle(); await settle(); expect(control('provider name').disabled).toBe(false); @@ -3417,7 +3321,7 @@ describe('provider discovery supersession and busy controls', () => { const { promise: listGate, resolve: releaseList } = Promise.withResolvers(); vi.mocked(sdk.ConnectProvider).mockImplementationOnce(() => connectGate as never); vi.mocked(sdk.ListProviders) - .mockImplementationOnce(async () => ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [disconnected] })) + .mockImplementationOnce(async () => json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [disconnected] })) .mockImplementationOnce(() => listGate as never); const { target } = await mountApp(connection); await openProviderEditor(target); @@ -3432,12 +3336,12 @@ describe('provider discovery supersession and busy controls', () => { await settle(); expect(key.disabled).toBe(true); expect((target.querySelector('[aria-label="Submit connect"]') as HTMLButtonElement).disabled).toBe(true); - releaseConnect(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: disconnected })); + releaseConnect(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: disconnected })); await settle(); await settle(); // The mutation settled, but its provider-list refresh is still parked. expect((target.querySelector('[aria-label="Connect provider"]') as HTMLButtonElement).disabled).toBe(true); - releaseList(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, providers: [disconnected] })); + releaseList(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, providers: [disconnected] })); await settle(); await settle(); expect((target.querySelector('[aria-label="Connect provider"]') as HTMLButtonElement).disabled).toBe(false); @@ -3445,15 +3349,11 @@ describe('provider discovery supersession and busy controls', () => { it('keeps the create form and candidate selection disabled while the create is parked', async () => { const connection = editorConnection([]); - vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(ok([candidate('cand-1')])); + vi.mocked(sdk.DiscoverProviderCandidates).mockResolvedValue(json(200, [candidate('cand-1')])); const { promise: createGate, resolve: releaseCreate } = Promise.withResolvers(); vi.mocked(sdk.CreateProvider).mockImplementationOnce(() => createGate as never); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); - await settle(); - (target.querySelector('[aria-label="New provider"]') as HTMLElement).click(); - await settle(); + await openNewProvider(target); await changeInput(target, 'Provider id', 'prov2'); await changeInput(target, 'Provider base url', 'http://127.0.0.1:2/v1'); await changeInput(target, 'Provider api key env', 'PROV2_KEY'); @@ -3468,19 +3368,19 @@ describe('provider discovery supersession and busy controls', () => { expect((target.querySelector('[aria-label="Provider base url"]') as HTMLInputElement).disabled).toBe(true); expect(checkbox.disabled).toBe(true); expect((target.querySelector('[aria-label="Discover provider candidates"]') as HTMLButtonElement).disabled).toBe(true); - releaseCreate(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); + releaseCreate(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: null })); await settle(); await settle(); }); it('keeps the upsert form and candidate selection disabled while the upsert is parked', async () => { const connection = editorConnection([providerFixture()]); - vi.mocked(sdk.DiscoverProviderModelCandidates).mockResolvedValue(ok([candidate('cand-9')])); + vi.mocked(sdk.DiscoverProviderModelCandidates).mockResolvedValue(json(200, [candidate('cand-9')])); const { promise: upsertGate, resolve: releaseUpsert } = Promise.withResolvers(); const { promise: detailGate, resolve: releaseDetail } = Promise.withResolvers(); vi.mocked(sdk.UpdateProviderModel).mockImplementationOnce(() => upsertGate as never); vi.mocked(sdk.GetProviderDetail) - .mockResolvedValueOnce(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })) + .mockResolvedValueOnce(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture() })) .mockImplementationOnce(() => detailGate as never); const { target } = await mountApp(connection); await openProviderEditor(target); @@ -3496,12 +3396,12 @@ describe('provider discovery supersession and busy controls', () => { expect((target.querySelector('[aria-label="Model id"]') as HTMLInputElement).disabled).toBe(true); expect((target.querySelector('[aria-label="Model name"]') as HTMLInputElement).disabled).toBe(true); expect((target.querySelector('[aria-label="Select candidate cand-9"]') as HTMLButtonElement).disabled).toBe(true); - releaseUpsert(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); + releaseUpsert(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: modelFixture() })); await settle(); await settle(); // The upsert settled, but its required follow-up detail read is parked. expect((target.querySelector('[aria-label="Add model"]') as HTMLButtonElement).disabled).toBe(true); - releaseDetail(okRaw(JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, provider: providerFixture() }))); + releaseDetail(native(200, JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, provider: providerFixture() }), false)); await settle(); await settle(); expect((target.querySelector('[aria-label="Add model"]') as HTMLButtonElement).disabled).toBe(false); @@ -3509,31 +3409,61 @@ describe('provider discovery supersession and busy controls', () => { }); describe('file viewer', () => { - it('reads an explicit workspace/path and displays canonical path and truncation', async () => { + it.each([ + { + title: 'reads an explicit workspace/path and displays canonical path and truncation', + path: 'src/a.ts', + reply: json(200, { content: 'file body', canonical_path: '/ws/src/a.ts', truncated: true }), + verify: (target: HTMLElement) => { + expect(vi.mocked(sdk.ReadWorkspaceFile)).toHaveBeenCalledWith('{"body":{"workspace":"/ws","path":"src/a.ts"}}'); + expect(target.textContent).toContain('/ws/src/a.ts'); + expect(target.textContent).toContain('truncated'); + expect(target.textContent).toContain('file body'); + }, + }, + { + title: 'shows a denied read as an addressed error', + path: '../etc/passwd', + reply: json(404, { code: 'not_found', message: 'path is outside the workspace' }), + verify: (target: HTMLElement) => { + expect(target.textContent).toContain('path is outside the workspace'); + }, + }, + ])('$title', async ({ path, reply, verify }) => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ReadWorkspaceFile).mockResolvedValue(ok({ content: 'file body', canonical_path: '/ws/src/a.ts', truncated: true })); + vi.mocked(sdk.ReadWorkspaceFile).mockResolvedValue(reply); const { target } = await mountApp(connection); await openSettings(target); (target.querySelector('[aria-label="File viewer"]') as HTMLElement).click(); await settle(); - await changeInput(target, 'File path', 'src/a.ts'); + await changeInput(target, 'File path', path); (target.querySelector('[aria-label="Open file"]') as HTMLElement).click(); await settle(); await settle(); - expect(vi.mocked(sdk.ReadWorkspaceFile)).toHaveBeenCalledWith('{"body":{"workspace":"/ws","path":"src/a.ts"}}'); - expect(target.textContent).toContain('/ws/src/a.ts'); - expect(target.textContent).toContain('truncated'); - expect(target.textContent).toContain('file body'); + verify(target); }); - it('ignores a superseded viewer reply and keeps the newest read', async () => { + it.each([ + { + title: 'ignores a superseded viewer reply and keeps the newest read', + newest: json(200, { content: 'second body', canonical_path: '/ws/b.ts', truncated: false }), + stale: json(200, { content: 'stale body', canonical_path: '/ws/a.ts', truncated: false }), + absent: 'stale body', + }, + { + title: 'ignores a superseded viewer error and keeps the newest read', + newest: json(200, { content: 'fresh body', canonical_path: '/ws/b.ts', truncated: false }), + stale: json(404, { code: 'not_found', message: 'stale denial' }), + absent: 'stale denial', + }, + ])('$title', async ({ newest, stale, absent }) => { const connection = fakeConnection(); basicMocks(connection); const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); vi.mocked(sdk.ReadWorkspaceFile) .mockImplementationOnce(() => first as never) - .mockResolvedValueOnce(ok({ content: 'second body', canonical_path: '/ws/b.ts', truncated: false })); + .mockResolvedValueOnce(newest); const { target } = await mountApp(connection); await openSettings(target); (target.querySelector('[aria-label="File viewer"]') as HTMLElement).click(); @@ -3545,26 +3475,12 @@ describe('file viewer', () => { (target.querySelector('[aria-label="Open file"]') as HTMLElement).click(); await settle(); await settle(); - releaseFirst(ok({ content: 'stale body', canonical_path: '/ws/a.ts', truncated: false })); + // The stale first reply is released only after the newer reply published. + releaseFirst(stale); await settle(); await settle(); expect(target.textContent).toContain('/ws/b.ts'); - expect(target.textContent).not.toContain('stale body'); - }); - - it('shows a denied read as an addressed error', async () => { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.ReadWorkspaceFile).mockResolvedValue(httpError(404, { code: 'not_found', message: 'path is outside the workspace' })); - const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="File viewer"]') as HTMLElement).click(); - await settle(); - await changeInput(target, 'File path', '../etc/passwd'); - (target.querySelector('[aria-label="Open file"]') as HTMLElement).click(); - await settle(); - await settle(); - expect(target.textContent).toContain('path is outside the workspace'); + expect(target.textContent).not.toContain(absent); }); }); @@ -3572,17 +3488,16 @@ describe('code snapshots', () => { it('reverts current code to a chosen Operation boundary and shows a partial failure', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(ok([ + vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(json(200, [ { operation_id: 'op1', group_item_id: 'group1', files: [{ path: 'a.ts', existed: true }] }, ])); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(httpError(409, { + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(409, { restored: ['a.ts'], skipped: [{ path: 'b.ts', reason: 'io' }], error: { code: 'storage', message: 'partial failure' }, })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); await settle(); expect(target.textContent).toContain('Operation op1'); @@ -3598,16 +3513,14 @@ describe('code snapshots', () => { it('reads retained turns as exact text and sends after_turn as an exact token', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(okRaw( + vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(native(200, '{"turns":[{"turn":9007199254740993,"files":[{"path":"legacy.ts","existed":true}]}]}', - )); + false)); vi.mocked(sdk.RevertRetainedCode).mockRejectedValue(new Error('retained rewind lost')); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); - await changeInput(target, 'Retained session id', 'abcd1234'); - (target.querySelector('[aria-label="Read retained snapshots"]') as HTMLElement).click(); + await readRetainedSnapshots(target, 'abcd1234'); await settle(); await settle(); expect(target.textContent).toContain('after turn 9007199254740993'); @@ -3622,16 +3535,14 @@ describe('code snapshots', () => { it('offers the initial retained boundary 0 for a loaded target with only later turns', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(okRaw( + vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(native(200, '{"turns":[{"turn":1,"files":[{"path":"legacy.ts","existed":true}]}]}', - )); + false)); vi.mocked(sdk.RevertRetainedCode).mockRejectedValue(new Error('retained rewind lost')); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); - await changeInput(target, 'Retained session id', 'abcd1234'); - (target.querySelector('[aria-label="Read retained snapshots"]') as HTMLElement).click(); + await readRetainedSnapshots(target, 'abcd1234'); await settle(); await settle(); // The existing per-turn boundary row keeps its exact lexeme. @@ -3652,17 +3563,16 @@ describe('code snapshots', () => { it('reports a current rewind outcome addressed to its loaded Session', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(ok([ + vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(json(200, [ { operation_id: 'op1', group_item_id: 'group1', files: [{ path: 'a.ts', existed: true }] }, ])); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(httpError(409, { + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(409, { restored: ['a.ts'], skipped: [], error: { code: 'storage', message: 'partial failure' }, })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); await settle(); (target.querySelector('[aria-label="Revert code to before operation op1"]') as HTMLElement).click(); @@ -3677,16 +3587,14 @@ describe('code snapshots', () => { it('reports a retained rewind outcome addressed to its loaded Workspace and ID', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(okRaw( + vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(native(200, '{"turns":[{"turn":1,"files":[{"path":"legacy.ts","existed":true}]}]}', - )); - vi.mocked(sdk.RevertRetainedCode).mockResolvedValue(ok({ restored: ['legacy.ts'], skipped: [] })); + false)); + vi.mocked(sdk.RevertRetainedCode).mockResolvedValue(json(200, { restored: ['legacy.ts'], skipped: [] })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); - await changeInput(target, 'Retained session id', 'abcd1234'); - (target.querySelector('[aria-label="Read retained snapshots"]') as HTMLElement).click(); + await readRetainedSnapshots(target, 'abcd1234'); await settle(); await settle(); (target.querySelector('[aria-label="Revert to initial retained boundary"]') as HTMLElement).click(); @@ -3701,14 +3609,13 @@ describe('code snapshots', () => { it('reports a pending rewind after a repair unmounts the dialog, without retransmitting', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(ok([ + vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(json(200, [ { operation_id: 'op1', group_item_id: 'group1', files: [{ path: 'a.ts', existed: true }] }, ])); const { promise: revertGate, reject: releaseRevert } = Promise.withResolvers(); vi.mocked(sdk.RevertSessionCode).mockImplementationOnce(() => revertGate as never); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); await settle(); (target.querySelector('[aria-label="Revert code to before operation op1"]') as HTMLElement).click(); @@ -3734,7 +3641,7 @@ describe('usage details and global warnings', () => { it('lists exact per-model usage rows without inventing zero rows', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ usage: { totals: { by_model: [ @@ -3756,15 +3663,15 @@ describe('usage details and global warnings', () => { it('falls back to GetWarnings globals when no hydration is displayed', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetWarnings).mockResolvedValue(ok({ + vi.mocked(sdk.GetWarnings).mockResolvedValue(json(200, { warnings_revision: { instance_id: INSTANCE, revision: '1' }, warnings: [ { source: 'g', kind: 'k', message: 'global warning' }, { source: 's', kind: 'k', message: 'session warning', session_id: 'root1' }, ], })); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root1', NEWER))); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root1', NEWER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); const { target } = await mountApp(connection); // Remove the selected root: no hydration remains, so the global-only // indicator filters absent session_id from the GetWarnings snapshot. @@ -3785,24 +3692,6 @@ describe('usage details and global warnings', () => { }); describe('replacement Fork and ordered rewind', () => { - function forkConnection(overrides: Partial = {}) { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ - conversation: { - items: [ - inputItem('i-owned', 'user', 'op-owned'), - inputItem('i-copied', 'user'), - inputItem('i-runtime', 'runtime', 'op-runtime'), - inputItem('i-plugin', 'plugin', 'op-plugin'), - ], - }, - operations: [runningOperation('op-owned'), runningOperation('op-runtime'), runningOperation('op-plugin')], - ...overrides, - }))); - return connection; - } - it('exposes Fork only on root user-origin inputs, with rewind only for an owned input', async () => { const connection = forkConnection(); const { target } = await mountApp(connection); @@ -3826,9 +3715,9 @@ describe('replacement Fork and ordered rewind', () => { vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; if (id === 'child1') { - return ok(makeHydration({ conversation: { items: [{ ...inputItem('child-owned', 'user', 'op-child'), content: [{ kind: 'text' as const, text: 'hi' }, { kind: 'opaque' as const, opaque_wire_type: 'w', extra: { big: JSON.rawJSON('9007199254740993') } }] }] } })); + return json(200, hydration({ conversation: { items: [{ ...inputItem('child-owned', 'user', 'op-child'), content: [{ kind: 'text' as const, text: 'hi' }, { kind: 'opaque' as const, opaque_wire_type: 'w', extra: { big: JSON.rawJSON('9007199254740993') } }] }] } })); } - return ok(makeHydration({ background: [{ kind: 'child', id: 'child1' }] })); + return json(200, hydration({ background: [{ kind: 'child', id: 'child1' }] })); }); const { target } = await mountApp(connection); (target.querySelector('[aria-label="Background sessions"]') as HTMLElement).click(); @@ -3850,11 +3739,11 @@ describe('replacement Fork and ordered rewind', () => { { kind: 'image_url' as const, url: 'http://img/x.png' }, { kind: 'opaque' as const, opaque_wire_type: 'w', extra: { a: 1 } }, ]; - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ conversation: { items: [{ ...inputItem('i-owned', 'user', 'op-owned'), content: multipart }] }, operations: [runningOperation('op-owned')], }))); - vi.mocked(sdk.ForkSession).mockResolvedValue(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('caller') })); + vi.mocked(sdk.ForkSession).mockResolvedValue(json(200, { session: session('dest1', NEWER), operation: runningOperation('caller') })); const { target } = await mountApp(connection); (target.querySelector('[aria-label="Fork from input i-owned"]') as HTMLElement).click(); await settle(); @@ -3874,7 +3763,7 @@ describe('replacement Fork and ordered rewind', () => { it('malformed authored replacement content is a known local rejection with its draft retained and no dispatch', async () => { const connection = forkConnection(); - vi.mocked(sdk.ForkSession).mockResolvedValue(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('caller') })); + vi.mocked(sdk.ForkSession).mockResolvedValue(json(200, { session: session('dest1', NEWER), operation: runningOperation('caller') })); const { target } = await mountApp(connection); (target.querySelector('[aria-label="Fork from input i-owned"]') as HTMLElement).click(); await settle(); @@ -3914,13 +3803,13 @@ describe('replacement Fork and ordered rewind', () => { const order: string[] = []; vi.mocked(sdk.RevertSessionCode).mockImplementation(async () => { order.push('rewind'); - return ok({ restored: ['a.ts'], skipped: [{ path: 'b.ts', reason: 'unchanged' }] }); + return json(200, { restored: ['a.ts'], skipped: [{ path: 'b.ts', reason: 'unchanged' }] }); }); vi.mocked(sdk.ForkSession).mockImplementation(async (options) => { order.push('fork'); const request = options as string; const body = JSON.parse(bodyOf(request)) as { operation_id: string }; - return ok({ session: rootSession('dest1', NEWER), operation: runningOperation(body.operation_id) }); + return json(200, { session: session('dest1', NEWER), operation: runningOperation(body.operation_id) }); }); const { target } = await mountApp(connection); await openOwnedFork(target, true); @@ -3942,7 +3831,7 @@ describe('replacement Fork and ordered rewind', () => { it('shows a known partial restore failure before still issuing Fork', async () => { const connection = forkConnection(); const { promise: forkGate, resolve: releaseFork } = Promise.withResolvers(); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(httpError(409, { + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(409, { restored: ['a.ts'], skipped: [{ path: 'b.ts', reason: 'io' }], error: { code: 'storage', message: 'partial failure' }, @@ -3957,7 +3846,7 @@ describe('replacement Fork and ordered rewind', () => { expect(target.textContent).toContain('Skipped: b.ts (io)'); expect(target.textContent).toContain('Restore failed: partial failure'); expect(vi.mocked(sdk.ForkSession)).toHaveBeenCalledTimes(1); - releaseFork(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('x') })); + releaseFork(json(200, { session: session('dest1', NEWER), operation: runningOperation('x') })); await settle(); await settle(); // The fast successful Fork closes the dialog but the earlier effects stay @@ -3972,7 +3861,7 @@ describe('replacement Fork and ordered rewind', () => { it('publishes a handled restore success with the addressed Fork outcome', async () => { const connection = forkConnection(); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(ok({ restored: ['a.ts'], skipped: [] })); + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(200, { restored: ['a.ts'], skipped: [] })); const { target } = await mountApp(connection); await openOwnedFork(target, true); await settle(); @@ -3986,7 +3875,7 @@ describe('replacement Fork and ordered rewind', () => { it('a late non-adopted destination still reports the earlier restore effects', async () => { const connection = forkConnection(); const { promise: forkGate, resolve: releaseFork } = Promise.withResolvers(); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(httpError(409, { + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(409, { restored: ['a.ts'], skipped: [], error: { code: 'storage', message: 'partial failure' }, @@ -4001,7 +3890,7 @@ describe('replacement Fork and ordered rewind', () => { await settle(); (target.querySelectorAll('dialog .row')[1] as HTMLElement).click(); await settle(); - releaseFork(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('x') })); + releaseFork(json(200, { session: session('dest1', NEWER), operation: runningOperation('x') })); await settle(); await settle(); const outcome = target.querySelector('.note.outcome')?.textContent ?? ''; @@ -4031,13 +3920,13 @@ describe('replacement Fork and ordered rewind', () => { it('retries an unknown Fork with the same identity and never reruns the handled restore', async () => { const connection = forkConnection(); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(ok({ restored: [], skipped: [] })); + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(200, { restored: [], skipped: [] })); vi.mocked(sdk.ForkSession) .mockRejectedValueOnce(new Error('fork lost')) .mockImplementationOnce(async (options) => { const request = options as string; const body = JSON.parse(bodyOf(request)) as { operation_id: string }; - return ok({ session: rootSession('dest1', NEWER), operation: runningOperation(body.operation_id) }); + return json(200, { session: session('dest1', NEWER), operation: runningOperation(body.operation_id) }); }); const { target } = await mountApp(connection); await openOwnedFork(target, true); @@ -4061,7 +3950,7 @@ describe('replacement Fork and ordered rewind', () => { it('a held Retry of an unknown Fork transitions to pending and permits no second retry while its response is held', async () => { const connection = forkConnection(); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(ok({ restored: [], skipped: [] })); + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(200, { restored: [], skipped: [] })); const { promise: firstGate, resolve: releaseFork } = Promise.withResolvers(); const { promise: retryGate, resolve: releaseRetry } = Promise.withResolvers(); vi.mocked(sdk.ForkSession) @@ -4093,7 +3982,7 @@ describe('replacement Fork and ordered rewind', () => { expect(bodyOf(vi.mocked(sdk.ForkSession).mock.calls[1][0] as never)).toBe(firstBody); expect(vi.mocked(sdk.RevertSessionCode)).toHaveBeenCalledTimes(1); expect(target.textContent).toContain('Restored: (none)'); - releaseRetry(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('x') })); + releaseRetry(json(200, { session: session('dest1', NEWER), operation: runningOperation('x') })); await settle(); await settle(); await settle(); @@ -4116,7 +4005,7 @@ describe('replacement Fork and ordered rewind', () => { await openOwnedFork(target, false); await settle(); const firstBody = bodyOf(vi.mocked(sdk.ForkSession).mock.calls[0][0] as never); - releaseFork(httpError(409, { code: 'conflict', message: 'fork rejected' })); + releaseFork(json(409, { code: 'conflict', message: 'fork rejected' })); await settle(); await settle(); expect(target.textContent).toContain('fork rejected'); @@ -4134,7 +4023,7 @@ describe('replacement Fork and ordered rewind', () => { textarea.value = '[{"kind":"text","text":"newer draft"}]'; textarea.dispatchEvent(new Event('input', { bubbles: true })); await settle(); - releaseRetry(httpError(409, { code: 'conflict', message: 'fork rejected again' })); + releaseRetry(json(409, { code: 'conflict', message: 'fork rejected again' })); await settle(); await settle(); // The retry's own final outcome is reported, the dialog stays open with @@ -4161,7 +4050,7 @@ describe('replacement Fork and ordered rewind', () => { await settle(); (target.querySelectorAll('dialog .row')[1] as HTMLElement).click(); await settle(); - releaseFork(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('x') })); + releaseFork(json(200, { session: session('dest1', NEWER), operation: runningOperation('x') })); await settle(); await settle(); expect(target.textContent).toContain('selection moved'); @@ -4176,7 +4065,7 @@ describe('replacement Fork and ordered rewind', () => { it('a rejected Fork keeps a newer replacement draft while the dialog stays open', async () => { const connection = forkConnection(); - vi.mocked(sdk.ForkSession).mockResolvedValue(httpError(409, { code: 'conflict', message: 'fork rejected' })); + vi.mocked(sdk.ForkSession).mockResolvedValue(json(409, { code: 'conflict', message: 'fork rejected' })); const { target } = await mountApp(connection); await openOwnedFork(target, false); await settle(); @@ -4195,20 +4084,9 @@ describe('replacement Fork and ordered rewind', () => { }); describe('restore outcomes through the fork path', () => { - function forkConnection() { - const connection = fakeConnection(); - basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ - conversation: { items: [inputItem('i-owned', 'user', 'op-owned')] }, - operations: [runningOperation('op-owned')], - }))); - vi.mocked(sdk.ForkSession).mockResolvedValue(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('x') })); - return connection; - } - it('warns on a plain pre-traversal restore rejection and still issues Fork', async () => { const connection = forkConnection(); - vi.mocked(sdk.RevertSessionCode).mockResolvedValue(httpError(404, { code: 'not_found', message: 'no snapshot group' })); + vi.mocked(sdk.RevertSessionCode).mockResolvedValue(json(404, { code: 'not_found', message: 'no snapshot group' })); const { target } = await mountApp(connection); await openOwnedFork(target, true); await settle(); @@ -4218,8 +4096,8 @@ describe('restore outcomes through the fork path', () => { }); it.each([ - ['treats a malformed 2xx restore body as outcome unknown and never sends Fork', ok({})], - ['treats a 2xx restore body carrying an error member as outcome unknown and never sends Fork', ok({ + ['treats a malformed 2xx restore body as outcome unknown and never sends Fork', json(200, {})], + ['treats a 2xx restore body carrying an error member as outcome unknown and never sends Fork', json(200, { restored: ['a.ts'], skipped: [], error: { code: 'storage', message: 'partial failure' }, @@ -4236,39 +4114,13 @@ describe('restore outcomes through the fork path', () => { }); }); -describe('viewer supersession with an error', () => { - it('ignores a superseded viewer error and keeps the newest read', async () => { const connection = fakeConnection(); - basicMocks(connection); - const { promise: first, resolve: releaseFirst } = Promise.withResolvers(); - vi.mocked(sdk.ReadWorkspaceFile) - .mockImplementationOnce(() => first as never) - .mockResolvedValueOnce(ok({ content: 'fresh body', canonical_path: '/ws/b.ts', truncated: false })); - const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="File viewer"]') as HTMLElement).click(); - await settle(); - await changeInput(target, 'File path', 'a.ts'); - (target.querySelector('[aria-label="Open file"]') as HTMLElement).click(); - await settle(); - await changeInput(target, 'File path', 'b.ts'); - (target.querySelector('[aria-label="Open file"]') as HTMLElement).click(); - await settle(); - await settle(); - releaseFirst(httpError(404, { code: 'not_found', message: 'stale denial' })); - await settle(); - await settle(); - expect(target.textContent).toContain('/ws/b.ts'); - expect(target.textContent).not.toContain('stale denial'); - }); -}); - describe('editor read supersession', () => { it('ignores a superseded provider detail read after switching providers', async () => { const connection = fakeConnection(); basicMocks(connection); const prov1 = providerFixture(); const prov2 = providerFixture({ id: 'prov2', name: 'Prov Two' }); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ + vi.mocked(sdk.ListProviders).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [prov1, prov2], })); @@ -4276,7 +4128,7 @@ describe('editor read supersession', () => { vi.mocked(sdk.GetProviderDetail).mockImplementation(async (params: string) => { const id = (JSON.parse(params) as { query: { provider_id: string } }).query.provider_id; if (id === 'prov1') return first as never; - return ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov2 }); + return json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov2 }); }); const { target } = await mountApp(connection); await openSettings(target); @@ -4288,7 +4140,7 @@ describe('editor read supersession', () => { (target.querySelector('[aria-label="Select provider prov2"]') as HTMLElement).click(); await settle(); await settle(); - releaseFirst(okRaw(JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov1 }))); + releaseFirst(native(200, JSON.stringify({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: prov1 }), false)); await settle(); await settle(); expect((target.querySelector('[aria-label="provider name"]') as HTMLInputElement).value).toBe('Prov Two'); @@ -4321,7 +4173,7 @@ describe('read lifetime boundaries', () => { expect(target.textContent).toContain('Opening…'); // The selection moves; no newer viewer read starts. await selectRoot2ByLabel(target); - releaseRead(ok({ content: 'late body', canonical_path: '/ws/a.ts', truncated: false })); + releaseRead(json(200, { content: 'late body', canonical_path: '/ws/a.ts', truncated: false })); await settle(); await settle(); expect(target.textContent).not.toContain('late body'); @@ -4348,12 +4200,12 @@ describe('read lifetime boundaries', () => { await settle(); // The newer read owns the loading state. expect(target.textContent).toContain('Opening…'); - releaseFirst(ok({ content: 'stale body', canonical_path: '/ws/a.ts', truncated: false })); + releaseFirst(json(200, { content: 'stale body', canonical_path: '/ws/a.ts', truncated: false })); await settle(); await settle(); expect(target.textContent).not.toContain('stale body'); expect(target.textContent).toContain('Opening…'); - releaseSecond(ok({ content: 'fresh body', canonical_path: '/ws/b.ts', truncated: false })); + releaseSecond(json(200, { content: 'fresh body', canonical_path: '/ws/b.ts', truncated: false })); await settle(); await settle(); expect(target.textContent).toContain('fresh body'); @@ -4370,8 +4222,8 @@ describe('read lifetime boundaries', () => { // The boot global read is the first call; the editor's exact read is the // parked second call, and a later editor read serves the fresh document. if (configCalls === 2) return textRead as never; - if (configCalls > 2) return okRaw(RAW_CONFIGURATION); - return ok({ + if (configCalls > 2) return native(200, RAW_CONFIGURATION, false); + return json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [], providers: [], @@ -4383,7 +4235,7 @@ describe('read lifetime boundaries', () => { expect(target.textContent).toContain('Loading…'); // The selection move retires the editor; the stale body publishes nothing. await selectRoot2ByLabel(target); - releaseText(okRaw(RAW_CONFIGURATION)); + releaseText(native(200, RAW_CONFIGURATION, false)); await settle(); await settle(); expect(target.querySelector('[aria-label="Close settings"]')).toBeNull(); @@ -4416,12 +4268,12 @@ describe('read lifetime boundaries', () => { await settle(); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'm', ctrlKey: true })); await settle(); - releaseFirst(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [model('prov/a', 'Model A')] })); + releaseFirst(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [model('prov/a', 'Model A')] })); await settle(); await settle(); // The older read neither populated the reopened dialog nor cleared its loading. expect(target.textContent).toContain('Loading models...'); - releaseSecond(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [model('prov/b', 'Model B')] })); + releaseSecond(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, models: [model('prov/b', 'Model B')] })); await settle(); await settle(); expect(target.textContent).not.toContain('Loading models...'); @@ -4442,8 +4294,8 @@ function settingsRawConnection(raw = RAW_CONFIGURATION) { const connection = fakeConnection(); basicMocks(connection); // The one raw document serves the global read and the editor's exact read. - vi.mocked(sdk.GetConfiguration).mockImplementation(async () => okRaw(raw)); - vi.mocked(sdk.UpdateConfigurationSettings).mockResolvedValue(ok({ + vi.mocked(sdk.GetConfiguration).mockImplementation(async () => native(200, raw, false)); + vi.mocked(sdk.UpdateConfigurationSettings).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, })); @@ -4516,12 +4368,11 @@ describe('loaded snapshot identity', () => { it('a selection move retires the snapshots dialog and a reopen reads the new session', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(ok([ + vi.mocked(sdk.GetSessionCodeSnapshots).mockResolvedValue(json(200, [ { operation_id: 'op1', group_item_id: 'g1', files: [{ path: 'a.ts', existed: true }] }, ])); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); await settle(); expect(target.textContent).toContain('loaded root1'); @@ -4535,8 +4386,7 @@ describe('loaded snapshot identity', () => { expect(target.querySelector('[aria-label="Revert code to before operation op1"]')).toBeNull(); expect(vi.mocked(sdk.RevertSessionCode)).not.toHaveBeenCalled(); // A reopen reads the currently selected session through the plain task. - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); await settle(); expect(vi.mocked(sdk.GetSessionCodeSnapshots).mock.calls.at(-1)?.[0]).toEqual('{"session_id":"root2"}'); @@ -4545,16 +4395,14 @@ describe('loaded snapshot identity', () => { it('retained revert uses the loaded Workspace/ID and boundary after the draft ID changes', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(okRaw( + vi.mocked(sdk.GetRetainedCodeSnapshots).mockResolvedValue(native(200, '{"turns":[{"turn":9007199254740993,"files":[{"path":"legacy.ts","existed":true}]}]}', - )); - vi.mocked(sdk.RevertRetainedCode).mockResolvedValue(ok({ restored: ['legacy.ts'], skipped: [] })); + false)); + vi.mocked(sdk.RevertRetainedCode).mockResolvedValue(json(200, { restored: ['legacy.ts'], skipped: [] })); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); - await changeInput(target, 'Retained session id', 'abcd1234'); - (target.querySelector('[aria-label="Read retained snapshots"]') as HTMLElement).click(); + await readRetainedSnapshots(target, 'abcd1234'); await settle(); await settle(); expect(target.textContent).toContain('Loaded retained target: /ws · abcd1234'); @@ -4573,15 +4421,13 @@ describe('loaded snapshot identity', () => { const { promise: pending, resolve: releaseRead } = Promise.withResolvers(); vi.mocked(sdk.GetRetainedCodeSnapshots).mockImplementationOnce(() => pending as never); const { target } = await mountApp(connection); - await openSettings(target); - (target.querySelector('[aria-label="Snapshots"]') as HTMLElement).click(); + await openSnapshots(target); await settle(); - await changeInput(target, 'Retained session id', 'abcd1234'); - (target.querySelector('[aria-label="Read retained snapshots"]') as HTMLElement).click(); + await readRetainedSnapshots(target, 'abcd1234'); await settle(); // The draft target changes while the read is parked. await changeInput(target, 'Retained session id', 'ffffffff'); - releaseRead(okRaw('{"turns":[{"turn":1,"files":[]}]}')); + releaseRead(native(200, '{"turns":[{"turn":1,"files":[]}]}', false)); await settle(); await settle(); // The stale completion publishes nothing. @@ -4610,8 +4456,8 @@ describe('workspace and empty-selection repair', () => { it('a repair does not recompose an intentionally empty selection', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root1', NEWER))); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root1', NEWER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); const { target } = await mountApp(connection); window.dispatchEvent(new KeyboardEvent('keydown', { key: 's', ctrlKey: true })); await settle(); @@ -4640,8 +4486,8 @@ describe('session mutation commits', () => { it('an archive success clears the selected root even when Delete then fails', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root1', NEWER))); - vi.mocked(sdk.DeleteSession).mockResolvedValue(httpError(500, { code: 'internal', message: 'delete blew up' })); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root1', NEWER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(json(500, { code: 'internal', message: 'delete blew up' })); const { target } = await mountApp(connection); window.dispatchEvent(new KeyboardEvent('keydown', { key: 's', ctrlKey: true })); await settle(); @@ -4659,7 +4505,7 @@ describe('session mutation commits', () => { basicMocks(connection); const { promise: pending, resolve: releaseArchive } = Promise.withResolvers(); vi.mocked(sdk.ArchiveSession).mockImplementationOnce(() => pending as never); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); const { target } = await mountApp(connection); window.dispatchEvent(new KeyboardEvent('keydown', { key: 's', ctrlKey: true })); await settle(); @@ -4675,7 +4521,7 @@ describe('session mutation commits', () => { await settle(); (target.querySelectorAll('dialog .row')[1] as HTMLElement).click(); await settle(); - releaseArchive(ok(rootSession('root1', NEWER))); + releaseArchive(json(200, session('root1', NEWER))); await settle(); await settle(); expect(target.textContent).toContain('root2'); @@ -4687,35 +4533,25 @@ describe('session mutation commits', () => { describe('global snapshot adoption', () => { const OTHER_AGENT = { name: 'other-agent', description: '', subagent: false, model: '', readonly: false, write_dir: '', tools: [], capabilities: [] }; - it('never publishes a stale configuration payload with its older clock', async () => { - const connection = fakeConnection(); - basicMocks(connection); - const { target } = await mountApp(connection); - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ - configuration_revision: { instance_id: INSTANCE, generation: '0' }, - settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, - agents: [OTHER_AGENT], - providers: [], - })); - deliverEvent(connection, { - kind: 'configuration_changed', - configuration_revision: { instance_id: INSTANCE, generation: '1' }, - }); - await settle(); - await settle(); - await openSettings(target); - (target.querySelector('[aria-label="Agent type"]') as HTMLElement).click(); - await settle(); - expect(target.textContent).toContain('primary'); - expect(target.textContent).not.toContain('other-agent'); - }); - - it('adopts an equal-clock configuration payload', async () => { + it.each([ + { + title: 'never publishes a stale configuration payload with its older clock', + readGeneration: '0', + visible: 'primary', + absent: 'other-agent', + }, + { + title: 'adopts an equal-clock configuration payload', + readGeneration: '1', + visible: 'other-agent', + absent: null, + }, + ])('$title', async ({ readGeneration, visible, absent }) => { const connection = fakeConnection(); basicMocks(connection); const { target } = await mountApp(connection); - vi.mocked(sdk.GetConfiguration).mockResolvedValue(ok({ - configuration_revision: { instance_id: INSTANCE, generation: '1' }, + vi.mocked(sdk.GetConfiguration).mockResolvedValue(json(200, { + configuration_revision: { instance_id: INSTANCE, generation: readGeneration }, settings: { sessions: { auto_archive: false, archive_after_days: 1, delete_after_archive_days: 1 }, plugins: {} }, agents: [OTHER_AGENT], providers: [], @@ -4729,26 +4565,27 @@ describe('global snapshot adoption', () => { await openSettings(target); (target.querySelector('[aria-label="Agent type"]') as HTMLElement).click(); await settle(); - expect(target.textContent).toContain('other-agent'); + expect(target.textContent).toContain(visible); + if (absent !== null) expect(target.textContent).not.toContain(absent); }); it('never publishes a stale warnings payload with its older clock', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetWarnings).mockResolvedValue(ok({ + vi.mocked(sdk.GetWarnings).mockResolvedValue(json(200, { warnings_revision: { instance_id: INSTANCE, revision: '2' }, warnings: [{ source: 'g', kind: 'k', message: 'g-old' }], })); - vi.mocked(sdk.ArchiveSession).mockResolvedValue(ok(rootSession('root1', NEWER))); - vi.mocked(sdk.DeleteSession).mockResolvedValue(noContent()); + vi.mocked(sdk.ArchiveSession).mockResolvedValue(json(200, session('root1', NEWER))); + vi.mocked(sdk.DeleteSession).mockResolvedValue(native(204, '', false)); // The displayed hydration must clear the accepted warnings floor ('2'); // the fixture carries the matching revision so the reader is not held // dirty by an inconsistent mock. - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ warnings_revision: { instance_id: INSTANCE, revision: '2' }, }))); const { target } = await mountApp(connection); - vi.mocked(sdk.GetWarnings).mockResolvedValue(ok({ + vi.mocked(sdk.GetWarnings).mockResolvedValue(json(200, { warnings_revision: { instance_id: INSTANCE, revision: '1' }, warnings: [{ source: 'g', kind: 'k', message: 'g-stale' }], })); @@ -4777,9 +4614,9 @@ describe('cost whole-member replacement', () => { async function costEditor(model: Record) { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListProviders).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture({ models: [model] })] })); - vi.mocked(sdk.GetProviderDetail).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture({ models: [model] }) })); - vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(ok({ configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: model as never })); + vi.mocked(sdk.ListProviders).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, providers: [providerFixture({ models: [model] })] })); + vi.mocked(sdk.GetProviderDetail).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '1' }, provider: providerFixture({ models: [model] }) })); + vi.mocked(sdk.UpdateProviderModel).mockResolvedValue(json(200, { configuration_revision: { instance_id: INSTANCE, generation: '2' }, result: model as never })); const { target } = await mountApp(connection); await openSettings(target); (target.querySelector('[aria-label="Providers"]') as HTMLElement).click(); @@ -4821,7 +4658,7 @@ describe('selection-bound Fork source', () => { it('an in-flight Fork cannot pair its captured item with another live root', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.GetSessionHydration).mockResolvedValue(ok(makeHydration({ + vi.mocked(sdk.GetSessionHydration).mockResolvedValue(json(200, hydration({ conversation: { items: [inputItem('i-owned', 'user', 'op-owned')] }, operations: [runningOperation('op-owned')], }))); @@ -4840,7 +4677,7 @@ describe('selection-bound Fork source', () => { (target.querySelectorAll('dialog .row')[1] as HTMLElement).click(); await settle(); expect(target.querySelector('[aria-label="Close fork dialog"]')).toBeNull(); - releaseFork(ok({ session: rootSession('dest1', NEWER), operation: runningOperation('x') })); + releaseFork(json(200, { session: session('dest1', NEWER), operation: runningOperation('x') })); await settle(); await settle(); const call = JSON.parse(vi.mocked(sdk.ForkSession).mock.calls[0][0]) as { session_id: string }; @@ -4851,7 +4688,7 @@ describe('selection-bound Fork source', () => { it('a Workspace change retires the open Session picker and its rows', async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([ + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [ { root: '/ws', display_name: 'ws' }, { root: '/ws2', display_name: 'ws2' }, ])); @@ -4875,9 +4712,9 @@ describe('selection-bound Fork source', () => { describe('composition failure outcomes', () => { function compositionMocks(connection: ReturnType, failure: 'known' | 'uncertain') { basicMocks(connection); - vi.mocked(sdk.ListSessions).mockResolvedValue(ok([])); + vi.mocked(sdk.ListSessions).mockResolvedValue(json(200, [])); if (failure === 'known') { - vi.mocked(sdk.CreateSession).mockResolvedValueOnce(httpError(500, { code: 'internal', message: 'create rejected' })); + vi.mocked(sdk.CreateSession).mockResolvedValueOnce(json(500, { code: 'internal', message: 'create rejected' })); } else { vi.mocked(sdk.CreateSession).mockRejectedValueOnce(new Error('create lost')); } @@ -4900,10 +4737,10 @@ describe('composition failure outcomes', () => { } expect(target.textContent).toContain('no session'); // A fresh explicit New creates and adopts its destination. - vi.mocked(sdk.CreateSession).mockResolvedValue(ok(rootSession('created-1', NEWER))); + vi.mocked(sdk.CreateSession).mockResolvedValue(json(200, session('created-1', NEWER))); vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; - return ok(makeHydration({ session: rootSession(id, NEWER) })); + return json(200, hydration({ session: session(id, NEWER) })); }); window.dispatchEvent(new KeyboardEvent('keydown', { key: 's', ctrlKey: true })); await settle(); @@ -4921,8 +4758,8 @@ describe('composition failure outcomes', () => { it(`a stale ${initial} initial completion never adopts or publishes and the picker reports its captured address`, async () => { const connection = fakeConnection(); basicMocks(connection); - vi.mocked(sdk.ListSessions).mockResolvedValue(ok([])); - vi.mocked(sdk.ListWorkspaces).mockResolvedValue(ok([ + vi.mocked(sdk.ListSessions).mockResolvedValue(json(200, [])); + vi.mocked(sdk.ListWorkspaces).mockResolvedValue(json(200, [ { root: '/wsB', display_name: 'B' }, ])); const { promise: initialGate, resolve: releaseInitial } = Promise.withResolvers(); @@ -4930,10 +4767,10 @@ describe('composition failure outcomes', () => { vi.mocked(sdk.CreateSession) .mockImplementationOnce(() => initialGate as never) .mockImplementationOnce(() => pickerGate as never) - .mockResolvedValue(ok(rootSession('b-1', NEWER))); + .mockResolvedValue(json(200, session('b-1', NEWER))); vi.mocked(sdk.GetSessionHydration).mockImplementation(async (options) => { const id = (JSON.parse(options) as { session_id: string }).session_id; - return ok(makeHydration({ session: rootSession(id, NEWER) })); + return json(200, hydration({ session: session(id, NEWER) })); }); const { target } = await mountAppNoWait(connection); await vi.waitFor(() => expect(vi.mocked(sdk.CreateSession)).toHaveBeenCalledTimes(1)); @@ -4949,7 +4786,7 @@ describe('composition failure outcomes', () => { await settle(); (target.querySelector('[aria-label="Select workspace /wsB"]') as HTMLElement).click(); await settle(); - releaseInitial(initial === 'accepted' ? ok(rootSession('stale-1', NEWER)) : new Error('stale initial lost')); + releaseInitial(initial === 'accepted' ? json(200, session('stale-1', NEWER)) : new Error('stale initial lost')); await settle(); await settle(); // The old initial completion neither adopts nor publishes a note. @@ -4979,11 +4816,11 @@ describe('uniform archive→delete for both lifecycles', () => { basicMocks(connection); vi.mocked(sdk.ListSessions).mockImplementation(async (options) => { const lifecycle = (JSON.parse(options) as { query: { lifecycle: string } }).query.lifecycle; - if (lifecycle === 'open') return ok([rootSession('root1', NEWER)]); - return ok([{ ...rootSession('arch1', OLDER), lifecycle: 'archived' as const }]); + if (lifecycle === 'open') return json(200, [session('root1', NEWER)]); + return json(200, [{ ...session('arch1', OLDER), lifecycle: 'archived' as const }]); }); - vi.mocked(sdk.ArchiveSession).mockImplementation(async () => ok(rootSession('arch1', OLDER))); - vi.mocked(sdk.DeleteSession).mockImplementation(async () => noContent()); + vi.mocked(sdk.ArchiveSession).mockImplementation(async () => json(200, session('arch1', OLDER))); + vi.mocked(sdk.DeleteSession).mockImplementation(async () => native(204, '', false)); const { target } = await mountApp(connection); window.dispatchEvent(new KeyboardEvent('keydown', { key: 's', ctrlKey: true })); await settle(); diff --git a/frontend/src/client/attempts.test.ts b/frontend/src/client/attempts.test.ts index 872a62c6..24112b14 100644 --- a/frontend/src/client/attempts.test.ts +++ b/frontend/src/client/attempts.test.ts @@ -4,36 +4,9 @@ // ID without resurrection of removed attempts. import { describe, expect, it, vi } from 'vitest'; -import type { Hydration } from '../generated/protocol/types.gen'; import { AttemptLedger, mintOperationID } from './attempts'; import type { RestoreOutcome } from './restore'; - -const INSTANCE = '0123456789abcdef0123456789abcdef'; - -function makeHydration(overrides: Partial = {}): Hydration { - return { - session: { - session_id: 's1', - workspace: '/ws', - lifecycle: 'open', - agent_type: 'primary', - created_at: '0', - last_activity: '0', - session_revision: { instance_id: INSTANCE, durable_revision: '1', local_revision: '0' }, - }, - selected_model: null, - session_revision: { instance_id: INSTANCE, durable_revision: '1', local_revision: '0' }, - configuration_revision: { instance_id: INSTANCE, generation: '1' }, - warnings_revision: { instance_id: INSTANCE, revision: '1' }, - operations: [], - pending: { steering: [], queued: [] }, - conversation: { items: [] }, - usage: { totals: { by_model: [] }, context: { used_tokens: '0', context_window: 0 } }, - warnings: [], - background: [], - ...overrides, - }; -} +import { hydration, session } from './test-fixtures'; describe('mintOperationID', () => { it('mints exactly 16 crypto-random bytes in lowercase hex', () => { @@ -75,7 +48,8 @@ describe('AttemptLedger', () => { ledger.markUnknown(queued.id); ledger.markUnknown(steered.id); ledger.markUnknown(unseen.id); - const accepted = ledger.adoptHydration('s1', makeHydration({ + const accepted = ledger.adoptHydration('s1', hydration({ + session: session('s1', '0'), operations: [{ operation_id: admitted.id, request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'p/m', usage: { totals: { by_model: [] } } }], pending: { steering: [{ operation_id: steered.id, origin: 'user', content: [] }], @@ -96,7 +70,8 @@ describe('AttemptLedger', () => { const ledger = new AttemptLedger(); const other = ledger.create('submit', 'regular', [{ kind: 'text', text: 'x' }], 's2', 1); ledger.markUnknown(other.id); - const accepted = ledger.adoptHydration('s1', makeHydration({ + const accepted = ledger.adoptHydration('s1', hydration({ + session: session('s1', '0'), operations: [{ operation_id: other.id, request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'p/m', usage: { totals: { by_model: [] } } }], })); expect(accepted).toHaveLength(0); @@ -138,7 +113,8 @@ describe('AttemptLedger', () => { const start = seen.length; const attempt = ledger.create('submit', 'regular', [{ kind: 'text', text: 'x' }], 's1', 1); ledger.markUnknown(attempt.id); - ledger.adoptHydration('s1', makeHydration({ + ledger.adoptHydration('s1', hydration({ + session: session('s1', '0'), operations: [{ operation_id: attempt.id, request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'p/m', usage: { totals: { by_model: [] } } }], })); expect(ledger.getAttempts()).toHaveLength(0); @@ -197,17 +173,8 @@ describe('fork attempts', () => { const ledger = new AttemptLedger(); const attempt = fork(ledger); ledger.markUnknown(attempt.id); - const accepted = ledger.adoptHydration('dest1', makeHydration({ - session: { - session_id: 'dest1', - workspace: '/ws', - source_session_id: 's1', - lifecycle: 'open', - agent_type: 'primary', - created_at: '0', - last_activity: '0', - session_revision: { instance_id: INSTANCE, durable_revision: '1', local_revision: '0' }, - }, + const accepted = ledger.adoptHydration('dest1', hydration({ + session: { ...session('dest1', '0'), source_session_id: 's1' }, operations: [{ operation_id: attempt.id, request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'p/m', usage: { totals: { by_model: [] } } }], })); expect(accepted.map((entry) => entry.id)).toEqual([attempt.id]); @@ -219,21 +186,13 @@ describe('fork attempts', () => { const attempt = fork(ledger); ledger.markUnknown(attempt.id); // Another destination without the lineage does not accept. - const unrelated = ledger.adoptHydration('dest2', makeHydration({ - session: { - session_id: 'dest2', - workspace: '/ws', - lifecycle: 'open', - agent_type: 'primary', - created_at: '0', - last_activity: '0', - session_revision: { instance_id: INSTANCE, durable_revision: '1', local_revision: '0' }, - }, + const unrelated = ledger.adoptHydration('dest2', hydration({ + session: session('dest2', '0'), operations: [{ operation_id: attempt.id, request_kind: 'message', status: 'running', admitted_at: '0', agent_type: 'primary', model: 'p/m', usage: { totals: { by_model: [] } } }], })); expect(unrelated).toHaveLength(0); // The source Session's own hydration absence proves nothing. - const source = ledger.adoptHydration('s1', makeHydration()); + const source = ledger.adoptHydration('s1', hydration({ session: session('s1', '0') })); expect(source).toHaveLength(0); expect(ledger.get(attempt.id)?.status).toBe('unknown'); }); diff --git a/frontend/src/client/reader.test.ts b/frontend/src/client/reader.test.ts index 672e972b..62f8453f 100644 --- a/frontend/src/client/reader.test.ts +++ b/frontend/src/client/reader.test.ts @@ -21,12 +21,7 @@ import { unresolvedAssistantIDs, type ReaderOutcome, } from './reader'; - -const INSTANCE = '0123456789abcdef0123456789abcdef'; - -function revision(durable: string, local = '0', instance = INSTANCE): SessionRevision { - return { instance_id: instance, durable_revision: durable, local_revision: local }; -} +import { INSTANCE, hydration as hydrationFixture, revision } from './test-fixtures'; function inputItem(id: string): ConversationItem { return { @@ -60,8 +55,11 @@ function range(start: number, end: number): ConversationItem[] { return items; } +// The reader's own adapter: the supplied revision object, its argument-derived +// Session/configuration instance and optional older cursor stay authoritative; +// the shared builder supplies the remaining nominal defaults. function hydration(revisionValue: SessionRevision, items: ConversationItem[], olderCursor: string | null, extra: Partial = {}): Hydration { - return { + return hydrationFixture({ session: { session_id: 's1', workspace: '/ws', @@ -71,18 +69,12 @@ function hydration(revisionValue: SessionRevision, items: ConversationItem[], ol last_activity: '0', session_revision: revisionValue, }, - selected_model: null, session_revision: revisionValue, configuration_revision: { instance_id: revisionValue.instance_id, generation: '1' }, warnings_revision: { instance_id: revisionValue.instance_id, revision: '1' }, - operations: [], - pending: { steering: [], queued: [] }, conversation: olderCursor === null ? { items } : { items, older_cursor: olderCursor }, - usage: { totals: { by_model: [] }, context: { used_tokens: '0', context_window: 0 } }, - warnings: [], - background: [], ...extra, - }; + }); } function page(revisionValue: SessionRevision, items: ConversationItem[], olderCursor: string | null): HistoryPage { diff --git a/frontend/src/client/stack.test.ts b/frontend/src/client/stack.test.ts index d594aefb..e3772f90 100644 --- a/frontend/src/client/stack.test.ts +++ b/frontend/src/client/stack.test.ts @@ -23,53 +23,10 @@ import { RuntimeConnection } from './connection'; import { extractSettingsText } from './exactjson'; import { invokeNative } from './results'; import { assertNativeInvocations } from './nativecontract'; +import { INSTANCE, NEWER, OLDER, hydration, json, native, session } from './test-fixtures'; -const INSTANCE = '0123456789abcdef0123456789abcdef'; -const NEWER = '2026-10-02T10:00:00Z'; -const OLDER = '2026-10-01T05:00:00Z'; const mounted: Array> = []; -function session(id: string, lastActivity: string, parent?: string) { - return { - session_id: id, - workspace: '/ws', - ...(parent ? { parent_session_id: parent } : {}), - lifecycle: 'open', - agent_type: 'primary', - created_at: '0', - last_activity: lastActivity, - session_revision: { instance_id: INSTANCE, durable_revision: '1', local_revision: '0' }, - }; -} - -function hydration(sessionID: string, overrides: Record = {}) { - return { - session: session(sessionID, NEWER), - selected_model: null, - session_revision: { instance_id: INSTANCE, durable_revision: '1', local_revision: '0' }, - configuration_revision: { instance_id: INSTANCE, generation: '1' }, - warnings_revision: { instance_id: INSTANCE, revision: '1' }, - operations: [], - pending: { steering: [], queued: [] }, - conversation: { items: [] }, - usage: { totals: { by_model: [] }, context: { used_tokens: '0', context_window: 0 } }, - warnings: [], - background: [], - ...overrides, - }; -} - -function json(status: number, value: unknown): desktopclient.HTTPResponse { - return native(status, JSON.stringify(value), false); -} - -// native supplies one scripted local result exactly as the native owner -// returns it, including the explicit invalid-delivery bit; producer -// validation stays at the Go response owner. -function native(status: number, body: string, invalid: boolean): desktopclient.HTTPResponse { - return { status, body, invalid }; -} - // routeState is the scripted in-memory Runtime behind the byte bridge. interface Delayed { delay: Promise<{ status: number; body: unknown }>; @@ -117,7 +74,7 @@ function bridge(script: Script = {}) { const raw = script.hydration.shift()!; return native(raw.status, raw.body, raw.invalid); } - return json(200, hydration(envelope.session_id!)); + return json(200, hydration({ session: session(envelope.session_id!, NEWER) })); } if (operation === 'GetConfiguration') { const scriptedRead = script.configuration?.shift(); @@ -269,10 +226,6 @@ function configurationView(generation: string, agentName: string) { }; } -function rawJSONResponse(value: unknown) { - return { status: 200, body: JSON.stringify(value), invalid: false }; -} - function typeInto(target: HTMLElement, text: string) { const textarea = target.querySelector('textarea') as HTMLTextAreaElement; textarea.value = text; @@ -290,6 +243,16 @@ async function settle() { await tick(); } +// submitOnce types one mounted draft and presses Send through the real +// composer, leaving each case its own scripted outcome and predicates. +async function submitOnce(target: HTMLElement, text: string) { + typeInto(target, text); + await settle(); + (target.querySelector('.send') as HTMLElement).click(); + await settle(); + await settle(); +} + afterEach(async () => { // The mounted UI's recorded native invocations are asserted here, outside // every application promise and catch, before clearAllMocks removes them: a @@ -325,11 +288,7 @@ describe('real generated stack over the byte bridge', () => { it('a real native Call rejection is a transport loss, not a server rejection', async () => { const { target } = await mountReal({ submit: 'throw' }); - typeInto(target, 'lost message'); - await settle(); - (target.querySelector('.send') as HTMLElement).click(); - await settle(); - await settle(); + await submitOnce(target, 'lost message'); // The binding rejects; the consumer must show Outcome unknown, never a // generated protocol error. expect(target.querySelector('.attempt.unknown')?.textContent).toContain('Outcome unknown'); @@ -340,11 +299,7 @@ describe('real generated stack over the byte bridge', () => { const { target } = await mountReal({ submit: () => ({ status: 400, body: { code: 'invalid', message: 'content is not admitted' } }), }); - typeInto(target, 'bad message'); - await settle(); - (target.querySelector('.send') as HTMLElement).click(); - await settle(); - await settle(); + await submitOnce(target, 'bad message'); expect(target.querySelector('.attempt.rejected')?.textContent).toContain('content is not admitted'); expect((target.querySelector('textarea') as HTMLTextAreaElement).value).toBe('bad message'); expect(target.querySelector('.attempt.unknown')).toBeNull(); @@ -352,11 +307,7 @@ describe('real generated stack over the byte bridge', () => { it('a real 200 disposition clears the matching draft and retires the attempt', async () => { const { target, calls } = await mountReal(); - typeInto(target, 'delivered text'); - await settle(); - (target.querySelector('.send') as HTMLElement).click(); - await settle(); - await settle(); + await submitOnce(target, 'delivered text'); const submit = calls.find((call) => call.operation === 'SubmitSession'); expect(submit).toBeDefined(); const body = (JSON.parse(submit!.params) as { body: { operation_id: string; mode: string; content: unknown[] } }).body; @@ -476,7 +427,7 @@ describe('real generated stack over the byte bridge', () => { // survives the binding and the actual reader repairs through the fresh // Open, while the event pull stays live and no blank projection is ever // adopted. - const unusable = { status: 200, body: unusableBody, invalid: true }; + const unusable = native(200, unusableBody, true); const newerHint = JSON.stringify({ kind: 'session_changed', scope: { kind: 'session', session_id: 'root1' }, @@ -499,16 +450,16 @@ describe('real generated stack over the byte bridge', () => { let releaseRead: () => void = () => {}; const readGate = new Promise((resolve) => { releaseRead = resolve; }); const retired = variant === 'success' - ? rawJSONResponse(configurationView('9', 'stale')) + ? json(200, configurationView('9', 'stale')) : variant === 'loss' ? ('throw' as const) - : { status: 200, body: '{not json', invalid: true }; + : native(200, '{not json', true); const { target, calls, connection } = await mountReal({ openGate, configuration: [ - { wait: Promise.resolve(), outcome: rawJSONResponse(configurationView('1', 'boot')) }, + { wait: Promise.resolve(), outcome: json(200, configurationView('1', 'boot')) }, { wait: readGate, outcome: retired }, - { wait: Promise.resolve(), outcome: rawJSONResponse(configurationView('1', 'successor')) }, + { wait: Promise.resolve(), outcome: json(200, configurationView('1', 'successor')) }, ], events: [ JSON.stringify({ @@ -553,7 +504,7 @@ describe('real generated stack over the byte bridge', () => { // call→reader path must render every FIFO occurrence on the initial body // and again on a replacement body, without deduplicating or minting // attempt rows. - const firstBody = hydration('root1', { + const firstBody = hydration({ pending: { steering: [ { operation_id: 'dup', origin: 'user', content: [{ kind: 'text', text: 'steer first' }] }, @@ -565,7 +516,7 @@ describe('real generated stack over the byte bridge', () => { ], }, }); - const secondBody = hydration('root1', { + const secondBody = hydration({ session_revision: { instance_id: INSTANCE, durable_revision: '2', local_revision: '0' }, pending: { steering: [ @@ -663,7 +614,7 @@ describe('Step 8 real generated captures over the byte bridge', () => { it('captures the ordered rewind-then-Fork requests from the shared invocation', async () => { const { target, calls } = await mountReal({ - hydration: [json(200, hydration('root1', { conversation: { items: [forkInput] }, operations: [forkOperation] }))], + hydration: [json(200, hydration({ conversation: { items: [forkInput] }, operations: [forkOperation] }))], rewind: () => ({ status: 200, body: { restored: ['a.ts'], skipped: [] } }), }); await submitRewindFork(target); @@ -685,7 +636,7 @@ describe('Step 8 real generated captures over the byte bridge', () => { it('never sends Fork after a real transport-unknown rewind', async () => { const { target, calls } = await mountReal({ - hydration: [json(200, hydration('root1', { conversation: { items: [forkInput] }, operations: [forkOperation] }))], + hydration: [json(200, hydration({ conversation: { items: [forkInput] }, operations: [forkOperation] }))], rewind: 'throw', }); await submitRewindFork(target); @@ -801,7 +752,7 @@ describe('the shared invocation boundary', () => { }); it('treats a null success document as invalid delivery beside its non-null and 204 siblings', async () => { - vi.mocked(Call).mockResolvedValue({ status: 200, body: 'null', invalid: false }); + vi.mocked(Call).mockResolvedValue(native(200, 'null', false)); const nulled = await invokeNative(() => handle, 'submit', 'SubmitSession', '{"session_id":"root1","body":{}}', JSON.parse); expect(nulled.kind).toBe('invalid'); if (nulled.kind === 'ok') throw new Error('null document accepted as ok'); @@ -809,9 +760,26 @@ describe('the shared invocation boundary', () => { const accepted = await invokeNative(() => handle, 'submit', 'SubmitSession', '{"session_id":"root1","body":{}}', JSON.parse); expect(accepted.kind).toBe('ok'); if (accepted.kind === 'ok') expect(accepted.data).toEqual({ disposition: 'admitted' }); - vi.mocked(Call).mockResolvedValue({ status: 204, body: '', invalid: false }); + vi.mocked(Call).mockResolvedValue(native(204, '', false)); const empty = await invokeNative(() => handle, 'interrupt', 'InterruptSession', '{"session_id":"root1"}', JSON.parse); expect(empty.kind).toBe('ok'); if (empty.kind === 'ok') expect(empty.data).toEqual({}); }); + + it('refuses an otherwise decodable body carrying the invalid bit, unlike its false-bit control', async () => { + const params = '{"session_id":"root1","body":{}}'; + const body = '{"disposition":"admitted"}'; + vi.mocked(Call).mockResolvedValue(native(200, body, true)); + const refused = await invokeNative(() => handle, 'submit', 'SubmitSession', params, JSON.parse); + expect(refused.kind).toBe('invalid'); + vi.mocked(Call).mockResolvedValue(native(200, body, false)); + const accepted = await invokeNative(() => handle, 'submit', 'SubmitSession', params, JSON.parse); + expect(accepted.kind).toBe('ok'); + if (accepted.kind === 'ok') expect(accepted.data).toEqual({ disposition: 'admitted' }); + // The two calls carried the same exact params; only the invalid bit differs. + expect(vi.mocked(Call).mock.calls).toEqual([ + ['1', 'SubmitSession', params], + ['1', 'SubmitSession', params], + ]); + }); }); diff --git a/frontend/src/client/test-fixtures.ts b/frontend/src/client/test-fixtures.ts new file mode 100644 index 00000000..fcd3192e --- /dev/null +++ b/frontend/src/client/test-fixtures.ts @@ -0,0 +1,58 @@ +// Shared nominal values for the frontend's mounted and reader tests: one +// Session/Hydration shape with fresh defaults per call, and the two lossless +// response forms the generated bindings return. Callers spread their own +// overrides shallowly, so deliberate aliases of a supplied object stay. +import type { Hydration, Session, SessionRevision } from '../generated/protocol/types.gen'; +import type { desktopclient } from '../../client-bindings/wailsjs/go/models'; + +export const INSTANCE = '0123456789abcdef0123456789abcdef'; +// Descending last_activity then Session ID orders RFC3339 strings correctly; +// the fixtures use comparable timestamps. +export const NEWER = '2026-10-02T10:00:00Z'; +export const OLDER = '2026-10-01T05:00:00Z'; + +export function revision(durable: string, local = '0', instance = INSTANCE): SessionRevision { + return { instance_id: instance, durable_revision: durable, local_revision: local }; +} + +export function session(id: string, lastActivity: string, parent?: string): Session { + return { + session_id: id, + workspace: '/ws', + ...(parent ? { parent_session_id: parent } : {}), + lifecycle: 'open', + agent_type: 'primary', + created_at: '0', + last_activity: lastActivity, + session_revision: revision('1'), + }; +} + +export function hydration(overrides: Partial = {}): Hydration { + return { + session: session('root1', NEWER), + selected_model: null, + session_revision: revision('1'), + configuration_revision: { instance_id: INSTANCE, generation: '1' }, + warnings_revision: { instance_id: INSTANCE, revision: '1' }, + operations: [], + pending: { steering: [], queued: [] }, + conversation: { items: [] }, + usage: { totals: { by_model: [] }, context: { used_tokens: '0', context_window: 0 } }, + warnings: [], + background: [], + ...overrides, + }; +} + +// native supplies one scripted local result exactly as the native owner +// returns it, including the explicit invalid-delivery bit; producer validation +// stays at the Go response owner. +export function native(status: number, body: string, invalid: boolean): desktopclient.HTTPResponse { + return { status, body, invalid }; +} + +// json stringifies once and delegates: the body bytes are the producer's. +export function json(status: number, value: unknown): desktopclient.HTTPResponse { + return native(status, JSON.stringify(value), false); +} diff --git a/go.mod b/go.mod index 378a23ff..da01c182 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/getkin/kin-openapi v0.142.0 github.com/gofrs/flock v0.13.0 github.com/mattn/go-sqlite3 v1.14.50 + github.com/oapi-codegen/oapi-codegen/v2 v2.8.0 github.com/oapi-codegen/runtime v1.7.0 - github.com/pkoukk/tiktoken-go v0.1.8 github.com/wailsapp/wails/v2 v2.12.0 golang.org/x/sys v0.47.0 golang.org/x/term v0.45.0 @@ -18,7 +18,6 @@ require ( git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/bep/debounce v1.2.1 // indirect - github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.23.1 // indirect @@ -35,7 +34,6 @@ require ( github.com/leaanthony/u v1.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/oapi-codegen/oapi-codegen/v2 v2.8.0 // indirect github.com/oasdiff/yaml v0.1.1 // indirect github.com/oasdiff/yaml3 v0.0.14 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect @@ -51,7 +49,6 @@ require ( github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/wailsapp/go-webview2 v1.0.22 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect @@ -59,5 +56,3 @@ require ( golang.org/x/text v0.40.0 // indirect golang.org/x/tools v0.48.0 // indirect ) - -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen diff --git a/go.sum b/go.sum index 57fde61b..0e58164f 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,6 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkoukk/tiktoken-go v0.1.8 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo= -github.com/pkoukk/tiktoken-go v0.1.8/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -164,8 +162,6 @@ github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4X github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c= github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/harness/admission_test.go b/harness/admission_test.go index 8434b979..7cd5a7dd 100644 --- a/harness/admission_test.go +++ b/harness/admission_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -88,9 +89,7 @@ func newTestHarness(t *testing.T, store Storage, prepare func(context.Context, P } } h, err := New(context.Background(), Dependencies{Storage: store, Prepare: prepare}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) return h } @@ -142,9 +141,7 @@ func mustAdmitWithoutExecution(t *testing.T, h *Harness, sessionID, operationID t.Helper() c := leaseCoordinator(t, h, sessionID) release, err := h.reserve(context.Background(), c) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) defer release() rec, prepared, disposition, err := h.admitReserved(context.Background(), c, admissionRequest{ SessionID: sessionID, @@ -153,12 +150,8 @@ func mustAdmitWithoutExecution(t *testing.T, h *Harness, sessionID, operationID Origin: InputOriginUser, Content: content, }) - if err != nil { - t.Fatalf("admitReserved: %v", err) - } - if disposition != DispositionAdmitted || prepared == nil { - t.Fatalf("disposition %q (prepared %v), want a new admission", disposition, prepared != nil) - } + testassert.NoError(t, err) + testassert.Require(t, disposition == DispositionAdmitted && prepared != nil, "disposition %q (prepared %v), want a new admission", disposition, prepared != nil) return rec, disposition } @@ -177,12 +170,8 @@ func mustAdmit(t *testing.T, h *Harness, sessionID, operationID string, content Content: content, Mode: MessageModeRegular, }) - if err != nil { - t.Fatalf("Submit: %v", err) - } - if res.Operation == nil { - t.Fatalf("Submit disposition %q carried no operation record", res.Disposition) - } + testassert.NoError(t, err) + testassert.Require(t, res.Operation != nil, "Submit disposition %q carried no operation record", res.Disposition) return *res.Operation, res.Disposition } @@ -214,20 +203,24 @@ func storedSessionState(s *graphStorage, sessionID string) (entries []Entry, reg // performs no storage work. func TestNewConstructionContract(t *testing.T) { store := emptyStore(t) - if _, err := New(context.Background(), Dependencies{}); !errors.Is(err, ErrInvalid) { - t.Fatalf("New without dependencies = %v, want ErrInvalid", err) + { + _, err := New(context.Background(), Dependencies{}) + testassert.Require(t, errors.Is(err, ErrInvalid), "New without dependencies = %v, want ErrInvalid", err) } - if _, err := New(context.Background(), Dependencies{Storage: store}); !errors.Is(err, ErrInvalid) { - t.Fatalf("New without preparation = %v, want ErrInvalid", err) + { + _, err := New(context.Background(), Dependencies{Storage: store}) + testassert.Require(t, errors.Is(err, ErrInvalid), "New without preparation = %v, want ErrInvalid", err) } canceled, cancel := context.WithCancel(context.Background()) cancel() deps := Dependencies{Storage: store, Prepare: func(context.Context, PreparationRequest) (PreparedExecution, error) { return PreparedExecution{}, nil }} - if _, err := New(canceled, deps); !errors.Is(err, ErrInvalid) { - t.Fatalf("New with a dead context = %v, want ErrInvalid", err) + { + _, err := New(canceled, deps) + testassert.Require(t, errors.Is(err, ErrInvalid), "New with a dead context = %v, want ErrInvalid", err) } - if _, err := New(context.Background(), deps); err != nil { - t.Fatalf("valid construction rejected: %v", err) + { + _, err := New(context.Background(), deps) + testassert.NoError(t, err) } } @@ -240,39 +233,21 @@ func TestCreateSessionWorkspaceIdentity(t *testing.T) { h := newTestHarness(t, store, nil) rec, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if rec.Identity.Workspace != "/tmp/works" { - t.Fatalf("stored workspace = %q, want the caller value unchanged", rec.Identity.Workspace) - } - if !regexp.MustCompile(`^[0-9a-f]{32}$`).MatchString(rec.Identity.SessionID) { - t.Fatalf("session id %q is not 32 lowercase hex characters", rec.Identity.SessionID) - } - if rec.State.Lifecycle != LifecycleOpen || rec.State.CurrentAgentType != "coder" || rec.Revision != 1 { - t.Fatalf("created session state = %+v", rec.State) - } - if rec.Identity.CreatedAt.IsZero() || !rec.State.LastActivity.Equal(rec.Identity.CreatedAt) { - t.Fatalf("creation did not sample one time for created_at and last_activity: %+v", rec) - } - if len(rec.State.Usage.ByModel) != 0 { - t.Fatalf("created session carries usage: %+v", rec.State.Usage) - } + testassert.NoError(t, err) + testassert.Require(t, rec.Identity.Workspace == "/tmp/works" && regexp.MustCompile(`^[0-9a-f]{32}$`).MatchString(rec.Identity.SessionID) && (rec.State.Lifecycle == LifecycleOpen && rec.State.CurrentAgentType == "coder" && rec.Revision == 1) && (!rec.Identity.CreatedAt.IsZero() && rec.State.LastActivity.Equal(rec.Identity.CreatedAt)) && len(rec.State.Usage.ByModel) == 0, "got %v, want %v; session id %q is not 32 lowercase hex characters; created session state = %+v; creation did not sample one time for created_at and last_activity: %+v; created session carries usage: %+v", rec.Identity.Workspace, "/tmp/works", rec.Identity.SessionID, rec.State, rec, rec.State.Usage) read, err := h.ReadSessionHeader(context.Background(), rec.Identity.SessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if read.Identity.Workspace != "/tmp/works" || read.Revision.DurableRevision != 1 { - t.Fatalf("read session = %+v", read) - } + testassert.NoError(t, err) + testassert.Require(t, read.Identity.Workspace == "/tmp/works" && read.Revision.DurableRevision == 1, "read session = %+v", read) for _, workspace := range []string{"relative/path", "/tmp/works/../works", ""} { - if _, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: workspace, AgentType: "coder"}); !errors.Is(err, ErrInvalid) { - t.Fatalf("CreateSession(%q) = %v, want ErrInvalid", workspace, err) + { + _, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: workspace, AgentType: "coder"}) + testassert.Require(t, errors.Is(err, ErrInvalid), "CreateSession(%q) = %v, want ErrInvalid", workspace, err) } } - if _, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works"}); !errors.Is(err, ErrInvalid) { - t.Fatalf("empty agent type = %v, want ErrInvalid", err) + { + _, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works"}) + testassert.Require(t, errors.Is(err, ErrInvalid), "empty agent type = %v, want ErrInvalid", err) } } @@ -283,28 +258,29 @@ func TestCreateSessionWorkspaceIdentity(t *testing.T) { func TestReadsMaterializeValidatedState(t *testing.T) { store := emptyStore(t) h := newTestHarness(t, store, nil) - if _, err := h.ReadSessionHeader(context.Background(), testSessionID); !errors.Is(err, ErrNotFound) { - t.Fatalf("absent session = %v, want ErrNotFound", err) + { + _, err := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.Require(t, errors.Is(err, ErrNotFound), "absent session = %v, want ErrNotFound", err) } - if _, err := h.ReadHistoryView(context.Background(), testSessionID); !errors.Is(err, ErrNotFound) { - t.Fatalf("absent history = %v, want ErrNotFound", err) + { + _, err := h.ReadHistoryView(context.Background(), testSessionID) + testassert.Require(t, errors.Is(err, ErrNotFound), "absent history = %v, want ErrNotFound", err) } - if _, err := h.ReadSessionHeader(context.Background(), "nope"); !errors.Is(err, ErrInvalid) { - t.Fatalf("malformed session id = %v, want ErrInvalid", err) + { + _, err := h.ReadSessionHeader(context.Background(), "nope") + testassert.Require(t, errors.Is(err, ErrInvalid), "malformed session id = %v, want ErrInvalid", err) } - if _, err := h.ReadOperation(context.Background(), testSessionID, testOpID); !errors.Is(err, ErrNotFound) { - t.Fatalf("absent operation = %v, want ErrNotFound", err) + { + _, err := h.ReadOperation(context.Background(), testSessionID, testOpID) + testassert.Require(t, errors.Is(err, ErrNotFound), "absent operation = %v, want ErrNotFound", err) } created, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) read, err := h.ReadSessionHeader(context.Background(), created.Identity.SessionID) - if err != nil || read.Revision.DurableRevision != 1 || read.Identity.SessionID != created.Identity.SessionID { - t.Fatalf("ReadSession = %+v, err %v", read, err) - } - if _, err := h.ReadOperation(context.Background(), created.Identity.SessionID, "op-1"); !errors.Is(err, ErrNotFound) { - t.Fatalf("absent operation after creation = %v, want ErrNotFound", err) + testassert.Require(t, err == nil && read.Revision.DurableRevision == 1 && read.Identity.SessionID == created.Identity.SessionID, "ReadSession = %+v, err %v", read, err) + { + _, err := h.ReadOperation(context.Background(), created.Identity.SessionID, "op-1") + testassert.Require(t, errors.Is(err, ErrNotFound), "absent operation after creation = %v, want ErrNotFound", err) } } @@ -314,9 +290,7 @@ func TestReadsMaterializeValidatedState(t *testing.T) { func TestCorruptSessionIsUnavailable(t *testing.T) { fixture := validTestGraph() raw, err := encodeInputEntry(*fixture.entries[0].input) - if err != nil { - t.Fatalf("fixture encode: %v", err) - } + testassert.NoError(t, err) fixture.entries[0].rawOverride = setKey(raw, "bogus", json.RawMessage(`1`)) store := fixture.storage(t) h := newTestHarness(t, store, nil) @@ -339,11 +313,10 @@ func TestCorruptSessionIsUnavailable(t *testing.T) { }()) sibling, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession on a store with a corrupt sibling: %v", err) - } - if _, err := h.ReadSessionHeader(context.Background(), sibling.Identity.SessionID); err != nil { - t.Fatalf("valid sibling session unreadable: %v", err) + testassert.NoError(t, err) + { + _, err := h.ReadSessionHeader(context.Background(), sibling.Identity.SessionID) + testassert.NoError(t, err) } } @@ -355,23 +328,14 @@ func TestChangeAgentType(t *testing.T) { h := newTestHarness(t, store, nil) before, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSessionHeader: %v", err) - } + testassert.NoError(t, err) beforeUsage := readSessionView(t, h, testSessionID).TotalUsage after, err := h.ChangeAgentType(context.Background(), testSessionID, "reviewer") - if err != nil { - t.Fatalf("ChangeAgentType: %v", err) - } - if after.State.CurrentAgentType != "reviewer" || after.Revision != before.Revision.DurableRevision+1 { - t.Fatalf("changed session = %+v", after.State) - } - if after.Identity != before.Identity || !after.State.LastActivity.Equal(before.LastActivity) || - !usageTotalsEqual(after.State.Usage, beforeUsage) { - t.Fatalf("agent-type change altered identity, activity or usage: %+v", after) - } - if _, err := h.ChangeAgentType(context.Background(), testSessionID, ""); !errors.Is(err, ErrInvalid) { - t.Fatalf("empty agent type = %v, want ErrInvalid", err) + testassert.NoError(t, err) + testassert.Require(t, after.State.CurrentAgentType == "reviewer" && after.Revision == before.Revision.DurableRevision+1 && (after.Identity == before.Identity && after.State.LastActivity.Equal(before.LastActivity) && usageTotalsEqual(after.State.Usage, beforeUsage)), "changed session = %+v; agent-type change altered identity, activity or usage: %+v", after.State, after) + { + _, err := h.ChangeAgentType(context.Background(), testSessionID, "") + testassert.Require(t, errors.Is(err, ErrInvalid), "empty agent type = %v, want ErrInvalid", err) } archived := &testGraph{session: validSessionRecord()} @@ -379,8 +343,9 @@ func TestChangeAgentType(t *testing.T) { archived.session.State.Lifecycle = LifecycleArchived archived.session.State.ArchivedAt = &stamped archivedHarness := newTestHarness(t, archived.storage(t), nil) - if _, err := archivedHarness.ChangeAgentType(context.Background(), testSessionID, "reviewer"); !errors.Is(err, ErrInvalid) { - t.Fatalf("agent type on an archived session = %v, want ErrInvalid", err) + { + _, err := archivedHarness.ChangeAgentType(context.Background(), testSessionID, "reviewer") + testassert.Require(t, errors.Is(err, ErrInvalid), "agent type on an archived session = %v, want ErrInvalid", err) } } @@ -389,16 +354,18 @@ func TestChangeAgentType(t *testing.T) { func TestAdmissionSessionPreconditions(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) - if _, err := h.Submit(context.Background(), SubmitRequest{SessionID: otherSession(), OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}); !errors.Is(err, ErrNotFound) { - t.Fatalf("absent session admission = %v, want ErrNotFound", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{SessionID: otherSession(), OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) + testassert.Require(t, errors.Is(err, ErrNotFound), "absent session admission = %v, want ErrNotFound", err) } archived := &testGraph{session: validSessionRecord()} stamped := testTime archived.session.State.Lifecycle = LifecycleArchived archived.session.State.ArchivedAt = &stamped archivedHarness := newTestHarness(t, archived.storage(t), newPrepareStub(validPrepared()).prepare) - if _, err := archivedHarness.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}); !errors.Is(err, ErrInvalid) { - t.Fatalf("archived session admission = %v, want ErrInvalid", err) + { + _, err := archivedHarness.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) + testassert.Require(t, errors.Is(err, ErrInvalid), "archived session admission = %v, want ErrInvalid", err) } } @@ -419,9 +386,7 @@ func TestAdmissionInputValidation(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: tc.operationID, Origin: tc.origin, Content: tc.content, Mode: MessageModeRegular}) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("invalid input = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "invalid input = %v, want ErrInvalid", err) }) } } @@ -436,34 +401,14 @@ func TestAdmissionPublishesRunningOperation(t *testing.T) { h := newTestHarness(t, store, stub.prepare) rec, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("hello")) - if disposition != DispositionAdmitted { - t.Fatalf("disposition = %q, want admitted", disposition) - } - if rec.State.Status != OperationRunning || rec.Admission.AgentType != "coder" || - rec.Admission.RequestKind != RequestKindMessage || rec.Admission.SessionID != testSessionID || - rec.Admission.OperationID != testOpID { - t.Fatalf("admitted operation = %+v", rec) - } - if rec.Admission.Execution.ConfigurationRevision != testCapture().ConfigurationRevision || - rec.Admission.Execution.Model != testModelRef() || len(rec.Admission.Execution.Tools) != 1 { - t.Fatalf("durable capture = %+v", rec.Admission.Execution) - } - if rec.Admission.AdmittedEntry.SessionID != testSessionID || rec.Admission.AdmittedEntry.EntryID == "" { - t.Fatalf("admitted entry reference = %+v", rec.Admission.AdmittedEntry) - } - if rec.State.StartedAt.IsZero() || !rec.Admission.AdmittedAt.Equal(rec.State.StartedAt) { - t.Fatalf("operation timestamps = %+v", rec.State) - } - if len(rec.State.PendingToolCalls) != 0 || len(rec.State.Usage.ByModel) != 0 { - t.Fatalf("fresh operation state = %+v", rec.State) - } + testassert.Equal(t, disposition, DispositionAdmitted) + testassert.Require(t, rec.State.Status == OperationRunning && rec.Admission.AgentType == "coder" && rec.Admission.RequestKind == RequestKindMessage && rec.Admission.SessionID == testSessionID && rec.Admission.OperationID == testOpID && (rec.Admission.Execution.ConfigurationRevision == testCapture().ConfigurationRevision && rec.Admission.Execution.Model == testModelRef() && len(rec.Admission.Execution.Tools) == 1) && (rec.Admission.AdmittedEntry.SessionID == testSessionID && rec.Admission.AdmittedEntry.EntryID != "") && (!rec.State.StartedAt.IsZero() && rec.Admission.AdmittedAt.Equal(rec.State.StartedAt)) && (len(rec.State.PendingToolCalls) == 0 && len(rec.State.Usage.ByModel) == 0), "admitted operation = %+v; durable capture = %+v; admitted entry reference = %+v; operation timestamps = %+v; fresh operation state = %+v", rec, rec.Admission.Execution, rec.Admission.AdmittedEntry, rec.State, rec.State) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 1 || entries[0].Kind != EntryInput || entries[0].OperationID != testOpID || entries[0].Sequence != 1 { - t.Fatalf("published entries = %+v", entries) - } - if _, err := decodeInputEntry(entries[0]); err != nil { - t.Fatalf("published input entry does not decode: %v", err) + testassert.Require(t, len(entries) == 1 && entries[0].Kind == EntryInput && entries[0].OperationID == testOpID && entries[0].Sequence == 1, "published entries = %+v", entries) + { + _, err := decodeInputEntry(entries[0]) + testassert.NoError(t, err) } var sessionReg, opReg *Register for i := range regs { @@ -474,24 +419,15 @@ func TestAdmissionPublishesRunningOperation(t *testing.T) { opReg = ®s[i] } } - if sessionReg == nil || opReg == nil { - t.Fatalf("published registers = %+v", regs) - } - if opReg.Key.OperationID != testOpID { - t.Fatalf("operation register key = %+v", opReg.Key) - } + testassert.Require(t, sessionReg != nil && opReg != nil, "published registers = %+v", regs) + testassert.Require(t, opReg.Key.OperationID == testOpID, "operation register key = %+v", opReg.Key) session, err := decodeSessionRegister(*sessionReg) - if err != nil { - t.Fatalf("session register decode: %v", err) - } - if session.State.CurrentOperationID != testOpID || session.Revision != 2 { - t.Fatalf("session register = revision %d, current op %q", session.Revision, session.State.CurrentOperationID) - } - if !session.State.LastActivity.Equal(entries[0].CommittedAt) { - t.Fatalf("last_activity %v did not advance to the input commit time %v", session.State.LastActivity, entries[0].CommittedAt) - } - if _, err := validateSessionGraph(context.Background(), store, testSessionID); err != nil { - t.Fatalf("published graph does not validate: %v", err) + testassert.NoError(t, err) + testassert.Require(t, session.State.CurrentOperationID == testOpID && session.Revision == 2, "session register = revision %d, current op %q", session.Revision, session.State.CurrentOperationID) + testassert.Require(t, session.State.LastActivity.Equal(entries[0].CommittedAt), "last_activity %v did not advance to the input commit time %v", session.State.LastActivity, entries[0].CommittedAt) + { + _, err := validateSessionGraph(context.Background(), store, testSessionID) + testassert.NoError(t, err) } } @@ -505,20 +441,16 @@ func TestAdmissionPreparationContract(t *testing.T) { store := freshSessionStore(t) stub := newPrepareStub(validPrepared()) h := newTestHarness(t, store, stub.prepare) - if _, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition = %q", disposition) + { + _, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } - if got := stub.callCount(); got != 1 { - t.Fatalf("preparation calls = %d, want exactly one", got) + { + got := stub.callCount() + testassert.Equal(t, got, 1) } req := stub.lastCall().req - if req.RequestKind != RequestKindMessage { - t.Fatalf("request kind = %q", req.RequestKind) - } - if req.Session.Identity.SessionID != testSessionID || req.Session.Identity.Workspace != "/tmp/works" || - req.Session.Identity.CreatedAt.IsZero() || req.Session.AgentType != "coder" { - t.Fatalf("preparation session view = %+v", req.Session) - } + testassert.Require(t, req.RequestKind == RequestKindMessage && (req.Session.Identity.SessionID == testSessionID && req.Session.Identity.Workspace == "/tmp/works" && !req.Session.Identity.CreatedAt.IsZero() && req.Session.AgentType == "coder"), "got %v, want %v; preparation session view = %+v", req.RequestKind, RequestKindMessage, req.Session) }) t.Run("invalid prepared execution publishes nothing", func(t *testing.T) { @@ -536,13 +468,9 @@ func TestAdmissionPreparationContract(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(prepared).prepare) _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Content: admissionContent("x"), Mode: MessageModeRegular}) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("invalid prepared execution = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "invalid prepared execution = %v, want ErrInvalid", err) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 || regs[0].Key.Kind != RegisterSession { - t.Fatalf("invalid preparation published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1 && regs[0].Key.Kind == RegisterSession, "invalid preparation published entries %v registers %v", entries, regs) }) } }) @@ -554,13 +482,9 @@ func TestAdmissionPreparationContract(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, stub.prepare) _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) - if err != prepErr { - t.Fatalf("preparation error = %v, want the exact callback error", err) - } + testassert.Equal(t, err, prepErr) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 { - t.Fatalf("failed preparation published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1, "failed preparation published entries %v registers %v", entries, regs) }) t.Run("caller cancellation stops preparation", func(t *testing.T) { @@ -578,16 +502,10 @@ func TestAdmissionPreparationContract(t *testing.T) { <-stub.arrived // the callback is parked cancel() err := <-done - if err == nil || !errors.Is(err, context.Canceled) { - t.Fatalf("canceled admission = %v, want a context error", err) - } - if stub.lastCall().ctx.Err() == nil { - t.Fatalf("preparation context was not canceled with the caller") - } + testassert.Require(t, err != nil && errors.Is(err, context.Canceled), "canceled admission = %v, want a context error", err) + testassert.Require(t, stub.lastCall().ctx.Err() != nil, "preparation context was not canceled with the caller") entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 { - t.Fatalf("canceled preparation published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1, "canceled preparation published entries %v registers %v", entries, regs) close(gate) }) @@ -600,12 +518,8 @@ func TestAdmissionPreparationContract(t *testing.T) { mustAdmit(t, h, testSessionID, testOpID, admissionContent("x")) tools[0].Parameters = json.RawMessage(`{"type":"string"}`) rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if string(rec.Admission.Execution.Tools[0].Parameters) != `{"type":"object"}` { - t.Fatalf("durable capture shares tool bytes with the caller: %q", rec.Admission.Execution.Tools[0].Parameters) - } + testassert.NoError(t, err) + testassert.Require(t, string(rec.Admission.Execution.Tools[0].Parameters) == `{"type":"object"}`, "durable capture shares tool bytes with the caller: %q", rec.Admission.Execution.Tools[0].Parameters) }) } @@ -624,8 +538,9 @@ func TestAdmissionReservationAndRaces(t *testing.T) { first := make(chan struct{}, 1) go func() { - if _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Content: admissionContent("first"), Mode: MessageModeRegular}); err != nil { - t.Errorf("first admission: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Content: admissionContent("first"), Mode: MessageModeRegular}) + testassert.Check(t, err == nil, "first admission: %v", err) } first <- struct{}{} }() @@ -633,8 +548,9 @@ func TestAdmissionReservationAndRaces(t *testing.T) { readDone := make(chan struct{}, 1) go func() { - if _, err := h.ReadSessionHeader(context.Background(), testSessionID); err != nil { - t.Errorf("ReadSession during preparation: %v", err) + { + _, err := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.Check(t, err == nil, "ReadSession during preparation: %v", err) } readDone <- struct{}{} }() @@ -647,23 +563,18 @@ func TestAdmissionReservationAndRaces(t *testing.T) { second := make(chan struct{}, 1) go func() { res, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Content: admissionContent("second"), Mode: MessageModeRegular}) - if err != nil { - t.Errorf("second admission: %v", err) - } - if res.Disposition != DispositionExisting || res.Operation == nil { - t.Errorf("second admission disposition = %q, want existing", res.Disposition) - } - if res.Operation != nil && res.Operation.Admission.AdmittedAt != firstAdmittedAt(t, h, testSessionID, testOpID) { - t.Errorf("second admission returned a different operation") - } + testassert.Check(t, err == nil, "second admission: %v", err) + testassert.Check(t, res.Disposition == DispositionExisting && res.Operation != nil, "second admission disposition = %q, want existing", res.Disposition) + testassert.Check(t, res.Operation == nil || res.Operation.Admission.AdmittedAt == firstAdmittedAt(t, h, testSessionID, testOpID), "second admission returned a different operation") second <- struct{}{} }() close(gate) <-first <-second - if got := stub.callCount(); got != 1 { - t.Fatalf("preparation calls = %d, want one: the blocked admission must not prepare again", got) + { + got := stub.callCount() + testassert.Equal(t, got, 1) } }) @@ -681,20 +592,17 @@ func TestAdmissionReservationAndRaces(t *testing.T) { }() <-stub.arrived - if err := store.Transact(context.Background(), func(tx Transaction) error { - return foreignAgentTypeChange(tx, testSessionID, "foreign") - }); err != nil { - t.Fatalf("foreign mutation: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return foreignAgentTypeChange(tx, testSessionID, "foreign") + }) + testassert.NoError(t, err) } close(gate) err := <-done - if !errors.Is(err, ErrConflict) { - t.Fatalf("raced admission = %v, want ErrConflict", err) - } + testassert.Require(t, errors.Is(err, ErrConflict), "raced admission = %v, want ErrConflict", err) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 || regs[0].Revision != 2 { - t.Fatalf("conflicted admission published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1 && regs[0].Revision == 2, "conflicted admission published entries %v registers %v", entries, regs) }) t.Run("agent-type change during preparation affects later admission only", func(t *testing.T) { @@ -707,12 +615,8 @@ func TestAdmissionReservationAndRaces(t *testing.T) { admitted := make(chan struct{}, 1) go func() { res, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) - if err != nil { - t.Errorf("admission: %v", err) - } - if res.Disposition != DispositionAdmitted || res.Operation == nil || res.Operation.Admission.AgentType != "coder" { - t.Errorf("admitted operation = %+v disposition %q", res.Operation, res.Disposition) - } + testassert.Check(t, err == nil, "admission: %v", err) + testassert.Check(t, res.Disposition == DispositionAdmitted && res.Operation != nil && res.Operation.Admission.AgentType == "coder", "admitted operation = %+v disposition %q", res.Operation, res.Disposition) admitted <- struct{}{} }() <-stub.arrived @@ -720,12 +624,8 @@ func TestAdmissionReservationAndRaces(t *testing.T) { changed := make(chan struct{}, 1) go func() { rec, err := h.ChangeAgentType(context.Background(), testSessionID, "reviewer") - if err != nil { - t.Errorf("ChangeAgentType: %v", err) - } - if rec.State.CurrentAgentType != "reviewer" { - t.Errorf("changed session = %+v", rec.State) - } + testassert.Check(t, err == nil, "ChangeAgentType: %v", err) + testassert.Check(t, rec.State.CurrentAgentType == "reviewer", "changed session = %+v", rec.State) changed <- struct{}{} }() @@ -733,25 +633,17 @@ func TestAdmissionReservationAndRaces(t *testing.T) { <-admitted <-changed rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.Admission.AgentType != "coder" { - t.Fatalf("admitted operation agent type = %q, want the capture-time value", rec.Admission.AgentType) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.Admission.AgentType, "coder") session, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil || session.CurrentAgentType != "reviewer" { - t.Fatalf("session agent type = %+v err %v", session, err) - } + testassert.Require(t, err == nil && session.CurrentAgentType == "reviewer", "session agent type = %+v err %v", session, err) }) } func firstAdmittedAt(t *testing.T, h *Harness, sessionID, operationID string) time.Time { t.Helper() rec, err := h.ReadOperation(context.Background(), sessionID, operationID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } + testassert.NoError(t, err) return rec.Admission.AdmittedAt } @@ -792,16 +684,13 @@ func TestAdmissionPartialFailurePublishesNothing(t *testing.T) { } h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Content: admissionContent("x"), Mode: MessageModeRegular}) - if err != injected { - t.Fatalf("admission error = %v, want the injected failure", err) - } + testassert.Equal(t, err, injected) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 || regs[0].Revision != 1 { - t.Fatalf("partial failure published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1 && regs[0].Revision == 1, "partial failure published entries %v registers %v", entries, regs) store.txHook = nil - if _, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("x")); disposition != DispositionAdmitted { - t.Fatalf("retry disposition = %q", disposition) + { + _, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("x")) + testassert.Equal(t, disposition, DispositionAdmitted) } }) } @@ -819,26 +708,17 @@ func TestAdmissionIdempotency(t *testing.T) { stub := newPrepareStub(validPrepared()) h := newTestHarness(t, store, stub.prepare) first, firstDisp := mustAdmit(t, h, testSessionID, testOpID, admissionContent("first")) - if firstDisp != DispositionAdmitted { - t.Fatalf("first disposition = %q", firstDisp) - } + testassert.Equal(t, firstDisp, DispositionAdmitted) retry, retryDisp := mustAdmit(t, h, testSessionID, testOpID, admissionContent("second")) - if retryDisp != DispositionExisting { - t.Fatalf("retry disposition = %q, want existing", retryDisp) - } - if retry.Admission.AdmittedEntry != first.Admission.AdmittedEntry || !retry.Admission.AdmittedAt.Equal(first.Admission.AdmittedAt) { - t.Fatalf("retry returned a different operation: %+v vs %+v", retry, first) - } + testassert.Equal(t, retryDisp, DispositionExisting) + testassert.Require(t, retry.Admission.AdmittedEntry == first.Admission.AdmittedEntry && retry.Admission.AdmittedAt.Equal(first.Admission.AdmittedAt), "retry returned a different operation: %+v vs %+v", retry, first) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 1 || len(regs) != 2 { - t.Fatalf("retry published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 1 && len(regs) == 2, "retry published entries %v registers %v", entries, regs) decoded, err := decodeInputEntry(entries[0]) - if err != nil || decoded.Content[0].Text != "first" { - t.Fatalf("retry replaced the first payload: %+v err %v", decoded, err) - } - if got := stub.callCount(); got != 1 { - t.Fatalf("preparation calls = %d, want one", got) + testassert.Require(t, err == nil && decoded.Content[0].Text == "first", "retry replaced the first payload: %+v err %v", decoded, err) + { + got := stub.callCount() + testassert.Equal(t, got, 1) } }) @@ -856,30 +736,21 @@ func TestAdmissionIdempotency(t *testing.T) { // A concurrent admission of the same identity wins: it // publishes the full input, operation, and session state // directly into storage. - if err := store.Transact(context.Background(), func(tx Transaction) error { - return insertForeignAdmission(t, tx, testSessionID, testOpID) - }); err != nil { - t.Fatalf("foreign admission: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return insertForeignAdmission(t, tx, testSessionID, testOpID) + }) + testassert.NoError(t, err) } }, check: func(t *testing.T, store *graphStorage, h *Harness, _ string, _ int64, res SubmitResult, err error) { - if err != nil { - t.Fatalf("raced admission: %v", err) - } - if res.Disposition != DispositionExisting { - t.Fatalf("raced admission disposition = %q, want existing", res.Disposition) - } + testassert.NoError(t, err) + testassert.Equal(t, res.Disposition, DispositionExisting) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 1 || len(regs) != 2 { - t.Fatalf("raced admission published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 1 && len(regs) == 2, "raced admission published entries %v registers %v", entries, regs) rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.Admission.Execution.ConfigurationRevision != testCapture().ConfigurationRevision { - t.Fatalf("raced admission returned the wrong operation: %+v", rec) - } + testassert.NoError(t, err) + testassert.Require(t, rec.Admission.Execution.ConfigurationRevision == testCapture().ConfigurationRevision, "raced admission returned the wrong operation: %+v", rec) }, }, { @@ -889,51 +760,41 @@ func TestAdmissionIdempotency(t *testing.T) { // caller ID, with its typed settlement and no Session-register // change. settlementID := hexID(60) - if err := store.Transact(context.Background(), func(tx Transaction) error { - settlement, err := encodeOperationSettlementEntry(operationSettlementEntry{ - SessionID: testSessionID, EntryID: settlementID, OperationID: testOpID, Status: OperationSuccess, - }) - if err != nil { - return err - } - if _, err := tx.InsertEntry(EntryDraft{SessionID: testSessionID, ID: settlementID, OperationID: testOpID, Kind: EntryOperationSettlement, Payload: settlement}); err != nil { - return err - } - winner := compactOperationRecord(testOpID) - stamped := testTime - winner.State.Status = OperationSuccess - winner.State.SettledAt = &stamped - winner.State.Terminal = &OperationTerminal{SettlementEntry: EntryRef{SessionID: testSessionID, EntryID: settlementID}} - payload, err := encodeOperationRegister(winner) - if err != nil { + { + err := store.Transact(context.Background(), func(tx Transaction) error { + settlement, err := encodeOperationSettlementEntry(operationSettlementEntry{ + SessionID: testSessionID, EntryID: settlementID, OperationID: testOpID, Status: OperationSuccess, + }) + if err != nil { + return err + } + if _, err := tx.InsertEntry(EntryDraft{SessionID: testSessionID, ID: settlementID, OperationID: testOpID, Kind: EntryOperationSettlement, Payload: settlement}); err != nil { + return err + } + winner := compactOperationRecord(testOpID) + stamped := testTime + winner.State.Status = OperationSuccess + winner.State.SettledAt = &stamped + winner.State.Terminal = &OperationTerminal{SettlementEntry: EntryRef{SessionID: testSessionID, EntryID: settlementID}} + payload, err := encodeOperationRegister(winner) + if err != nil { + return err + } + _, err = tx.InsertRegister(RegisterDraft{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: payload}) return err - } - _, err = tx.InsertRegister(RegisterDraft{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: payload}) - return err - }); err != nil { - t.Fatalf("compact winner: %v", err) + }) + testassert.NoError(t, err) } }, check: func(t *testing.T, store *graphStorage, _ *Harness, _ string, _ int64, _ SubmitResult, err error) { - if !errors.Is(err, ErrInvalid) { - t.Fatalf("raced admission = %v, want the wrong-kind rejection", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "raced admission = %v, want the wrong-kind rejection", err) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 1 || entries[0].ID != hexID(60) { - t.Fatalf("raced admission published %v, want no extra input beside the winner's settlement", entries) - } - if len(regs) != 2 { - t.Fatalf("raced admission published %d registers, want only the session and the winner", len(regs)) - } + testassert.Require(t, len(entries) == 1 && entries[0].ID == hexID(60), "raced admission published %v, want no extra input beside the winner's settlement", entries) + testassert.Equal(t, len(regs), 2) wire, ok := registerLookup(regs, RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}) - if !ok { - t.Fatalf("the compact winner's register vanished") - } + testassert.Require(t, ok, "the compact winner's register vanished") rec, err := decodeOperationRegister(wire) - if err != nil || rec.Admission.RequestKind != RequestKindCompact || - rec.State.Terminal == nil || rec.State.Terminal.SettlementEntry.EntryID != hexID(60) { - t.Fatalf("winner rewritten = %+v err %v, want the compact terminal untouched", rec, err) - } + testassert.Require(t, err == nil && rec.Admission.RequestKind == RequestKindCompact && rec.State.Terminal != nil && rec.State.Terminal.SettlementEntry.EntryID == hexID(60), "winner rewritten = %+v err %v, want the compact terminal untouched", rec, err) }, }, { @@ -941,37 +802,29 @@ func TestAdmissionIdempotency(t *testing.T) { seed: func(t *testing.T, store *graphStorage, _ *Harness, other string) { // The winner lands in its own Session and changes only that // Session. - if err := store.Transact(context.Background(), func(tx Transaction) error { - return insertForeignAdmission(t, tx, other, testOpID) - }); err != nil { - t.Fatalf("foreign admission: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return insertForeignAdmission(t, tx, other, testOpID) + }) + testassert.NoError(t, err) } }, check: func(t *testing.T, store *graphStorage, _ *Harness, other string, baseRevision int64, _ SubmitResult, err error) { - if !errors.Is(err, ErrInvalid) { - t.Fatalf("raced admission = %v, want the foreign-identity rejection", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "raced admission = %v, want the foreign-identity rejection", err) entries, _ := storedSessionState(store, testSessionID) - if len(entries) != 0 { - t.Fatalf("raced admission published %v, want no extra input", entries) - } + testassert.Require(t, len(entries) == 0, "raced admission published %v, want no extra input", entries) _, otherRegs := storedSessionState(store, other) otherWire, ok := registerLookup(otherRegs, RegisterKey{SessionID: other, Kind: RegisterOperation, OperationID: testOpID}) - if !ok { - t.Fatalf("the foreign winner is not durable in its own Session") - } - if otherRec, err := decodeOperationRegister(otherWire); err != nil || otherRec.Admission.SessionID != other { - t.Fatalf("foreign winner = %+v err %v, want it owned by its own Session", otherRec, err) + testassert.Require(t, ok, "the foreign winner is not durable in its own Session") + { + otherRec, err := decodeOperationRegister(otherWire) + testassert.Require(t, err == nil && otherRec.Admission.SessionID == other, "foreign winner = %+v err %v, want it owned by its own Session", otherRec, err) } _, regs := storedSessionState(store, testSessionID) wire, ok := registerLookup(regs, RegisterKey{SessionID: testSessionID, Kind: RegisterSession}) - if !ok { - t.Fatalf("the session register vanished") - } + testassert.Require(t, ok, "the session register vanished") current, err := decodeSessionRegister(wire) - if err != nil || current.Revision != baseRevision { - t.Fatalf("this Session changed = %+v err %v (revision %d, was %d), want the foreign winner confined to its own Session", current, err, current.Revision, baseRevision) - } + testassert.Require(t, err == nil && current.Revision == baseRevision, "this Session changed = %+v err %v (revision %d, was %d), want the foreign winner confined to its own Session", current, err, current.Revision, baseRevision) }, }, } { @@ -982,9 +835,7 @@ func TestAdmissionIdempotency(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, stub.prepare) created, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) other := created.Identity.SessionID type outcome struct { @@ -1003,19 +854,16 @@ func TestAdmissionIdempotency(t *testing.T) { _, baseRegs := storedSessionState(store, testSessionID) baseWire, ok := registerLookup(baseRegs, RegisterKey{SessionID: testSessionID, Kind: RegisterSession}) - if !ok { - t.Fatalf("the session register vanished") - } + testassert.Require(t, ok, "the session register vanished") base, err := decodeSessionRegister(baseWire) - if err != nil { - t.Fatalf("decode session register: %v", err) - } + testassert.NoError(t, err) tc.seed(t, store, h, other) close(gate) out := <-done - if got := stub.callCount(); got != 1 { - t.Fatalf("preparation calls = %d, want one", got) + { + got := stub.callCount() + testassert.Equal(t, got, 1) } tc.check(t, store, h, other, base.Revision, out.res, out.err) }) @@ -1026,17 +874,11 @@ func TestAdmissionIdempotency(t *testing.T) { h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) mustAdmit(t, h, testSessionID, "shared-op", admissionContent("x")) created, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) _, err = h.Submit(context.Background(), SubmitRequest{SessionID: created.Identity.SessionID, OperationID: "shared-op", Origin: InputOriginUser, Content: admissionContent("y"), Mode: MessageModeRegular}) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("cross-session reuse = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "cross-session reuse = %v, want ErrInvalid", err) entries, regs := storedSessionState(store, created.Identity.SessionID) - if len(entries) != 0 || len(regs) != 1 { - t.Fatalf("rejected reuse published entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1, "rejected reuse published entries %v registers %v", entries, regs) }) } @@ -1167,66 +1009,58 @@ func TestAdmissionRechecksPreconditionsAfterReservation(t *testing.T) { store := freshSessionStore(t) stub := newPrepareStub(validPrepared()) h := newTestHarness(t, store, stub.prepare) - leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races - if _, err := h.ReadHistoryView(context.Background(), testSessionID); err != nil { // a real history use under the held lease - t.Fatalf("ReadHistoryView: %v", err) + leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races + // a real history use under the held lease + { + _, err := h.ReadHistoryView(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := store.Transact(context.Background(), func(tx Transaction) error { - return foreignArchiveChange(tx, testSessionID) - }); err != nil { - t.Fatalf("foreign archive: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return foreignArchiveChange(tx, testSessionID) + }) + testassert.NoError(t, err) } _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("admission over a foreign archive = %v, want ErrInvalid", err) - } - if got := stub.callCount(); got != 1 { - t.Fatalf("preparation calls = %d, want one: preparation may run against the stale view", got) + testassert.Require(t, errors.Is(err, ErrInvalid), "admission over a foreign archive = %v, want ErrInvalid", err) + { + got := stub.callCount() + testassert.Equal(t, got, 1) } entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 || regs[0].Revision != 2 { - t.Fatalf("refused admission left entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1 && regs[0].Revision == 2, "refused admission left entries %v registers %v", entries, regs) session, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if session.Lifecycle != LifecycleArchived { - t.Fatalf("read session kept the stale view: %+v", session) - } + testassert.NoError(t, err) + testassert.Require(t, session.Lifecycle == LifecycleArchived, "read session kept the stale view: %+v", session) }) t.Run("foreign committed running operation refuses at commit", func(t *testing.T) { store := freshSessionStore(t) stub := newPrepareStub(validPrepared()) h := newTestHarness(t, store, stub.prepare) - leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races - if _, err := h.ReadHistoryView(context.Background(), testSessionID); err != nil { // a real history use under the held lease - t.Fatalf("ReadHistoryView: %v", err) + leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races + // a real history use under the held lease + { + _, err := h.ReadHistoryView(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := store.Transact(context.Background(), func(tx Transaction) error { - return insertForeignAdmission(t, tx, testSessionID, "foreign-op") - }); err != nil { - t.Fatalf("foreign admission: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return insertForeignAdmission(t, tx, testSessionID, "foreign-op") + }) + testassert.NoError(t, err) } _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("admission over a foreign running operation = %v, want ErrInvalid", err) - } - if got := stub.callCount(); got != 1 { - t.Fatalf("preparation calls = %d, want one: preparation may run against the stale view", got) + testassert.Require(t, errors.Is(err, ErrInvalid), "admission over a foreign running operation = %v, want ErrInvalid", err) + { + got := stub.callCount() + testassert.Equal(t, got, 1) } entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 1 || len(regs) != 2 { - t.Fatalf("refused admission left entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 1 && len(regs) == 2, "refused admission left entries %v registers %v", entries, regs) session, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if session.CurrentOperationID != "foreign-op" { - t.Fatalf("read session kept the stale view: %+v", session) - } + testassert.NoError(t, err) + testassert.Require(t, session.CurrentOperationID == "foreign-op", "read session kept the stale view: %+v", session) }) } @@ -1247,46 +1081,44 @@ func TestForeignChangeRefreshesTheView(t *testing.T) { done <- err }() <-stub.arrived - if err := store.Transact(context.Background(), func(tx Transaction) error { - return foreignAgentTypeChange(tx, testSessionID, "foreign") - }); err != nil { - t.Fatalf("foreign mutation: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return foreignAgentTypeChange(tx, testSessionID, "foreign") + }) + testassert.NoError(t, err) } close(gate) - if err := <-done; !errors.Is(err, ErrConflict) { - t.Fatalf("raced admission = %v, want ErrConflict", err) + { + err := <-done + testassert.Require(t, errors.Is(err, ErrConflict), "raced admission = %v, want ErrConflict", err) } session, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if session.CurrentAgentType != "foreign" { - t.Fatalf("read session kept the stale view: %+v", session) - } + testassert.NoError(t, err) + testassert.Require(t, session.CurrentAgentType == "foreign", "read session kept the stale view: %+v", session) }) t.Run("failed agent-type change", func(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, nil) - leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races - if _, err := h.ReadHistoryView(context.Background(), testSessionID); err != nil { // a real history use under the held lease - t.Fatalf("ReadHistoryView: %v", err) + leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races + // a real history use under the held lease + { + _, err := h.ReadHistoryView(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := store.Transact(context.Background(), func(tx Transaction) error { - return foreignAgentTypeChange(tx, testSessionID, "foreign") - }); err != nil { - t.Fatalf("foreign mutation: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return foreignAgentTypeChange(tx, testSessionID, "foreign") + }) + testassert.NoError(t, err) } - if _, err := h.ChangeAgentType(context.Background(), testSessionID, "reviewer"); !errors.Is(err, ErrConflict) { - t.Fatalf("raced change = %v, want ErrConflict", err) + { + _, err := h.ChangeAgentType(context.Background(), testSessionID, "reviewer") + testassert.Require(t, errors.Is(err, ErrConflict), "raced change = %v, want ErrConflict", err) } session, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if session.CurrentAgentType != "foreign" { - t.Fatalf("read session kept the stale view: %+v", session) - } + testassert.NoError(t, err) + testassert.Require(t, session.CurrentAgentType == "foreign", "read session kept the stale view: %+v", session) }) } @@ -1311,9 +1143,7 @@ func TestHarnessCancellationGatesPublication(t *testing.T) { stub.ignoreCtx = true // preparation completes despite the canceled harness context store := freshSessionStore(t) h, err := New(harnessCtx, Dependencies{Storage: store, Prepare: stub.prepare}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) done := make(chan error, 1) go func() { @@ -1324,16 +1154,10 @@ func TestHarnessCancellationGatesPublication(t *testing.T) { cancelHarness() close(gate) err = <-done - if !errors.Is(err, context.Canceled) { - t.Fatalf("admission over a canceled harness context = %v, want a context error", err) - } - if opens != 0 { - t.Fatalf("opener calls = %d, want a failed publication to discard the prepared values without opening", opens) - } + testassert.Require(t, errors.Is(err, context.Canceled), "admission over a canceled harness context = %v, want a context error", err) + testassert.Equal(t, opens, 0) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 || regs[0].Revision != 1 { - t.Fatalf("canceled publication left entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1 && regs[0].Revision == 1, "canceled publication left entries %v registers %v", entries, regs) } // TestReadOperationReturnsOwnedRecords proves every pointer field of a @@ -1344,39 +1168,25 @@ func TestReadOperationReturnsOwnedRecords(t *testing.T) { fixture.ops[0].State.ActiveEffect = &ActiveEffect{Kind: EffectModel, ResultEntryID: hexID(3)} h := newTestHarness(t, fixture.storage(t), nil) rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } + testassert.NoError(t, err) rec.State.ActiveEffect.Kind = EffectTool rec.State.ActiveEffect.ResultEntryID = hexID(9) again, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("re-read: %v", err) - } - if again.State.ActiveEffect == nil || again.State.ActiveEffect.Kind != EffectModel || again.State.ActiveEffect.ResultEntryID != hexID(3) { - t.Fatalf("returned record shares the active effect with the coordinator view: %+v", again.State.ActiveEffect) - } + testassert.NoError(t, err) + testassert.Require(t, again.State.ActiveEffect != nil && again.State.ActiveEffect.Kind == EffectModel && again.State.ActiveEffect.ResultEntryID == hexID(3), "returned record shares the active effect with the coordinator view: %+v", again.State.ActiveEffect) }) t.Run("terminal pointers", func(t *testing.T) { h := newTestHarness(t, terminalTestGraph().storage(t), nil) rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } + testassert.NoError(t, err) other := testTime.Add(time.Hour) *rec.State.SettledAt = other rec.State.Terminal.Detail = "tampered" rec.State.Terminal.SettlementEntry.EntryID = hexID(9) again, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("re-read: %v", err) - } - if again.State.SettledAt == nil || !again.State.SettledAt.Equal(testTime) || - again.State.Terminal == nil || again.State.Terminal.Detail != "" || - again.State.Terminal.SettlementEntry.EntryID != hexID(3) { - t.Fatalf("returned record shares terminal pointers with the coordinator view: %+v", again.State) - } + testassert.NoError(t, err) + testassert.Require(t, again.State.SettledAt != nil && again.State.SettledAt.Equal(testTime) && again.State.Terminal != nil && again.State.Terminal.Detail == "" && again.State.Terminal.SettlementEntry.EntryID == hexID(3), "returned record shares terminal pointers with the coordinator view: %+v", again.State) }) } @@ -1386,8 +1196,9 @@ func TestAdmissionOwnsInputContent(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) content := []model.ContentPart{{Kind: model.PartText, Text: "hello", Extra: model.Extra{"k": json.RawMessage(`"v"`)}}} - if _, disposition := mustAdmit(t, h, testSessionID, testOpID, content); disposition != DispositionAdmitted { - t.Fatalf("disposition = %q", disposition) + { + _, disposition := mustAdmit(t, h, testSessionID, testOpID, content) + testassert.Equal(t, disposition, DispositionAdmitted) } content[0].Text = "tampered" content[0].Extra["k"] = json.RawMessage(`"tampered"`) @@ -1397,9 +1208,7 @@ func TestAdmissionOwnsInputContent(t *testing.T) { c.mu.Lock() entry := *c.graph.Entries[0].Input c.mu.Unlock() - if entry.Content[0].Text != "hello" || string(entry.Content[0].Extra["k"]) != `"v"` { - t.Fatalf("coordinator view shares input content with the caller: %+v", entry.Content) - } + testassert.Require(t, entry.Content[0].Text == "hello" && string(entry.Content[0].Extra["k"]) == `"v"`, "coordinator view shares input content with the caller: %+v", entry.Content) } // TestReadOperationValidatesIdentity proves an empty operation identity is @@ -1407,11 +1216,13 @@ func TestAdmissionOwnsInputContent(t *testing.T) { func TestReadOperationValidatesIdentity(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, nil) - if _, err := h.ReadOperation(context.Background(), testSessionID, ""); !errors.Is(err, ErrInvalid) { - t.Fatalf("empty operation id = %v, want ErrInvalid", err) + { + _, err := h.ReadOperation(context.Background(), testSessionID, "") + testassert.Require(t, errors.Is(err, ErrInvalid), "empty operation id = %v, want ErrInvalid", err) } - if _, err := h.ReadOperation(context.Background(), testSessionID, "op-ghost"); !errors.Is(err, ErrNotFound) { - t.Fatalf("absent operation id = %v, want ErrNotFound", err) + { + _, err := h.ReadOperation(context.Background(), testSessionID, "op-ghost") + testassert.Require(t, errors.Is(err, ErrNotFound), "absent operation id = %v, want ErrNotFound", err) } } @@ -1424,8 +1235,9 @@ func TestReadOperationValidatesIdentity(t *testing.T) { func TestAdmitExistingBeforeLifecycle(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) - if _, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("first")); disposition != DispositionAdmitted { - t.Fatalf("first disposition = %q", disposition) + { + _, disposition := mustAdmit(t, h, testSessionID, testOpID, admissionContent("first")) + testassert.Equal(t, disposition, DispositionAdmitted) } h.mu.Lock() c := h.sessions[testSessionID] @@ -1437,12 +1249,8 @@ func TestAdmitExistingBeforeLifecycle(t *testing.T) { c.mu.Unlock() res, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Content: admissionContent("retry"), Mode: MessageModeRegular}) - if err != nil { - t.Fatalf("retry on an archived session = %v, want the existing Operation", err) - } - if res.Disposition != DispositionExisting || res.Operation == nil { - t.Fatalf("retry disposition = %q, want existing", res.Disposition) - } + testassert.NoError(t, err) + testassert.Require(t, res.Disposition == DispositionExisting && res.Operation != nil, "retry disposition = %q, want existing", res.Disposition) } // TestAdmitDeadContextSkipsPreparation proves a dead combined context never @@ -1452,21 +1260,16 @@ func TestAdmitDeadContextSkipsPreparation(t *testing.T) { stub := newPrepareStub(validPrepared()) store := freshSessionStore(t) h, err := New(harnessCtx, Dependencies{Storage: store, Prepare: stub.prepare}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) cancelHarness() // the harness context dies after construction, before admission _, admitErr := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) - if !errors.Is(admitErr, context.Canceled) { - t.Fatalf("admission over a dead harness context = %v, want a context error", admitErr) - } - if got := stub.callCount(); got != 0 { - t.Fatalf("preparation calls = %d, want zero: a dead context must not invoke Prepare", got) + testassert.Require(t, errors.Is(admitErr, context.Canceled), "admission over a dead harness context = %v, want a context error", admitErr) + { + got := stub.callCount() + testassert.Equal(t, got, 0) } entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 { - t.Fatalf("dead-context admission left entries %v registers %v", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1, "dead-context admission left entries %v registers %v", entries, regs) } // foreignCorruptRevision bumps one Session register to a new valid revision and @@ -1501,36 +1304,39 @@ func TestRefreshReturnsDiscoveredCorruption(t *testing.T) { t.Run("admission", func(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) - if _, err := h.ReadSessionHeader(context.Background(), testSessionID); err != nil { // materialize revision 1 - t.Fatalf("ReadSession: %v", err) + // materialize revision 1 + { + _, err := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := store.Transact(context.Background(), func(tx Transaction) error { return foreignCorruptRevision(tx, testSessionID) }); err != nil { - t.Fatalf("foreign corrupt revision: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { return foreignCorruptRevision(tx, testSessionID) }) + testassert.NoError(t, err) } _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, Mode: MessageModeRegular}) var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("admission over a corrupt foreign revision = %v, want the discovered CorruptionError", err) - } - if _, rerr := h.ReadSessionHeader(context.Background(), testSessionID); !errors.As(rerr, &corrupt) { - t.Fatalf("subsequent read = %v, want the marked CorruptionError", rerr) + testassert.Require(t, errors.As(err, &corrupt), "admission over a corrupt foreign revision = %v, want the discovered CorruptionError", err) + { + _, rerr := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.Require(t, errors.As(rerr, &corrupt), "subsequent read = %v, want the marked CorruptionError", rerr) } }) t.Run("agent-type change", func(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, nil) - if _, err := h.ReadSessionHeader(context.Background(), testSessionID); err != nil { // materialize revision 1 - t.Fatalf("ReadSession: %v", err) + // materialize revision 1 + { + _, err := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := store.Transact(context.Background(), func(tx Transaction) error { return foreignCorruptRevision(tx, testSessionID) }); err != nil { - t.Fatalf("foreign corrupt revision: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { return foreignCorruptRevision(tx, testSessionID) }) + testassert.NoError(t, err) } _, err := h.ChangeAgentType(context.Background(), testSessionID, "reviewer") var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("agent-type change over a corrupt foreign revision = %v, want the discovered CorruptionError", err) - } + testassert.Require(t, errors.As(err, &corrupt), "agent-type change over a corrupt foreign revision = %v, want the discovered CorruptionError", err) }) } @@ -1545,12 +1351,8 @@ func TestOwnCaptureDropsZeroLengthBacking(t *testing.T) { mustAdmit(t, h, testSessionID, testOpID, admissionContent("x")) _ = append(tools, testToolDefinition()) // the caller reuses its backing after admission rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.Admission.Execution.Tools != nil { - t.Fatalf("admitted capture retained the caller's zero-length backing: %+v", rec.Admission.Execution.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, rec.Admission.Execution.Tools == nil, "admitted capture retained the caller's zero-length backing: %+v", rec.Admission.Execution.Tools) } // TestOwnCaptureOwnsCapabilitySelection proves the admitted capture keeps the @@ -1568,12 +1370,8 @@ func TestOwnCaptureOwnsCapabilitySelection(t *testing.T) { mustAdmit(t, h, testSessionID, testOpID, admissionContent("x")) _ = append(capabilities, "ghost") // the caller reuses its backing after admission rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.Admission.Execution.Capabilities != nil { - t.Fatalf("admitted capture retained the caller's zero-length capability backing: %+v", rec.Admission.Execution.Capabilities) - } + testassert.NoError(t, err) + testassert.Require(t, rec.Admission.Execution.Capabilities == nil, "admitted capture retained the caller's zero-length capability backing: %+v", rec.Admission.Execution.Capabilities) }) t.Run("caller mutation after admission", func(t *testing.T) { @@ -1584,12 +1382,8 @@ func TestOwnCaptureOwnsCapabilitySelection(t *testing.T) { mustAdmit(t, h, testSessionID, testOpID, admissionContent("x")) capabilities[0] = "tampered" rec, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if len(rec.Admission.Execution.Capabilities) != 1 || rec.Admission.Execution.Capabilities[0] != "cap-a" { - t.Fatalf("admitted capture aliases the caller's capability selection: %+v", rec.Admission.Execution.Capabilities) - } + testassert.NoError(t, err) + testassert.Require(t, len(rec.Admission.Execution.Capabilities) == 1 && rec.Admission.Execution.Capabilities[0] == "cap-a", "admitted capture aliases the caller's capability selection: %+v", rec.Admission.Execution.Capabilities) }) } @@ -1601,19 +1395,11 @@ func TestAdmittedOperationCarriesRegisterRevision(t *testing.T) { h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) rec, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) - if disposition != DispositionAdmitted { - t.Fatalf("disposition = %q, want admitted", disposition) - } - if rec.Revision != 1 { - t.Fatalf("admitted operation revision = %d, want the register revision assigned at insert", rec.Revision) - } + testassert.Equal(t, disposition, DispositionAdmitted) + testassert.Equal(t, rec.Revision, 1) viewed, err := h.ReadOperation(context.Background(), testSessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if viewed.Revision != 1 { - t.Fatalf("coordinator-view operation revision = %d, want the register revision assigned at insert", viewed.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, viewed.Revision, 1) } // corruptGateStore wraps one Storage and parks its first ReadRegisters call @@ -1669,7 +1455,7 @@ func (s *corruptGateStore) readCount() int { func TestConcurrentMaterializationSticksDiscoveredCorruption(t *testing.T) { store := freshSessionStore(t) validRaw, err := encodeSessionRegister(validSessionRecord()) - mustEncode(t, err) + testassert.NoError(t, err) gate := &corruptGateStore{ Storage: store, entered: make(chan struct{}), @@ -1701,8 +1487,9 @@ func TestConcurrentMaterializationSticksDiscoveredCorruption(t *testing.T) { // The concurrent acquirer never started a second load: it waited on the // stable cell and observed the same load's outcome. wantCorruption(t, <-sharedErr) - if got := gate.readCount(); got != 1 { - t.Fatalf("register reads for two concurrent first acquisitions = %d, want the one shared load", got) + { + got := gate.readCount() + testassert.Equal(t, got, 1) } wantCorruption(t, func() error { _, err := h.ReadSessionHeader(context.Background(), testSessionID) @@ -1714,10 +1501,9 @@ func TestConcurrentMaterializationSticksDiscoveredCorruption(t *testing.T) { }()) sibling, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession with a corrupt sibling: %v", err) - } - if _, err := h.ReadSessionHeader(context.Background(), sibling.Identity.SessionID); err != nil { - t.Fatalf("valid sibling session unreadable: %v", err) + testassert.NoError(t, err) + { + _, err := h.ReadSessionHeader(context.Background(), sibling.Identity.SessionID) + testassert.NoError(t, err) } } diff --git a/harness/agent_type_test.go b/harness/agent_type_test.go index 2afdf761..f8707cc4 100644 --- a/harness/agent_type_test.go +++ b/harness/agent_type_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -37,39 +38,26 @@ func TestResolveAgentTypeSelectsExactName(t *testing.T) { available := testAgentTypes() selected, err := ResolveAgentType("primary", available) - if err != nil { - t.Fatalf("ResolveAgentType(primary): %v", err) + testassert.NoError(t, err) + testassert.Require(t, selected.Name == "primary" && selected.Model == available[0].Model && selected.SystemPrompt == "full" && selected.Prompt == "primary prompt", "selected = %+v, want the primary fields unchanged", selected) + { + want := []string{"read_file", "write_file", "read_file"} + testassert.Require(t, len(selected.Tools) == 3 && selected.Tools[0] == want[0] && selected.Tools[1] == want[1] && selected.Tools[2] == want[2], "tools = %q, want loaded order with duplicates retained", selected.Tools) } - if selected.Name != "primary" || selected.Model != available[0].Model || - selected.SystemPrompt != "full" || selected.Prompt != "primary prompt" { - t.Fatalf("selected = %+v, want the primary fields unchanged", selected) - } - if want := []string{"read_file", "write_file", "read_file"}; len(selected.Tools) != 3 || - selected.Tools[0] != want[0] || selected.Tools[1] != want[1] || selected.Tools[2] != want[2] { - t.Fatalf("tools = %q, want loaded order with duplicates retained", selected.Tools) - } - if want := []string{"cap-b", "cap-a"}; len(selected.Capabilities) != 2 || - selected.Capabilities[0] != want[0] || selected.Capabilities[1] != want[1] { - t.Fatalf("capabilities = %q, want selected order", selected.Capabilities) + { + want := []string{"cap-b", "cap-a"} + testassert.Require(t, len(selected.Capabilities) == 2 && selected.Capabilities[0] == want[0] && selected.Capabilities[1] == want[1], "capabilities = %q, want selected order", selected.Capabilities) } other, err := ResolveAgentType("explore", available) - if err != nil { - t.Fatalf("ResolveAgentType(explore): %v", err) - } - if other.Model != (model.ModelRef{}) || other.Capabilities != nil || other.Readonly || other.WriteDir != "" { - t.Fatalf("two Agent types share selection state: %+v", other) - } + testassert.NoError(t, err) + testassert.Require(t, other.Model == (model.ModelRef{}) && other.Capabilities == nil && !other.Readonly && other.WriteDir == "", "two Agent types share selection state: %+v", other) selected.Tools[0] = "tampered" selected.Capabilities[0] = "tampered" again, err := ResolveAgentType("primary", available) - if err != nil { - t.Fatalf("re-resolve: %v", err) - } - if again.Tools[0] != "read_file" || again.Capabilities[0] != "cap-b" { - t.Fatalf("selection aliases the supplied definitions: %+v", again) - } + testassert.NoError(t, err) + testassert.Require(t, again.Tools[0] == "read_file" && again.Capabilities[0] == "cap-b", "selection aliases the supplied definitions: %+v", again) } // TestResolveAgentTypeRejections proves the invalid-input matrix: an empty or @@ -110,12 +98,8 @@ func TestResolveAgentTypeRejections(t *testing.T) { got, err = ResolveAgentType("x", row.available) } } - if !errors.Is(err, ErrInvalid) { - t.Fatalf("%s: err = %v, want ErrInvalid", row.name, err) - } - if got.Name != "" || got.Model != (model.ModelRef{}) || got.SystemPrompt != "" || got.Prompt != "" || got.Tools != nil || got.Capabilities != nil { - t.Fatalf("%s: returned %+v alongside the error, want the zero value", row.name, got) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "%s: err = %v, want ErrInvalid", row.name, err) + testassert.Require(t, got.Name == "" && got.Model == (model.ModelRef{}) && got.SystemPrompt == "" && got.Prompt == "" && got.Tools == nil && got.Capabilities == nil, "%s: returned %+v alongside the error, want the zero value", row.name, got) } } @@ -126,13 +110,6 @@ func TestResolveAgentTypeRejections(t *testing.T) { func TestResolveAgentTypeAcceptsEmptySelection(t *testing.T) { available := []AgentType{{Name: "bare", SystemPrompt: "none", Tools: []string{"t", "t"}}} selected, err := ResolveAgentType("bare", available) - if err != nil { - t.Fatalf("ResolveAgentType(bare): %v", err) - } - if !selected.Model.IsZero() || selected.Prompt != "" || selected.Capabilities != nil { - t.Fatalf("selected = %+v", selected) - } - if len(selected.Tools) != 2 { - t.Fatalf("tools = %q, want duplicates retained", selected.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, selected.Model.IsZero() && selected.Prompt == "" && selected.Capabilities == nil && len(selected.Tools) == 2, "selected = %+v; tools = %q, want duplicates retained", selected, selected.Tools) } diff --git a/harness/attempt_public_table_test.go b/harness/attempt_public_table_test.go index 5ee6901e..d4785fa2 100644 --- a/harness/attempt_public_table_test.go +++ b/harness/attempt_public_table_test.go @@ -25,6 +25,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -56,8 +57,9 @@ func newAttemptTableFixture(t *testing.T, store harness.Storage, conversationFn, } t.Cleanup(func() { cancel() - if err := h.Wait(context.Background()); err != nil { - t.Errorf("harness retirement: %v", err) + { + err := h.Wait(context.Background()) + testassert.Check(t, err == nil, "harness retirement: %v", err) } }) return &attemptTableFixture{h: h} @@ -143,9 +145,7 @@ func attemptTableStatusServer(t *testing.T, status int) (*httptest.Server, *atom func attemptTableTransport(t *testing.T, srv *httptest.Server, apiKey string) func(context.Context, model.Request) (model.Stream, error) { t.Helper() tr, err := model.NewTransport(model.ResolvedTransport{Model: publicModelRef, BaseURL: srv.URL, APIKey: apiKey}) - if err != nil { - t.Fatalf("transport construction failed for a valid resolved input: %v", err) - } + testassert.NoError(t, err) return func(ctx context.Context, req model.Request) (model.Stream, error) { stream, _, err := tr.Stream(ctx, req, nil) return stream, err @@ -157,9 +157,7 @@ func attemptTableTransport(t *testing.T, srv *httptest.Server, apiKey string) fu func attemptTableRunConversation(t *testing.T, f *attemptTableFixture, session, operation string) harness.OperationRecord { t.Helper() res, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello") - if err != nil || res.Disposition != harness.DispositionAdmitted { - t.Fatalf("submit = %+v err %v, want admitted", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted, "submit = %+v err %v, want admitted", res, err) return awaitTerminal(t, f.h, session, operation) } @@ -167,8 +165,9 @@ func attemptTableRunConversation(t *testing.T, f *attemptTableFixture, session, // carries compactable history and is idle again. func attemptTableSeedHistory(t *testing.T, f *attemptTableFixture, session string) { t.Helper() - if rec := attemptTableRunConversation(t, f, session, "op-seed"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("seed turn state = %+v, want success", rec.State) + { + rec := attemptTableRunConversation(t, f, session, "op-seed") + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "seed turn state = %+v, want success", rec.State) } } @@ -184,12 +183,7 @@ func attemptTableRunCompact(t *testing.T, f *attemptTableFixture, session, opera // detail. func attemptTableRequireTerminal(t *testing.T, rec harness.OperationRecord, want harness.OperationState, detailContains string) { t.Helper() - if rec.State.Status != want { - t.Fatalf("operation state = %+v, want %s", rec.State, want) - } - if rec.State.Terminal == nil || !strings.Contains(rec.State.Terminal.Detail, detailContains) { - t.Fatalf("operation terminal = %+v, want detail containing %q", rec.State.Terminal, detailContains) - } + testassert.Require(t, rec.State.Status == want && (rec.State.Terminal != nil && strings.Contains(rec.State.Terminal.Detail, detailContains)), "operation state = %+v, want %s; operation terminal = %+v, want detail containing %q", rec.State, want, rec.State.Terminal, detailContains) } // parkedAttempt returns the park function one physical callback calls before @@ -211,8 +205,9 @@ func parkedAttempt() (park func(context.Context), arrived <-chan struct{}) { func interruptParkedAttempt(t *testing.T, f *attemptTableFixture, session string, arrived <-chan struct{}) { t.Helper() <-arrived - if err := f.h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := f.h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } } @@ -230,9 +225,7 @@ func TestPublicSharedAttemptOrchestrationTable(t *testing.T) { {"memory", func(t *testing.T) harness.Storage { return storage.NewMemory() }}, {"sqlite", func(t *testing.T) harness.Storage { store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { store.Close() }) return store }}, @@ -272,23 +265,20 @@ var publicAttemptTableRows = []struct { rec := attemptTableRunConversation(t, f, session, "op-1") attemptTableRequireTerminal(t, rec, harness.OperationSuccess, "") kinds := entryKindsByOperation(t, store, session, "op-1") - if !slices.Contains(kinds, harness.EntryAssistant) { - t.Fatalf("operation entries = %v, want the accepted turn's assistant entry", kinds) - } + testassert.Require(t, slices.Contains(kinds, harness.EntryAssistant), "operation entries = %v, want the accepted turn's assistant entry", kinds) } else { attemptTableSeedHistory(t, f, session) rec := attemptTableRunCompact(t, f, session, "c-1") attemptTableRequireTerminal(t, rec, harness.OperationSuccess, "") kinds := entryKindsByOperation(t, store, session, "c-1") - if len(kinds) != 2 || kinds[0] != harness.EntryCompaction || kinds[1] != harness.EntryOperationSettlement { - t.Fatalf("compact operation entries = %v, want the compaction entry and its settlement", kinds) - } + testassert.Require(t, len(kinds) == 2 && kinds[0] == harness.EntryCompaction && kinds[1] == harness.EntryOperationSettlement, "compact operation entries = %v, want the compaction entry and its settlement", kinds) } if hits.Load() != 1 { t.Fatalf("physical requests = %d, want the one accepted attempt", hits.Load()) } - if got := auth.Load().(string); got != "Bearer table-key" { - t.Fatalf("Authorization header = %q, want the keyed transport's credential", got) + { + got := auth.Load().(string) + testassert.Equal(t, got, "Bearer table-key") } }, }, @@ -311,8 +301,9 @@ var publicAttemptTableRows = []struct { if hits.Load() != 1 { t.Fatalf("physical requests = %d, want the one accepted attempt", hits.Load()) } - if got := auth.Load().(string); got != "" { - t.Fatalf("Authorization header = %q, want none from the keyless transport", got) + { + got := auth.Load().(string) + testassert.Equal(t, got, "") } }, }, @@ -327,17 +318,16 @@ var publicAttemptTableRows = []struct { operation := "op-1" if conversation { res, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello") - if err != nil || res.Disposition != harness.DispositionAdmitted { - t.Fatalf("submit = %+v err %v, want admitted", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted, "submit = %+v err %v, want admitted", res, err) } else { attemptTableSeedHistory(t, f, session) operation = "c-1" compactWhenIdle(t, f.h, session, operation) } <-requests // the first attempt failed into the standard backoff - if err := f.h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := f.h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } rec := awaitTerminal(t, f.h, session, operation) attemptTableRequireTerminal(t, rec, harness.OperationInterruption, "agent interrupted") @@ -366,9 +356,7 @@ var publicAttemptTableRows = []struct { rec := attemptTableRunConversation(t, f, session, operation) attemptTableRequireTerminal(t, rec, harness.OperationFailure, "provider dropped") kinds := entryKindsByOperation(t, store, session, operation) - if slices.Contains(kinds, harness.EntryAssistant) || !slices.Contains(kinds, harness.EntryOperationSettlement) { - t.Fatalf("operation entries = %v, want the settlement entry and no assistant", kinds) - } + testassert.Require(t, !slices.Contains(kinds, harness.EntryAssistant) && slices.Contains(kinds, harness.EntryOperationSettlement), "operation entries = %v, want the settlement entry and no assistant", kinds) } else { attemptTableSeedHistory(t, f, session) operation = "c-1" @@ -423,8 +411,9 @@ var publicAttemptTableRows = []struct { session := createSession(t, f.h) operation := "op-1" if conversation { - if _, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } } else { attemptTableSeedHistory(t, f, session) @@ -450,8 +439,9 @@ var publicAttemptTableRows = []struct { session := createSession(t, f.h) operation := "op-1" if conversation { - if _, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } } else { attemptTableSeedHistory(t, f, session) @@ -464,9 +454,7 @@ var publicAttemptTableRows = []struct { // nearest sibling of failure-under-cancellation settles the // boundary failure, not the interruption. attemptTableRequireTerminal(t, rec, harness.OperationFailure, "neither exactly one stream nor one error") - if stream.closes != 1 { - t.Fatalf("supplied stream closes = %d, want exactly one before the boundary failure", stream.closes) - } + testassert.Equal(t, stream.closes, 1) }, }, { @@ -482,8 +470,9 @@ var publicAttemptTableRows = []struct { session := createSession(t, f.h) operation := "op-1" if conversation { - if _, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, operation, harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } } else { attemptTableSeedHistory(t, f, session) @@ -546,20 +535,14 @@ var publicAttemptTableRows = []struct { } intentMu.Lock() defer intentMu.Unlock() - if len(intentIDs) != 4 || intentIDs[0] == "" { - t.Fatalf("attempt-intent identities %v, want one durable intent observed by every attempt", intentIDs) - } + testassert.Require(t, len(intentIDs) == 4 && intentIDs[0] != "", "attempt-intent identities %v, want one durable intent observed by every attempt", intentIDs) for i, id := range intentIDs { - if id != intentIDs[0] { - t.Fatalf("attempt %d observed intent identity %q, want the one intent %q committed before the first attempt", i, id, intentIDs[0]) - } + testassert.Require(t, id == intentIDs[0], "attempt %d observed intent identity %q, want the one intent %q committed before the first attempt", i, id, intentIDs[0]) } if requests.Load() != 4 || policyCalls.Load() != 3 { t.Fatalf("requests = %d policy calls = %d, want three retries and the accepted fourth attempt", requests.Load(), policyCalls.Load()) } - if stream.closes != 1 { - t.Fatalf("accepted stream closes = %d, want exactly once", stream.closes) - } + testassert.Equal(t, stream.closes, 1) if kinds := entryKindsByOperation(t, store, session, operation); conversation && !slices.Contains(kinds, harness.EntryAssistant) { t.Fatalf("operation entries = %v, want the accepted turn's assistant entry", kinds) } else if !conversation && !slices.Contains(kinds, harness.EntryCompaction) { diff --git a/harness/background_test.go b/harness/background_test.go index e179896a..94587ed3 100644 --- a/harness/background_test.go +++ b/harness/background_test.go @@ -6,6 +6,8 @@ import ( "regexp" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // stubJobStopper satisfies the seam; Step 2 only proves a non-nil stopper is @@ -36,13 +38,9 @@ var completionIDShape = regexp.MustCompile(`^[0-9a-f]{32}$`) func TestDependenciesAcceptJobStopper(t *testing.T) { withJobs := newTestHarness(t, freshSessionStore(t), nil) withJobs.deps.Jobs = stubJobStopper{} - if withJobs.deps.Jobs == nil { - t.Fatalf("Jobs dependency rejected a stopper") - } + testassert.Require(t, withJobs.deps.Jobs != nil, "Jobs dependency rejected a stopper") withoutJobs := newTestHarness(t, freshSessionStore(t), nil) - if withoutJobs.deps.Jobs != nil { - t.Fatalf("Jobs dependency defaults to non-nil, want legal nil") - } + testassert.Require(t, withoutJobs.deps.Jobs == nil, "Jobs dependency defaults to non-nil, want legal nil") } // TestAdmitBackgroundMemberInitializesGroup proves group initialization on the @@ -52,40 +50,20 @@ func TestDependenciesAcceptJobStopper(t *testing.T) { func TestAdmitBackgroundMemberInitializesGroup(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) c := leaseCoordinator(t, h, testSessionID) - if c.bgState != bgOpen { - t.Fatalf("fresh coordinator bgState = %q, want open", c.bgState) - } + testassert.Equal(t, c.bgState, bgOpen) first, err := admitLocked(h, c, memberChild, "child-1", 0) - if err != nil { - t.Fatalf("first admission: %v", err) - } - if c.group == nil || len(c.group.members) != 1 { - t.Fatalf("first admission did not initialize the group: %+v", c.group) - } - if first.kind != memberChild || first.id != "child-1" { - t.Fatalf("member = %+v", first) - } - if !completionIDShape.MatchString(first.completionID) { - t.Fatalf("completion id %q is not 32 lowercase hex", first.completionID) - } - if first.claimed { - t.Fatalf("fresh member is claimed") - } + testassert.NoError(t, err) + testassert.Require(t, c.group != nil && len(c.group.members) == 1, "first admission did not initialize the group: %+v", c.group) + testassert.Require(t, first.kind == memberChild && first.id == "child-1", "member = %+v", first) + testassert.Require(t, completionIDShape.MatchString(first.completionID) && !first.claimed, "completion id %q is not 32 lowercase hex; fresh member is claimed", first.completionID) select { case <-first.done: t.Fatalf("fresh member done channel is closed") default: } second, err := admitLocked(h, c, memberJob, "job-1", 0) - if err != nil { - t.Fatalf("second admission: %v", err) - } - if len(c.group.members) != 2 { - t.Fatalf("group holds %d members, want 2", len(c.group.members)) - } - if c.group.members[first.completionID] != first || c.group.members[second.completionID] != second { - t.Fatalf("members are not keyed by completion id") - } + testassert.NoError(t, err) + testassert.Require(t, len(c.group.members) == 2 && (c.group.members[first.completionID] == first && c.group.members[second.completionID] == second), "got %v, want %v; members are not keyed by completion id", len(c.group.members), 2) } // TestCreateSessionStartsOpenLifecycle proves the CreateSession construction @@ -93,18 +71,12 @@ func TestAdmitBackgroundMemberInitializesGroup(t *testing.T) { func TestCreateSessionStartsOpenLifecycle(t *testing.T) { h := newTestHarness(t, emptyStore(t), nil) rec, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) h.mu.Lock() c := h.sessions[rec.Identity.SessionID] h.mu.Unlock() - if c == nil { - t.Fatalf("CreateSession did not cache a coordinator") - } - if c.bgState != bgOpen { - t.Fatalf("created coordinator bgState = %q, want open", c.bgState) - } + testassert.Require(t, c != nil, "CreateSession did not cache a coordinator") + testassert.Equal(t, c.bgState, bgOpen) } // TestAdmitBackgroundMemberCapRejection proves the child concurrency cap: with @@ -114,21 +86,16 @@ func TestAdmitBackgroundMemberCapRejection(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) c := leaseCoordinator(t, h, testSessionID) for i := 0; i < 2; i++ { - if _, err := admitLocked(h, c, memberChild, "child", 2); err != nil { - t.Fatalf("admission %d: %v", i+1, err) + { + _, err := admitLocked(h, c, memberChild, "child", 2) + testassert.Require(t, err == nil, "admission %d: %v", i+1, err) } } _, err := admitLocked(h, c, memberChild, "child", 2) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("third admission = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "third admission = %v, want ErrInvalid", err) want := "background group is full (2/2). Wait for a background member to complete, then retry" - if err == nil || err.Error() != "harness: invalid storage request: "+want { - t.Fatalf("third admission error = %q, want %q", err, want) - } - if len(c.group.members) != 2 { - t.Fatalf("rejected admission created a member: %d members", len(c.group.members)) - } + testassert.Require(t, err != nil && err.Error() == "harness: invalid storage request: "+want, "third admission error = %q, want %q", err, want) + testassert.Equal(t, len(c.group.members), 2) } // TestAdmitBackgroundMemberClosedAndArchivedRejection proves the lifecycle @@ -144,12 +111,8 @@ func TestAdmitBackgroundMemberClosedAndArchivedRejection(t *testing.T) { for _, kind := range []memberKind{memberChild, memberJob} { _, err := admitLocked(h, c, kind, "member", 0) want := invalidInput("session %q is archived; admission requires an open Session", testSessionID) - if err == nil || err.Error() != want.Error() { - t.Fatalf("%s admission on archived session = %v, want %v", kind, err, want) - } - if c.group != nil { - t.Fatalf("rejected admission created a group") - } + testassert.Require(t, err != nil && err.Error() == want.Error(), "%s admission on archived session = %v, want %v", kind, err, want) + testassert.Require(t, c.group == nil, "rejected admission created a group") } h2 := newTestHarness(t, freshSessionStore(t), nil) @@ -159,12 +122,8 @@ func TestAdmitBackgroundMemberClosedAndArchivedRejection(t *testing.T) { c2.bgState = state c2.mu.Unlock() _, err := admitLocked(h2, c2, memberChild, "child", 0) - if err == nil || err.Error() != "harness: invalid storage request: background group is closed or stopping" { - t.Fatalf("admission in state %q = %v, want the closed-or-stopping rejection", state, err) - } - if c2.group != nil { - t.Fatalf("rejected admission created a group") - } + testassert.Require(t, err != nil && err.Error() == "harness: invalid storage request: background group is closed or stopping", "admission in state %q = %v, want the closed-or-stopping rejection", state, err) + testassert.Require(t, c2.group == nil, "rejected admission created a group") } } @@ -179,20 +138,14 @@ func TestAdmitBackgroundMemberAfterHarnessCancellation(t *testing.T) { return PreparedExecution{}, errors.New("no preparation configured for this test") }, }) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) cancel() c := leaseCoordinator(t, h, testSessionID) c.mu.Lock() _, admitErr := h.admitBackgroundMember(c, memberChild, "child", 0) c.mu.Unlock() - if !errors.Is(admitErr, context.Canceled) || admitErr.Error() != "context canceled" { - t.Fatalf("admission after cancellation = %v, want the raw context.Canceled", admitErr) - } - if c.group != nil { - t.Fatalf("rejected admission created a member") - } + testassert.Require(t, errors.Is(admitErr, context.Canceled) && admitErr.Error() == "context canceled", "admission after cancellation = %v, want the raw context.Canceled", admitErr) + testassert.Require(t, c.group == nil, "rejected admission created a member") } // TestClaimBackgroundMember proves the first-writer claim: the first claim @@ -201,28 +154,22 @@ func TestAdmitBackgroundMemberAfterHarnessCancellation(t *testing.T) { func TestClaimBackgroundMember(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) c := leaseCoordinator(t, h, testSessionID) - if m, ok := claimLocked(h, c, "absent"); m != nil || ok { - t.Fatalf("unknown claim = (%v, %v), want (nil, false)", m, ok) + { + m, ok := claimLocked(h, c, "absent") + testassert.Require(t, m == nil && !ok, "unknown claim = (%v, %v), want (nil, false)", m, ok) } m, err := admitLocked(h, c, memberJob, "job-1", 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) got, ok := claimLocked(h, c, m.completionID) - if !ok || got != m { - t.Fatalf("first claim = (%v, %v), want the member", got, ok) - } - if !m.claimed { - t.Fatalf("claimed member is not marked claimed") - } + testassert.Require(t, ok && got == m, "first claim = (%v, %v), want the member", got, ok) + testassert.Require(t, m.claimed, "claimed member is not marked claimed") c.mu.Lock() _, present := c.group.members[m.completionID] c.mu.Unlock() - if !present { - t.Fatalf("claimed member left the group before finishing") - } - if got, ok := claimLocked(h, c, m.completionID); got != nil || ok { - t.Fatalf("second claim = (%v, %v), want (nil, false)", got, ok) + testassert.Require(t, present, "claimed member left the group before finishing") + { + got, ok := claimLocked(h, c, m.completionID) + testassert.Require(t, got == nil && !ok, "second claim = (%v, %v), want (nil, false)", got, ok) } } @@ -232,9 +179,7 @@ func TestFinishBackgroundMember(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) c := leaseCoordinator(t, h, testSessionID) m, err := admitLocked(h, c, memberChild, "child-1", 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) h.finishBackgroundMember(c, m) select { case <-m.done: @@ -245,17 +190,11 @@ func TestFinishBackgroundMember(t *testing.T) { _, present := c.group.members[m.completionID] empty := len(c.group.members) == 0 c.mu.Unlock() - if present { - t.Fatalf("finished member is still in the group") - } - if !empty { - t.Fatalf("group is not empty after finish") - } + testassert.Require(t, !present, "finished member is still in the group") + testassert.Require(t, empty, "group is not empty after finish") h.finishBackgroundMember(c, m) // must not panic or double-close c.mu.Lock() _, present = c.group.members[m.completionID] c.mu.Unlock() - if present { - t.Fatalf("no-op finish re-inserted the member") - } + testassert.Require(t, !present, "no-op finish re-inserted the member") } diff --git a/harness/codec_test.go b/harness/codec_test.go index 3450fcc0..47a2766e 100644 --- a/harness/codec_test.go +++ b/harness/codec_test.go @@ -7,10 +7,12 @@ import ( "errors" "fmt" "math" + "reflect" "strings" "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -36,10 +38,8 @@ func testToolDefinition() model.ToolDefinition { func testCompactCapture() CompactCapture { return CompactCapture{ - Model: model.ModelRef{Provider: "cprov", Model: "compact-x"}, - ContextWindow: 2048, - OutputReserve: 1024, - SystemPrompt: "summarize", + Model: model.ModelRef{Provider: "cprov", Model: "compact-x"}, + SystemPrompt: "summarize", } } @@ -206,9 +206,7 @@ func validHookResultEntry(status hookResultStatus) hookResultEntry { func wireObject(t *testing.T, raw json.RawMessage) map[string]json.RawMessage { t.Helper() obj, err := decodePayloadObject(raw) - if err != nil { - t.Fatalf("fixture payload is not a strict object: %v", err) - } + testassert.NoError(t, err) return obj } @@ -356,17 +354,17 @@ func TestEntryPayloadRoundTrip(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { raw, err := tc.encode() - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) env := tc.env env.Payload = raw - if err := tc.decode(env); err != nil { - t.Fatalf("decode: %v", err) + { + err := tc.decode(env) + testassert.NoError(t, err) } // A second decode of the same bytes stays stable. - if err := tc.decode(env); err != nil { - t.Fatalf("second decode: %v", err) + { + err := tc.decode(env) + testassert.NoError(t, err) } }) } @@ -450,9 +448,7 @@ func TestEntryPayloadRejectsInvalidWire(t *testing.T) { for _, kind := range kinds { t.Run(kind.name, func(t *testing.T) { raw, err := kind.payload() - if err != nil { - t.Fatalf("encode valid: %v", err) - } + testassert.NoError(t, err) base := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Sequence: 1, CommittedAt: testTime} switch kind.name { case "operation_settlement": @@ -477,8 +473,9 @@ func TestEntryPayloadRejectsInvalidWire(t *testing.T) { t.Run(m.name, func(t *testing.T) { env := base env.Payload = m.payload - if err := kind.decode(env); err == nil { - t.Fatalf("expected rejection") + { + err := kind.decode(env) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -492,12 +489,11 @@ func TestEntryPayloadRejectsInvalidWire(t *testing.T) { func TestEntryPayloadOperationlessRules(t *testing.T) { env := Entry{SessionID: testSessionID, ID: testEntryID, Kind: EntryAssistant, Sequence: 1, CommittedAt: testTime} raw, err := encodeAssistantEntry(validAssistantEntry("")) - if err != nil { - t.Fatalf("encode operationless assistant: %v", err) - } + testassert.NoError(t, err) env.Payload = raw - if _, err := decodeAssistantEntry(env); err != nil { - t.Fatalf("operationless assistant without usage must decode: %v", err) + { + _, err := decodeAssistantEntry(env) + testassert.NoError(t, err) } withUsage := validAssistantEntry("") @@ -509,8 +505,9 @@ func TestEntryPayloadOperationlessRules(t *testing.T) { } forced := setKey(raw, "usage", json.RawMessage(`{"input_tokens":1,"cached_input_tokens":0,"output_tokens":0}`)) env.Payload = forced - if _, err := decodeAssistantEntry(env); err == nil { - t.Fatalf("operationless assistant with stored usage must be rejected") + { + _, err := decodeAssistantEntry(env) + testassert.Require(t, err != nil, "operationless assistant with stored usage must be rejected") } } @@ -521,52 +518,58 @@ func TestEntryPayloadOperationlessRules(t *testing.T) { // Operation identity rejects. func TestHookResultStatusRules(t *testing.T) { t.Run("success rules", func(t *testing.T) { - if _, err := encodeHookResultEntry(validHookResultEntry(hookSucceeded)); err != nil { - t.Fatalf("encode valid success: %v", err) + { + _, err := encodeHookResultEntry(validHookResultEntry(hookSucceeded)) + testassert.NoError(t, err) } nonObject := validHookResultEntry(hookSucceeded) nonObject.Arguments = json.RawMessage(`[1,2]`) - if _, err := encodeHookResultEntry(nonObject); err == nil { - t.Fatalf("array arguments must reject") + { + _, err := encodeHookResultEntry(nonObject) + testassert.Require(t, err != nil, "array arguments must reject") } nullArguments := validHookResultEntry(hookSucceeded) nullArguments.Arguments = json.RawMessage(`null`) - if _, err := encodeHookResultEntry(nullArguments); err == nil { - t.Fatalf("null arguments must reject") + { + _, err := encodeHookResultEntry(nullArguments) + testassert.Require(t, err != nil, "null arguments must reject") } withError := validHookResultEntry(hookSucceeded) withError.Error = "boom" - if _, err := encodeHookResultEntry(withError); err == nil { - t.Fatalf("success with an error must reject") + { + _, err := encodeHookResultEntry(withError) + testassert.Require(t, err != nil, "success with an error must reject") } }) t.Run("failure and interruption rules", func(t *testing.T) { for _, status := range []hookResultStatus{hookFailed, hookInterrupted} { - if _, err := encodeHookResultEntry(validHookResultEntry(status)); err != nil { - t.Fatalf("encode valid %s: %v", status, err) + { + _, err := encodeHookResultEntry(validHookResultEntry(status)) + testassert.Require(t, err == nil, "encode valid %s: %v", status, err) } empty := validHookResultEntry(status) empty.Error = "" - if _, err := encodeHookResultEntry(empty); err == nil { - t.Fatalf("%s without an error must reject", status) + { + _, err := encodeHookResultEntry(empty) + testassert.Require(t, err != nil, "%s without an error must reject", status) } withArguments := validHookResultEntry(status) withArguments.Arguments = json.RawMessage(`{}`) - if _, err := encodeHookResultEntry(withArguments); err == nil { - t.Fatalf("%s with arguments must reject", status) + { + _, err := encodeHookResultEntry(withArguments) + testassert.Require(t, err != nil, "%s with arguments must reject", status) } } }) t.Run("unknown status", func(t *testing.T) { - if _, err := encodeHookResultEntry(validHookResultEntry("ok")); err == nil { - t.Fatalf("unknown status must reject") + { + _, err := encodeHookResultEntry(validHookResultEntry("ok")) + testassert.Require(t, err != nil, "unknown status must reject") } }) t.Run("decode-side rejections", func(t *testing.T) { raw, err := encodeHookResultEntry(validHookResultEntry(hookSucceeded)) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) for _, mutation := range []struct { name string payload json.RawMessage @@ -578,16 +581,15 @@ func TestHookResultStatusRules(t *testing.T) { {"arguments swapped onto failure", func() json.RawMessage { failed := validHookResultEntry(hookFailed) failedRaw, err := encodeHookResultEntry(failed) - if err != nil { - t.Fatalf("encode failed: %v", err) - } + testassert.NoError(t, err) return setKey(failedRaw, "arguments", json.RawMessage(`{}`)) }()}, } { t.Run(mutation.name, func(t *testing.T) { env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryHookResult, Sequence: 1, CommittedAt: testTime, Payload: mutation.payload} - if _, err := decodeHookResultEntry(env); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeHookResultEntry(env) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -611,8 +613,9 @@ func TestHookActiveEffectCodecRules(t *testing.T) { valid := op valid.State.ActiveEffect = &ActiveEffect{Kind: EffectHook, ResultEntryID: hexID(9), ToolCallID: "call-1", HookID: "cap.hook.one"} - if _, err := encodeOperationRegister(valid); err != nil { - t.Fatalf("encode valid hook effect: %v", err) + { + _, err := encodeOperationRegister(valid) + testassert.NoError(t, err) } for _, tc := range []struct { @@ -630,28 +633,26 @@ func TestHookActiveEffectCodecRules(t *testing.T) { t.Run(tc.name, func(t *testing.T) { bad := op bad.State.ActiveEffect = &tc.effect - if _, err := encodeOperationRegister(bad); err == nil || !strings.Contains(err.Error(), tc.wantErr) { - t.Fatalf("encode = %v, want %q", err, tc.wantErr) + { + _, err := encodeOperationRegister(bad) + testassert.Require(t, err != nil && strings.Contains(err.Error(), tc.wantErr), "encode = %v, want %q", err, tc.wantErr) } // decode-side: the persisted active_effect member with the same // shape rejects through decodeOperationRegister; a null hook_id // member (absence encodes by omission) rejects too. raw, err := encodeOperationRegister(op) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) effectJSON, merr := json.Marshal(tc.effect) - if merr != nil { - t.Fatalf("marshal effect: %v", merr) - } + testassert.NoError(t, merr) if tc.name == "hook effect without a hook id" { effectJSON = json.RawMessage(`{"kind":"hook","result_entry_id":"` + hexID(9) + `","tool_call_id":"call-1","hook_id":null}`) } stateObj := mustState(t, raw) patchedState := setKey(stateObj, "active_effect", effectJSON) full := setKey(raw, "state", patchedState) - if _, err := decodeOperationRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: full}); err == nil { - t.Fatalf("expected rejection of %s", tc.name) + { + _, err := decodeOperationRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: full}) + testassert.Require(t, err != nil, "expected rejection of %s", tc.name) } }) } @@ -662,51 +663,21 @@ func TestHookActiveEffectCodecRules(t *testing.T) { func TestRegisterPayloadRoundTrip(t *testing.T) { session := validSessionRecord() sessionRaw, err := encodeSessionRegister(session) - if err != nil { - t.Fatalf("encode session register: %v", err) - } + testassert.NoError(t, err) sessionReg := Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Revision: 7, Payload: sessionRaw} decoded, err := decodeSessionRegister(sessionReg) - if err != nil { - t.Fatalf("decode session register: %v", err) - } - if decoded.Revision != 7 || decoded.Identity != session.Identity { - t.Fatalf("decoded session register %+v does not round-trip", decoded) - } - if decoded.State.Lifecycle != session.State.Lifecycle || - decoded.State.CurrentAgentType != session.State.CurrentAgentType || - decoded.State.CurrentOperationID != session.State.CurrentOperationID || - decoded.State.ArchivedAt != nil || - !decoded.State.LastActivity.Equal(session.State.LastActivity) || - !usageTotalsEqual(decoded.State.Usage, session.State.Usage) { - t.Fatalf("decoded session state %+v does not round-trip", decoded.State) - } - if strings.Contains(string(sessionRaw), `"revision"`) { - t.Fatalf("revision leaked into the session payload: %s", sessionRaw) - } + testassert.NoError(t, err) + testassert.Require(t, decoded.Revision == 7 && decoded.Identity == session.Identity && (decoded.State.Lifecycle == session.State.Lifecycle && decoded.State.CurrentAgentType == session.State.CurrentAgentType && decoded.State.CurrentOperationID == session.State.CurrentOperationID && decoded.State.ArchivedAt == nil && decoded.State.LastActivity.Equal(session.State.LastActivity) && usageTotalsEqual(decoded.State.Usage, session.State.Usage)), "decoded session register %+v does not round-trip; decoded session state %+v does not round-trip", decoded, decoded.State) + testassert.Require(t, !strings.Contains(string(sessionRaw), `"revision"`), "revision leaked into the session payload: %s", sessionRaw) operation := validOperationRecord() opRaw, err := encodeOperationRegister(operation) - if err != nil { - t.Fatalf("encode operation register: %v", err) - } + testassert.NoError(t, err) opReg := Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Revision: 3, Payload: opRaw} decodedOp, err := decodeOperationRegister(opReg) - if err != nil { - t.Fatalf("decode operation register: %v", err) - } - if decodedOp.Revision != 3 || - decodedOp.Admission.SessionID != operation.Admission.SessionID || - decodedOp.Admission.OperationID != operation.Admission.OperationID || - decodedOp.Admission.AgentType != operation.Admission.AgentType || - !decodedOp.Admission.AdmittedAt.Equal(operation.Admission.AdmittedAt) || - decodedOp.State.Status != operation.State.Status || - len(decodedOp.State.PendingToolCalls) != len(operation.State.PendingToolCalls) { - t.Fatalf("decoded operation register does not round-trip: %+v", decodedOp) - } - if strings.Contains(string(opRaw), `"revision"`) { - t.Fatalf("revision leaked into the operation payload: %s", opRaw) - } + testassert.NoError(t, err) + testassert.Require(t, decodedOp.Revision == 3 && decodedOp.Admission.SessionID == operation.Admission.SessionID && decodedOp.Admission.OperationID == operation.Admission.OperationID && decodedOp.Admission.AgentType == operation.Admission.AgentType && decodedOp.Admission.AdmittedAt.Equal(operation.Admission.AdmittedAt) && decodedOp.State.Status == operation.State.Status && len(decodedOp.State.PendingToolCalls) == len(operation.State.PendingToolCalls), "decoded operation register does not round-trip: %+v", decodedOp) + testassert.Require(t, !strings.Contains(string(opRaw), `"revision"`), "revision leaked into the operation payload: %s", opRaw) } // TestOperationStateEncodingShape pins the state section's encoding shape @@ -717,49 +688,36 @@ func TestOperationStateEncodingShape(t *testing.T) { v := validOperationRecord() v.State.PendingToolCalls = nil // nil is a valid decoded shape for a quiet running Operation raw, err := encodeOperationRegister(v) - if err != nil { - t.Fatalf("encode: %v", err) - } - if strings.Contains(string(raw), `"pending_tool_calls":null`) { - t.Fatalf("pending_tool_calls encoded as null: %s", raw) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(raw), `"pending_tool_calls":null`), "pending_tool_calls encoded as null: %s", raw) obj := wireObject(t, raw) stateObj, err := objectMember(obj, "state", true) - if err != nil { - t.Fatalf("state member: %v", err) - } + testassert.NoError(t, err) pending, err := arrayMember(stateObj, "pending_tool_calls", true) - if err != nil { - t.Fatalf("pending_tool_calls: %v", err) - } - if len(pending) != 0 { - t.Fatalf("pending_tool_calls = %v, want the empty array", pending) - } + testassert.NoError(t, err) + testassert.Require(t, len(pending) == 0, "pending_tool_calls = %v, want the empty array", pending) for _, optional := range []string{"settled_at", "active_effect", "terminal"} { - if _, present := stateObj[optional]; present { - t.Fatalf("running state carries optional member %q", optional) + { + _, present := stateObj[optional] + testassert.Require(t, !present, "running state carries optional member %q", optional) } } - if _, err := decodeOperationRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: raw}); err != nil { - t.Fatalf("nil-pending state does not round-trip: %v", err) + { + _, err := decodeOperationRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: raw}) + testassert.NoError(t, err) } effect := validOperationRecord() effect.State.ActiveEffect = &ActiveEffect{Kind: EffectModel, ResultEntryID: hexID(3)} // model effect: no tool call id raw, err = encodeOperationRegister(effect) - if err != nil { - t.Fatalf("encode model effect: %v", err) - } + testassert.NoError(t, err) stateObj, err = objectMember(wireObject(t, raw), "state", true) - if err != nil { - t.Fatalf("state member: %v", err) - } + testassert.NoError(t, err) effectObj, err := objectMember(stateObj, "active_effect", true) - if err != nil { - t.Fatalf("active_effect: %v", err) - } - if _, present := effectObj["tool_call_id"]; present { - t.Fatalf("model active effect carries tool_call_id: %s", raw) + testassert.NoError(t, err) + { + _, present := effectObj["tool_call_id"] + testassert.Require(t, !present, "model active effect carries tool_call_id: %s", raw) } } @@ -767,13 +725,9 @@ func TestOperationStateEncodingShape(t *testing.T) { // exact keys, containers, and envelope-key identity agreement. func TestRegisterPayloadRejectsInvalidWire(t *testing.T) { sessionRaw, err := encodeSessionRegister(validSessionRecord()) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) opRaw, err := encodeOperationRegister(validOperationRecord()) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) sessionMutations := []struct { name string payload json.RawMessage @@ -788,8 +742,9 @@ func TestRegisterPayloadRejectsInvalidWire(t *testing.T) { for _, m := range sessionMutations { t.Run("session/"+m.name, func(t *testing.T) { reg := Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Payload: m.payload} - if _, err := decodeSessionRegister(reg); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeSessionRegister(reg) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -801,13 +756,14 @@ func TestRegisterPayloadRejectsInvalidWire(t *testing.T) { {"miscased key", renameKey(opRaw, "admission", "Admission")}, {"null admission", setKey(opRaw, "admission", json.RawMessage(`null`))}, {"wrong state container", setKey(opRaw, "state", json.RawMessage(`[]`))}, - {"operation mismatch", setKey(opRaw, "admission", json.RawMessage(`{"session_id":"`+testSessionID+`","operation_id":"ghost","request_kind":"message","admitted_entry":{"session_id":"`+testSessionID+`","entry_id":"`+hexID(1)+`"},"agent_type":"coder","execution":{"configuration_revision":"rev-1","model":{"provider":"prov","model":"gpt-x"},"context_window":4096,"output_reserve":2048,"system_prompt":"system","tools":[],"readonly":false,"write_dir":"","compact":{"model":{"provider":"cprov","model":"compact-x"},"context_window":2048,"output_reserve":1024,"system_prompt":"summarize"}},"admitted_at":"2026-01-02T03:04:05.123456789Z"}`))}, + {"operation mismatch", setKey(opRaw, "admission", json.RawMessage(`{"session_id":"`+testSessionID+`","operation_id":"ghost","request_kind":"message","admitted_entry":{"session_id":"`+testSessionID+`","entry_id":"`+hexID(1)+`"},"agent_type":"coder","execution":{"configuration_revision":"rev-1","model":{"provider":"prov","model":"gpt-x"},"context_window":4096,"output_reserve":2048,"system_prompt":"system","tools":[],"readonly":false,"write_dir":"","compact":{"model":{"provider":"cprov","model":"compact-x"},"system_prompt":"summarize"}},"admitted_at":"2026-01-02T03:04:05.123456789Z"}`))}, } for _, m := range opMutations { t.Run("operation/"+m.name, func(t *testing.T) { reg := Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: m.payload} - if _, err := decodeOperationRegister(reg); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeOperationRegister(reg) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -819,29 +775,21 @@ func TestRegisterPayloadRejectsInvalidWire(t *testing.T) { // object. func TestModelRefDurableShape(t *testing.T) { raw, err := encodeModelRef(model.ModelRef{Provider: "a/b", Model: "c/d"}) - if err != nil { - t.Fatalf("encode slashed ref: %v", err) - } - if string(raw) != `{"provider":"a/b","model":"c/d"}` { - t.Fatalf("durable model ref wire = %s, want the two-field object", raw) - } + testassert.NoError(t, err) + testassert.Require(t, string(raw) == `{"provider":"a/b","model":"c/d"}`, "durable model ref wire = %s, want the two-field object", raw) obj, err := decodePayloadObject(raw) - if err != nil { - t.Fatalf("decode wire: %v", err) - } + testassert.NoError(t, err) ref, err := decodeModelRef(obj) - if err != nil { - t.Fatalf("decode slashed ref: %v", err) - } - if ref.Provider != "a/b" || ref.Model != "c/d" { - t.Fatalf("slashed ref decoded as %q/%q", ref.Provider, ref.Model) - } + testassert.NoError(t, err) + testassert.Require(t, ref.Provider == "a/b" && ref.Model == "c/d", "slashed ref decoded as %q/%q", ref.Provider, ref.Model) - if _, err := encodeModelRef(model.ModelRef{}); !errors.Is(err, ErrInvalid) { - t.Fatalf("zero ref encode = %v, want ErrInvalid", err) + { + _, err := encodeModelRef(model.ModelRef{}) + testassert.Require(t, errors.Is(err, ErrInvalid), "zero ref encode = %v, want ErrInvalid", err) } - if _, err := encodeModelRef(model.ModelRef{Provider: "prov"}); !errors.Is(err, ErrInvalid) { - t.Fatalf("partial ref encode = %v, want ErrInvalid", err) + { + _, err := encodeModelRef(model.ModelRef{Provider: "prov"}) + testassert.Require(t, errors.Is(err, ErrInvalid), "partial ref encode = %v, want ErrInvalid", err) } for _, bad := range []json.RawMessage{ json.RawMessage(`"prov/gpt-x"`), // combined-string coercion @@ -850,8 +798,9 @@ func TestModelRefDurableShape(t *testing.T) { json.RawMessage(`null`), json.RawMessage(`{"provider":"","model":"m"}`), // empty fields } { - if _, err := decodeModelRefFromRaw(bad); err == nil { - t.Fatalf("model ref %s must be rejected", bad) + { + _, err := decodeModelRefFromRaw(bad) + testassert.Require(t, err != nil, "model ref %s must be rejected", bad) } } } @@ -875,21 +824,13 @@ func TestToolArgumentsRoundTripByteExact(t *testing.T) { entry := validAssistantEntry(testOpID) entry.ToolCalls = []toolCallRecord{call} rawWire, err := encodeAssistantEntry(entry) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryAssistant, Payload: rawWire} decoded, err := decodeAssistantEntry(env) - if err != nil { - t.Fatalf("decode: %v", err) - } + testassert.NoError(t, err) got, err := base64.StdEncoding.Strict().DecodeString(decoded.ToolCalls[0].ArgumentsBase64) - if err != nil { - t.Fatalf("stored arguments_base64 does not decode: %v", err) - } - if string(got) != string(raw) { - t.Fatalf("arguments round-tripped as %x, want %x", got, raw) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == string(raw), "arguments round-tripped as %x, want %x", got, raw) } // TestUsageAccounting proves checked signed usage addition: positive and @@ -897,20 +838,17 @@ func TestToolArgumentsRoundTripByteExact(t *testing.T) { // partial result, and totals stay unique and lexicographically sorted. func TestUsageAccounting(t *testing.T) { sum, err := addUsageCount(UsageCount{InputTokens: 5, OutputTokens: -2}, UsageCount{InputTokens: 7, CachedInputTokens: 3, OutputTokens: 2}) - if err != nil { - t.Fatalf("add: %v", err) - } - if sum != (UsageCount{InputTokens: 12, CachedInputTokens: 3, OutputTokens: 0}) { - t.Fatalf("signed sum = %+v", sum) - } + testassert.NoError(t, err) + testassert.Equal(t, sum, (UsageCount{InputTokens: 12, CachedInputTokens: 3, OutputTokens: 0})) maxed := UsageCount{InputTokens: math.MaxInt64} if out, err := addUsageCount(maxed, UsageCount{InputTokens: 1}); !errors.Is(err, ErrStorage) { t.Fatalf("overflow = (%+v, %v), want ErrStorage", out, err) } else if out != (UsageCount{}) { t.Fatalf("overflow published a partial result %+v", out) } - if out, err := addUsageCount(UsageCount{InputTokens: math.MinInt64}, UsageCount{InputTokens: -1}); !errors.Is(err, ErrStorage) { - t.Fatalf("negative overflow = (%+v, %v), want ErrStorage", out, err) + { + out, err := addUsageCount(UsageCount{InputTokens: math.MinInt64}, UsageCount{InputTokens: -1}) + testassert.Require(t, errors.Is(err, ErrStorage), "negative overflow = (%+v, %v), want ErrStorage", out, err) } a := UsageTotals{ByModel: []ModelUsage{ @@ -922,24 +860,19 @@ func TestUsageAccounting(t *testing.T) { {Model: model.ModelRef{Provider: "a", Model: "b"}, Usage: UsageCount{}}, }} merged, err := addUsageTotals(a, b) - if err != nil { - t.Fatalf("merge: %v", err) - } + testassert.NoError(t, err) want := []ModelUsage{ {Model: model.ModelRef{Provider: "a", Model: "b"}, Usage: UsageCount{}}, {Model: model.ModelRef{Provider: "a", Model: "x"}, Usage: UsageCount{InputTokens: 2}}, {Model: model.ModelRef{Provider: "z", Model: "m"}, Usage: UsageCount{InputTokens: 1, OutputTokens: 4}}, } - if len(merged.ByModel) != len(want) { - t.Fatalf("merged %d entries, want %d", len(merged.ByModel), len(want)) - } + testassert.Equal(t, len(merged.ByModel), len(want)) for i := range want { - if merged.ByModel[i] != want[i] { - t.Fatalf("merged[%d] = %+v, want %+v", i, merged.ByModel[i], want[i]) - } + testassert.Require(t, merged.ByModel[i] == want[i], "merged[%d] = %+v, want %+v", i, merged.ByModel[i], want[i]) } - if _, err := addUsageTotals(UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: maxed}}}, UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: UsageCount{InputTokens: 1}}}}); !errors.Is(err, ErrStorage) { - t.Fatalf("totals overflow = %v, want ErrStorage", err) + { + _, err := addUsageTotals(UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: maxed}}}, UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: UsageCount{InputTokens: 1}}}}) + testassert.Require(t, errors.Is(err, ErrStorage), "totals overflow = %v, want ErrStorage", err) } // Decoded totals reject duplicates and unsorted orders. @@ -953,8 +886,9 @@ func TestUsageAccounting(t *testing.T) { {Model: model.ModelRef{Provider: "a", Model: "x"}, Usage: UsageCount{}}, }}, } { - if err := validateUsageTotals(bad); err == nil { - t.Fatalf("totals %+v must be rejected", bad) + { + err := validateUsageTotals(bad) + testassert.Require(t, err != nil, "totals %+v must be rejected", bad) } } } @@ -977,17 +911,11 @@ func TestToolCallNormalizedArgumentsNullRejected(t *testing.T) { call.NormalizedArguments = json.RawMessage(`{"x":1}`) entry.ToolCalls = []toolCallRecord{call} raw, err := encodeAssistantEntry(entry) - if err != nil { - t.Fatalf("encode non-null normalized_arguments: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryAssistant, Payload: raw} decoded, err := decodeAssistantEntry(env) - if err != nil { - t.Fatalf("decode: %v", err) - } - if string(decoded.ToolCalls[0].NormalizedArguments) != `{"x":1}` { - t.Fatalf("normalized_arguments round-tripped as %s", decoded.ToolCalls[0].NormalizedArguments) - } + testassert.NoError(t, err) + testassert.Require(t, string(decoded.ToolCalls[0].NormalizedArguments) == `{"x":1}`, "normalized_arguments round-tripped as %s", decoded.ToolCalls[0].NormalizedArguments) } // TestToolCallNormalizedArgumentsObjectOnly proves the normalized_arguments @@ -1003,8 +931,9 @@ func TestToolCallNormalizedArgumentsObjectOnly(t *testing.T) { call := validToolCallRecord() call.NormalizedArguments = json.RawMessage(wrong) entry.ToolCalls = []toolCallRecord{call} - if _, err := encodeAssistantEntry(entry); !errors.Is(err, ErrInvalid) { - t.Fatalf("encode normalized_arguments %s = %v, want the ErrInvalid class", wrong, err) + { + _, err := encodeAssistantEntry(entry) + testassert.Require(t, errors.Is(err, ErrInvalid), "encode normalized_arguments %s = %v, want the ErrInvalid class", wrong, err) } // decode side: rewrite one valid encoded payload's member to the wrong @@ -1014,30 +943,27 @@ func TestToolCallNormalizedArgumentsObjectOnly(t *testing.T) { vcall.NormalizedArguments = json.RawMessage(`{"x":1}`) valid.ToolCalls = []toolCallRecord{vcall} raw, err := encodeAssistantEntry(valid) - if err != nil { - t.Fatalf("encode valid assistant: %v", err) - } + testassert.NoError(t, err) obj := map[string]json.RawMessage{} - if err := json.Unmarshal(raw, &obj); err != nil { - t.Fatalf("encoded payload is not an object: %v", err) + { + err := json.Unmarshal(raw, &obj) + testassert.NoError(t, err) } items := []map[string]json.RawMessage{} - if err := json.Unmarshal(obj["tool_calls"], &items); err != nil || len(items) != 1 { - t.Fatalf("encoded tool_calls = %s (%v)", obj["tool_calls"], err) + { + err := json.Unmarshal(obj["tool_calls"], &items) + testassert.Require(t, err == nil && len(items) == 1, "encoded tool_calls = %s (%v)", obj["tool_calls"], err) } items[0]["normalized_arguments"] = json.RawMessage(wrong) edited, err := json.Marshal(items) - if err != nil { - t.Fatalf("marshal edited tool_calls: %v", err) - } + testassert.NoError(t, err) obj["tool_calls"] = edited out, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal edited payload: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryAssistant, Payload: out} - if _, err := decodeAssistantEntry(env); err == nil { - t.Fatalf("decode normalized_arguments %s must fail", wrong) + { + _, err := decodeAssistantEntry(env) + testassert.Require(t, err != nil, "decode normalized_arguments %s must fail", wrong) } } @@ -1051,36 +977,26 @@ func TestToolResultMetadataRules(t *testing.T) { v := validToolResultEntry(testOpID) v.Metadata = valid raw, err := encodeToolResultEntry(v) - if err != nil { - t.Fatalf("encode metadata: %v", err) - } - if _, present := wireObject(t, raw)["metadata"]; !present { - t.Fatalf("encoded payload %s carries no metadata member", raw) + testassert.NoError(t, err) + { + _, present := wireObject(t, raw)["metadata"] + testassert.Require(t, present, "encoded payload %s carries no metadata member", raw) } env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryToolResult, Payload: raw} decoded, err := decodeToolResultEntry(env) - if err != nil { - t.Fatalf("decode metadata: %v", err) - } - if string(decoded.Metadata) != string(valid) { - t.Fatalf("metadata round-tripped as %s, want the stored bytes verbatim", decoded.Metadata) - } + testassert.NoError(t, err) + testassert.Require(t, string(decoded.Metadata) == string(valid), "metadata round-tripped as %s, want the stored bytes verbatim", decoded.Metadata) decoded.Metadata[0] = '[' // mutating the decoded copy must not touch stored state again, err := decodeToolResultEntry(env) - if err != nil { - t.Fatalf("second decode: %v", err) - } - if string(again.Metadata) != string(valid) { - t.Fatalf("metadata after mutating a decoded copy = %s, want the stored bytes", again.Metadata) - } + testassert.NoError(t, err) + testassert.Require(t, string(again.Metadata) == string(valid), "metadata after mutating a decoded copy = %s, want the stored bytes", again.Metadata) // Absence encodes by omission: no metadata member appears at all. bare, err := encodeToolResultEntry(validToolResultEntry(testOpID)) - if err != nil { - t.Fatalf("encode result without metadata: %v", err) - } - if _, present := wireObject(t, bare)["metadata"]; present { - t.Fatalf("metadata-less payload encodes the member: %s", bare) + testassert.NoError(t, err) + { + _, present := wireObject(t, bare)["metadata"] + testassert.Require(t, !present, "metadata-less payload encodes the member: %s", bare) } rejected := []struct { @@ -1096,8 +1012,9 @@ func TestToolResultMetadataRules(t *testing.T) { t.Run(tc.name, func(t *testing.T) { v := validToolResultEntry(testOpID) v.Metadata = tc.metadata - if _, err := encodeToolResultEntry(v); !errors.Is(err, ErrInvalid) { - t.Fatalf("encode error = %v, want the ErrInvalid class", err) + { + _, err := encodeToolResultEntry(v) + testassert.Require(t, errors.Is(err, ErrInvalid), "encode error = %v, want the ErrInvalid class", err) } }) } @@ -1106,8 +1023,9 @@ func TestToolResultMetadataRules(t *testing.T) { // null and oversized members are rejected, while a value exactly at the // bound decodes. rawValid := raw - if _, err := decodeToolResultEntry(Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryToolResult, Payload: rawValid}); err != nil { - t.Fatalf("re-decode: %v", err) + { + _, err := decodeToolResultEntry(Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryToolResult, Payload: rawValid}) + testassert.NoError(t, err) } for _, tc := range []struct { name string @@ -1121,14 +1039,10 @@ func TestToolResultMetadataRules(t *testing.T) { env.Payload = setKey(raw, "metadata", tc.metadata) _, err := decodeToolResultEntry(env) if tc.name == "at the bound" { - if err != nil { - t.Fatalf("metadata exactly at the bound rejected: %v", err) - } + testassert.NoError(t, err) continue } - if err == nil { - t.Fatalf("persisted %s metadata decoded, want rejection", tc.name) - } + testassert.Require(t, err != nil, "persisted %s metadata decoded, want rejection", tc.name) } } @@ -1142,16 +1056,10 @@ func TestOwnedToolMetadataEncodedForm(t *testing.T) { const want = `{"n":[1,1.0,1e0,9007199254740993],"s":"\u003c\u003e\u0026"}` raw := json.RawMessage(input) got := ownedToolMetadata(raw) - if string(got) != want { - t.Fatalf("owned metadata = %s, want the durable encoded bytes %s", got, want) - } - if string(raw) != input { - t.Fatalf("caller input changed to %s, want it unchanged", raw) - } + testassert.Require(t, string(got) == want, "owned metadata = %s, want the durable encoded bytes %s", got, want) + testassert.Require(t, string(raw) == input, "caller input changed to %s, want it unchanged", raw) raw[1] = 'x' // mutating the caller's buffer cannot alter the returned bytes - if string(got) != want { - t.Fatalf("owned metadata after mutating the caller = %s, want the independent encoded bytes %s", got, want) - } + testassert.Require(t, string(got) == want, "owned metadata after mutating the caller = %s, want the independent encoded bytes %s", got, want) } // TestCodecRejectsInvalidValues proves validate-before-encoding: every @@ -1389,12 +1297,7 @@ func TestCodecRejectsInvalidValues(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := tc.encode() - if err == nil { - t.Fatalf("expected rejection") - } - if !errors.Is(err, ErrInvalid) { - t.Fatalf("rejection = %v, want the ErrInvalid class", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrInvalid), "expected rejection; rejection = %v, want the ErrInvalid class", err) }) } } @@ -1407,32 +1310,22 @@ func TestCodecOwnedValues(t *testing.T) { entry.Extra = model.Extra{"k": json.RawMessage(`{"deep":[1]}`)} entry.ToolCalls = []toolCallRecord{validToolCallRecord()} raw, err := encodeAssistantEntry(entry) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryAssistant, Payload: raw} first, err := decodeAssistantEntry(env) - if err != nil { - t.Fatalf("decode: %v", err) - } + testassert.NoError(t, err) second, err := decodeAssistantEntry(env) - if err != nil { - t.Fatalf("decode: %v", err) - } + testassert.NoError(t, err) first.Extra["k"] = json.RawMessage(`"mutated"`) first.Content = append(first.Content, model.ContentPart{Kind: model.PartText, Text: "extra"}) first.ToolCalls[0].ID = "mutated" - if string(second.Extra["k"]) != `{"deep":[1]}` || len(second.Content) != 1 || second.ToolCalls[0].ID != "call-1" { - t.Fatalf("decoded values alias each other") - } + testassert.Require(t, string(second.Extra["k"]) == `{"deep":[1]}` && len(second.Content) == 1 && second.ToolCalls[0].ID == "call-1", "decoded values alias each other") // Mutating the caller's payload buffer after decode must never reach the // decoded owned values. env.Payload[0] = ' ' - if string(second.Extra["k"]) != `{"deep":[1]}` || second.Content[0].Text != "hi" { - t.Fatalf("decoded value aliased the caller payload buffer") - } + testassert.Require(t, string(second.Extra["k"]) == `{"deep":[1]}` && second.Content[0].Text == "hi", "decoded value aliased the caller payload buffer") } // TestAdoptedFormsAcrossInsertAndFork extends the result/fork ownership @@ -1500,42 +1393,20 @@ func TestAdoptedFormsAcrossInsertAndFork(t *testing.T) { } assertAssistantAdopted := func(t *testing.T, got *assistantEntry) { t.Helper() - if got == nil { - t.Fatal("adopted assistant entry is missing") - } - if got.Content[0].Text != wantReplacedText { - t.Fatalf("adopted text = %q, want invalid bytes replaced by U+FFFD with ordinary multi-byte text preserved (%q)", got.Content[0].Text, wantReplacedText) - } - if string(got.Content[0].Extra["raw"]) != verbatimExtra { - t.Fatalf("adopted raw Extra = %q, want it verbatim", got.Content[0].Extra["raw"]) - } - if string(got.Content[0].Extra["utf8"]) != wantInvalidRaw { - t.Fatalf("adopted raw UTF-8 Extra = %q, want the raw bytes verbatim", got.Content[0].Extra["utf8"]) - } - if string(got.Content[1].Extra["nested"]) != nestedExtra { - t.Fatalf("adopted nested content extra = %q, want the nested form unchanged", got.Content[1].Extra["nested"]) - } - if string(got.Content[1].Extra["lexemes"]) != lexemeExtra { - t.Fatalf("adopted numeric lexemes = %q, want 9007199254740993 and its siblings exact", got.Content[1].Extra["lexemes"]) - } - if string(got.Extra["msg"]) != nestedExtra { - t.Fatalf("adopted message extra = %q, want the nested form unchanged", got.Extra["msg"]) - } - if string(got.ToolCalls[0].Extra["nested"]) != nestedExtra { - t.Fatalf("adopted call extra = %q, want the nested form unchanged", got.ToolCalls[0].Extra["nested"]) - } - if string(got.ToolCalls[0].NormalizedArguments) != nestedArguments { - t.Fatalf("adopted normalized arguments = %q, want the nested form with exact lexemes unchanged", got.ToolCalls[0].NormalizedArguments) - } + testassert.Require(t, got != nil, "%v", "adopted assistant entry is missing") + testassert.Equal(t, got.Content[0].Text, wantReplacedText) + testassert.Require(t, string(got.Content[0].Extra["raw"]) == verbatimExtra, "adopted raw Extra = %q, want it verbatim", got.Content[0].Extra["raw"]) + testassert.Require(t, string(got.Content[0].Extra["utf8"]) == wantInvalidRaw, "adopted raw UTF-8 Extra = %q, want the raw bytes verbatim", got.Content[0].Extra["utf8"]) + testassert.Require(t, string(got.Content[1].Extra["nested"]) == nestedExtra, "adopted nested content extra = %q, want the nested form unchanged", got.Content[1].Extra["nested"]) + testassert.Require(t, string(got.Content[1].Extra["lexemes"]) == lexemeExtra, "adopted numeric lexemes = %q, want 9007199254740993 and its siblings exact", got.Content[1].Extra["lexemes"]) + testassert.Require(t, string(got.Extra["msg"]) == nestedExtra, "adopted message extra = %q, want the nested form unchanged", got.Extra["msg"]) + testassert.Require(t, string(got.ToolCalls[0].Extra["nested"]) == nestedExtra, "adopted call extra = %q, want the nested form unchanged", got.ToolCalls[0].Extra["nested"]) + testassert.Require(t, string(got.ToolCalls[0].NormalizedArguments) == nestedArguments, "adopted normalized arguments = %q, want the nested form with exact lexemes unchanged", got.ToolCalls[0].NormalizedArguments) } assertResultAdopted := func(t *testing.T, got *toolResultEntry) { t.Helper() - if got == nil { - t.Fatal("adopted tool result entry is missing") - } - if string(got.Metadata) != nestedMetadata { - t.Fatalf("adopted metadata = %q, want the nested form with exact lexemes unchanged", got.Metadata) - } + testassert.Require(t, got != nil, "%v", "adopted tool result entry is missing") + testassert.Require(t, string(got.Metadata) == nestedMetadata, "adopted metadata = %q, want the nested form with exact lexemes unchanged", got.Metadata) } t.Run("decode after insert", func(t *testing.T) { @@ -1558,9 +1429,7 @@ func TestAdoptedFormsAcrossInsertAndFork(t *testing.T) { adoptedResult, err = insertAndAdopt(tx, testSessionID, EntryDraft{SessionID: testSessionID, ID: hexID(3), OperationID: testOpID, Kind: EntryToolResult, Payload: resultPayload}) return err }) - if err != nil { - t.Fatalf("insert and adopt: %v", err) - } + testassert.NoError(t, err) clobberAssistant(assistant) clobber(result.Metadata) assertAssistantAdopted(t, adoptedAssistant.Assistant) @@ -1599,9 +1468,7 @@ func TestAdoptedFormsAcrossInsertAndFork(t *testing.T) { Content: []model.ContentPart{{Kind: model.PartText, Text: "continue"}}, }, nil, testCapture(), &commit) }) - if err != nil { - t.Fatalf("forkTransaction: %v", err) - } + testassert.NoError(t, err) clobberAssistant(assistant) clobber(result.Metadata) @@ -1617,9 +1484,7 @@ func TestAdoptedFormsAcrossInsertAndFork(t *testing.T) { assertResultAdopted(t, entry.ToolResult) } } - if copied != 2 { - t.Fatalf("fork copied %d of the interesting entries, want the assistant and the tool result", copied) - } + testassert.Equal(t, copied, 2) }) } @@ -1628,30 +1493,26 @@ func TestAdoptedFormsAcrossInsertAndFork(t *testing.T) { // than UTC rejected. func TestTimestampRules(t *testing.T) { stamped, err := encodeTime(testTime) - if err != nil { - t.Fatalf("encodeTime: %v", err) + testassert.NoError(t, err) + testassert.Equal(t, stamped, "2026-01-02T03:04:05.123456789Z") + { + _, err := encodeTime(time.Time{}) + testassert.Require(t, err != nil, "zero timestamp encode must fail") } - if stamped != "2026-01-02T03:04:05.123456789Z" { - t.Fatalf("encoded timestamp = %q", stamped) - } - if _, err := encodeTime(time.Time{}); err == nil { - t.Fatalf("zero timestamp encode must fail") - } - if _, err := encodeTime(time.Date(12000, 1, 2, 3, 4, 5, 0, time.UTC)); err == nil { - t.Fatalf("timestamp with year 12000 encode must fail") + { + _, err := encodeTime(time.Date(12000, 1, 2, 3, 4, 5, 0, time.UTC)) + testassert.Require(t, err != nil, "timestamp with year 12000 encode must fail") } decoded, err := decodeTime(stamped) - if err != nil { - t.Fatalf("decodeTime: %v", err) + testassert.NoError(t, err) + testassert.Require(t, decoded.UTC().Equal(testTime), "decoded timestamp = %v", decoded) + { + _, err := decodeTime("2026-01-02T03:04:05.123456789+02:00") + testassert.Require(t, err != nil, "non-UTC offset must be rejected") } - if !decoded.UTC().Equal(testTime) { - t.Fatalf("decoded timestamp = %v", decoded) - } - if _, err := decodeTime("2026-01-02T03:04:05.123456789+02:00"); err == nil { - t.Fatalf("non-UTC offset must be rejected") - } - if _, err := decodeTime("0001-01-01T00:00:00Z"); err == nil { - t.Fatalf("zero timestamp must be rejected") + { + _, err := decodeTime("0001-01-01T00:00:00Z") + testassert.Require(t, err != nil, "zero timestamp must be rejected") } } @@ -1659,19 +1520,23 @@ func TestTimestampRules(t *testing.T) { // IDs are exactly 32 lowercase hexadecimal characters; Operation and tool-call // identities are non-empty opaque strings. func TestIDShapes(t *testing.T) { - if err := validateHexID(hexID(1), "id"); err != nil { - t.Fatalf("valid hex id rejected: %v", err) + { + err := validateHexID(hexID(1), "id") + testassert.NoError(t, err) } for _, bad := range []string{"", strings.Repeat("A", 32), strings.Repeat("g", 32), hexID(1) + "0", hexID(1)[:31]} { - if err := validateHexID(bad, "id"); err == nil { - t.Fatalf("id %q must be rejected", bad) + { + err := validateHexID(bad, "id") + testassert.Require(t, err != nil, "id %q must be rejected", bad) } } - if err := validateOperationIdentity("", "operation id"); err == nil { - t.Fatalf("empty operation id must be rejected") + { + err := validateOperationIdentity("", "operation id") + testassert.Require(t, err != nil, "empty operation id must be rejected") } - if err := validateOperationIdentity("any opaque caller identity", "operation id"); err != nil { - t.Fatalf("opaque operation id rejected: %v", err) + { + err := validateOperationIdentity("any opaque caller identity", "operation id") + testassert.NoError(t, err) } } @@ -1686,63 +1551,53 @@ func TestExecutionCaptureCapabilities(t *testing.T) { v := testCapture() v.Capabilities = capabilities raw, err := encodeExecutionCapture(v) - if err != nil { - t.Fatalf("encodeExecutionCapture: %v", err) - } + testassert.NoError(t, err) return raw } for _, empty := range [][]string{nil, {}} { obj, err := decodePayloadObject(encode(t, empty)) - if err != nil { - t.Fatalf("decodePayloadObject: %v", err) - } - if _, present := obj["capabilities"]; present { - t.Fatalf("empty capability selection %#v leaked the capabilities member", empty) + testassert.NoError(t, err) + { + _, present := obj["capabilities"] + testassert.Require(t, !present, "empty capability selection %#v leaked the capabilities member", empty) } } ordered := []string{"retired-plugin.capability", "cap-mid", "cap-last"} obj, err := decodePayloadObject(encode(t, ordered)) - if err != nil { - t.Fatalf("decodePayloadObject: %v", err) - } - if got := string(obj["capabilities"]); got != `["retired-plugin.capability","cap-mid","cap-last"]` { - t.Fatalf("encoded capabilities member = %s", got) + testassert.NoError(t, err) + { + got := string(obj["capabilities"]) + testassert.Equal(t, got, `["retired-plugin.capability","cap-mid","cap-last"]`) } decoded, err := decodeExecutionCapture(obj) - if err != nil { - t.Fatalf("historical capture without plugin definitions must decode: %v", err) - } - if len(decoded.Capabilities) != 3 || decoded.Capabilities[0] != "retired-plugin.capability" || - decoded.Capabilities[1] != "cap-mid" || decoded.Capabilities[2] != "cap-last" { - t.Fatalf("decoded capabilities = %q, want the selected order", decoded.Capabilities) - } + testassert.NoError(t, err) + testassert.Require(t, len(decoded.Capabilities) == 3 && decoded.Capabilities[0] == "retired-plugin.capability" && decoded.Capabilities[1] == "cap-mid" && decoded.Capabilities[2] == "cap-last", "decoded capabilities = %q, want the selected order", decoded.Capabilities) decoded.Capabilities[0] = "tampered" again, err := decodeExecutionCapture(obj) - if err != nil || again.Capabilities[0] != "retired-plugin.capability" { - t.Fatalf("decoded capabilities alias the payload bytes: %q, %v", again.Capabilities, err) - } + testassert.Require(t, err == nil && again.Capabilities[0] == "retired-plugin.capability", "decoded capabilities alias the payload bytes: %q, %v", again.Capabilities, err) captureWith := func(capabilities string) map[string]json.RawMessage { members, err := decodePayloadObject(encode(t, nil)) - if err != nil { - t.Fatalf("decodePayloadObject: %v", err) - } + testassert.NoError(t, err) if capabilities != "" { members["capabilities"] = json.RawMessage(capabilities) } return members } - if got, err := decodeExecutionCapture(captureWith("")); err != nil || got.Capabilities != nil { - t.Fatalf("omitted capabilities = %q, %v, want none", got.Capabilities, err) + { + got, err := decodeExecutionCapture(captureWith("")) + testassert.Require(t, err == nil && got.Capabilities == nil, "omitted capabilities = %q, %v, want none", got.Capabilities, err) } - if got, err := decodeExecutionCapture(captureWith("[]")); err != nil || got.Capabilities != nil { - t.Fatalf("empty-array capabilities = %q, %v, want none", got.Capabilities, err) + { + got, err := decodeExecutionCapture(captureWith("[]")) + testassert.Require(t, err == nil && got.Capabilities == nil, "empty-array capabilities = %q, %v, want none", got.Capabilities, err) } for _, bad := range []string{"null", `["cap-a","cap-a"]`, `[""]`, `[5]`, `"cap-a"`, `["cap-a",null]`} { - if got, err := decodeExecutionCapture(captureWith(bad)); err == nil { - t.Fatalf("capabilities %s decoded to %q, want rejection", bad, got.Capabilities) + { + got, err := decodeExecutionCapture(captureWith(bad)) + testassert.Require(t, err != nil, "capabilities %s decoded to %q, want rejection", bad, got.Capabilities) } } } @@ -1756,34 +1611,28 @@ func TestExecutionCapturePermissionMembers(t *testing.T) { encode := func(t *testing.T, v ExecutionCapture) map[string]json.RawMessage { t.Helper() raw, err := encodeExecutionCapture(v) - if err != nil { - t.Fatalf("encodeExecutionCapture: %v", err) - } + testassert.NoError(t, err) members, err := decodePayloadObject(raw) - if err != nil { - t.Fatalf("decodePayloadObject: %v", err) - } + testassert.NoError(t, err) return members } explicit := encode(t, testCapture()) - if got := string(explicit["readonly"]); got != "false" { - t.Fatalf("encoded readonly member = %s, want the explicit false", got) + { + got := string(explicit["readonly"]) + testassert.Equal(t, got, "false") } - if got := string(explicit["write_dir"]); got != `""` { - t.Fatalf("encoded write_dir member = %s, want the explicit empty string", got) + { + got := string(explicit["write_dir"]) + testassert.Equal(t, got, `""`) } v := testCapture() v.Readonly = true v.WriteDir = "/w/sub" decoded, err := decodeExecutionCapture(encode(t, v)) - if err != nil { - t.Fatalf("decodeExecutionCapture: %v", err) - } - if decoded.Readonly != true || decoded.WriteDir != "/w/sub" { - t.Fatalf("round-tripped members = %v %q, want true and %q", decoded.Readonly, decoded.WriteDir, "/w/sub") - } + testassert.NoError(t, err) + testassert.Require(t, decoded.Readonly == true && decoded.WriteDir == "/w/sub", "round-tripped members = %v %q, want true and %q", decoded.Readonly, decoded.WriteDir, "/w/sub") for _, mutation := range []struct { name string @@ -1799,8 +1648,9 @@ func TestExecutionCapturePermissionMembers(t *testing.T) { t.Run(mutation.name, func(t *testing.T) { members := encode(t, v) mutation.members(members) - if got, err := decodeExecutionCapture(members); err == nil { - t.Fatalf("capture decoded to %+v, want rejection", got) + { + got, err := decodeExecutionCapture(members) + testassert.Require(t, err != nil, "capture decoded to %+v, want rejection", got) } }) } @@ -1808,70 +1658,55 @@ func TestExecutionCapturePermissionMembers(t *testing.T) { // TestExecutionCaptureCompactionMembers pins the durable compaction // configuration members: context_window, output_reserve and compact are -// required members encoded always, the nested compact object carries exactly -// the model, context_window, output_reserve and system_prompt keys with the -// model in the durable two-field form, everything round-trips unchanged, and -// a capture missing a member, carrying a non-positive window or reserve, an -// incomplete compact model identity, or an empty compact prompt is invalid on -// both the encode and decode paths. +// required conversation members encoded always, the nested compact object +// carries exactly the model and system_prompt keys with the model in the +// durable two-field form, everything round-trips unchanged, a capture missing +// a member, carrying a non-positive conversation window or reserve, an +// incomplete compact model identity, an empty compact prompt, or a compact +// object carrying a retired budget member is invalid on both the encode and +// decode paths. func TestExecutionCaptureCompactionMembers(t *testing.T) { encode := func(t *testing.T, v ExecutionCapture) map[string]json.RawMessage { t.Helper() raw, err := encodeExecutionCapture(v) - if err != nil { - t.Fatalf("encodeExecutionCapture: %v", err) - } + testassert.NoError(t, err) members, err := decodePayloadObject(raw) - if err != nil { - t.Fatalf("decodePayloadObject: %v", err) - } + testassert.NoError(t, err) return members } members := encode(t, testCapture()) - if got := string(members["context_window"]); got != "4096" { - t.Fatalf("encoded context_window member = %s, want 4096", got) + { + got := string(members["context_window"]) + testassert.Equal(t, got, "4096") } - if got := string(members["output_reserve"]); got != "2048" { - t.Fatalf("encoded output_reserve member = %s, want 2048", got) + { + got := string(members["output_reserve"]) + testassert.Equal(t, got, "2048") } compactObj, err := decodePayloadObject(members["compact"]) - if err != nil { - t.Fatalf("decodePayloadObject(compact): %v", err) - } - if err := rejectUnknownMembers(compactObj, "model", "context_window", "output_reserve", "system_prompt"); err != nil { - t.Fatalf("encoded compact object carries unexpected members: %v", err) + testassert.NoError(t, err) + { + err := rejectUnknownMembers(compactObj, "model", "system_prompt") + testassert.NoError(t, err) } - for _, key := range []string{"model", "context_window", "output_reserve", "system_prompt"} { - if _, present := compactObj[key]; !present { - t.Fatalf("encoded compact object misses the %q member", key) + for _, key := range []string{"model", "system_prompt"} { + { + _, present := compactObj[key] + testassert.Require(t, present, "encoded compact object misses the %q member", key) } } - if got := string(compactObj["context_window"]); got != "2048" { - t.Fatalf("encoded compact context_window member = %s, want 2048", got) - } - if got := string(compactObj["output_reserve"]); got != "1024" { - t.Fatalf("encoded compact output_reserve member = %s, want 1024", got) - } compactModelObj, err := decodePayloadObject(compactObj["model"]) - if err != nil { - t.Fatalf("decodePayloadObject(compact.model): %v", err) - } + testassert.NoError(t, err) compactModel, err := decodeModelRef(compactModelObj) - if err != nil { - t.Fatalf("encoded compact model is not a durable model reference: %v", err) - } + testassert.NoError(t, err) if compactModel != (model.ModelRef{Provider: "cprov", Model: "compact-x"}) { t.Fatalf("encoded compact model = %s, want cprov/compact-x", compactModel.String()) } decoded, err := decodeExecutionCapture(members) - if err != nil { - t.Fatalf("decodeExecutionCapture: %v", err) - } - if decoded.ContextWindow != 4096 || decoded.OutputReserve != 2048 { - t.Fatalf("round-tripped windows = %d %d, want 4096 and 2048", decoded.ContextWindow, decoded.OutputReserve) - } + testassert.NoError(t, err) + testassert.Require(t, decoded.ContextWindow == 4096 && decoded.OutputReserve == 2048, "round-tripped windows = %d %d, want 4096 and 2048", decoded.ContextWindow, decoded.OutputReserve) if decoded.Compact != testCompactCapture() { t.Fatalf("round-tripped compact = %+v, want %+v", decoded.Compact, testCompactCapture()) } @@ -1886,17 +1721,14 @@ func TestExecutionCaptureCompactionMembers(t *testing.T) { {"negative output reserve", func(v *ExecutionCapture) { v.OutputReserve = -1 }}, {"empty compact model", func(v *ExecutionCapture) { v.Compact.Model = model.ModelRef{} }}, {"incomplete compact model", func(v *ExecutionCapture) { v.Compact.Model = model.ModelRef{Provider: "cprov"} }}, - {"zero compact window", func(v *ExecutionCapture) { v.Compact.ContextWindow = 0 }}, - {"negative compact window", func(v *ExecutionCapture) { v.Compact.ContextWindow = -2 }}, - {"zero compact reserve", func(v *ExecutionCapture) { v.Compact.OutputReserve = 0 }}, - {"negative compact reserve", func(v *ExecutionCapture) { v.Compact.OutputReserve = -3 }}, {"empty compact prompt", func(v *ExecutionCapture) { v.Compact.SystemPrompt = "" }}, } { t.Run(mutation.name, func(t *testing.T) { v := testCapture() mutation.mutate(&v) - if _, err := encodeExecutionCapture(v); err == nil { - t.Fatalf("capture with %s encoded, want rejection", mutation.name) + { + _, err := encodeExecutionCapture(v) + testassert.Require(t, err != nil, "capture with %s encoded, want rejection", mutation.name) } }) } @@ -1904,8 +1736,9 @@ func TestExecutionCaptureCompactionMembers(t *testing.T) { for _, missing := range []string{"context_window", "output_reserve", "compact"} { m := encode(t, testCapture()) delete(m, missing) - if got, err := decodeExecutionCapture(m); err == nil { - t.Fatalf("capture missing %q decoded to %+v, want rejection", missing, got) + { + got, err := decodeExecutionCapture(m) + testassert.Require(t, err != nil, "capture missing %q decoded to %+v, want rejection", missing, got) } } for _, mutation := range []struct { @@ -1919,8 +1752,9 @@ func TestExecutionCaptureCompactionMembers(t *testing.T) { } { m := encode(t, testCapture()) mutation.mutat(m) - if got, err := decodeExecutionCapture(m); err == nil { - t.Fatalf("capture with %s decoded to %+v, want rejection", mutation.name, got) + { + got, err := decodeExecutionCapture(m) + testassert.Require(t, err != nil, "capture with %s decoded to %+v, want rejection", mutation.name, got) } } @@ -1934,23 +1768,106 @@ func TestExecutionCaptureCompactionMembers(t *testing.T) { {"unknown compact member", func(m map[string]json.RawMessage) { m["extra"] = json.RawMessage(`1`) }}, {"missing compact prompt", func(m map[string]json.RawMessage) { delete(m, "system_prompt") }}, {"null compact prompt", func(m map[string]json.RawMessage) { m["system_prompt"] = json.RawMessage("null") }}, - {"missing compact window", func(m map[string]json.RawMessage) { delete(m, "context_window") }}, + {"retired compact context window", func(m map[string]json.RawMessage) { m["context_window"] = json.RawMessage(`2048`) }}, + {"retired compact output reserve", func(m map[string]json.RawMessage) { m["output_reserve"] = json.RawMessage(`1024`) }}, } for _, mutation := range compactMutations { t.Run("wire "+mutation.name, func(t *testing.T) { m := encode(t, testCapture()) obj, err := decodePayloadObject(m["compact"]) - if err != nil { - t.Fatalf("decodePayloadObject: %v", err) - } + testassert.NoError(t, err) mutation.mutat(obj) raw, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal compact: %v", err) - } + testassert.NoError(t, err) m["compact"] = raw - if got, err := decodeExecutionCapture(m); err == nil { - t.Fatalf("capture with %s decoded to %+v, want rejection", mutation.name, got) + { + got, err := decodeExecutionCapture(m) + testassert.Require(t, err != nil, "capture with %s decoded to %+v, want rejection", mutation.name, got) + } + }) + } +} + +// TestOperationSettlementCompactionUsageMarker pins the usage provenance +// marker's wire shape: false encodes by omission, true encodes and round-trips +// unchanged, an omitted member decodes as false, a marker beside no usage is +// inert and valid, and null and non-boolean marker values are rejected through +// the member reader. +func TestOperationSettlementCompactionUsageMarker(t *testing.T) { + roundTrip := func(t *testing.T, v operationSettlementEntry) operationSettlementEntry { + t.Helper() + raw, err := encodeOperationSettlementEntry(v) + testassert.NoError(t, err) + decoded, err := decodeOperationSettlementEntry(Entry{ + SessionID: v.SessionID, + ID: v.EntryID, + OperationID: v.OperationID, + Kind: EntryOperationSettlement, + Payload: raw, + }) + testassert.NoError(t, err) + return decoded + } + + unmarked := validSettlementEntry() + raw, err := encodeOperationSettlementEntry(unmarked) + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(raw), "compaction_usage"), "false marker encoded as a member: %s", raw) + { + got := roundTrip(t, unmarked) + testassert.Require(t, !got.CompactionUsage, "round-tripped omitted marker = true, want the omitted/false equivalence") + } + + ref := testModelRef() + usage := &UsageCount{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 6} + marked := validSettlementEntry() + marked.Status = OperationFailure + marked.Detail = "summary failure" + marked.Model = &ref + marked.Usage = usage + marked.CompactionUsage = true + raw, err = encodeOperationSettlementEntry(marked) + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(raw), `"compaction_usage":true`), "marked settlement payload %s, want the true member", raw) + { + got := roundTrip(t, marked) + testassert.Require(t, reflect.DeepEqual(got, marked), "round-tripped settlement = %+v, want %+v", got, marked) + } + + inert := validSettlementEntry() + inert.Status = OperationInterruption + inert.Detail = "interrupted" + inert.CompactionUsage = true + { + got := roundTrip(t, inert) + testassert.Require(t, got.CompactionUsage && got.Model == nil && got.Usage == nil, "round-tripped marker-without-usage = %+v, want the inert true marker with no usage", got) + } + + for _, row := range []struct { + name string + value string + }{ + {"null marker", "null"}, + {"number marker", "1"}, + {"string marker", `"true"`}, + } { + t.Run(row.name, func(t *testing.T) { + unmarkedRaw, err := encodeOperationSettlementEntry(unmarked) + testassert.NoError(t, err) + members, err := decodePayloadObject(unmarkedRaw) + testassert.NoError(t, err) + members["compaction_usage"] = json.RawMessage(row.value) + raw, err := json.Marshal(members) + testassert.NoError(t, err) + { + _, err := decodeOperationSettlementEntry(Entry{ + SessionID: unmarked.SessionID, + ID: unmarked.EntryID, + OperationID: unmarked.OperationID, + Kind: EntryOperationSettlement, + Payload: raw, + }) + testassert.Require(t, err != nil, "settlement with %s decoded, want rejection", row.name) } }) } @@ -1964,40 +1881,27 @@ func TestSessionIdentityParentLineage(t *testing.T) { child := validSessionRecord() child.Identity.ParentSessionID = otherSession() childRaw, err := encodeSessionRegister(child) - if err != nil { - t.Fatalf("encode child identity: %v", err) - } - if !strings.Contains(string(childRaw), `"parent_session_id":"`+otherSession()+`"`) { - t.Fatalf("child identity payload lacks parent_session_id: %s", childRaw) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(childRaw), `"parent_session_id":"`+otherSession()+`"`), "child identity payload lacks parent_session_id: %s", childRaw) decoded, err := decodeSessionRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Payload: childRaw}) - if err != nil { - t.Fatalf("decode child identity: %v", err) - } + testassert.NoError(t, err) if decoded.Identity.ParentSessionID != otherSession() { t.Fatalf("decoded parent session id = %q, want %q", decoded.Identity.ParentSessionID, otherSession()) } rootRaw, err := encodeSessionRegister(validSessionRecord()) - if err != nil { - t.Fatalf("encode root identity: %v", err) - } - if strings.Contains(string(rootRaw), "parent_session_id") { - t.Fatalf("root identity carries parent_session_id: %s", rootRaw) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(rootRaw), "parent_session_id"), "root identity carries parent_session_id: %s", rootRaw) fork := validSessionRecord() fork.Identity.SourceSessionID = otherSession() fork.Identity.SourceBoundaryEntryID = otherEntry() forkRaw, err := encodeSessionRegister(fork) - if err != nil { - t.Fatalf("encode fork identity: %v", err) - } - if strings.Contains(string(forkRaw), "parent_session_id") { - t.Fatalf("fork identity carries parent_session_id: %s", forkRaw) - } - if _, err := decodeSessionRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Payload: forkRaw}); err != nil { - t.Fatalf("decode fork identity: %v", err) + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(forkRaw), "parent_session_id"), "fork identity carries parent_session_id: %s", forkRaw) + { + _, err := decodeSessionRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Payload: forkRaw}) + testassert.NoError(t, err) } // Encode-side rejections use the existing invalid-input class. @@ -2012,8 +1916,9 @@ func TestSessionIdentityParentLineage(t *testing.T) { } for _, tc := range encodeRejections { t.Run("encode/"+tc.name, func(t *testing.T) { - if _, err := encodeSessionIdentity(tc.value); !errors.Is(err, ErrInvalid) { - t.Fatalf("error = %v, want ErrInvalid", err) + { + _, err := encodeSessionIdentity(tc.value) + testassert.Require(t, errors.Is(err, ErrInvalid), "error = %v, want ErrInvalid", err) } }) } @@ -2046,12 +1951,11 @@ func TestSessionIdentityParentLineage(t *testing.T) { for _, tc := range decodeRejections { t.Run("decode/"+tc.name, func(t *testing.T) { obj, err := objectMember(wireObject(t, childRaw), "identity", true) - if err != nil { - t.Fatalf("identity member: %v", err) - } + testassert.NoError(t, err) tc.mutate(obj) - if _, err := decodeSessionIdentity(obj); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeSessionIdentity(obj) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -2068,23 +1972,15 @@ func TestBackgroundCompletionSignalRules(t *testing.T) { jobID := "0a1b2c3d" raw, err := encodeSignalEntry(validBackgroundCompletionEntry()) - if err != nil { - t.Fatalf("encode child completion: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, Kind: EntrySignal, Sequence: 1, CommittedAt: testTime} env.Payload = raw decoded, err := decodeSignalEntry(env) - if err != nil { - t.Fatalf("decode child completion: %v", err) - } - if decoded.OperationID != "" || decoded.RelatedOperation != nil { - t.Fatalf("completion signal is not operationless: %+v", decoded) - } - if decoded.RelatedMember == nil || decoded.RelatedMember.Kind != "child" || decoded.RelatedMember.ID != childID { - t.Fatalf("decoded related member = %+v, want child %q", decoded.RelatedMember, childID) - } - if _, present := wireObject(t, raw)["related_operation"]; present { - t.Fatalf("completion signal carries related_operation: %s", raw) + testassert.NoError(t, err) + testassert.Require(t, decoded.OperationID == "" && decoded.RelatedOperation == nil && (decoded.RelatedMember != nil && decoded.RelatedMember.Kind == "child" && decoded.RelatedMember.ID == childID), "completion signal is not operationless: %+v; decoded related member = %+v, want child %q", decoded, decoded.RelatedMember, childID) + { + _, present := wireObject(t, raw)["related_operation"] + testassert.Require(t, !present, "completion signal carries related_operation: %s", raw) } jobRaw, err := encodeSignalEntry(func() signalEntry { @@ -2092,23 +1988,21 @@ func TestBackgroundCompletionSignalRules(t *testing.T) { v.RelatedMember = &relatedMember{Kind: "job", ID: jobID} return v }()) - if err != nil { - t.Fatalf("encode job completion: %v", err) - } + testassert.NoError(t, err) env.Payload = jobRaw - if _, err := decodeSignalEntry(env); err != nil { - t.Fatalf("decode job completion: %v", err) + { + _, err := decodeSignalEntry(env) + testassert.NoError(t, err) } // The fixed kinds reject a related_member member. fixedRaw, err := encodeSignalEntry(validSignalEntry(testOpID)) - if err != nil { - t.Fatalf("encode fixed signal: %v", err) - } + testassert.NoError(t, err) fixedEnv := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntrySignal, Sequence: 1, CommittedAt: testTime} fixedEnv.Payload = setKey(fixedRaw, "related_member", json.RawMessage(`{"kind":"child","id":"`+childID+`"}`)) - if _, err := decodeSignalEntry(fixedEnv); err == nil { - t.Fatalf("fixed signal with related_member must be rejected") + { + _, err := decodeSignalEntry(fixedEnv) + testassert.Require(t, err != nil, "fixed signal with related_member must be rejected") } // Encode-side rejections on the subtype value. @@ -2141,8 +2035,9 @@ func TestBackgroundCompletionSignalRules(t *testing.T) { } for _, tc := range encodeRejections { t.Run("encode/"+tc.name, func(t *testing.T) { - if _, err := encodeSignalEntry(tc.value); !errors.Is(err, ErrInvalid) { - t.Fatalf("error = %v, want ErrInvalid", err) + { + _, err := encodeSignalEntry(tc.value) + testassert.Require(t, errors.Is(err, ErrInvalid), "error = %v, want ErrInvalid", err) } }) } @@ -2171,8 +2066,9 @@ func TestBackgroundCompletionSignalRules(t *testing.T) { for _, tc := range decodeRejections { t.Run("decode/"+tc.name, func(t *testing.T) { env.Payload = tc.payload - if _, err := decodeSignalEntry(env); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeSignalEntry(env) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -2190,47 +2086,29 @@ func TestCompactionEntryPayloadRules(t *testing.T) { v.Usage = &UsageCount{InputTokens: 11, CachedInputTokens: 4, OutputTokens: 7} } raw, err := encodeCompactionEntry(v) - if err != nil { - t.Fatalf("encode (usage %v): %v", withUsage, err) - } + testassert.Require(t, err == nil, "encode (usage %v): %v", withUsage, err) env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryCompaction, Sequence: 1, CommittedAt: testTime, Payload: raw} got, err := decodeCompactionEntry(env) - if err != nil { - t.Fatalf("decode (usage %v): %v", withUsage, err) - } - if (got.Usage == nil) != (v.Usage == nil) { - t.Fatalf("decoded usage presence %+v, want %+v", got.Usage, v.Usage) - } + testassert.Require(t, err == nil, "decode (usage %v): %v", withUsage, err) + testassert.Require(t, (got.Usage == nil) == (v.Usage == nil), "decoded usage presence %+v, want %+v", got.Usage, v.Usage) if withUsage && *got.Usage != *v.Usage { t.Fatalf("decoded usage %+v, want %+v", *got.Usage, *v.Usage) } got.Usage = v.Usage // pointer identity is irrelevant; the counts compared above - if got != v { - t.Fatalf("decoded %+v, want %+v", got, v) - } + testassert.Equal(t, got, v) again, err := encodeCompactionEntry(got) - if err != nil { - t.Fatalf("re-encode (usage %v): %v", withUsage, err) - } - if string(again) != string(raw) { - t.Fatalf("re-encoded bytes %s, want %s", again, raw) - } + testassert.Require(t, err == nil, "re-encode (usage %v): %v", withUsage, err) + testassert.Require(t, string(again) == string(raw), "re-encoded bytes %s, want %s", again, raw) } }) t.Run("round trips operationless fork copy", func(t *testing.T) { raw, err := encodeCompactionEntry(validCompactionEntry("")) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, Kind: EntryCompaction, Sequence: 1, CommittedAt: testTime, Payload: raw} got, err := decodeCompactionEntry(env) - if err != nil { - t.Fatalf("decode: %v", err) - } - if got.OperationID != "" || got.Usage != nil { - t.Fatalf("operationless decode = %+v", got) - } + testassert.NoError(t, err) + testassert.Require(t, got.OperationID == "" && got.Usage == nil, "operationless decode = %+v", got) }) t.Run("encode rejects invalid values", func(t *testing.T) { @@ -2252,21 +2130,14 @@ func TestCompactionEntryPayloadRules(t *testing.T) { v := validCompactionEntry(testOpID) tc.mutate(&v) _, err := encodeCompactionEntry(v) - if err == nil { - t.Fatalf("expected rejection") - } - if !errors.Is(err, ErrInvalid) { - t.Fatalf("error %v is not ErrInvalid", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrInvalid), "expected rejection; error %v is not ErrInvalid", err) }) } }) t.Run("decode rejects invalid wire", func(t *testing.T) { raw, err := encodeCompactionEntry(validCompactionEntry(testOpID)) - if err != nil { - t.Fatalf("encode valid: %v", err) - } + testassert.NoError(t, err) env := Entry{SessionID: testSessionID, ID: testEntryID, OperationID: testOpID, Kind: EntryCompaction, Sequence: 1, CommittedAt: testTime} rejections := []struct { name string @@ -2295,8 +2166,9 @@ func TestCompactionEntryPayloadRules(t *testing.T) { for _, tc := range rejections { t.Run(tc.name, func(t *testing.T) { env.Payload = tc.payload - if _, err := decodeCompactionEntry(env); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeCompactionEntry(env) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -2305,12 +2177,11 @@ func TestCompactionEntryPayloadRules(t *testing.T) { t.Run("operationless with stored usage is rejected", func(t *testing.T) { env := Entry{SessionID: testSessionID, ID: testEntryID, Kind: EntryCompaction, Sequence: 1, CommittedAt: testTime} raw, err := encodeCompactionEntry(validCompactionEntry("")) - if err != nil { - t.Fatalf("encode operationless: %v", err) - } + testassert.NoError(t, err) env.Payload = setKey(raw, "usage", json.RawMessage(`{"input_tokens":1,"cached_input_tokens":0,"output_tokens":0}`)) - if _, err := decodeCompactionEntry(env); err == nil { - t.Fatalf("operationless compaction with stored usage must be rejected") + { + _, err := decodeCompactionEntry(env) + testassert.Require(t, err != nil, "operationless compaction with stored usage must be rejected") } }) } @@ -2323,14 +2194,10 @@ func TestSessionStateCompactionEntryIDRules(t *testing.T) { rec := validSessionRecord() rec.State.CompactionEntryID = hexID(3) raw, err := encodeSessionRegister(rec) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) reg := Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Revision: 1, Payload: raw} got, err := decodeSessionRegister(reg) - if err != nil { - t.Fatalf("decode: %v", err) - } + testassert.NoError(t, err) if got.State.CompactionEntryID != hexID(3) { t.Fatalf("decoded CompactionEntryID %q, want %q", got.State.CompactionEntryID, hexID(3)) } @@ -2338,36 +2205,27 @@ func TestSessionStateCompactionEntryIDRules(t *testing.T) { t.Run("omits the member when empty", func(t *testing.T) { raw, err := encodeSessionRegister(validSessionRecord()) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) state := wireObject(t, mustState(t, raw)) - if _, present := state["compaction_entry_id"]; present { - t.Fatalf("empty CompactionEntryID must encode by omission") + { + _, present := state["compaction_entry_id"] + testassert.Require(t, !present, "empty CompactionEntryID must encode by omission") } }) patchState := func(t *testing.T, mutate func(obj map[string]json.RawMessage)) json.RawMessage { t.Helper() raw, err := encodeSessionRegister(validSessionRecord()) - if err != nil { - t.Fatalf("encode: %v", err) - } + testassert.NoError(t, err) obj := wireObject(t, raw) state, err := decodePayloadObject(mustState(t, raw)) - if err != nil { - t.Fatalf("decode state: %v", err) - } + testassert.NoError(t, err) mutate(state) patchedState, err := json.Marshal(state) - if err != nil { - t.Fatalf("marshal state: %v", err) - } + testassert.NoError(t, err) obj["state"] = patchedState patched, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal payload: %v", err) - } + testassert.NoError(t, err) return patched } @@ -2383,8 +2241,9 @@ func TestSessionStateCompactionEntryIDRules(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { reg := Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterSession}, Revision: 1, Payload: patchState(t, tc.mutate)} - if _, err := decodeSessionRegister(reg); err == nil { - t.Fatalf("expected rejection") + { + _, err := decodeSessionRegister(reg) + testassert.Require(t, err != nil, "expected rejection") } }) } @@ -2393,8 +2252,9 @@ func TestSessionStateCompactionEntryIDRules(t *testing.T) { t.Run("encode rejections", func(t *testing.T) { rec := validSessionRecord() rec.State.CompactionEntryID = "ghost" - if _, err := encodeSessionRegister(rec); !errors.Is(err, ErrInvalid) { - t.Fatalf("error %v, want ErrInvalid", err) + { + _, err := encodeSessionRegister(rec) + testassert.Require(t, errors.Is(err, ErrInvalid), "error %v, want ErrInvalid", err) } }) } diff --git a/harness/cold_test.go b/harness/cold_test.go new file mode 100644 index 00000000..cf1be46e --- /dev/null +++ b/harness/cold_test.go @@ -0,0 +1,111 @@ +// Cold-process execution observation: a completed Operation, a mid-flight +// Interrupt and the canceled Harness's Wait all finish in a fresh child +// process without contacting an external transport. +package harness + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "os/exec" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/MMinasyan/lightcode/internal/testassert" + "github.com/MMinasyan/lightcode/model" +) + +// coldChildEnv switches this test binary into the child mode of the cold +// execution regression. +const coldChildEnv = "LIGHTCODE_COLD_CHILD" + +// coldChildDone is the child's completion marker: the parent requires it so a +// child that ran no assertion cannot pass by exiting zero. +const coldChildDone = "cold child: done" + +// refusedTransport fails every attempt after recording it, so the child can +// prove that no hidden external acquisition reached a transport. +type refusedTransport struct{ attempts atomic.Int64 } + +func (f *refusedTransport) RoundTrip(*http.Request) (*http.Response, error) { + f.attempts.Add(1) + return nil, errors.New("external transport is unavailable in this process") +} + +// TestColdChildNoExternalIO runs one real Operation, a mid-flight Interrupt +// and a Wait in a fresh child process with a transport that refuses (and +// records) every attempt. All three must finish without contacting that +// transport. +func TestColdChildNoExternalIO(t *testing.T) { + if os.Getenv(coldChildEnv) == "1" { + runColdChild(t) + return + } + cmd := exec.Command(os.Args[0], "-test.run=^"+t.Name()+"$", "-test.timeout=120s") + cmd.Env = append(os.Environ(), coldChildEnv+"=1") + out, err := cmd.CombinedOutput() + testassert.Require(t, err == nil, "cold child: %v\n%s", err, out) + testassert.Require(t, strings.Contains(string(out), coldChildDone), "the cold child ran no completed work:\n%s", out) +} + +// runColdChild is the child body: a completed Operation, a parked Operation +// interrupted mid-flight, and the canceled Harness's Wait join. +func runColdChild(t *testing.T) { + transport := &refusedTransport{} + http.DefaultTransport = transport + + store := emptyStore(t) + started := make(chan struct{}) + var requests atomic.Int64 + modelFn := func(ctx context.Context, _ model.Request) (model.Stream, error) { + if requests.Add(1) == 1 { + return completedTurnStream(), nil + } + close(started) // the second Operation parks inside its model attempt + <-ctx.Done() + return nil, ctx.Err() + } + h, cancel := newCancelableHarness(t, store, modelPrepared(modelFn), nil) + session := createSession(t, h) + watch := watchSettlements(store) + + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) + } + watch.next() + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationSuccess) + } + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "park") + testassert.NoError(t, err) + } + <-started + { + err := h.Interrupt(context.Background(), session) + testassert.NoError(t, err) + } + watch.next() + { + rec := settledOperation(t, store, session, "op-2") + testassert.Equal(t, rec.State.Status, OperationInterruption) + } + cancel() + waitCtx, waitCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer waitCancel() + { + err := h.Wait(waitCtx) + testassert.NoError(t, err) + } + { + attempts := transport.attempts.Load() + testassert.Equal(t, attempts, 0) + } + fmt.Println(coldChildDone) +} diff --git a/harness/compact.go b/harness/compact.go index a4c7e84a..1eeda077 100644 --- a/harness/compact.go +++ b/harness/compact.go @@ -11,32 +11,6 @@ import ( "github.com/MMinasyan/lightcode/model" ) -// compactionWrapperOverhead is the fixed token reserve for the compact -// request's wrapper text around the serialized piece: role framing and the -// Previous-summary block's fixed scaffolding. -const compactionWrapperOverhead = 64 - -// usageAccumulator is the orchestration's process-local piece-usage total: -// one UsageCount summed field-wise across the pieces, all of which run on the -// one compact model. A piece reporting no usage contributes nothing; the -// total stays nil until a piece first reports usage. -type usageAccumulator struct { - total *UsageCount -} - -func (a *usageAccumulator) add(piece UsageCount) error { - if a.total == nil { - a.total = &piece - return nil - } - total, err := addUsageCount(*a.total, piece) - if err != nil { - return err - } - a.total = &total - return nil -} - // serializeCompactionMessages renders the frozen projected conversation as // the compaction input: one inert JSON line per message, in order. Each // message is copied before any field is rewritten — a shallow struct copy @@ -72,38 +46,29 @@ func serializeCompactionMessages(messages []model.Message) ([]string, error) { return lines, nil } -// compactionPieceBudget computes one piece's serialized-input token budget: -// the compact window minus the output reserve, the compact system prompt's -// plain-text estimate, the fixed wrapper overhead, and — when a previous -// summary exists — the previous summary carried as one message. -func compactionPieceBudget(capture CompactCapture, previous string) int { - budget := capture.ContextWindow - capture.OutputReserve - estimateTokens(capture.SystemPrompt, nil) - compactionWrapperOverhead - if previous != "" { - budget -= estimateTokens("", []model.Message{{Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: previous}}}}) - } - return budget -} - -// compactModelEffect encloses one compact piece's model request in one -// logical model-effect intent/result pair — a sibling of modelEffect closing -// over the compact transport and the orchestration's usage accumulator. The -// shared active-effect intent transaction commits the same durable intent; -// the in-memory intent's expected identity is the compact model, the assembly +// compactModelEffect encloses the compact summary request in one logical +// model-effect intent/result pair — a sibling of modelEffect closing over the +// compact transport and the orchestration's reported-usage slot. The shared +// active-effect intent transaction commits the same durable intent; the +// in-memory intent's expected identity is the compact model, the assembly // source the agent's callback validates and the terminal usage model, because -// the pieces run on the compact transport while the durable active-effect -// state is model-agnostic. The standard attempt-retry loop runs through the -// compact transport, and each piece's reported usage joins the accumulator -// before its settlement. A completed output settles by the empty running -// result — no entry, the active effect cleared, both registers replaced with -// no usage, the reserved identity unused — and returns the ready settlement -// whose output carries the piece summary; the orchestration validates the -// summary text after the settlement. A piece failure and an interruption -// settle through the effect's own terminal settlement with the accumulated -// usage keyed by the compact model, returning the assembled output carried on -// the failure and interruption rows — the conversation effect's own shape, -// and the run's callback gate requires it whenever the assembly callback ran -// — while pre-assembly failures and cancellations return no output. -func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Execution, capture ExecutionCapture, accumulated *usageAccumulator) agent.ModelEffect { +// the summary runs on the compact transport while the durable active-effect +// state is model-agnostic, and the intent is marked as compaction so its +// terminal usage carries summary provenance. The standard attempt-retry loop +// runs through the compact transport, and the assembled output's reported +// usage — nil-preserving, a reported zero stays known zero — lands on the +// slot before that output settles. A completed output settles by the empty +// running result — no entry, the active effect cleared, both registers +// replaced with no usage, the reserved identity unused — and returns the +// ready settlement whose output carries the summary; the orchestration +// validates the summary text after the settlement. A summary failure and an +// interruption settle through the effect's own terminal settlement with the +// slot's current usage keyed by the compact model, returning the assembled +// output carried on the failure and interruption rows — the conversation +// effect's own shape, and the run's callback gate requires it whenever the +// assembly callback ran — while pre-assembly failures and cancellations +// return no output. +func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Execution, capture ExecutionCapture, reported **UsageCount) agent.ModelEffect { attempt := exec.CompactModel return func(ctx context.Context, req model.Request, assemble agent.AssemblyCallback) (agent.ModelSettlement, error) { intent, err := h.beginModelEffect(ctx, c, operationID) @@ -115,8 +80,9 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex if _, cerr := h.commitEffectResult(context.WithoutCancel(h.ctx), c, operationID, nil, modelResult{ terminal: OperationInterruption, detail: executionInterruptedDetail, - usage: accumulated.total, + usage: *reported, usageModel: capture.Compact.Model, + compaction: true, }); cerr != nil { return agent.ModelSettlement{}, cerr } @@ -126,13 +92,14 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex } // The durable intent transaction is the conversation model effect's // own; only the in-memory intent's expected identity is the compact - // model's. + // model's, and its provenance is compaction. intent.expected = capture.Compact.Model + intent.compaction = true // Execution cancellation stops new callbacks: a context that died // between the committed intent and the callback settles the Operation // as terminal interruption without ever starting the callback. if ctx.Err() != nil { - return h.interruptModelEffect(c, operationID, intent, accumulated.total) + return h.interruptModelEffect(c, operationID, intent, *reported) } // Result and terminal transactions run without cancellation so an // already-produced result or required terminal settlement publishes. @@ -141,7 +108,7 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex if _, err := h.commitEffectResult(settleCtx, c, operationID, &intent, modelResult{ terminal: OperationFailure, detail: cause.Error(), - usage: accumulated.total, + usage: *reported, }); err != nil { return agent.ModelSettlement{}, err } @@ -159,7 +126,7 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex } output = out case attemptInterrupted: - return h.interruptModelEffect(c, operationID, intent, accumulated.total) + return h.interruptModelEffect(c, operationID, intent, *reported) case attemptInvalid: return settle(res.err) case attemptFailed: @@ -167,38 +134,38 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex if _, err := h.commitEffectResult(settleCtx, c, operationID, &intent, modelResult{ terminal: OperationFailure, detail: res.err.Error(), - usage: accumulated.total, + usage: *reported, }); err != nil { return agent.ModelSettlement{}, err } return committed, nil } - // The piece's reported usage joins the accumulator before its - // settlement; a piece reporting no usage contributes nothing. + // The assembled output's reported usage lands on the orchestration's + // slot before that output settles; an output reporting no usage leaves + // the slot untouched. if output.Usage != nil { - if err := accumulated.add(UsageCount{ + usage := UsageCount{ InputTokens: int64(output.Usage.InputTokens), CachedInputTokens: int64(output.Usage.CachedInputTokens), OutputTokens: int64(output.Usage.OutputTokens), - }); err != nil { - return agent.ModelSettlement{}, err } + *reported = &usage } if output.Status == model.OutputCompleted { // The empty running result inserts no entry, clears the active // effect, and replaces both registers with no usage; the reserved - // result identity stays unused. The piece's summary text is the + // result identity stays unused. The summary text is the // orchestration's validation, made after this ready settlement. if _, err := h.commitEffectResult(settleCtx, c, operationID, &intent, modelResult{}); err != nil { return agent.ModelSettlement{}, err } return agent.ModelSettlement{Disposition: agent.DispoReady, Output: &output}, nil } - // A piece failure and an interruption settle through the effect's own - // terminal settlement, consuming the reserved identity. The assembled - // output rides the returned settlement — an errored output on the - // failure row, an interrupted one on the interruption row — while a - // pre-assembly failure or cancellation returns none. + // A summary failure and an interruption settle through the effect's + // own terminal settlement, consuming the reserved identity. The + // assembled output rides the returned settlement — an errored output + // on the failure row, an interrupted one on the interruption row — + // while a pre-assembly failure or cancellation returns none. terminal := OperationFailure disposition := agent.DispoFailure if output.Status == model.OutputInterrupted { @@ -208,7 +175,7 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex if _, err := h.commitEffectResult(settleCtx, c, operationID, &intent, modelResult{ terminal: terminal, detail: output.Detail, - usage: accumulated.total, + usage: *reported, }); err != nil { return agent.ModelSettlement{}, err } @@ -219,25 +186,24 @@ func (h *Harness) compactModelEffect(c *coordinator, operationID string, exec Ex // runCompaction summarizes the frozen projected conversation through the // no-Session compact agent. The snapshot — the complete projected // conversation minus the leading system message, supplied by the caller — is -// serialized into inert transcript lines and packed whole-message into -// pieces that each fit the compact budget; a message that exceeds a nonempty -// piece's remaining budget starts the next piece, and no message, reasoning -// part, tool call, or tool result is ever split. Each piece runs one -// agent.Run invocation: the fixed compact prompt as the system message, the -// framed piece as the user message — the Previous summary block only when a -// previous piece already produced a summary — the compact model effect over -// the compact transport, and a tool boundary that rejects any hallucinated -// dispatch. Each piece's request budget accounts for the previous summary -// known when that piece is packed. Intermediate summaries stay process-local; -// the final piece's output is the summary candidate returned to the caller -// together with the accumulated piece usage — the commit is the caller's. A -// textless completed piece fails the orchestration after its ready -// settlement. A piece failure or interruption settles durably inside its own -// effect; a run-level terminal or any other failure — a pre-run cancellation, -// a cancellation observed after a settled piece, an empty snapshot, an empty -// piece summary, an error after a settled piece — settles directly on the -// quiet running Operation with the accumulated usage keyed by the compact -// model. No compaction entry is written on any path. +// serialized into inert transcript lines and joined whole into one user +// message: the complete input is sent as one logical full-input request, +// never split. The fixed no-Session Agent runs once: the captured compact +// prompt as the system message, the serialized conversation as the user +// message, the compact model effect over the compact transport, and a tool +// boundary that rejects any hallucinated dispatch. The orchestration's +// reported-usage slot is set by the effect from the assembled output before +// that output settles and read after the run returns: a nil slot means the +// summary reported no usage, and a reported zero stays known zero. The +// summary candidate and that usage are returned to the caller — the commit is +// the caller's. A textless completed summary fails the orchestration after +// its ready settlement. A summary failure or interruption settles durably +// inside its own effect; a run-level terminal or any other failure — a +// pre-run cancellation, a cancellation observed after the settled summary +// result, an empty snapshot, an empty summary, an error after the settled +// result — settles directly on the quiet running Operation with the slot's +// usage keyed by the compact model. No compaction entry is written on any +// failure path. func (h *Harness) runCompaction(execCtx context.Context, c *coordinator, operationID string, exec Execution, capture ExecutionCapture, snapshot []model.Message) (string, *UsageCount, error) { if len(snapshot) == 0 { return "", nil, h.settleCompactionFailure(c, operationID, OperationFailure, errors.New("nothing to compact"), capture, nil) @@ -246,79 +212,58 @@ func (h *Harness) runCompaction(execCtx context.Context, c *coordinator, operati if err != nil { return "", nil, h.settleCompactionFailure(c, operationID, OperationFailure, err, capture, nil) } - accumulated := &usageAccumulator{} - effect := h.compactModelEffect(c, operationID, exec, capture, accumulated) - previous := "" - index := 0 - for index < len(lines) { - budget := compactionPieceBudget(capture.Compact, previous) - var piece []string - used := 0 - for index < len(lines) { - cost := estimateTokens(lines[index], nil) - if len(piece) > 0 && used+cost > budget { - break - } - piece = append(piece, lines[index]) - used += cost - index++ - } - requestText := "" - if previous != "" { - requestText = "Previous summary:\n" + previous + "\n\nContinuation:\n" - } - requestText += strings.Join(piece, "\n") - res, runErr := agent.Run(execCtx, agent.Invocation{ - ExpectedModel: capture.Compact.Model, - Tools: nil, - Context: func(context.Context) ([]model.Message, error) { - return []model.Message{ - {Role: model.RoleSystem, Content: []model.ContentPart{{Kind: model.PartText, Text: capture.Compact.SystemPrompt}}}, - {Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: requestText}}}, - }, nil - }, - ModelEffect: effect, - ToolEffect: func(context.Context, model.ToolCall) (model.ToolResult, error) { - return model.ToolResult{}, &agent.ProtocolError{Boundary: "tool", Detail: "compact agent has no tools"} - }, - }) - if runErr != nil { - return "", accumulated.total, h.settleCompactionFailure(c, operationID, OperationFailure, runErr, capture, accumulated.total) - } - switch res.Status { - case agent.TerminalSuccess: - previous = res.LastOutput.Message.TextContent() - if previous == "" { - // A textless completed piece — including refusal-only — - // fails the orchestration after the piece's ready - // settlement: no retry, no next piece, the direct - // settlement carrying the retained detail and the - // accumulated usage. - return "", accumulated.total, h.settleCompactionFailure(c, operationID, OperationFailure, errors.New("compaction summary is empty"), capture, accumulated.total) - } - default: - // A run-level terminal — a pre-run cancellation, or a - // cancellation observed after a settled piece before the run's - // success return — leaves the Operation running and quiet with - // no settlement; the direct settlement covers it, and takes no - // compensating write when a piece effect already settled the - // same terminal. - terminal := OperationFailure - if res.Status == agent.TerminalInterruption { - terminal = OperationInterruption - } - return "", accumulated.total, h.settleCompactionFailure(c, operationID, terminal, errors.New(res.Detail), capture, accumulated.total) + var reported *UsageCount + effect := h.compactModelEffect(c, operationID, exec, capture, &reported) + res, runErr := agent.Run(execCtx, agent.Invocation{ + ExpectedModel: capture.Compact.Model, + Tools: nil, + Context: func(context.Context) ([]model.Message, error) { + return []model.Message{ + {Role: model.RoleSystem, Content: []model.ContentPart{{Kind: model.PartText, Text: capture.Compact.SystemPrompt}}}, + {Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: strings.Join(lines, "\n")}}}, + }, nil + }, + ModelEffect: effect, + ToolEffect: func(context.Context, model.ToolCall) (model.ToolResult, error) { + return model.ToolResult{}, &agent.ProtocolError{Boundary: "tool", Detail: "compact agent has no tools"} + }, + }) + if runErr != nil { + return "", reported, h.settleCompactionFailure(c, operationID, OperationFailure, runErr, capture, reported) + } + switch res.Status { + case agent.TerminalSuccess: + summary := res.LastOutput.Message.TextContent() + if summary == "" { + // A textless completed summary — including refusal-only — + // fails the orchestration after its ready settlement: no + // retry, the direct settlement carrying the retained detail + // and the reported usage. + return "", reported, h.settleCompactionFailure(c, operationID, OperationFailure, errors.New("compaction summary is empty"), capture, reported) + } + return summary, reported, nil + default: + // A run-level terminal — a pre-run cancellation, or a cancellation + // observed after the settled summary result before the run's + // success return — leaves the Operation running and quiet with no + // settlement; the direct settlement covers it, and takes no + // compensating write when the effect already settled the same + // terminal. + terminal := OperationFailure + if res.Status == agent.TerminalInterruption { + terminal = OperationInterruption } + return "", reported, h.settleCompactionFailure(c, operationID, terminal, errors.New(res.Detail), capture, reported) } - return previous, accumulated.total, nil } // settleCompactionFailure is the direct terminal settlement of one // orchestration failure or run-level terminal on the quiet running Operation: -// the given terminal state and detail commit with the accumulated usage keyed -// by the compact model, and the cause returns to the caller. An Operation -// already settled by a piece effect, or one whose committed intent state a -// publication failure left for recovery, takes no compensating write. +// the given terminal state and detail commit with the reported summary usage +// keyed by the compact model and marked as compaction usage, and the cause +// returns to the caller. An Operation already settled by the summary effect, +// or one whose committed intent state a publication failure left for +// recovery, takes no compensating write. func (h *Harness) settleCompactionFailure(c *coordinator, operationID string, terminal OperationState, cause error, capture ExecutionCapture, usage *UsageCount) error { c.mu.Lock() op, ok := c.graph.Operation(operationID) @@ -338,6 +283,7 @@ func (h *Harness) settleCompactionFailure(c *coordinator, operationID string, te detail: cause.Error(), usage: usage, usageModel: capture.Compact.Model, + compaction: true, }); err != nil { return err } @@ -347,7 +293,7 @@ func (h *Harness) settleCompactionFailure(c *coordinator, operationID string, te // compactionFailureSettlement converts one compaction-trigger failure into // the model effect's return shape by the Operation's durable state — the // settled outcome's single discriminator. An Operation already settled (a -// piece failure settled inside its own effect, or any other failure settled +// summary failure settled inside its own effect, or any other failure settled // through the direct terminal settlement) keeps the failure settlement with a // nil error: the request whose checkpoint failed is never sent, with no // retry and no uncompacted fallback. A still-running or absent Operation — @@ -443,7 +389,7 @@ func compactionBoundary(entries []graphEntry, namedCompactionID string, snapshot // model on the entry and both register totals in the same transaction. A // failed transaction leaves every previous value durable and settles the // quiet running Operation through the direct terminal settlement carrying -// the commit error and the accumulated usage keyed by the compact model. +// the commit error and the reported usage keyed by the compact model. func (h *Harness) commitCompaction(c *coordinator, operationID string, capture ExecutionCapture, summary string, snapshotLen int, usage *UsageCount, manual bool) error { c.mu.Lock() viewOp, ok := c.graph.Operation(operationID) @@ -522,7 +468,7 @@ func (h *Harness) commitCompaction(c *coordinator, operationID string, capture E } newEntries = append(newEntries, adopted) - // The orchestration's accumulated total lands keyed by the compact + // The orchestration's reported total lands keyed by the compact // model on the entry and both register totals. var contribution UsageTotals if usage != nil { @@ -567,7 +513,7 @@ func (h *Harness) commitCompaction(c *coordinator, operationID string, capture E // the same transaction; the settlement itself carries no usage // because the compaction part already added the totals. if manual { - settledOp, settledSess, settledEntries, err := commitTerminalSettlement(tx, sessionID, operationID, committedSess, nextOp, replacedSession.Revision, replaced.Revision, OperationSuccess, "", nil, model.ModelRef{}, nil) + settledOp, settledSess, settledEntries, err := commitTerminalSettlement(tx, sessionID, operationID, committedSess, nextOp, replacedSession.Revision, replaced.Revision, OperationSuccess, "", nil, model.ModelRef{}, nil, false) if err != nil { return err } diff --git a/harness/compact_manual_public_test.go b/harness/compact_manual_public_test.go index ab08bf1e..36560837 100644 --- a/harness/compact_manual_public_test.go +++ b/harness/compact_manual_public_test.go @@ -13,72 +13,24 @@ import ( "encoding/json" "errors" "strings" - "sync" "testing" "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) var compactModelRef = model.ModelRef{Provider: "cprov", Model: "compact-x"} -// compactManualFixture wires one Harness whose conversation and compact -// transports are separate scripted models over the same prepared capture, -// with the fixture's preparation hook available for gating. -type compactManualFixture struct { - h *harness.Harness - store harness.Storage - cancel context.CancelFunc - prepMu sync.Mutex - preps []harness.PreparationRequest - prepareHook func(call int, req harness.PreparationRequest) (harness.PreparedExecution, error) - defaultPrep harness.PreparedExecution -} - +// compactManualCapture is the manual compaction fixture's capture: the +// conversation capture with the compact model identity. func compactManualCapture() harness.ExecutionCapture { capture := publicCapture() capture.Compact.Model = compactModelRef return capture } -func newCompactManualFixture(t *testing.T, store harness.Storage, conversation, compact *scriptModel) *compactManualFixture { - t.Helper() - f := &compactManualFixture{store: store, cancel: func() {}} - f.defaultPrep = harness.PreparedExecution{ - Capture: compactManualCapture(), - Open: func(context.Context, harness.OperationAdmission) (harness.Execution, error) { - return publicExecution(conversation.effect, compact.effect), nil - }, - } - ctx, cancel := context.WithCancel(context.Background()) - f.cancel = cancel - h, err := harness.New(ctx, harness.Dependencies{Storage: store, Prepare: func(_ context.Context, req harness.PreparationRequest) (harness.PreparedExecution, error) { - f.prepMu.Lock() - call := len(f.preps) - f.preps = append(f.preps, req) - hook := f.prepareHook - f.prepMu.Unlock() - if hook != nil { - return hook(call, req) - } - return f.defaultPrep, nil - }}) - if err != nil { - t.Fatalf("New: %v", err) - } - f.h = h - return f -} - -func (f *compactManualFixture) preparationCalls() []harness.PreparationRequest { - f.prepMu.Lock() - defer f.prepMu.Unlock() - return append([]harness.PreparationRequest(nil), f.preps...) -} - -func (f *compactManualFixture) close() { f.cancel() } - // summaryTurnStream assembles to one completed output carrying the given // summary text and reported usage. func summaryTurnStream(text string, usage model.Usage) model.Stream { @@ -97,9 +49,7 @@ func summaryAttempt(text string) publicAttempt { func compactionEntryIDs(t *testing.T, store harness.Storage, sessionID string) []string { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var ids []string for _, entry := range entries { if entry.Kind == harness.EntryCompaction { @@ -109,30 +59,44 @@ func compactionEntryIDs(t *testing.T, store harness.Storage, sessionID string) [ return ids } -// entryKindsByOperation returns the entry kinds owned by one Operation. +// entryKindsByOperation returns the entry kinds owned by one Operation +// through the shared entry-kind selection. func entryKindsByOperation(t *testing.T, store harness.Storage, sessionID, operationID string) []harness.EntryKind { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - var kinds []harness.EntryKind + testassert.NoError(t, err) + owned := make([]harness.Entry, 0, len(entries)) for _, entry := range entries { if entry.OperationID == operationID { - kinds = append(kinds, entry.Kind) + owned = append(owned, entry) + } + } + return kindsOf(owned) +} + +// settlementPayloadOf returns the one settlement entry payload of the given +// Operation. +func settlementPayloadOf(t *testing.T, store harness.Storage, sessionID, operationID string) string { + t.Helper() + entries, err := store.ReadEntries(context.Background(), sessionID, 0) + testassert.NoError(t, err) + var found string + for _, entry := range entries { + if entry.OperationID == operationID && entry.Kind == harness.EntryOperationSettlement { + testassert.Require(t, found == "", "operation %q carries more than one settlement entry", operationID) + found = string(entry.Payload) } } - return kinds + testassert.Require(t, found != "", "operation %q carries no settlement entry", operationID) + return found } // idleHistory runs one completed conversation turn so the Session carries // compactable history and is idle again. -func idleHistory(t *testing.T, f *compactManualFixture, session string) { +func idleHistory(t *testing.T, f *publicFixture, session string) { t.Helper() res, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") - if err != nil || res.Disposition != harness.DispositionAdmitted { - t.Fatalf("submit = %+v err %v, want admitted", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted, "submit = %+v err %v, want admitted", res, err) awaitTerminal(t, f.h, session, "op-1") } @@ -146,12 +110,8 @@ func awaitSettled(t *testing.T, h *harness.Harness, session, operation string) h if err == nil && rec.State.Terminal != nil { return rec } - if err != nil && !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("ReadOperation: %v", err) - } - if time.Now().After(deadline) { - t.Fatalf("operation %s never settled within the wait bound", operation) - } + testassert.Require(t, err == nil || errors.Is(err, harness.ErrNotFound), "ReadOperation: %v", err) + testassert.Require(t, !time.Now().After(deadline), "operation %s never settled within the wait bound", operation) time.Sleep(time.Millisecond) } } @@ -167,13 +127,10 @@ func compactWhenIdle(t *testing.T, h *harness.Harness, session, operation string if err == nil { return rec } - if !strings.Contains(err.Error(), "is not idle; admission requires an idle Session") { // only the transient idle-guard rejection retries - t.Fatalf("Compact %s: %v", operation, err) - } + // only the transient idle-guard rejection retries + testassert.Require(t, strings.Contains(err.Error(), "is not idle; admission requires an idle Session"), "Compact %s: %v", operation, err) last = err - if time.Now().After(deadline) { - t.Fatalf("Compact %s never admitted within the wait bound: %v", operation, last) - } + testassert.Require(t, !time.Now().After(deadline), "Compact %s never admitted within the wait bound: %v", operation, last) time.Sleep(time.Millisecond) } } @@ -224,7 +181,7 @@ func (s *compactCommitGateStore) Transact(ctx context.Context, fn func(harness.T } // parkedUsageCount reports whether one raw register usage section carries -// exactly the piece counts keyed by the compact model. +// exactly the summary counts keyed by the compact model. func parkedUsageCount(t *testing.T, raw json.RawMessage, wantRef model.ModelRef) bool { t.Helper() // The register's usage section encodes its model identity in the wire @@ -238,8 +195,9 @@ func parkedUsageCount(t *testing.T, raw json.RawMessage, wantRef model.ModelRef) Usage harness.UsageCount `json:"usage"` } `json:"by_model"` } - if err := json.Unmarshal(raw, &totals); err != nil { - t.Fatalf("decode usage section: %v", err) + { + err := json.Unmarshal(raw, &totals) + testassert.NoError(t, err) } for _, mu := range totals.ByModel { if mu.Model.Provider == wantRef.Provider && mu.Model.Model == wantRef.Model { @@ -254,16 +212,15 @@ func TestPublicManualCompactIdleCommitsAndSettles(t *testing.T) { conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("S1")) gated := &compactCommitGateStore{Storage: store, started: make(chan struct{}, 1), release: make(chan struct{})} - f := newCompactManualFixture(t, gated, conversation, compact) + f := newPublicFixtureWithJobs(t, gated, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) idleHistory(t, f, session) conversationCalls := len(conversation.seen()) res := compactWhenIdle(t, f.h, session, "c-1") - if res.Admission.RequestKind != harness.RequestKindCompact || res.Admission.AdmittedEntry != (harness.EntryRef{}) { - t.Fatalf("admission = %+v, want the compact kind with no admitted entry", res.Admission) - } + testassert.Require(t, res.Admission.RequestKind == harness.RequestKindCompact && res.Admission.AdmittedEntry == (harness.EntryRef{}), "admission = %+v, want the compact kind with no admitted entry", res.Admission) // The frozen instant: the manual settle transaction committed the // compaction entry and the already-terminal register state together, // and its caller has not returned — a split implementation would show @@ -274,51 +231,39 @@ func TestPublicManualCompactIdleCommitsAndSettles(t *testing.T) { t.Fatalf("the manual compact never committed its settle transaction") } parkedEntries, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("frozen entries read: %v", err) - } + testassert.NoError(t, err) var parkedCompaction *harness.Entry for i := range parkedEntries { if parkedEntries[i].Kind == harness.EntryCompaction { parkedCompaction = &parkedEntries[i] } } - if parkedCompaction == nil || parkedCompaction.OperationID != "c-1" { - t.Fatalf("parked compaction entry = %+v, want the c-1-owned commit", parkedCompaction) - } + testassert.Require(t, parkedCompaction != nil && parkedCompaction.OperationID == "c-1", "parked compaction entry = %+v, want the c-1-owned commit", parkedCompaction) var parkedWire struct { Summary string `json:"summary"` Usage *harness.UsageCount `json:"usage"` } - if err := json.Unmarshal(parkedCompaction.Payload, &parkedWire); err != nil { - t.Fatalf("decode parked compaction entry: %v", err) - } - if parkedWire.Summary != "S1" || parkedWire.Usage == nil || *parkedWire.Usage != (harness.UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5}) { - t.Fatalf("parked compaction entry = %+v, want the committed summary and piece usage", parkedWire) + { + err := json.Unmarshal(parkedCompaction.Payload, &parkedWire) + testassert.NoError(t, err) } + testassert.Require(t, parkedWire.Summary == "S1" && parkedWire.Usage != nil && *parkedWire.Usage == (harness.UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5}), "parked compaction entry = %+v, want the committed summary and summary usage", parkedWire) parkedSessionReg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("frozen session register read: %v", err) - } + testassert.NoError(t, err) var parkedSession struct { State struct { CompactionEntryID string `json:"compaction_entry_id"` Usage json.RawMessage `json:"usage"` } `json:"state"` } - if err := json.Unmarshal(parkedSessionReg.Payload, &parkedSession); err != nil { - t.Fatalf("decode frozen session register: %v", err) - } - if parkedSession.State.CompactionEntryID != parkedCompaction.ID { - t.Fatalf("parked compaction_entry_id = %q, want the committed entry %q", parkedSession.State.CompactionEntryID, parkedCompaction.ID) - } - if !parkedUsageCount(t, parkedSession.State.Usage, compactModelRef) { - t.Fatalf("parked session usage = %s, want the piece counts keyed by the compact model", parkedSessionReg.Payload) + { + err := json.Unmarshal(parkedSessionReg.Payload, &parkedSession) + testassert.NoError(t, err) } + testassert.Equal(t, parkedSession.State.CompactionEntryID, parkedCompaction.ID) + testassert.Require(t, parkedUsageCount(t, parkedSession.State.Usage, compactModelRef), "parked session usage = %s, want the summary counts keyed by the compact model", parkedSessionReg.Payload) parkedOpReg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: session, Kind: harness.RegisterOperation, OperationID: "c-1"}) - if err != nil { - t.Fatalf("frozen operation register read: %v", err) - } + testassert.NoError(t, err) var parkedOperation struct { State struct { Status harness.OperationState `json:"status"` @@ -330,55 +275,44 @@ func TestPublicManualCompactIdleCommitsAndSettles(t *testing.T) { Usage json.RawMessage `json:"usage"` } `json:"state"` } - if err := json.Unmarshal(parkedOpReg.Payload, &parkedOperation); err != nil { - t.Fatalf("decode frozen operation register: %v", err) - } - if parkedOperation.State.Status != harness.OperationSuccess || parkedOperation.State.Terminal == nil { - t.Fatalf("parked operation state = %s, want the register already terminal at the frozen instant", parkedOpReg.Payload) - } - if !parkedUsageCount(t, parkedOperation.State.Usage, compactModelRef) { - t.Fatalf("parked operation usage = %s, want the piece counts keyed by the compact model", parkedOpReg.Payload) + { + err := json.Unmarshal(parkedOpReg.Payload, &parkedOperation) + testassert.NoError(t, err) } + testassert.Require(t, parkedOperation.State.Status == harness.OperationSuccess && parkedOperation.State.Terminal != nil, "parked operation state = %s, want the register already terminal at the frozen instant", parkedOpReg.Payload) + testassert.Require(t, parkedUsageCount(t, parkedOperation.State.Usage, compactModelRef), "parked operation usage = %s, want the summary counts keyed by the compact model", parkedOpReg.Payload) close(gated.release) rec := awaitTerminal(t, f.h, session, "c-1") - if rec.State.Status != harness.OperationSuccess || rec.State.Terminal == nil { - t.Fatalf("compact operation state = %+v, want the success terminal", rec.State) + testassert.Require(t, rec.State.Status == harness.OperationSuccess && rec.State.Terminal != nil, "compact operation state = %+v, want the success terminal", rec.State) + { + got := len(compact.seen()) + testassert.Equal(t, got, 1) } - if got := len(compact.seen()); got != 1 { - t.Fatalf("compact transport calls = %d, want exactly one piece", got) - } - if got := len(conversation.seen()); got != conversationCalls { - t.Fatalf("conversation transport calls = %d, want unchanged during compaction", got) + { + got := len(conversation.seen()) + testassert.Equal(t, got, conversationCalls) } // No input entry and no model-visible user message: the compact // Operation owns only its compaction entry and its settlement. kinds := entryKindsByOperation(t, store, session, "c-1") - if len(kinds) != 2 || kinds[0] != harness.EntryCompaction || kinds[1] != harness.EntryOperationSettlement { - t.Fatalf("compact Operation entries = %v, want exactly one compaction entry and its settlement", kinds) + testassert.Require(t, len(kinds) == 2 && kinds[0] == harness.EntryCompaction && kinds[1] == harness.EntryOperationSettlement, "compact Operation entries = %v, want exactly one compaction entry and its settlement", kinds) + { + payload := settlementPayloadOf(t, store, session, "c-1") + testassert.Require(t, !strings.Contains(payload, "compaction_usage"), "manual success settlement payload %s, want the marker omitted", payload) } - if ids := compactionEntryIDs(t, store, session); len(ids) != 1 { - t.Fatalf("compaction entries = %v, want exactly one", ids) + { + ids := compactionEntryIDs(t, store, session) + testassert.Require(t, len(ids) == 1, "compaction entries = %v, want exactly one", ids) } - // The piece usage landed keyed by the compact model. + // The summary usage landed keyed by the compact model. view, err := f.h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - found := false - for _, mu := range view.TotalUsage.ByModel { - if mu.Model == compactModelRef { - found = mu.Usage == (harness.UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5}) - } - } - if !found { - t.Fatalf("session usage = %+v, want the piece counts keyed by the compact model", view.TotalUsage) - } + testassert.NoError(t, err) + pieces := harness.UsageByModelForTest(view.TotalUsage, compactModelRef) + testassert.Require(t, len(pieces.ByModel) == 1 && pieces.ByModel[0].Usage == (harness.UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5}), "session usage = %+v, want the summary counts keyed by the compact model", view.TotalUsage) // The compact preparation carried the compact request kind while the // conversation turn's preparation carried the message kind. preps := f.preparationCalls() - if len(preps) != 2 || preps[0].RequestKind != harness.RequestKindMessage || preps[1].RequestKind != harness.RequestKindCompact { - t.Fatalf("preparations = %+v, want the message and the compact kinds", preps) - } + testassert.Require(t, len(preps) == 2 && preps[0].RequestKind == harness.RequestKindMessage && preps[1].RequestKind == harness.RequestKindCompact, "preparations = %+v, want the message and the compact kinds", preps) // A retry with the same identity resolves the first compact Operation // without new work — the resolution precedes the idle check, so it @@ -386,39 +320,35 @@ func TestPublicManualCompactIdleCommitsAndSettles(t *testing.T) { before := len(compact.seen()) beforePreps := len(f.preparationCalls()) retry, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "c-1"}) - if err != nil { - t.Fatalf("retry Compact: %v", err) - } - if retry.Admission.AdmittedAt != res.Admission.AdmittedAt || retry.State.Status != harness.OperationSuccess { - t.Fatalf("retry record = %+v, want the first settled compact Operation", retry) - } + testassert.NoError(t, err) + testassert.Require(t, retry.Admission.AdmittedAt == res.Admission.AdmittedAt && retry.State.Status == harness.OperationSuccess, "retry record = %+v, want the first settled compact Operation", retry) if len(compact.seen()) != before || len(f.preparationCalls()) != beforePreps { t.Fatalf("retry ran new work: compact calls %d preps %d", len(compact.seen()), len(f.preparationCalls())) } - if ids := compactionEntryIDs(t, store, session); len(ids) != 1 { - t.Fatalf("compaction entries after retry = %v, want exactly one", ids) + { + ids := compactionEntryIDs(t, store, session) + testassert.Require(t, len(ids) == 1, "compaction entries after retry = %v, want exactly one", ids) } // The next conversation request proceeds from the summary projection. - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "second"); err != nil { - t.Fatalf("submit op-2: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "second") + testassert.NoError(t, err) } awaitSettled(t, f.h, session, "op-2") reqs := conversation.seen() - if len(reqs) != conversationCalls+1 { - t.Fatalf("conversation transport calls = %d, want one more after the compaction", len(reqs)) - } + testassert.Equal(t, len(reqs), conversationCalls+1) texts := texts(reqs[len(reqs)-1]) - if len(texts) != 3 || !strings.Contains(texts[1], "[Previous conversation summary]") || !strings.Contains(texts[1], "S1") || texts[2] != "second" { - t.Fatalf("post-compaction request texts = %q, want the system, summary, and new message", texts) - } + testassert.Require(t, len(texts) == 3 && strings.Contains(texts[1], "[Previous conversation summary]") && strings.Contains(texts[1], "S1") && texts[2] == "second", "post-compaction request texts = %q, want the system, summary, and new message", texts) // Cross-kind identity reuse is rejected on both sides. - if _, err := submit(t, f.h, session, "c-1", harness.MessageModeRegular, "reuse"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit reuse of the compact identity = %v, want ErrInvalid", err) + { + _, err := submit(t, f.h, session, "c-1", harness.MessageModeRegular, "reuse") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit reuse of the compact identity = %v, want ErrInvalid", err) } - if _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "op-1"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("Compact reuse of a message identity = %v, want ErrInvalid", err) + { + _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "op-1"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "Compact reuse of a message identity = %v, want ErrInvalid", err) } }) } @@ -433,26 +363,24 @@ func TestPublicManualCompactRejectsNonIdle(t *testing.T) { eachStore(t, func(t *testing.T, store harness.Storage) { conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("S1")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) gate := make(chan struct{}) conversation.gate = gate res, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") - if err != nil || res.Disposition != harness.DispositionAdmitted { - t.Fatalf("submit = %+v err %v, want admitted", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted, "submit = %+v err %v, want admitted", res, err) <-conversation.arrived rejectIdle := func(stage string) { t.Helper() _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "c-1"}) - if err == nil || !strings.Contains(err.Error(), "is not idle; admission requires an idle Session") { - t.Fatalf("%s: Compact error = %v, want the idle-guard rejection", stage, err) - } - if _, err := f.h.ReadOperation(context.Background(), session, "c-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("%s: compact Operation = %v, want not found", stage, err) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "is not idle; admission requires an idle Session"), "%s: Compact error = %v, want the idle-guard rejection", stage, err) + { + _, err := f.h.ReadOperation(context.Background(), session, "c-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "%s: compact Operation = %v, want not found", stage, err) } } @@ -461,14 +389,16 @@ func TestPublicManualCompactRejectsNonIdle(t *testing.T) { // A Session with buffered steering: the boundary exit delivers it // through its own successor admission; Compact must not admit beside it. - if res, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "steer"); err != nil || res.Disposition != harness.DispositionSteering { - t.Fatalf("steering submit = %+v err %v, want steering", res, err) + { + res, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "steer") + testassert.Require(t, err == nil && res.Disposition == harness.DispositionSteering, "steering submit = %+v err %v, want steering", res, err) } rejectIdle("steering buffered") // A Session with a queued message. - if res, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "queue"); err != nil || res.Disposition != harness.DispositionQueued { - t.Fatalf("queued submit = %+v err %v, want queued", res, err) + { + res, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "queue") + testassert.Require(t, err == nil && res.Disposition == harness.DispositionQueued, "queued submit = %+v err %v, want queued", res, err) } rejectIdle("queued buffered") @@ -490,11 +420,13 @@ func TestPublicManualCompactRejectsNonIdle(t *testing.T) { close(releaseDrain) // The normal drain delivered the queued message; the compaction never // overtook it and never entered either buffer. - if rec := awaitSettled(t, f.h, session, "op-3"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("queued operation state = %+v, want success", rec.State.Status) + { + rec := awaitSettled(t, f.h, session, "op-3") + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } - if _, err := f.h.ReadOperation(context.Background(), session, "c-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("compact Operation after the drain = %v, want not found", err) + { + _, err := f.h.ReadOperation(context.Background(), session, "c-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "compact Operation after the drain = %v, want not found", err) } }) } @@ -507,7 +439,8 @@ func TestPublicManualCompactSnapshotShape(t *testing.T) { eachStore(t, func(t *testing.T, store harness.Storage) { conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("S1"), summaryAttempt("S2"), summaryAttempt("S3")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) idleHistory(t, f, session) @@ -521,35 +454,26 @@ func TestPublicManualCompactSnapshotShape(t *testing.T) { compactWhenIdle(t, f.h, session, "c-2") awaitSettled(t, f.h, session, "c-2") reqs := compact.seen() - if len(reqs) != 2 { - t.Fatalf("compact transport calls = %d, want two", len(reqs)) - } + testassert.Equal(t, len(reqs), 2) summaryOnly := texts(reqs[1])[1] - if !strings.Contains(summaryOnly, "[Previous conversation summary]") || !strings.Contains(summaryOnly, "S1") { - t.Fatalf("summary-only request = %q, want the serialized summary message", summaryOnly) - } - if strings.Contains(summaryOnly, "hello") { - t.Fatalf("summary-only request carried pre-boundary entries: %q", summaryOnly) - } + testassert.Require(t, strings.Contains(summaryOnly, "[Previous conversation summary]") && strings.Contains(summaryOnly, "S1") && !strings.Contains(summaryOnly, "hello"), "summary-only request = %q, want the serialized summary message; summary-only request carried pre-boundary entries: %q", summaryOnly, summaryOnly) // With later entries, the snapshot carries the summary and the // post-boundary entries. - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "second turn"); err != nil { - t.Fatalf("submit op-2: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "second turn") + testassert.NoError(t, err) } awaitSettled(t, f.h, session, "op-2") compactWhenIdle(t, f.h, session, "c-3") awaitSettled(t, f.h, session, "c-3") reqs = compact.seen() - if len(reqs) != 3 { - t.Fatalf("compact transport calls = %d, want three", len(reqs)) - } + testassert.Equal(t, len(reqs), 3) both := texts(reqs[2])[1] - if !strings.Contains(both, "S2") || !strings.Contains(both, "second turn") { - t.Fatalf("snapshot request = %q, want the summary and the later entries", both) - } - if ids := compactionEntryIDs(t, store, session); len(ids) != 3 { - t.Fatalf("compaction entries = %v, want three", ids) + testassert.Require(t, strings.Contains(both, "S2") && strings.Contains(both, "second turn"), "snapshot request = %q, want the summary and the later entries", both) + { + ids := compactionEntryIDs(t, store, session) + testassert.Require(t, len(ids) == 3, "compaction entries = %v, want three", ids) } }) } @@ -562,53 +486,99 @@ func TestPublicManualCompactEmptyConversationFails(t *testing.T) { eachStore(t, func(t *testing.T, store harness.Storage) { conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("S1")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) - if _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "c-1"}); err != nil { - t.Fatalf("Compact: %v", err) + { + _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "c-1"}) + testassert.NoError(t, err) } rec := awaitTerminal(t, f.h, session, "c-1") - if rec.State.Status != harness.OperationFailure || rec.State.Terminal.Detail != "nothing to compact" { - t.Fatalf("compact operation = %+v, want the failure with the retained detail", rec.State) - } - if got := len(compact.seen()); got != 0 { - t.Fatalf("compact transport calls = %d, want none", got) + testassert.Require(t, rec.State.Status == harness.OperationFailure && rec.State.Terminal.Detail == "nothing to compact", "compact operation = %+v, want the failure with the retained detail", rec.State) + { + got := len(compact.seen()) + testassert.Equal(t, got, 0) } kinds := entryKindsByOperation(t, store, session, "c-1") - if len(kinds) != 1 || kinds[0] != harness.EntryOperationSettlement { - t.Fatalf("compact Operation entries = %v, want only the settlement", kinds) + testassert.Require(t, len(kinds) == 1 && kinds[0] == harness.EntryOperationSettlement, "compact Operation entries = %v, want only the settlement", kinds) + { + payload := settlementPayloadOf(t, store, session, "c-1") + testassert.Require(t, strings.Contains(payload, `"compaction_usage":true`), "failed compact settlement payload %s, want the inert summary marker durable", payload) } - if ids := compactionEntryIDs(t, store, session); len(ids) != 0 { - t.Fatalf("compaction entries = %v, want none", ids) + { + ids := compactionEntryIDs(t, store, session) + testassert.Require(t, len(ids) == 0, "compaction entries = %v, want none", ids) } sessionReg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("session register read: %v", err) - } + testassert.NoError(t, err) var sessionState struct { State struct { CompactionEntryID string `json:"compaction_entry_id"` } `json:"state"` } - if err := json.Unmarshal(sessionReg.Payload, &sessionState); err != nil { - t.Fatalf("decode session register: %v", err) - } - if sessionState.State.CompactionEntryID != "" { - t.Fatalf("compaction_entry_id = %q, want it unchanged", sessionState.State.CompactionEntryID) + { + err := json.Unmarshal(sessionReg.Payload, &sessionState) + testassert.NoError(t, err) } + testassert.Equal(t, sessionState.State.CompactionEntryID, "") // Subsequent messages never see a synthetic compact request. - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } awaitSettled(t, f.h, session, "op-1") reqs := conversation.seen() - if len(reqs) != 1 { - t.Fatalf("conversation calls = %d, want one", len(reqs)) + testassert.Equal(t, len(reqs), 1) + { + texts := texts(reqs[0]) + testassert.Require(t, len(texts) == 2 && texts[1] == "hello", "first conversation request = %q, want the system and the user message alone", texts) } - if texts := texts(reqs[0]); len(texts) != 2 || texts[1] != "hello" { - t.Fatalf("first conversation request = %q, want the system and the user message alone", texts) + }) +} + +// TestPublicManualCompactFailedSummaryMarkerSurvivesReload proves the usage +// provenance across a cold reload on both stores: a manual compaction whose +// summary fails after reporting usage leaves that usage durably on the marked +// settlement entry, and the conversation measurement reads the assistant +// turn's usage unchanged through the warm view and through a freshly built +// Harness over the same store — whose Session graph cold-loads from storage +// via validateSessionGraph — because the marked settlement never supplies the +// measurement. A reload that lost the marker would surface the settlement's +// {5,7} in place of the assistant's {3,1,2} and flip this assertion. +func TestPublicManualCompactFailedSummaryMarkerSurvivesReload(t *testing.T) { + eachStore(t, func(t *testing.T, store harness.Storage) { + conversation := newScriptModel(publicTurn()) + compact := newScriptModel(publicAttempt{stream: publicPartialTurnStream()}) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() + defer f.close() + session := createSession(t, f.h) + idleHistory(t, f, session) + + compactWhenIdle(t, f.h, session, "c-1") + rec := awaitTerminal(t, f.h, session, "c-1") + testassert.Require(t, rec.State.Status == harness.OperationFailure && strings.Contains(rec.State.Terminal.Detail, "provider failure"), "compact operation = %+v, want the failed summary terminal", rec.State) + payload := settlementPayloadOf(t, store, session, "c-1") + testassert.Require(t, strings.Contains(payload, `"compaction_usage":true`) && strings.Contains(payload, `"input_tokens":5`), "failed compact settlement payload %s, want the marked {5,7} summary usage durable", payload) + + want := harness.UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2} + // The cold reload first: a fresh Harness over the same store + // cold-loads the Session graph from storage; the same value the warm + // live graph returns must survive the durable round-trip. + cold := newPublicFixtureWithJobs(t, store, nil, newScriptModel(publicTurn()), nil, newScriptModel(publicTurn()).effect) + cold.defaultPrep.Capture = compactManualCapture() + defer cold.close() + view, err := cold.h.ReadSessionView(context.Background(), session) + testassert.NoError(t, err) + got := view.History.LatestContextUsage() + testassert.Require(t, got != nil && *got == want, "reloaded measurement = %+v, want the assistant turn's %+v: the cold load lost the settlement marker", got, want) + warm, err := f.h.ReadSessionView(context.Background(), session) + testassert.NoError(t, err) + { + measured := warm.History.LatestContextUsage() + testassert.Require(t, measured != nil && *measured == *got, "warm measurement = %+v, want the reloaded view's %+v", measured, got) } }) } @@ -622,7 +592,8 @@ func TestPublicManualCompactSubmitAfterAdmissionStaysBuffered(t *testing.T) { eachStore(t, func(t *testing.T, store harness.Storage) { conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("S1")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) idleHistory(t, f, session) @@ -653,39 +624,33 @@ func TestPublicManualCompactSubmitAfterAdmissionStaysBuffered(t *testing.T) { // A Submit arriving after Compact starts stays buffered. res, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "second turn") - if err != nil || res.Disposition != harness.DispositionSteering { - t.Fatalf("submit during compaction = %+v err %v, want steering", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionSteering, "submit during compaction = %+v err %v, want steering", res, err) // The pure manual snapshot never carried the buffered message. snapshot := texts(compact.seen()[0])[1] - if strings.Contains(snapshot, "second turn") { - t.Fatalf("compact snapshot carried the buffered message: %q", snapshot) - } + testassert.Require(t, !strings.Contains(snapshot, "second turn"), "compact snapshot carried the buffered message: %q", snapshot) // A second Compact under the running compaction is rejected. - if _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "c-2"}); err == nil || !strings.Contains(err.Error(), "is not idle; admission requires an idle Session") { - t.Fatalf("second Compact = %v, want the idle-guard rejection", err) + { + _, err := f.h.Compact(context.Background(), harness.CompactRequest{SessionID: session, OperationID: "c-2"}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "is not idle; admission requires an idle Session"), "second Compact = %v, want the idle-guard rejection", err) } close(gate) - if err := <-compactErr; err != nil { - t.Fatalf("Compact: %v", err) + { + err := <-compactErr + testassert.NoError(t, err) } awaitSettled(t, f.h, session, "c-1") // The buffered message is delivered only after the compaction settles. awaitSettled(t, f.h, session, "op-2") reqs := conversation.seen() - if len(reqs) != 2 { - t.Fatalf("conversation calls = %d, want the pre-compaction turn and the drained one", len(reqs)) - } + testassert.Equal(t, len(reqs), 2) texts := texts(reqs[1]) - if len(texts) != 3 || !strings.Contains(texts[1], "S1") || texts[2] != "second turn" { - t.Fatalf("drained request = %q, want the summary projection with the buffered message", texts) - } + testassert.Require(t, len(texts) == 3 && strings.Contains(texts[1], "S1") && texts[2] == "second turn", "drained request = %q, want the summary projection with the buffered message", texts) }) } // TestPublicManualCompactInterruptKeepsPriorProjection proves the interrupt -// row: a live interrupt of a manual compact Operation parked at its piece +// row: a live interrupt of a manual compact Operation parked at its summary request // settles the interruption with no new compaction entry, the previous // projection stays current, and the Session's current Operation clears. func TestPublicManualCompactInterruptKeepsPriorProjection(t *testing.T) { @@ -693,24 +658,24 @@ func TestPublicManualCompactInterruptKeepsPriorProjection(t *testing.T) { ctx := context.Background() conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("S1")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) idleHistory(t, f, session) compactWhenIdle(t, f.h, session, "c-1") - if rec := awaitTerminal(t, f.h, session, "c-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("first compact = %+v, want success", rec.State) + { + rec := awaitTerminal(t, f.h, session, "c-1") + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "first compact = %+v, want success", rec.State) } prior := compactionEntryIDs(t, store, session) - if len(prior) != 1 { - t.Fatalf("prior compaction entries = %v, want exactly one", prior) - } + testassert.Require(t, len(prior) == 1, "prior compaction entries = %v, want exactly one", prior) priorID := prior[0] - // The second compact's piece parks until its own effect context + // The second compact's summary request parks until its own effect context // ends: the interrupt releases it and settles the interruption. - pieceStarted := make(chan struct{}, 1) + summaryStarted := make(chan struct{}, 1) f.prepareHook = func(call int, req harness.PreparationRequest) (harness.PreparedExecution, error) { if req.RequestKind == harness.RequestKindCompact && call >= 2 { return harness.PreparedExecution{ @@ -720,10 +685,10 @@ func TestPublicManualCompactInterruptKeepsPriorProjection(t *testing.T) { Model: conversation.effect, CompactModel: func(ctx context.Context, _ model.Request) (model.Stream, error) { select { - case pieceStarted <- struct{}{}: + case summaryStarted <- struct{}{}: default: } - <-ctx.Done() // the interrupt ends the parked piece + <-ctx.Done() // the interrupt ends the parked summary request return nil, ctx.Err() }, Tool: func(_ context.Context, call model.ToolCall) harness.PreparedTool { @@ -738,49 +703,43 @@ func TestPublicManualCompactInterruptKeepsPriorProjection(t *testing.T) { } compactWhenIdle(t, f.h, session, "c-2") select { - case <-pieceStarted: + case <-summaryStarted: case <-time.After(10 * time.Second): - t.Fatalf("the second compact piece never started") + t.Fatalf("the second compact summary request never started") } - if err := f.h.Interrupt(ctx, session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := f.h.Interrupt(ctx, session) + testassert.NoError(t, err) } rec := awaitTerminal(t, f.h, session, "c-2") - if rec.State.Status != harness.OperationInterruption { - t.Fatalf("interrupted compact = %+v, want the interruption terminal", rec.State) - } + testassert.Require(t, rec.State.Status == harness.OperationInterruption, "interrupted compact = %+v, want the interruption terminal", rec.State) // No new compaction entry: the prior projection stays current. - if ids := compactionEntryIDs(t, store, session); len(ids) != 1 || ids[0] != priorID { - t.Fatalf("compaction entries after the interrupt = %v, want only the prior %q", ids, priorID) + { + ids := compactionEntryIDs(t, store, session) + testassert.Require(t, len(ids) == 1 && ids[0] == priorID, "compaction entries after the interrupt = %v, want only the prior %q", ids, priorID) } reg, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("session register: %v", err) - } + testassert.NoError(t, err) var wire struct { State struct { CurrentOperationID string `json:"current_operation_id"` CompactionEntryID string `json:"compaction_entry_id"` } `json:"state"` } - if err := json.Unmarshal(reg.Payload, &wire); err != nil { - t.Fatalf("decode session register: %v", err) - } - if wire.State.CompactionEntryID != priorID { - t.Fatalf("compaction_entry_id = %q, want the prior projection %q", wire.State.CompactionEntryID, priorID) + { + err := json.Unmarshal(reg.Payload, &wire) + testassert.NoError(t, err) } + testassert.Equal(t, wire.State.CompactionEntryID, priorID) deadline := time.Now().Add(10 * time.Second) for wire.State.CurrentOperationID != "" { - if time.Now().After(deadline) { - t.Fatalf("current Operation %q never cleared after the interruption", wire.State.CurrentOperationID) - } + testassert.Require(t, !time.Now().After(deadline), "current Operation %q never cleared after the interruption", wire.State.CurrentOperationID) time.Sleep(time.Millisecond) reg, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("session register reread: %v", err) - } - if err := json.Unmarshal(reg.Payload, &wire); err != nil { - t.Fatalf("decode session register reread: %v", err) + testassert.NoError(t, err) + { + err := json.Unmarshal(reg.Payload, &wire) + testassert.NoError(t, err) } } }) diff --git a/harness/compact_manual_test.go b/harness/compact_manual_test.go index 794df1ab..ee1e7909 100644 --- a/harness/compact_manual_test.go +++ b/harness/compact_manual_test.go @@ -10,6 +10,8 @@ import ( "errors" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // insertForeignCompactAdmission publishes one complete compact admission of @@ -51,83 +53,65 @@ func compactOperationRecord(operationID string) OperationRecord { func TestCompactAdmissionKindResolution(t *testing.T) { t.Run("compact resolves its own existing identity without new work", func(t *testing.T) { store := freshSessionStore(t) - if err := store.Transact(context.Background(), func(tx Transaction) error { - return insertForeignCompactAdmission(t, tx, testSessionID, "cx-1") - }); err != nil { - t.Fatalf("foreign compact admission: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return insertForeignCompactAdmission(t, tx, testSessionID, "cx-1") + }) + testassert.NoError(t, err) } stub := newPrepareStub(validPrepared()) h := newTestHarness(t, store, stub.prepare) beforeEntries, beforeRegs := storedSessionState(store, testSessionID) rec, err := h.Compact(context.Background(), CompactRequest{SessionID: testSessionID, OperationID: "cx-1"}) - if err != nil { - t.Fatalf("Compact: %v", err) - } - if rec.Admission.RequestKind != RequestKindCompact || rec.Admission.AdmittedEntry != (EntryRef{}) { - t.Fatalf("resolved record = %+v, want the existing compact admission", rec.Admission) - } - if rec.State.Status != OperationRunning { - t.Fatalf("resolved state = %+v, want the running first Operation", rec.State) - } - if got := stub.callCount(); got != 0 { - t.Fatalf("preparation calls = %d, want none on an existing resolution", got) + testassert.NoError(t, err) + testassert.Require(t, rec.Admission.RequestKind == RequestKindCompact && rec.Admission.AdmittedEntry == (EntryRef{}) && rec.State.Status == OperationRunning, "resolved record = %+v, want the existing compact admission; resolved state = %+v, want the running first Operation", rec.Admission, rec.State) + { + got := stub.callCount() + testassert.Equal(t, got, 0) } afterEntries, afterRegs := storedSessionState(store, testSessionID) - if len(afterEntries) != len(beforeEntries) || len(afterRegs) != len(beforeRegs) { - t.Fatalf("retry published entries %v registers %v, want the unchanged %v %v", afterEntries, afterRegs, beforeEntries, beforeRegs) - } + testassert.Require(t, len(afterEntries) == len(beforeEntries) && len(afterRegs) == len(beforeRegs), "retry published entries %v registers %v, want the unchanged %v %v", afterEntries, afterRegs, beforeEntries, beforeRegs) }) t.Run("compact rejects an existing message identity before the idle check", func(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("x")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("x")) + testassert.Equal(t, disposition, DispositionAdmitted) } _, err := h.Compact(context.Background(), CompactRequest{SessionID: testSessionID, OperationID: testOpID}) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("compact reuse of a message identity = %v, want ErrInvalid", err) - } - if strings.Contains(err.Error(), "is not idle") { - t.Fatalf("the resolution kind check must precede the idle guard: %v", err) - } - if !strings.Contains(err.Error(), "different request kind") { - t.Fatalf("error %v, want the request-kind rejection text", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid) && !strings.Contains(err.Error(), "is not idle") && strings.Contains(err.Error(), "different request kind"), "compact reuse of a message identity = %v, want ErrInvalid; the resolution kind check must precede the idle guard: %v; error %v, want the request-kind rejection text", err, err, err) }) t.Run("message admission rejects a compact identity at the reserved path", func(t *testing.T) { store := freshSessionStore(t) - if err := store.Transact(context.Background(), func(tx Transaction) error { - return insertForeignCompactAdmission(t, tx, testSessionID, "cx-1") - }); err != nil { - t.Fatalf("foreign compact admission: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return insertForeignCompactAdmission(t, tx, testSessionID, "cx-1") + }) + testassert.NoError(t, err) } h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) c := leaseCoordinator(t, h, testSessionID) release, err := h.reserve(context.Background(), c) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) defer release() _, _, _, err = h.admitReserved(context.Background(), c, admissionRequest{Kind: RequestKindMessage, SessionID: testSessionID, OperationID: "cx-1", Origin: InputOriginUser, Content: admissionContent("x")}) - if !errors.Is(err, ErrInvalid) || !strings.Contains(err.Error(), "different request kind") { - t.Fatalf("reserved reuse = %v, want the request-kind rejection", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "different request kind"), "reserved reuse = %v, want the request-kind rejection", err) }) t.Run("submit rejects a compact identity at its pre-routing check", func(t *testing.T) { store := freshSessionStore(t) - if err := store.Transact(context.Background(), func(tx Transaction) error { - return insertForeignCompactAdmission(t, tx, testSessionID, "cx-1") - }); err != nil { - t.Fatalf("foreign compact admission: %v", err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + return insertForeignCompactAdmission(t, tx, testSessionID, "cx-1") + }) + testassert.NoError(t, err) } h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) _, err := h.Submit(context.Background(), SubmitRequest{SessionID: testSessionID, OperationID: "cx-1", Origin: InputOriginUser, Content: admissionContent("x"), Mode: MessageModeRegular}) - if !errors.Is(err, ErrInvalid) || !strings.Contains(err.Error(), "different request kind") { - t.Fatalf("submit reuse = %v, want the request-kind rejection", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "different request kind"), "submit reuse = %v, want the request-kind rejection", err) }) } @@ -145,9 +129,7 @@ func TestCompactAdmissionConflictKindCheck(t *testing.T) { h := newTestHarness(t, store, stub.prepare) c := leaseCoordinator(t, h, testSessionID) release, err := h.reserve(context.Background(), c) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) defer release() type outcome struct { rec *OperationRecord @@ -169,15 +151,14 @@ func TestCompactAdmissionConflictKindCheck(t *testing.T) { out <- outcome{err: err} }() <-stub.arrived - if err := store.Transact(context.Background(), foreign); err != nil { - t.Fatalf("foreign admission: %v", err) + { + err := store.Transact(context.Background(), foreign) + testassert.NoError(t, err) } close(gate) res := <-out if wantExisting { - if res.err != nil || res.rec == nil { - t.Fatalf("raced admission = (%+v, %v), want the foreign winner's record", res.rec, res.err) - } + testassert.Require(t, res.err == nil && res.rec != nil, "raced admission = (%+v, %v), want the foreign winner's record", res.rec, res.err) } else if !errors.Is(res.err, ErrInvalid) || !strings.Contains(res.err.Error(), "different request kind") { t.Fatalf("raced admission error = %v, want the request-kind rejection", res.err) } @@ -200,9 +181,7 @@ func TestCompactAdmissionConflictKindCheck(t *testing.T) { rec := race(t, RequestKindCompact, func(tx Transaction) error { return insertForeignCompactAdmission(t, tx, testSessionID, testOpID) }, true) - if rec.Admission.RequestKind != RequestKindCompact { - t.Fatalf("resolved record = %+v, want the compact winner", rec.Admission) - } + testassert.Require(t, rec.Admission.RequestKind == RequestKindCompact, "resolved record = %+v, want the compact winner", rec.Admission) }) } @@ -212,31 +191,28 @@ func tamperOperationAdmission(t *testing.T, store *graphStorage, sessionID, oper t.Helper() key := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: operationID} reg, err := store.ReadRegister(context.Background(), key) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) payload := map[string]json.RawMessage{} - if err := json.Unmarshal(reg.Payload, &payload); err != nil { - t.Fatalf("unmarshal register payload: %v", err) + { + err := json.Unmarshal(reg.Payload, &payload) + testassert.NoError(t, err) } admission := map[string]json.RawMessage{} - if err := json.Unmarshal(payload["admission"], &admission); err != nil { - t.Fatalf("unmarshal admission: %v", err) + { + err := json.Unmarshal(payload["admission"], &admission) + testassert.NoError(t, err) } mutate(admission) mutatedAdmission, err := json.Marshal(admission) - if err != nil { - t.Fatalf("marshal admission: %v", err) - } + testassert.NoError(t, err) mutated, err := json.Marshal(map[string]json.RawMessage{"admission": mutatedAdmission, "state": payload["state"]}) - if err != nil { - t.Fatalf("marshal payload: %v", err) - } - if err := store.Transact(context.Background(), func(tx Transaction) error { - _, err := tx.ReplaceRegister(key, reg.Revision, mutated) - return err - }); err != nil { - t.Fatalf("tamper: %v", err) + testassert.NoError(t, err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + _, err := tx.ReplaceRegister(key, reg.Revision, mutated) + return err + }) + testassert.NoError(t, err) } } @@ -267,23 +243,16 @@ func TestCompactAdmissionForkKindArm(t *testing.T) { OperationID: "cx-1", Content: admissionContent("fork input"), }) - if !errors.Is(err, ErrInvalid) || !strings.Contains(err.Error(), "different fork lineage or request kind") { - t.Fatalf("fork over a compact identity = %v, want the lineage-or-kind rejection", err) - } - if got := stub.callCount(); got != 0 { - t.Fatalf("preparation calls = %d, want none — the kind check precedes preparation", got) + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "different fork lineage or request kind"), "fork over a compact identity = %v, want the lineage-or-kind rejection", err) + { + got := stub.callCount() + testassert.Equal(t, got, 0) } ids, err := store.ListSessionIDs(context.Background()) - if err != nil { - t.Fatalf("ListSessionIDs: %v", err) - } - if len(ids) != 1 { - t.Fatalf("sessions after the rejected fork = %v, want only the seeded one", ids) - } + testassert.NoError(t, err) + testassert.Require(t, len(ids) == 1, "sessions after the rejected fork = %v, want only the seeded one", ids) entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 2 { - t.Fatalf("rejected fork published entries %v registers %v, want the unchanged fixture", entries, regs) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 2, "rejected fork published entries %v registers %v, want the unchanged fixture", entries, regs) } // TestCompactOperationGraphInvariant proves the request-kind-specific @@ -301,8 +270,9 @@ func TestCompactOperationGraphInvariant(t *testing.T) { t.Run("compact operation without an admitted input validates", func(t *testing.T) { store := validCompactGraph().storage(t) - if _, err := validateFixture(t, store, testSessionID); err != nil { - t.Fatalf("compact graph: %v", err) + { + _, err := validateFixture(t, store, testSessionID) + testassert.NoError(t, err) } }) @@ -322,8 +292,9 @@ func TestCompactOperationGraphInvariant(t *testing.T) { tamperOperationAdmission(t, store, testSessionID, "cx-1", func(admission map[string]json.RawMessage) { admission["admitted_entry"] = json.RawMessage(`{"session_id":"` + testSessionID + `","entry_id":"` + hexID(1) + `"}`) }) - if _, err := validateFixture(t, store, testSessionID); !isCorruption(err) { - t.Fatalf("compact graph with an admitted entry = %v, want corruption", err) + { + _, err := validateFixture(t, store, testSessionID) + testassert.Require(t, isCorruption(err), "compact graph with an admitted entry = %v, want corruption", err) } }) @@ -332,29 +303,26 @@ func TestCompactOperationGraphInvariant(t *testing.T) { tamperOperationAdmission(t, store, testSessionID, testOpID, func(admission map[string]json.RawMessage) { delete(admission, "admitted_entry") }) - if _, err := validateFixture(t, store, testSessionID); !isCorruption(err) { - t.Fatalf("message graph without its admitted input = %v, want corruption", err) + { + _, err := validateFixture(t, store, testSessionID) + testassert.Require(t, isCorruption(err), "message graph without its admitted input = %v, want corruption", err) } }) t.Run("recovery settles a running compact Operation and preserves the invariant", func(t *testing.T) { store := validCompactGraph().storage(t) - if err := Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := Recover(context.Background(), store) + testassert.NoError(t, err) } reg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: "cx-1"}) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) op, err := decodeOperationRegister(reg) - if err != nil { - t.Fatalf("decodeOperationRegister: %v", err) - } - if op.State.Status != OperationInterruption || op.State.Terminal == nil { - t.Fatalf("recovered state = %+v, want the interruption terminal", op.State) - } - if _, err := validateFixture(t, store, testSessionID); err != nil { - t.Fatalf("post-recovery graph: %v", err) + testassert.NoError(t, err) + testassert.Require(t, op.State.Status == OperationInterruption && op.State.Terminal != nil, "recovered state = %+v, want the interruption terminal", op.State) + { + _, err := validateFixture(t, store, testSessionID) + testassert.NoError(t, err) } }) @@ -363,11 +331,13 @@ func TestCompactOperationGraphInvariant(t *testing.T) { tamperOperationAdmission(t, store, testSessionID, "cx-1", func(admission map[string]json.RawMessage) { admission["admitted_entry"] = json.RawMessage(`{"session_id":"` + testSessionID + `","entry_id":"` + hexID(1) + `"}`) }) - if err := Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := Recover(context.Background(), store) + testassert.NoError(t, err) } - if _, err := validateFixture(t, store, testSessionID); !isCorruption(err) { - t.Fatalf("post-recovery graph = %v, want the preserved corruption", err) + { + _, err := validateFixture(t, store, testSessionID) + testassert.Require(t, isCorruption(err), "post-recovery graph = %v, want the preserved corruption", err) } }) } @@ -378,19 +348,11 @@ func TestCompactOperationGraphInvariant(t *testing.T) { func TestCompactAdmissionCodecRules(t *testing.T) { compact := compactOperationRecord(testOpID) raw, err := encodeOperationRegister(compact) - if err != nil { - t.Fatalf("encode compact register: %v", err) - } - if strings.Contains(string(raw), `"admitted_entry"`) { - t.Fatalf("compact admission carries the admitted_entry member: %s", raw) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(raw), `"admitted_entry"`), "compact admission carries the admitted_entry member: %s", raw) decoded, err := decodeOperationRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: raw}) - if err != nil { - t.Fatalf("decode compact register: %v", err) - } - if decoded.Admission.RequestKind != RequestKindCompact || decoded.Admission.AdmittedEntry != (EntryRef{}) { - t.Fatalf("decoded admission = %+v, want the compact kind with no admitted entry", decoded.Admission) - } + testassert.NoError(t, err) + testassert.Require(t, decoded.Admission.RequestKind == RequestKindCompact && decoded.Admission.AdmittedEntry == (EntryRef{}), "decoded admission = %+v, want the compact kind with no admitted entry", decoded.Admission) // A compact payload carrying the member rejects. admission := wireObject(t, raw)["admission"] @@ -399,16 +361,15 @@ func TestCompactAdmissionCodecRules(t *testing.T) { // A message payload without the member rejects. messageRaw, err := encodeOperationRegister(validOperationRecord()) - if err != nil { - t.Fatalf("encode message register: %v", err) - } + testassert.NoError(t, err) messageless := setKey(messageRaw, "admission", deleteKey(t, wireObject(t, messageRaw)["admission"], "admitted_entry")) // An unknown kind rejects on both directions. unknown := compactOperationRecord(testOpID) unknown.Admission.RequestKind = RequestKind("bogus") - if _, err := encodeOperationRegister(unknown); err == nil { - t.Fatalf("unknown request kind encoded") + { + _, err := encodeOperationRegister(unknown) + testassert.Require(t, err != nil, "unknown request kind encoded") } unknownWire := setKey(raw, "admission", setKey(admission, "request_kind", json.RawMessage(`"bogus"`))) @@ -426,12 +387,8 @@ func TestCompactAdmissionCodecRules(t *testing.T) { for _, tc := range rejections { t.Run(tc.name, func(t *testing.T) { _, err := decodeOperationRegister(Register{Key: RegisterKey{SessionID: testSessionID, Kind: RegisterOperation, OperationID: testOpID}, Payload: tc.payload}) - if err == nil { - t.Fatalf("%s decoded", tc.name) - } - if tc.wantText != "" && !strings.Contains(err.Error(), tc.wantText) { - t.Fatalf("error %v, want the %s member named", err, tc.wantText) - } + testassert.Require(t, err != nil, "%s decoded", tc.name) + testassert.Require(t, tc.wantText == "" || strings.Contains(err.Error(), tc.wantText), "error %v, want the %s member named", err, tc.wantText) }) } } @@ -440,13 +397,12 @@ func TestCompactAdmissionCodecRules(t *testing.T) { func deleteKey(t *testing.T, raw json.RawMessage, key string) json.RawMessage { t.Helper() obj := map[string]json.RawMessage{} - if err := json.Unmarshal(raw, &obj); err != nil { - t.Fatalf("unmarshal: %v", err) + { + err := json.Unmarshal(raw, &obj) + testassert.NoError(t, err) } delete(obj, key) out, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal: %v", err) - } + testassert.NoError(t, err) return out } diff --git a/harness/compact_test.go b/harness/compact_test.go index 1309d475..1312bf00 100644 --- a/harness/compact_test.go +++ b/harness/compact_test.go @@ -1,15 +1,14 @@ // Contract coverage for the compaction orchestration: the frozen projected -// conversation is serialized into inert transcript lines, packed whole-message -// into budget-fitting pieces, and each piece runs one agent.Run invocation -// whose model effect commits the ordinary intent over the compact transport -// and settles without writing entries. +// conversation is serialized into inert transcript lines, joined whole into +// one full-input compact request, and that request runs one agent.Run +// invocation whose model effect commits the ordinary intent over the compact +// transport and settles without writing entries. package harness import ( "context" "encoding/json" "errors" - "fmt" "net" "reflect" "strconv" @@ -17,6 +16,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/agent" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -48,9 +48,7 @@ func compactSnapshotMessages() []model.Message { func registerRevision(t *testing.T, store *graphStorage, sessionID string, key RegisterKey) int64 { t.Helper() reg, err := store.ReadRegister(context.Background(), key) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) return reg.Revision } @@ -61,15 +59,11 @@ func settlementEntryOf(t *testing.T, graph *sessionGraph) *operationSettlementEn var found *operationSettlementEntry for i := range graph.Entries { if graph.Entries[i].Settlement != nil { - if found != nil { - t.Fatalf("graph carries more than one settlement entry") - } + testassert.Require(t, found == nil, "graph carries more than one settlement entry") found = graph.Entries[i].Settlement } } - if found == nil { - t.Fatalf("graph carries no settlement entry") - } + testassert.Require(t, found != nil, "graph carries no settlement entry") return found } @@ -83,12 +77,12 @@ func entryIDs(t *testing.T, graph *sessionGraph) map[string]bool { return ids } -// TestRunCompactionOneBudgetSnapshotRunsOnePiece proves the one-piece shape: -// a snapshot that fits one budget runs exactly one request whose user text is -// the serialized snapshot alone, one intent transaction commits before it, -// the ready settlement inserts no entry and leaves both registers with no -// usage, and the reserved identity stays unused. -func TestRunCompactionOneBudgetSnapshotRunsOnePiece(t *testing.T) { +// TestRunCompactionReadyPathRunsOneRequest proves the ready shape: the run +// executes exactly one request whose user text is the serialized snapshot +// alone, one intent transaction commits before it, the ready settlement +// inserts no entry and leaves both registers with no usage, and the reserved +// identity stays unused. +func TestRunCompactionReadyPathRunsOneRequest(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) opKey := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID} sessionKey := RegisterKey{SessionID: sessionID, Kind: RegisterSession} @@ -118,193 +112,91 @@ func TestRunCompactionOneBudgetSnapshotRunsOnePiece(t *testing.T) { exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} summary, usage, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), snapshot) - if err != nil { - t.Fatalf("runCompaction: %v", err) - } - if summary != "summary one" { - t.Fatalf("summary %q, want the piece output text", summary) - } - if usage == nil || *usage != (UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5}) { - t.Fatalf("usage = %+v, want the one piece's reported counts", usage) - } - if len(requests) != 1 { - t.Fatalf("compact transport calls = %d, want exactly one piece request", len(requests)) - } + testassert.NoError(t, err) + testassert.Equal(t, summary, "summary one") + testassert.Require(t, usage != nil && *usage == (UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5}), "usage = %+v, want the one request's reported counts", usage) + testassert.Equal(t, len(requests), 1) req := requests[0] - if len(req.Messages) != 2 { - t.Fatalf("request messages = %d, want exactly a system and a user message", len(req.Messages)) - } - if len(req.Tools) != 0 { - t.Fatalf("request tools = %d, want none advertised", len(req.Tools)) - } - if req.Messages[0].Role != model.RoleSystem || req.Messages[0].Source != (model.ModelRef{}) { - t.Fatalf("system message = %+v, want role system with zero source", req.Messages[0]) - } - if got := req.Messages[0].TextContent(); got != testCapture().Compact.SystemPrompt { - t.Fatalf("system message text %q, want the compact prompt", got) - } - if req.Messages[1].Role != model.RoleUser || req.Messages[1].Source != (model.ModelRef{}) { - t.Fatalf("user message = %+v, want role user with zero source", req.Messages[1]) + testassert.Require(t, len(req.Messages) == 2 && len(req.Tools) == 0, "got %v, want %v; got %v, want %v", len(req.Messages), 2, len(req.Tools), 0) + testassert.Require(t, req.Messages[0].Role == model.RoleSystem && req.Messages[0].Source == (model.ModelRef{}), "system message = %+v, want role system with zero source", req.Messages[0]) + { + got := req.Messages[0].TextContent() + testassert.Equal(t, got, testCapture().Compact.SystemPrompt) } + testassert.Require(t, req.Messages[1].Role == model.RoleUser && req.Messages[1].Source == (model.ModelRef{}), "user message = %+v, want role user with zero source", req.Messages[1]) expectedLines, err := serializeCompactionMessages(snapshot) - if err != nil { - t.Fatalf("serializeCompactionMessages: %v", err) - } - if got := req.Messages[1].TextContent(); got != strings.Join(expectedLines, "\n") { - t.Fatalf("user request text %q, want the serialized snapshot alone", got) + testassert.NoError(t, err) + { + got := req.Messages[1].TextContent() + testassert.Equal(t, got, strings.Join(expectedLines, "\n")) } if strings.Contains(req.Messages[1].TextContent(), "Previous summary:") { - t.Fatalf("the one-piece request carries a Previous summary block: %q", req.Messages[1].TextContent()) + t.Fatalf("the request carries a Previous summary block: %q", req.Messages[1].TextContent()) } // One intent transaction and one result transaction: the operation // register advanced exactly twice, the session register once. - if got := registerRevision(t, store, sessionID, opKey); got != opRevision+2 { - t.Fatalf("operation register revision %d, want %d (one intent and one result transaction)", got, opRevision+2) + { + got := registerRevision(t, store, sessionID, opKey) + testassert.Equal(t, got, opRevision+2) } - if got := registerRevision(t, store, sessionID, sessionKey); got != sessionRevision+1 { - t.Fatalf("session register revision %d, want %d", got, sessionRevision+1) + { + got := registerRevision(t, store, sessionID, sessionKey) + testassert.Equal(t, got, sessionRevision+1) } afterEntries, _ := storedSessionState(store, sessionID) - if len(afterEntries) != len(beforeEntries) { - t.Fatalf("entry count %d, want the unchanged %d", len(afterEntries), len(beforeEntries)) - } + testassert.Equal(t, len(afterEntries), len(beforeEntries)) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-run graph: %v", err) - } + testassert.NoError(t, err) ids := entryIDs(t, graph) - if reservedID == "" || ids[reservedID] { - t.Fatalf("reserved identity %q must stay unused by any entry", reservedID) - } + testassert.Require(t, reservedID != "" && !ids[reservedID], "reserved identity %q must stay unused by any entry", reservedID) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil { - t.Fatalf("operation state = %+v, want running with the effect cleared", rec.State) - } - if len(rec.State.Usage.ByModel) != 0 { - t.Fatalf("operation usage = %+v, want none on the ready path", rec.State.Usage) - } - if len(graph.Session.State.Usage.ByModel) != 0 { - t.Fatalf("session usage = %+v, want none on the ready path", graph.Session.State.Usage) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && len(rec.State.Usage.ByModel) == 0, "operation state = %+v, want running with the effect cleared; operation usage = %+v, want none on the ready path", rec.State, rec.State.Usage) + testassert.Require(t, len(graph.Session.State.Usage.ByModel) == 0, "session usage = %+v, want none on the ready path", graph.Session.State.Usage) } -// TestRunCompactionMultiPieceConsumesEveryMessageOnce proves the rolling -// shape: a message that exceeds a nonempty piece's remaining budget starts -// the next piece, every message is consumed exactly once in order, no message -// is split, and every continuation request carries the previous summary. -func TestRunCompactionMultiPieceConsumesEveryMessageOnce(t *testing.T) { +// TestRunCompactionFullInputRunsOneRequest is the single-request regression: +// a serialized input larger than any per-piece budget the removed packing +// loop imposed runs as one full-input request whose user text is the +// serialized snapshot alone, with the one response's reported usage. +func TestRunCompactionFullInputRunsOneRequest(t *testing.T) { h, _, c, _ := newEffectHarness(t, nil) capture := testCapture() - - // Fixture sized to the contract's exact case: the middle message fits a - // fresh empty piece but exceeds the space left in the piece carrying the - // first message, so it starts the next piece, and the third message - // exceeds the middle piece's remaining budget and starts the third piece. - // No message exceeds a whole empty piece's budget. - budget := compactionPieceBudget(capture.Compact, "") unit := "summarize the whole conversation turn carefully " - alpha := strings.Repeat(unit, 12) - alphaLines, err := serializeCompactionMessages([]model.Message{{Role: model.RoleUser, Content: admissionContent(alpha)}}) - if err != nil { - t.Fatalf("serialize: %v", err) - } - costAlpha := estimateTokens(alphaLines[0], nil) - big := "" - costBig := 0 - for n := 1; ; n++ { - candidate := strings.Repeat(unit, n) - sized, err := serializeCompactionMessages([]model.Message{{Role: model.RoleUser, Content: admissionContent(candidate)}}) - if err != nil { - t.Fatalf("serialize: %v", err) - } - cost := estimateTokens(sized[0], nil) - if cost > budget { - t.Fatalf("no message size lands in the %d-token window between the first piece's remaining budget and the whole piece budget", costAlpha) - } - if cost > budget-costAlpha { - big, costBig = candidate, cost - break - } - } - gamma := strings.Repeat(unit, 14) - gammaLines, err := serializeCompactionMessages([]model.Message{{Role: model.RoleUser, Content: admissionContent(gamma)}}) - if err != nil { - t.Fatalf("serialize: %v", err) - } - costGamma := estimateTokens(gammaLines[0], nil) - if costAlpha+costBig <= budget { - t.Fatalf("fixture precondition: the middle message would fit the first piece's remaining budget") - } - if costBig > budget { - t.Fatalf("fixture precondition: the middle message exceeds a whole empty piece's budget") - } - if budget2 := compactionPieceBudget(capture.Compact, "S1"); costBig+costGamma <= budget2 { - t.Fatalf("fixture precondition: the third message would fit the middle piece's remaining budget") - } snapshot := []model.Message{ - {Role: model.RoleUser, Content: admissionContent(alpha)}, - {Role: model.RoleUser, Content: admissionContent(big)}, - {Role: model.RoleUser, Content: admissionContent(gamma)}, + {Role: model.RoleUser, Content: admissionContent(strings.Repeat(unit, 70))}, + {Role: model.RoleUser, Content: admissionContent(strings.Repeat(unit, 70))}, + {Role: model.RoleUser, Content: admissionContent(strings.Repeat(unit, 70))}, } lines, err := serializeCompactionMessages(snapshot) - if err != nil { - t.Fatalf("serialize: %v", err) - } - + testassert.NoError(t, err) var requests []model.Request - usages := []model.Usage{{InputTokens: 1, OutputTokens: 2}, {InputTokens: 3, CachedInputTokens: 1, OutputTokens: 4}, {InputTokens: 5, CachedInputTokens: 2, OutputTokens: 6}} - call := 0 compact := func(ctx context.Context, req model.Request) (model.Stream, error) { requests = append(requests, req) - call++ - if call <= len(usages) { - return summaryStream(fmt.Sprintf("S%d", call), usages[call-1]), nil - } - return summaryStream("S9", model.Usage{}), nil + return summaryStream("S1", model.Usage{InputTokens: 5, OutputTokens: 3}), nil } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} - summary, usage, err := h.runCompaction(context.Background(), c, testOpID, exec, capture, snapshot) - if err != nil { - t.Fatalf("runCompaction: %v", err) - } - if summary != "S3" { - t.Fatalf("summary %q, want the final piece output", summary) - } - if usage == nil || *usage != (UsageCount{InputTokens: 9, CachedInputTokens: 3, OutputTokens: 12}) { - t.Fatalf("usage = %+v, want the field-wise sum across the three pieces", usage) - } - if len(requests) != 3 { - t.Fatalf("compact transport calls = %d, want three pieces", len(requests)) - } - wantUserText := []string{ - lines[0], - "Previous summary:\nS1\n\nContinuation:\n" + lines[1], - "Previous summary:\nS2\n\nContinuation:\n" + lines[2], - } - for i, req := range requests { - if len(req.Messages) != 2 { - t.Fatalf("request %d messages = %d, want exactly two", i, len(req.Messages)) - } - if got := req.Messages[1].TextContent(); got != wantUserText[i] { - t.Fatalf("request %d user text %q, want %q", i, got, wantUserText[i]) - } + testassert.NoError(t, err) + testassert.Equal(t, summary, "S1") + testassert.Require(t, usage != nil && *usage == (UsageCount{InputTokens: 5, OutputTokens: 3}), "usage = %+v, want the one request's reported counts", usage) + testassert.Equal(t, len(requests), 1) + { + got := requests[0].Messages[1].TextContent() + testassert.Equal(t, got, strings.Join(lines, "\n")) } } // TestCompactModelEffectSettlements proves the effect contract: attempts run -// through the compact transport under the retry policy, a piece failure -// settles the terminal with the accumulated usage keyed by the compact model -// and carries the errored assembly on the failure row, an interrupted effect -// settles through the existing terminal path with no output before any -// assembly, and a textless completed output settles ready with its output — -// the orchestration validates the summary text after the settlement. The -// ready settlement's durable shape is pinned by the one-budget orchestration -// test. +// through the compact transport under the retry policy, a failed summary that +// reports usage settles the terminal with that reported usage keyed by the +// compact model and carries the errored assembly on the failure row, an +// interrupted effect settles through the existing terminal path with no output +// before any assembly, and a textless completed output settles ready with its +// output — the orchestration validates the summary text after the settlement. +// The ready settlement's durable shape is pinned by the ready-path +// orchestration test. func TestCompactModelEffectSettlements(t *testing.T) { compactRef := testCompactCapture().Model @@ -317,77 +209,52 @@ func TestCompactModelEffectSettlements(t *testing.T) { if calls == 1 { return nil, &net.OpError{Op: "dial", Err: errors.New("boom")} } - return summaryStream("piece summary", model.Usage{}), nil + return summaryStream("summary one", model.Usage{}), nil } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, Retry: spy.retry, NormalizeTool: objectNormalize} - me := h.compactModelEffect(c, testOpID, exec, testCapture(), &usageAccumulator{}) - req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("piece")}}}) - if err != nil { - t.Fatalf("request: %v", err) - } - set, err := me(context.Background(), req, func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - }) - if err != nil { - t.Fatalf("compact model effect: %v", err) - } - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready", set.Disposition) - } - if calls != 2 { - t.Fatalf("compact transport calls = %d, want the retried attempt pair", calls) - } - if len(spy.attempts) != 1 || spy.attempts[0] != 1 || len(spy.seen) != 1 { - t.Fatalf("retry consultations = %v seen %v, want the one failed attempt classified by the policy", spy.attempts, spy.seen) - } - if _, ok := spy.seen[0].(*net.OpError); !ok { - t.Fatalf("classified failure %v, want the first attempt's transport error", spy.seen[0]) + var reported *UsageCount + me := h.compactModelEffect(c, testOpID, exec, testCapture(), &reported) + req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("summary")}}}) + testassert.NoError(t, err) + set, err := invokeModelEffect(t, me, req, nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, calls, 2) + testassert.Require(t, len(spy.attempts) == 1 && spy.attempts[0] == 1 && len(spy.seen) == 1, "retry consultations = %v seen %v, want the one failed attempt classified by the policy", spy.attempts, spy.seen) + { + _, ok := spy.seen[0].(*net.OpError) + testassert.Require(t, ok, "classified failure %v, want the first attempt's transport error", spy.seen[0]) } }) - t.Run("piece failure settles the accumulated usage", func(t *testing.T) { + t.Run("failed summary settles the reported usage", func(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) compact := func(ctx context.Context, req model.Request) (model.Stream, error) { - return failStream(errors.New("provider failure")), nil + // The failing response reports usage before the read failure: + // the errored assembly retains it. + return failStream(errors.New("provider failure"), + turnDelta("", "partial"), usageDelta(model.Usage{InputTokens: 5, CachedInputTokens: 5, OutputTokens: 5})), nil } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, Retry: (&retrySpy{limit: 0}).retry, NormalizeTool: objectNormalize} - accum := &usageAccumulator{} - if err := accum.add(UsageCount{InputTokens: 5, CachedInputTokens: 5, OutputTokens: 5}); err != nil { - t.Fatalf("seed accumulator: %v", err) - } - me := h.compactModelEffect(c, testOpID, exec, testCapture(), accum) - req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("piece")}}}) - if err != nil { - t.Fatalf("request: %v", err) - } - set, err := me(context.Background(), req, func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - }) - if err != nil { - t.Fatalf("compact model effect: %v", err) - } - if set.Disposition != agent.DispoFailure || set.Detail == "" || !strings.Contains(set.Detail, "provider failure") { - t.Fatalf("settlement = %+v, want failure carrying the piece failure detail", set) - } - if set.Output == nil || set.Output.Status != model.OutputErrored { - t.Fatalf("settlement output = %+v, want the errored assembly carried on the failure row", set.Output) - } + var reported *UsageCount + me := h.compactModelEffect(c, testOpID, exec, testCapture(), &reported) + req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("summary")}}}) + testassert.NoError(t, err) + set, err := invokeModelEffect(t, me, req, nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Detail != "" && strings.Contains(set.Detail, "provider failure") && (set.Output != nil && set.Output.Status == model.OutputErrored), "settlement = %+v, want the failure carrying the summary failure detail; settlement output = %+v, want the errored assembly carried on the failure row", set, set.Output) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-settlement graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationFailure || !strings.Contains(settlement.Detail, "provider failure") { - t.Fatalf("settlement entry = %+v, want the failure terminal", settlement) - } - if settlement.Model == nil || *settlement.Model != compactRef { - t.Fatalf("settlement model = %+v, want the compact model identity", settlement.Model) - } - if settlement.Usage == nil || *settlement.Usage != (UsageCount{InputTokens: 5, CachedInputTokens: 5, OutputTokens: 5}) { - t.Fatalf("settlement usage = %+v, want the accumulated total", settlement.Usage) - } - if len(graph.Session.State.Usage.ByModel) != 1 || graph.Session.State.Usage.ByModel[0].Model != compactRef { - t.Fatalf("session usage = %+v, want the accumulated total keyed by the compact model", graph.Session.State.Usage) + testassert.Require(t, settlement.Status == OperationFailure && strings.Contains(settlement.Detail, "provider failure"), "settlement entry = %+v, want the failure terminal", settlement) + testassert.Require(t, settlement.Model != nil && *settlement.Model == compactRef, "settlement model = %+v, want the compact model identity", settlement.Model) + testassert.Require(t, settlement.Usage != nil && *settlement.Usage == (UsageCount{InputTokens: 5, CachedInputTokens: 5, OutputTokens: 5}) && settlement.CompactionUsage, "settlement usage = %+v, want the reported counts; settlement compaction_usage = false, want the summary usage provenance marked", settlement.Usage) + testassert.Require(t, len(graph.Session.State.Usage.ByModel) == 1 && graph.Session.State.Usage.ByModel[0].Model == compactRef, "session usage = %+v, want the reported total keyed by the compact model", graph.Session.State.Usage) + view, err := h.ReadSessionView(context.Background(), sessionID) + testassert.NoError(t, err) + { + measured := view.History.LatestContextUsage() + testassert.Require(t, measured == nil, "latest context measurement = %+v, want the marked summary usage billed but ineligible", measured) } }) @@ -398,42 +265,26 @@ func TestCompactModelEffectSettlements(t *testing.T) { return nil, errors.New("unreachable") } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} - me := h.compactModelEffect(c, testOpID, exec, testCapture(), &usageAccumulator{}) - req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("piece")}}}) - if err != nil { - t.Fatalf("request: %v", err) - } + var reported *UsageCount + me := h.compactModelEffect(c, testOpID, exec, testCapture(), &reported) + req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("summary")}}}) + testassert.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) cancel() - set, err := me(ctx, req, func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - }) - if err != nil { - t.Fatalf("compact model effect: %v", err) - } - if set.Disposition != agent.DispoInterruption || set.Detail != executionInterruptedDetail { - t.Fatalf("settlement = %+v, want the fixed interruption settlement", set) - } - if set.Output != nil { - t.Fatalf("settlement output = %+v, want none on the pre-assembly cancellation", set.Output) - } + set, err := invokeModelEffectCtx(t, me, ctx, req, nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoInterruption && set.Detail == executionInterruptedDetail && set.Output == nil, "settlement = %+v, want the fixed interruption settlement; settlement output = %+v, want none on the pre-assembly cancellation", set, set.Output) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-settlement graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationInterruption || settlement.Detail != executionInterruptedDetail { - t.Fatalf("settlement entry = %+v, want the interruption terminal", settlement) - } + testassert.Require(t, settlement.Status == OperationInterruption && settlement.Detail == executionInterruptedDetail && settlement.CompactionUsage, "settlement entry = %+v, want the interruption terminal; settlement compaction_usage = false, want the compact effect's interruption provenance marked", settlement) var signals int for i := range graph.Entries { if graph.Entries[i].Signal != nil { signals++ } } - if signals != 1 { - t.Fatalf("interruption signal entries = %d, want exactly one", signals) - } + testassert.Equal(t, signals, 1) }) t.Run("refusal-only completed output settles ready with the carried output", func(t *testing.T) { @@ -447,119 +298,59 @@ func TestCompactModelEffectSettlements(t *testing.T) { return streamOf(refusal, usageDelta(model.Usage{InputTokens: 2, OutputTokens: 1})), nil } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, Retry: spy.retry, NormalizeTool: objectNormalize} - me := h.compactModelEffect(c, testOpID, exec, testCapture(), &usageAccumulator{}) - req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("piece")}}}) - if err != nil { - t.Fatalf("request: %v", err) - } - set, err := me(context.Background(), req, func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - }) - if err != nil { - t.Fatalf("compact model effect: %v", err) - } - if set.Disposition != agent.DispoReady || set.Output == nil || set.Output.Status != model.OutputCompleted { - t.Fatalf("settlement = %+v, want the ready settlement carrying the completed output", set) - } - if calls != 1 { - t.Fatalf("compact transport calls = %d, want exactly one attempt and no retry", calls) - } + var reported *UsageCount + me := h.compactModelEffect(c, testOpID, exec, testCapture(), &reported) + req, err := model.NewRequest(model.Request{Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("summary")}}}) + testassert.NoError(t, err) + set, err := invokeModelEffect(t, me, req, nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoReady && set.Output != nil && set.Output.Status == model.OutputCompleted, "settlement = %+v, want the ready settlement carrying the completed output", set) + testassert.Equal(t, calls, 1) }) } -// compactTwoPieceSnapshot returns a two-message snapshot whose second message -// fits a fresh empty piece but exceeds the first piece's remaining budget, -// so the orchestration runs two pieces. -func compactTwoPieceSnapshot(t *testing.T) []model.Message { - t.Helper() - capture := testCapture() - budget := compactionPieceBudget(capture.Compact, "") - unit := "summarize the whole conversation turn carefully " - first := strings.Repeat(unit, 12) - firstLines, err := serializeCompactionMessages([]model.Message{{Role: model.RoleUser, Content: admissionContent(first)}}) - if err != nil { - t.Fatalf("serialize: %v", err) - } - costFirst := estimateTokens(firstLines[0], nil) - second := "" - for n := 1; ; n++ { - candidate := strings.Repeat(unit, n) - sized, err := serializeCompactionMessages([]model.Message{{Role: model.RoleUser, Content: admissionContent(candidate)}}) - if err != nil { - t.Fatalf("serialize: %v", err) - } - cost := estimateTokens(sized[0], nil) - if cost > budget { - t.Fatalf("no message size lands in the %d-token window between the first piece's remaining budget and the whole piece budget", costFirst) - } - if cost > budget-costFirst { - second = candidate - break - } - } - return []model.Message{ - {Role: model.RoleUser, Content: admissionContent(first)}, - {Role: model.RoleUser, Content: admissionContent(second)}, - } -} - // TestRunCompactionCanceledBeforeRunSettlesInterruption proves the pre-run -// window: an execution context already canceled before the first piece makes -// the run return interruption with no effect ever begun, and the +// window: an execution context already canceled before the compact request +// makes the run return interruption with no effect ever begun, and the // orchestration settles the quiet running Operation as interruption with no // usage and no compaction entry. func TestRunCompactionCanceledBeforeRunSettlesInterruption(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) compact := func(ctx context.Context, req model.Request) (model.Stream, error) { - t.Errorf("no piece may run under a canceled execution context") + t.Errorf("no request may run under a canceled execution context") return nil, errors.New("unreachable") } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} ctx, cancel := context.WithCancel(context.Background()) cancel() summary, usage, err := h.runCompaction(ctx, c, testOpID, exec, testCapture(), compactSnapshotMessages()) - if err == nil || err.Error() != executionInterruptedDetail { - t.Fatalf("error %v, want the run's interruption detail", err) - } - if summary != "" || usage != nil { - t.Fatalf("summary %q usage %+v, want no summary and no usage", summary, usage) - } + testassert.Require(t, err != nil && err.Error() == executionInterruptedDetail, "error %v, want the run's interruption detail", err) + testassert.Require(t, summary == "" && usage == nil, "summary %q usage %+v, want no summary and no usage", summary, usage) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-settlement graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationInterruption || settlement.Detail != executionInterruptedDetail { - t.Fatalf("settlement entry = %+v, want the run's interruption terminal", settlement) - } - if settlement.Model != nil || settlement.Usage != nil { - t.Fatalf("settlement entry = %+v, want no usage on the never-run compaction", settlement) - } + testassert.Require(t, settlement.Status == OperationInterruption && settlement.Detail == executionInterruptedDetail && (settlement.Model == nil && settlement.Usage == nil) && settlement.CompactionUsage, "settlement entry = %+v, want the run's interruption terminal; settlement entry = %+v, want no usage on the never-run compaction; settlement compaction_usage = false, want the direct interruption settlement marked", settlement, settlement) var signals int for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed on a failure path") - } + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on a failure path") if graph.Entries[i].Signal != nil { signals++ } } - if signals != 1 { - t.Fatalf("interruption signal entries = %d, want exactly one", signals) - } + testassert.Equal(t, signals, 1) } -// TestRunCompactionCanceledBetweenPiecesSettlesInterruption proves the -// between-pieces window: a two-piece snapshot whose first piece completes and -// settles ready durably, with cancellation observed before the run's success -// return — the run returns interruption, and the orchestration settles the -// quiet running Operation as interruption carrying the first piece's -// accumulated usage keyed by the compact model. A cancellation landing -// between the runs instead returns the same terminal at the second run's loop -// top and takes the same direct settlement. -func TestRunCompactionCanceledBetweenPiecesSettlesInterruption(t *testing.T) { +// TestRunCompactionCanceledAfterSummarySettlesInterruption proves the +// post-result window: the single summary request completes and its ready +// settlement lands durably, with cancellation observed before the run's +// success return — the run returns interruption, and the orchestration settles +// the quiet running Operation as interruption carrying the summary's reported +// usage keyed by the compact model, with the marker provenance. A +// cancellation landing inside the run's loop instead returns the same terminal +// and takes the same direct settlement. +func TestRunCompactionCanceledAfterSummarySettlesInterruption(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) - snapshot := compactTwoPieceSnapshot(t) + snapshot := compactSnapshotMessages() ctx, cancel := context.WithCancel(context.Background()) requests := 0 compact := func(ctx context.Context, req model.Request) (model.Stream, error) { @@ -568,36 +359,18 @@ func TestRunCompactionCanceledBetweenPiecesSettlesInterruption(t *testing.T) { } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} summary, usage, err := h.runCompaction(ctx, c, testOpID, exec, testCapture(), snapshot) - if err == nil || err.Error() != executionInterruptedDetail { - t.Fatalf("error %v, want the run's interruption detail", err) - } - if summary != "" { - t.Fatalf("summary %q, want none on the interrupted orchestration", summary) - } - if usage == nil || *usage != (UsageCount{InputTokens: 4, OutputTokens: 2}) { - t.Fatalf("usage = %+v, want the first piece's reported counts", usage) - } - if requests != 1 { - t.Fatalf("compact transport calls = %d, want only the first piece", requests) - } + testassert.Require(t, err != nil && err.Error() == executionInterruptedDetail, "error %v, want the run's interruption detail", err) + testassert.Equal(t, summary, "") + testassert.Require(t, usage != nil && *usage == (UsageCount{InputTokens: 4, OutputTokens: 2}), "usage = %+v, want the summary's reported counts", usage) + testassert.Equal(t, requests, 1) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-settlement graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationInterruption || settlement.Detail != executionInterruptedDetail { - t.Fatalf("settlement entry = %+v, want the run's interruption terminal", settlement) - } - if settlement.Model == nil || *settlement.Model != testCompactCapture().Model { - t.Fatalf("settlement model = %+v, want the compact model identity", settlement.Model) - } - if settlement.Usage == nil || *settlement.Usage != (UsageCount{InputTokens: 4, OutputTokens: 2}) { - t.Fatalf("settlement usage = %+v, want the first piece's accumulated counts", settlement.Usage) - } + testassert.Require(t, settlement.Status == OperationInterruption && settlement.Detail == executionInterruptedDetail, "settlement entry = %+v, want the run's interruption terminal", settlement) + testassert.Require(t, settlement.Model != nil && *settlement.Model == testCompactCapture().Model, "settlement model = %+v, want the compact model identity", settlement.Model) + testassert.Require(t, settlement.Usage != nil && *settlement.Usage == (UsageCount{InputTokens: 4, OutputTokens: 2}) && settlement.CompactionUsage, "settlement usage = %+v, want the summary's reported counts; settlement compaction_usage = false, want the summary usage provenance marked", settlement.Usage) for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed on a failure path") - } + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on a failure path") } } @@ -627,118 +400,73 @@ func TestSerializeCompactionMessagesCompleteInput(t *testing.T) { owned := make([]model.Message, 0, len(input)) for _, m := range input { msg, err := model.NewMessage(m) - if err != nil { - t.Fatalf("fixture message: %v", err) - } + testassert.NoError(t, err) owned = append(owned, msg) } snapshot := append([]model.Message(nil), owned...) lines, err := serializeCompactionMessages(snapshot) - if err != nil { - t.Fatalf("serializeCompactionMessages: %v", err) - } - if len(lines) != len(snapshot) { - t.Fatalf("lines = %d, want one per message", len(lines)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(lines), len(snapshot)) decoded := make([]map[string]any, 0, len(lines)) for i, line := range lines { - if !json.Valid([]byte(line)) { - t.Fatalf("line %d is not valid JSON: %q", i, line) - } + testassert.Require(t, json.Valid([]byte(line)), "line %d is not valid JSON: %q", i, line) var obj map[string]any - if err := json.Unmarshal([]byte(line), &obj); err != nil { - t.Fatalf("line %d unmarshal: %v", i, err) + { + err := json.Unmarshal([]byte(line), &obj) + testassert.Require(t, err == nil, "line %d unmarshal: %v", i, err) } decoded = append(decoded, obj) - if strings.Contains(line, testModelRef().Provider) { - t.Fatalf("line %d carries model-source replay metadata: %q", i, line) - } + testassert.Require(t, !strings.Contains(line, testModelRef().Provider), "line %d carries model-source replay metadata: %q", i, line) } // The signal's wrapped text, the parts, refusal, extras and identities // survive verbatim. first := decoded[0] - if first["Role"] != "user" { - t.Fatalf("first line role = %v, want user", first["Role"]) - } + testassert.Equal(t, first["Role"], "user") signalParts, ok := first["Content"].([]any) - if !ok || len(signalParts) != 1 { - t.Fatalf("first line content = %v, want one part", first["Content"]) - } + testassert.Require(t, ok && len(signalParts) == 1, "first line content = %v, want one part", first["Content"]) signalPart, _ := signalParts[0].(map[string]any) - if signalPart["Text"] != "hello & quoted" { - t.Fatalf("signal wrapper text lost: %v", signalPart["Text"]) - } + testassert.Equal(t, signalPart["Text"], "hello & quoted") second := decoded[1] - if second["Role"] != "assistant" || second["Refusal"] != "partly refused" { - t.Fatalf("assistant line = %v, want role and refusal preserved", second) - } - if second["Source"] != "" { - t.Fatalf("assistant line source = %v, want the cleared source identity", second["Source"]) - } - if !strings.Contains(lines[1], `"OpaqueWireType":"reasoning"`) || !strings.Contains(lines[1], `"summary":"hidden"`) { - t.Fatalf("opaque part lost: %q", lines[1]) - } - if !strings.Contains(lines[1], `"URL":"https://example.com/img.png"`) { - t.Fatalf("image part lost: %q", lines[1]) - } - if !strings.Contains(lines[1], `"finish":"stop"`) { - t.Fatalf("message extra lost: %q", lines[1]) - } + testassert.Require(t, second["Role"] == "assistant" && second["Refusal"] == "partly refused" && second["Source"] == "", "assistant line = %v, want role and refusal preserved; got %v, want %v", second, second["Source"], "") + testassert.Require(t, strings.Contains(lines[1], `"OpaqueWireType":"reasoning"`) && strings.Contains(lines[1], `"summary":"hidden"`), "opaque part lost: %q", lines[1]) + testassert.Require(t, strings.Contains(lines[1], `"URL":"https://example.com/img.png"`), "image part lost: %q", lines[1]) + testassert.Require(t, strings.Contains(lines[1], `"finish":"stop"`), "message extra lost: %q", lines[1]) calls, ok := second["ToolCalls"].([]any) - if !ok || len(calls) != 2 { - t.Fatalf("tool calls = %v, want both preserved", second["ToolCalls"]) - } + testassert.Require(t, ok && len(calls) == 2, "tool calls = %v, want both preserved", second["ToolCalls"]) firstCall, _ := calls[0].(map[string]any) - if firstCall["ID"] != "call-1" || firstCall["Name"] != "echo" { - t.Fatalf("first call = %v, want identity preserved", firstCall) - } - if !strings.Contains(lines[1], `"kind":"custom"`) { - t.Fatalf("tool-call extra lost: %q", lines[1]) - } + testassert.Require(t, firstCall["ID"] == "call-1" && firstCall["Name"] == "echo", "first call = %v, want identity preserved", firstCall) + testassert.Require(t, strings.Contains(lines[1], `"kind":"custom"`), "tool-call extra lost: %q", lines[1]) // Tool-call arguments ride the JSON encoding of their quoted byte // representation, keeping even malformed bytes marshalable and recoverable. for _, idx := range []int{0, 1} { call, _ := calls[idx].(map[string]any) raw, err := json.Marshal(call["Arguments"]) - if err != nil { - t.Fatalf("call %d arguments marshal: %v", idx, err) - } + testassert.Require(t, err == nil, "call %d arguments marshal: %v", idx, err) var quoted string - if err := json.Unmarshal(raw, "ed); err != nil { - t.Fatalf("call %d arguments is not a JSON string: %v", idx, err) + { + err := json.Unmarshal(raw, "ed) + testassert.Require(t, err == nil, "call %d arguments is not a JSON string: %v", idx, err) } recovered, err := strconv.Unquote(quoted) - if err != nil { - t.Fatalf("call %d quoted arguments do not unquote: %v", idx, err) - } + testassert.Require(t, err == nil, "call %d quoted arguments do not unquote: %v", idx, err) want := `{"x":1}` if idx == 1 { want = `{"x":1` } - if recovered != want { - t.Fatalf("call %d recovered arguments %q, want the original raw bytes %q", idx, recovered, want) - } + testassert.Require(t, recovered == want, "call %d recovered arguments %q, want the original raw bytes %q", idx, recovered, want) } third := decoded[2] - if third["Role"] != "tool" || third["ToolCallID"] != "call-1" { - t.Fatalf("tool line = %v, want the tool-result identity preserved", third) - } - if !strings.Contains(lines[2], "result body") { - t.Fatalf("tool result content lost: %q", lines[2]) - } + testassert.Require(t, third["Role"] == "tool" && third["ToolCallID"] == "call-1", "tool line = %v, want the tool-result identity preserved", third) + testassert.Require(t, strings.Contains(lines[2], "result body"), "tool result content lost: %q", lines[2]) // The source messages are unchanged, including Source, the ToolCalls // slice and the raw argument bytes. for i := range owned { - if !reflect.DeepEqual(owned[i], snapshot[i]) { - t.Fatalf("message %d changed through serialization:\nbefore %+v\nafter %+v", i, owned[i], snapshot[i]) - } + testassert.Require(t, reflect.DeepEqual(owned[i], snapshot[i]), "message %d changed through serialization:\nbefore %+v\nafter %+v", i, owned[i], snapshot[i]) for j, call := range owned[i].ToolCalls { - if string(call.Arguments) != string(snapshot[i].ToolCalls[j].Arguments) { - t.Fatalf("message %d call %d argument bytes changed", i, j) - } + testassert.Require(t, string(call.Arguments) == string(snapshot[i].ToolCalls[j].Arguments), "message %d call %d argument bytes changed", i, j) } } } @@ -765,18 +493,15 @@ func compactedFixtureGraph(postBoundary ...testEntry) *testGraph { func projectedSnapshot(t *testing.T, h *Harness, c *coordinator) []model.Message { t.Helper() msgs, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if len(msgs) == 0 || msgs[0].Role != model.RoleSystem { - t.Fatalf("projection = %+v, want a leading system message", msgs) - } + testassert.NoError(t, err) + testassert.Require(t, len(msgs) != 0 && msgs[0].Role == model.RoleSystem, "projection = %+v, want a leading system message", msgs) return msgs[1:] } // TestRunCompactionExistingSummaryAndNewMessages proves the register-named // summary message is part of the frozen snapshot together with the entries -// after the boundary, and the system prompt stays outside the input. +// after the boundary as ordinary projected input, and the system prompt stays +// outside the input. func TestRunCompactionExistingSummaryAndNewMessages(t *testing.T) { input4, settlement4, op4 := projectionFollowUp(hexID(4), "op-2", "after the boundary", hexID(5), 4, 5) fixture := compactedFixtureGraph(input4, settlement4) @@ -784,9 +509,7 @@ func TestRunCompactionExistingSummaryAndNewMessages(t *testing.T) { h := newTestHarness(t, fixture.storage(t), nil) c := leaseCoordinator(t, h, testSessionID) snapshot := projectedSnapshot(t, h, c) - if len(snapshot) != 2 { - t.Fatalf("snapshot messages = %d, want the summary message and the post-boundary entry", len(snapshot)) - } + testassert.Equal(t, len(snapshot), 2) var requests []model.Request compact := func(ctx context.Context, req model.Request) (model.Stream, error) { @@ -795,28 +518,11 @@ func TestRunCompactionExistingSummaryAndNewMessages(t *testing.T) { } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} summary, _, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), snapshot) - if err != nil { - t.Fatalf("runCompaction: %v", err) - } - if summary != "S1" { - t.Fatalf("summary %q, want the piece output", summary) - } - if len(requests) != 1 { - t.Fatalf("compact transport calls = %d, want one piece", len(requests)) - } + testassert.NoError(t, err) + testassert.Equal(t, summary, "S1") + testassert.Equal(t, len(requests), 1) userText := requests[0].Messages[1].TextContent() - if strings.Contains(userText, "Previous summary:") { - t.Fatalf("the first piece carries a Previous summary block: %q", userText) - } - if !strings.Contains(userText, "Summary of the earlier conversation.") { - t.Fatalf("the serialized input lost the existing summary: %q", userText) - } - if !strings.Contains(userText, "after the boundary") { - t.Fatalf("the serialized input lost the post-boundary message: %q", userText) - } - if strings.Contains(userText, `"system"`) { - t.Fatalf("the system prompt entered the serialized conversation: %q", userText) - } + testassert.Require(t, !strings.Contains(userText, "Previous summary:") && strings.Contains(userText, "Summary of the earlier conversation.") && strings.Contains(userText, "after the boundary") && !strings.Contains(userText, `"system"`), "the request carries a Previous summary block: %q; the serialized input lost the existing summary: %q; the serialized input lost the post-boundary message: %q; the system prompt entered the serialized conversation: %q", userText, userText, userText, userText) } // TestRunCompactionSummaryOnlySessionSuppliesSummary proves a summary-only @@ -826,9 +532,7 @@ func TestRunCompactionSummaryOnlySessionSuppliesSummary(t *testing.T) { h := newTestHarness(t, fixture.storage(t), nil) c := leaseCoordinator(t, h, testSessionID) snapshot := projectedSnapshot(t, h, c) - if len(snapshot) != 1 { - t.Fatalf("snapshot messages = %d, want only the summary message", len(snapshot)) - } + testassert.Equal(t, len(snapshot), 1) var requests []model.Request compact := func(ctx context.Context, req model.Request) (model.Stream, error) { @@ -836,19 +540,13 @@ func TestRunCompactionSummaryOnlySessionSuppliesSummary(t *testing.T) { return summaryStream("S1", model.Usage{}), nil } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} - if _, _, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), snapshot); err != nil { - t.Fatalf("runCompaction: %v", err) - } - if len(requests) != 1 { - t.Fatalf("compact transport calls = %d, want one piece", len(requests)) + { + _, _, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), snapshot) + testassert.NoError(t, err) } + testassert.Equal(t, len(requests), 1) userText := requests[0].Messages[1].TextContent() - if !strings.Contains(userText, "Summary of the earlier conversation.") { - t.Fatalf("the summary-only input lost the summary: %q", userText) - } - if strings.Contains(userText, "Previous summary:") { - t.Fatalf("the first piece carries a Previous summary block: %q", userText) - } + testassert.Require(t, strings.Contains(userText, "Summary of the earlier conversation.") && !strings.Contains(userText, "Previous summary:"), "the summary-only input lost the summary: %q; the request carries a Previous summary block: %q", userText, userText) } // TestRunCompactionEmptySnapshotFailsNothingToCompact proves an empty @@ -857,85 +555,55 @@ func TestRunCompactionSummaryOnlySessionSuppliesSummary(t *testing.T) { func TestRunCompactionEmptySnapshotFailsNothingToCompact(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) compact := func(ctx context.Context, req model.Request) (model.Stream, error) { - t.Errorf("no piece may run for an empty snapshot") + t.Errorf("no request may run for an empty snapshot") return nil, errors.New("unreachable") } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} summary, usage, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), nil) - if err == nil || err.Error() != "nothing to compact" { - t.Fatalf("error %v, want the retained nothing-to-compact failure", err) - } - if summary != "" || usage != nil { - t.Fatalf("summary %q usage %+v, want no summary and no usage", summary, usage) - } + testassert.Require(t, err != nil && err.Error() == "nothing to compact", "error %v, want the retained nothing-to-compact failure", err) + testassert.Require(t, summary == "" && usage == nil, "summary %q usage %+v, want no summary and no usage", summary, usage) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationFailure || settlement.Detail != "nothing to compact" { - t.Fatalf("settlement entry = %+v, want the nothing-to-compact failure terminal", settlement) - } - if settlement.Model != nil || settlement.Usage != nil { - t.Fatalf("settlement entry = %+v, want no usage on the never-run compaction", settlement) - } + testassert.Require(t, settlement.Status == OperationFailure && settlement.Detail == "nothing to compact" && (settlement.Model == nil && settlement.Usage == nil), "settlement entry = %+v, want the nothing-to-compact failure terminal; settlement entry = %+v, want no usage on the never-run compaction", settlement, settlement) for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed on a failure path") - } + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on a failure path") } } -// TestRunCompactionErroredPieceCarriesOutputThroughTheRun proves the carried -// output through a real agent.Run: an errored piece with a retained payload +// TestRunCompactionErroredSummaryCarriesOutputThroughTheRun proves the carried +// output through a real agent.Run: an errored summary with a retained payload // settles its terminal inside its own effect and the returned failure // settlement carries the errored output, satisfying the run's callback gate — -// the run returns the piece's own failure detail, never a settlement-shape +// the run returns the summary's own failure detail, never a settlement-shape // boundary violation about an outputless settlement. -func TestRunCompactionErroredPieceCarriesOutputThroughTheRun(t *testing.T) { +func TestRunCompactionErroredSummaryCarriesOutputThroughTheRun(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) compact := func(ctx context.Context, req model.Request) (model.Stream, error) { return erroredTurnStream(), nil // text "partial" with usage, then the read failure } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, Retry: (&retrySpy{limit: 0}).retry, NormalizeTool: objectNormalize} summary, usage, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), compactSnapshotMessages()) - if err == nil || !strings.Contains(err.Error(), "provider failure") { - t.Fatalf("error %v, want the piece's own failure detail, not a settlement-shape boundary violation", err) - } - if summary != "" { - t.Fatalf("summary %q, want none on the failed piece", summary) - } - if usage == nil || *usage != (UsageCount{InputTokens: 5, OutputTokens: 7}) { - t.Fatalf("usage = %+v, want the errored piece's reported counts", usage) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "provider failure"), "error %v, want the summary's own failure detail, not a settlement-shape boundary violation", err) + testassert.Equal(t, summary, "") + testassert.Require(t, usage != nil && *usage == (UsageCount{InputTokens: 5, OutputTokens: 7}), "usage = %+v, want the errored summary's reported counts", usage) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-settlement graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationFailure || !strings.Contains(settlement.Detail, "provider failure") { - t.Fatalf("settlement entry = %+v, want the failure terminal", settlement) - } - if settlement.Model == nil || *settlement.Model != testCompactCapture().Model { - t.Fatalf("settlement model = %+v, want the compact model identity", settlement.Model) - } - if settlement.Usage == nil || *settlement.Usage != (UsageCount{InputTokens: 5, OutputTokens: 7}) { - t.Fatalf("settlement usage = %+v, want the accumulated counts", settlement.Usage) - } + testassert.Require(t, settlement.Status == OperationFailure && strings.Contains(settlement.Detail, "provider failure"), "settlement entry = %+v, want the failure terminal", settlement) + testassert.Require(t, settlement.Model != nil && *settlement.Model == testCompactCapture().Model, "settlement model = %+v, want the compact model identity", settlement.Model) + testassert.Require(t, settlement.Usage != nil && *settlement.Usage == (UsageCount{InputTokens: 5, OutputTokens: 7}) && settlement.CompactionUsage, "settlement usage = %+v, want the reported counts; settlement compaction_usage = false, want the summary usage provenance marked", settlement.Usage) for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed on a failure path") - } + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on a failure path") } } -// TestRunCompactionEmptyPieceSummaryFailsWithTheRetainedDetail proves the -// relocated textless-completed rule: the effect settles a textless completed -// piece ready, and the orchestration fails the empty summary after the run — -// the direct settlement carries the retained detail and the accumulated -// usage, exactly one model call runs with no retry and no next piece, and no -// compaction entry commits. -func TestRunCompactionEmptyPieceSummaryFailsWithTheRetainedDetail(t *testing.T) { +// TestRunCompactionEmptySummaryFailsWithTheRetainedDetail proves the +// textless-completed rule: the effect settles a textless completed summary +// ready, and the orchestration fails the empty summary after the run — the +// direct settlement carries the retained detail and the reported usage, +// exactly one model call runs with no retry, and no compaction entry commits. +func TestRunCompactionEmptySummaryFailsWithTheRetainedDetail(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) refusal := turnDelta("stop", "") refusal.RefusalFragment = "no" @@ -946,57 +614,18 @@ func TestRunCompactionEmptyPieceSummaryFailsWithTheRetainedDetail(t *testing.T) } exec := Execution{Model: forbiddenConversationModel(t), CompactModel: compact, NormalizeTool: objectNormalize} summary, usage, err := h.runCompaction(context.Background(), c, testOpID, exec, testCapture(), compactSnapshotMessages()) - if err == nil || err.Error() != "compaction summary is empty" { - t.Fatalf("error %v, want the retained empty-summary detail", err) - } - if summary != "" { - t.Fatalf("summary %q, want none on the failed piece", summary) - } - if usage == nil || *usage != (UsageCount{InputTokens: 2, OutputTokens: 1}) { - t.Fatalf("usage = %+v, want the piece's reported counts", usage) - } - if calls != 1 { - t.Fatalf("compact transport calls = %d, want exactly one with no retry and no next piece", calls) - } + testassert.Require(t, err != nil && err.Error() == "compaction summary is empty", "error %v, want the retained empty-summary detail", err) + testassert.Equal(t, summary, "") + testassert.Require(t, usage != nil && *usage == (UsageCount{InputTokens: 2, OutputTokens: 1}), "usage = %+v, want the summary's reported counts", usage) + testassert.Equal(t, calls, 1) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationFailure || settlement.Detail != "compaction summary is empty" { - t.Fatalf("settlement entry = %+v, want the empty-summary failure terminal", settlement) - } - if settlement.Model == nil || *settlement.Model != testCompactCapture().Model { - t.Fatalf("settlement model = %+v, want the compact model identity", settlement.Model) - } - if settlement.Usage == nil || *settlement.Usage != (UsageCount{InputTokens: 2, OutputTokens: 1}) { - t.Fatalf("settlement usage = %+v, want the accumulated counts", settlement.Usage) - } + testassert.Require(t, settlement.Status == OperationFailure && settlement.Detail == "compaction summary is empty", "settlement entry = %+v, want the empty-summary failure terminal", settlement) + testassert.Require(t, settlement.Model != nil && *settlement.Model == testCompactCapture().Model, "settlement model = %+v, want the compact model identity", settlement.Model) + testassert.Require(t, settlement.Usage != nil && *settlement.Usage == (UsageCount{InputTokens: 2, OutputTokens: 1}) && settlement.CompactionUsage, "settlement usage = %+v, want the reported counts; settlement compaction_usage = false, want the summary usage provenance marked", settlement.Usage) for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed on a failure path") - } - } -} - -// TestCompactionPieceBudgetArithmetic pins the budget arithmetic: the fixed -// 64-token wrapper overhead and the previous-summary subtraction in its -// message form. -func TestCompactionPieceBudgetArithmetic(t *testing.T) { - capture := testCompactCapture() - base := capture.ContextWindow - capture.OutputReserve - estimateTokens(capture.SystemPrompt, nil) - 64 - if got := compactionPieceBudget(capture, ""); got != base { - t.Fatalf("empty-previous budget = %d, want %d", got, base) - } - previous := "a previous summary of some length" - messageForm := estimateTokens("", []model.Message{{Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: previous}}}}) - if got := compactionPieceBudget(capture, previous); got != base-messageForm { - t.Fatalf("previous-summary budget = %d, want %d", got, base-messageForm) - } - // The subtraction uses the message form, not the plain-text form: the - // estimator's per-message overhead is part of it. - if messageForm != estimateTokens(previous, nil)+4 { - t.Fatalf("message-form estimate %d, want the plain-text estimate plus the per-message overhead", messageForm) + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on a failure path") } } @@ -1016,8 +645,9 @@ func TestCompactionBoundaryRows(t *testing.T) { t.Run("every projectable kind derives the covered last message", func(t *testing.T) { for _, covered := range []graphEntry{input, assistant, toolResult, signal} { - if got := compactionBoundary([]graphEntry{covered}, "", 1); got != covered.Envelope.ID { - t.Fatalf("boundary over the %s row = %q, want the covered entry %q", covered.Envelope.Kind, got, covered.Envelope.ID) + { + got := compactionBoundary([]graphEntry{covered}, "", 1) + testassert.Require(t, got == covered.Envelope.ID, "boundary over the %s row = %q, want the covered entry %q", covered.Envelope.Kind, got, covered.Envelope.ID) } } }) @@ -1027,34 +657,44 @@ func TestCompactionBoundaryRows(t *testing.T) { // The frozen snapshot carried the prior summary and the raced // signal: the boundary is the first entry after the target's // sequence, not the first after the named entry's own. - if got := compactionBoundary([]graphEntry{input, assistant, signal, compaction}, compaction.Envelope.ID, 2); got != signal.Envelope.ID { - t.Fatalf("boundary = %q, want the raced signal %q counted after the boundary target", got, signal.Envelope.ID) + { + got := compactionBoundary([]graphEntry{input, assistant, signal, compaction}, compaction.Envelope.ID, 2) + testassert.Equal(t, got, signal.Envelope.ID) } }) t.Run("the summary-only re-compaction names the prior compaction entry", func(t *testing.T) { compaction := graphEntry{Envelope: Entry{ID: "co-1", Sequence: 3}, Compaction: &compactionEntry{BoundaryEntryID: assistant.Envelope.ID}} - if got := compactionBoundary([]graphEntry{input, assistant, compaction}, compaction.Envelope.ID, 1); got != compaction.Envelope.ID { - t.Fatalf("boundary = %q, want the named compaction entry itself", got) + { + got := compactionBoundary([]graphEntry{input, assistant, compaction}, compaction.Envelope.ID, 1) + testassert.Equal(t, got, compaction.Envelope.ID) } }) } +// UsageByModelForTest returns the totals' pieces keyed by ref: the shared +// usage-by-model selection; each caller keeps its own expected counts. +func UsageByModelForTest(totals UsageTotals, ref model.ModelRef) UsageTotals { + var out UsageTotals + for _, mu := range totals.ByModel { + if mu.Model == ref { + out.ByModel = append(out.ByModel, mu) + } + } + return out +} + // compactedEntryOf returns the one compaction entry of the validated graph. func compactedEntryOf(t *testing.T, graph *sessionGraph) *compactionEntry { t.Helper() var found *compactionEntry for i := range graph.Entries { if graph.Entries[i].Compaction != nil { - if found != nil { - t.Fatalf("graph carries more than one compaction entry") - } + testassert.Require(t, found == nil, "graph carries more than one compaction entry") found = graph.Entries[i].Compaction } } - if found == nil { - t.Fatalf("graph carries no compaction entry") - } + testassert.Require(t, found != nil, "graph carries no compaction entry") return found } @@ -1086,80 +726,54 @@ func TestCommitCompactionAutomaticCommitsAtomically(t *testing.T) { usage := &UsageCount{InputTokens: 7, CachedInputTokens: 3, OutputTokens: 4} // snapshotLen 1: the fresh fixture's frozen snapshot carries the one // admitted input message. - if err := h.commitCompaction(c, testOpID, testCapture(), "summary one", 1, usage, false); err != nil { - t.Fatalf("commitCompaction: %v", err) + { + err := h.commitCompaction(c, testOpID, testCapture(), "summary one", 1, usage, false) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-commit graph: %v", err) - } + testassert.NoError(t, err) comp := compactedEntryOf(t, graph) - if comp.OperationID != testOpID || comp.Summary != "summary one" { - t.Fatalf("compaction entry = %+v, want the Operation-owned committed summary", comp) - } - if comp.BoundaryEntryID != graph.Entries[0].Envelope.ID { - t.Fatalf("boundary %q, want the last projectable entry %q", comp.BoundaryEntryID, graph.Entries[0].Envelope.ID) - } - if comp.Model != testCompactCapture().Model || comp.ConfigurationRevision != "rev-1" { - t.Fatalf("compaction entry identity = %+v rev %q, want the compact model and the capture's revision", comp.Model, comp.ConfigurationRevision) - } - if comp.Usage == nil || *comp.Usage != *usage { - t.Fatalf("compaction entry usage = %+v, want the orchestration's accumulated total", comp.Usage) - } - if graph.Session.State.CompactionEntryID != comp.EntryID { - t.Fatalf("compaction_entry_id %q, want the new entry %q", graph.Session.State.CompactionEntryID, comp.EntryID) - } + testassert.Require(t, comp.OperationID == testOpID && comp.Summary == "summary one", "compaction entry = %+v, want the Operation-owned committed summary", comp) + testassert.Equal(t, comp.BoundaryEntryID, graph.Entries[0].Envelope.ID) + testassert.Require(t, comp.Model == testCompactCapture().Model && comp.ConfigurationRevision == "rev-1", "compaction entry identity = %+v rev %q, want the compact model and the capture's revision", comp.Model, comp.ConfigurationRevision) + testassert.Require(t, comp.Usage != nil && *comp.Usage == *usage, "compaction entry usage = %+v, want the orchestration's reported total", comp.Usage) + testassert.Equal(t, graph.Session.State.CompactionEntryID, comp.EntryID) // The Operation continues: running, current, quiet, with no settlement. - if graph.Session.State.CurrentOperationID != testOpID { - t.Fatalf("current Operation %q, want the continuing %q", graph.Session.State.CurrentOperationID, testOpID) - } + testassert.Equal(t, graph.Session.State.CurrentOperationID, testOpID) state := operationStateOf(t, graph, testOpID) - if state.Status != OperationRunning || state.ActiveEffect != nil { - t.Fatalf("operation state = %+v, want running and quiet", state) - } + testassert.Require(t, state.Status == OperationRunning && state.ActiveEffect == nil, "operation state = %+v, want running and quiet", state) for i := range graph.Entries { - if graph.Entries[i].Settlement != nil { - t.Fatalf("settlement entry committed on the automatic path") - } + testassert.Require(t, graph.Entries[i].Settlement == nil, "settlement entry committed on the automatic path") } - // Both register totals carry the accumulated usage keyed by the compact + // Both register totals carry the reported usage keyed by the compact // model. want := UsageTotals{ByModel: []ModelUsage{{Model: testCompactCapture().Model, Usage: *usage}}} - if !usageTotalsEqual(state.Usage, want) { - t.Fatalf("operation usage = %+v, want %+v", state.Usage, want) - } - if !usageTotalsEqual(graph.Session.State.Usage, want) { - t.Fatalf("session usage = %+v, want %+v", graph.Session.State.Usage, want) - } + testassert.Require(t, usageTotalsEqual(state.Usage, want), "operation usage = %+v, want %+v", state.Usage, want) + testassert.Require(t, usageTotalsEqual(graph.Session.State.Usage, want), "session usage = %+v, want %+v", graph.Session.State.Usage, want) // One transaction: one new entry, each register advanced once. afterEntries, _ := storedSessionState(store, sessionID) - if len(afterEntries) != len(beforeEntries)+1 { - t.Fatalf("entry count %d, want %d (exactly the compaction entry)", len(afterEntries), len(beforeEntries)+1) - } - if got := registerRevision(t, store, sessionID, opKey); got != opRevision+1 { - t.Fatalf("operation register revision %d, want %d", got, opRevision+1) + testassert.Equal(t, len(afterEntries), len(beforeEntries)+1) + { + got := registerRevision(t, store, sessionID, opKey) + testassert.Equal(t, got, opRevision+1) } - if got := registerRevision(t, store, sessionID, sessionKey); got != sessionRevision+1 { - t.Fatalf("session register revision %d, want %d", got, sessionRevision+1) + { + got := registerRevision(t, store, sessionID, sessionKey) + testassert.Equal(t, got, sessionRevision+1) } // The projection flips to the summary message. after, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if len(after) != 2 { - t.Fatalf("projection messages = %d, want the system message and the summary", len(after)) - } - if after[1].Role != model.RoleAssistant || after[1].Source != testCompactCapture().Model { - t.Fatalf("summary message = %+v, want an assistant message sourced by the compact model", after[1]) - } - if got := after[1].TextContent(); got != "[Previous conversation summary]\n\nsummary one\n\n[End of summary. Continue from here.]" { - t.Fatalf("summary message text %q, want the verbatim framing around the committed summary", got) + testassert.NoError(t, err) + testassert.Equal(t, len(after), 2) + testassert.Require(t, after[1].Role == model.RoleAssistant && after[1].Source == testCompactCapture().Model, "summary message = %+v, want an assistant message sourced by the compact model", after[1]) + { + got := after[1].TextContent() + testassert.Equal(t, got, "[Previous conversation summary]\n\nsummary one\n\n[End of summary. Continue from here.]") } } @@ -1167,7 +781,7 @@ func TestCommitCompactionAutomaticCommitsAtomically(t *testing.T) { // shape: the automatic members land, then the dedicated Operation settles as // success in the same transaction — settlement entry, terminal Operation // register, cleared current Operation — with the settlement itself carrying -// no usage. +// no usage and the unmarked source. func TestCommitCompactionManualSettlesInTheSameTransaction(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) opKey := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID} @@ -1179,74 +793,52 @@ func TestCommitCompactionManualSettlesInTheSameTransaction(t *testing.T) { usage := &UsageCount{InputTokens: 6, CachedInputTokens: 1, OutputTokens: 2} // snapshotLen 1: the fresh fixture's frozen snapshot carries the one // admitted input message. - if err := h.commitCompaction(c, testOpID, testCapture(), "manual summary", 1, usage, true); err != nil { - t.Fatalf("commitCompaction: %v", err) + { + err := h.commitCompaction(c, testOpID, testCapture(), "manual summary", 1, usage, true) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-commit graph: %v", err) - } + testassert.NoError(t, err) comp := compactedEntryOf(t, graph) - if comp.Summary != "manual summary" || comp.BoundaryEntryID != graph.Entries[0].Envelope.ID { - t.Fatalf("compaction entry = %+v, want the committed summary at the projectable boundary", comp) - } + testassert.Require(t, comp.Summary == "manual summary" && comp.BoundaryEntryID == graph.Entries[0].Envelope.ID, "compaction entry = %+v, want the committed summary at the projectable boundary", comp) settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationSuccess || settlement.Detail != "" { - t.Fatalf("settlement entry = %+v, want the success terminal with no detail", settlement) - } - if settlement.Model != nil || settlement.Usage != nil { - t.Fatalf("settlement entry = %+v, want no usage on the settlement itself", settlement) - } + testassert.Require(t, settlement.Status == OperationSuccess && settlement.Detail == "" && (settlement.Model == nil && settlement.Usage == nil) && !settlement.CompactionUsage, "settlement entry = %+v, want the success terminal with no detail; settlement entry = %+v, want no usage on the settlement itself; settlement compaction_usage = true, want the manual settlement unmarked", settlement, settlement) state := operationStateOf(t, graph, testOpID) - if state.Status != OperationSuccess || state.Terminal == nil || state.Terminal.SettlementEntry.EntryID != settlement.EntryID { - t.Fatalf("operation state = %+v, want the success terminal naming the settlement entry", state) - } - if graph.Session.State.CurrentOperationID != "" { - t.Fatalf("current Operation %q, want it cleared", graph.Session.State.CurrentOperationID) - } - if graph.Session.State.CompactionEntryID != comp.EntryID { - t.Fatalf("compaction_entry_id %q, want the new entry %q", graph.Session.State.CompactionEntryID, comp.EntryID) - } + testassert.Require(t, state.Status == OperationSuccess && state.Terminal != nil && state.Terminal.SettlementEntry.EntryID == settlement.EntryID, "operation state = %+v, want the success terminal naming the settlement entry", state) + testassert.Equal(t, graph.Session.State.CurrentOperationID, "") + testassert.Equal(t, graph.Session.State.CompactionEntryID, comp.EntryID) // The usage landed through the compaction part: both totals carry it // keyed by the compact model. want := UsageTotals{ByModel: []ModelUsage{{Model: testCompactCapture().Model, Usage: *usage}}} - if !usageTotalsEqual(state.Usage, want) { - t.Fatalf("operation usage = %+v, want %+v", state.Usage, want) - } - if !usageTotalsEqual(graph.Session.State.Usage, want) { - t.Fatalf("session usage = %+v, want %+v", graph.Session.State.Usage, want) - } + testassert.Require(t, usageTotalsEqual(state.Usage, want), "operation usage = %+v, want %+v", state.Usage, want) + testassert.Require(t, usageTotalsEqual(graph.Session.State.Usage, want), "session usage = %+v, want %+v", graph.Session.State.Usage, want) // One transaction: the entry and the settlement are the only new // entries, and each register advanced once per write inside it. afterEntries, _ := storedSessionState(store, sessionID) - if len(afterEntries) != len(beforeEntries)+2 { - t.Fatalf("entry count %d, want %d (the compaction entry and the settlement)", len(afterEntries), len(beforeEntries)+2) + testassert.Equal(t, len(afterEntries), len(beforeEntries)+2) + { + got := registerRevision(t, store, sessionID, opKey) + testassert.Equal(t, got, opRevision+2) } - if got := registerRevision(t, store, sessionID, opKey); got != opRevision+2 { - t.Fatalf("operation register revision %d, want %d", got, opRevision+2) - } - if got := registerRevision(t, store, sessionID, sessionKey); got != sessionRevision+2 { - t.Fatalf("session register revision %d, want %d", got, sessionRevision+2) + { + got := registerRevision(t, store, sessionID, sessionKey) + testassert.Equal(t, got, sessionRevision+2) } // The projection flips to the summary message. after, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if len(after) != 2 || after[1].Role != model.RoleAssistant || after[1].TextContent() != "[Previous conversation summary]\n\nmanual summary\n\n[End of summary. Continue from here.]" { - t.Fatalf("projection = %+v, want the system message and the committed summary", after) - } + testassert.NoError(t, err) + testassert.Require(t, len(after) == 2 && after[1].Role == model.RoleAssistant && after[1].TextContent() == "[Previous conversation summary]\n\nmanual summary\n\n[End of summary. Continue from here.]", "projection = %+v, want the system message and the committed summary", after) } // TestCommitCompactionInjectedFailureLeavesPreviousStateCurrent proves the // failure contract: an injected transaction failure commits nothing of the // compaction — the previous projection stays current — and the commit's own // failure settles the quiet running Operation through the direct terminal -// settlement carrying the commit error and the accumulated usage keyed by the +// settlement carrying the commit error and the reported usage keyed by the // compact model. func TestCommitCompactionInjectedFailureLeavesPreviousStateCurrent(t *testing.T) { t.Run("a persistently failing store leaves every durable value unchanged", func(t *testing.T) { @@ -1257,51 +849,38 @@ func TestCommitCompactionInjectedFailureLeavesPreviousStateCurrent(t *testing.T) sessionRevision := registerRevision(t, store, sessionID, sessionKey) beforeEntries, _ := storedSessionState(store, sessionID) before, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } + testassert.NoError(t, err) store.txHook = func(string) error { return errors.New("injected storage failure") } - if err := h.commitCompaction(c, testOpID, testCapture(), "summary", 1, nil, false); err == nil { - t.Fatalf("commitCompaction succeeded past an injected failure") + { + err := h.commitCompaction(c, testOpID, testCapture(), "summary", 1, nil, false) + testassert.Require(t, err != nil, "commitCompaction succeeded past an injected failure") } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) afterEntries, _ := storedSessionState(store, sessionID) - if len(afterEntries) != len(beforeEntries) { - t.Fatalf("entry count %d, want the unchanged %d", len(afterEntries), len(beforeEntries)) + testassert.Equal(t, len(afterEntries), len(beforeEntries)) + { + got := registerRevision(t, store, sessionID, opKey) + testassert.Equal(t, got, opRevision) } - if got := registerRevision(t, store, sessionID, opKey); got != opRevision { - t.Fatalf("operation register revision %d, want the unchanged %d", got, opRevision) - } - if got := registerRevision(t, store, sessionID, sessionKey); got != sessionRevision { - t.Fatalf("session register revision %d, want the unchanged %d", got, sessionRevision) - } - if graph.Session.State.CompactionEntryID != "" { - t.Fatalf("compaction_entry_id %q, want it empty", graph.Session.State.CompactionEntryID) + { + got := registerRevision(t, store, sessionID, sessionKey) + testassert.Equal(t, got, sessionRevision) } + testassert.Equal(t, graph.Session.State.CompactionEntryID, "") state := operationStateOf(t, graph, testOpID) - if state.Status != OperationRunning { - t.Fatalf("operation state = %+v, want still running", state) - } + testassert.Require(t, state.Status == OperationRunning, "operation state = %+v, want still running", state) after, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if !reflect.DeepEqual(before, after) { - t.Fatalf("projection changed through a failed commit:\nbefore %+v\nafter %+v", before, after) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(before, after), "projection changed through a failed commit:\nbefore %+v\nafter %+v", before, after) }) - t.Run("a one-shot commit failure settles the accumulated usage", func(t *testing.T) { + t.Run("a one-shot commit failure settles the reported usage", func(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) before, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } + testassert.NoError(t, err) injected := errors.New("injected commit failure") seen := false store.txHook = func(step string) error { @@ -1314,47 +893,27 @@ func TestCommitCompactionInjectedFailureLeavesPreviousStateCurrent(t *testing.T) usage := &UsageCount{InputTokens: 7, CachedInputTokens: 3, OutputTokens: 4} err = h.commitCompaction(c, testOpID, testCapture(), "summary", 1, usage, false) - if err == nil || !strings.Contains(err.Error(), "injected commit failure") { - t.Fatalf("error %v, want the injected commit failure", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "injected commit failure"), "error %v, want the injected commit failure", err) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed on a failed transaction") - } - } - if graph.Session.State.CompactionEntryID != "" { - t.Fatalf("compaction_entry_id %q, want it empty", graph.Session.State.CompactionEntryID) + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on a failed transaction") } + testassert.Equal(t, graph.Session.State.CompactionEntryID, "") settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationFailure || !strings.Contains(settlement.Detail, "injected commit failure") { - t.Fatalf("settlement entry = %+v, want the commit failure terminal", settlement) - } - if settlement.Model == nil || *settlement.Model != testCompactCapture().Model { - t.Fatalf("settlement model = %+v, want the compact model identity", settlement.Model) - } - if settlement.Usage == nil || *settlement.Usage != *usage { - t.Fatalf("settlement usage = %+v, want the accumulated total", settlement.Usage) - } + testassert.Require(t, settlement.Status == OperationFailure && strings.Contains(settlement.Detail, "injected commit failure"), "settlement entry = %+v, want the commit failure terminal", settlement) + testassert.Require(t, settlement.Model != nil && *settlement.Model == testCompactCapture().Model, "settlement model = %+v, want the compact model identity", settlement.Model) + testassert.Require(t, settlement.Usage != nil && *settlement.Usage == *usage && settlement.CompactionUsage, "settlement usage = %+v, want the reported total; settlement compaction_usage = false, want the summary usage provenance marked", settlement.Usage) after, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if !reflect.DeepEqual(before, after) { - t.Fatalf("projection changed through a failed commit:\nbefore %+v\nafter %+v", before, after) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(before, after), "projection changed through a failed commit:\nbefore %+v\nafter %+v", before, after) }) t.Run("the manual settlement shares the commit transaction", func(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) before, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } + testassert.NoError(t, err) injected := errors.New("injected settlement failure") inserts := 0 store.txHook = func(step string) error { @@ -1368,33 +927,19 @@ func TestCommitCompactionInjectedFailureLeavesPreviousStateCurrent(t *testing.T) } err = h.commitCompaction(c, testOpID, testCapture(), "summary", 1, nil, true) - if err == nil || !strings.Contains(err.Error(), "injected settlement failure") { - t.Fatalf("error %v, want the injected settlement failure", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "injected settlement failure"), "error %v, want the injected settlement failure", err) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry survived the failed shared transaction") - } - } - if graph.Session.State.CompactionEntryID != "" { - t.Fatalf("compaction_entry_id %q, want it empty", graph.Session.State.CompactionEntryID) + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry survived the failed shared transaction") } + testassert.Equal(t, graph.Session.State.CompactionEntryID, "") // The commit's own failure settlement stands alone. settlement := settlementEntryOf(t, graph) - if settlement.Status != OperationFailure || !strings.Contains(settlement.Detail, "injected settlement failure") { - t.Fatalf("settlement entry = %+v, want the commit failure terminal", settlement) - } + testassert.Require(t, settlement.Status == OperationFailure && strings.Contains(settlement.Detail, "injected settlement failure"), "settlement entry = %+v, want the commit failure terminal", settlement) after, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if !reflect.DeepEqual(before, after) { - t.Fatalf("projection changed through a failed commit:\nbefore %+v\nafter %+v", before, after) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(before, after), "projection changed through a failed commit:\nbefore %+v\nafter %+v", before, after) }) } diff --git a/harness/compact_trigger_test.go b/harness/compact_trigger_test.go index 1acc6234..ccb6fb12 100644 --- a/harness/compact_trigger_test.go +++ b/harness/compact_trigger_test.go @@ -1,13 +1,17 @@ // Contract coverage for the conversation model effect's compaction trigger: -// an overflowing request compacts before it is sent and proceeds against the -// new projection, a fitting request is untouched, a failed checkpoint fails -// the Operation with the request unsent, a storage failure that leaves the -// Operation running propagates as the raw effect error to the storage-failure -// latch and Wait — over a live piece intent and over the quiet direct -// settlement — steering submitted during the orchestration stays buffered for -// the next model boundary, a later overflow in the same Operation compacts -// again, the committed boundary covers exactly the frozen snapshot so an entry -// appended between the freeze and the commit still projects, and compact piece +// the newest reported conversation usage after the current compaction boundary +// decides the checkpoint — an overflowing report compacts before the request +// is sent and the turn proceeds against the new projection, a report at or +// below the threshold leaves the request untouched, a missing report never +// compacts, a present zero obeys the same inequality, a failed checkpoint +// fails the Operation with the request unsent, a storage failure that leaves +// the Operation running propagates as the raw effect error to the +// storage-failure latch and Wait — over a live summary intent and over the +// quiet direct settlement — steering submitted during the orchestration stays +// buffered for the next model boundary, a later overflow in the same Operation +// compacts again, the committed boundary covers exactly the frozen snapshot so +// an entry appended between the freeze and the commit still projects, summary +// usage is billed without ever supplying the measurement, and compact summary // requests never trigger compaction. package harness @@ -24,6 +28,7 @@ import ( "time" "github.com/MMinasyan/lightcode/agent" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -32,108 +37,206 @@ import ( func projectedTriggerRequest(t *testing.T, h *Harness, c *coordinator) model.Request { t.Helper() msgs, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } + testassert.NoError(t, err) req, err := model.NewRequest(model.Request{ Messages: msgs, Tools: []model.ToolDefinition{testToolDefinition()}, }) - if err != nil { - t.Fatalf("request: %v", err) - } + testassert.NoError(t, err) return req } -// triggerCapture returns the fixture capture with the conversation window set -// against the request's estimate-plus-reserve threshold: one below it makes -// the request overflow the trigger's strict inequality, exactly at it leaves -// the request fitting. -func triggerCapture(t *testing.T, req model.Request, overflow bool) ExecutionCapture { - t.Helper() +// triggerUsageCapture returns the fixture capture whose conversation +// threshold — ContextWindow minus OutputReserve, the value the trigger's +// strict inequality compares reported usage against — is exactly the given +// value. A negative threshold is a capture whose reserve exceeds its window. +func triggerUsageCapture(threshold int64) ExecutionCapture { capture := testCapture() - threshold := estimateTokens("", req.Messages) + capture.OutputReserve - if overflow { - capture.ContextWindow = threshold - 1 - } else { - capture.ContextWindow = threshold - } + capture.ContextWindow = int(threshold) + 1024 + capture.OutputReserve = 1024 return capture } -// overflowingTriggerCapture sizes the conversation window through the -// triggerCapture arithmetic over the deterministic projection of one admitted -// text input — the captured system message plus the one input message — so -// the projected request overflows the trigger's estimate-plus-reserve -// threshold by one. -func overflowingTriggerCapture(input string) ExecutionCapture { - capture := testCapture() - projected := []model.Message{ - {Role: model.RoleSystem, Content: []model.ContentPart{{Kind: model.PartText, Text: capture.SystemPrompt}}}, - {Role: model.RoleUser, Content: admissionContent(input)}, - } - capture.ContextWindow = estimateTokens("", projected) + capture.OutputReserve - 1 - return capture -} - -// invokeTriggerEffect drives one model effect with the given request through -// the real Agent assembly. -func invokeTriggerEffect(t *testing.T, me agent.ModelEffect, req model.Request) (agent.ModelSettlement, error) { +// seedConversationUsage runs one ordinary conversation model effect whose +// completed output reports the given usage: the committed assistant entry is +// the provider measurement the trigger consumes. The seed effect runs over the +// given capture before any report the fixture tests exists. +func seedConversationUsage(t *testing.T, h *Harness, c *coordinator, capture ExecutionCapture, usage UsageCount) { t.Helper() - return me(context.Background(), req, func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - }) + modelFn := func(context.Context, model.Request) (model.Stream, error) { + return streamOf(turnDelta("stop", "seed turn"), usageDelta(model.Usage{ + InputTokens: int(usage.InputTokens), + CachedInputTokens: int(usage.CachedInputTokens), + OutputTokens: int(usage.OutputTokens), + })), nil + } + exec := Execution{Model: modelFn, CompactModel: forbiddenConversationModel(t), NormalizeTool: objectNormalize} + { + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), projectedTriggerRequest(t, h, c), nil) + testassert.Require(t, err == nil && set.Disposition == agent.DispoReady, "seed turn settlement = %+v, err %v, want ready", set, err) + } } -// TestModelEffectCompactTriggerFittingRequestUntouched proves the fitting row: -// a request whose estimate plus the output reserve does not exceed the -// conversation window is sent unchanged, the compact transport never runs, and -// no compaction work commits. -func TestModelEffectCompactTriggerFittingRequestUntouched(t *testing.T) { +// TestModelEffectCompactTriggerReportedUsageCompacts is the provider-usage +// trigger regression: the request messages are too small for any text estimate +// to overflow the window, and a seeded reported conversation usage above the +// window minus the output reserve carries the trigger instead. Every summand +// is required for the verdict: the full total 2049 strictly overflows the +// fixture threshold 2048, while dropping any single summand lands at or below +// it — the cached input count participates exactly like the others. +func TestModelEffectCompactTriggerReportedUsageCompacts(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) - req := projectedTriggerRequest(t, h, c) - capture := triggerCapture(t, req, false) - var sent []model.Request + capture := testCapture() + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 1000, CachedInputTokens: 1000, OutputTokens: 49}) + compactCalls := 0 + conversationCalls := 0 exec := Execution{ - Model: func(ctx context.Context, r model.Request) (model.Stream, error) { - sent = append(sent, r) + Model: func(context.Context, model.Request) (model.Stream, error) { + conversationCalls++ return completedTurnStream(), nil }, - CompactModel: forbiddenConversationModel(t), + CompactModel: func(context.Context, model.Request) (model.Stream, error) { + compactCalls++ + return summaryStream("summary one", model.Usage{InputTokens: 10, OutputTokens: 5}), nil + }, NormalizeTool: objectNormalize, } - set, err := invokeTriggerEffect(t, h.modelEffect(c, testOpID, exec, capture), req) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready", set.Disposition) - } - if len(sent) != 1 { - t.Fatalf("conversation transport calls = %d, want exactly one", len(sent)) - } - if !reflect.DeepEqual(req.Messages, sent[0].Messages) { - t.Fatalf("sent messages changed through a fitting request:\nwant %+v\ngot %+v", req.Messages, sent[0].Messages) - } + req := projectedTriggerRequest(t, h, c) + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, compactCalls, 1) + testassert.Equal(t, conversationCalls, 1) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) + testassert.NoError(t, err) + comp := compactedEntryOf(t, graph) + testassert.Require(t, comp.OperationID == testOpID && comp.Summary == "summary one", "compaction entry = %+v, want the Operation-owned committed summary", comp) +} + +// TestModelEffectCompactTriggerNoReportNeverCompacts is the missing-report +// row: a window so small that every text estimate overflows it still never +// compacts while no reported usage exists. +func TestModelEffectCompactTriggerNoReportNeverCompacts(t *testing.T) { + h, _, c, _ := newEffectHarness(t, nil) + capture := testCapture() + capture.ContextWindow = 1 + compactCalls := 0 + conversationCalls := 0 + exec := Execution{ + Model: func(context.Context, model.Request) (model.Stream, error) { + conversationCalls++ + return completedTurnStream(), nil + }, + CompactModel: func(context.Context, model.Request) (model.Stream, error) { + compactCalls++ + return summaryStream("summary one", model.Usage{}), nil + }, + NormalizeTool: objectNormalize, } - if graph.Session.State.CompactionEntryID != "" { - t.Fatalf("compaction_entry_id %q, want it empty on a fitting request", graph.Session.State.CompactionEntryID) + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), projectedTriggerRequest(t, h, c), nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, compactCalls, 0) + testassert.Equal(t, conversationCalls, 1) +} + +// TestModelEffectCompactTriggerFittingRequestUntouched proves the fitting +// rows: a reported usage below the threshold and a reported usage exactly at +// the threshold are sent unchanged — the trigger is a strict inequality — the +// compact transport never runs, and no compaction work commits. +func TestModelEffectCompactTriggerFittingRequestUntouched(t *testing.T) { + for _, row := range []struct { + name string + usage UsageCount + }{ + {"below threshold", UsageCount{InputTokens: 1000, CachedInputTokens: 1000, OutputTokens: 47}}, + {"equal threshold", UsageCount{InputTokens: 1000, CachedInputTokens: 1000, OutputTokens: 48}}, + } { + t.Run(row.name, func(t *testing.T) { + h, store, c, sessionID := newEffectHarness(t, nil) + capture := testCapture() // threshold 4096 - 2048 = 2048 + seedConversationUsage(t, h, c, capture, row.usage) + req := projectedTriggerRequest(t, h, c) + var sent []model.Request + exec := Execution{ + Model: func(ctx context.Context, r model.Request) (model.Stream, error) { + sent = append(sent, r) + return completedTurnStream(), nil + }, + CompactModel: forbiddenConversationModel(t), + NormalizeTool: objectNormalize, + } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, len(sent), 1) + testassert.Require(t, reflect.DeepEqual(req.Messages, sent[0].Messages), "sent messages changed through a fitting request:\nwant %+v\ngot %+v", req.Messages, sent[0].Messages) + graph, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) + testassert.Equal(t, graph.Session.State.CompactionEntryID, "") + for i := range graph.Entries { + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed for a fitting request") + } + rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationRunning) + }) } - for i := range graph.Entries { - if graph.Entries[i].Compaction != nil { - t.Fatalf("compaction entry committed for a fitting request") - } +} + +// TestModelEffectCompactTriggerPresentZeroWithReserveOverWindowCompacts +// proves the present-zero row: a reported all-zero usage obeys the same +// strict inequality, and when the output reserve exceeds the context window +// the threshold is negative, so the zero report overflows it. +func TestModelEffectCompactTriggerPresentZeroWithReserveOverWindowCompacts(t *testing.T) { + h, store, c, sessionID := newEffectHarness(t, nil) + capture := triggerUsageCapture(-1) // window 1023, reserve 1024: threshold -1 + seedConversationUsage(t, h, c, capture, UsageCount{}) + compactCalls := 0 + conversationCalls := 0 + exec := Execution{ + Model: func(context.Context, model.Request) (model.Stream, error) { + conversationCalls++ + return completedTurnStream(), nil + }, + CompactModel: func(context.Context, model.Request) (model.Stream, error) { + compactCalls++ + return summaryStream("summary one", model.Usage{}), nil + }, + NormalizeTool: objectNormalize, } - rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), projectedTriggerRequest(t, h, c), nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Require(t, compactCalls == 1 && conversationCalls == 1, "compact/conversation transport calls = %d/%d, want 1/1 for the overflowing zero report", compactCalls, conversationCalls) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } - if rec.State.Status != OperationRunning { - t.Fatalf("operation status %q, want running after the fitting request", rec.State.Status) +} + +// TestModelEffectCompactTriggerWideUsageSumCompacts proves big.Int exactness: +// counts whose total exceeds the widest int64 trigger through the exact +// comparison where int64 addition would wrap to a negative total. +func TestModelEffectCompactTriggerWideUsageSumCompacts(t *testing.T) { + h, _, c, _ := newEffectHarness(t, nil) + capture := testCapture() + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 6_000_000_000_000_000_000, CachedInputTokens: 6_000_000_000_000_000_000}) + compactCalls := 0 + exec := Execution{ + Model: func(context.Context, model.Request) (model.Stream, error) { + return completedTurnStream(), nil + }, + CompactModel: func(context.Context, model.Request) (model.Stream, error) { + compactCalls++ + return summaryStream("summary one", model.Usage{}), nil + }, + NormalizeTool: objectNormalize, } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), projectedTriggerRequest(t, h, c), nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, compactCalls, 1) } // triggerCompactCommitStore holds the successful compaction transaction @@ -172,14 +275,15 @@ func (s *triggerCompactCommitStore) Transact(ctx context.Context, fn func(Transa // TestModelEffectCompactTriggerOverflowCompactsFirst proves the overflow row: // the orchestration runs and the automatic commit lands before the request is // sent, the sent request carries the projection's summary message with no -// post-boundary entries, and the piece usage is attributed to the compaction -// entry and both register totals keyed by the compact model. +// post-boundary entries, and the summary usage is attributed to the +// compaction entry and both register totals keyed by the compact model. func TestModelEffectCompactTriggerOverflowCompactsFirst(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) + capture := testCapture() + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 3000, CachedInputTokens: 100, OutputTokens: 50}) req := projectedTriggerRequest(t, h, c) - capture := triggerCapture(t, req, true) compactRef := testCompactCapture().Model - pieceUsage := UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5} + summaryUsage := UsageCount{InputTokens: 10, CachedInputTokens: 2, OutputTokens: 5} var sent []model.Request var conversationCalls atomic.Int64 compactCalls := 0 @@ -238,9 +342,7 @@ func TestModelEffectCompactTriggerOverflowCompactsFirst(t *testing.T) { } }() go func() { - set, err := h.modelEffect(c, testOpID, exec, capture)(context.Background(), req, func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - }) + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) done <- effectResult{set, err} close(done) }() @@ -254,153 +356,91 @@ func TestModelEffectCompactTriggerOverflowCompactsFirst(t *testing.T) { // was still running — a split implementation would show the entry without // the register changes. entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) sreg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: sessionID, Kind: RegisterSession}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) sess, err := decodeSessionRegister(sreg) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) oreg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) op, err := decodeOperationRegister(oreg) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) parked := struct { entries []Entry session SessionRecord operation OperationRecord }{entries, sess, op} - if len(parked.entries) == 0 || parked.entries[len(parked.entries)-1].Kind != EntryCompaction { - t.Fatalf("parked entry tail = %+v, want the compaction entry committed last", parked.entries) - } + testassert.Require(t, len(parked.entries) != 0 && parked.entries[len(parked.entries)-1].Kind == EntryCompaction, "parked entry tail = %+v, want the compaction entry committed last", parked.entries) var parkedEntry struct { EntryID string `json:"entry_id"` BoundaryEntryID string `json:"boundary_entry_id"` Summary string `json:"summary"` Usage *UsageCount `json:"usage"` } - if err := json.Unmarshal(parked.entries[len(parked.entries)-1].Payload, &parkedEntry); err != nil { - t.Fatalf("decode parked compaction entry: %v", err) - } - if parkedEntry.BoundaryEntryID != boundary { - t.Fatalf("parked boundary %q != the last pre-commit entry %q", parkedEntry.BoundaryEntryID, boundary) - } - if parkedEntry.Summary != "summary one" || parkedEntry.Usage == nil || *parkedEntry.Usage != pieceUsage { - t.Fatalf("parked compaction summary/usage = %+v", parkedEntry) - } - if parked.session.State.CompactionEntryID != parkedEntry.EntryID { - t.Fatalf("parked compaction_entry_id = %q, want the committed entry %q", parked.session.State.CompactionEntryID, parkedEntry.EntryID) - } - parkedSessionUsage := UsageTotals{} - for _, mu := range parked.session.State.Usage.ByModel { - if mu.Model == compactRef { - parkedSessionUsage.ByModel = append(parkedSessionUsage.ByModel, mu) - } - } - if len(parkedSessionUsage.ByModel) != 1 || parkedSessionUsage.ByModel[0].Usage != pieceUsage { - t.Fatalf("parked session usage = %+v, want the piece's counts", parked.session.State.Usage) - } - if parked.operation.State.Status != OperationRunning || parked.operation.State.Terminal != nil { - t.Fatalf("parked operation status = %q, want the enclosing Operation still running", parked.operation.State.Status) - } - parkedOperationUsage := UsageTotals{} - for _, mu := range parked.operation.State.Usage.ByModel { - if mu.Model == compactRef { - parkedOperationUsage.ByModel = append(parkedOperationUsage.ByModel, mu) - } - } - if len(parkedOperationUsage.ByModel) != 1 || parkedOperationUsage.ByModel[0].Usage != pieceUsage { - t.Fatalf("parked operation usage = %+v, want the piece's counts", parked.operation.State.Usage) - } + { + err := json.Unmarshal(parked.entries[len(parked.entries)-1].Payload, &parkedEntry) + testassert.NoError(t, err) + } + testassert.Require(t, parkedEntry.BoundaryEntryID == boundary && (parkedEntry.Summary == "summary one" && parkedEntry.Usage != nil && *parkedEntry.Usage == summaryUsage), "got %v, want %v; parked compaction summary/usage = %+v", parkedEntry.BoundaryEntryID, boundary, parkedEntry) + testassert.Equal(t, parked.session.State.CompactionEntryID, parkedEntry.EntryID) + parkedSessionUsage := UsageByModelForTest(parked.session.State.Usage, compactRef) + testassert.Require(t, len(parkedSessionUsage.ByModel) == 1 && parkedSessionUsage.ByModel[0].Usage == summaryUsage, "parked session usage = %+v, want the summary's counts", parked.session.State.Usage) + testassert.Require(t, parked.operation.State.Status == OperationRunning && parked.operation.State.Terminal == nil, "parked operation status = %q, want the enclosing Operation still running", parked.operation.State.Status) + parkedOperationUsage := UsageByModelForTest(parked.operation.State.Usage, compactRef) + testassert.Require(t, len(parkedOperationUsage.ByModel) == 1 && parkedOperationUsage.ByModel[0].Usage == summaryUsage, "parked operation usage = %+v, want the summary's counts", parked.operation.State.Usage) select { case result := <-done: t.Fatalf("automatic effect returned while compaction transaction return held: %+v", result) case <-time.After(100 * time.Millisecond): } - if conversationCalls.Load() != 0 { - t.Fatal("conversation callback ran before compaction transaction return") - } + testassert.Require(t, conversationCalls.Load() == 0, "%v", "conversation callback ran before compaction transaction return") close(gated.release) select { case result := <-done: - if result.err != nil || result.set.Disposition != agent.DispoReady { - t.Fatalf("automatic effect after release = %+v", result) - } + testassert.Require(t, result.err == nil && result.set.Disposition == agent.DispoReady, "automatic effect after release = %+v", result) case <-time.After(2 * time.Second): t.Fatal("automatic effect did not finish after compaction transaction release") } - if compactCalls != 1 { - t.Fatalf("compact transport calls = %d, want exactly one piece", compactCalls) - } - if len(sent) != 1 { - t.Fatalf("conversation transport calls = %d, want exactly one after the compaction", len(sent)) - } + testassert.Equal(t, compactCalls, 1) + testassert.Equal(t, len(sent), 1) sentMsgs := sent[0].Messages - if len(sentMsgs) != 2 { - t.Fatalf("sent messages = %d, want the system message and the summary with no post-boundary entries:\n%+v", len(sentMsgs), sentMsgs) - } - if sentMsgs[0].Role != model.RoleSystem || sentMsgs[0].TextContent() != capture.SystemPrompt { - t.Fatalf("sent system message = %+v, want the captured prompt", sentMsgs[0]) - } - if sentMsgs[1].Role != model.RoleAssistant || sentMsgs[1].Source != compactRef { - t.Fatalf("sent summary message = %+v, want an assistant message sourced by the compact model", sentMsgs[1]) - } - if got := sentMsgs[1].TextContent(); got != "[Previous conversation summary]\n\nsummary one\n\n[End of summary. Continue from here.]" { - t.Fatalf("sent summary text %q, want the verbatim framing around the committed summary", got) + testassert.Require(t, len(sentMsgs) == 2, "sent messages = %d, want the system message and the summary with no post-boundary entries:\n%+v", len(sentMsgs), sentMsgs) + testassert.Require(t, sentMsgs[0].Role == model.RoleSystem && sentMsgs[0].TextContent() == capture.SystemPrompt, "sent system message = %+v, want the captured prompt", sentMsgs[0]) + testassert.Require(t, sentMsgs[1].Role == model.RoleAssistant && sentMsgs[1].Source == compactRef, "sent summary message = %+v, want an assistant message sourced by the compact model", sentMsgs[1]) + { + got := sentMsgs[1].TextContent() + testassert.Equal(t, got, "[Previous conversation summary]\n\nsummary one\n\n[End of summary. Continue from here.]") } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) comp := compactedEntryOf(t, graph) - if comp.OperationID != testOpID || comp.Summary != "summary one" { - t.Fatalf("compaction entry = %+v, want the Operation-owned committed summary", comp) - } - if comp.Usage == nil || *comp.Usage != pieceUsage { - t.Fatalf("compaction entry usage = %+v, want the piece's reported counts", comp.Usage) - } - if graph.Session.State.CompactionEntryID != comp.EntryID { - t.Fatalf("compaction_entry_id %q, want the new entry %q", graph.Session.State.CompactionEntryID, comp.EntryID) - } - compactionUsage := UsageTotals{} - for _, mu := range graph.Session.State.Usage.ByModel { - if mu.Model == compactRef { - compactionUsage.ByModel = append(compactionUsage.ByModel, mu) - } - } - if len(compactionUsage.ByModel) != 1 || compactionUsage.ByModel[0].Usage != pieceUsage { - t.Fatalf("session usage = %+v, want the piece's counts keyed by the compact model", graph.Session.State.Usage) - } + testassert.Require(t, comp.OperationID == testOpID && comp.Summary == "summary one", "compaction entry = %+v, want the Operation-owned committed summary", comp) + testassert.Require(t, comp.Usage != nil && *comp.Usage == summaryUsage, "compaction entry usage = %+v, want the summary's reported counts", comp.Usage) + testassert.Equal(t, graph.Session.State.CompactionEntryID, comp.EntryID) + compactionUsage := UsageByModelForTest(graph.Session.State.Usage, compactRef) + testassert.Require(t, len(compactionUsage.ByModel) == 1 && compactionUsage.ByModel[0].Usage == summaryUsage, "session usage = %+v, want the summary's counts keyed by the compact model", graph.Session.State.Usage) assistants := 0 for i := range graph.Entries { if graph.Entries[i].Assistant != nil { assistants++ } } - if assistants != 1 { - t.Fatalf("assistant entries = %d, want the one turn committed after the compaction", assistants) - } + testassert.Equal(t, assistants, 2) } -// TestModelEffectCompactTriggerCheckpointFailureUnsent proves the failure row: -// a piece failure inside its own effect settles the terminal failure with the -// accumulated usage keyed by the compact model, the effect returns the failure -// settlement with the request never sent, and the previous projection stays -// current with its successful prior compaction unchanged. The two-piece snapshot makes the -// accumulated total real: the first piece settles ready reporting usage, the -// second fails before any attempt. +// TestModelEffectCompactTriggerCheckpointFailureUnsent proves the failure +// row: a failed summary response that reports usage settles its own effect's +// terminal failure with that reported usage keyed by the compact model, the +// effect returns the failure settlement with the request never sent, and the +// previous projection stays current with its successful prior compaction +// unchanged. The prior checkpoint ran on a seeded overflowing report, and the +// failed checkpoint runs on the prior turn's present-zero report under a +// capture whose reserve exceeds its window. func TestModelEffectCompactTriggerCheckpointFailureUnsent(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) + priorCapture := testCapture() + seedConversationUsage(t, h, c, priorCapture, UsageCount{InputTokens: 3000}) priorRequest := projectedTriggerRequest(t, h, c) - priorCapture := triggerCapture(t, priorRequest, true) priorUsage := UsageCount{InputTokens: 7, OutputTokens: 3} priorExec := Execution{ Model: func(context.Context, model.Request) (model.Stream, error) { @@ -411,53 +451,45 @@ func TestModelEffectCompactTriggerCheckpointFailureUnsent(t *testing.T) { }, NormalizeTool: objectNormalize, } - if result, err := invokeTriggerEffect(t, h.modelEffect(c, testOpID, priorExec, priorCapture), priorRequest); err != nil || result.Disposition != agent.DispoReady { - t.Fatalf("prior automatic checkpoint = %+v, %v", result, err) + { + result, err := invokeModelEffect(t, h.modelEffect(c, testOpID, priorExec, priorCapture), priorRequest, nil) + testassert.Require(t, err == nil && result.Disposition == agent.DispoReady, "prior automatic checkpoint = %+v, %v", result, err) } - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, context.Background()); err != nil { - t.Fatal(err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, context.Background()) + testassert.NoError(t, err) } priorGraph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) prior := *compactedEntryOf(t, priorGraph) - if prior.EntryID == "" || prior.Summary != "prior summary" || prior.Usage == nil || *prior.Usage != priorUsage || operationStateOf(t, priorGraph, testOpID).Status != OperationSuccess { - t.Fatalf("successful nonempty prior checkpoint missing: %+v", prior) - } - const firstPieceOp = "op-2" + testassert.Require(t, prior.EntryID != "" && prior.Summary == "prior summary" && prior.Usage != nil && *prior.Usage == priorUsage && operationStateOf(t, priorGraph, testOpID).Status == OperationSuccess, "successful nonempty prior checkpoint missing: %+v", prior) + const laterOperation = "op-2" const failingOperation = "op-3" - pieces := compactTwoPieceSnapshot(t) - // Two durable admitted inputs — the split-Operation shape of two delivered - // messages — make the later checkpoint need a continuation piece. - mustAdmitWithoutExecution(t, h, sessionID, firstPieceOp, pieces[0].Content) - if err := h.settleAgentTerminal(c, firstPieceOp, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, context.Background()); err != nil { - t.Fatal(err) - } - mustAdmitWithoutExecution(t, h, sessionID, failingOperation, pieces[1].Content) + // Two durable admitted inputs — the split-Operation shape of two + // delivered messages. + mustAdmitWithoutExecution(t, h, sessionID, laterOperation, admissionContent("later input")) + { + err := h.settleAgentTerminal(c, laterOperation, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, context.Background()) + testassert.NoError(t, err) + } + mustAdmitWithoutExecution(t, h, sessionID, failingOperation, admissionContent("failing input")) before, err := h.projectContext(c, failingOperation) - if err != nil { - t.Fatalf("projectContext: %v", err) - } + testassert.NoError(t, err) preexisting, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) for i := range preexisting { preexisting[i].Payload = append(json.RawMessage(nil), preexisting[i].Payload...) } - // Use the actual projection: the prior committed summary/answer plus two - // durable inputs make the later checkpoint need a continuation piece. + // Use the actual projection: the prior committed summary and turn plus + // the two later inputs make the failed checkpoint's frozen snapshot. req, err := model.NewRequest(model.Request{ Messages: before, Tools: []model.ToolDefinition{testToolDefinition()}, }) - if err != nil { - t.Fatalf("request: %v", err) - } - capture := triggerCapture(t, req, true) + testassert.NoError(t, err) + capture := triggerUsageCapture(-1) // the prior turn's zero report overflows a negative threshold compactRef := testCompactCapture().Model - pieceCalls := 0 + summaryCalls := 0 conversationCalls := 0 exec := Execution{ Model: func(context.Context, model.Request) (model.Stream, error) { @@ -465,109 +497,72 @@ func TestModelEffectCompactTriggerCheckpointFailureUnsent(t *testing.T) { return nil, errors.New("failed checkpoint sent its conversation request") }, CompactModel: func(ctx context.Context, r model.Request) (model.Stream, error) { - pieceCalls++ - if pieceCalls == 1 { - if !strings.Contains(r.Messages[len(r.Messages)-1].TextContent(), "prior summary") { - return nil, errors.New("later checkpoint omitted the committed prior summary") - } - // the first piece: reports usage and settles ready - return summaryStream("S1", model.Usage{InputTokens: 4, OutputTokens: 2}), nil + summaryCalls++ + if !strings.Contains(r.Messages[len(r.Messages)-1].TextContent(), "prior summary") { + return nil, errors.New("later checkpoint omitted the committed prior summary") } - // the continuation piece: fails before any attempt - return nil, errors.New("provider failure") + // The one failed summary response: it reports usage before + // the read failure. + return failStream(errors.New("provider failure"), + turnDelta("", "partial"), usageDelta(model.Usage{InputTokens: 4, OutputTokens: 2})), nil }, Retry: (&retrySpy{limit: 0}).retry, NormalizeTool: objectNormalize, } - set, err := invokeTriggerEffect(t, h.modelEffect(c, failingOperation, exec, capture), req) - if err != nil { - t.Fatalf("model effect = %v, want the settled failure with a nil error", err) - } - if set.Disposition != agent.DispoFailure || set.Output != nil || !strings.Contains(set.Detail, "provider failure") { - t.Fatalf("settlement = %+v, want the outputless failure carrying the piece detail", set) - } - if pieceCalls != 2 || conversationCalls != 0 { - t.Fatalf("later compact/conversation calls = %d/%d, want 2/0", pieceCalls, conversationCalls) - } + set, err := invokeModelEffect(t, h.modelEffect(c, failingOperation, exec, capture), req, nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Output == nil && strings.Contains(set.Detail, "provider failure"), "settlement = %+v, want the outputless failure carrying the summary failure detail", set) + testassert.Require(t, summaryCalls == 1 && conversationCalls == 0, "later compact/conversation calls = %d/%d, want 1/0", summaryCalls, conversationCalls) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) var settlement *operationSettlementEntry for _, entry := range graph.Entries { if entry.Settlement != nil && entry.Envelope.OperationID == failingOperation { settlement = entry.Settlement } } - if settlement == nil { - t.Fatal("later failed Operation settlement missing") - } - if settlement.Status != OperationFailure || !strings.Contains(settlement.Detail, "provider failure") { - t.Fatalf("settlement entry = %+v, want the failure terminal", settlement) - } - if settlement.Model == nil || *settlement.Model != compactRef { - t.Fatalf("settlement model = %+v, want the compact model identity", settlement.Model) - } - if settlement.Usage == nil || *settlement.Usage != (UsageCount{InputTokens: 4, OutputTokens: 2}) { - t.Fatalf("settlement usage = %+v, want the accumulated piece usage", settlement.Usage) - } - // The failed checkpoint's accumulated usage reaches both totals keyed by + testassert.Require(t, settlement != nil && (settlement.Status == OperationFailure && strings.Contains(settlement.Detail, "provider failure")), "%v; settlement entry = %+v, want the failure terminal", "later failed Operation settlement missing", settlement) + testassert.Require(t, settlement.Model != nil && *settlement.Model == compactRef, "settlement model = %+v, want the compact model identity", settlement.Model) + testassert.Require(t, settlement.Usage != nil && *settlement.Usage == (UsageCount{InputTokens: 4, OutputTokens: 2}) && settlement.CompactionUsage, "settlement usage = %+v, want the failed summary's reported usage; settlement compaction_usage = false, want the summary usage provenance marked", settlement.Usage) + // The failed checkpoint's reported usage reaches both totals keyed by // the compact model: the Operation register and the Session register. wantTotals := UsageTotals{ByModel: []ModelUsage{{Model: compactRef, Usage: UsageCount{InputTokens: 4, OutputTokens: 2}}}} - if state := operationStateOf(t, graph, failingOperation); state.Status != OperationFailure || !usageTotalsEqual(state.Usage, wantTotals) { - t.Fatalf("operation usage = %+v, want the accumulated piece counts keyed by the compact model", state.Usage) - } - compactSessionTotals := UsageTotals{} - for _, contribution := range graph.Session.State.Usage.ByModel { - if contribution.Model == compactRef { - compactSessionTotals.ByModel = append(compactSessionTotals.ByModel, contribution) - } + { + state := operationStateOf(t, graph, failingOperation) + testassert.Require(t, state.Status == OperationFailure && usageTotalsEqual(state.Usage, wantTotals), "operation usage = %+v, want the reported summary counts keyed by the compact model", state.Usage) } + compactSessionTotals := UsageByModelForTest(graph.Session.State.Usage, compactRef) wantSessionTotals := UsageTotals{ByModel: []ModelUsage{{Model: compactRef, Usage: UsageCount{InputTokens: 11, OutputTokens: 5}}}} - if !usageTotalsEqual(compactSessionTotals, wantSessionTotals) { - t.Fatalf("session compact usage = %+v, want prior plus failed piece counts", compactSessionTotals) - } - if graph.Session.State.CompactionEntryID != prior.EntryID { - t.Fatalf("compaction_entry_id %q, want prior %q on failed checkpoint", graph.Session.State.CompactionEntryID, prior.EntryID) - } - if current := compactedEntryOf(t, graph); current.EntryID != prior.EntryID || current.Summary != prior.Summary { - t.Fatalf("prior compaction replaced: before=%+v after=%+v", prior, current) + testassert.Require(t, usageTotalsEqual(compactSessionTotals, wantSessionTotals), "session compact usage = %+v, want prior plus failed summary counts", compactSessionTotals) + testassert.Equal(t, graph.Session.State.CompactionEntryID, prior.EntryID) + { + current := compactedEntryOf(t, graph) + testassert.Require(t, current.EntryID == prior.EntryID && current.Summary == prior.Summary, "prior compaction replaced: before=%+v after=%+v", prior, current) } afterEntries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatal(err) - } - if len(afterEntries) < len(preexisting) || !reflect.DeepEqual(preexisting, afterEntries[:len(preexisting)]) { - t.Fatal("pre-existing entry envelope/payload bytes changed through later failure") - } + testassert.NoError(t, err) + testassert.Require(t, len(afterEntries) >= len(preexisting) && reflect.DeepEqual(preexisting, afterEntries[:len(preexisting)]), "%v", "pre-existing entry envelope/payload bytes changed through later failure") tail, err := store.ReadEntries(context.Background(), sessionID, preexisting[len(preexisting)-1].Sequence) - if err != nil { - t.Fatal(err) - } - if len(tail) != 1 || tail[0].Kind != EntryOperationSettlement || tail[0].OperationID != failingOperation { - t.Fatalf("exact later-checkpoint tail = %+v, want only failed Operation settlement and no replacement compaction", tail) - } + testassert.NoError(t, err) + testassert.Require(t, len(tail) == 1 && tail[0].Kind == EntryOperationSettlement && tail[0].OperationID == failingOperation, "exact later-checkpoint tail = %+v, want only failed Operation settlement and no replacement compaction", tail) after, err := h.projectContext(c, failingOperation) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if !reflect.DeepEqual(before, after) { - t.Fatalf("projection changed through the failed checkpoint:\nwant %+v\ngot %+v", before, after) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(before, after), "projection changed through the failed checkpoint:\nwant %+v\ngot %+v", before, after) } // TestModelEffectCompactTriggerSteeringStaysBuffered proves the steering row: // steering submitted during the orchestration never enters the frozen snapshot // and stays buffered — the rebuilt request carries only the compacted -// projection, no compact piece exits at a boundary, and the completed -// turn settles ready so the steering is delivered through ordinary successor -// admission after the Operation's terminal. +// projection, no compact summary request exits at a boundary, and the +// completed turn settles ready so the steering is delivered through ordinary +// successor admission after the Operation's terminal. func TestModelEffectCompactTriggerSteeringStaysBuffered(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, func(context.Context, model.Request) (model.Stream, error) { return completedTurnStream(), nil // the successor admission's own run completes }) + capture := triggerUsageCapture(4) + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 13}) req := projectedTriggerRequest(t, h, c) - capture := triggerCapture(t, req, true) var sent []model.Request exec := Execution{ Model: func(ctx context.Context, r model.Request) (model.Stream, error) { @@ -588,71 +583,65 @@ func TestModelEffectCompactTriggerSteeringStaysBuffered(t *testing.T) { }, NormalizeTool: objectNormalize, } - set, err := invokeTriggerEffect(t, h.modelEffect(c, testOpID, exec, capture), req) - if err != nil { - t.Fatalf("model effect: %v", err) - } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) + testassert.NoError(t, err) // The completed turn settles ready: waiting steering never keeps the - // Operation running inside the model effect, and no compact piece drains - // or exits at a boundary for the steering. - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want the ordinary ready settlement", set.Disposition) - } - if len(sent) != 1 { - t.Fatalf("conversation transport calls = %d, want exactly one", len(sent)) - } + // Operation running inside the model effect, and no compact summary + // request drains or exits at a boundary for the steering. + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, len(sent), 1) for _, msg := range sent[0].Messages { - if strings.Contains(msg.TextContent(), "s1") { - t.Fatalf("the rebuilt request carried the buffered steering: %+v", sent[0].Messages) - } + testassert.Require(t, !strings.Contains(msg.TextContent(), "s1"), "the rebuilt request carried the buffered steering: %+v", sent[0].Messages) } c.mu.Lock() buffered := len(c.steering) c.mu.Unlock() - if buffered != 1 { - t.Fatalf("steering buffered = %d, want the steering submitted during compaction to stay buffered", buffered) - } - if got := strings.Join(entryTexts(t, store, sessionID), ","); got != "hello" { - t.Fatalf("committed inputs = %q, want the steering never committed in place", got) + testassert.Equal(t, buffered, 1) + { + got := strings.Join(entryTexts(t, store, sessionID), ",") + testassert.Equal(t, got, "hello") } // The steering stays pending under its own identity for the successor // admission after the compaction-owning Operation's terminal: no separate // Operation was ever admitted while the orchestration ran. - if storedOperationExists(store, sessionID, "op-2") { - t.Fatalf("the steering submitted during compaction admitted its own operation") - } + testassert.Require(t, !storedOperationExists(store, sessionID, "op-2"), "the steering submitted during compaction admitted its own operation") // After the orchestration, the ordinary completion owner admits the head // under its own caller identity. - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, context.Background()); err != nil { - t.Fatalf("terminal settlement: %v", err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, context.Background()) + testassert.NoError(t, err) } release, err := h.reserve(h.ctx, c) // direct drain tests own the reservation around the body - if err != nil { - t.Fatalf("drain reservation: %v", err) - } + testassert.NoError(t, err) h.drainBuffers(c, &activeExecution{done: make(chan struct{}), execCtx: context.Background(), cancel: func() {}}) release() awaitOperationTerminal(t, store, sessionID, "op-2", OperationSuccess) awaitHarnessQuiet(t, h, sessionID) - if got := strings.Join(entryTexts(t, store, sessionID), ","); got != "hello,s1" { - t.Fatalf("committed inputs = %q, want the head admitted as its own input after orchestration", got) + { + got := strings.Join(entryTexts(t, store, sessionID), ",") + testassert.Equal(t, got, "hello,s1") } - if owner, _ := steeringInputOwner(t, store, sessionID, "s1"); owner != "op-2" { - t.Fatalf("steering input owned by %q, want its own caller-ID Operation", owner) + { + owner, _ := steeringInputOwner(t, store, sessionID, "s1") + testassert.Equal(t, owner, "op-2") } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } } // TestModelEffectCompactTriggerSecondOverflowSameOperation proves the -// no-count rule: a second overflowing request later in the same Operation +// no-count rule: a second overflowing report later in the same Operation // compacts again, committing a second compaction entry owned by the same -// Operation, and the Operation continues against the newest projection. +// Operation, and the Operation continues against the newest projection. The +// later turn's own modest report carries the second overflow; the seed's +// larger report stays behind the committed boundary. func TestModelEffectCompactTriggerSecondOverflowSameOperation(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) + capture := triggerUsageCapture(4) + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 13}) req1 := projectedTriggerRequest(t, h, c) - capture := triggerCapture(t, req1, true) summaries := []string{"S1", "S2"} compactCalls := 0 var sentTexts [][]string @@ -663,7 +652,7 @@ func TestModelEffectCompactTriggerSecondOverflowSameOperation(t *testing.T) { texts = append(texts, msg.TextContent()) } sentTexts = append(sentTexts, texts) - return completedTurnStream(), nil + return completedTurnStream(), nil // reports {3,1,2}: above threshold 4 }, CompactModel: func(ctx context.Context, r model.Request) (model.Stream, error) { compactCalls++ @@ -675,63 +664,88 @@ func TestModelEffectCompactTriggerSecondOverflowSameOperation(t *testing.T) { NormalizeTool: objectNormalize, } me := h.modelEffect(c, testOpID, exec, capture) - if set, err := invokeTriggerEffect(t, me, req1); err != nil || set.Disposition != agent.DispoReady { - t.Fatalf("first request: settlement %+v err %v, want ready", set, err) + { + set, err := invokeModelEffect(t, me, req1, nil) + testassert.Require(t, err == nil && set.Disposition == agent.DispoReady, "first request: settlement %+v err %v, want ready", set, err) } req2 := projectedTriggerRequest(t, h, c) - if estimateTokens("", req2.Messages)+capture.OutputReserve <= capture.ContextWindow { - t.Fatalf("fixture precondition: the second request %d tokens fits the window", estimateTokens("", req2.Messages)) - } - if set, err := invokeTriggerEffect(t, me, req2); err != nil || set.Disposition != agent.DispoReady { - t.Fatalf("second request: settlement %+v err %v, want ready", set, err) - } - if compactCalls != 2 { - t.Fatalf("compact transport calls = %d, want one piece per compaction", compactCalls) - } - if len(sentTexts) != 2 { - t.Fatalf("conversation transport calls = %d, want one per boundary", len(sentTexts)) + { + set, err := invokeModelEffect(t, me, req2, nil) + testassert.Require(t, err == nil && set.Disposition == agent.DispoReady, "second request: settlement %+v err %v, want ready", set, err) } + testassert.Equal(t, compactCalls, 2) + testassert.Equal(t, len(sentTexts), 2) // The second request was built against the first compaction's projection // and the second against the newest one. - if len(sentTexts[0]) != 2 || !strings.Contains(sentTexts[0][1], "S1") { - t.Fatalf("first sent request = %+v, want the system message and the first summary", sentTexts[0]) - } - if len(sentTexts[1]) != 2 || !strings.Contains(sentTexts[1][1], "S2") { - t.Fatalf("second sent request = %+v, want the system message and the second summary", sentTexts[1]) - } + testassert.Require(t, len(sentTexts[0]) == 2 && strings.Contains(sentTexts[0][1], "S1"), "first sent request = %+v, want the system message and the first summary", sentTexts[0]) + testassert.Require(t, len(sentTexts[1]) == 2 && strings.Contains(sentTexts[1][1], "S2"), "second sent request = %+v, want the system message and the second summary", sentTexts[1]) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) var comps []*compactionEntry for i := range graph.Entries { if graph.Entries[i].Compaction != nil { comps = append(comps, graph.Entries[i].Compaction) } } - if len(comps) != 2 { - t.Fatalf("compaction entries = %d, want one per overflow in the same Operation", len(comps)) - } + testassert.Equal(t, len(comps), 2) for i, comp := range comps { - if comp.OperationID != testOpID || comp.Summary != summaries[i] { - t.Fatalf("compaction entry %d = %+v, want the Operation-owned %q summary", i, comp, summaries[i]) - } + testassert.Require(t, comp.OperationID == testOpID && comp.Summary == summaries[i], "compaction entry %d = %+v, want the Operation-owned %q summary", i, comp, summaries[i]) + } + testassert.Equal(t, graph.Session.State.CompactionEntryID, comps[1].EntryID) +} + +// TestModelEffectCompactTriggerSummaryUsageStaysTotalsOnly proves the +// measurement exclusion and the cutoff in one row: a committed summary's +// usage is billed on the entry and both totals but never supplies the +// trigger measurement, and the seed report that overflowed the first +// checkpoint stays behind the committed boundary. The turn's modest report +// fits the threshold, so the second checkpoint does not compact. +func TestModelEffectCompactTriggerSummaryUsageStaysTotalsOnly(t *testing.T) { + h, store, c, sessionID := newEffectHarness(t, nil) + capture := triggerUsageCapture(12) + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 13}) + summaryUsage := UsageCount{InputTokens: 1000, CachedInputTokens: 1000, OutputTokens: 1000} + compactCalls := 0 + conversationCalls := 0 + exec := Execution{ + Model: func(context.Context, model.Request) (model.Stream, error) { + conversationCalls++ + return completedTurnStream(), nil // reports {3,1,2}: below threshold 12 + }, + CompactModel: func(context.Context, model.Request) (model.Stream, error) { + compactCalls++ + return summaryStream("summary one", model.Usage{InputTokens: 1000, CachedInputTokens: 1000, OutputTokens: 1000}), nil + }, + NormalizeTool: objectNormalize, + } + me := h.modelEffect(c, testOpID, exec, capture) + { + set, err := invokeModelEffect(t, me, projectedTriggerRequest(t, h, c), nil) + testassert.Require(t, err == nil && set.Disposition == agent.DispoReady, "overflow checkpoint: settlement %+v err %v, want ready", set, err) } - if graph.Session.State.CompactionEntryID != comps[1].EntryID { - t.Fatalf("compaction_entry_id %q, want the newest entry %q", graph.Session.State.CompactionEntryID, comps[1].EntryID) + { + set, err := invokeModelEffect(t, me, projectedTriggerRequest(t, h, c), nil) + testassert.Require(t, err == nil && set.Disposition == agent.DispoReady, "fitting checkpoint: settlement %+v err %v, want ready", set, err) } + testassert.Equal(t, compactCalls, 1) + testassert.Equal(t, conversationCalls, 2) + graph, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) + comp := compactedEntryOf(t, graph) // exactly one compaction entry + testassert.Require(t, comp.Usage != nil && *comp.Usage == summaryUsage, "compaction entry usage = %+v, want the summary's reported counts", comp.Usage) + compactionUsage := UsageByModelForTest(graph.Session.State.Usage, testCompactCapture().Model) + testassert.Require(t, len(compactionUsage.ByModel) == 1 && compactionUsage.ByModel[0].Usage == summaryUsage, "session compact usage = %+v, want the summary's counts billed exactly once", graph.Session.State.Usage) } // TestModelEffectCompactRequestNeverTriggers proves the structural recursion -// guard: with the conversation window small enough to fire the trigger on any -// request and the compact window large, the compact piece requests run the -// orchestration's single piece and commit exactly one entry — a trigger inside -// the compact model effect would recurse into the orchestration without ever +// guard: with an overflowing report live, the orchestration's summary request +// runs one request and commits exactly one entry — a trigger inside the +// compact model effect would recurse into the orchestration without ever // reaching a transport. func TestModelEffectCompactRequestNeverTriggers(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) - capture := testCapture() - capture.ContextWindow = 1 // every conversation request overflows; the compact capture stays large + capture := triggerUsageCapture(4) + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 13}) compactCalls := 0 conversationCalls := 0 exec := Execution{ @@ -746,47 +760,36 @@ func TestModelEffectCompactRequestNeverTriggers(t *testing.T) { NormalizeTool: objectNormalize, } req := projectedTriggerRequest(t, h, c) - set, err := invokeTriggerEffect(t, h.modelEffect(c, testOpID, exec, capture), req) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready", set.Disposition) - } - if compactCalls != 1 { - t.Fatalf("compact transport calls = %d, want exactly one piece with no recursion", compactCalls) - } - if conversationCalls != 1 { - t.Fatalf("conversation transport calls = %d, want exactly the post-compaction request", conversationCalls) - } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, compactCalls, 1) + testassert.Equal(t, conversationCalls, 1) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) comp := compactedEntryOf(t, graph) // exactly one compaction entry - if comp.OperationID != testOpID || comp.Summary != "summary one" { - t.Fatalf("compaction entry = %+v, want the Operation-owned committed summary", comp) - } + testassert.Require(t, comp.OperationID == testOpID && comp.Summary == "summary one", "compaction entry = %+v, want the Operation-owned committed summary", comp) } // TestModelEffectCompactTriggerStorageFailurePropagatesUnsettled proves the -// unsettled row: a storage failure at a piece's ready result commit — the -// piece intent stays live, the failure left the Operation running for +// unsettled row: a storage failure at the summary's ready result commit — the +// summary intent stays live, the failure left the Operation running for // recovery — returns from the trigger as the raw storage error with no // settlement shape, and the Operation keeps its committed running/intent -// state with no entry beyond the admitted input. +// state with no entry beyond the seed turn. func TestModelEffectCompactTriggerStorageFailurePropagatesUnsettled(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) + capture := triggerUsageCapture(4) + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 13}) req := projectedTriggerRequest(t, h, c) - capture := triggerCapture(t, req, true) - injected := fmt.Errorf("piece result commit storage failure: %w", ErrStorage) + injected := fmt.Errorf("summary result commit storage failure: %w", ErrStorage) replaces := 0 store.txHook = func(step string) error { if step != "replace_register" { return nil } replaces++ - if replaces == 2 { // #1 the piece intent, #2/#3 the ready result commit + if replaces == 2 { // #1 the summary intent, #2/#3 the ready result commit return injected } return nil @@ -798,46 +801,39 @@ func TestModelEffectCompactTriggerStorageFailurePropagatesUnsettled(t *testing.T }, NormalizeTool: objectNormalize, } - set, err := invokeTriggerEffect(t, h.modelEffect(c, testOpID, exec, capture), req) - if !errors.Is(err, injected) { - t.Fatalf("model effect = %v, want the raw storage error returned unsettled", err) - } - if !reflect.DeepEqual(set, agent.ModelSettlement{}) { - t.Fatalf("settlement = %+v, want no settlement shape behind the raw error", set) - } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) + testassert.Require(t, errors.Is(err, injected), "model effect = %v, want the raw storage error returned unsettled", err) + testassert.Require(t, reflect.DeepEqual(set, agent.ModelSettlement{}), "settlement = %+v, want no settlement shape behind the raw error", set) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect == nil { - t.Fatalf("operation state = %+v, want the committed running/intent state for recovery", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect != nil, "operation state = %+v, want the committed running/intent state for recovery", rec.State) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } - if len(graph.Entries) != 1 { // only the admitted input: the failed ready commit rolled back - t.Fatalf("entries = %d, want only the admitted input after the rollback", len(graph.Entries)) - } + testassert.NoError(t, err) + // the admitted input and the seed turn: the failed ready commit rolled back + testassert.Equal(t, len(graph.Entries), 2) } // TestCompactTriggerUnsettledStorageFailureReachesWait proves the full latch -// row over a live piece intent: a storage failure at the piece's ready result -// commit — the intent stays live, the Operation running — propagates as the -// raw effect error through the run and the outer settlement, is latched as -// the storage failure that stopped admitted work, and Wait reports it while -// the Operation keeps its committed running/intent state for recovery. +// row over a live summary intent: an ordinary turn first commits the reported +// usage the later checkpoint measures, a storage failure at that checkpoint's +// summary ready result commit — the intent stays live, the Operation running — +// propagates as the raw effect error through the run and the outer +// settlement, is latched as the storage failure that stopped admitted work, +// and Wait reports it while the checkpoint Operation keeps its committed +// running/intent state for recovery. func TestCompactTriggerUnsettledStorageFailureReachesWait(t *testing.T) { store := emptyStore(t) - capture := overflowingTriggerCapture("hello") - injected := fmt.Errorf("piece result commit storage failure: %w", ErrStorage) + capture := testCapture() + injected := fmt.Errorf("summary result commit storage failure: %w", ErrStorage) fired := make(chan struct{}, 1) exec := Execution{ - Model: forbiddenConversationModel(t), + Model: func(context.Context, model.Request) (model.Stream, error) { + return streamOf(turnDelta("stop", "seeded turn"), usageDelta(model.Usage{InputTokens: 3000})), nil + }, CompactModel: func(ctx context.Context, r model.Request) (model.Stream, error) { // The intent's replace_register has already committed when the - // transport runs; the next replace_register is the piece result - // commit's operation-register write. + // transport runs; the next replace_register is the summary + // result commit's operation-register write. store.txHook = func(step string) error { if step != "replace_register" { return nil @@ -858,22 +854,27 @@ func TestCompactTriggerUnsettledStorageFailureReachesWait(t *testing.T) { h, cancel := newCancelableHarness(t, store, prepared, nil) defer cancel() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } - <-fired // the piece's ready result commit failed at its operation-register write - cancel() - if err := h.Wait(context.Background()); !errors.Is(err, injected) { - t.Fatalf("Wait = %v, want the injected storage error reported over the running Operation", err) + awaitOperationTerminal(t, store, session, "op-1", OperationSuccess) // the ordinary turn commits the report + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "overflow") + testassert.NoError(t, err) } - rec := settledOperation(t, store, session, "op-1") - if rec.State.Status != OperationRunning || rec.State.ActiveEffect == nil { - t.Fatalf("operation state = %+v, want the running state with its live intent for recovery", rec.State) + <-fired // the checkpoint's summary result commit failed at its operation-register write + cancel() + { + err := h.Wait(context.Background()) + testassert.Require(t, errors.Is(err, injected), "Wait = %v, want the injected storage error reported over the running Operation", err) } + rec := settledOperation(t, store, session, "op-2") + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect != nil, "operation state = %+v, want the running state with its live intent for recovery", rec.State) } // TestCompactTriggerQuietSettlementStorageFailureReachesWait proves the quiet -// row: the textless piece settles ready and clears the effect, and the +// row: the textless summary settles ready and clears the effect, and the // direct empty-summary settlement's storage failure — the Operation quiet and // running — propagates as the raw effect error, takes the outer settlement's // no-compensating-write branch, is latched, and Wait reports it with the @@ -881,16 +882,18 @@ func TestCompactTriggerUnsettledStorageFailureReachesWait(t *testing.T) { // committed. func TestCompactTriggerQuietSettlementStorageFailureReachesWait(t *testing.T) { store := emptyStore(t) - capture := overflowingTriggerCapture("hello") + capture := testCapture() injected := fmt.Errorf("direct settlement storage failure: %w", ErrStorage) fired := make(chan struct{}, 1) refusal := turnDelta("stop", "") refusal.RefusalFragment = "no" exec := Execution{ - Model: forbiddenConversationModel(t), + Model: func(context.Context, model.Request) (model.Stream, error) { + return streamOf(turnDelta("stop", "seeded turn"), usageDelta(model.Usage{InputTokens: 3000})), nil + }, CompactModel: func(ctx context.Context, r model.Request) (model.Stream, error) { - // The piece's ready settlement only replaces registers; the first - // insert_entry of the trigger invocation is the direct + // The summary's ready settlement only replaces registers; the + // first insert_entry of the checkpoint is the direct // settlement's settlement entry. store.txHook = func(step string) error { if step != "insert_entry" { @@ -912,37 +915,51 @@ func TestCompactTriggerQuietSettlementStorageFailureReachesWait(t *testing.T) { h, cancel := newCancelableHarness(t, store, prepared, nil) defer cancel() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) + } + awaitOperationTerminal(t, store, session, "op-1", OperationSuccess) // the ordinary turn commits the report + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "overflow") + testassert.NoError(t, err) } <-fired // the direct settlement failed at its settlement entry insert cancel() - if err := h.Wait(context.Background()); !errors.Is(err, injected) { - t.Fatalf("Wait = %v, want the injected storage error reported over the running quiet Operation", err) - } - rec := settledOperation(t, store, session, "op-1") - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil { - t.Fatalf("operation state = %+v, want the running quiet state for recovery", rec.State) + { + err := h.Wait(context.Background()) + testassert.Require(t, errors.Is(err, injected), "Wait = %v, want the injected storage error reported over the running quiet Operation", err) } + rec := settledOperation(t, store, session, "op-2") + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil, "operation state = %+v, want the running quiet state for recovery", rec.State) graph, err := validateFixture(t, store, session) - if err != nil { - t.Fatalf("graph: %v", err) - } - if len(graph.Entries) != 1 { // only the admitted input: no settlement and no compaction entry committed - t.Fatalf("entries = %d, want only the admitted input with no settlement entry", len(graph.Entries)) + testassert.NoError(t, err) + settlements := 0 + for i := range graph.Entries { + testassert.Require(t, graph.Entries[i].Compaction == nil, "compaction entry committed on the failed direct settlement") + if graph.Entries[i].Settlement != nil { + settlements++ + testassert.Equal(t, graph.Entries[i].Envelope.OperationID, "op-1") + } } + // only op-1's ordinary terminal: no checkpoint settlement committed + testassert.Equal(t, settlements, 1) } // TestModelEffectCompactBoundaryExcludesPostFreezeAppends proves the frozen // boundary against the live interleave: a background completion delivered -// while the trigger's piece attempt is parked appends an entry after the +// while the trigger's summary attempt is parked appends an entry after the // frozen snapshot, and the committed boundary still names the freeze-time // last projectable entry, so the appended signal projects after the summary // instead of being named as covered and hidden. func TestModelEffectCompactBoundaryExcludesPostFreezeAppends(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) + capture := triggerUsageCapture(4) + seedConversationUsage(t, h, c, capture, UsageCount{InputTokens: 13}) + seeded, err := store.ReadEntries(context.Background(), sessionID, 0) + testassert.NoError(t, err) + testassert.Require(t, len(seeded) == 2 && seeded[1].Kind == EntryAssistant, "seed entries = %+v, want the admitted input and the seed assistant", seeded) req := projectedTriggerRequest(t, h, c) - capture := triggerCapture(t, req, true) arrived := make(chan struct{}, 1) release := make(chan struct{}) var sent []model.Request @@ -964,7 +981,7 @@ func TestModelEffectCompactBoundaryExcludesPostFreezeAppends(t *testing.T) { } done := make(chan invocation, 1) go func() { // a synchronous call would deadlock on the park - set, err := invokeTriggerEffect(t, h.modelEffect(c, testOpID, exec, capture), req) + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, capture), req, nil) done <- invocation{set: set, err: err} }() <-arrived @@ -972,186 +989,107 @@ func TestModelEffectCompactBoundaryExcludesPostFreezeAppends(t *testing.T) { // the closed group makes the delivery commit the durable signal // regardless of the running Operation. member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(c) - if err := h.DeliverBackgroundCompletion(context.Background(), sessionID, member.completionID, "Task completed."); err != nil { - t.Fatalf("closed delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), sessionID, member.completionID, "Task completed.") + testassert.NoError(t, err) } signals := storedSignals(t, store, sessionID) - if len(signals) != 1 { - t.Fatalf("durable background_completion signals = %d, want the one closed-path completion", len(signals)) - } - if signals[0].OperationID != "" || signals[0].RelatedMember == nil || signals[0].RelatedMember.Kind != "child" || - signals[0].RelatedMember.ID != completionChildID || signals[0].Content != "Task completed." { - t.Fatalf("committed signal = %+v, want the operationless child-member completion", signals[0]) - } + testassert.Equal(t, len(signals), 1) + testassert.Require(t, signals[0].OperationID == "" && signals[0].RelatedMember != nil && signals[0].RelatedMember.Kind == "child" && signals[0].RelatedMember.ID == completionChildID && signals[0].Content == "Task completed.", "committed signal = %+v, want the operationless child-member completion", signals[0]) close(release) out := <-done - if out.err != nil { - t.Fatalf("model effect: %v", out.err) - } - if out.set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready after the rebuilt request", out.set.Disposition) - } + testassert.Require(t, out.err == nil && out.set.Disposition == agent.DispoReady, "model effect: %v; got %v, want %v", out.err, out.set.Disposition, agent.DispoReady) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) comp := compactedEntryOf(t, graph) - if comp.BoundaryEntryID != graph.Entries[0].Envelope.ID { - t.Fatalf("boundary %q, want the freeze-time last projectable entry %q", comp.BoundaryEntryID, graph.Entries[0].Envelope.ID) - } + testassert.Equal(t, comp.BoundaryEntryID, seeded[1].ID) // The rebuilt request carried the appended signal after the summary. - if len(sent) != 1 { - t.Fatalf("conversation transport calls = %d, want exactly the rebuilt request", len(sent)) - } + testassert.Equal(t, len(sent), 1) sentMsgs := sent[0].Messages - if len(sentMsgs) != 3 || sentMsgs[0].Role != model.RoleSystem || - sentMsgs[1].Role != model.RoleAssistant || sentMsgs[1].Source != testCompactCapture().Model || - sentMsgs[2].Role != model.RoleUser || sentMsgs[2].TextContent() != "Task completed." { - t.Fatalf("rebuilt request = %+v, want the system message, the summary, and the wrapped post-freeze signal", sentMsgs) - } + testassert.Require(t, len(sentMsgs) == 3 && sentMsgs[0].Role == model.RoleSystem && sentMsgs[1].Role == model.RoleAssistant && sentMsgs[1].Source == testCompactCapture().Model && sentMsgs[2].Role == model.RoleUser && sentMsgs[2].TextContent() == "Task completed.", "rebuilt request = %+v, want the system message, the summary, and the wrapped post-freeze signal", sentMsgs) // The post-commit projection keeps the signal visible after the // summary, ahead of the turn the settled request committed. after, err := h.projectContext(c, testOpID) - if err != nil { - t.Fatalf("projectContext: %v", err) - } - if len(after) != 4 || after[0].Role != model.RoleSystem || - after[1].Role != model.RoleAssistant || after[1].Source != testCompactCapture().Model || - after[1].TextContent() != "[Previous conversation summary]\n\nsummary one\n\n[End of summary. Continue from here.]" || - after[2].Role != model.RoleUser || after[2].TextContent() != "Task completed." || - after[3].Role != model.RoleAssistant || after[3].TextContent() != "done" { - t.Fatalf("post-commit projection = %+v, want the system message, the summary, the wrapped post-freeze signal, and the settled turn", after) - } + testassert.NoError(t, err) + testassert.Require(t, len(after) == 4 && after[0].Role == model.RoleSystem && after[1].Role == model.RoleAssistant && after[1].Source == testCompactCapture().Model && after[1].TextContent() == "[Previous conversation summary]\n\nsummary one\n\n[End of summary. Continue from here.]" && after[2].Role == model.RoleUser && after[2].TextContent() == "Task completed." && after[3].Role == model.RoleAssistant && after[3].TextContent() == "done", "post-commit projection = %+v, want the system message, the summary, the wrapped post-freeze signal, and the settled turn", after) } -// TestModelEffectCompactTriggerTwoPieceCommitAccumulatesUsage proves the -// rolling-commit chain the deleted Runtime row carried: the real two-piece -// orchestration succeeds on both pieces, the complete rich input — the errored -// partial with its failure signal, the ordered rich tool turn, its complete -// tool result and the refusal turn — is consumed exactly once in order over -// the estimator-sized complete pieces, the run returns the field-wise -// accumulated total, the second piece carries exactly the remaining message -// whole under the previous-summary framing, and the commit attributes that -// same accumulated total to the Operation-owned compaction entry and both -// registers in one transaction. -func TestModelEffectCompactTriggerTwoPieceCommitAccumulatesUsage(t *testing.T) { +// TestModelEffectCompactTriggerFullInputCommitAttributesUsage is the +// full-input commit observation the deleted rolling rows carried: the real +// orchestration succeeds on its single request over the complete rich input — +// the errored partial with its failure signal, the ordered rich tool turn, +// its complete tool result, the refusal turn, and the tail message — consumed +// exactly once in order, the run returns the one response's reported usage, +// the request carries the whole serialized snapshot under one framing, and the +// commit attributes that same total to the Operation-owned compaction entry +// and both registers in one transaction at the frozen boundary. +func TestModelEffectCompactTriggerFullInputCommitAttributesUsage(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) capture := testCapture() compactRef := testCompactCapture().Model - var pieces []model.Request + var requests []model.Request exec := Execution{ Model: forbiddenConversationModel(t), CompactModel: func(ctx context.Context, r model.Request) (model.Stream, error) { - pieces = append(pieces, r) - if len(pieces) == 1 { - return summaryStream("piece one", model.Usage{InputTokens: 4, OutputTokens: 2}), nil - } - return summaryStream("piece two", model.Usage{InputTokens: 1, OutputTokens: 1}), nil + requests = append(requests, r) + return summaryStream("full summary", model.Usage{InputTokens: 5, OutputTokens: 3}), nil }, NormalizeTool: objectNormalize, } // The complete-input snapshot: the rich conversation prefix followed by - // the estimator-sized two-message tail, so the first piece carries the - // whole rich history plus the first complete message and the second - // piece carries only the remaining message. + // one further message. rich := completeCompactionInputMessages(t) - tail := compactTwoPieceSnapshot(t) + tail := []model.Message{{Role: model.RoleUser, Content: admissionContent("the tail message")}} snapshot := append(append([]model.Message(nil), rich...), tail...) - // Fixture preconditions against the real estimator: the prefix plus the - // first tail message fit one fresh piece, and the second tail message - // exceeds that piece's remaining budget while fitting a whole piece. - budget := compactionPieceBudget(capture.Compact, "") - prefixCost := 0 - for _, line := range mustSerializeCompaction(t, rich) { - prefixCost += estimateTokens(line, nil) - } - costFirst := estimateTokens(mustSerializeCompaction(t, tail[:1])[0], nil) - costSecond := estimateTokens(mustSerializeCompaction(t, tail[1:])[0], nil) - if prefixCost+costFirst > budget { - t.Fatalf("fixture precondition: the rich prefix plus the first message costs %d over the %d-token piece budget", prefixCost+costFirst, budget) - } - if costSecond <= budget-prefixCost-costFirst { - t.Fatalf("fixture precondition: the second message would fit the first piece's remaining budget") - } summary, usage, err := h.runCompaction(context.Background(), c, testOpID, exec, capture, snapshot) - if err != nil { - t.Fatalf("runCompaction: %v", err) - } - if summary != "piece two" { - t.Fatalf("summary = %q, want the final piece output", summary) - } - if usage == nil || *usage != (UsageCount{InputTokens: 5, OutputTokens: 3}) { - t.Fatalf("run usage = %+v, want the field-wise piece sum {5,3}", usage) - } - if len(pieces) != 2 { - t.Fatalf("compact transport calls = %d, want exactly two pieces", len(pieces)) - } - // Consumed exactly once in order: the two pieces' user texts carry the - // snapshot's serialized lines whole and in snapshot order. + testassert.NoError(t, err) + testassert.Equal(t, summary, "full summary") + testassert.Require(t, usage != nil && *usage == (UsageCount{InputTokens: 5, OutputTokens: 3}), "run usage = %+v, want the one request's reported counts", usage) + testassert.Equal(t, len(requests), 1) + // Consumed exactly once in order: the request's user text carries every + // serialized snapshot line whole in snapshot order. wantLines := mustSerializeCompaction(t, snapshot) - firstText := pieces[0].Messages[1].TextContent() - if firstText != strings.Join(wantLines[:len(wantLines)-1], "\n") { - t.Fatalf("first piece user text = %q, want the whole rich history plus the first message in order", firstText) - } - // The second piece carries exactly the remaining message whole under the - // continuation framing — the one-message-per-piece isolation. - framing := "Previous summary:\npiece one\n\nContinuation:\n" - if got := pieces[1].Messages[1].TextContent(); got != framing+wantLines[len(wantLines)-1] { - t.Fatalf("second piece user text %q, want the whole remaining message under the continuation framing", got) - } - // The first piece's rich lines on the wire: the errored partial, the - // wrapped failure signal, the ordered content parts, the raw tool-call - // arguments, the complete tool result and the refusal turn. - for i, line := range strings.Split(firstText, "\n")[:len(rich)] { + userText := requests[0].Messages[1].TextContent() + testassert.Require(t, userText == strings.Join(wantLines, "\n") && (!strings.Contains(userText, "Previous summary:") && !strings.Contains(userText, "Continuation:")), "got %v, want %v; the request carries continuation framing: %q", userText, strings.Join(wantLines, "\n"), userText) + // The rich lines on the wire: the errored partial, the wrapped failure + // signal, the ordered content parts, the raw tool-call arguments, the + // complete tool result and the refusal turn. + for i, line := range strings.Split(userText, "\n")[:len(rich)] { var got compactPieceWireLine - if err := json.Unmarshal([]byte(line), &got); err != nil { - t.Fatalf("first piece line %d unmarshal: %v", i, err) + { + err := json.Unmarshal([]byte(line), &got) + testassert.Require(t, err == nil, "request line %d unmarshal: %v", i, err) } if i == 3 { // the call's raw arguments ride the quoted representation - if len(got.ToolCalls) != 1 || got.ToolCalls[0].ID != "call-rich" || got.ToolCalls[0].Name != "noop" { - t.Fatalf("first piece line 3 tool calls = %+v, want exactly the one noop call", got.ToolCalls) - } + testassert.Require(t, len(got.ToolCalls) == 1 && got.ToolCalls[0].ID == "call-rich" && got.ToolCalls[0].Name == "noop", "request line 3 tool calls = %+v, want exactly the one noop call", got.ToolCalls) args, uerr := strconv.Unquote(got.ToolCalls[0].Arguments) - if uerr != nil || args != `{"x":1}` { - t.Fatalf("serialized tool-call arguments = %q (unquote error %v), want the submitted bytes", got.ToolCalls[0].Arguments, uerr) - } + testassert.Require(t, uerr == nil && args == `{"x":1}`, "serialized tool-call arguments = %q (unquote error %v), want the submitted bytes", got.ToolCalls[0].Arguments, uerr) got.ToolCalls[0].Arguments = "" } - if !reflect.DeepEqual(got, wantRichPieceLines[i]) { - t.Fatalf("first piece line %d = %+v, want %+v", i, got, wantRichPieceLines[i]) - } + testassert.Require(t, reflect.DeepEqual(got, wantRichPieceLines[i]), "request line %d = %+v, want %+v", i, got, wantRichPieceLines[i]) } - // The rolling commit attributes the same accumulated total to the - // Operation-owned compaction entry and both registers in one transaction. - if err := h.commitCompaction(c, testOpID, capture, summary, 1, usage, false); err != nil { - t.Fatalf("commitCompaction: %v", err) + // The commit attributes the same reported total to the Operation-owned + // compaction entry and both registers in one transaction. + { + err := h.commitCompaction(c, testOpID, capture, summary, 1, usage, false) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) comp := compactedEntryOf(t, graph) - if comp.OperationID != testOpID || comp.Summary != "piece two" { - t.Fatalf("compaction entry = %+v, want the Operation-owned final piece summary", comp) - } - if comp.Usage == nil || *comp.Usage != (UsageCount{InputTokens: 5, OutputTokens: 3}) { - t.Fatalf("compaction entry usage = %+v, want the accumulated piece sum {5,3}", comp.Usage) - } - if graph.Session.State.CompactionEntryID != comp.EntryID { - t.Fatalf("compaction_entry_id %q, want the new entry %q", graph.Session.State.CompactionEntryID, comp.EntryID) - } + testassert.Require(t, comp.OperationID == testOpID && comp.Summary == "full summary", "compaction entry = %+v, want the Operation-owned summary", comp) + testassert.Equal(t, comp.BoundaryEntryID, graph.Entries[0].Envelope.ID) + testassert.Require(t, comp.Usage != nil && *comp.Usage == (UsageCount{InputTokens: 5, OutputTokens: 3}), "compaction entry usage = %+v, want the reported counts {5,3}", comp.Usage) + testassert.Equal(t, graph.Session.State.CompactionEntryID, comp.EntryID) want := UsageTotals{ByModel: []ModelUsage{{Model: compactRef, Usage: UsageCount{InputTokens: 5, OutputTokens: 3}}}} - if state := operationStateOf(t, graph, testOpID); !usageTotalsEqual(state.Usage, want) { - t.Fatalf("operation usage = %+v, want the accumulated piece counts keyed by the compact model", state.Usage) - } - if !usageTotalsEqual(graph.Session.State.Usage, want) { - t.Fatalf("session usage = %+v, want the accumulated piece counts keyed by the compact model", graph.Session.State.Usage) + { + state := operationStateOf(t, graph, testOpID) + testassert.Require(t, usageTotalsEqual(state.Usage, want), "operation usage = %+v, want the reported counts keyed by the compact model", state.Usage) } + testassert.Require(t, usageTotalsEqual(graph.Session.State.Usage, want), "session usage = %+v, want the reported counts keyed by the compact model", graph.Session.State.Usage) } // compactPieceWireLine is one serialized compaction line's observed shape: the @@ -1194,7 +1132,7 @@ var wantRichPieceLines = []compactPieceWireLine{ {Role: "assistant", Refusal: "cannot do that"}, } -// completeCompactionInputMessages builds the deleted complete-input row's rich +// completeCompactionInputMessages builds the complete-input row's rich // conversation prefix through the accepting message constructor: the errored // partial with its production-wrapped failure signal, the ordered rich tool // turn, its complete tool result and the refusal turn. @@ -1202,9 +1140,7 @@ func completeCompactionInputMessages(t *testing.T) []model.Message { t.Helper() build := func(m model.Message) model.Message { msg, err := model.NewMessage(m) - if err != nil { - t.Fatalf("fixture message: %v", err) - } + testassert.NoError(t, err) return msg } return []model.Message{ @@ -1231,8 +1167,6 @@ func completeCompactionInputMessages(t *testing.T) []model.Message { func mustSerializeCompaction(t *testing.T, messages []model.Message) []string { t.Helper() lines, err := serializeCompactionMessages(messages) - if err != nil { - t.Fatalf("serializeCompactionMessages: %v", err) - } + testassert.NoError(t, err) return lines } diff --git a/harness/completion_test.go b/harness/completion_test.go index 09d789aa..10d080d9 100644 --- a/harness/completion_test.go +++ b/harness/completion_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -32,9 +33,7 @@ func cachedCoordinator(t *testing.T, h *Harness, sessionID string) *coordinator h.mu.Lock() defer h.mu.Unlock() c := h.sessions[sessionID] - if c == nil { - t.Fatalf("no cached coordinator for session %q", sessionID) - } + testassert.Require(t, c != nil, "no cached coordinator for session %q", sessionID) return c } @@ -97,12 +96,13 @@ func addRootSession(t *testing.T, store *graphStorage, sessionID string) { rec := validSessionRecord() rec.Identity.SessionID = sessionID raw, err := encodeSessionRegister(rec) - mustEncode(t, err) - if err := store.Transact(context.Background(), func(tx Transaction) error { - _, err := tx.InsertRegister(RegisterDraft{Key: RegisterKey{SessionID: sessionID, Kind: RegisterSession}, Payload: raw}) - return err - }); err != nil { - t.Fatalf("insert session %q: %v", sessionID, err) + testassert.NoError(t, err) + { + err := store.Transact(context.Background(), func(tx Transaction) error { + _, err := tx.InsertRegister(RegisterDraft{Key: RegisterKey{SessionID: sessionID, Kind: RegisterSession}, Payload: raw}) + return err + }) + testassert.Require(t, err == nil, "insert session %q: %v", sessionID, err) } } @@ -118,9 +118,7 @@ func storedSignals(t *testing.T, store *graphStorage, sessionID string) []signal continue } v, err := decodeSignalEntry(env) - if err != nil { - t.Fatalf("decode signal entry %s: %v", env.ID, err) - } + testassert.Require(t, err == nil, "decode signal entry %s: %v", env.ID, err) if v.Signal == SignalBackgroundCompletion { out = append(out, v) } @@ -162,9 +160,7 @@ func waitMemberDone(t *testing.T, m *backgroundMember) { func runtimeInputText(t *testing.T, store *graphStorage, sessionID string) string { t.Helper() graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("session graph: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.Input != nil && entry.Input.Origin == InputOriginRuntime { return entry.Input.Content[0].Text @@ -188,27 +184,22 @@ func TestDeliverBackgroundCompletionModes(t *testing.T) { session := createSession(t, h) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + testassert.NoError(t, err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the run is parked at its model boundary cancel() // harness loss while the run still settles - if err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "lost work"); err != nil { - t.Fatalf("closed delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "lost work") + testassert.NoError(t, err) } signals := storedSignals(t, store, session) - if len(signals) != 1 { - t.Fatalf("durable background_completion signals = %d, want 1", len(signals)) - } + testassert.Equal(t, len(signals), 1) sig := signals[0] - if sig.OperationID != "" || sig.RelatedMember == nil || sig.RelatedMember.Kind != "child" || - sig.RelatedMember.ID != completionChildID || sig.Content != "lost work" { - t.Fatalf("committed signal = %+v, want the operationless child-member completion", sig) - } + testassert.Require(t, sig.OperationID == "" && sig.RelatedMember != nil && sig.RelatedMember.Kind == "child" && sig.RelatedMember.ID == completionChildID && sig.Content == "lost work", "committed signal = %+v, want the operationless child-member completion", sig) waitMemberDone(t, member) script.releaseGate() // let the parked run converge @@ -233,24 +224,26 @@ func TestDeliverBackgroundCompletionModes(t *testing.T) { watch := watchSettlements(store) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) - if err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "background answer"); err != nil { - t.Fatalf("idle delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "background answer") + testassert.NoError(t, err) } waitMemberDone(t, member) watch.next() // the admitted completion settles - if rec := settledOperation(t, store, session, member.completionID); rec.State.Status != OperationSuccess { - t.Fatalf("completion operation settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, session, member.completionID) + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if signals := storedSignals(t, store, session); len(signals) != 0 { - t.Fatalf("a receivable parent recorded %d signal entries, want none", len(signals)) + { + signals := storedSignals(t, store, session) + testassert.Equal(t, len(signals), 0) } - if got := runtimeInputText(t, store, session); got != "background answer" { - t.Fatalf("admitted input = %q, want the completion content", got) + { + got := runtimeInputText(t, store, session) + testassert.Equal(t, got, "background answer") } }) @@ -264,40 +257,45 @@ func TestDeliverBackgroundCompletionModes(t *testing.T) { watch := watchSettlements(store) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + testassert.NoError(t, err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the operation is parked at its model boundary - if err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "steered completion"); err != nil { - t.Fatalf("steering delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "steered completion") + testassert.NoError(t, err) } waitMemberDone(t, member) - if n := storedEntryCount(store, session); n != 1 { - t.Fatalf("steering committed %d durable entries, want only the admitted input", n) + { + n := storedEntryCount(store, session) + testassert.Equal(t, n, 1) } script.releaseGate() <-script.arrived // the completion identity's own Operation carries the steered completion - if texts := strings.Join(script.lastTexts(), "|"); !strings.Contains(texts, "steered completion") { - t.Fatalf("continuation projection = %q, want the steered completion", texts) + { + texts := strings.Join(script.lastTexts(), "|") + testassert.Require(t, strings.Contains(texts, "steered completion"), "continuation projection = %q, want the steered completion", texts) } watch.next() - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationSuccess { - t.Fatalf("steered operation settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationSuccess) } // The delivered completion runs as its own successor Operation: wait // for its terminal commit and Session quiet before raw graph reads. awaitOperationTerminal(t, store, session, member.completionID, OperationSuccess) awaitHarnessQuiet(t, h, session) - if got := entryTexts(t, store, session); !reflect.DeepEqual(got, []string{"hello", "steered completion"}) { - t.Fatalf("committed inputs = %q, want the steered completion after the first input", got) + { + got := entryTexts(t, store, session) + testassert.Require(t, reflect.DeepEqual(got, []string{"hello", "steered completion"}), "committed inputs = %q, want the steered completion after the first input", got) } - if got := runtimeInputText(t, store, session); got != "steered completion" { - t.Fatalf("steered input origin/content = %q, want the runtime completion", got) + { + got := runtimeInputText(t, store, session) + testassert.Equal(t, got, "steered completion") } }) @@ -311,27 +309,29 @@ func TestDeliverBackgroundCompletionModes(t *testing.T) { watch := watchSettlements(store) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + testassert.NoError(t, err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "queued completion"); err != nil { - t.Fatalf("steering delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "queued completion") + testassert.NoError(t, err) } waitMemberDone(t, member) script.releaseGate() // op-1 settles failure; the preserved steering drains through admission watch.next() // op-1's settlement watch.next() // the drain-admitted completion's settlement - if rec := settledOperation(t, store, session, member.completionID); rec.State.Status != OperationSuccess { - t.Fatalf("completion operation settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, session, member.completionID) + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if got := runtimeInputText(t, store, session); got != "queued completion" { - t.Fatalf("admitted input = %q, want the completion content under its identity", got) + { + got := runtimeInputText(t, store, session) + testassert.Equal(t, got, "queued completion") } }) } @@ -342,18 +342,15 @@ func TestDeliverBackgroundCompletionEmptyContentRejected(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } - if err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, ""); !errors.Is(err, ErrInvalid) { - t.Fatalf("empty content = %v, want ErrInvalid", err) + testassert.NoError(t, err) + { + err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "") + testassert.Require(t, errors.Is(err, ErrInvalid), "empty content = %v, want ErrInvalid", err) } c.mu.Lock() claimed := member.claimed c.mu.Unlock() - if claimed { - t.Fatalf("the rejected delivery claimed the member") - } + testassert.Require(t, !claimed, "the rejected delivery claimed the member") } // TestDeliverBackgroundCompletionSecondDeliveryIsNoOp proves the first-writer @@ -369,42 +366,42 @@ func TestDeliverBackgroundCompletionSecondDeliveryIsNoOp(t *testing.T) { watch := watchSettlements(store) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) - if err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "first"); err != nil { - t.Fatalf("first delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "first") + testassert.NoError(t, err) } waitMemberDone(t, member) watch.next() // the delivered completion's own Operation settles awaitOperationTerminal(t, store, session, member.completionID, OperationSuccess) - if calls := len(script.seen()); calls != 1 { - t.Fatalf("first-delivery model requests = %d, want exactly one", calls) + { + calls := len(script.seen()) + testassert.Equal(t, calls, 1) } beforeEntries, beforeRegs := storedSessionState(store, session) - if err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "second"); err != nil { - t.Fatalf("second delivery = %v, want a no-op success", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "second") + testassert.NoError(t, err) } afterEntries, afterRegs := storedSessionState(store, session) - if !reflect.DeepEqual(beforeEntries, afterEntries) || !reflect.DeepEqual(beforeRegs, afterRegs) { - t.Fatalf("the duplicate delivery changed the durable registers or entries") + testassert.Require(t, reflect.DeepEqual(beforeEntries, afterEntries) && reflect.DeepEqual(beforeRegs, afterRegs), "the duplicate delivery changed the durable registers or entries") + { + calls := len(script.seen()) + testassert.Equal(t, calls, 1) } - if calls := len(script.seen()); calls != 1 { - t.Fatalf("model requests after the duplicate = %d, want no second model call", calls) - } - if signals := storedSignals(t, store, session); len(signals) != 0 { - t.Fatalf("the duplicate committed signal entries %+v, want no parent signal", signals) + { + signals := storedSignals(t, store, session) + testassert.Require(t, len(signals) == 0, "the duplicate committed signal entries %+v, want no parent signal", signals) } - if got := entryTexts(t, store, session); !reflect.DeepEqual(got, []string{"first"}) { - t.Fatalf("committed inputs = %q, want exactly the first delivery", got) - } - if !storedOperationExists(store, session, member.completionID) { - t.Fatalf("the completion identity was not admitted") + { + got := entryTexts(t, store, session) + testassert.Require(t, reflect.DeepEqual(got, []string{"first"}), "committed inputs = %q, want exactly the first delivery", got) } + testassert.Require(t, storedOperationExists(store, session, member.completionID), "the completion identity was not admitted") } // TestDeliverBackgroundCompletionCorruptParent proves an unavailable parent @@ -416,13 +413,9 @@ func TestDeliverBackgroundCompletionCorruptParent(t *testing.T) { h := newTestHarness(t, store, nil) c := leaseCoordinator(t, h, testSessionID) childMember, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("child admission: %v", err) - } + testassert.NoError(t, err) jobMember, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("job admission: %v", err) - } + testassert.NoError(t, err) h.mu.Lock() c.corru = corruptSession(testSessionID, "injected corruption") h.mu.Unlock() @@ -430,19 +423,16 @@ func TestDeliverBackgroundCompletionCorruptParent(t *testing.T) { for _, member := range []*backgroundMember{childMember, jobMember} { err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "x") var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("%s delivery = %v, want a corruption error", member.kind, err) - } + testassert.Require(t, errors.As(err, &corrupt), "%s delivery = %v, want a corruption error", member.kind, err) } entries, regs := storedSessionState(store, testSessionID) - if len(entries) != 0 || len(regs) != 1 { - t.Fatalf("corrupt-parent delivery wrote entries %d registers %d, want none beyond the session register", len(entries), len(regs)) - } + testassert.Require(t, len(entries) == 0 && len(regs) == 1, "corrupt-parent delivery wrote entries %d registers %d, want none beyond the session register", len(entries), len(regs)) waitMemberDone(t, childMember) waitMemberDone(t, jobMember) - if err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, childMember.completionID, "x"); err != nil { - t.Fatalf("duplicate callback = %v, want a no-op success", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, childMember.completionID, "x") + testassert.NoError(t, err) } } @@ -460,9 +450,7 @@ func TestDeliverBackgroundCompletionIdleRaceReRoutesToSteering(t *testing.T) { session := createSession(t, h) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) submitted := make(chan error, 1) go func() { @@ -482,19 +470,19 @@ func TestDeliverBackgroundCompletionIdleRaceReRoutesToSteering(t *testing.T) { delivered <- h.DeliverBackgroundCompletion(context.Background(), session, member.completionID, "raced") }() close(stub.gate) // the submission publishes, installs its run, releases the reservation - if err := <-submitted; err != nil { - t.Fatalf("submit: %v", err) + { + err := <-submitted + testassert.NoError(t, err) } - if err := <-delivered; err != nil { - t.Fatalf("raced delivery: %v", err) + { + err := <-delivered + testassert.NoError(t, err) } // The rerouted completion is steering, never a separate admission while // it waits: it is either still buffered or already delivered through its // own successor admission after the boundary exit. - if storedOperationExists(store, session, member.completionID) { - t.Fatalf("the raced completion admitted a new operation") - } + testassert.Require(t, !storedOperationExists(store, session, member.completionID), "the raced completion admitted a new operation") script.releaseGate() <-script.arrived // the boundary exits; the successor admission delivers the steering @@ -514,18 +502,21 @@ func TestDeliverBackgroundCompletionIdleRaceReRoutesToSteering(t *testing.T) { t.Fatalf("the rerouted completion never reached a model boundary (requests %d)", len(reqs)) } } - if reqs := script.seen(); len(reqs) > 2 { - t.Fatalf("model requests = %d, want the boundary plus at most the drained continuation", len(reqs)) + { + reqs := script.seen() + testassert.Require(t, len(reqs) <= 2, "model requests = %d, want the boundary plus at most the drained continuation", len(reqs)) } // The completion executes as its own successor Operation: wait for its // terminal commit and Session quiet before any raw graph validation. awaitOperationTerminal(t, store, session, member.completionID, OperationSuccess) awaitHarnessQuiet(t, h, session) - if got := entryTexts(t, store, session); !reflect.DeepEqual(got, []string{"hello", "raced"}) { - t.Fatalf("committed inputs = %q, want the rerouted completion as steering", got) + { + got := entryTexts(t, store, session) + testassert.Require(t, reflect.DeepEqual(got, []string{"hello", "raced"}), "committed inputs = %q, want the rerouted completion as steering", got) } - if owner, origin := steeringInputOwner(t, store, session, "raced"); owner != member.completionID || origin != InputOriginRuntime { - t.Fatalf("raced input owner/origin = %q/%v, want %q/%v", owner, origin, member.completionID, InputOriginRuntime) + { + owner, origin := steeringInputOwner(t, store, session, "raced") + testassert.Require(t, owner == member.completionID && origin == InputOriginRuntime, "raced input owner/origin = %q/%v, want %q/%v", owner, origin, member.completionID, InputOriginRuntime) } waitMemberDone(t, member) } @@ -542,24 +533,19 @@ func TestDeliverBackgroundCompletionReserveFailureTakesClosedPath(t *testing.T) session := createSession(t, h) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) release, err := h.reserve(context.Background(), c) // the test holds the admission reservation - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) defer release() cancel() // the idle delivery's reserve call fails against the dead context - if err := h.deliverIdleCompletion(c, member, admissionContent("closed on failure"), "closed on failure"); err != nil { - t.Fatalf("reserve-failure delivery = %v, want the closed path success", err) + { + err := h.deliverIdleCompletion(c, member, admissionContent("closed on failure"), "closed on failure") + testassert.NoError(t, err) } signals := storedSignals(t, store, session) - if len(signals) != 1 || signals[0].Content != "closed on failure" { - t.Fatalf("durable signals = %+v, want the closed-path record", signals) - } + testassert.Require(t, len(signals) == 1 && signals[0].Content == "closed on failure", "durable signals = %+v, want the closed-path record", signals) h.finishBackgroundMember(c, member) waitMemberDone(t, member) } @@ -577,14 +563,10 @@ func TestDeliverBackgroundCompletionAdmissionFailuresFinal(t *testing.T) { wantErr func(t *testing.T, err error) }{ {"storage failure", fmt.Errorf("%w: injected storage failure", ErrStorage), true, func(t *testing.T, err error) { - if !errors.Is(err, ErrStorage) { - t.Fatalf("delivery error = %v, want the storage class", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "delivery error = %v, want the storage class", err) }}, {"conflict failure", fmt.Errorf("%w: injected conflict", ErrConflict), false, func(t *testing.T, err error) { - if !errors.Is(err, ErrInvalid) || !strings.Contains(err.Error(), "already exists in another session") { - t.Fatalf("delivery error = %v, want the cross-session reuse rejection", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "already exists in another session"), "delivery error = %v, want the cross-session reuse rejection", err) }}, } for _, tc := range cases { @@ -595,9 +577,7 @@ func TestDeliverBackgroundCompletionAdmissionFailuresFinal(t *testing.T) { }) c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) store.txHook = func(string) error { return tc.err } // the delivery's admission transaction fails once err = h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "content") @@ -605,23 +585,22 @@ func TestDeliverBackgroundCompletionAdmissionFailuresFinal(t *testing.T) { tc.wantErr(t, err) waitMemberDone(t, member) - if storedOperationExists(store, testSessionID, member.completionID) { - t.Fatalf("the failed admission published a replacement Operation") - } - if n := storedEntryCount(store, testSessionID); n != 0 { - t.Fatalf("the failed admission or a retry wrote %d entries, want none", n) + testassert.Require(t, !storedOperationExists(store, testSessionID, member.completionID), "the failed admission published a replacement Operation") + { + n := storedEntryCount(store, testSessionID) + testassert.Equal(t, n, 0) } - if signals := storedSignals(t, store, testSessionID); len(signals) != 0 { - t.Fatalf("a fallback signal write committed %+v, want none", signals) + { + signals := storedSignals(t, store, testSessionID) + testassert.Require(t, len(signals) == 0, "a fallback signal write committed %+v, want none", signals) } h.mu.Lock() latched := h.storageFailure != nil h.mu.Unlock() - if latched != tc.wantLatched { - t.Fatalf("storage failure latched = %v, want %v", latched, tc.wantLatched) - } - if err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "content"); err != nil { - t.Fatalf("duplicate completion = %v, want a no-op success", err) + testassert.Equal(t, latched, tc.wantLatched) + { + err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "content") + testassert.NoError(t, err) } }) } @@ -637,9 +616,7 @@ func TestClosedDeliveryCommitsBeforeDoneCloses(t *testing.T) { session := createSession(t, h) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(c) arrived := make(chan struct{}, 1) @@ -665,15 +642,13 @@ func TestClosedDeliveryCommitsBeforeDoneCloses(t *testing.T) { default: } close(gate) - if err := <-delivered; err != nil { - t.Fatalf("closed delivery: %v", err) + { + err := <-delivered + testassert.NoError(t, err) } waitMemberDone(t, member) signals := storedSignals(t, store, session) - if len(signals) != 1 || signals[0].RelatedMember == nil || signals[0].RelatedMember.Kind != "job" || - signals[0].RelatedMember.ID != jobFixtureID || signals[0].Content != "durable first" { - t.Fatalf("committed signal = %+v, want the job-member completion", signals) - } + testassert.Require(t, len(signals) == 1 && signals[0].RelatedMember != nil && signals[0].RelatedMember.Kind == "job" && signals[0].RelatedMember.ID == jobFixtureID && signals[0].Content == "durable first", "committed signal = %+v, want the job-member completion", signals) } // TestClosedDeliveryFailureRetainedBeforeMemberFinish proves the shutdown @@ -690,9 +665,7 @@ func TestClosedDeliveryFailureRetainedBeforeMemberFinish(t *testing.T) { session := createSession(t, h) c := leaseCoordinator(t, h, session) member, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) // The closed delivery's signal insert fails before any durable write with // a storage-class error. @@ -736,23 +709,21 @@ func TestClosedDeliveryFailureRetainedBeforeMemberFinish(t *testing.T) { } waitCtx, waitCancel := context.WithTimeout(context.Background(), 5*time.Second) defer waitCancel() - if err := h.Wait(waitCtx); !errors.Is(err, ErrStorage) || !strings.Contains(err.Error(), "injected storage failure") { - t.Fatalf("Wait while the failed delivery is parked after member finish = %v, want the retained storage failure", err) + { + err := h.Wait(waitCtx) + testassert.Require(t, errors.Is(err, ErrStorage) && strings.Contains(err.Error(), "injected storage failure"), "Wait while the failed delivery is parked after member finish = %v, want the retained storage failure", err) } release() - if err := <-produced; !errors.Is(err, ErrStorage) { - t.Fatalf("closed delivery = %v, want the storage class", err) + { + err := <-produced + testassert.Require(t, errors.Is(err, ErrStorage), "closed delivery = %v, want the storage class", err) } c.mu.Lock() _, present := c.group.members[member.completionID] c.mu.Unlock() - if present { - t.Fatalf("the member survived its finish") - } - if finished != 1 { - t.Fatalf("the Job's finish invalidation fired %d time(s), want exactly once", finished) - } + testassert.Require(t, !present, "the member survived its finish") + testassert.Equal(t, finished, 1) } // TestConcurrentClosedDeliveriesSerialize proves two closed deliveries @@ -767,13 +738,9 @@ func TestConcurrentClosedDeliveriesSerialize(t *testing.T) { session := createSession(t, h) c := leaseCoordinator(t, h, session) memberA, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("child admission: %v", err) - } + testassert.NoError(t, err) memberB, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("job admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(c) arrived := make(chan struct{}, 2) @@ -802,19 +769,19 @@ func TestConcurrentClosedDeliveriesSerialize(t *testing.T) { close(gate) <-arrived // the second delivery's transaction enters storage only after the release - if err := <-resA; err != nil { - t.Fatalf("child delivery: %v", err) + { + err := <-resA + testassert.NoError(t, err) } - if err := <-resB; err != nil { - t.Fatalf("job delivery: %v", err) + { + err := <-resB + testassert.NoError(t, err) } waitMemberDone(t, memberA) waitMemberDone(t, memberB) signals := storedSignals(t, store, session) - if len(signals) != 2 { - t.Fatalf("durable signals = %d, want both completions", len(signals)) - } + testassert.Equal(t, len(signals), 2) c.mu.Lock() var cached []string for _, entry := range c.graph.Entries { @@ -824,9 +791,7 @@ func TestConcurrentClosedDeliveriesSerialize(t *testing.T) { } c.mu.Unlock() durable := []string{signals[0].RelatedMember.Kind, signals[1].RelatedMember.Kind} - if !reflect.DeepEqual(cached, durable) { - t.Fatalf("cached signal order %v disagrees with durable order %v", cached, durable) - } + testassert.Require(t, reflect.DeepEqual(cached, durable), "cached signal order %v disagrees with durable order %v", cached, durable) } // TestClosedDeliveryRevisionDrift proves the closed path never retries: a @@ -845,9 +810,7 @@ func TestClosedDeliveryRevisionDrift(t *testing.T) { _, err = tx.ReplaceRegister(key, reg.Revision, reg.Payload) return err }) - if err != nil { - t.Fatalf("foreign revision advance: %v", err) - } + testassert.NoError(t, err) } t.Run("drift returns the conflict, rematerializes once, finishes the member", func(t *testing.T) { @@ -857,26 +820,21 @@ func TestClosedDeliveryRevisionDrift(t *testing.T) { defer cancel() c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(c) advanceRevision(t, store) err = h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "x") - if !errors.Is(err, ErrConflict) { - t.Fatalf("drifted delivery = %v, want the conflict class", err) - } + testassert.Require(t, errors.Is(err, ErrConflict), "drifted delivery = %v, want the conflict class", err) waitMemberDone(t, member) - if n := storedEntryCount(store, testSessionID); n != 0 { - t.Fatalf("the drifted delivery or a retry committed %d entries, want none", n) + { + n := storedEntryCount(store, testSessionID) + testassert.Equal(t, n, 0) } c.mu.Lock() revision := c.graph.Session.Revision c.mu.Unlock() - if revision != 2 { - t.Fatalf("cached revision %d, want the rematerialized revision 2", revision) - } + testassert.Equal(t, revision, 2) }) t.Run("failing rematerialization returns corruption", func(t *testing.T) { @@ -886,31 +844,29 @@ func TestClosedDeliveryRevisionDrift(t *testing.T) { defer cancel() c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(c) advanceRevision(t, store) - if err := store.Transact(context.Background(), func(tx Transaction) error { - key := RegisterKey{SessionID: testSessionID, Kind: RegisterSession} - reg, err := tx.ReadRegister(key) - if err != nil { + { + err := store.Transact(context.Background(), func(tx Transaction) error { + key := RegisterKey{SessionID: testSessionID, Kind: RegisterSession} + reg, err := tx.ReadRegister(key) + if err != nil { + return err + } + _, err = tx.ReplaceRegister(key, reg.Revision, json.RawMessage(`{"bogus":true}`)) return err - } - _, err = tx.ReplaceRegister(key, reg.Revision, json.RawMessage(`{"bogus":true}`)) - return err - }); err != nil { - t.Fatalf("corrupt register: %v", err) + }) + testassert.NoError(t, err) } err = h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "x") var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("delivery = %v, want the rematerialization corruption", err) - } + testassert.Require(t, errors.As(err, &corrupt), "delivery = %v, want the rematerialization corruption", err) waitMemberDone(t, member) - if n := storedEntryCount(store, testSessionID); n != 0 { - t.Fatalf("the failed delivery committed %d entries, want none", n) + { + n := storedEntryCount(store, testSessionID) + testassert.Equal(t, n, 0) } }) @@ -921,23 +877,17 @@ func TestClosedDeliveryRevisionDrift(t *testing.T) { defer cancel() c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(c) advanceRevision(t, store) store.registersErr = fmt.Errorf("%w: storage down", ErrStorage) err = h.DeliverBackgroundCompletion(context.Background(), testSessionID, member.completionID, "x") - if !errors.Is(err, ErrStorage) { - t.Fatalf("delivery = %v, want the rematerialization storage failure", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "delivery = %v, want the rematerialization storage failure", err) h.mu.Lock() latched := h.storageFailure != nil h.mu.Unlock() - if !latched { - t.Fatalf("the storage-class rematerialization failure was not latched") - } + testassert.Require(t, latched, "the storage-class rematerialization failure was not latched") waitMemberDone(t, member) }) } @@ -973,41 +923,35 @@ func TestJobCompletionWaitsForChildSuccessorRetirement(t *testing.T) { cP := leaseCoordinator(t, h, completionParentID) cC := leaseCoordinator(t, h, completionChildID) member, err := admitLocked(h, cP, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("child member admission: %v", err) - } + testassert.NoError(t, err) cC.mu.Lock() cC.pendingCompletion = &launchInfo{completionID: member.completionID, outputLimit: 1024} cC.mu.Unlock() jobMember, err := admitLocked(h, cC, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("job admission: %v", err) - } - - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: completionChildID, - OperationID: "op-1", - Origin: InputOriginUser, - Content: admissionContent("hello"), - Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("child submit: %v", err) + testassert.NoError(t, err) + + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: completionChildID, + OperationID: "op-1", + Origin: InputOriginUser, + Content: admissionContent("hello"), + Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } <-script.arrived // the child run is parked at its model boundary - if err := h.DeliverBackgroundCompletion(context.Background(), completionChildID, jobMember.completionID, "job done"); err != nil { - t.Fatalf("job completion delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), completionChildID, jobMember.completionID, "job done") + testassert.NoError(t, err) } waitMemberDone(t, jobMember) cC.mu.Lock() pending, steering := cC.pendingCompletion != nil, len(cC.steering) cC.mu.Unlock() - if !pending || steering != 1 { - t.Fatalf("after the job delivery: pending %v steering %d, want the obligation deferred and the completion queued", pending, steering) - } - if storedOperationExists(store, completionChildID, jobMember.completionID) { - t.Fatalf("the job completion admitted an operation while the run was active") - } + testassert.Require(t, pending && steering == 1, "after the job delivery: pending %v steering %d, want the obligation deferred and the completion queued", pending, steering) + testassert.Require(t, !storedOperationExists(store, completionChildID, jobMember.completionID), "the job completion admitted an operation while the run was active") stub.gate = make(chan struct{}) // the drain's admission of the completion will park here script.releaseGate() // op-1 settles quiet success with the completion still queued @@ -1019,29 +963,25 @@ func TestJobCompletionWaitsForChildSuccessorRetirement(t *testing.T) { cC.mu.Lock() pendingDeferred, runInstalled := cC.pendingCompletion != nil, cC.run != nil cC.mu.Unlock() - if !pendingDeferred || !runInstalled { - t.Fatalf("deferred check: pending %v run %v, want the obligation held on the installed run", pendingDeferred, runInstalled) - } - if storedOperationExists(store, completionParentID, member.completionID) { - t.Fatalf("the parent received the completion before the successor retired") - } + testassert.Require(t, pendingDeferred && runInstalled, "deferred check: pending %v run %v, want the obligation held on the installed run", pendingDeferred, runInstalled) + testassert.Require(t, !storedOperationExists(store, completionParentID, member.completionID), "the parent received the completion before the successor retired") close(stub.gate) // the drain admits the completion as the successor operation watch.next() // the successor's settlement committed watch.next() // the parent completion's settlement committed - if rec := settledOperation(t, store, completionParentID, member.completionID); rec.State.Status != OperationSuccess { - t.Fatalf("parent completion settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, completionParentID, member.completionID) + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if got := runtimeInputText(t, store, completionParentID); got != tc.want || strings.Contains(got, "earlier") { - t.Fatalf("delivered completion = %q, want %q and never the predecessor's earlier text", got, tc.want) + { + got := runtimeInputText(t, store, completionParentID) + testassert.Require(t, got == tc.want && !strings.Contains(got, "earlier"), "delivered completion = %q, want %q and never the predecessor's earlier text", got, tc.want) } cC.mu.Lock() pendingAfter := cC.pendingCompletion cC.mu.Unlock() - if pendingAfter != nil { - t.Fatalf("the pending obligation survived the completed delivery") - } + testassert.Require(t, pendingAfter == nil, "the pending obligation survived the completed delivery") waitMemberDone(t, member) }) } @@ -1059,9 +999,7 @@ func TestChildCompletionSettledAbandonment(t *testing.T) { cC := leaseCoordinator(t, h, completionChildID) cP := leaseCoordinator(t, h, completionParentID) member, err := admitLocked(h, cP, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("member admission: %v", err) - } + testassert.NoError(t, err) cC.mu.Lock() cC.pendingCompletion = &launchInfo{completionID: member.completionID, outputLimit: 1024} cC.mu.Unlock() @@ -1075,11 +1013,10 @@ func TestChildCompletionSettledAbandonment(t *testing.T) { cC.mu.Lock() pending := cC.pendingCompletion cC.mu.Unlock() - if pending != nil { - t.Fatalf("the abandoned obligation survived") - } - if n := storedEntryCount(store, completionParentID); n != 0 { - t.Fatalf("abandonment wrote %d durable entries, want none", n) + testassert.Require(t, pending == nil, "the abandoned obligation survived") + { + n := storedEntryCount(store, completionParentID) + testassert.Equal(t, n, 0) } }) @@ -1094,9 +1031,7 @@ func TestChildCompletionSettledAbandonment(t *testing.T) { cC.mu.Lock() pending := cC.pendingCompletion != nil cC.mu.Unlock() - if !pending { - t.Fatalf("the deferred obligation was taken") - } + testassert.Require(t, pending, "the deferred obligation was taken") }) } @@ -1128,9 +1063,7 @@ func TestChildCompletionRendersParentFacingText(t *testing.T) { cC := leaseCoordinator(t, h, completionChildID) cP := leaseCoordinator(t, h, completionParentID) member, err := admitLocked(h, cP, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("member admission: %v", err) - } + testassert.NoError(t, err) cC.mu.Lock() cC.pendingCompletion = &launchInfo{completionID: member.completionID, outputLimit: 1024} cC.mu.Unlock() @@ -1138,18 +1071,18 @@ func TestChildCompletionRendersParentFacingText(t *testing.T) { h.childCompletionSettled(cC, "") waitMemberDone(t, member) watch.next() // the parent completion's settlement committed - if rec := settledOperation(t, store, completionParentID, member.completionID); rec.State.Status != OperationSuccess { - t.Fatalf("parent completion settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, completionParentID, member.completionID) + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if got := runtimeInputText(t, store, completionParentID); got != tc.want { - t.Fatalf("rendered completion = %q, want %q", got, tc.want) + { + got := runtimeInputText(t, store, completionParentID) + testassert.Equal(t, got, tc.want) } cC.mu.Lock() pending := cC.pendingCompletion cC.mu.Unlock() - if pending != nil { - t.Fatalf("the delivered obligation survived") - } + testassert.Require(t, pending == nil, "the delivered obligation survived") }) } } @@ -1176,9 +1109,7 @@ func TestChildCompletionTruncatesAtOutputLimit(t *testing.T) { cC := leaseCoordinator(t, h, completionChildID) cP := leaseCoordinator(t, h, completionParentID) member, err := admitLocked(h, cP, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("member admission: %v", err) - } + testassert.NoError(t, err) closeBackgroundMode(cP) // the closed path records the rendered content verbatim cC.mu.Lock() cC.pendingCompletion = &launchInfo{completionID: member.completionID, outputLimit: tc.limit} @@ -1187,15 +1118,12 @@ func TestChildCompletionTruncatesAtOutputLimit(t *testing.T) { h.childCompletionSettled(cC, "") waitMemberDone(t, member) signals := storedSignals(t, store, completionParentID) - if len(signals) != 1 { - t.Fatalf("durable signals = %d, want the closed-path record", len(signals)) - } - if got := signals[0].Content; got != tc.want { - t.Fatalf("truncated content = %q, want %q", got, tc.want) - } - if len(signals[0].Content) > tc.limit { - t.Fatalf("truncated content exceeds the limit: %d > %d", len(signals[0].Content), tc.limit) + testassert.Equal(t, len(signals), 1) + { + got := signals[0].Content + testassert.Equal(t, got, tc.want) } + testassert.Require(t, len(signals[0].Content) <= tc.limit, "truncated content exceeds the limit: %d > %d", len(signals[0].Content), tc.limit) }) } } diff --git a/harness/context.go b/harness/context.go index 841d7981..5dabd80f 100644 --- a/harness/context.go +++ b/harness/context.go @@ -27,8 +27,8 @@ var errSteeringBoundary = errors.New("harness: steering boundary") // another request, leaving every head buffered for the ordinary post-terminal // drain — otherwise it projects the fresh model.Message history. Selection // consumes no head and performs no storage, reservation, cancellation or -// observation. Physical retries and compact-piece context callbacks never -// reach this boundary. +// observation. Physical retries and the compact Agent's own context callback +// never reach this boundary. func (h *Harness) contextSource(c *coordinator, operationID string) agent.ContextSource { firstRequest := true return func(ctx context.Context) ([]model.Message, error) { diff --git a/harness/context_test.go b/harness/context_test.go index fbc6b70b..03576111 100644 --- a/harness/context_test.go +++ b/harness/context_test.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -75,9 +76,7 @@ func projectionFollowUp(entryID, operationID, text, settlementID string, sequenc func projectionMessages(t *testing.T, h *Harness, c *coordinator) []model.Message { t.Helper() msgs, err := h.contextSource(c, testOpID)(context.Background()) - if err != nil { - t.Fatalf("context source: %v", err) - } + testassert.NoError(t, err) return msgs } @@ -90,9 +89,7 @@ func wantSummaryMessage(t *testing.T, compaction compactionEntry) model.Message Source: compaction.Model, Content: []model.ContentPart{{Kind: model.PartText, Text: "[Previous conversation summary]\n\n" + compaction.Summary + "\n\n[End of summary. Continue from here.]"}}, }) - if err != nil { - t.Fatalf("summary message: %v", err) - } + testassert.NoError(t, err) return msg } @@ -101,9 +98,7 @@ func wantSummaryMessage(t *testing.T, compaction compactionEntry) model.Message // the complete assertion, including the absence of any second summary. func assertProjectionEqual(t *testing.T, got, want []model.Message) { t.Helper() - if !reflect.DeepEqual(got, want) { - t.Fatalf("projected messages = %+v, want %+v", got, want) - } + testassert.Require(t, reflect.DeepEqual(got, want), "projected messages = %+v, want %+v", got, want) } // TestContextSourceSummarizedProjection proves the summarized projection: with @@ -246,9 +241,7 @@ func mustSystemMessage(t *testing.T, text string) model.Message { Role: model.RoleSystem, Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, }) - if err != nil { - t.Fatalf("system message: %v", err) - } + testassert.NoError(t, err) return msg } @@ -259,9 +252,7 @@ func mustInputMessage(t *testing.T, text string) model.Message { Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, }) - if err != nil { - t.Fatalf("input message: %v", err) - } + testassert.NoError(t, err) return msg } @@ -273,8 +264,6 @@ func mustAssistantMessage(t *testing.T, text string) model.Message { Source: testModelRef(), Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, }) - if err != nil { - t.Fatalf("assistant message: %v", err) - } + testassert.NoError(t, err) return msg } diff --git a/harness/coordination_test.go b/harness/coordination_test.go index f85375a4..f1d7bfac 100644 --- a/harness/coordination_test.go +++ b/harness/coordination_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -153,18 +154,14 @@ func newCancelableHarness(t *testing.T, store Storage, prepared PreparedExecutio } hctx, cancel := context.WithCancel(context.Background()) h, err := New(hctx, Dependencies{Storage: store, Prepare: prepare}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) return h, cancel } func createSession(t *testing.T, h *Harness) string { t.Helper() session, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) return session.Identity.SessionID } @@ -186,9 +183,7 @@ func submitText(t *testing.T, h *Harness, sessionID, operationID string, mode Me func entryTexts(t *testing.T, store *graphStorage, sessionID string) []string { t.Helper() graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("session graph: %v", err) - } + testassert.NoError(t, err) out := []string{} for _, entry := range graph.Entries { if entry.Input != nil { @@ -205,9 +200,7 @@ func entryTexts(t *testing.T, store *graphStorage, sessionID string) []string { func steeringInputOwner(t *testing.T, store Storage, sessionID, text string) (string, InputOrigin) { t.Helper() graph, err := validateSessionGraph(context.Background(), store, sessionID) - if err != nil { - t.Fatalf("session graph: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.Input != nil && len(entry.Input.Content) > 0 && entry.Input.Content[0].Text == text { return entry.Envelope.OperationID, entry.Input.Origin @@ -224,13 +217,9 @@ func steeringInputOwner(t *testing.T, store Storage, sessionID, text string) (st func settledOperation(t *testing.T, store *graphStorage, sessionID, operationID string) OperationRecord { t.Helper() reg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: operationID}) - if err != nil { - t.Fatalf("read operation register %q: %v", operationID, err) - } + testassert.Require(t, err == nil, "read operation register %q: %v", operationID, err) rec, err := decodeOperationRegister(reg) - if err != nil { - t.Fatalf("decode operation register %q: %v", operationID, err) - } + testassert.Require(t, err == nil, "decode operation register %q: %v", operationID, err) return rec } @@ -243,21 +232,15 @@ func awaitOperationTerminal(t *testing.T, store *graphStorage, sessionID, operat reg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: operationID}) if err == nil { rec, derr := decodeOperationRegister(reg) - if derr != nil { - t.Fatalf("decode operation register %q: %v", operationID, derr) - } + testassert.Require(t, derr == nil, "decode operation register %q: %v", operationID, derr) if rec.State.Terminal != nil { - if rec.State.Status != want { - t.Fatalf("operation %q status = %s, want %s", operationID, rec.State.Status, want) - } + testassert.Require(t, rec.State.Status == want, "operation %q status = %s, want %s", operationID, rec.State.Status, want) return rec } } else if !errors.Is(err, ErrNotFound) { t.Fatalf("read operation register %q: %v", operationID, err) } - if time.Now().After(deadline) { - t.Fatalf("operation %q never settled as %s", operationID, want) - } + testassert.Require(t, !time.Now().After(deadline), "operation %q never settled as %s", operationID, want) time.Sleep(2 * time.Millisecond) } } @@ -276,38 +259,29 @@ func TestSubmitRoutesIdleAndActive(t *testing.T) { session := createSession(t, h) res, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") - if err != nil || res.Disposition != DispositionAdmitted || res.Operation == nil || res.Operation.State.Status != OperationRunning { - t.Fatalf("idle regular submit = %+v err %v, want admitted with the record", res, err) - } + testassert.Require(t, err == nil && res.Disposition == DispositionAdmitted && res.Operation != nil && res.Operation.State.Status == OperationRunning, "idle regular submit = %+v err %v, want admitted with the record", res, err) <-script.arrived // the execution reached its parked model boundary: the Session is active steered, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1") - if err != nil || steered.Disposition != DispositionSteering || steered.Operation != nil { - t.Fatalf("active regular submit = %+v err %v, want steering without a record", steered, err) - } + testassert.Require(t, err == nil && steered.Disposition == DispositionSteering && steered.Operation == nil, "active regular submit = %+v err %v, want steering without a record", steered, err) queued, err := submitText(t, h, session, "op-3", MessageModeQueued, "q1") - if err != nil || queued.Disposition != DispositionQueued || queued.Operation != nil { - t.Fatalf("active queued submit = %+v err %v, want queued without a record", queued, err) - } + testassert.Require(t, err == nil && queued.Disposition == DispositionQueued && queued.Operation == nil, "active queued submit = %+v err %v, want queued without a record", queued, err) existing, err := submitText(t, h, session, "op-1", MessageModeRegular, "retry") - if err != nil || existing.Disposition != DispositionExisting || existing.Operation == nil || - existing.Operation.Admission.OperationID != "op-1" { - t.Fatalf("active existing submit = %+v err %v, want the first record before routing", existing, err) - } - if entries, regs := storedSessionState(store, session); len(entries) != 1 || len(regs) != 2 { - t.Fatalf("buffered submits published entries %v registers %v", entries, regs) + testassert.Require(t, err == nil && existing.Disposition == DispositionExisting && existing.Operation != nil && existing.Operation.Admission.OperationID == "op-1", "active existing submit = %+v err %v, want the first record before routing", existing, err) + { + entries, regs := storedSessionState(store, session) + testassert.Require(t, len(entries) == 1 && len(regs) == 2, "buffered submits published entries %v registers %v", entries, regs) } - if _, err := submitText(t, h, session, "op-4", MessageMode("urgent"), "x"); !errors.Is(err, ErrInvalid) { - t.Fatalf("unknown mode = %v, want ErrInvalid", err) + { + _, err := submitText(t, h, session, "op-4", MessageMode("urgent"), "x") + testassert.Require(t, errors.Is(err, ErrInvalid), "unknown mode = %v, want ErrInvalid", err) } // Idle queued input uses normal admission: a fresh idle Session admits it. other := createSession(t, h) idleQueued, err := submitText(t, h, other, "idle-queued", MessageModeQueued, "queued-idle") - if err != nil || idleQueued.Disposition != DispositionAdmitted || idleQueued.Operation == nil { - t.Fatalf("idle queued submit = %+v err %v, want normal admission", idleQueued, err) - } + testassert.Require(t, err == nil && idleQueued.Disposition == DispositionAdmitted && idleQueued.Operation != nil, "idle queued submit = %+v err %v, want normal admission", idleQueued, err) } // TestSteeringAdmitsHeadsInFIFOOrder proves the steering row: while an @@ -345,26 +319,30 @@ func TestSteeringAdmitsHeadsInFIFOOrder(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, openerArrived, "the predecessor's Open") - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit 1: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } close(openerGate) // the next head is buffered from the predecessor's Open, before its first request receiveBounded(t, script.arrived, "op-1's first model request") - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: session, OperationID: "op-3", Origin: InputOriginRuntime, - Content: admissionContent("s2"), Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("steering submit 2: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: session, OperationID: "op-3", Origin: InputOriginRuntime, + Content: admissionContent("s2"), Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: session, OperationID: "op-4", Origin: InputOriginPlugin, - Content: admissionContent("q1"), Mode: MessageModeQueued, - }); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: session, OperationID: "op-4", Origin: InputOriginPlugin, + Content: admissionContent("q1"), Mode: MessageModeQueued, + }) + testassert.NoError(t, err) } script.releaseGate() // the first model result settles with steering waiting: the boundary exits through the token @@ -377,46 +355,49 @@ func TestSteeringAdmitsHeadsInFIFOOrder(t *testing.T) { } } reqs := script.seen() - if len(reqs) != 5 { - t.Fatalf("model requests = %d, want one per head plus the reused-call successor's continuation", len(reqs)) - } - if texts := strings.Join(textsOf(reqs[0]), "|"); texts != "system|hello" { - t.Fatalf("first request = %q, want the admitted input only despite the buffered head", texts) + testassert.Equal(t, len(reqs), 5) + { + texts := strings.Join(textsOf(reqs[0]), "|") + testassert.Equal(t, texts, "system|hello") } - if texts := strings.Join(textsOf(reqs[1]), "|"); texts != "system|hello|done|s1" { - t.Fatalf("successor request = %q, want s1 as its own Operation's first receiving request", texts) + { + texts := strings.Join(textsOf(reqs[1]), "|") + testassert.Equal(t, texts, "system|hello|done|s1") } - if texts := strings.Join(textsOf(reqs[2]), "|"); texts != "system|hello|done|s1|done|ok|s2" { - t.Fatalf("third request = %q, want s2 delivered after s1's own turn", texts) + { + texts := strings.Join(textsOf(reqs[2]), "|") + testassert.Equal(t, texts, "system|hello|done|s1|done|ok|s2") } - if texts := strings.Join(textsOf(reqs[3]), "|"); texts != "system|hello|done|s1|done|ok|s2|done|ok" { - t.Fatalf("continuation request = %q, want the reused call id's result under its own Operation", texts) + { + texts := strings.Join(textsOf(reqs[3]), "|") + testassert.Equal(t, texts, "system|hello|done|s1|done|ok|s2|done|ok") } - if texts := strings.Join(textsOf(reqs[4]), "|"); texts != "system|hello|done|s1|done|ok|s2|done|ok|done|q1" { - t.Fatalf("queued request = %q, want q1 trailing every steering head", texts) + { + texts := strings.Join(textsOf(reqs[4]), "|") + testassert.Equal(t, texts, "system|hello|done|s1|done|ok|s2|done|ok|done|q1") } awaitOperationTerminal(t, store, session, "op-4", OperationSuccess) // the explicit successor rendezvous awaitHarnessQuiet(t, h, session) - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,s1,s2,q1" { - t.Fatalf("committed inputs = %q, want steering admitted in FIFO order before the queued follower", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,s1,s2,q1") } - if owner, origin := steeringInputOwner(t, store, session, "s1"); owner != "op-2" || origin != InputOriginUser { - t.Fatalf("steering input s1 owner/origin = %q/%v, want op-2/user", owner, origin) + { + owner, origin := steeringInputOwner(t, store, session, "s1") + testassert.Require(t, owner == "op-2" && origin == InputOriginUser, "steering input s1 owner/origin = %q/%v, want op-2/user", owner, origin) } - if owner, origin := steeringInputOwner(t, store, session, "s2"); owner != "op-3" || origin != InputOriginRuntime { - t.Fatalf("steering input s2 owner/origin = %q/%v, want op-3/runtime", owner, origin) + { + owner, origin := steeringInputOwner(t, store, session, "s2") + testassert.Require(t, owner == "op-3" && origin == InputOriginRuntime, "steering input s2 owner/origin = %q/%v, want op-3/runtime", owner, origin) } - if owner, origin := steeringInputOwner(t, store, session, "q1"); owner != "op-4" || origin != InputOriginPlugin { - t.Fatalf("queued input q1 owner/origin = %q/%v, want op-4/plugin", owner, origin) + { + owner, origin := steeringInputOwner(t, store, session, "q1") + testassert.Require(t, owner == "op-4" && origin == InputOriginPlugin, "queued input q1 owner/origin = %q/%v, want op-4/plugin", owner, origin) } graph, err := validateFixture(t, store, session) - if err != nil { - t.Fatalf("graph after the deliveries: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Signal != nil { - t.Fatalf("entry %s added a signal behind a boundary exit", entry.Envelope.ID) - } + testassert.Require(t, entry.Signal == nil, "entry %s added a signal behind a boundary exit", entry.Envelope.ID) } } @@ -449,27 +430,32 @@ func TestSteeringContinuationAcrossOutputShapes(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, script.arrived, "op-1's first model request") - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer") + testassert.NoError(t, err) } script.releaseGate() receiveBounded(t, watch.seen, "op-1's terminal settlement") - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != tc.wantEnd { - t.Fatalf("operation settled %q, want %q", rec.State.Status, tc.wantEnd) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, tc.wantEnd) } receiveBounded(t, watch.seen, "the steering successor's settlement") - if texts := strings.Join(script.lastTexts(), "|"); !strings.Contains(texts, "steer") { - t.Fatalf("delivered projection = %q, want the steering carried by its own Operation's request", texts) + { + texts := strings.Join(script.lastTexts(), "|") + testassert.Require(t, strings.Contains(texts, "steer"), "delivered projection = %q, want the steering carried by its own Operation's request", texts) } awaitOperationTerminal(t, store, session, "op-2", OperationSuccess) // the explicit successor rendezvous awaitHarnessQuiet(t, h, session) - if owner, _ := steeringInputOwner(t, store, session, "steer"); owner != "op-2" { - t.Fatalf("steering input owned by %q, want its own Operation op-2", owner) + { + owner, _ := steeringInputOwner(t, store, session, "steer") + testassert.Equal(t, owner, "op-2") } }) } @@ -489,32 +475,39 @@ func TestPostTerminalQueuedDrain(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submitText(t, h, session, "turn-2", MessageModeQueued, "q1"); err != nil { - t.Fatalf("queued submit 1: %v", err) + { + _, err := submitText(t, h, session, "turn-2", MessageModeQueued, "q1") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "turn-3", MessageModeQueued, "q2"); err != nil { - t.Fatalf("queued submit 2: %v", err) + { + _, err := submitText(t, h, session, "turn-3", MessageModeQueued, "q2") + testassert.NoError(t, err) } script.releaseGate() //turn-1 settles; the drain admits q1, then q2 after its terminal watch.next() watch.next() - if rec := settledOperation(t, store, session, "turn-2"); rec.State.Status != OperationSuccess { - t.Fatalf("first drained turn settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, session, "turn-2") + testassert.Equal(t, rec.State.Status, OperationSuccess) } watch.next() - if rec := settledOperation(t, store, session, "turn-3"); rec.State.Status != OperationSuccess { - t.Fatalf("second drained turn settled %q, want success", rec.State.Status) + { + rec := settledOperation(t, store, session, "turn-3") + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if texts := strings.Join(script.lastTexts(), "|"); texts != "system|hello|done|q1|done|q2" { - t.Fatalf("last drained projection = %q, want FIFO order through q2", texts) + { + texts := strings.Join(script.lastTexts(), "|") + testassert.Equal(t, texts, "system|hello|done|q1|done|q2") } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,q1,q2" { - t.Fatalf("committed inputs = %q, want one admitted queued item per turn", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,q1,q2") } } @@ -543,31 +536,35 @@ func TestBufferedItemFailureIsFinal(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submitText(t, h, session, "turn-2", mode, "q1"); err != nil { - t.Fatalf("queued submit 1: %v", err) + { + _, err := submitText(t, h, session, "turn-2", mode, "q1") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "turn-3", mode, "q2"); err != nil { - t.Fatalf("queued submit 2: %v", err) + { + _, err := submitText(t, h, session, "turn-3", mode, "q2") + testassert.NoError(t, err) } script.releaseGate() watch.next() watch.next() - if rec := settledOperation(t, store, session, "turn-3"); rec.State.Status != OperationSuccess { - t.Fatalf("next buffered message settled %+v, want q2 admitted and successful", rec.State) - } - if calls != 3 { - t.Fatalf("preparation calls = %d, want one per admitted turn with the failed item dropped", calls) + { + rec := settledOperation(t, store, session, "turn-3") + testassert.Require(t, rec.State.Status == OperationSuccess, "next buffered message settled %+v, want q2 admitted and successful", rec.State) } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,q2" { - t.Fatalf("committed inputs = %q, want the failed item gone without retry", got) + testassert.Equal(t, calls, 3) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,q2") } - if _, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: session, Kind: RegisterOperation, OperationID: "turn-2"}); !errors.Is(err, ErrNotFound) { - t.Fatalf("failed item's operation register = %v, want nothing retained in storage", err) + { + _, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: session, Kind: RegisterOperation, OperationID: "turn-2"}) + testassert.Require(t, errors.Is(err, ErrNotFound), "failed item's operation register = %v, want nothing retained in storage", err) } }) @@ -590,25 +587,30 @@ func TestBufferedItemFailureIsFinal(t *testing.T) { return nil } - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submitText(t, h, session, "turn-2", mode, "q1"); err != nil { - t.Fatalf("queued submit 1: %v", err) + { + _, err := submitText(t, h, session, "turn-2", mode, "q1") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "turn-3", mode, "q2"); err != nil { - t.Fatalf("queued submit 2: %v", err) + { + _, err := submitText(t, h, session, "turn-3", mode, "q2") + testassert.NoError(t, err) } script.releaseGate() watch.next() watch.next() - if rec := settledOperation(t, store, session, "turn-3"); rec.State.Status != OperationSuccess { - t.Fatalf("next buffered message settled %+v, want q2 admitted and successful", rec.State) + { + rec := settledOperation(t, store, session, "turn-3") + testassert.Require(t, rec.State.Status == OperationSuccess, "next buffered message settled %+v, want q2 admitted and successful", rec.State) } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,q2" { - t.Fatalf("committed inputs = %q, want the failed item gone without retry", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,q2") } }) @@ -627,29 +629,35 @@ func TestBufferedItemFailureIsFinal(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // parked at the first model boundary: the queued items buffer before any terminal - if _, err := submitText(t, h, session, "turn-2", mode, "q1"); err != nil { - t.Fatalf("queued submit 1: %v", err) + { + _, err := submitText(t, h, session, "turn-2", mode, "q1") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "turn-3", mode, "q2"); err != nil { - t.Fatalf("queued submit 2: %v", err) + { + _, err := submitText(t, h, session, "turn-3", mode, "q2") + testassert.NoError(t, err) } script.releaseGate() watch.next() // turn-1 succeeds watch.next() // the drain admits q1, whose Operation settles failure - if rec := settledOperation(t, store, session, "turn-2"); rec.State.Status != OperationFailure { - t.Fatalf("first drained turn settled %q, want failure", rec.State.Status) + { + rec := settledOperation(t, store, session, "turn-2") + testassert.Equal(t, rec.State.Status, OperationFailure) } watch.next() // the next buffered message proceeds - if rec := settledOperation(t, store, session, "turn-3"); rec.State.Status != OperationSuccess { - t.Fatalf("next buffered message settled %+v, want q2 admitted and successful", rec.State) + { + rec := settledOperation(t, store, session, "turn-3") + testassert.Require(t, rec.State.Status == OperationSuccess, "next buffered message settled %+v, want q2 admitted and successful", rec.State) } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,q1,q2" { - t.Fatalf("committed inputs = %q, want both queued items delivered in order", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,q1,q2") } }) }) @@ -675,8 +683,9 @@ func TestFinalBoundarySerialization(t *testing.T) { script.model = func(ctx context.Context, req model.Request) (model.Stream, error) { if first { // submit at the model-result boundary, before the settlement returns first = false - if _, err := submitText(t, h, session, "boundary", MessageModeRegular, "at-boundary"); err != nil { - t.Errorf("boundary submit: %v", err) + { + _, err := submitText(t, h, session, "boundary", MessageModeRegular, "at-boundary") + testassert.Check(t, err == nil, "boundary submit: %v", err) } } return inner(ctx, req) @@ -687,26 +696,32 @@ func TestFinalBoundarySerialization(t *testing.T) { session = createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, watch.seen, "turn-1's quiet success settlement") // the natural-success boundary exit receiveBounded(t, watch.seen, "the boundary input's own settlement") - if rec := settledOperation(t, store, session, "turn-1"); rec.State.Status != OperationSuccess { - t.Fatalf("operation settled %q, want the boundary's quiet success", rec.State.Status) + { + rec := settledOperation(t, store, session, "turn-1") + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if rec := settledOperation(t, store, session, "boundary"); rec.State.Status != OperationSuccess { - t.Fatalf("boundary operation settled %q, want the boundary input admitted and successful", rec.State.Status) + { + rec := settledOperation(t, store, session, "boundary") + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if texts := strings.Join(script.lastTexts(), "|"); texts != "system|hello|done|at-boundary" { - t.Fatalf("final projection = %q, want the boundary steering as the successor's first request", texts) + { + texts := strings.Join(script.lastTexts(), "|") + testassert.Equal(t, texts, "system|hello|done|at-boundary") } awaitHarnessQuiet(t, h, session) // raw graph readers await Session convergence after the successor's terminal - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,at-boundary" { - t.Fatalf("committed inputs = %q, want each input owned by its own Operation", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,at-boundary") } - if owner, _ := steeringInputOwner(t, store, session, "at-boundary"); owner != "boundary" { - t.Fatalf("boundary input owned by %q, want its own Operation", owner) + { + owner, _ := steeringInputOwner(t, store, session, "at-boundary") + testassert.Equal(t, owner, "boundary") } }) @@ -722,8 +737,9 @@ func TestFinalBoundarySerialization(t *testing.T) { script.model = func(ctx context.Context, req model.Request) (model.Stream, error) { if first { // defer one queued message at the final model-result boundary first = false - if _, err := submitText(t, h, session, "turn-2", MessageModeQueued, "at-boundary"); err != nil { - t.Errorf("boundary submit: %v", err) + { + _, err := submitText(t, h, session, "turn-2", MessageModeQueued, "at-boundary") + testassert.Check(t, err == nil, "boundary submit: %v", err) } } return inner(ctx, req) @@ -734,16 +750,19 @@ func TestFinalBoundarySerialization(t *testing.T) { session = createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } watch.next() watch.next() - if rec := settledOperation(t, store, session, "turn-2"); rec.State.Status != OperationSuccess { - t.Fatalf("drained turn settled %+v, want the boundary queued input admitted after the terminal", rec.State) + { + rec := settledOperation(t, store, session, "turn-2") + testassert.Require(t, rec.State.Status == OperationSuccess, "drained turn settled %+v, want the boundary queued input admitted after the terminal", rec.State) } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,at-boundary" { - t.Fatalf("committed inputs = %q, want the boundary queued input delivered", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,at-boundary") } }) } @@ -771,32 +790,31 @@ func TestAgentTypeChangeDuringBlockedEffect(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "turn-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submitText(t, h, session, "turn-2", MessageModeQueued, "q1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, h, session, "turn-2", MessageModeQueued, "q1") + testassert.NoError(t, err) } - if _, err := h.ChangeAgentType(context.Background(), session, "reviewer"); err != nil { - t.Fatalf("ChangeAgentType during the blocked effect: %v", err) + { + _, err := h.ChangeAgentType(context.Background(), session, "reviewer") + testassert.NoError(t, err) } script.releaseGate() watch.next() watch.next() - if rec := settledOperation(t, store, session, "turn-2"); rec.State.Status != OperationSuccess { - t.Fatalf("drained turn settled %+v, want success", rec.State) + { + rec := settledOperation(t, store, session, "turn-2") + testassert.Require(t, rec.State.Status == OperationSuccess, "drained turn settled %+v, want success", rec.State) } prepareMu.Lock() defer prepareMu.Unlock() - if len(prepareReqs) != 2 { - t.Fatalf("preparation calls = %d, want the drained admission to prepare again", len(prepareReqs)) - } - if prepareReqs[0].Session.AgentType != "coder" || prepareReqs[1].Session.AgentType != "reviewer" { - t.Fatalf("preparation agent types = %q then %q, want the capture-time value then the changed one", - prepareReqs[0].Session.AgentType, prepareReqs[1].Session.AgentType) - } + testassert.Equal(t, len(prepareReqs), 2) + testassert.Require(t, prepareReqs[0].Session.AgentType == "coder" && prepareReqs[1].Session.AgentType == "reviewer", "preparation agent types = %q then %q, want the capture-time value then the changed one", prepareReqs[0].Session.AgentType, prepareReqs[1].Session.AgentType) } // TestWaitConvergence proves the Harness-lifetime row: Wait returns only after @@ -815,8 +833,9 @@ func TestWaitConvergence(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived waited := make(chan error, 1) @@ -824,12 +843,14 @@ func TestWaitConvergence(t *testing.T) { cancel() // Harness loss closes admission and ends the run script.releaseGate() // the already-started callback publishes, then the run interrupts - if err := <-waited; err != nil { - t.Fatalf("Wait: %v", err) + { + err := <-waited + testassert.NoError(t, err) } watch.next() - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationInterruption { - t.Fatalf("converged operation = %q, want interruption", rec.State.Status) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationInterruption) } }) @@ -843,9 +864,7 @@ func TestWaitConvergence(t *testing.T) { <-gate return PreparedExecution{}, prepCtx.Err() // the dead harness context fails the parked preparation }}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) session := createSession(t, h) admitted := make(chan error, 1) go func() { @@ -859,14 +878,17 @@ func TestWaitConvergence(t *testing.T) { cancel() // the Harness context dies while preparation is parked close(gate) // the parked preparation fails on the dead context - if err := <-admitted; err == nil { - t.Fatalf("preparation over a lost Harness context succeeded, want an error") + { + err := <-admitted + testassert.Require(t, err != nil, "preparation over a lost Harness context succeeded, want an error") } - if err := <-waited; err != nil { - t.Fatalf("Wait: %v", err) + { + err := <-waited + testassert.NoError(t, err) } - if _, regs := storedSessionState(store, session); len(regs) != 1 { - t.Fatalf("converged store holds %d registers, want the session register only", len(regs)) + { + _, regs := storedSessionState(store, session) + testassert.Equal(t, len(regs), 1) } }) @@ -891,8 +913,9 @@ func TestWaitConvergence(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived script.releaseGate() // the completed output with one call dispatches the tool @@ -903,14 +926,13 @@ func TestWaitConvergence(t *testing.T) { cancel() close(toolGate) // the produced result publishes, the run interrupts - if err := <-waited; err != nil { - t.Fatalf("Wait: %v", err) + { + err := <-waited + testassert.NoError(t, err) } watch.next() rec := settledOperation(t, store, session, "op-1") - if rec.State.Status != OperationInterruption || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("converged operation = %+v, want interruption with the produced call settled", rec.State) - } + testassert.Require(t, rec.State.Status == OperationInterruption && len(rec.State.PendingToolCalls) == 0, "converged operation = %+v, want interruption with the produced call settled", rec.State) }) t.Run("the first storage failure that stopped admitted work is retained", func(t *testing.T) { @@ -939,22 +961,26 @@ func TestWaitConvergence(t *testing.T) { } } sessionA := createSession(t, h) - if _, err := submitText(t, h, sessionA, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, sessionA, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-failed // the admitted work stopped at the intent publication sessionB := createSession(t, h) - if _, err := submitText(t, h, sessionB, "op-1", MessageModeRegular, "hello"); err == nil { - t.Fatalf("later storage failure did not surface from Submit") + { + _, err := submitText(t, h, sessionB, "op-1", MessageModeRegular, "hello") + testassert.Require(t, err != nil, "later storage failure did not surface from Submit") } cancel() - if err := h.Wait(context.Background()); !errors.Is(err, firstErr) { - t.Fatalf("Wait = %v, want the first retained storage error (later: %v)", err, laterErr) + { + err := h.Wait(context.Background()) + testassert.Require(t, errors.Is(err, firstErr), "Wait = %v, want the first retained storage error (later: %v)", err, laterErr) } - if rec := settledOperation(t, store, sessionA, "op-1"); rec.State.Status != OperationRunning { - t.Fatalf("stopped operation = %q, want the last committed running state for recovery", rec.State.Status) + { + rec := settledOperation(t, store, sessionA, "op-1") + testassert.Equal(t, rec.State.Status, OperationRunning) } }) @@ -991,29 +1017,28 @@ func TestWaitConvergence(t *testing.T) { return nil } sessionA := createSession(t, h) - if _, err := submitText(t, h, sessionA, "op-a", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, sessionA, "op-a", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-closedA // the cleanup ran after the failed terminal-settlement attempt sessionB := createSession(t, h) - if _, err := submitText(t, h, sessionB, "op-b", MessageModeRegular, "hello"); err != nil { - t.Fatalf("second submit: %v", err) + { + _, err := submitText(t, h, sessionB, "op-b", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-closedB cancel() err := h.Wait(context.Background()) - if !errors.Is(err, firstErr) || !errors.Is(err, cleanupErr) { - t.Fatalf("Wait = %v, want the join of the retained storage failure and the retained cleanup failure", err) - } - if errors.Is(err, laterCleanupErr) { - t.Fatalf("Wait = %v, want the first retained cleanup failure not replaced by a later one", err) + testassert.Require(t, errors.Is(err, firstErr) && errors.Is(err, cleanupErr) && !errors.Is(err, laterCleanupErr), "Wait = %v, want the join of the retained storage failure and the retained cleanup failure; Wait = %v, want the first retained cleanup failure not replaced by a later one", err, err) + { + rec := settledOperation(t, store, sessionA, "op-a") + testassert.Equal(t, rec.State.Status, OperationRunning) } - if rec := settledOperation(t, store, sessionA, "op-a"); rec.State.Status != OperationRunning { - t.Fatalf("A stopped operation = %q, want the last committed running state for recovery", rec.State.Status) - } - if rec := settledOperation(t, store, sessionB, "op-b"); rec.State.Status != OperationSuccess { - t.Fatalf("B operation = %q, want the failed cleanup to leave the settled terminal unchanged", rec.State.Status) + { + rec := settledOperation(t, store, sessionB, "op-b") + testassert.Equal(t, rec.State.Status, OperationSuccess) } }) } @@ -1048,37 +1073,38 @@ func TestWaitDiscardsBuffersOfStuckSessions(t *testing.T) { } } - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-failed // the admitted work stopped; the Operation stays running for recovery c := leaseCoordinator(t, h, session) // The stuck Operation keeps the Session active: both submits buffer. - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "op-3", MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, h, session, "op-3", MessageModeQueued, "queued-1") + testassert.NoError(t, err) } c.mu.Lock() buffered := len(c.steering) + len(c.queued) c.mu.Unlock() - if buffered != 2 { - t.Fatalf("buffered items = %d, want both FIFOs present before cancellation", buffered) - } + testassert.Equal(t, buffered, 2) cancel() - if err := h.Wait(context.Background()); !errors.Is(err, firstErr) { - t.Fatalf("Wait = %v, want the retained storage error", err) + { + err := h.Wait(context.Background()) + testassert.Require(t, errors.Is(err, firstErr), "Wait = %v, want the retained storage error", err) } c.mu.Lock() left := len(c.steering) + len(c.queued) c.mu.Unlock() - if left != 0 { - t.Fatalf("%d buffered items survived cancellation + Wait, want both FIFOs cleared", left) - } - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationRunning { - t.Fatalf("stopped operation = %q, want the last committed running state for recovery", rec.State.Status) + testassert.Equal(t, left, 0) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationRunning) } } @@ -1095,31 +1121,38 @@ func TestHarnessLossDiscardsBuffers(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "op-3", MessageModeQueued, "q1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, h, session, "op-3", MessageModeQueued, "q1") + testassert.NoError(t, err) } cancel() // Harness loss - if _, err := submitText(t, h, session, "op-4", MessageModeRegular, "late"); !errors.Is(err, context.Canceled) { - t.Fatalf("submit after Harness loss = %v, want a context error", err) + { + _, err := submitText(t, h, session, "op-4", MessageModeRegular, "late") + testassert.Require(t, errors.Is(err, context.Canceled), "submit after Harness loss = %v, want a context error", err) } script.releaseGate() // the produced result publishes, the run interrupts, the buffers discard - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } watch.next() - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationInterruption { - t.Fatalf("converged operation = %q, want interruption", rec.State.Status) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationInterruption) } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello" { - t.Fatalf("committed inputs = %q, want both buffers discarded without delivery", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello") } } @@ -1139,8 +1172,9 @@ func TestSubmitCancellationGate(t *testing.T) { defer cancel() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the Session is active @@ -1162,18 +1196,14 @@ func TestSubmitCancellationGate(t *testing.T) { select { case err := <-errCh: - if !errors.Is(err, context.Canceled) { - t.Fatalf("gated submit = %v, want a context error", err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "gated submit = %v, want a context error", err) case res := <-done: t.Fatalf("gated submit returned %+v, want a context error", res) } c.mu.Lock() buffered := len(c.steering) + len(c.queued) c.mu.Unlock() - if buffered != 0 { - t.Fatalf("%d items buffered over a canceled context, want nothing published", buffered) - } + testassert.Equal(t, buffered, 0) script.releaseGate() }) @@ -1187,21 +1217,21 @@ func TestSubmitCancellationGate(t *testing.T) { defer cancel() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the Session is active submitCtx, cancelSubmit := context.WithCancel(context.Background()) res, err := h.Submit(submitCtx, SubmitRequest{SessionID: session, OperationID: "op-2", Origin: InputOriginUser, Content: admissionContent("late"), Mode: MessageModeRegular}) - if err != nil || res.Disposition != DispositionSteering { - t.Fatalf("steering submit = %+v err %v, want the enqueue to win", res, err) - } + testassert.Require(t, err == nil && res.Disposition == DispositionSteering, "steering submit = %+v err %v, want the enqueue to win", res, err) cancelSubmit() // later caller cancellation does not own the item script.releaseGate() <-script.arrived - if got := strings.Join(script.lastTexts(), "|"); !strings.Contains(got, "late") { - t.Fatalf("continuation projection = %q, want the item delivered despite the later cancellation", got) + { + got := strings.Join(script.lastTexts(), "|") + testassert.Require(t, strings.Contains(got, "late"), "continuation projection = %q, want the item delivered despite the later cancellation", got) } }) } @@ -1221,15 +1251,15 @@ func TestDrainRetiresItsOwnRunSlot(t *testing.T) { return validPrepared(), nil }) session, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if _, disposition := mustAdmitWithoutExecution(t, h, session.Identity.SessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + testassert.NoError(t, err) + { + _, disposition := mustAdmitWithoutExecution(t, h, session.Identity.SessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := leaseCoordinator(t, h, session.Identity.SessionID) - if _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationSuccess}); err != nil { - t.Fatalf("settle the admitted operation: %v", err) + { + _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationSuccess}) + testassert.NoError(t, err) } return h, c } @@ -1242,18 +1272,14 @@ func TestDrainRetiresItsOwnRunSlot(t *testing.T) { c.mu.Unlock() release, err := h.reserve(h.ctx, c) // direct drain tests own the reservation around the body - if err != nil { - t.Fatalf("drain reservation: %v", err) - } + testassert.NoError(t, err) h.drainBuffers(c, run) // synchronous: the empty FIFOs retire the slot in the scan's critical section release() c.mu.Lock() got := c.run c.mu.Unlock() - if got != nil { - t.Fatalf("empty drain left a run slot installed: %+v", got) - } + testassert.Require(t, got == nil, "empty drain left a run slot installed: %+v", got) }) t.Run("an empty drain never clears a replacement run", func(t *testing.T) { @@ -1265,18 +1291,14 @@ func TestDrainRetiresItsOwnRunSlot(t *testing.T) { c.mu.Unlock() release, err := h.reserve(h.ctx, c) - if err != nil { - t.Fatalf("drain reservation: %v", err) - } + testassert.NoError(t, err) h.drainBuffers(c, oldRun) release() c.mu.Lock() got := c.run c.mu.Unlock() - if got != replacement { - t.Fatalf("empty drain cleared a replacement run: got %+v", got) - } + testassert.Equal(t, got, replacement) }) } @@ -1306,24 +1328,28 @@ func TestSessionsExecuteConcurrently(t *testing.T) { sessionB := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, sessionA, "op-a", MessageModeRegular, "first"); err != nil { - t.Fatalf("first session submit: %v", err) + { + _, err := submitText(t, h, sessionA, "op-a", MessageModeRegular, "first") + testassert.NoError(t, err) } <-first.arrived // session A parked at its model boundary - if _, err := submitText(t, h, sessionB, "op-b", MessageModeRegular, "second"); err != nil { - t.Fatalf("second session submit: %v", err) + { + _, err := submitText(t, h, sessionB, "op-b", MessageModeRegular, "second") + testassert.NoError(t, err) } <-second.arrived // session B reached its model boundary while A stays parked watch.next() - if rec := settledOperation(t, store, sessionB, "op-b"); rec.State.Status != OperationSuccess { - t.Fatalf("second session settled %q while the first is parked, want concurrent progress", rec.State.Status) + { + rec := settledOperation(t, store, sessionB, "op-b") + testassert.Equal(t, rec.State.Status, OperationSuccess) } first.releaseGate() watch.next() - if rec := settledOperation(t, store, sessionA, "op-a"); rec.State.Status != OperationSuccess { - t.Fatalf("first session settled %q, want success after its release", rec.State.Status) + { + rec := settledOperation(t, store, sessionA, "op-a") + testassert.Equal(t, rec.State.Status, OperationSuccess) } } @@ -1335,19 +1361,14 @@ func TestSessionsExecuteConcurrently(t *testing.T) { func TestSnapshotCoordinatorsReleasesRegistryMutex(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) session, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) c := leaseCoordinator(t, h, session.Identity.SessionID) c.mu.Lock() // a coordinator mutex held across the snapshot call got := h.snapshotCoordinators() c.mu.Unlock() - if len(got) != 1 || got[0] != c { - t.Fatalf("snapshot = %d coordinators, want exactly the materialized one", len(got)) - } - if !h.mu.TryLock() { // the registry mutex is released once the snapshot returns - t.Fatalf("snapshotCoordinators retained the registry mutex") - } + testassert.Require(t, len(got) == 1 && got[0] == c, "snapshot = %d coordinators, want exactly the materialized one", len(got)) + // the registry mutex is released once the snapshot returns + testassert.Require(t, h.mu.TryLock(), "snapshotCoordinators retained the registry mutex") h.mu.Unlock() } diff --git a/harness/corruption_capture_test.go b/harness/corruption_capture_test.go index ecf0e206..ded0cecc 100644 --- a/harness/corruption_capture_test.go +++ b/harness/corruption_capture_test.go @@ -9,6 +9,8 @@ import ( "sync" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // The capture-availability contract regressions: the surviving reads must @@ -44,14 +46,14 @@ const captureBarrierWait = 5 * time.Second func corruptRegisterLifecyclePayload(t *testing.T, regPayload json.RawMessage) json.RawMessage { t.Helper() state, err := decodePayloadObject(mustState(t, regPayload)) - mustEncode(t, err) + testassert.NoError(t, err) state["lifecycle"] = json.RawMessage(`"stopping"`) patchedState, err := json.Marshal(state) - mustEncode(t, err) + testassert.NoError(t, err) obj := wireObject(t, regPayload) obj["state"] = patchedState patched, err := json.Marshal(obj) - mustEncode(t, err) + testassert.NoError(t, err) return patched } @@ -69,9 +71,7 @@ func foreignCorruptRegister(t *testing.T, store *graphStorage, sessionID string) _, err = tx.ReplaceRegister(key, reg.Revision, patched) return err }) - if err != nil { - t.Fatalf("foreign corrupt register write: %v", err) - } + testassert.NoError(t, err) } // warmCorruptFixture builds one warm Session whose cached graph carries a @@ -86,17 +86,20 @@ func warmCorruptFixture(t *testing.T) (*Harness, *graphStorage, *prepareStub, *c stub := newPrepareStub(validPrepared()) h := newTestHarness(t, store, stub.prepare) ctx := context.Background() - if _, err := h.ReadSessionHeader(ctx, testSessionID); err != nil { - t.Fatalf("warm ReadSessionHeader: %v", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.NoError(t, err) } sibling := createSession(t, h) c := cachedCoordinator(t, h, testSessionID) mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) - if _, err := h.commitEffectResult(ctx, c, testOpID, nil, modelResult{terminal: OperationSuccess}); err != nil { - t.Fatalf("terminal settlement: %v", err) + { + _, err := h.commitEffectResult(ctx, c, testOpID, nil, modelResult{terminal: OperationSuccess}) + testassert.NoError(t, err) } - if header, err := h.ReadSessionHeader(ctx, testSessionID); err != nil || header.CurrentOperationID != "" { - t.Fatalf("post-settlement header = %+v (%v), want an idle Session", header, err) + { + header, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.Require(t, err == nil && header.CurrentOperationID == "", "post-settlement header = %+v (%v), want an idle Session", header, err) } foreignCorruptRegister(t, store, testSessionID) return h, store, stub, c, sibling @@ -209,13 +212,9 @@ func forkMarker(ctx context.Context, h *Harness, req ForkRequest, out chan<- err // error of the marked Session, not captured state. func assertCaptureCorruption(t *testing.T, err error, sessionID, read string) { t.Helper() - if !isCorruption(err) { - t.Fatalf("%s after the mark = %v, want the typed corruption error", read, err) - } + testassert.Require(t, isCorruption(err), "%s after the mark = %v, want the typed corruption error", read, err) var corrupt *CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != sessionID { - t.Fatalf("%s error = %v, want the typed CorruptionError naming %q", read, err, sessionID) - } + testassert.Require(t, errors.As(err, &corrupt) && corrupt.SessionID == sessionID, "%s error = %v, want the typed CorruptionError naming %q", read, err, sessionID) } // runMarkWindow performs the shared deterministic window rendezvous for one @@ -275,15 +274,10 @@ func runMarkWindow(t *testing.T, ctx context.Context, h *Harness, stub *prepareS releaseGate() // the transaction fails on the corrupt register and the real mark lands, with no c.mu held marker, landed := awaitCorruptMark(h, c) - if !landed { - t.Fatalf("the real Fork transition's corruption marker did not land within %s", captureBarrierWait) - } + testassert.Require(t, landed, "the real Fork transition's corruption marker did not land within %s", captureBarrierWait) releaseReader() // the reader's real capture proceeds, strictly after the mark var corrupt *CorruptionError - if !errors.As(marker, &corrupt) || corrupt.SessionID != req.SourceSessionID || - !strings.Contains(corrupt.Detail, "session register") { - t.Fatalf("sticky marker = %v, want the Fork transition's register corruption naming %q", marker, req.SourceSessionID) - } + testassert.Require(t, errors.As(marker, &corrupt) && corrupt.SessionID == req.SourceSessionID && strings.Contains(corrupt.Detail, "session register"), "sticky marker = %v, want the Fork transition's register corruption naming %q", marker, req.SourceSessionID) return reader, forkDone } @@ -300,16 +294,8 @@ func TestCorruptMarkBetweenLookupAndCapture(t *testing.T) { h, _, stub, c, _ := warmCorruptFixture(t) view, err := h.ReadSessionView(ctx, testSessionID) - if err != nil { - t.Fatalf("pre-marker ReadSessionView: %v", err) - } - if len(view.Operations) != 1 || view.Operations[0].OperationID != testOpID || - view.Operations[0].Status != OperationSuccess { - t.Fatalf("pre-marker operations = %+v, want the settled %s", view.Operations, testOpID) - } - if view.History.ExecutionBusy { - t.Fatalf("pre-marker view = busy, want the idle post-settlement state") - } + testassert.NoError(t, err) + testassert.Require(t, len(view.Operations) == 1 && view.Operations[0].OperationID == testOpID && view.Operations[0].Status == OperationSuccess && !view.History.ExecutionBusy, "pre-marker operations = %+v, want the settled %s; pre-marker view = busy, want the idle post-settlement state", view.Operations, testOpID) // The captured copy is owned: mutating it cannot reach the coordinator. view.Operations[0].Status = OperationRunning facts := view.History.HistoryFacts(historyHeaderIDs(view.History.HistoryHeaders())) @@ -320,39 +306,23 @@ func TestCorruptMarkBetweenLookupAndCapture(t *testing.T) { fact.Input.Content[0].Text = "mutated" } } - if inputText == "" { - t.Fatal("pre-marker view carries no input fact to mutate") - } + testassert.Require(t, inputText != "", "%v", "pre-marker view carries no input fact to mutate") again, err := h.ReadSessionView(ctx, testSessionID) - if err != nil { - t.Fatalf("pre-marker reread: %v", err) - } + testassert.NoError(t, err) rereadFacts := again.History.HistoryFacts(historyHeaderIDs(again.History.HistoryHeaders())) for _, fact := range rereadFacts { - if fact.Input != nil && fact.Input.Content[0].Text == "mutated" { - t.Fatalf("a mutation of the returned view reached the coordinator: %+v", again) - } - } - if again.Operations[0].Status != OperationSuccess { - t.Fatalf("a mutation of the returned view reached the coordinator: %+v", again) + testassert.Require(t, fact.Input == nil || fact.Input.Content[0].Text != "mutated", "a mutation of the returned view reached the coordinator: %+v", again) } + testassert.Require(t, again.Operations[0].Status == OperationSuccess, "a mutation of the returned view reached the coordinator: %+v", again) // The same window pins the Operation record's own ownership. rec, err := h.ReadOperation(ctx, testSessionID, testOpID) - if err != nil { - t.Fatalf("pre-marker ReadOperation: %v", err) - } - if rec.Admission.OperationID != testOpID || rec.State.Status != OperationSuccess { - t.Fatalf("pre-marker record = %+v, want the settled %s", rec, testOpID) - } + testassert.NoError(t, err) + testassert.Require(t, rec.Admission.OperationID == testOpID && rec.State.Status == OperationSuccess, "pre-marker record = %+v, want the settled %s", rec, testOpID) rec.State.Status = OperationFailure reread, err := h.ReadOperation(ctx, testSessionID, testOpID) - if err != nil { - t.Fatalf("pre-marker reread: %v", err) - } - if reread.State.Status != OperationSuccess { - t.Fatalf("a mutation of the returned record reached the coordinator: %+v", reread) - } + testassert.NoError(t, err) + testassert.Require(t, reread.State.Status == OperationSuccess, "a mutation of the returned record reached the coordinator: %+v", reread) reader, forkDone := runMarkWindow(t, ctx, h, stub, c, ForkRequest{ SourceSessionID: testSessionID, @@ -363,13 +333,9 @@ func TestCorruptMarkBetweenLookupAndCapture(t *testing.T) { outcome := receiveBounded(t, reader, "the parked capture never completed") assertCaptureCorruption(t, outcome.err, testSessionID, "captureAvailable") - if outcome.callbackRan { - t.Fatal("the capture callback executed after the mark") - } + testassert.Require(t, !outcome.callbackRan, "%v", "the capture callback executed after the mark") forkErr := receiveBounded(t, forkDone, "Fork never completed") - if !isCorruption(forkErr) { - t.Fatalf("Fork = %v, want its own register-decode corruption", forkErr) - } + testassert.Require(t, isCorruption(forkErr), "Fork = %v, want its own register-decode corruption", forkErr) } // TestCaptureAvailabilityFamily pins the surrounding semantic rows of the @@ -381,20 +347,25 @@ func TestCaptureAvailabilityFamily(t *testing.T) { t.Run("gone session answers not-found from both reads", func(t *testing.T) { ctx := context.Background() h := newTestHarness(t, freshSessionStore(t), newPrepareStub(validPrepared()).prepare) - if _, err := h.ReadSessionHeader(ctx, testSessionID); err != nil { - t.Fatalf("warm ReadSessionHeader: %v", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.NoError(t, err) } - if _, err := h.ArchiveSession(ctx, testSessionID); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(ctx, testSessionID) + testassert.NoError(t, err) } - if err := h.DeleteSession(ctx, testSessionID); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := h.DeleteSession(ctx, testSessionID) + testassert.NoError(t, err) } - if _, err := h.ReadSessionView(ctx, testSessionID); !errors.Is(err, ErrNotFound) { - t.Fatalf("ReadSessionView after deletion = %v, want the not-found class", err) + { + _, err := h.ReadSessionView(ctx, testSessionID) + testassert.Require(t, errors.Is(err, ErrNotFound), "ReadSessionView after deletion = %v, want the not-found class", err) } - if _, err := h.ReadOperation(ctx, testSessionID, testOpID); !errors.Is(err, ErrNotFound) { - t.Fatalf("ReadOperation after deletion = %v, want the not-found class", err) + { + _, err := h.ReadOperation(ctx, testSessionID, testOpID) + testassert.Require(t, errors.Is(err, ErrNotFound), "ReadOperation after deletion = %v, want the not-found class", err) } }) @@ -410,27 +381,27 @@ func TestCaptureAvailabilityFamily(t *testing.T) { OperationID: "op-fork-sibling", Content: admissionContent("fork"), }, forkDone) - if err := receiveBounded(t, forkDone, "Fork never completed"); !isCorruption(err) { - t.Fatalf("Fork over the corrupt register = %v, want its register-decode corruption", err) + { + err := receiveBounded(t, forkDone, "Fork never completed") + testassert.Require(t, isCorruption(err), "Fork over the corrupt register = %v, want its register-decode corruption", err) } listed, err := h.ListSessions(ctx) - if err != nil { - t.Fatalf("ListSessions: %v", err) - } - if len(listed) != 1 || listed[0].Identity.SessionID != sibling { - t.Fatalf("ListSessions = %+v, want exactly the valid sibling %q", listed, sibling) - } - if _, err := h.ReadSessionView(ctx, sibling); err != nil { - t.Fatalf("sibling ReadSessionView = %v, want the valid read", err) + testassert.NoError(t, err) + testassert.Require(t, len(listed) == 1 && listed[0].Identity.SessionID == sibling, "ListSessions = %+v, want exactly the valid sibling %q", listed, sibling) + { + _, err := h.ReadSessionView(ctx, sibling) + testassert.NoError(t, err) } - if _, err := h.ReadSessionHeader(ctx, sibling); err != nil { - t.Fatalf("sibling ReadSessionHeader = %v, want the valid read", err) + { + _, err := h.ReadSessionHeader(ctx, sibling) + testassert.NoError(t, err) } // The sibling's own absent Operation answers its not-found class, not // the subject's corruption: the marker never crosses coordinators. - if _, err := h.ReadOperation(ctx, sibling, "op-absent"); !errors.Is(err, ErrNotFound) { - t.Fatalf("sibling ReadOperation = %v, want the sibling's own not-found class", err) + { + _, err := h.ReadOperation(ctx, sibling, "op-absent") + testassert.Require(t, errors.Is(err, ErrNotFound), "sibling ReadOperation = %v, want the sibling's own not-found class", err) } }) @@ -445,24 +416,30 @@ func TestCaptureAvailabilityFamily(t *testing.T) { OperationID: "op-fork-family", Content: admissionContent("fork"), }, forkDone) - if err := receiveBounded(t, forkDone, "Fork never completed"); !isCorruption(err) { - t.Fatalf("Fork over the corrupt register = %v, want its register-decode corruption", err) + { + err := receiveBounded(t, forkDone, "Fork never completed") + testassert.Require(t, isCorruption(err), "Fork over the corrupt register = %v, want its register-decode corruption", err) } - if _, err := h.ReadSessionView(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("fresh ReadSessionView = %v, want the corruption class", err) + { + _, err := h.ReadSessionView(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "fresh ReadSessionView = %v, want the corruption class", err) } - if _, err := h.ReadHistoryView(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("fresh ReadHistoryView = %v, want the corruption class", err) + { + _, err := h.ReadHistoryView(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "fresh ReadHistoryView = %v, want the corruption class", err) } - if _, err := h.ReadOperation(ctx, testSessionID, testOpID); !isCorruption(err) { - t.Fatalf("fresh ReadOperation = %v, want the corruption class", err) + { + _, err := h.ReadOperation(ctx, testSessionID, testOpID) + testassert.Require(t, isCorruption(err), "fresh ReadOperation = %v, want the corruption class", err) } - if _, err := h.ReadSessionHeader(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("ReadSessionHeader = %v, want the corruption class", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "ReadSessionHeader = %v, want the corruption class", err) } - if _, _, ok := h.ReadObservation(testSessionID); ok { - t.Fatal("ReadObservation reported a corrupt Session available") + { + _, _, ok := h.ReadObservation(testSessionID) + testassert.Require(t, !ok, "%v", "ReadObservation reported a corrupt Session available") } }) @@ -471,11 +448,13 @@ func TestCaptureAvailabilityFamily(t *testing.T) { _, store, _, _, _ := warmCorruptFixture(t) cold := newTestHarness(t, store, nil) - if _, err := cold.ReadSessionView(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("cold ReadSessionView = %v, want the corruption class", err) + { + _, err := cold.ReadSessionView(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "cold ReadSessionView = %v, want the corruption class", err) } - if _, err := cold.ReadOperation(ctx, testSessionID, testOpID); !isCorruption(err) { - t.Fatalf("cold ReadOperation = %v, want the corruption class", err) + { + _, err := cold.ReadOperation(ctx, testSessionID, testOpID) + testassert.Require(t, isCorruption(err), "cold ReadOperation = %v, want the corruption class", err) } }) } diff --git a/harness/data/LICENSE b/harness/data/LICENSE deleted file mode 100644 index 83ed1036..00000000 --- a/harness/data/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 OpenAI, Shantanu Jain - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/harness/data/cl100k_base.tiktoken b/harness/data/cl100k_base.tiktoken deleted file mode 100644 index f1f50811..00000000 --- a/harness/data/cl100k_base.tiktoken +++ /dev/null @@ -1,100256 +0,0 @@ -IQ== 0 -Ig== 1 -Iw== 2 -JA== 3 -JQ== 4 -Jg== 5 -Jw== 6 -KA== 7 -KQ== 8 -Kg== 9 -Kw== 10 -LA== 11 -LQ== 12 -Lg== 13 -Lw== 14 -MA== 15 -MQ== 16 -Mg== 17 -Mw== 18 -NA== 19 -NQ== 20 -Ng== 21 -Nw== 22 -OA== 23 -OQ== 24 -Og== 25 -Ow== 26 -PA== 27 -PQ== 28 -Pg== 29 -Pw== 30 -QA== 31 -QQ== 32 -Qg== 33 -Qw== 34 -RA== 35 -RQ== 36 -Rg== 37 -Rw== 38 -SA== 39 -SQ== 40 -Sg== 41 -Sw== 42 -TA== 43 -TQ== 44 -Tg== 45 -Tw== 46 -UA== 47 -UQ== 48 -Ug== 49 -Uw== 50 -VA== 51 -VQ== 52 -Vg== 53 -Vw== 54 -WA== 55 -WQ== 56 -Wg== 57 -Ww== 58 -XA== 59 -XQ== 60 -Xg== 61 -Xw== 62 -YA== 63 -YQ== 64 -Yg== 65 -Yw== 66 -ZA== 67 -ZQ== 68 -Zg== 69 -Zw== 70 -aA== 71 -aQ== 72 -ag== 73 -aw== 74 -bA== 75 -bQ== 76 -bg== 77 -bw== 78 -cA== 79 -cQ== 80 -cg== 81 -cw== 82 -dA== 83 -dQ== 84 -dg== 85 -dw== 86 -eA== 87 -eQ== 88 -eg== 89 -ew== 90 -fA== 91 -fQ== 92 -fg== 93 -oQ== 94 -og== 95 -ow== 96 -pA== 97 -pQ== 98 -pg== 99 -pw== 100 -qA== 101 -qQ== 102 -qg== 103 -qw== 104 -rA== 105 -rg== 106 -rw== 107 -sA== 108 -sQ== 109 -sg== 110 -sw== 111 -tA== 112 -tQ== 113 -tg== 114 -tw== 115 -uA== 116 -uQ== 117 -ug== 118 -uw== 119 -vA== 120 -vQ== 121 -vg== 122 -vw== 123 -wA== 124 -wQ== 125 -wg== 126 -ww== 127 -xA== 128 -xQ== 129 -xg== 130 -xw== 131 -yA== 132 -yQ== 133 -yg== 134 -yw== 135 -zA== 136 -zQ== 137 -zg== 138 -zw== 139 -0A== 140 -0Q== 141 -0g== 142 -0w== 143 -1A== 144 -1Q== 145 -1g== 146 -1w== 147 -2A== 148 -2Q== 149 -2g== 150 -2w== 151 -3A== 152 -3Q== 153 -3g== 154 -3w== 155 -4A== 156 -4Q== 157 -4g== 158 -4w== 159 -5A== 160 -5Q== 161 -5g== 162 -5w== 163 -6A== 164 -6Q== 165 -6g== 166 -6w== 167 -7A== 168 -7Q== 169 -7g== 170 -7w== 171 -8A== 172 -8Q== 173 -8g== 174 -8w== 175 -9A== 176 -9Q== 177 -9g== 178 -9w== 179 -+A== 180 -+Q== 181 -+g== 182 -+w== 183 -/A== 184 -/Q== 185 -/g== 186 -/w== 187 -AA== 188 -AQ== 189 -Ag== 190 -Aw== 191 -BA== 192 -BQ== 193 -Bg== 194 -Bw== 195 -CA== 196 -CQ== 197 -Cg== 198 -Cw== 199 -DA== 200 -DQ== 201 -Dg== 202 -Dw== 203 -EA== 204 -EQ== 205 -Eg== 206 -Ew== 207 -FA== 208 -FQ== 209 -Fg== 210 -Fw== 211 -GA== 212 -GQ== 213 -Gg== 214 -Gw== 215 -HA== 216 -HQ== 217 -Hg== 218 -Hw== 219 -IA== 220 -fw== 221 -gA== 222 -gQ== 223 -gg== 224 -gw== 225 -hA== 226 -hQ== 227 -hg== 228 -hw== 229 -iA== 230 -iQ== 231 -ig== 232 -iw== 233 -jA== 234 -jQ== 235 -jg== 236 -jw== 237 -kA== 238 -kQ== 239 -kg== 240 -kw== 241 -lA== 242 -lQ== 243 -lg== 244 -lw== 245 -mA== 246 -mQ== 247 -mg== 248 -mw== 249 -nA== 250 -nQ== 251 -ng== 252 -nw== 253 -oA== 254 -rQ== 255 -ICA= 256 -ICAgIA== 257 -aW4= 258 -IHQ= 259 -ICAgICAgICA= 260 -ZXI= 261 -ICAg 262 -b24= 263 -IGE= 264 -cmU= 265 -YXQ= 266 -c3Q= 267 -ZW4= 268 -b3I= 269 -IHRo 270 -Cgo= 271 -IGM= 272 -bGU= 273 -IHM= 274 -aXQ= 275 -YW4= 276 -YXI= 277 -YWw= 278 -IHRoZQ== 279 -Owo= 280 -IHA= 281 -IGY= 282 -b3U= 283 -ID0= 284 -aXM= 285 -ICAgICAgIA== 286 -aW5n 287 -ZXM= 288 -IHc= 289 -aW9u 290 -ZWQ= 291 -aWM= 292 -IGI= 293 -IGQ= 294 -ZXQ= 295 -IG0= 296 -IG8= 297 -CQk= 298 -cm8= 299 -YXM= 300 -ZWw= 301 -Y3Q= 302 -bmQ= 303 -IGlu 304 -IGg= 305 -ZW50 306 -aWQ= 307 -IG4= 308 -YW0= 309 -ICAgICAgICAgICA= 310 -IHRv 311 -IHJl 312 -LS0= 313 -IHs= 314 -IG9m 315 -b20= 316 -KTsK 317 -aW0= 318 -DQo= 319 -ICg= 320 -aWw= 321 -Ly8= 322 -IGFuZA== 323 -dXI= 324 -c2U= 325 -IGw= 326 -ZXg= 327 -IFM= 328 -YWQ= 329 -ICI= 330 -Y2g= 331 -dXQ= 332 -aWY= 333 -Kio= 334 -IH0= 335 -ZW0= 336 -b2w= 337 -ICAgICAgICAgICAgICAgIA== 338 -dGg= 339 -KQo= 340 -IHsK 341 -IGc= 342 -aWc= 343 -aXY= 344 -LAo= 345 -Y2U= 346 -b2Q= 347 -IHY= 348 -YXRl 349 -IFQ= 350 -YWc= 351 -YXk= 352 -ICo= 353 -b3Q= 354 -dXM= 355 -IEM= 356 -IHN0 357 -IEk= 358 -dW4= 359 -dWw= 360 -dWU= 361 -IEE= 362 -b3c= 363 -ICc= 364 -ZXc= 365 -IDw= 366 -YXRpb24= 367 -KCk= 368 -IGZvcg== 369 -YWI= 370 -b3J0 371 -dW0= 372 -YW1l 373 -IGlz 374 -cGU= 375 -dHI= 376 -Y2s= 377 -4oA= 378 -IHk= 379 -aXN0 380 -LS0tLQ== 381 -LgoK 382 -aGU= 383 -IGU= 384 -bG8= 385 -IE0= 386 -IGJl 387 -ZXJz 388 -IG9u 389 -IGNvbg== 390 -YXA= 391 -dWI= 392 -IFA= 393 -ICAgICAgICAgICAgICAg 394 -YXNz 395 -aW50 396 -Pgo= 397 -bHk= 398 -dXJu 399 -ICQ= 400 -OwoK 401 -YXY= 402 -cG9ydA== 403 -aXI= 404 -LT4= 405 -bnQ= 406 -Y3Rpb24= 407 -ZW5k 408 -IGRl 409 -MDA= 410 -aXRo 411 -b3V0 412 -dHVybg== 413 -b3Vy 414 -ICAgICA= 415 -bGlj 416 -cmVz 417 -cHQ= 418 -PT0= 419 -IHRoaXM= 420 -IHdo 421 -IGlm 422 -IEQ= 423 -dmVy 424 -YWdl 425 -IEI= 426 -aHQ= 427 -ZXh0 428 -PSI= 429 -IHRoYXQ= 430 -KioqKg== 431 -IFI= 432 -IGl0 433 -ZXNz 434 -IEY= 435 -IHI= 436 -b3M= 437 -YW5k 438 -IGFz 439 -ZWN0 440 -a2U= 441 -cm9t 442 -IC8v 443 -Y29u 444 -IEw= 445 -KCI= 446 -cXU= 447 -bGFzcw== 448 -IHdpdGg= 449 -aXo= 450 -ZGU= 451 -IE4= 452 -IGFs 453 -b3A= 454 -dXA= 455 -Z2V0 456 -IH0K 457 -aWxl 458 -IGFu 459 -YXRh 460 -b3Jl 461 -cmk= 462 -IHBybw== 463 -Ow0K 464 -CQkJCQ== 465 -dGVy 466 -YWlu 467 -IFc= 468 -IEU= 469 -IGNvbQ== 470 -IHJldHVybg== 471 -YXJ0 472 -IEg= 473 -YWNr 474 -aW1wb3J0 475 -dWJsaWM= 476 -IG9y 477 -ZXN0 478 -bWVudA== 479 -IEc= 480 -YWJsZQ== 481 -IC0= 482 -aW5l 483 -aWxs 484 -aW5k 485 -ZXJl 486 -Ojo= 487 -aXR5 488 -ICs= 489 -IHRy 490 -ZWxm 491 -aWdodA== 492 -KCc= 493 -b3Jt 494 -dWx0 495 -c3Ry 496 -Li4= 497 -Iiw= 498 -IHlvdQ== 499 -eXBl 500 -cGw= 501 -IG5ldw== 502 -IGo= 503 -ICAgICAgICAgICAgICAgICAgIA== 504 -IGZyb20= 505 -IGV4 506 -IE8= 507 -MjA= 508 -bGQ= 509 -IFs= 510 -b2M= 511 -Ogo= 512 -IHNl 513 -IGxl 514 -LS0tLS0tLS0= 515 -LnM= 516 -ewo= 517 -Jyw= 518 -YW50 519 -IGF0 520 -YXNl 521 -LmM= 522 -IGNo 523 -PC8= 524 -YXZl 525 -YW5n 526 -IGFyZQ== 527 -IGludA== 528 -4oCZ 529 -X3Q= 530 -ZXJ0 531 -aWFs 532 -YWN0 533 -fQo= 534 -aXZl 535 -b2Rl 536 -b3N0 537 -IGNsYXNz 538 -IG5vdA== 539 -b2c= 540 -b3Jk 541 -YWx1ZQ== 542 -YWxs 543 -ZmY= 544 -KCk7Cg== 545 -b250 546 -aW1l 547 -YXJl 548 -IFU= 549 -IHBy 550 -IDo= 551 -aWVz 552 -aXpl 553 -dXJl 554 -IGJ5 555 -aXJl 556 -IH0KCg== 557 -LnA= 558 -IHNo 559 -aWNl 560 -YXN0 561 -cHRpb24= 562 -dHJpbmc= 563 -b2s= 564 -X18= 565 -Y2w= 566 -IyM= 567 -IGhl 568 -YXJk 569 -KS4= 570 -IEA= 571 -aWV3 572 -CQkJ 573 -IHdhcw== 574 -aXA= 575 -dGhpcw== 576 -IHU= 577 -IFRoZQ== 578 -aWRl 579 -YWNl 580 -aWI= 581 -YWM= 582 -cm91 583 -IHdl 584 -amVjdA== 585 -IHB1YmxpYw== 586 -YWs= 587 -dmU= 588 -YXRo 589 -b2lk 590 -ID0+ 591 -dXN0 592 -cXVl 593 -IHJlcw== 594 -KSk= 595 -J3M= 596 -IGs= 597 -YW5z 598 -eXN0 599 -dW5jdGlvbg== 600 -KioqKioqKio= 601 -IGk= 602 -IHVz 603 -cHA= 604 -MTA= 605 -b25l 606 -YWls 607 -PT09PQ== 608 -bmFtZQ== 609 -IHN0cg== 610 -IC8= 611 -ICY= 612 -YWNo 613 -ZGl2 614 -eXN0ZW0= 615 -ZWxs 616 -IGhhdmU= 617 -ZXJy 618 -b3VsZA== 619 -dWxs 620 -cG9u 621 -IEo= 622 -X3A= 623 -ID09 624 -aWdu 625 -U3Q= 626 -Lgo= 627 -IHBs 628 -KTsKCg== 629 -Zm9ybQ== 630 -cHV0 631 -b3VudA== 632 -fQoK 633 -ZGQ= 634 -aXRl 635 -IGdldA== 636 -cnI= 637 -b21l 638 -IOKA 639 -YXJhbQ== 640 -Y2M= 641 -ICov 642 -RVI= 643 -SW4= 644 -bGVz 645 -X3M= 646 -b25n 647 -aWU= 648 -IGNhbg== 649 -IFY= 650 -ZXJ2 651 -cHI= 652 -IHVu 653 -cm93 654 -YmVy 655 -IGRv 656 -bGw= 657 -IGVs 658 -IHNlbGY= 659 -YXRlZA== 660 -YXJ5 661 -IC4= 662 -J10= 663 -dWQ= 664 -IGVu 665 -IFRo 666 -ICAgICAgICAgICAgICAgICAgICAgICA= 667 -dGU= 668 -X2M= 669 -dWN0 670 -IGFi 671 -b3Jr 672 -LmdldA== 673 -ICM= 674 -YXc= 675 -cmVzcw== 676 -b2I= 677 -TmFtZQ== 678 -MjAx 679 -YXBw 680 -Wyc= 681 -IGFsbA== 682 -b3J5 683 -aXRpb24= 684 -YW5jZQ== 685 -ZWFy 686 -IGNvbnQ= 687 -dmVudA== 688 -aWE= 689 -IHdpbGw= 690 -SU4= 691 -ICAgICAgICAg 692 -cmV0dXJu 693 -IDwv 694 -ZGF0YQ== 695 -KQoK 696 -UmU= 697 -cGxl 698 -aWxk 699 -dGhlcg== 700 -IHlvdXI= 701 -Igo= 702 -KCQ= 703 -IG91dA== 704 -KSw= 705 -IGhhcw== 706 -U3RyaW5n 707 -c28= 708 -IHVw 709 -YXg= 710 -IGRlZg== 711 -IGJv 712 -Z2U= 713 -YWxzZQ== 714 -T04= 715 -cGVy 716 -MTI= 717 -aWNo 718 -IGJ1dA== 719 -IAo= 720 -IF8= 721 -X20= 722 -YWRk 723 -cXVlc3Q= 724 -b2RlbA== 725 -c2VsZg== 726 -ZXJ5 727 -ZnQ= 728 -ZW5z 729 -Ly8vLw== 730 -YWtl 731 -LkM= 732 -IGdv 733 -IGZ1bmN0aW9u 734 -IEs= 735 -aXZhdGU= 736 -IGlt 737 -IGNvbnN0 738 -LnQ= 739 -ICovCg== 740 -KTsNCg== 741 -IHZvaWQ= 742 -IHNldA== 743 -IFN5c3RlbQ== 744 -Y3Jp 745 -KCkK 746 -bGk= 747 -CWlm 748 -Lm0= 749 -YWxseQ== 750 -c2V0 751 -ZXA= 752 -4oCZcw== 753 -Ym8= 754 -ZGVm 755 -JywK 756 -IG1l 757 -ICE= 758 -YXRjaA== 759 -Ij4= 760 -IiwK 761 -ZWM= 762 -IElu 763 -cGg= 764 -IHw= 765 -X2Y= 766 -IHZhcg== 767 -ZW5jZQ== 768 -SWQ= 769 -cmVl 770 -aW5r 771 -bGVjdA== 772 -dWc= 773 -ZXRo 774 -IGVsc2U= 775 -LS0tLS0tLS0tLS0tLS0tLQ== 776 -MTk= 777 -Y29udA== 778 -IHNv 779 -YXRpYw== 780 -IGxv 781 -cHJv 782 -dG9u 783 -c3M= 784 -b3du 785 -YWJlbA== 786 -b2ludA== 787 -b3Vz 788 -ZWxk 789 -U1Q= 790 -VGhl 791 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 792 -UkU= 793 -Ijo= 794 -b2xvcg== 795 -dHA= 796 -ZWc= 797 -a2V5 798 -dWRl 799 -IFN0 800 -b3VuZA== 801 -IGFy 802 -Iik7Cg== 803 -ZW5lcg== 804 -c2Vy 805 -MTE= 806 -YmplY3Q= 807 -ZXNzYWdl 808 -ZmVy 809 -IG1vcmU= 810 -YXRpb25z 811 -ZW50cw== 812 -IGhpcw== 813 -IHRoZXk= 814 -LlM= 815 -IFk= 816 -dXNl 817 -bmU= 818 -aXNo 819 -b2xk 820 -X2Q= 821 -aW8= 822 -aWVsZA== 823 -IHBlcg== 824 -Q29udA== 825 -aW5ncw== 826 -IyMjIw== 827 -IGRhdGE= 828 -IHNh 829 -ZWY= 830 -Zm8= 831 -IG9uZQ== 832 -ZW5n 833 -IGRpcw== 834 -QVQ= 835 -IG5hbWU= 836 -IHRydWU= 837 -dmFs 838 -bGVk 839 -LmY= 840 -IG5l 841 -IGVuZA== 842 -MzI= 843 -LlQ= 844 -MTY= 845 -Y3Jl 846 -YXJr 847 -bG9n 848 -RXg= 849 -ZXJyb3I= 850 -X2lk 851 -dXJyZQ== 852 -YW5nZQ== 853 -IG51bGw= 854 -cnJheQ== 855 -IG15 856 -cGFu 857 -aWN0 858 -YXRvcg== 859 -Vmlldw== 860 -TGlzdA== 861 -CXJldHVybg== 862 -4oCd 863 -IHByZQ== 864 -IHg= 865 -Y2x1ZGU= 866 -YXJn 867 -MTU= 868 -b3Y= 869 -Lmg= 870 -ID4= 871 -IHRoZWly 872 -Jyk= 873 -aXJzdA== 874 -aWNr 875 -Z2g= 876 -TEU= 877 -T1I= 878 -IHByaXZhdGU= 879 -dGVt 880 -DQoNCg== 881 -dXNlcg== 882 -ICk= 883 -Y29t 884 -LkE= 885 -IjsK 886 -IGlk 887 -cmVhZA== 888 -IHdobw== 889 -X2I= 890 -Ij4K 891 -IHRpbWU= 892 -IG1hbg== 893 -cnk= 894 -PT09PT09PT0= 895 -cm91cA== 896 -cm9w 897 -cHVibGlj 898 -dmVs 899 -dW1iZXI= 900 -Ymxl 901 -IHdoaWNo 902 -KioqKioqKioqKioqKioqKg== 903 -IGFueQ== 904 -IGZhbHNl 905 -d2U= 906 -IHZhbHVl 907 -IGxp 908 -Iik= 909 -bmRlcg== 910 -Z3I= 911 -IG5v 912 -cGFyYW0= 913 -MjU= 914 -Zmln 915 -LmNvbQ== 916 -IGFwcA== 917 -X2w= 918 -aW9ucw== 919 -LkQ= 920 -IENo 921 -IGFib3V0 922 -IGFkZA== 923 -IHN1 924 -IHN0cmluZw== 925 -SUQ= 926 -IG92ZXI= 927 -c3RyaW5n 928 -Lmw= 929 -b3VyY2U= 930 -MDAw 931 -X0M= 932 -XQo= 933 -IHF1 934 -IFN0cmluZw== 935 -Y2E= 936 -U0U= 937 -IHJv 938 -c2g= 939 -dWFs 940 -VHlwZQ== 941 -c29u 942 -bmV3 943 -ZXJu 944 -IGFn 945 -QVI= 946 -XTsK 947 -XS4= 948 -ID8= 949 -aWNhbA== 950 -IGRlcw== 951 -dXRo 952 -aXg= 953 -YXlz 954 -IHR5cGU= 955 -J3Q= 956 -YXVsdA== 957 -IGludGVy 958 -dmFy 959 -LmI= 960 -IHBhcnQ= 961 -LmQ= 962 -dXJyZW50 963 -SVQ= 964 -RU4= 965 -MzA= 966 -ZW5j 967 -KGY= 968 -cmE= 969 -dmFsdWU= 970 -Y2hv 971 -MTg= 972 -dXR0b24= 973 -b3Nl 974 -MTQ= 975 -ICE9 976 -YXRlcg== 977 -w6k= 978 -cmVhdGU= 979 -b2xs 980 -cG9z 981 -eWxl 982 -bmc= 983 -QUw= 984 -dXNpbmc= 985 -YW1lcw== 986 -IHsNCg== 987 -YXRlcw== 988 -ZWx5 989 -IHdvcms= 990 -IGVt 991 -aW5hbA== 992 -IHNw 993 -IHdoZW4= 994 -LnNldA== 995 -ICAgICAg 996 -KToK 997 -dG8= 998 -cXVpcmU= 999 -aW5kb3c= 1000 -bGVtZW50 1001 -cGVjdA== 1002 -YXNo 1003 -W2k= 1004 -IHVzZQ== 1005 -LkY= 1006 -cGVj 1007 -IGFk 1008 -b3Zl 1009 -Y2VwdGlvbg== 1010 -ZW5ndGg= 1011 -aW5jbHVkZQ== 1012 -YWRlcg== 1013 -ICAgICAgICAgICAgICAgICAgICAgICAgICAg 1014 -YXR1cw== 1015 -VGg= 1016 -aXRsZQ== 1017 -cml0 1018 -dm9pZA== 1019 -KCku 1020 -KAo= 1021 -IG9mZg== 1022 -IG90aGVy 1023 -ICYm 1024 -JzsK 1025 -bXM= 1026 -IGJlZW4= 1027 -IHRl 1028 -bWw= 1029 -Y28= 1030 -bmM= 1031 -MTM= 1032 -ZXJ2aWNl 1033 -ICU= 1034 -KioK 1035 -YW5u 1036 -YWRl 1037 -CgoKCg== 1038 -bG9jaw== 1039 -Y29uc3Q= 1040 -MTAw 1041 -cG9uc2U= 1042 -IHN1cA== 1043 -Kys= 1044 -ZGF0ZQ== 1045 -IGFjYw== 1046 -IGhhZA== 1047 -IGJ1 1048 -MjAw 1049 -IFJl 1050 -IHdlcmU= 1051 -IGZpbGU= 1052 -IHdvdWxk 1053 -IOKAnA== 1054 -dmVu 1055 -aXNz 1056 -IG91cg== 1057 -Y2xhc3M= 1058 -cmF3 1059 -IHllYXI= 1060 -RGF0YQ== 1061 -IHZhbA== 1062 -IHNvbWU= 1063 -ZnRlcg== 1064 -eXM= 1065 -IC8vLw== 1066 -cm91bmQ= 1067 -dmlldw== 1068 -IHBl 1069 -IHRoZXJl 1070 -IHNhaWQ= 1071 -ZHU= 1072 -b2Y= 1073 -bGluZQ== 1074 -Lyo= 1075 -ZHVjdA== 1076 -IGhlcg== 1077 -ICAgICAgICAgICAgIA== 1078 -UmVz 1079 -IGNv 1080 -IGNvbW0= 1081 -aXNl 1082 -bWlu 1083 -ICAgIAo= 1084 -I2luY2x1ZGU= 1085 -ZXRob2Q= 1086 -LlA= 1087 -dXRl 1088 -IGFzcw== 1089 -SW50 1090 -YXNr 1091 -bG9j 1092 -IGxpa2U= 1093 -b2R5 1094 -IGxldA== 1095 -bG9hZA== 1096 -IGFt 1097 -cm9s 1098 -IGdy 1099 -eXA= 1100 -IGFsc28= 1101 -IEl0 1102 -dXJs 1103 -aWZpYw== 1104 -b3Jz 1105 -X1A= 1106 -X24= 1107 -aWdo 1108 -IHRoYW4= 1109 -Q29t 1110 -QU4= 1111 -VUw= 1112 -YXRpbmc= 1113 -MTc= 1114 -IFRoaXM= 1115 -cmVm 1116 -X1M= 1117 -IHN0YXRpYw== 1118 -cm9sbA== 1119 -IGp1c3Q= 1120 -IHJlc3VsdA== 1121 -aWFu 1122 -aWR0aA== 1123 -IHRoZW0= 1124 -KSk7Cg== 1125 -ZGVy 1126 -cmVhaw== 1127 -Q29u 1128 -Oi8v 1129 -dWxl 1130 -Li4u 1131 -YXJjaA== 1132 -ZW1lbnQ= 1133 -IDw8 1134 -NTA= 1135 -dXNo 1136 -ZW5zZQ== 1137 -YXJy 1138 -IGludG8= 1139 -Y2Vzcw== 1140 -YW1w 1141 -aWVk 1142 -dW1lbnQ= 1143 -IFw= 1144 -XSw= 1145 -d28= 1146 -YWxz 1147 -IHdoYXQ= 1148 -YW5j 1149 -VmFsdWU= 1150 -PSc= 1151 -b2x1bQ== 1152 -IHBvcw== 1153 -YWdlcw== 1154 -YXllcg== 1155 -IHNj 1156 -dWVz 1157 -IikK 1158 -X1Q= 1159 -IGxpc3Q= 1160 -KHM= 1161 -IGNhc2U= 1162 -Q2g= 1163 -CQkJCQk= 1164 -Ly8vLy8vLy8= 1165 -cG9uZW50 1166 -IHo= 1167 -IGtu 1168 -bGV0 1169 -REU= 1170 -cmVk 1171 -IGZl 1172 -IH0sCg== 1173 -ICw= 1174 -KHQ= 1175 -IGZpcnN0 1176 -Jyk7Cg== 1177 -d29yZA== 1178 -IGltcG9ydA== 1179 -IGFjdA== 1180 -IGNoYXI= 1181 -Q1Q= 1182 -IFRy 1183 -b3BsZQ== 1184 -PXs= 1185 -CWY= 1186 -MjQ= 1187 -aWVudA== 1188 -Y2VudA== 1189 -Lmo= 1190 -bGVjdGlvbg== 1191 -KSkK 1192 -IG9ubHk= 1193 -IHByaW50 1194 -bWVy 1195 -Llc= 1196 -b2Nr 1197 -IC0t 1198 -VGV4dA== 1199 -IG9w 1200 -YW5r 1201 -IGl0cw== 1202 -IGJhY2s= 1203 -WyI= 1204 -IG5lZWQ= 1205 -IGNs 1206 -IHN1Yg== 1207 -IGxh 1208 -KCg= 1209 -LiI= 1210 -T2JqZWN0 1211 -IHN0YXJ0 1212 -ZmlsZQ== 1213 -KHNlbGY= 1214 -bmVy 1215 -ZXk= 1216 -IHVzZXI= 1217 -IGVudA== 1218 -IENvbQ== 1219 -aXRz 1220 -IENvbg== 1221 -b3VibGU= 1222 -b3dlcg== 1223 -aXRlbQ== 1224 -dmVyeQ== 1225 -IFdl 1226 -NjQ= 1227 -bGljaw== 1228 -IFE= 1229 -cGhw 1230 -dHRw 1231 -Jzo= 1232 -aWNz 1233 -IHVuZGVy 1234 -ICoK 1235 -Lkw= 1236 -KTs= 1237 -aWNlcw== 1238 -IHJlZw== 1239 -KQ0K 1240 -CXB1YmxpYw== 1241 -U1M= 1242 -IHRoZW4= 1243 -cmVhdA== 1244 -aW91cw== 1245 -Lkc= 1246 -ZWs= 1247 -aXJlY3Q= 1248 -aGVjaw== 1249 -Y3JpcHQ= 1250 -bmluZw== 1251 -IFVu 1252 -IG1heQ== 1253 -IFdo 1254 -Qm8= 1255 -SXRlbQ== 1256 -c3RydWN0 1257 -LnN0 1258 -cmVhbQ== 1259 -aWJsZQ== 1260 -bG9hdA== 1261 -IG9yZw== 1262 -dW5k 1263 -c3Vt 1264 -X2lu 1265 -Li4v 1266 -X00= 1267 -IGhvdw== 1268 -cml0ZQ== 1269 -Jwo= 1270 -VG8= 1271 -NDA= 1272 -d3c= 1273 -IHBlb3BsZQ== 1274 -aW5kZXg= 1275 -Lm4= 1276 -aHR0cA== 1277 -KG0= 1278 -ZWN0b3I= 1279 -IGluZA== 1280 -IGphdg== 1281 -XSwK 1282 -IEhl 1283 -X3N0 1284 -ZnVs 1285 -b2xl 1286 -KXsK 1287 -IHNob3VsZA== 1288 -b3B5 1289 -ZWxw 1290 -aWVy 1291 -X25hbWU= 1292 -ZXJzb24= 1293 -SU9O 1294 -b3Rl 1295 -IHRlc3Q= 1296 -IGJldA== 1297 -cnJvcg== 1298 -dWxhcg== 1299 -44A= 1300 -INA= 1301 -YnM= 1302 -dGluZw== 1303 -IG1ha2U= 1304 -VHI= 1305 -IGFmdGVy 1306 -YXJnZXQ= 1307 -Uk8= 1308 -b2x1bW4= 1309 -cmM= 1310 -X3Jl 1311 -ZGVmaW5l 1312 -MjI= 1313 -IHJpZ2h0 1314 -cmlnaHQ= 1315 -ZGF5 1316 -IGxvbmc= 1317 -W10= 1318 -KHA= 1319 -dGQ= 1320 -Y29uZA== 1321 -IFBybw== 1322 -IHJlbQ== 1323 -cHRpb25z 1324 -dmlk 1325 -Lmc= 1326 -IGV4dA== 1327 -IF9f 1328 -JykK 1329 -cGFjZQ== 1330 -bXA= 1331 -IG1pbg== 1332 -c3RhbmNl 1333 -YWly 1334 -YWN0aW9u 1335 -d2g= 1336 -dHlwZQ== 1337 -dXRpbA== 1338 -YWl0 1339 -PD8= 1340 -SUM= 1341 -dGV4dA== 1342 -IHBo 1343 -IGZs 1344 -Lk0= 1345 -Y2Nlc3M= 1346 -YnI= 1347 -Zm9yZQ== 1348 -ZXJzaW9u 1349 -KSwK 1350 -LnJl 1351 -YXRlZw== 1352 -IGxvYw== 1353 -aW5z 1354 -LXM= 1355 -dHJpYg== 1356 -IEludA== 1357 -IGFycmF5 1358 -LCI= 1359 -UHJv 1360 -KGM= 1361 -ZXNzaW9u 1362 -PgoK 1363 -IHNoZQ== 1364 -Il0= 1365 -YXBo 1366 -IGV4cA== 1367 -ZXJ0eQ== 1368 -IFNl 1369 -IHBhcg== 1370 -dW5j 1371 -RVQ= 1372 -IHJlYWQ= 1373 -cHJpbnQ= 1374 -IHJlbA== 1375 -IGZvcm0= 1376 -IGRy 1377 -RXhjZXB0aW9u 1378 -aW5wdXQ= 1379 -IHRyYW5z 1380 -IyMjIyMjIyM= 1381 -b3JkZXI= 1382 -Qnk= 1383 -IGF3 1384 -aXRpZXM= 1385 -dWZm 1386 -cGxheQ== 1387 -LmFkZA== 1388 -IOKAkw== 1389 -IHdhbnQ= 1390 -IGNvbXA= 1391 -bWVudHM= 1392 -IHx8 1393 -YXo= 1394 -YmU= 1395 -IG51bWJlcg== 1396 -IHJlcXVpcmU= 1397 -IEV4 1398 -NjA= 1399 -IGNvbA== 1400 -IGtleQ== 1401 -ZW1iZXI= 1402 -IHR3bw== 1403 -IHNpemU= 1404 -IHdoZXJl 1405 -VVQ= 1406 -cmVzdWx0 1407 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 1408 -b3VnaA== 1409 -b3JsZA== 1410 -b29k 1411 -dWNo 1412 -YXRpdmU= 1413 -Z2Vy 1414 -YXJlbnQ= 1415 -IC8q 1416 -IGFyZw== 1417 -IHdoaWxl 1418 -MjM= 1419 -KHRoaXM= 1420 -IHJlYw== 1421 -IGRpZg== 1422 -U3RhdGU= 1423 -IHNwZWM= 1424 -cmlkZQ== 1425 -X0Y= 1426 -IGxvb2s= 1427 -QU0= 1428 -aWxpdHk= 1429 -ZXRlcg== 1430 -4oCZdA== 1431 -CgoK 1432 -YXlvdXQ= 1433 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 1434 -YWdlcg== 1435 -IGNvdWxk 1436 -IGJy 1437 -ZW5kcw== 1438 -dXJlcw== 1439 -IGtub3c= 1440 -ZXRz 1441 -IElm 1442 -IFNo 1443 -Lnc= 1444 -YmFjaw== 1445 -IHNlcg== 1446 -ICs9 1447 -IGZy 1448 -KCkpOwo= 1449 -IGhhbmQ= 1450 -SW5k 1451 -VUxM 1452 -SW0= 1453 -KCk7Cgo= 1454 -IG1vc3Q= 1455 -IHRyeQ== 1456 -IG5vdw== 1457 -cm91Z2g= 1458 -Pg0K 1459 -YWNrYWdl 1460 -IGhpbQ== 1461 -Ll8= 1462 -aWZ5 1463 -IGJyZWFr 1464 -ICk7Cg== 1465 -cmVu 1466 -I2RlZmluZQ== 1467 -aXR0 1468 -IGFw 1469 -CWM= 1470 -KG4= 1471 -IFlvdQ== 1472 -OgoK 1473 -LW0= 1474 -IGV2ZXJ5 1475 -dXN0b20= 1476 -bGllbnQ= 1477 -b2N1bWVudA== 1478 -Y3JpcHRpb24= 1479 -RXJyb3I= 1480 -LWI= 1481 -0L4= 1482 -XVs= 1483 -OTk= 1484 -dHJhbnM= 1485 -IHBvaW50 1486 -IHN0ZA== 1487 -IGZpbA== 1488 -VGltZQ== 1489 -ODA= 1490 -IG1vZA== 1491 -IC0+ 1492 -IGVycm9y 1493 -YWg= 1494 -IHRleHQ= 1495 -cm9sbGVy 1496 -bG9zZQ== 1497 -cWw= 1498 -IHBvbA== 1499 -Pjwv 1500 -IHNob3c= 1501 -VXNlcg== 1502 -YXNlZA== 1503 -IHsKCg== 1504 -IGZpbmQ= 1505 -0LA= 1506 -RUQ= 1507 -c3Bhbg== 1508 -ZW51 1509 -IGN1cnJlbnQ= 1510 -IHVzZWQ= 1511 -Y2VwdA== 1512 -Y2x1ZA== 1513 -IHBsYXk= 1514 -IGxvZw== 1515 -dXRpb24= 1516 -Zmw= 1517 -IHNlZQ== 1518 -aW5kb3dz 1519 -IGhlbHA= 1520 -IHRoZXNl 1521 -IHBhc3M= 1522 -IGRvd24= 1523 -IGV2ZW4= 1524 -YXNvbg== 1525 -dWlsZA== 1526 -ZnJvbQ== 1527 -KGQ= 1528 -IGJs 1529 -bGFiZWw= 1530 -ZWxzZQ== 1531 -0LU= 1532 -ICgh 1533 -aXplZA== 1534 -KCks 1535 -IG9i 1536 -IGl0ZW0= 1537 -dW1w 1538 -VVI= 1539 -b3Ju 1540 -IGRvbg== 1541 -U2U= 1542 -bWFu 1543 -Mjc= 1544 -YW1wbGU= 1545 -dG4= 1546 -PT09PT09PT09PT09PT09PQ== 1547 -SGU= 1548 -Z3JhbQ== 1549 -IGRpZA== 1550 -d24= 1551 -X2g= 1552 -aXZlcg== 1553 -IHNt 1554 -IHRocm91Z2g= 1555 -IEFu 1556 -Y2hl 1557 -IGludg== 1558 -b3VzZQ== 1559 -IGVz 1560 -IE5ldw== 1561 -ZXhwb3J0 1562 -bWFyeQ== 1563 -dXRv 1564 -bGVy 1565 -IGxhc3Q= 1566 -IGV2ZW50 1567 -dHJ5 1568 -77w= 1569 -aWx5 1570 -aWduZWQ= 1571 -aW5lcw== 1572 -b2xsb3c= 1573 -aWNlbnNl 1574 -c29sZQ== 1575 -bGVhcg== 1576 -KGludA== 1577 -IGFnYWlu 1578 -IGhpZ2g= 1579 -aHRtbA== 1580 -SW5kZXg= 1581 -dXRob3I= 1582 -IC8qKgo= 1583 -IGxpbmU= 1584 -RXZlbnQ= 1585 -X0Q= 1586 -IGRvZXM= 1587 -aXRpYWw= 1588 -IGNy 1589 -YXJz 1590 -Mjg= 1591 -IHRlbQ== 1592 -Y2F1c2U= 1593 -ZmFjZQ== 1594 -IGA= 1595 -X0E= 1596 -QnV0dG9u 1597 -YXR1cmU= 1598 -ZWN0ZWQ= 1599 -RVM= 1600 -aXN0ZXI= 1601 -CQo= 1602 -IGJlZm9yZQ== 1603 -YWxl 1604 -b3RoZXI= 1605 -IGJlY2F1c2U= 1606 -cm9pZA== 1607 -IGVk 1608 -aWs= 1609 -cmVn 1610 -IERl 1611 -IGRpc3Q= 1612 -fSwK 1613 -IHN0YXRl 1614 -IGNvbnM= 1615 -cmludA== 1616 -YXR0 1617 -IGhlcmU= 1618 -aW5lZA== 1619 -IGZpbmFs 1620 -ICIi 1621 -S2V5 1622 -TE8= 1623 -IGRlbA== 1624 -cHR5 1625 -dGhpbmc= 1626 -MjY= 1627 -IEFuZA== 1628 -IHJ1bg== 1629 -IFg= 1630 -eW0= 1631 -LmFwcA== 1632 -IHZlcnk= 1633 -Y2Vz 1634 -X04= 1635 -YXJlZA== 1636 -d2FyZA== 1637 -bGlzdA== 1638 -aXRlZA== 1639 -b2xvZw== 1640 -aXRjaA== 1641 -Qm94 1642 -aWZl 1643 -MzM= 1644 -IGFj 1645 -IG1vZGVs 1646 -IG1vbg== 1647 -IHdheQ== 1648 -bGV0ZQ== 1649 -IGNhbGw= 1650 -IGF0dA== 1651 -IGNhbA== 1652 -dmVydA== 1653 -IGRlYw== 1654 -bGVhc2U= 1655 -b3Vu 1656 -IH0pOwo= 1657 -ZnI= 1658 -Zm9ybWF0aW9u 1659 -ZXRhaWw= 1660 -IG51bQ== 1661 -YWo= 1662 -cXVlcnk= 1663 -IHdlbGw= 1664 -IG9iamVjdA== 1665 -IEFz 1666 -IHllYXJz 1667 -Q29sb3I= 1668 -SVM= 1669 -IGRlZmF1bHQ= 1670 -V2g= 1671 -IGlucw== 1672 -YWludA== 1673 -IGphdmE= 1674 -IHNpbQ== 1675 -IEFy 1676 -bW9u 1677 -dGls 1678 -KCk7DQo= 1679 -KTo= 1680 -U2V0 1681 -Mjk= 1682 -YXR0ZXI= 1683 -IHZpZXc= 1684 -IHByZXM= 1685 -YXJyYXk= 1686 -V2U= 1687 -QXQ= 1688 -IGJlbA== 1689 -IG1hbnk= 1690 -MjE= 1691 -TWFu 1692 -ZW5kZXI= 1693 -IGJlaW5n 1694 -IGdvb2Q= 1695 -CQkJCQkJ 1696 -YXRpb25hbA== 1697 -d2FyZQ== 1698 -LmxvZw== 1699 -ew0K 1700 -IHVzaW5n 1701 -X0I= 1702 -IDo9 1703 -X3c= 1704 -aXN0cw== 1705 -bGlzaA== 1706 -IHN0dWQ= 1707 -IEFs 1708 -IGd1 1709 -Y29uZmln 1710 -dXJpbmc= 1711 -dGltZQ== 1712 -b2tlbg== 1713 -YW1lc3BhY2U= 1714 -IHJlcXVlc3Q= 1715 -IGNoaWxk 1716 -IMM= 1717 -bG9i 1718 -IHBhcmFt 1719 -IH0NCg== 1720 -MDE= 1721 -IGVjaG8= 1722 -ZnVuY3Rpb24= 1723 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 1724 -cHM= 1725 -RWxlbWVudA== 1726 -YWxr 1727 -bGljYXRpb24= 1728 -Ynk= 1729 -U2l6ZQ== 1730 -cmF3aW5n 1731 -IHBlcnNvbg== 1732 -ICAgICAgICAgICAgICAgICA= 1733 -XG4= 1734 -b2JqZWN0 1735 -aW5jZQ== 1736 -RW4= 1737 -RmlsZQ== 1738 -dWY= 1739 -ZmZlY3Q= 1740 -QUM= 1741 -IHN0eWxl 1742 -c3VtbWFyeQ== 1743 -IHF1ZQ== 1744 -X3I= 1745 -ICgk 1746 -TW9kZWw= 1747 -aWRlbnQ= 1748 -IG1ldGhvZA== 1749 -SUw= 1750 -b3R0 1751 -bGVzcw== 1752 -SU5H 1753 -ICgp 1754 -IGV4cGVjdA== 1755 -eW5j 1756 -cGFja2FnZQ== 1757 -MzU= 1758 -dXJz 1759 -IHByb3Q= 1760 -Li8= 1761 -cHJl 1762 -ICkK 1763 -bWE= 1764 -IHN1cg== 1765 -IGZvdW5k 1766 -SW5mbw== 1767 -cGFy 1768 -aW1lcw== 1769 -LmU= 1770 -YWlucw== 1771 -IHBvc3Q= 1772 -LWQ= 1773 -NDU= 1774 -b2xlYW4= 1775 -IHNs 1776 -UEU= 1777 -IHN1Y2g= 1778 -c2VsZWN0 1779 -YWluZXI= 1780 -IHRoaW5r 1781 -IGRpZmZlcg== 1782 -LnI= 1783 -LyoqCg== 1784 -RkY= 1785 -b29s 1786 -cGxhdGU= 1787 -cXVhbA== 1788 -IEZvcg== 1789 -IG11Y2g= 1790 -dWM= 1791 -KG5ldw== 1792 -b2R1bGU= 1793 -IHNvbQ== 1794 -IGh0dHA= 1795 -IExpc3Q= 1796 -IGNvdW50 1797 -IGluc3Q= 1798 -Y2hhcg== 1799 -bWl0 1800 -Lmlk 1801 -YWtpbmc= 1802 -IGdlbmVy 1803 -cHg= 1804 -dmljZQ== 1805 -Mzc= 1806 -X2RhdGE= 1807 -IE5VTEw= 1808 -fQ0K 1809 -aWRk 1810 -44CC 1811 -IG1lZA== 1812 -b3Jn 1813 -aWRlcg== 1814 -YWNoZQ== 1815 -d29yaw== 1816 -IGNoZWNr 1817 -d2Vlbg== 1818 -ICgo 1819 -dGhl 1820 -YW50cw== 1821 -Pjw= 1822 -LkI= 1823 -LWM= 1824 -IG9wZW4= 1825 -IGVzdA== 1826 -ICAgICAgICAK 1827 -IG5leHQ= 1828 -SU0= 1829 -0YI= 1830 -T1Q= 1831 -w7M= 1832 -IGZvbGxvdw== 1833 -Y29udGVudA== 1834 -ICAgICAgICAgICAg 1835 -IGluY2x1ZA== 1836 -SEU= 1837 -IFJlcw== 1838 -IGhyZWY= 1839 -0Lg= 1840 -IGNhcg== 1841 -eXBlcw== 1842 -aW1hZ2U= 1843 -VW4= 1844 -IGJvb2w= 1845 -QUQ= 1846 -IGdhbWU= 1847 -LkZvcm0= 1848 -cm93cw== 1849 -Ki8= 1850 -dmVsb3A= 1851 -LkRyYXdpbmc= 1852 -IHBhdGg= 1853 -aXNpb24= 1854 -IGVhY2g= 1855 -IFBs 1856 -X3R5cGU= 1857 -UGF0aA== 1858 -bmVjdGlvbg== 1859 -IGF2 1860 -Jyku 1861 -IHN1cHBvcnQ= 1862 -RU5U 1863 -cmVt 1864 -Iiku 1865 -IG93bg== 1866 -IGNvcg== 1867 -Y291bnQ= 1868 -bWlzcw== 1869 -dWFsbHk= 1870 -IG1lbQ== 1871 -c3Rk 1872 -aWVuY2U= 1873 -c2VhcmNo 1874 -IgoK 1875 -Rm9ybQ== 1876 -IHNleA== 1877 -ZW5hbWU= 1878 -IHNpZ24= 1879 -IGV0 1880 -ICAgICAgICAgIA== 1881 -Jywn 1882 -IEFwcA== 1883 -IHRob3Nl 1884 -b2Zm 1885 -IGVycg== 1886 -IHN5c3RlbQ== 1887 -IGJlc3Q= 1888 -Y29kZQ== 1889 -IHNhbWU= 1890 -IGRp 1891 -dXNz 1892 -IGNyZWF0ZQ== 1893 -YXRoZXI= 1894 -QXJyYXk= 1895 -Lmlu 1896 -ZmU= 1897 -U2VydmljZQ== 1898 -VU4= 1899 -YXRz 1900 -IFo= 1901 -YWx0aA== 1902 -IG1hZGU= 1903 -dHJ1ZQ== 1904 -QUI= 1905 -IG1hcms= 1906 -cmlk 1907 -aWZpZWQ= 1908 -LA0K 1909 -eW4= 1910 -cHJlc3M= 1911 -IGdyb3Vw 1912 -IGZpbg== 1913 -IExpY2Vuc2U= 1914 -RmllbGQ= 1915 -ZWdlcg== 1916 -IHdvcmxk 1917 -aW5lc3M= 1918 -dHk= 1919 -IHByb2Nlc3M= 1920 -KGI= 1921 -IGNyZQ== 1922 -YXJu 1923 -aXZlcw== 1924 -IG1haW4= 1925 -aWRlbw== 1926 -MzY= 1927 -X2c= 1928 -QUc= 1929 -dmFsaWQ= 1930 -aW1n 1931 -UEk= 1932 -IGNvbG9y 1933 -IHJlcG9ydA== 1934 -IHRha2U= 1935 -cmli 1936 -T00= 1937 -IGRheQ== 1938 -UmVxdWVzdA== 1939 -IHNr 1940 -YmVycw== 1941 -CXM= 1942 -LkFkZA== 1943 -b290 1944 -SW1hZ2U= 1945 -IGNvbXBsZQ== 1946 -b2xsZWN0aW9u 1947 -IHRvcA== 1948 -IGZyZWU= 1949 -QVM= 1950 -RGU= 1951 -IE9u 1952 -SUc= 1953 -OTA= 1954 -ZXRh 1955 -RGF0ZQ== 1956 -IGFjdGlvbg== 1957 -MzQ= 1958 -T3Zlcg== 1959 -aXRvcg== 1960 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 1961 -bm90 1962 -IGluZGV4 1963 -aGVy 1964 -aWNvbg== 1965 -T24= 1966 -Ow0KDQo= 1967 -aXZpdHk= 1968 -bWFuZA== 1969 -LldpbmRvd3M= 1970 -T0w= 1971 -IHJlYWw= 1972 -IG1heA== 1973 -bGFuZA== 1974 -Li4uLg== 1975 -cmFwaA== 1976 -IGJ1aWxk 1977 -bGVn 1978 -YXNzd29yZA== 1979 -PwoK 1980 -4oCm 1981 -b29r 1982 -dWNr 1983 -IG1lc3NhZ2U= 1984 -dGVzdA== 1985 -aXZlcnM= 1986 -Mzg= 1987 -IGlucHV0 1988 -IGFydA== 1989 -IGJldHdlZW4= 1990 -R2V0 1991 -ZW50ZXI= 1992 -Z3JvdW5k 1993 -ZW5l 1994 -w6E= 1995 -Lmxlbmd0aA== 1996 -Tm9kZQ== 1997 -KGk= 1998 -Q2xhc3M= 1999 -Zm9y 2000 -IOKAlA== 2001 -dGVu 2002 -b2lu 2003 -IGtl 2004 -dWk= 2005 -IElO 2006 -IHRhYmxl 2007 -c3Vi 2008 -IExl 2009 -IGhlYWQ= 2010 -IG11c3Q= 2011 -Ly8vLy8vLy8vLy8vLy8vLw== 2012 -LnV0aWw= 2013 -Q29udGV4dA== 2014 -IG9yZGVy 2015 -IG1vdg== 2016 -b3Zlcg== 2017 -IGNvbnRpbg== 2018 -IHNheQ== 2019 -c3RhdGlj 2020 -LlRleHQ= 2021 -IGNsYXNzTmFtZQ== 2022 -cGFueQ== 2023 -IHRlcg== 2024 -aGVhZA== 2025 -cmc= 2026 -IHByb2R1Y3Q= 2027 -VGhpcw== 2028 -LuKAnQ== 2029 -IEJ1dA== 2030 -NzA= 2031 -bG95 2032 -IGRvdWJsZQ== 2033 -c2c= 2034 -IHBsYWNl 2035 -Lng= 2036 -bWVzc2FnZQ== 2037 -IGluZm9ybWF0aW9u 2038 -cHJpdmF0ZQ== 2039 -IG9wZXI= 2040 -Y2Vk 2041 -ZGI= 2042 -Ij48Lw== 2043 -UGFyYW0= 2044 -aWNsZQ== 2045 -IHdlZWs= 2046 -IHByb3A= 2047 -dGFibGU= 2048 -aWRnZXQ= 2049 -cGxhY2U= 2050 -UHJvcA== 2051 -IEFsbA== 2052 -ZWxz 2053 -Ym94 2054 -LgoKCgo= 2055 -LlI= 2056 -IFRv 2057 -aXRlcg== 2058 -U2g= 2059 -dXJhdGlvbg== 2060 -b2xkZXI= 2061 -X2xpc3Q= 2062 -Y29tZQ== 2063 -IHN3 2064 -aXphdGlvbg== 2065 -CWZvcg== 2066 -Ymw= 2067 -IHByb2dyYW0= 2068 -KGU= 2069 -YXBl 2070 -Y2hlY2s= 2071 -LkZvcm1z 2072 -IHVuZA== 2073 -YXRlZ29yeQ== 2074 -NzU= 2075 -YWdz 2076 -IHJlc3BvbnNl 2077 -VVM= 2078 -cmVxdWVzdA== 2079 -IHN0cnVjdA== 2080 -ZXNjcmlwdGlvbg== 2081 -IGNvZGU= 2082 -X0g= 2083 -dWZmZXI= 2084 -IHdpdGhvdXQ= 2085 -bG9iYWw= 2086 -TWFuYWdlcg== 2087 -aWx0ZXI= 2088 -UE8= 2089 -CXRoaXM= 2090 -b3B0aW9u 2091 -IHNvbA== 2092 -ID09PQ== 2093 -YWtlcw== 2094 -Q29udHJvbGxlcg== 2095 -NDQ= 2096 -TWVzc2FnZQ== 2097 -IHJlZg== 2098 -ZXZlcg== 2099 -IFNv 2100 -YWluaW5n 2101 -LmFwcGVuZA== 2102 -IHN0aWxs 2103 -IHByb3ZpZA== 2104 -IGFzc2VydA== 2105 -bWVk 2106 -IGNhcA== 2107 -dXNpbmVzcw== 2108 -IHJlcA== 2109 -dGluZ3M= 2110 -dmVk 2111 -Lk4= 2112 -YXBp 2113 -T0Q= 2114 -IGZpZWxk 2115 -aXZlbg== 2116 -b3Rv 2117 -4oCc 2118 -Y29s 2119 -KHg= 2120 -Z2h0 2121 -UmVzdWx0 2122 -Q29kZQ== 2123 -Lmlz 2124 -bGluaw== 2125 -IGNvdXI= 2126 -QW4= 2127 -IHRlYW0= 2128 -CWludA== 2129 -aWZ0 2130 -NTU= 2131 -IHNlY29uZA== 2132 -IGdvaW5n 2133 -IHJhbmdl 2134 -X0U= 2135 -bmVzcw== 2136 -Mzk= 2137 -IGZhbQ== 2138 -IG5pbA== 2139 -IENvbnQ= 2140 -YWlsYWJsZQ== 2141 -dXRlcw== 2142 -YXRhYg== 2143 -IGZhY3Q= 2144 -IHZpcw== 2145 -KCY= 2146 -IEFO 2147 -MzE= 2148 -QWw= 2149 -dGl0bGU= 2150 -IGFuZHJvaWQ= 2151 -Q0U= 2152 -XCI= 2153 -aXJ0 2154 -IHdyaXQ= 2155 -0L0= 2156 -CW0= 2157 -ZnR3YXJl 2158 -b25k 2159 -IHJldA== 2160 -b3NpdGlvbg== 2161 -IGhvbWU= 2162 -IGxlZnQ= 2163 -YXJncw== 2164 -bWVyaWM= 2165 -NDg= 2166 -IGRpcmVjdA== 2167 -b2Np 2168 -UGw= 2169 -QXM= 2170 -cmV0 2171 -YWRv 2172 -T2Y= 2173 -Y2hu 2174 -IEdldA== 2175 -ZWU= 2176 -cm9zcw== 2177 -KCk7 2178 -X19fXw== 2179 -LnBo 2180 -SXQ= 2181 -b3V0ZQ== 2182 -IGV4cGVy 2183 -Y2hvb2w= 2184 -d3d3 2185 -fSw= 2186 -IGFsbG93 2187 -IMI= 2188 -KCkp 2189 -c2l6ZQ== 2190 -aXNt 2191 -YWk= 2192 -dHJhY3Q= 2193 -YW5l 2194 -Li4uCgo= 2195 -Y29udGV4dA== 2196 -IGJlZw== 2197 -Q0g= 2198 -IHBhZ2U= 2199 -aGlw 2200 -bm8= 2201 -Y29yZQ== 2202 -c3A= 2203 -IGRpZmZlcmVudA== 2204 -aWFibGU= 2205 -IE1l 2206 -X0lO 2207 -YnV0dG9u 2208 -IElz 2209 -ZXJ2aWNlcw== 2210 -IGNh 2211 -IGFyb3VuZA== 2212 -QXBw 2213 -cmF0aW9u 2214 -IHJlY2U= 2215 -IHJlYWxseQ== 2216 -IGltYWdl 2217 -IHRhcmdldA== 2218 -IGRlcA== 2219 -b3B5cmlnaHQ= 2220 -dHJh 2221 -aW5nbGU= 2222 -aXRhbA== 2223 -TGF5b3V0 2224 -IGJvdGg= 2225 -T3ZlcnJpZGU= 2226 -YXJt 2227 -PT4= 2228 -YXRlcmlhbA== 2229 -aWxlZA== 2230 -IHB1dA== 2231 -UXU= 2232 -0YA= 2233 -dW5n 2234 -bWFw 2235 -CQkJCQkJCQk= 2236 -IGxldmVs 2237 -Q29tcG9uZW50 2238 -Ym9vaw== 2239 -Y3JlZW4= 2240 -X1JF 2241 -IGNvbmZpZw== 2242 -44E= 2243 -T3I= 2244 -LmRhdGE= 2245 -IGRvY3VtZW50 2246 -Iiwi 2247 -dHJpYnV0ZQ== 2248 -dXg= 2249 -TG9n 2250 -ZmVyZW5jZQ== 2251 -cG9zdA== 2252 -X2U= 2253 -IGxvY2Fs 2254 -YW5kb20= 2255 -YXNzZXJ0 2256 -VmFs 2257 -bGVjdGVk 2258 -aW5h 2259 -YXRhYmFzZQ== 2260 -QWRk 2261 -IGNvbnRlbnQ= 2262 -LnByaW50 2263 -c2lnbmVk 2264 -cmlj 2265 -LiIKCg== 2266 -IGZh 2267 -IQoK 2268 -LWY= 2269 -aXZlZA== 2270 -IHF1ZXN0 2271 -LmV4 2272 -IGZsb2F0 2273 -IGRldmVsb3A= 2274 -0L7Q 2275 -TWFw 2276 -YWRpbmc= 2277 -IHBvc3M= 2278 -VUU= 2279 -bmFtZXNwYWNl 2280 -X08= 2281 -CWI= 2282 -LkdldA== 2283 -Pig= 2284 -anNvbg== 2285 -ZXRhaWxz 2286 -NjY= 2287 -IHRvbw== 2288 -IGV4dGVuZHM= 2289 -IE5vbmU= 2290 -IGZvcmU= 2291 -KFN0cmluZw== 2292 -Zm9ybWF0 2293 -IGdyZWF0 2294 -aW50ZXI= 2295 -Y2FsZQ== 2296 -0YE= 2297 -cm9u 2298 -aXZpbmc= 2299 -RW50 2300 -ZW5jeQ== 2301 -eHQ= 2302 -b3k= 2303 -MDU= 2304 -IG1vbnRo 2305 -IGhhcHA= 2306 -IHN1cGVy 2307 -YmFy 2308 -ZGVmYXVsdA== 2309 -X2Rl 2310 -b3Jkcw== 2311 -bG4= 2312 -KHsK 2313 -IEluZA== 2314 -YXNlcw== 2315 -IHRpdGxl 2316 -IGNvbnRleHQ= 2317 -MDg= 2318 -b2g= 2319 -LXA= 2320 -RW0= 2321 -IG1ldA== 2322 -VGVzdA== 2323 -IGxpZmU= 2324 -X3Y= 2325 -IFVT 2326 -VUk= 2327 -b2NhdGlvbg== 2328 -bWQ= 2329 -IFsK 2330 -IF0= 2331 -c3c= 2332 -IGluY3Jl 2333 -c2NyaXB0 2334 -ZW50aWFs 2335 -d2F5cw== 2336 -LmRl 2337 -IHNyYw== 2338 -IGNhdGNo 2339 -IEFtZXJpYw== 2340 -Ly8K 2341 -ICAgICAgICAgICAgICA= 2342 -IHBheQ== 2343 -cGxpdA== 2344 -4oCU 2345 -IGNvdW4= 2346 -b2Jq 2347 -LnBocA== 2348 -IGNoYW5nZQ== 2349 -ZXRoaW5n 2350 -J3Jl 2351 -YXN0ZXI= 2352 -bG9z 2353 -bGF0aW9u 2354 -ICAK 2355 -TGU= 2356 -w6Q= 2357 -KHs= 2358 -cmVhZHk= 2359 -IE5v 2360 -IHBvc2l0aW9u 2361 -IG9sZA== 2362 -IGJvb2s= 2363 -YWJsZWQ= 2364 -YnVn 2365 -MjAy 2366 -SGFuZA== 2367 -fTsKCg== 2368 -aXNwbGF5 2369 -YXZpbmc= 2370 -MDQ= 2371 -IGdvdmVy 2372 -IHZlcnNpb24= 2373 -U3lzdGVt 2374 -bmVjdA== 2375 -cmVzcG9uc2U= 2376 -U3R5bGU= 2377 -VXA= 2378 -YW5ndQ== 2379 -IHRocmVl 2380 -aW5pdA== 2381 -ZXJv 2382 -IGxhdw== 2383 -ZW5kaWY= 2384 -IGJhc2U= 2385 -ZW1haWw= 2386 -KGw= 2387 -X1Y= 2388 -IGNvbmY= 2389 -QVRF 2390 -IGR1cmluZw== 2391 -dGVz 2392 -IGNvbnNvbGU= 2393 -IFBy 2394 -IHNwZQ== 2395 -dmVz 2396 -NjU= 2397 -cGF0aA== 2398 -aWFsb2c= 2399 -ZGl0aW9u 2400 -X3Rv 2401 -YXJkcw== 2402 -IGFnYWluc3Q= 2403 -ZXR3b3Jr 2404 -IFBo 2405 -X0w= 2406 -Y3Vy 2407 -aW1pdA== 2408 -V2l0aA== 2409 -IHBvd2Vy 2410 -aXVt 2411 -JzsKCg== 2412 -IHdvbQ== 2413 -bGVmdA== 2414 -b3VyY2Vz 2415 -YXRyaQ== 2416 -IElt 2417 -IE1hbg== 2418 -b3J0aA== 2419 -JHs= 2420 -ODg= 2421 -cXVhbHM= 2422 -ZXNl 2423 -X3NpemU= 2424 -IGlzcw== 2425 -b3RhbA== 2426 -LWc= 2427 -aXF1ZQ== 2428 -cmFtZQ== 2429 -IHdpZHRo 2430 -ZXJn 2431 -KSg= 2432 -aXR0bGU= 2433 -VFI= 2434 -IFRoZXk= 2435 -ZW5jZXM= 2436 -MDI= 2437 -cmw= 2438 -b25z 2439 -IGxhYmVs 2440 -Lnk= 2441 -LXQ= 2442 -dXBkYXRl 2443 -YW5lbA== 2444 -c2M= 2445 -LnRv 2446 -IHByb2plY3Q= 2447 -w7w= 2448 -IGVsZW1lbnQ= 2449 -IHN1Y2Nlc3M= 2450 -CQkK 2451 -LnNo 2452 -cmFt 2453 -Y2hlZA== 2454 -KCkpCg== 2455 -ICgK 2456 -IGRhdGU= 2457 -IHRvdA== 2458 -X1NU 2459 -QWxs 2460 -aWZpY2F0aW9u 2461 -CXZhcg== 2462 -IHRyaQ== 2463 -Y2hlbQ== 2464 -bXk= 2465 -IGJpZw== 2466 -IEFk 2467 -IEF0 2468 -b3Rz 2469 -bnVt 2470 -QWN0 2471 -IG1hcA== 2472 -ZXJh 2473 -Y29wZQ== 2474 -LiQ= 2475 -LOKAnQ== 2476 -IHBvcA== 2477 -IGZldw== 2478 -IGxlbg== 2479 -dWlk 2480 -ZXRlcnM= 2481 -dWxlcw== 2482 -w60= 2483 -c291cmNl 2484 -aHR0cHM= 2485 -IGRlbQ== 2486 -IGVhcg== 2487 -IyMjIyMjIyMjIyMjIyMjIw== 2488 -IG1hdGNo 2489 -b3JpZXM= 2490 -NDk= 2491 -YWNlcw== 2492 -IENs 2493 -IG5vZGU= 2494 -Nzg= 2495 -aXJj 2496 -bG9jYWw= 2497 -dW5pdHk= 2498 -fTsK 2499 -IGFub3RoZXI= 2500 -PDw= 2501 -b2dsZQ== 2502 -IHNpdA== 2503 -ZXdvcms= 2504 -VEU= 2505 -Lkk= 2506 -TlM= 2507 -b2xvZ3k= 2508 -b3VnaHQ= 2509 -LkNvbnQ= 2510 -Pj4= 2511 -IGNhcmU= 2512 -c3RhdGU= 2513 -CXByaXZhdGU= 2514 -IGVmZmVjdA== 2515 -Kysp 2516 -X2ZpbGU= 2517 -ZW5kaW5n 2518 -TGluZQ== 2519 -Rm9y 2520 -aW9y 2521 -IFNj 2522 -IGZ1bg== 2523 -LlNpemU= 2524 -CWVsc2U= 2525 -XSk= 2526 -c3RhcnQ= 2527 -dmlvdXM= 2528 -IH0s 2529 -b3Vycw== 2530 -IGxlZw== 2531 -IHNlcnZpY2U= 2532 -IHNpbmNl 2533 -aXJvbg== 2534 -TGFiZWw= 2535 -IG5vbg== 2536 -IGxvcw== 2537 -aWN0aW9u 2538 -IGZ1bGw= 2539 -YWN0ZXI= 2540 -Ym9hcmQ= 2541 -Z3Jlc3M= 2542 -IHR1cm4= 2543 -aXRoZXI= 2544 -MDk= 2545 -LnNpemU= 2546 -IGJvZHk= 2547 -cmVzaA== 2548 -ZXR1cm4= 2549 -MTk5 2550 -KF8= 2551 -eWxlcw== 2552 -b3JtYWw= 2553 -cGk= 2554 -IHNvbWV0aGluZw== 2555 -IS0t 2556 -dWludA== 2557 -IHByb2R1 2558 -IHN0YW5k 2559 -IHByb2JsZQ== 2560 -IGF2YWlsYWJsZQ== 2561 -bXQ= 2562 -IEJs 2563 -IC4uLg== 2564 -IGJsb2Nr 2565 -SW5wdXQ= 2566 -IGtlZXA= 2567 -Q291bnQ= 2568 -b3Blbg== 2569 -IFsn 2570 -IHRocm93 2571 -dWlsZGVy 2572 -QWN0aW9u 2573 -IHRoaW5ncw== 2574 -VHJ1ZQ== 2575 -IHVybA== 2576 -IEJv 2577 -cHJpbnRm 2578 -IHJlZA== 2579 -anM= 2580 -LmNyZWF0ZQ== 2581 -IE9y 2582 -U3RhdHVz 2583 -SW5zdGFuY2U= 2584 -IGNvbnRyb2w= 2585 -IGNvbWU= 2586 -IGN1c3RvbQ== 2587 -bG9jYXRpb24= 2588 -MDc= 2589 -bW9kZWw= 2590 -IA0K 2591 -IHNvdXJjZQ== 2592 -IGVhcw== 2593 -Lm91dA== 2594 -XQoK 2595 -b25leQ== 2596 -IGF3YWl0 2597 -IHBhcnRpYw== 2598 -QVA= 2599 -dWJsaXNo 2600 -b2Rlcw== 2601 -X3Bybw== 2602 -cGx5 2603 -cml0ZXI= 2604 -IHByb3Y= 2605 -IG1pbGw= 2606 -SFQ= 2607 -XSkK 2608 -IGNoYW5n 2609 -IGFzaw== 2610 -ICAgICAgICAgICAgICAgICAgICAg 2611 -IG91dHB1dA== 2612 -IGVtYWls 2613 -Njg= 2614 -LnB1c2g= 2615 -IH0NCg0K 2616 -aW5hdGlvbg== 2617 -NDc= 2618 -YXRyaXg= 2619 -VGFibGU= 2620 -dWNjZXNz 2621 -XSk7Cg== 2622 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 2623 -IGRpc2M= 2624 -KFs= 2625 -IGJ1c2luZXNz 2626 -aGVpZ2h0 2627 -Lmh0bWw= 2628 -dGE= 2629 -ZmllbGQ= 2630 -IHJlcXVpcmVk 2631 -X1I= 2632 -IGdvdmVybg== 2633 -fQ0KDQo= 2634 -bGV4 2635 -NTAw 2636 -Liw= 2637 -IFNldA== 2638 -dXJjaA== 2639 -Ly8v 2640 -dHM= 2641 -YWY= 2642 -IG1pZ2h0 2643 -aXN0b3J5 2644 -U3Ry 2645 -IG5ldmVy 2646 -UmVzcG9uc2U= 2647 -YXJzZQ== 2648 -YWRh 2649 -IEhvdw== 2650 -ICop 2651 -IDs= 2652 -IGhhcmQ= 2653 -QWQ= 2654 -IGludGVybg== 2655 -dXNlZA== 2656 -KGRhdGE= 2657 -bW9k 2658 -YW5uZWw= 2659 -IG5w 2660 -dWdn 2661 -IC8+Cg== 2662 -IGNhbGxlZA== 2663 -Ym9keQ== 2664 -IGNobw== 2665 -KHI= 2666 -X3NldA== 2667 -aXJk 2668 -ID49 2669 -IH07Cg== 2670 -IG9wdGlvbnM= 2671 -IEdlbmVy 2672 -IGhlaWdodA== 2673 -UG9pbnQ= 2674 -WW91 2675 -ZXR5 2676 -Q2xpY2s= 2677 -IHNtYWxs 2678 -IGlkZQ== 2679 -IGFjY2Vzcw== 2680 -YW5ndWFnZQ== 2681 -IHByb3RlY3RlZA== 2682 -IGpvYg== 2683 -IFRoZXJl 2684 -RGVm 2685 -IGFkZHJlc3M= 2686 -IHVpbnQ= 2687 -Tm90 2688 -b28= 2689 -YXBz 2690 -PGRpdg== 2691 -YWluZWQ= 2692 -YXR1cg== 2693 -IHN1bQ== 2694 -LXc= 2695 -IERhdGU= 2696 -IGxpdHRsZQ== 2697 -IGZyaQ== 2698 -WVBF 2699 -IHBvcnQ= 2700 -ZWg= 2701 -cHJpbmc= 2702 -X3BhdGg= 2703 -IHN0YXR1cw== 2704 -MDY= 2705 -YWlt 2706 -Ym9vbA== 2707 -IGFwcGU= 2708 -IG9z 2709 -Lm5hbWU= 2710 -ZW5zaW9u 2711 -X0c= 2712 -IHVwZGF0ZQ== 2713 -Q29uZmln 2714 -YWZm 2715 -RVJS 2716 -IDw9 2717 -YXRlbHk= 2718 -I2lm 2719 -dWN0aW9u 2720 -OTU= 2721 -IFRl 2722 -IGxpbms= 2723 -IFVzZXI= 2724 -LmZpbmQ= 2725 -Lm9yZw== 2726 -bWU= 2727 -IGdpdmVu 2728 -T3V0 2729 -I2VuZGlm 2730 -IGJldHRlcg== 2731 -UGFnZQ== 2732 -IGZlZWw= 2733 -ZW5u 2734 -TUw= 2735 -IGFscmVhZHk= 2736 -IGluY2x1ZGluZw== 2737 -b29nbGU= 2738 -cnU= 2739 -aWNhbGx5 2740 -cHJvcA== 2741 -bGVhbg== 2742 -b3V0ZXI= 2743 -IGFsd2F5cw== 2744 -b3JkaW5n 2745 -SWY= 2746 -b3JhZ2U= 2747 -IHBhcmVudA== 2748 -dmlz 2749 -CQkJCQkJCQ== 2750 -IGdvdA== 2751 -c3RhbmQ= 2752 -IGxlc3M= 2753 -L3M= 2754 -IEFzcw== 2755 -YXB0 2756 -aXJlZA== 2757 -IEFkZA== 2758 -IGFjY291bnQ= 2759 -cGxveQ== 2760 -IGRlcg== 2761 -cmVzZW50 2762 -IGxvdA== 2763 -IHZhbGlk 2764 -CWQ= 2765 -IGJpdA== 2766 -cG9uZW50cw== 2767 -IGZvbGxvd2luZw== 2768 -X2V4 2769 -U09O 2770 -IHN1cmU= 2771 -b2NpYWw= 2772 -IHByb20= 2773 -ZXJ0aWVz 2774 -aGVhZGVy 2775 -LnBybw== 2776 -IGJvb2xlYW4= 2777 -IHNlYXJjaA== 2778 -a2Vu 2779 -IG9yaWc= 2780 -IGVy 2781 -RWQ= 2782 -RU0= 2783 -YXV0 2784 -bGluZw== 2785 -YWxpdHk= 2786 -QnlJZA== 2787 -YmVk 2788 -CWNhc2U= 2789 -NDY= 2790 -ZXRoZXI= 2791 -cG9zaXQ= 2792 -IGludmVzdA== 2793 -IE9S 2794 -IHNheXM= 2795 -bWlzc2lvbg== 2796 -QU1F 2797 -IHRlbXA= 2798 -b2Fk 2799 -IHJlc3Q= 2800 -aW5mbw== 2801 -IGludGVyZXN0 2802 -QXJn 2803 -IHBlcmZvcm0= 2804 -cG9ucw== 2805 -IFZpZXc= 2806 -IHZlcg== 2807 -bGli 2808 -KGNvbnN0 2809 -VXRpbA== 2810 -TGlzdGVuZXI= 2811 -YXJnZQ== 2812 -Nzc= 2813 -IG11bHQ= 2814 -IGRpZQ== 2815 -IHNpdGU= 2816 -Li4vLi4v 2817 -RUw= 2818 -IHZhbHVlcw== 2819 -IH0pCg== 2820 -cGVu 2821 -Tm8= 2822 -aWNybw== 2823 -IGJlaA== 2824 -ICcuLw== 2825 -YWN5 2826 -cmVj 2827 -KCktPg== 2828 -CSAgIA== 2829 -Iikp 2830 -Q29udGVudA== 2831 -X1c= 2832 -cGxlbWVudA== 2833 -IHdvbg== 2834 -IHZpZGVv 2835 -YWRp 2836 -cG9pbnQ= 2837 -JSU= 2838 -MDM= 2839 -IGds 2840 -ZXJ2ZWQ= 2841 -dmlyb24= 2842 -SUY= 2843 -dXRlZA== 2844 -44M= 2845 -J20= 2846 -IGNlcnQ= 2847 -IHByb2Y= 2848 -IGNlbGw= 2849 -YXJp 2850 -IHBsYXllcg== 2851 -YWlz 2852 -IGNvc3Q= 2853 -IGh1bQ== 2854 -KFI= 2855 -IG9mZmlj 2856 -a3M= 2857 -LnRleHQ= 2858 -YXR1cmVz 2859 -IHRvdGFs 2860 -ICovCgo= 2861 -b3Bl 2862 -IHN0YXQ= 2863 -VU0= 2864 -IGxvYWQ= 2865 -aWdodHM= 2866 -IGNsZWFy 2867 -dXJv 2868 -IHRlY2hu 2869 -dXBwb3J0 2870 -SVI= 2871 -IHJvdw== 2872 -IHNlZW0= 2873 -IHE= 2874 -IHNob3J0 2875 -IE5vdA== 2876 -aXBw 2877 -R3JvdXA= 2878 -c2VjdGlvbg== 2879 -bWF4 2880 -aXJs 2881 -IG92ZXJyaWRl 2882 -IGNvbXBhbnk= 2883 -IGRvbmU= 2884 -Iik7DQo= 2885 -IGdyZQ== 2886 -LlJl 2887 -IGJlbGll 2888 -cmlzdA== 2889 -IGhlYWx0aA== 2890 -QU5U 2891 -KCkKCg== 2892 -IEJl 2893 -LnZhbHVl 2894 -IEdy 2895 -b3R0b20= 2896 -IGFyZ3M= 2897 -UFQ= 2898 -c3RhdHVz 2899 -ZnVuYw== 2900 -dW1lbnRz 2901 -LWg= 2902 -TnVtYmVy 2903 -Og0K 2904 -IExvZw== 2905 -ZXJ2ZXI= 2906 -ICksCg== 2907 -YW1lbnQ= 2908 -IG9iag== 2909 -aW5j 2910 -IGNoaWxkcmVu 2911 -aWN5 2912 -SVo= 2913 -YW5kcw== 2914 -YWJseQ== 2915 -IGRpc3RyaWI= 2916 -IGN1cg== 2917 -ZXJpYWw= 2918 -IGRheXM= 2919 -cmVhdGVk 2920 -cmVjdA== 2921 -LWw= 2922 -aXJt 2923 -aWRkZW4= 2924 -b21i 2925 -IGluaXRpYWw= 2926 -Lmpz 2927 -IOI= 2928 -UXVlcnk= 2929 -IG9ubGluZQ== 2930 -aW1hbA== 2931 -LmNvbg== 2932 -YXU= 2933 -VXJs 2934 -Y29udHJvbA== 2935 -aXJlY3Rpb24= 2936 -IGluc3RhbmNl 2937 -T1JU 2938 -IEZy 2939 -d2hlcmU= 2940 -IGphdmF4 2941 -IG9yZ2Fu 2942 -YXB0ZXI= 2943 -IHJlYXNvbg== 2944 -b3B0aW9ucw== 2945 -NTk= 2946 -IE1hcg== 2947 -KGE= 2948 -IHdpdGhpbg== 2949 -LuKAnQoK 2950 -T0RF 2951 -X0RF 2952 -YWRtaW4= 2953 -ZW5kZWQ= 2954 -IGRlc2lnbg== 2955 -IERhdGE= 2956 -dW5l 2957 -IEZpbGU= 2958 -cm9vdA== 2959 -IGNlbnQ= 2960 -IGFycg== 2961 -X2FkZA== 2962 -bGVu 2963 -cGFnZQ== 2964 -LCc= 2965 -X3N0cg== 2966 -IGJybw== 2967 -YWJpbGl0eQ== 2968 -b3V0aA== 2969 -NTg= 2970 -L2M= 2971 -cG9zZQ== 2972 -aXJ0dWFs 2973 -ZWFyY2g= 2974 -X3VybA== 2975 -YXJnaW4= 2976 -SHR0cA== 2977 -IHNjaG9vbA== 2978 -YXZh 2979 -IGNvbnNpZGVy 2980 -LmxhYmVs 2981 -IEFycmF5 2982 -NDI= 2983 -d2Vi 2984 -b3B0 2985 -LnByaW50bG4= 2986 -dWxhdGlvbg== 2987 -IGZ1bmM= 2988 -UEw= 2989 -ICJc 2990 -IFRleHQ= 2991 -YWN0b3J5 2992 -KGZ1bmN0aW9u 2993 -bnVsbA== 2994 -IGVuZw== 2995 -ZG93bg== 2996 -IGluY2x1ZGU= 2997 -IEVu 2998 -IERy 2999 -IGRi 3000 -ISE= 3001 -c2lkZQ== 3002 -IGluaXQ= 3003 -cXVpcmVk 3004 -IFNoZQ== 3005 -Q29sdW1u 3006 -cmVhY3Q= 3007 -IGFubg== 3008 -IHN0b3A= 3009 -IGxhdGVy 3010 -IFRoYXQ= 3011 -ZW50aW9u 3012 -ZGY= 3013 -VUc= 3014 -SUxF 3015 -IGNsaWVudA== 3016 -cmFmdA== 3017 -ZmZlcg== 3018 -UE9TVA== 3019 -ZWxwZXI= 3020 -IGxvdmU= 3021 -cXVvdGU= 3022 -b3Vk 3023 -IGpzb24= 3024 -IGFibGU= 3025 -IG1lbg== 3026 -QVg= 3027 -IENvcHlyaWdodA== 3028 -w7Y= 3029 -YXZpZw== 3030 -cmVx 3031 -Q2xpZW50 3032 -fSk7Cg== 3033 -LkNvbQ== 3034 -ZXJj 3035 -aWx0 3036 -cGVjaWFs 3037 -X2NvbQ== 3038 -cm9vbQ== 3039 -Lk5hbWU= 3040 -IGdpdmU= 3041 -YW1i 3042 -aWtl 3043 -IGNvbmRpdGlvbg== 3044 -Y2xpZW50 3045 -YXRvcnM= 3046 -OiI= 3047 -IGNvcHk= 3048 -dXR1cmU= 3049 -aXZlcnNpdHk= 3050 -ZXJuYWw= 3051 -e3s= 3052 -IENhbg== 3053 -b3VuYw== 3054 -ZG8= 3055 -IG9jYw== 3056 -IGFwcHJv 3057 -dGhlcnM= 3058 -emU= 3059 -IGVpdGhlcg== 3060 -IEZs 3061 -IGltcG9ydGFudA== 3062 -IGxlYWQ= 3063 -YXR0cg== 3064 -QVJU 3065 -RXF1YWw= 3066 -IGRh 3067 -ZXRjaA== 3068 -ZW50aXR5 3069 -IGZhbWlseQ== 3070 -YWRkaW5n 3071 -IG9wdGlvbg== 3072 -IGV4aXN0 3073 -aWNh 3074 -IE9iamVjdA== 3075 -Njk= 3076 -J3Zl 3077 -dmVycw== 3078 -aXRpb25hbA== 3079 -Njc= 3080 -b3V0cHV0 3081 -IFRydWU= 3082 -IE9G 3083 -X3RpbWU= 3084 -IG9mZmVy 3085 -IH0pOwoK 3086 -SEVS 3087 -ZWdpbg== 3088 -IiI= 3089 -IHdhdGVy 3090 -IGNoZQ== 3091 -IE15 3092 -b3JlZA== 3093 -IHN0ZXA= 3094 -YW5jZXM= 3095 -Q0s= 3096 -QVk= 3097 -4Lg= 3098 -c3RydWN0aW9u 3099 -KEM= 3100 -MzAw 3101 -b3VjaA== 3102 -U3RyZWFt 3103 -YWN0aXZl 3104 -YW1h 3105 -RW50aXR5 3106 -cHJvZHVjdA== 3107 -KCl7Cg== 3108 -IGdvdmVybm1lbnQ= 3109 -IElE 3110 -YWpvcg== 3111 -QW5k 3112 -IGRpc3BsYXk= 3113 -0Ls= 3114 -IHRpbWVz 3115 -IGZvdXI= 3116 -IGZhcg== 3117 -IHByZXNlbnQ= 3118 -IE5T 3119 -IFwK 3120 -dWVzdA== 3121 -IGJhcw== 3122 -ZWNobw== 3123 -Y2hpbGQ= 3124 -aWZpZXI= 3125 -SGFuZGxlcg== 3126 -IGxpYg== 3127 -UHJvcGVydHk= 3128 -dHJhbnNsYXRpb24= 3129 -IHJvb20= 3130 -IG9uY2U= 3131 -IFtd 3132 -Y2VudGVy 3133 -PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0= 3134 -IHJlc3VsdHM= 3135 -IGNvbnRpbnVl 3136 -IHRhbGs= 3137 -X2dldA== 3138 -IGdyb3c= 3139 -LnN3 3140 -ZWI= 3141 -IFB1YmxpYw== 3142 -T1A= 3143 -ZWN1dGU= 3144 -b2xz 3145 -ICoq 3146 -Iik7Cgo= 3147 -IG1hc3M= 3148 -dXJlZA== 3149 -LmNsYXNz 3150 -b21pYw== 3151 -IG1lYW4= 3152 -aXBz 3153 -IGF1dA== 3154 -KTsNCg0K 3155 -IHVudGls 3156 -IG1hcmtldA== 3157 -IGFyZWE= 3158 -dWl0 3159 -IGxlbmd0aA== 3160 -IFdpdGg= 3161 -c3RydWN0b3I= 3162 -ZXZlbnQ= 3163 -Ij48 3164 -IFNw 3165 -SVY= 3166 -IG11cw== 3167 -aWZm 3168 -IGtpbmQ= 3169 -YXV0aG9y 3170 -b3VuZHM= 3171 -bWI= 3172 -X2tleQ== 3173 -NDE= 3174 -d2lkdGg= 3175 -cG9zaXRvcnk= 3176 -IGxpZ2h0 3177 -dWs= 3178 -Um93 3179 -b2hu 3180 -YWxm 3181 -dmlyb25tZW50 3182 -YXBwZXI= 3183 -b2xsZWN0aW9ucw== 3184 -IHNpZGU= 3185 -X2luZm8= 3186 -IGV4YW1wbGU= 3187 -aW1hcnk= 3188 -IHdy 3189 -IGNhbXA= 3190 -Y3JpYmU= 3191 -MjU1 3192 -Ii8= 3193 -IG1pc3M= 3194 -d2F5 3195 -IGJhc2Vk 3196 -IHBsYW4= 3197 -Vmlz 3198 -b21haW4= 3199 -dW5r 3200 -IGF3YXk= 3201 -VVA= 3202 -PFQ= 3203 -T1M= 3204 -aW9k 3205 -IE1vbg== 3206 -4oCZcmU= 3207 -IGxpaw== 3208 -w6c= 3209 -aXZlbHk= 3210 -LnY= 3211 -aW1lcg== 3212 -aXplcg== 3213 -U3Vi 3214 -IGJ1dHRvbg== 3215 -IFVw 3216 -IGV4cGVyaWVuY2U= 3217 -Q0w= 3218 -IHJlbmRlcg== 3219 -X3ZhbHVl 3220 -IG5lYXI= 3221 -VVJM 3222 -YWx0 3223 -IGNvdW50cnk= 3224 -aWJpbGl0eQ== 3225 -NTc= 3226 -KCksCg== 3227 -ZWFk 3228 -IGF1dGhvcg== 3229 -IHNwZWNpZmlj 3230 -YmFzZQ== 3231 -KG5hbWU= 3232 -b25lcw== 3233 -IERv 3234 -IGFsb25n 3235 -eWVhcg== 3236 -IGV4cHJlc3M= 3237 -Lic= 3238 -ZW52 3239 -IGJlZ2lu 3240 -IHNvZnR3YXJl 3241 -IGltcA== 3242 -IHdpbg== 3243 -w7Nu 3244 -IHRoaW5n 3245 -VHJhbnM= 3246 -IFRIRQ== 3247 -IDw/ 3248 -IHdoeQ== 3249 -IGRvZXNu 3250 -aWo= 3251 -Z2luZw== 3252 -CWc= 3253 -IHNpbmdsZQ== 3254 -b2Zmc2V0 3255 -YXJuaW5n 3256 -b2dyYXBo 3257 -bGV5 3258 -X2NvdW50 3259 -IGFuYWw= 3260 -Y3JlYXRl 3261 -L20= 3262 -IFJlZw== 3263 -OTg= 3264 -dW5jaA== 3265 -PSQ= 3266 -aXNr 3267 -IHJpZ2h0cw== 3268 -KE0= 3269 -ICIiIgo= 3270 -YXBlcg== 3271 -Lm1vZGVs 3272 -IHBv 3273 -ZW1wdHk= 3274 -YXJ0bWVudA== 3275 -IGFudA== 3276 -IFdoZW4= 3277 -IHdvbWVu 3278 -IEVk 3279 -IHNlYXNvbg== 3280 -IGRlc3Q= 3281 -w6M= 3282 -KGg= 3283 -IHBvc3NpYmxl 3284 -IHNldmVy 3285 -IGJ0bg== 3286 -IGRpZG4= 3287 -IHNlbnQ= 3288 -IGVuYw== 3289 -IGNvbW1hbmQ= 3290 -IF0sCg== 3291 -X3g= 3292 -IHJlY2VudA== 3293 -b2x1dGlvbg== 3294 -dmVjdG9y 3295 -IEJ5 3296 -IE1heQ== 3297 -IEFjdA== 3298 -u78= 3299 -IG1vbmV5 3300 -SU5U 3301 -YnNpdGU= 3302 -CXA= 3303 -Lg0K 3304 -77u/ 3305 -c2w= 3306 -YXR0ZXJu 3307 -IENsYXNz 3308 -IHRvbGQ= 3309 -dWRpbw== 3310 -Y3VycmVudA== 3311 -IGVxdQ== 3312 -IGF1dG8= 3313 -IFN0YXRl 3314 -ZGE= 3315 -bXNn 3316 -KSk7Cgo= 3317 -IHdvcmtpbmc= 3318 -IHF1ZXJ5 3319 -IEJy 3320 -IHdpbmRvdw== 3321 -YXV0aA== 3322 -b25seQ== 3323 -CXQ= 3324 -IGxlYXN0 3325 -YWdu 3326 -IGV4cGw= 3327 -aXR0ZXI= 3328 -YXJpbmc= 3329 -IGNvbHVtbg== 3330 -IEdlbmVyYWw= 3331 -Ijoi 3332 -ZXJhbA== 3333 -cmlvcg== 3334 -IHJlY29yZA== 3335 -SUI= 3336 -RVg= 3337 -IGRhdA== 3338 -IG1ha2luZw== 3339 -dWVk 3340 -IENhcg== 3341 -ZW1w 3342 -Ii4= 3343 -IE1lZA== 3344 -IGNsb3Nl 3345 -IHBlcmNlbnQ= 3346 -IHBhc3Q= 3347 -KGc= 3348 -Oig= 3349 -IHdyaXRl 3350 -IG1vdmU= 3351 -IHBhdA== 3352 -Q29udHJvbA== 3353 -LlRv 3354 -IHZp 3355 -Ki8K 3356 -aW5hdGU= 3357 -J2xs 3358 -YWdlZA== 3359 -TnVsbA== 3360 -IHNwZWNpYWw= 3361 -SVpF 3362 -IGNpdHk= 3363 -LyoK 3364 -IEVuZw== 3365 -aXhlZA== 3366 -aW5hcnk= 3367 -cHk= 3368 -IGVmZg== 3369 -YXJpbw== 3370 -IHRlbGw= 3371 -YXZvcg== 3372 -IHNlbGVjdA== 3373 -bGV2ZWw= 3374 -aW11bQ== 3375 -b3Blcg== 3376 -QnVpbGRlcg== 3377 -SVA= 3378 -JyksCg== 3379 -ZXNj 3380 -IGZvbnQ= 3381 -IjsKCg== 3382 -IEFt 3383 -aXNoZWQ= 3384 -aWxscw== 3385 -SW50ZXI= 3386 -T1c= 3387 -IGNvdXJzZQ== 3388 -IGxhdGU= 3389 -aWRkbGU= 3390 -NDM= 3391 -IGFtb3VudA== 3392 -IGFzeW5j 3393 -aW5v 3394 -Y3Vs 3395 -IOw= 3396 -YW5kbGU= 3397 -X3VzZXI= 3398 -IGJlbg== 3399 -IENhbA== 3400 -ICRf 3401 -IFJlcA== 3402 -IGVub3VnaA== 3403 -VG9rZW4= 3404 -LnVzZXI= 3405 -KGo= 3406 -U2M= 3407 -V2lkdGg= 3408 -bm93 3409 -YXRmb3Jt 3410 -IGxvb2tpbmc= 3411 -IGhvbGQ= 3412 -TW9kdWxl 3413 -SVRZ 3414 -dm8= 3415 -aXNvbg== 3416 -LkRhdGE= 3417 -eWM= 3418 -IHBvdA== 3419 -IFRydW1w 3420 -aWR1YWw= 3421 -aWRlcw== 3422 -cnQ= 3423 -IHByb3BlcnR5 3424 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 3425 -YW1ld29yaw== 3426 -Z28= 3427 -IGxvdw== 3428 -IHBhcmE= 3429 -IHByaWNl 3430 -dXJ5 3431 -IHRvZGF5 3432 -cm95 3433 -ICcv 3434 -IHBvbGl0 3435 -ICcn 3436 -eW1i 3437 -UGg= 3438 -IGFkdg== 3439 -IGF0dGFjaw== 3440 -IFN0ZQ== 3441 -Uk9N 3442 -NDAw 3443 -YW5h 3444 -IG1lYW5z 3445 -IHN0b3J5 3446 -aWRz 3447 -YWtlbg== 3448 -IG1lZXQ= 3449 -IG1vbQ== 3450 -IOKAmA== 3451 -ID8+ 3452 -IGRlbg== 3453 -b2JpbGU= 3454 -Y2hhbmdl 3455 -ICAgICAgICAgICAgCg== 3456 -aWNp 3457 -bmE= 3458 -IEZvcm0= 3459 -IHNvcnQ= 3460 -U2VsZWN0 3461 -cGFyZQ== 3462 -IHRob3VnaHQ= 3463 -X2Nvbg== 3464 -IHRhc2s= 3465 -b2N1cw== 3466 -IERF 3467 -IE1pbg== 3468 -IG9wdA== 3469 -CWJyZWFr 3470 -dW1lcg== 3471 -S0U= 3472 -dGhlbg== 3473 -IGRldA== 3474 -IFRlc3Q= 3475 -cG9ydHM= 3476 -IHJldmlldw== 3477 -KCcv 3478 -bW92ZQ== 3479 -IHN3aXRjaA== 3480 -RVJU 3481 -cGF0Y2g= 3482 -YW5ub3Q= 3483 -44I= 3484 -IGFib3Zl 3485 -aXRpdmU= 3486 -NTY= 3487 -IHF1ZXN0aW9u 3488 -IFF1 3489 -44CCCgo= 3490 -Z2xl 3491 -IHdvcmQ= 3492 -IHByb3ZpZGU= 3493 -IFJldHVybg== 3494 -IHJlc2VhcmNo 3495 -w6Nv 3496 -dXN0cg== 3497 -IHB1Ymxpc2g= 3498 -Y2hlbWE= 3499 -fX0= 3500 -IENPTg== 3501 -LWlu 3502 -YWxsYmFjaw== 3503 -IGNvdmVy 3504 -XFw= 3505 -Y29sb3I= 3506 -IElT 3507 -IHdoZXRoZXI= 3508 -aW1hdGU= 3509 -aXNj 3510 -QmFy 3511 -IGRpdg== 3512 -QmU= 3513 -b3Vybg== 3514 -IGhhdmluZw== 3515 -bGVt 3516 -cGxheWVy 3517 -YWJz 3518 -YW1lcmE= 3519 -bmV5 3520 -IGV4Yw== 3521 -Z2V0aGVy 3522 -cGxpZWQ= 3523 -YW8= 3524 -WyQ= 3525 -ICsr 3526 -aXBl 3527 -c2hvdw== 3528 -L2Q= 3529 -Wzo= 3530 -YWdlbWVudA== 3531 -bGV2 3532 -X0lE 3533 -OTc= 3534 -cmFyeQ== 3535 -YWRlcw== 3536 -X3Nl 3537 -YXVzZQ== 3538 -IGVtcGxveQ== 3539 -ICovDQo= 3540 -IGZyZQ== 3541 -ICdA 3542 -IGNvbXBsZXQ= 3543 -IGxhcmdl 3544 -cmFs 3545 -XHg= 3546 -IGZhYw== 3547 -PFN0cmluZw== 3548 -IGNyZWF0ZWQ= 3549 -dXBlcg== 3550 -LnN0YXRl 3551 -IGhvc3Q= 3552 -ZW5lcmlj 3553 -L2I= 3554 -KCE= 3555 -d2hpbGU= 3556 -aWFz 3557 -QlVH 3558 -ICk7Cgo= 3559 -IHJvbGU= 3560 -UmVn 3561 -IENvbG9y 3562 -U3RhcnQ= 3563 -IHBvcm4= 3564 -dG9w 3565 -IHdlYg== 3566 -IGRldg== 3567 -IGRlYWw= 3568 -KyspCg== 3569 -SW50ZWdlcg== 3570 -cG9zaXRpb24= 3571 -Lm9u 3572 -ICgi 3573 -5Lg= 3574 -IHByb2JsZW0= 3575 -c3Y= 3576 -IHByZXNz 3577 -QUJMRQ== 3578 -QVRJT04= 3579 -IFNlZQ== 3580 -YW5jaA== 3581 -IHRob3VnaA== 3582 -bGVlcA== 3583 -IDwhLS0= 3584 -IHBvaW50cw== 3585 -ICAgICAgICAgICAgICAgICAgICAgICAgIA== 3586 -Lko= 3587 -IDo6 3588 -cHRy 3589 -REI= 3590 -Kys7Cg== 3591 -LnBuZw== 3592 -bm9kZQ== 3593 -c29mdA== 3594 -cG9uZA== 3595 -IGV2ZXI= 3596 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 3597 -TWVudQ== 3598 -KCcj 3599 -IHNlcnZpY2Vz 3600 -cGc= 3601 -fSkK 3602 -cGFyYW1z 3603 -IGFjdHVhbGx5 3604 -ICIv 3605 -RW1wdHk= 3606 -TWV0aG9k 3607 -IGlkZW50 3608 -dW5pYw== 3609 -IG1pbGxpb24= 3610 -IGFmZg== 3611 -c3R5bGU= 3612 -IGNvbmM= 3613 -aW9z 3614 -aWdubWVudA== 3615 -VUxU 3616 -UHI= 3617 -IjsNCg== 3618 -IHVuZGVyc3RhbmQ= 3619 -dWFyeQ== 3620 -IGhhcHBlbg== 3621 -IHNlcnZlcg== 3622 -IENv 3623 -U0M= 3624 -IGxlcw== 3625 -IGZpbGVz 3626 -R3JpZA== 3627 -c3Fs 3628 -IG9mdGVu 3629 -IGluZm8= 3630 -X3Ry 3631 -c3Jj 3632 -b255 3633 -IHNwYWNl 3634 -dW1i 3635 -IHBhc3N3b3Jk 3636 -IHN0b3Jl 3637 -LAoK 3638 -IFdoYXQ= 3639 -Z2Vk 3640 -IEZhbHNl 3641 -VXM= 3642 -c3dlcg== 3643 -X2luZGV4 3644 -IGZvcm1hdA== 3645 -bW9zdA== 3646 -c20= 3647 -TmV3 3648 -IGRldGFpbHM= 3649 -IHByb2I= 3650 -IEFORA== 3651 -KCkNCg== 3652 -aWxhcg== 3653 -ICR7 3654 -cnlwdA== 3655 -LkNvbGxlY3Rpb25z 3656 -JHRoaXM= 3657 -IEZyZWU= 3658 -X29m 3659 -KGZhbHNl 3660 -ZGF0ZWQ= 3661 -ID4+ 3662 -IGZhY2U= 3663 -Q1RJT04= 3664 -IHNhdmU= 3665 -IHR5cA== 3666 -ZGV2 3667 -KCIj 3668 -QUdF 3669 -Y29udGFpbmVy 3670 -ZWRpdA== 3671 -UUw= 3672 -IGl0ZW1z 3673 -IHNvY2lhbA== 3674 -aWVu 3675 -IFJlYWN0 3676 -KS4KCg== 3677 -IG1hcg== 3678 -IHJlZHU= 3679 -IFJF 3680 -LnB1dA== 3681 -IG1ham9y 3682 -Q2VsbA== 3683 -bmV4dA== 3684 -IGV4cGVjdGVk 3685 -IHlldA== 3686 -IGluZGl2 3687 -dHJpYnV0ZXM= 3688 -YXRpcw== 3689 -YW1lZA== 3690 -IGZvb2Q= 3691 -U291cmNl 3692 -KHN0cmluZw== 3693 -ICsK 3694 -aXRlcw== 3695 -ZHI= 3696 -IG1lbWJlcnM= 3697 -IGNvbWI= 3698 -aXRlbXM= 3699 -IFBlcg== 3700 -VEg= 3701 -PVRydWU= 3702 -IGJhcg== 3703 -X1NF 3704 -Y29tbQ== 3705 -KHc= 3706 -KQoKCg== 3707 -IHNlbmQ= 3708 -IGluYw== 3709 -dW5zaWduZWQ= 3710 -RkE= 3711 -IHBhcmFtcw== 3712 -YXBwaW5n 3713 -cm9z 3714 -dWdpbg== 3715 -ZmE= 3716 -IGNvbm5lY3Rpb24= 3717 -IH07Cgo= 3718 -IGJlY29tZQ== 3719 -TW9kZQ== 3720 -IGV2 3721 -IGRpZmY= 3722 -IFVuaXRlZA== 3723 -SGVpZ2h0 3724 -ZnVsbHk= 3725 -aW1hZ2Vz 3726 -IG1ha2Vz 3727 -IGdsb2JhbA== 3728 -IGNvbnRhY3Q= 3729 -JzoK 3730 -IGFicw== 3731 -0LDQ 3732 -ZmxvYXQ= 3733 -IGV4Y2VwdA== 3734 -IFBvbA== 3735 -Q2hpbGQ= 3736 -dHlw 3737 -IGNlcnRhaW4= 3738 -acOzbg== 3739 -T1VU 3740 -IGltcHJv 3741 -aWxlcw== 3742 -IC0tPgo= 3743 -IFBhcnQ= 3744 -dmFsdWVz 3745 -b3Nz 3746 -Lyoq 3747 -aWxpdA== 3748 -IEV2ZW50 3749 -Y3VyaXR5 3750 -c3Rlcg== 3751 -IGNoYXJhY3Rlcg== 3752 -MTk4 3753 -IG5ld3M= 3754 -ICIs 3755 -IGRldmljZQ== 3756 -Y2Vs 3757 -bG9naW4= 3758 -aGVldA== 3759 -RGVmYXVsdA== 3760 -QCI= 3761 -CSA= 3762 -Y2xpY2s= 3763 -KHZhbHVl 3764 -IEFi 3765 -IHByZXZpb3Vz 3766 -RVJST1I= 3767 -b2NhbA== 3768 -IG1hdGVyaWFs 3769 -IGJlbG93 3770 -IENocmlzdA== 3771 -IG1lZGlh 3772 -Y292ZXI= 3773 -IFVJ 3774 -IGZhaWw= 3775 -IGJsYWNr 3776 -IGNvbXBvbmVudA== 3777 -IEFtZXJpY2Fu 3778 -IGFkZGVk 3779 -IGJ1eQ== 3780 -c3RpdA== 3781 -IGNhbWU= 3782 -IGRlbGV0ZQ== 3783 -cHJvcGVydHk= 3784 -b2Rpbmc= 3785 -IGNhcmQ= 3786 -cm9wcw== 3787 -IGh0dHBz 3788 -IHJvb3Q= 3789 -IGhhbmRsZQ== 3790 -Q0M= 3791 -QmFjaw== 3792 -ZW1wbGF0ZQ== 3793 -IGdldHRpbmc= 3794 -X2J5 3795 -bWFpbA== 3796 -X3No 3797 -LmFzc2VydA== 3798 -IERlYw== 3799 -KHRydWU= 3800 -IGNvbXB1dA== 3801 -IGNsYWlt 3802 -Jz0+ 3803 -IFN1Yg== 3804 -IGFpcg== 3805 -b3Bz 3806 -bmF2 3807 -ZW1lbnRz 3808 -KGlk 3809 -IGVudGVy 3810 -YW5nZWQ= 3811 -RW5k 3812 -IGxvY2F0aW9u 3813 -IG5pZ2h0 3814 -IGRvaW5n 3815 -IFJlZA== 3816 -bGlu 3817 -fQoKCg== 3818 -dmlkZXI= 3819 -IHBpY2s= 3820 -IHdhdGNo 3821 -ZXNzYWdlcw== 3822 -IGh1bWFu 3823 -IGRhbQ== 3824 -cGVuZA== 3825 -ZGly 3826 -IHRheA== 3827 -IGdpcmw= 3828 -cmVldA== 3829 -IGJveA== 3830 -IHN0cm9uZw== 3831 -KHY= 3832 -cmVs 3833 -IGludGVyZmFjZQ== 3834 -IG1zZw== 3835 -ZmVjdA== 3836 -X2F0 3837 -IGhvdXNl 3838 -IHRyYWNr 3839 -Jyk7Cgo= 3840 -amU= 3841 -IEpvaG4= 3842 -aXN0cg== 3843 -KFM= 3844 -dWJl 3845 -IGNl 3846 -aXR0ZWQ= 3847 -VkVS 3848 -Kik= 3849 -cGFyZW50 3850 -IGFwcGxpY2F0aW9u 3851 -YW55 3852 -LnN3aW5n 3853 -IHBhY2s= 3854 -XHU= 3855 -IHByYWN0 3856 -IHNlY3Rpb24= 3857 -Y3R4 3858 -IHVuc2lnbmVk 3859 -LlBvaW50 3860 -IE9uZQ== 3861 -xLE= 3862 -aXBsZQ== 3863 -YWlk 3864 -0YM= 3865 -VmVjdG9y 3866 -Ynl0ZQ== 3867 -IHdhaXQ= 3868 -IMOg 3869 -w6U= 3870 -IHRvZ2V0aGVy 3871 -IHRocm93cw== 3872 -Rk8= 3873 -Jykp 3874 -aG9zdA== 3875 -aXNpbmc= 3876 -LnZpZXc= 3877 -IHRlcm1z 3878 -ZnJhbWV3b3Jr 3879 -LXI= 3880 -IGFwcGx5 3881 -IHNlc3Npb24= 3882 -T3B0aW9ucw== 3883 -dWdnZXN0 3884 -IG90aGVycw== 3885 -d2l0dGVy 3886 -IGZ1bmQ= 3887 -SW5pdA== 3888 -X18o 3889 -ZW5zb3I= 3890 -R0VU 3891 -IHNldmVyYWw= 3892 -aWk= 3893 -W2o= 3894 -SU8= 3895 -IHRlbXBsYXRl 3896 -UG9zaXRpb24= 3897 -IGVjb24= 3898 -YWNoaW5l 3899 -IGls 3900 -LnNwcmluZw== 3901 -bWFpbg== 3902 -ZWx0 3903 -aW1lbnQ= 3904 -UmVj 3905 -bW0= 3906 -IFVuaXZlcnNpdHk= 3907 -dXJzb3I= 3908 -ICAgICAgICAgICAgICAgICAgICA= 3909 -R0w= 3910 -aWN0dXJl 3911 -aXRodWI= 3912 -Y2Vy 3913 -Y2FzdA== 3914 -RnJvbQ== 3915 -YWxlcw== 3916 -IHN1YmplY3Q= 3917 -cGFzc3dvcmQ= 3918 -bnk= 3919 -IGVzYw== 3920 -LndyaXRl 3921 -77yM 3922 -V2hhdA== 3923 -Lkg= 3924 -IGhpc3Rvcnk= 3925 -IEZl 3926 -IGluZGl2aWR1YWw= 3927 -dW5pdA== 3928 -IC0tPg== 3929 -IGR1 3930 -SVNU 3931 -IHVzZXJz 3932 -ZnM= 3933 -ZmFsc2U= 3934 -dW50 3935 -VGl0bGU= 3936 -IG1vdA== 3937 -IGZ1dHVyZQ== 3938 -YWNoZWQ= 3939 -IHN0YXJ0ZWQ= 3940 -IG1vZGU= 3941 -ICc8 3942 -X2FycmF5 3943 -IGF4 3944 -J107Cg== 3945 -aXJlcw== 3946 -VGhlcmU= 3947 -dWdodA== 3948 -dG1s 3949 -cG9zZWQ= 3950 -aWN1bHQ= 3951 -IHRvb2s= 3952 -IGdhbWVz 3953 -IH19 3954 -ID8+Cg== 3955 -IHByb2R1Y3Rz 3956 -SXM= 3957 -IGJhZA== 3958 -IERlcw== 3959 -LnBhdGg= 3960 -JwoK 3961 -IFBvc3Q= 3962 -YXZlbA== 3963 -KDo= 3964 -MTUw 3965 -IG5lZWRz 3966 -IGtub3du 3967 -Rmw= 3968 -IGV4ZWM= 3969 -IHNlZW4= 3970 -NTE= 3971 -dW1l 3972 -IGJvcmRlcg== 3973 -IGxpdmU= 3974 -dGVtcA== 3975 -UGVy 3976 -IHZhcmlhYmxl 3977 -aWV0 3978 -IERlZg== 3979 -IGdl 3980 -ZW1l 3981 -X2JhY2s= 3982 -Zmlyc3Q= 3983 -IHByb3ZpZGVk 3984 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8= 3985 -IGZpbGVuYW1l 3986 -IGhvcGU= 3987 -dWx5 3988 -YXV0bw== 3989 -ZmluZA== 3990 -X3N0cmluZw== 3991 -YnRu 3992 -aXR1ZGU= 3993 -QXR0cmlidXRl 3994 -IHlvdW5n 3995 -LnR4dA== 3996 -IHdlYnNpdGU= 3997 -IFByb3A= 3998 -IGV5 3999 -PigpOwo= 4000 -aW9uYWw= 4001 -QVJS 4002 -aWN0aW9uYXJ5 4003 -dXJ0aGVy 4004 -Ljwv 4005 -QUxM 4006 -IHN0dWR5 4007 -aWxp 4008 -IG5ldHdvcms= 4009 -eWw= 4010 -aXN0YW5jZQ== 4011 -T0s= 4012 -TlU= 4013 -cmVzdA== 4014 -IFNU 4015 -aWNyb3NvZnQ= 4016 -IGxpbWl0 4017 -IGN1dA== 4018 -KCk6Cg== 4019 -IGNvdQ== 4020 -b2du 4021 -IHNpemVvZg== 4022 -aXZhbA== 4023 -IHdlbnQ= 4024 -Lno= 4025 -TGluaw== 4026 -IGZpcmU= 4027 -IGFjcm9zcw== 4028 -IGNvbW11bml0eQ== 4029 -cmVnaW9u 4030 -TkU= 4031 -UmVm 4032 -IG9mZmljaWFs 4033 -IHZpc2l0 4034 -b2x2ZQ== 4035 -IHJlY2VpdmVk 4036 -IHRva2Vu 4037 -IG1vbnRocw== 4038 -IGFuaW0= 4039 -IHBhcnRpY3VsYXI= 4040 -c3R5bGVz 4041 -aWNv 4042 -IGVzcw== 4043 -ODc= 4044 -LkNvbnRyb2w= 4045 -IMOp 4046 -YmFsbA== 4047 -IGxlYXJu 4048 -aW5kaW5n 4049 -VmFy 4050 -IGRlY2w= 4051 -KGVycg== 4052 -TEVDVA== 4053 -T25l 4054 -cGhh 4055 -IH4= 4056 -Zm9ydA== 4057 -YXN1cmU= 4058 -IG1pbmQ= 4059 -IEVuZA== 4060 -Q2hlY2s= 4061 -IHF1aWNr 4062 -Iiks 4063 -QU5E 4064 -dXRpb25z 4065 -QmFzZQ== 4066 -X19fX19fX18= 4067 -IGNvbW1lbnQ= 4068 -SU5F 4069 -4oCZdmU= 4070 -QnV0 4071 -IEVs 4072 -IFVz 4073 -IGFkbWlu 4074 -bWFyaw== 4075 -IE5hbWU= 4076 -YAo= 4077 -IFR5cGU= 4078 -YW1pYw== 4079 -cGM= 4080 -bG9vcg== 4081 -RlQ= 4082 -IG9wcA== 4083 -Y2tldA== 4084 -KS0+ 4085 -dHg= 4086 -IHB1cg== 4087 -dWVs 4088 -eW1ib2w= 4089 -dWF0aW9u 4090 -YW5nZXI= 4091 -IGJhY2tncm91bmQ= 4092 -ZWNlc3M= 4093 -ZWZpbmVk 4094 -Li4uLi4uLi4= 4095 -IGRlc2NyaXB0aW9u 4096 -IHJlcHJlc2VudA== 4097 -IikpOwo= 4098 -cHJlc3Npb24= 4099 -cm93c2Vy 4100 -IHNlcmllcw== 4101 -d2FyZHM= 4102 -NTI= 4103 -KCRf 4104 -YWlzZQ== 4105 -IGhvdA== 4106 -YWNpdHk= 4107 -cmllcw== 4108 -YWN0aW9ucw== 4109 -Q3JlYXRl 4110 -YWRpbw== 4111 -YW1wbGVz 4112 -IG9yaWdpbmFs 4113 -ZW5zaXZl 4114 -Zm9udA== 4115 -c3RyZWFt 4116 -77u/dXNpbmc= 4117 -LnNwcmluZ2ZyYW1ld29yaw== 4118 -MDAx 4119 -c2VydmVy 4120 -IGJpbGw= 4121 -QUNL 4122 -aWxlbmFtZQ== 4123 -IGZyYW1l 4124 -ID0K 4125 -RWRpdA== 4126 -YWRpdXM= 4127 -IGRyYXc= 4128 -YW5rcw== 4129 -IGRldGVy 4130 -IGNvbWVz 4131 -X2ludA== 4132 -IGZvcmVhY2g= 4133 -YW5nbGU= 4134 -IGVsZWN0 4135 -cGVjdGVk 4136 -SGVhZGVy 4137 -aXN0cmF0aW9u 4138 -RmFsc2U= 4139 -IEdhbWU= 4140 -IGZpbHRlcg== 4141 -QWN0aXZpdHk= 4142 -IGxhcmc= 4143 -aW5pdGlvbg== 4144 -ICI8 4145 -MjU2 4146 -aXNlZA== 4147 -IHJlbW92ZQ== 4148 -IFRyYW5z 4149 -bWV0 4150 -c2Vl 4151 -Rm9ybWF0 4152 -Q29tbWFuZA== 4153 -IEVY 4154 -Tm9uZQ== 4155 -IGZyb250 4156 -QVNF 4157 -IFJlYw== 4158 -b3VuZGF0aW9u 4159 -IHZv 4160 -OTY= 4161 -PVwi 4162 -KCo= 4163 -Q2hhbmdl 4164 -LldyaXRl 4165 -Z3JvdXA= 4166 -aWVudHM= 4167 -dXk= 4168 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 4169 -IGRpZw== 4170 -aHI= 4171 -KC0= 4172 -IGdlbg== 4173 -bnVtYmVy 4174 -dmVj 4175 -dXJvcGU= 4176 -ZW50cnk= 4177 -TEw= 4178 -IHN0ZQ== 4179 -VmFsaWQ= 4180 -J10s 4181 -X3BhcmFt 4182 -IHNlbGVjdGVk 4183 -IGFjY29yZGluZw== 4184 -IERpcw== 4185 -IHV0aWw= 4186 -QnVmZmVy 4187 -X2Vycm9y 4188 -IGFzc29jaQ== 4189 -X1NJWkU= 4190 -IHdvcg== 4191 -IHByaW50Zg== 4192 -cmFn 4193 -wqA= 4194 -REQ= 4195 -IFZhbA== 4196 -IGFjdGl2 4197 -RW5n 4198 -ZXRpbWU= 4199 -IHZpcnR1YWw= 4200 -YWlnbg== 4201 -YXVy 4202 -IFByZXM= 4203 -IEV4Y2VwdGlvbg== 4204 -IGFueXRoaW5n 4205 -IE9mZg== 4206 -IGhvdXJz 4207 -IHdhcg== 4208 -QXJncw== 4209 -YWdpbmc= 4210 -IG1vZGVscw== 4211 -IFRpbWU= 4212 -T2I= 4213 -YW1z 4214 -am95 4215 -IGVhcmx5 4216 -LnJlYWQ= 4217 -ODY= 4218 -IGNlbnRlcg== 4219 -IEluaXRpYWw= 4220 -IGxhbmd1YWdl 4221 -bGVuZ3Ro 4222 -eHk= 4223 -IHNu 4224 -IGluZg== 4225 -UG9zdA== 4226 -IGFnbw== 4227 -IGVhc3k= 4228 -X2NvZGU= 4229 -IEFOWQ== 4230 -X2No 4231 -IGRvd25sb2Fk 4232 -KFQ= 4233 -YXZlZA== 4234 -4oCT 4235 -IHN0dWRlbnRz 4236 -IGZpZw== 4237 -bGlnaHQ= 4238 -eHg= 4239 -IGJ1ZmZlcg== 4240 -IERlcA== 4241 -IE1hdGg= 4242 -SVRI 4243 -IHZhcmk= 4244 -IGR1ZQ== 4245 -RmFjdG9yeQ== 4246 -IHBvcg== 4247 -IGVw 4248 -b3R5cGU= 4249 -IGNhbm5vdA== 4250 -IHdoaXRl 4251 -PGludA== 4252 -dGVybg== 4253 -IHJlZ2lzdGVy 4254 -IHByZWQ= 4255 -Y2x1cw== 4256 -X2RhdGU= 4257 -IC8qKg== 4258 -IGF1dGg= 4259 -IFtdCg== 4260 -IHBlcmlvZA== 4261 -bm93bg== 4262 -IHZvdA== 4263 -IHNjcmVlbg== 4264 -J2Q= 4265 -VHlwZXM= 4266 -IHRtcA== 4267 -0LXQ 4268 -dXJhbA== 4269 -IGJlbmVm 4270 -X3k= 4271 -IG5ldA== 4272 -IFN0YXRlcw== 4273 -J11bJw== 4274 -IE5l 4275 -IE5PVA== 4276 -IG5lZw== 4277 -MTAy 4278 -IGNvbW1vbg== 4279 -c2NvcGU= 4280 -IGNyZWQ= 4281 -Z2Vz 4282 -X1RZUEU= 4283 -IHN1Z2dlc3Q= 4284 -b29t 4285 -LgoKCg== 4286 -IGFjY2VwdA== 4287 -IHJhbmRvbQ== 4288 -ZXJt 4289 -IFZlY3Rvcg== 4290 -d2l0aA== 4291 -VEVS 4292 -KHN0cg== 4293 -IHJlc3BvbnM= 4294 -IGhpdA== 4295 -LlNldA== 4296 -Z3JpZA== 4297 -cmlh 4298 -IGNsaWNr 4299 -dW5kbGU= 4300 -Q2FzZQ== 4301 -aW5zZXJ0 4302 -VXRpbHM= 4303 -ICIiIg== 4304 -IGltcGxlbWVudA== 4305 -YXRhbA== 4306 -dGVtcHQ= 4307 -dGVtcGxhdGU= 4308 -b2Ny 4309 -cmV0dXJucw== 4310 -IHBsYXllcnM= 4311 -dXNlcnM= 4312 -ZWRlZg== 4313 -IFRoZXNl 4314 -IGFtb25n 4315 -IGRlYg== 4316 -aGE= 4317 -LmdldEVsZW1lbnQ= 4318 -IGNpcmM= 4319 -IGFuc3dlcg== 4320 -IHdhbGs= 4321 -IHRyZWF0 4322 -IEdl 4323 -IENyZWF0ZQ== 4324 -IGFnZQ== 4325 -IHJlcQ== 4326 -T1NU 4327 -YW5ndWxhcg== 4328 -0Y8= 4329 -IGZpdmU= 4330 -NTM= 4331 -IGRpc3RyaWJ1dGVk 4332 -IGZyaWVuZA== 4333 -VFA= 4334 -IGNsZWFu 4335 -b3dz 4336 -LkNvbnRyb2xz 4337 -ZGlz 4338 -IHdvcmRz 4339 -Lmlv 4340 -enk= 4341 -IGhlYWRlcg== 4342 -IENoZWNr 4343 -4oCZbQ== 4344 -anVzdA== 4345 -aG9sZGVy 4346 -PSI8Pw== 4347 -IEdOVQ== 4348 -IENvbA== 4349 -aW1lc3Q= 4350 -ZW50aWM= 4351 -ewoK 4352 -IHRyZQ== 4353 -bGFzdA== 4354 -bGE= 4355 -IFlvcms= 4356 -TG8= 4357 -IGRpc2N1c3M= 4358 -IEdvZA== 4359 -IGlzc3Vl 4360 -cmV3 4361 -V2luZG93 4362 -IGxhbmQ= 4363 -MTIw 4364 -IHN0cmVhbQ== 4365 -IFBhcg== 4366 -IHF1YWxpdHk= 4367 -UGFy 4368 -X251bQ== 4369 -NTQ= 4370 -IHNhbA== 4371 -ZWx2ZXM= 4372 -T1JE 4373 -KHVzZXI= 4374 -IHdvcmtz 4375 -IGhhbGY= 4376 -ZW5zZXM= 4377 -dmFz 4378 -IHBvbGljZQ== 4379 -KCIv 4380 -dWE= 4381 -IHNpbXBsZQ== 4382 -QWRkcmVzcw== 4383 -IGVtcHR5 4384 -ZXNo 4385 -MTI4 4386 -VXBkYXRl 4387 -IENyZWF0ZWQ= 4388 -KCcu 4389 -KS4K 4390 -ICAgICAgICAgICAgICAgICAg 4391 -IGFncmU= 4392 -IEZST00= 4393 -IGNvb2s= 4394 -IGV2ZXJ5dGhpbmc= 4395 -aWxpdGllcw== 4396 -LnN0YXR1cw== 4397 -IHJlbGF0aW9ucw== 4398 -ZXh0ZXJu 4399 -IG5vdGhpbmc= 4400 -IHJ1bm5pbmc= 4401 -CXZvaWQ= 4402 -Ukk= 4403 -X2E= 4404 -X0NPTg== 4405 -cG9y 4406 -LnN1Yg== 4407 -cmVxdWlyZQ== 4408 -IENpdHk= 4409 -IFdlc3Q= 4410 -IG1vcg== 4411 -c3RvcmU= 4412 -RXF1YWxz 4413 -b2Rlcg== 4414 -IG5h 4415 -IFtb 4416 -ICgn 4417 -IERvbg== 4418 -RVJT 4419 -L3A= 4420 -Lmpzb24= 4421 -YWJvcg== 4422 -IHNvbWVvbmU= 4423 -X3RleHQ= 4424 -LmNzcw== 4425 -LlRhYg== 4426 -IFNvbWU= 4427 -YXRv 4428 -ZG91Ymxl 4429 -IHNoYXJl 4430 -KHZvaWQ= 4431 -X2Rpcg== 4432 -IHVy 4433 -U3RhY2s= 4434 -IFdvcmxk 4435 -Llg= 4436 -c3RyYWN0 4437 -SG93 4438 -LkdlbmVyaWM= 4439 -aWNsZXM= 4440 -IGVudHJ5 4441 -IGNoYW5nZXM= 4442 -IHBlcnNvbmFs 4443 -KEE= 4444 -IG9mZnNldA== 4445 -X3B0cg== 4446 -IHBpZQ== 4447 -IEphbg== 4448 -LWdyb3Vw 4449 -bW9kdWxl 4450 -SXRlbXM= 4451 -IEhvd2V2ZXI= 4452 -dmVyYWdl 4453 -LkZvbnQ= 4454 -IGV2ZW50cw== 4455 -Lm1pbg== 4456 -IGludm9s 4457 -emE= 4458 -IHdob2xl 4459 -IG5lZWRlZA== 4460 -IGxpa2VseQ== 4461 -cmllZg== 4462 -T1JN 4463 -dmVyc2lvbg== 4464 -IGZpZ2h0 4465 -IGVpbg== 4466 -RnJhbWU= 4467 -MTk3 4468 -Z2Vu 4469 -IE91dA== 4470 -YXZpZ2F0aW9u 4471 -TGVuZ3Ro 4472 -aWxsZWQ= 4473 -cXVlbmNl 4474 -ICE9PQ== 4475 -IFNvZnR3YXJl 4476 -IHdyaXRpbmc= 4477 -IHJhdGU= 4478 -J10sCg== 4479 -UGFuZWw= 4480 -aW5uZXI= 4481 -IFsi 4482 -IHR3 4483 -Y2Q= 4484 -IDsK 4485 -X3N0YXRl 4486 -IFNt 4487 -IE1hcms= 4488 -KSkKCg== 4489 -cHJvdA== 4490 -IE1y 4491 -bWV0aG9k 4492 -dXN0b21lcg== 4493 -SWNvbg== 4494 -IGNvcnJlY3Q= 4495 -KG9iamVjdA== 4496 -IE1vcmU= 4497 -IGZhbGw= 4498 -IHZvbA== 4499 -IGRldmVsb3BtZW50 4500 -ZW50bHk= 4501 -IHNp 4502 -bWVkaQ== 4503 -dmluZw== 4504 -UFA= 4505 -YWtlcg== 4506 -IGluZHU= 4507 -IGVsaWY= 4508 -IHByZXQ= 4509 -IGJlbGlldmU= 4510 -bnM= 4511 -b21ldA== 4512 -MTIz 4513 -IEludGVybg== 4514 -UmVjdA== 4515 -U28= 4516 -LmVycm9y 4517 -UmVhZA== 4518 -IGZlYXR1cmVz 4519 -IG1pbnV0ZXM= 4520 -LS0t 4521 -YXNpbmc= 4522 -Y3JldA== 4523 -Ij4NCg== 4524 -LmFubm90 4525 -IGNvbGxlY3Rpb24= 4526 -Jy4= 4527 -IHNpbWlsYXI= 4528 -IHRha2Vu 4529 -KCIl 4530 -T3JkZXI= 4531 -J10K 4532 -LW1k 4533 -IFRI 4534 -YWNlZA== 4535 -IGlzbg== 4536 -L2o= 4537 -IHNvbg== 4538 -Z3JhcGg= 4539 -IEludGVnZXI= 4540 -IG5lY2Vzcw== 4541 -cmVlbg== 4542 -IHVt 4543 -IFw8 4544 -IG1vbWVudA== 4545 -IGJyaW5n 4546 -IGluZGlj 4547 -eXNpcw== 4548 -TGV2ZWw= 4549 -dmVyc2U= 4550 -dXJyZW5j 4551 -X3Rlc3Q= 4552 -IGVudGlyZQ== 4553 -RG93bg== 4554 -IH0KCgo= 4555 -KHJlc3VsdA== 4556 -IFJlYWQ= 4557 -w6g= 4558 -TW9k 4559 -IHRyeWluZw== 4560 -IiksCg== 4561 -IG1lbWJlcg== 4562 -IENvcg== 4563 -T0RP 4564 -LWNvbnRyb2w= 4565 -dW50aW1l 4566 -IFNpbQ== 4567 -RGlhbG9n 4568 -cGxvdA== 4569 -X29u 4570 -IHBoeXM= 4571 -fS8= 4572 -IG5hbWVzcGFjZQ== 4573 -CQ0K 4574 -YWNj 4575 -UGxheWVy 4576 -QVJF 4577 -ODk= 4578 -IGZvb3Q= 4579 -IGJvYXJk 4580 -cGFydA== 4581 -IHN1cw== 4582 -d2lzZQ== 4583 -IE1j 4584 -IHB1c2g= 4585 -QVRB 4586 -IHBsZWFzZQ== 4587 -cmllZA== 4588 -d2VldA== 4589 -Yml0 4590 -aWRlZA== 4591 -VkU= 4592 -IFN3 4593 -VUI= 4594 -IHR5cGVz 4595 -ZWRpYQ== 4596 -IGNsb3M= 4597 -YWNlYm9vaw== 4598 -V2hlbg== 4599 -IGVkaXQ= 4600 -aWdnZXI= 4601 -IGVuZXJn 4602 -Q29udGFpbmVy 4603 -IHBob3Q= 4604 -IENvdW50 4605 -IEV1cm9wZQ== 4606 -Lklz 4607 -IFJ1c3M= 4608 -cGVlZA== 4609 -IFN0cg== 4610 -IHB5 4611 -IGN1bHQ= 4612 -IGRlZmluZWQ= 4613 -Y2NvdW50 4614 -IG9idA== 4615 -LkxvY2F0aW9u 4616 -IHRocmVhZA== 4617 -aWxsZQ== 4618 -IGluc3RlYWQ= 4619 -c3Ryb25n 4620 -IFNlYw== 4621 -VVJF 4622 -IGlkZWE= 4623 -LnNl 4624 -ZW15 4625 -c2VsZWN0ZWQ= 4626 -Q29ubmVjdGlvbg== 4627 -YWNpbmc= 4628 -dGhyZWFk 4629 -Lm5leHQ= 4630 -IGNvbGw= 4631 -IGZpbG0= 4632 -aXN0aWM= 4633 -IGNvbXBldA== 4634 -IGNvbm4= 4635 -dGhvdWdo 4636 -IGNvbXBhbg== 4637 -b2NrZXQ= 4638 -IHRlYWNo 4639 -PSg= 4640 -IHBob25l 4641 -IGFjdGl2ZQ== 4642 -Nzk= 4643 -ZGVsZXRl 4644 -MTAx 4645 -dHJpZXM= 4646 -IG1v 4647 -IGRlYXRo 4648 -fSk7Cgo= 4649 -b2NvbA== 4650 -V2lkZ2V0 4651 -IGFydGljbGU= 4652 -cm9kdQ== 4653 -YW5kaWQ= 4654 -0Ys= 4655 -IENy 4656 -a2E= 4657 -KCk6 4658 -bG9vZA== 4659 -CQkJCg== 4660 -IGFsbW9zdA== 4661 -IHNlbGw= 4662 -ZXJ2bGV0 4663 -cmlw 4664 -VW5pdA== 4665 -IGFwcGxpYw== 4666 -IGNvbm5lY3Q= 4667 -IGZlYXR1cmU= 4668 -IHZpYQ== 4669 -Jyks 4670 -IGxpbQ== 4671 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 4672 -IEd1 4673 -RW5naW5l 4674 -IGVucw== 4675 -IGVudmlyb25tZW50 4676 -YmxvY2s= 4677 -SEVSRQ== 4678 -TlVMTA== 4679 -Z3k= 4680 -dGFn 4681 -KSku 4682 -ZXhw 4683 -IGNvbXBs 4684 -IGluc3RhbGw= 4685 -IGNvbXBsZXRl 4686 -cXVldWU= 4687 -YXR1cmFs 4688 -IGdlbmVyYWw= 4689 -dGhvbg== 4690 -IGFza2Vk 4691 -b3Jlcw== 4692 -KHJlcw== 4693 -IHJlc2VydmVk 4694 -U1A= 4695 -IOKApg== 4696 -xYI= 4697 -IHNpZ25pZmlj 4698 -T2Zm 4699 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 4700 -IEFn 4701 -IEp1c3Q= 4702 -IEVycm9y 4703 -IGluZmw= 4704 -YWRhdGE= 4705 -IGljb24= 4706 -YXNrcw== 4707 -Jyc= 4708 -X0xP 4709 -Py4= 4710 -YWNjb3VudA== 4711 -ICgq 4712 -JykKCg== 4713 -cmFw 4714 -X3Zhcg== 4715 -IEZPUg== 4716 -IHBhcnR5 4717 -IFlvdXI= 4718 -Y2F0 4719 -c3RyeQ== 4720 -Lm5ldw== 4721 -Ym9vdA== 4722 -IE5vdg== 4723 -IHZlY3Rvcg== 4724 -IG5vcm1hbA== 4725 -IGZ1cnRoZXI= 4726 -UmVwb3NpdG9yeQ== 4727 -ODAw 4728 -IGRhdGFiYXNl 4729 -YXR0bGU= 4730 -IG11c2lj 4731 -IHNwZWVk 4732 -IGRvYw== 4733 -cHJvY2Vzcw== 4734 -SUdIVA== 4735 -LnBhcnNl 4736 -IHRha2luZw== 4737 -IHZpb2w= 4738 -Y2VlZA== 4739 -IEFmdGVy 4740 -IGZvcndhcmQ= 4741 -IGNyaXQ= 4742 -Ii8+Cg== 4743 -cm90 4744 -IGZhaWxlZA== 4745 -ZWZvcmU= 4746 -IGNvbmNlcm4= 4747 -b2U= 4748 -YmE= 4749 -IHNlbmRlcg== 4750 -IHRlcm0= 4751 -aGFz 4752 -PSIj 4753 -IHBvdGVudGlhbA== 4754 -TnVt 4755 -IHB1Ymxpc2hlZA== 4756 -LmNsb3Nl 4757 -IEltYWdl 4758 -c3RyYWludA== 4759 -VUQ= 4760 -IE9i 4761 -IHByb2JhYmx5 4762 -bGlt 4763 -IjoK 4764 -b2x1bWU= 4765 -IGNvbnN1bQ== 4766 -NzY= 4767 -YWd1ZQ== 4768 -ZW5zaW9ucw== 4769 -IGludmVzdGln 4770 -LXllYXI= 4771 -Jyk7 4772 -LXNt 4773 -IGVuam95 4774 -b3JpZw== 4775 -ZXJpbmc= 4776 -Y3A= 4777 -bGVhc2Vk 4778 -cGxlbWVudHM= 4779 -IHJldHVybnM= 4780 -cGF0 4781 -Qk8= 4782 -IEhvdXNl 4783 -LkxhYmVs 4784 -IHdlaWdodA== 4785 -aWdoYg== 4786 -IGNvbmRpdGlvbnM= 4787 -IGV4Y2VwdGlvbg== 4788 -ZGVzY3JpcHRpb24= 4789 -IHRyYWQ= 4790 -LXRv 4791 -IHt9 4792 -IG1vZHVsZQ== 4793 -RU5E 4794 -LmFw 4795 -LnByb3Bz 4796 -IGNvbnN0cnVjdG9y 4797 -YXZlcw== 4798 -IGZhdm9y 4799 -IE5vdw== 4800 -O2k= 4801 -IE1haW4= 4802 -X2s= 4803 -ZXJpZXM= 4804 -4oCZbGw= 4805 -dHJhbnNmb3Jt 4806 -aW1lc3RhbXA= 4807 -UHJl 4808 -IG1lcg== 4809 -LnJlcw== 4810 -c3RhbnQ= 4811 -TG9jYXRpb24= 4812 -X05BTUU= 4813 -IGxvc3M= 4814 -IAoK 4815 -bmV0 4816 -IGVuZ2luZQ== 4817 -QmxvY2s= 4818 -IGlzc3Vlcw== 4819 -IHBhcnNl 4820 -IEJhcg== 4821 -IHN0YXk= 4822 -IEpTT04= 4823 -IGRvbQ== 4824 -YWlycw== 4825 -d25lcg== 4826 -IGxvd2Vy 4827 -IiwNCg== 4828 -IERlbQ== 4829 -dWZhY3Q= 4830 -IHBz 4831 -IHBlcmZlY3Q= 4832 -Ukw= 4833 -IGVkdWM= 4834 -bHM= 4835 -ZW1vcnk= 4836 -QVJSQU5U 4837 -dWdl 4838 -IGV4YWN0 4839 -LmtleQ== 4840 -YWxsZWQ= 4841 -ZWNo 4842 -aWVm 4843 -XC8= 4844 -b2tl 4845 -IGZvcm1lcg== 4846 -YWxsb2M= 4847 -IHNpeA== 4848 -aWRh 4849 -IG1hcmdpbg== 4850 -IGhlYXJ0 4851 -YWxk 4852 -cGFjaw== 4853 -LmdldEVsZW1lbnRCeUlk 4854 -IFdBUlJBTlQ= 4855 -IHJhdGhlcg== 4856 -IGJ1aWxkaW5n 4857 -ZXJtYW4= 4858 -bGljZQ== 4859 -IHF1ZXN0aW9ucw== 4860 -aXplcw== 4861 -bGVnZQ== 4862 -aXJlY3Rvcnk= 4863 -IGpl 4864 -IGNhcw== 4865 -cHJvcHM= 4866 -dXRm 4867 -IHNlY3VyaXR5 4868 -IGhvd2V2ZXI= 4869 -d2VpZ2h0 4870 -IGluc2lkZQ== 4871 -IHByZXNpZGVudA== 4872 -Q2hhcg== 4873 -IFdJVEg= 4874 -Lm1hcA== 4875 -IGdyYXBo 4876 -IHRhZw== 4877 -X3N0YXR1cw== 4878 -IGF0dGVtcHQ= 4879 -b3Bw 4880 -dXNlcw== 4881 -CWNvbnN0 4882 -IHJvdW5k 4883 -LCQ= 4884 -IGZyaWVuZHM= 4885 -RW1haWw= 4886 -Pz4= 4887 -UmVzb3VyY2U= 4888 -S0VZ 4889 -b3Nw 4890 -LnF1ZXJ5 4891 -IE5vcnRo 4892 -YWJsZXM= 4893 -aXN0cmli 4894 -X2NsYXNz 4895 -ZWxsbw== 4896 -VGhhdA== 4897 -0Lo= 4898 -cGVjaWFsbHk= 4899 -IFByZXNpZGVudA== 4900 -IGNhbXBhaWdu 4901 -IGFsdA== 4902 -YXJlYQ== 4903 -IGNoYWxs 4904 -IG9wcG9ydA== 4905 -LkNvbg== 4906 -IGVuZXJneQ== 4907 -bGlrZQ== 4908 -LnN0cmluZw== 4909 -aW5ndG9u 4910 -KSo= 4911 -eXk= 4912 -IHByb2Zlc3Npb24= 4913 -aXJ0aA== 4914 -IHNlZw== 4915 -5pw= 4916 -IGhvcg== 4917 -aWVycw== 4918 -Y2Fu 4919 -IGJlaGluZA== 4920 -UHJvZHVjdA== 4921 -Zmc= 4922 -IFNr 4923 -LmpwZw== 4924 -Pzo= 4925 -XTsKCg== 4926 -IGNhbGxiYWNr 4927 -IEh0dHA= 4928 -0Yw= 4929 -bG9uZw== 4930 -TVM= 4931 -QVRI 4932 -IHJhaXNl 4933 -IHdhbnRlZA== 4934 -cm93bg== 4935 -dXRvcg== 4936 -bHQ= 4937 -XT0= 4938 -ZWxpbmU= 4939 -TUE= 4940 -IHNlcGFy 4941 -Y3M= 4942 -c2VtYg== 4943 -RGlz 4944 -YnNlcnY= 4945 -IFdpbGw= 4946 -IHBvbGljeQ== 4947 -IHRoaXJk 4948 -cGhvbmU= 4949 -IGJlZA== 4950 -L2c= 4951 -Ll9f 4952 -IEluYw== 4953 -aXppbmc= 4954 -LnJlbW92ZQ== 4955 -aW5zdGFuY2U= 4956 -LnR5cGU= 4957 -IHNlcnY= 4958 -RWFjaA== 4959 -IGhhcg== 4960 -IE1lc3NhZ2U= 4961 -KGtleQ== 4962 -U0VMRUNU 4963 -UG9z 4964 -KSk7DQo= 4965 -IHJlY29tbQ== 4966 -IHRyYWluaW5n 4967 -IEVudA== 4968 -IENoYXI= 4969 -aWNodA== 4970 -KGZpbGU= 4971 -IHByaW9y 4972 -R2FtZQ== 4973 -IGV4aXQ= 4974 -UGFyYW1z 4975 -LmNvcmU= 4976 -UEM= 4977 -bmVz 4978 -YW5jZWQ= 4979 -KHJlcXVlc3Q= 4980 -UGFzc3dvcmQ= 4981 -fT4K 4982 -IG1hZw== 4983 -IHJlbGVhc2U= 4984 -IHNoYWxs 4985 -dWRlbnQ= 4986 -IFNvdXRo 4987 -YW5kbw== 4988 -Oic= 4989 -LlRhYkluZGV4 4990 -c2s= 4991 -YW5uZXI= 4992 -aXNzZXQ= 4993 -IG91dHNpZGU= 4994 -bGVkZ2U= 4995 -IOU= 4996 -IFJvYg== 4997 -IGltbQ== 4998 -IQo= 4999 -IFdlYg== 5000 -RGVz 5001 -QkM= 5002 -YW5jaWFs 5003 -Um91dGU= 5004 -RGVj 5005 -ZmVyZW5jZXM= 5006 -IHB1cmNo 5007 -IE1vZGVs 5008 -Y3Rvcg== 5009 -Z24= 5010 -X3N0YXJ0 5011 -X3Vu 5012 -Lio= 5013 -aXNlcw== 5014 -IGdyb3VuZA== 5015 -IHVuaXF1ZQ== 5016 -IGJlYXV0 5017 -eyI= 5018 -IHBvdXI= 5019 -IE9jdA== 5020 -IHRyZWU= 5021 -c2V0cw== 5022 -X3Jlcw== 5023 -JyktPg== 5024 -X3JlZw== 5025 -KCJc 5026 -IGJ5dGU= 5027 -Qmw= 5028 -IGRhdGluZw== 5029 -IG1hdHRlcg== 5030 -IFJlbQ== 5031 -ICcuLi8= 5032 -IEF1Zw== 5033 -IExh 5034 -ICQo 5035 -b3VybmFs 5036 -MTEx 5037 -aWFt 5038 -IHNob3dz 5039 -d3JpdGU= 5040 -IGJhbGw= 5041 -IHNpbXBseQ== 5042 -IGZhc3Q= 5043 -IG1lbW9yeQ== 5044 -QVNT 5045 -IE9m 5046 -b3ZlZA== 5047 -YW50ZQ== 5048 -YXVs 5049 -aXN0cnk= 5050 -KSkpOwo= 5051 -IGZpdA== 5052 -PHN0cmluZw== 5053 -IHBvbGl0aWNhbA== 5054 -YW5jZWw= 5055 -Xy4= 5056 -Y2FyZA== 5057 -LmN1cnJlbnQ= 5058 -b2No 5059 -X2ltYWdl 5060 -XHQ= 5061 -Iwo= 5062 -KEw= 5063 -IGluZHVzdHJ5 5064 -Y29taW5n 5065 -IGV4dHJh 5066 -NjAw 5067 -IHJlcG9ydGVk 5068 -LnN0YXJ0 5069 -IHJlc291cmNlcw== 5070 -IGltZw== 5071 -Zmxvdw== 5072 -X0VY 5073 -KG51bGw= 5074 -IFByZQ== 5075 -IHdyb25n 5076 -aW50ZXJmYWNl 5077 -UGFyYW1ldGVy 5078 -bmVycw== 5079 -4bs= 5080 -dHVyZQ== 5081 -ZXJzaXN0 5082 -b3VudHJ5 5083 -IHNlZW1z 5084 -YWxhbmNl 5085 -ZGVzdA== 5086 -CVN0cmluZw== 5087 -IG1haW50 5088 -IHVuaXQ= 5089 -YWN0ZXJz 5090 -IFRS 5091 -aWZ1bA== 5092 -ZXhwb3J0cw== 5093 -cHJvamVjdA== 5094 -QXBwbGljYXRpb24= 5095 -bGVnYXRl 5096 -IHRha2Vz 5097 -dGVybQ== 5098 -IGV0Yw== 5099 -dXN0ZXI= 5100 -IGFwcGVhcg== 5101 -YWRkcmVzcw== 5102 -IGZlbQ== 5103 -aHM= 5104 -IGhvbQ== 5105 -LC0= 5106 -IGRpZmZpY3VsdA== 5107 -IGNvbWluZw== 5108 -T3Blbg== 5109 -IHNldHRpbmdz 5110 -IFdhcg== 5111 -IFRoZW4= 5112 -IGF1dG9t 5113 -IEZvdW5kYXRpb24= 5114 -IHF1aXRl 5115 -RGVzY3JpcHRpb24= 5116 -IGJsb2c= 5117 -aXF1 5118 -UFM= 5119 -MTEw 5120 -X2ZpZWxk 5121 -SnNvbg== 5122 -U1NJT04= 5123 -IFNjaA== 5124 -IExP 5125 -IGRlc2NyaQ== 5126 -IGV2ZXJ5b25l 5127 -IHByZXR0eQ== 5128 -IGxvbmdlcg== 5129 -IG1lbnU= 5130 -IGN1cnJlbnRseQ== 5131 -c2Vj 5132 -IHJlbGF0aW9uc2hpcA== 5133 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM= 5134 -IE1hcA== 5135 -YXNldA== 5136 -IHBhcmFtZXRlcnM= 5137 -IGNydXNo 5138 -Ig0K 5139 -SUxJVFk= 5140 -aWdyYXRpb24= 5141 -IGNvdXQ= 5142 -dG90YWw= 5143 -IG5hbWVz 5144 -bmRlZg== 5145 -Iik7 5146 -cmllbmQ= 5147 -eW5hbWlj 5148 -IGVmZm9ydA== 5149 -IGFjdHVhbA== 5150 -IGZpZWxkcw== 5151 -T1VO 5152 -dGVycw== 5153 -MjUw 5154 -IGZpeA== 5155 -X21vZGVs 5156 -IGNhc2Vz 5157 -Q0E= 5158 -TXk= 5159 -SW50ZXJmYWNl 5160 -IFNF 5161 -MTk2 5162 -XV0= 5163 -YWxsZQ== 5164 -IE5hdGlvbmFs 5165 -IEFycmF5TGlzdA== 5166 -aW5saW5l 5167 -LlY= 5168 -YXJh 5169 -cmVmaXg= 5170 -YXNj 5171 -UmVhZGVy 5172 -INC/ 5173 -YXN0aWM= 5174 -KCgp 5175 -Q2w= 5176 -LmFubm90YXRpb24= 5177 -IHBlcmZvcm1hbmNl 5178 -YWlseQ== 5179 -LnRvU3RyaW5n 5180 -Lm5ldA== 5181 -dmlld3M= 5182 -LmVuZA== 5183 -YXllcnM= 5184 -bGF0ZQ== 5185 -IEFwcg== 5186 -ZWRlcmFs 5187 -J10p 5188 -LmJvZHk= 5189 -IGhpZ2hlcg== 5190 -X2Zs 5191 -Y3I= 5192 -YWxlcnQ= 5193 -X25vZGU= 5194 -IEdvb2dsZQ== 5195 -IGl0c2VsZg== 5196 -QXV0aA== 5197 -dXJyZW5jeQ== 5198 -IHNpZ25pZmljYW50 5199 -YXBwZW5k 5200 -IHJlc3BlY3Q= 5201 -c3RyYXA= 5202 -IHVuYQ== 5203 -cml0ZXJpYQ== 5204 -UE9SVA== 5205 -LmFwYWNoZQ== 5206 -T3V0cHV0 5207 -IHByb2dyZXNz 5208 -IG1pZA== 5209 -IE1pY3Jvc29mdA== 5210 -IHJlc291cmNl 5211 -YWJsaXNo 5212 -IGRpbQ== 5213 -LmxvYWQ= 5214 -LkFwcA== 5215 -IGRpcmVjdGlvbg== 5216 -IGFkZGl0aW9uYWw= 5217 -ICAgICAgICAgICAgICAgICAgICAgICAg 5218 -IG51bWJlcnM= 5219 -IGNvbXBhbmllcw== 5220 -LlRo 5221 -IHNvdW5k 5222 -dXNlcm5hbWU= 5223 -IHN0YXRlbWVudA== 5224 -IGFsZXJ0 5225 -IGNvbnRyYWN0 5226 -aG9tZQ== 5227 -X2xlbmd0aA== 5228 -LkNvbXBvbmVudA== 5229 -ZXY= 5230 -LkV4 5231 -77ya 5232 -Ijs= 5233 -IEhpZ2g= 5234 -ICkKCg== 5235 -IFBvaW50 5236 -b3Bo 5237 -IGxpbmVz 5238 -LT5f 5239 -IikKCg== 5240 -b3g= 5241 -YXBwbGljYXRpb24= 5242 -IF0K 5243 -CgoKCgoK 5244 -MTgw 5245 -IHNvb24= 5246 -Y3Rpb25z 5247 -aW5nZXI= 5248 -IGpvaW4= 5249 -IFBl 5250 -IOs= 5251 -IGxhcw== 5252 -LkU= 5253 -Y3Nz 5254 -L29y 5255 -IFN0YXJ0 5256 -IFRP 5257 -IHN1YnM= 5258 -Y29ubg== 5259 -Y29tcG9uZW50cw== 5260 -REVCVUc= 5261 -cXVhcmU= 5262 -RnVuY3Rpb24= 5263 -ZW5kYXI= 5264 -LmluZGV4 5265 -IGZpbGw= 5266 -xJk= 5267 -IGNob29zZQ== 5268 -aG93 5269 -IEFtZXJpY2E= 5270 -YXNzZXRz 5271 -LS0tLS0tLS0tLS0t 5272 -IFZhbHVl 5273 -IG9mZmljZQ== 5274 -IHZlaA== 5275 -IHRyYW5zZm9ybQ== 5276 -IEFydA== 5277 -IGluZGU= 5278 -IGZu 5279 -IGltcGxlbWVudHM= 5280 -YW5nbw== 5281 -cGxldGU= 5282 -KyI= 5283 -dG1w 5284 -YW1pbHk= 5285 -IGhhc2g= 5286 -bWlzc2lvbnM= 5287 -RVNU 5288 -Z3Q= 5289 -UHJvdmlkZXI= 5290 -ICAgICAgICAgICAgICAgICAgICAgIA== 5291 -IGZsYWc= 5292 -IHBhcnRpY2lw 5293 -ZGVu 5294 -IFJldHVybnM= 5295 -IG5vdGU= 5296 -w7xy 5297 -cG0= 5298 -aWRlb3M= 5299 -IHNwZWNpZmllZA== 5300 -IEVO 5301 -ZXN0ZXI= 5302 -b2xpZA== 5303 -IHVwb24= 5304 -KHN0ZA== 5305 -CXY= 5306 -ICdc 5307 -dXo= 5308 -IHZlcnQ= 5309 -IHZpY3Q= 5310 -CXNlbGY= 5311 -ICIk 5312 -ODU= 5313 -Lms= 5314 -IGdyb3Vwcw== 5315 -Z2l0aHVi 5316 -bGFuZw== 5317 -IG11dA== 5318 -VE8= 5319 -IHZl 5320 -IFBsZWFzZQ== 5321 -OwoKCg== 5322 -YWNjZXNz 5323 -IHsi 5324 -cmVh 5325 -IHJpc2s= 5326 -aWNrZXI= 5327 -b2dnbGU= 5328 -CXdoaWxl 5329 -QU5H 5330 -LnNlbmQ= 5331 -NzI= 5332 -IHdvbWFu 5333 -IGdldHM= 5334 -IGlnbg== 5335 -IElk 5336 -X2xvZw== 5337 -T05F 5338 -IGV2aWQ= 5339 -IEhhcg== 5340 -X3N1Yg== 5341 -IGVuZGw= 5342 -IGluY2x1ZGVk 5343 -KCkpOwoK 5344 -IEFw 5345 -aWdy 5346 -IHNlbQ== 5347 -IEJsYWNr 5348 -ZG9j 5349 -X3RhYmxl 5350 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 5351 -LXVw 5352 -IGNhdXNl 5353 -IC4u 5354 -IHZhbg== 5355 -X2RpY3Q= 5356 -IGZvY3Vz 5357 -SU5E 5358 -Q0VTUw== 5359 -LkxvZw== 5360 -IG11bHRpcGxl 5361 -aWRv 5362 -IHJlZ2FyZA== 5363 -LU0= 5364 -YW5kbGVy 5365 -b3Vyc2U= 5366 -IGRlZw== 5367 -LlU= 5368 -IGFkZGl0aW9u 5369 -IHZhcmlvdXM= 5370 -IHJlY2VpdmU= 5371 -0LXQvQ== 5372 -IEhU 5373 -T2Jq 5374 -REY= 5375 -IGluY3JlYXNl 5376 -IE9wZW4= 5377 -XTs= 5378 -IGNvbW1pdA== 5379 -Pwo= 5380 -YXRlZ29yaWVz 5381 -YXRvcnk= 5382 -c2hpcA== 5383 -IE1pY2g= 5384 -IGh0bWw= 5385 -cm9taXNl 5386 -IGxlYXZl 5387 -IHN0cmF0ZWc= 5388 -YXZlbg== 5389 -IENvbnNvbGU= 5390 -a25vd24= 5391 -LW4= 5392 -X0xF 5393 -LmNvbXBvbmVudA== 5394 -IGJyZQ== 5395 -U2Vzc2lvbg== 5396 -aWFuY2U= 5397 -IGFsaWdu 5398 -dHlwZWRlZg== 5399 -X3Jlc3VsdA== 5400 -IFdIRVJF 5401 -LnNwbGl0 5402 -IHJlYWRpbmc= 5403 -RkFVTFQ= 5404 -IGNsbw== 5405 -IG5vdGljZQ== 5406 -X3By 5407 -YXJ0ZXI= 5408 -IGxvY2s= 5409 -IHN0YW5kYXJk 5410 -ZXRpYw== 5411 -ZWxsb3c= 5412 -IHBhZGRpbmc= 5413 -IEhpcw== 5414 -IHN0YXRlcw== 5415 -X2Nhc3Q= 5416 -KFA= 5417 -YWE= 5418 -IGludGVybmFs 5419 -ZWFu 5420 -IFBSTw== 5421 -IEtleQ== 5422 -IGVzcGVjaWFsbHk= 5423 -bWluZw== 5424 -IGNyb3Nz 5425 -IG5hdGlvbmFs 5426 -X29iamVjdA== 5427 -ZmlsdGVy 5428 -IHNjcmlwdA== 5429 -LnVwZGF0ZQ== 5430 -X2k= 5431 -IEFzc2VydA== 5432 -L2NvcmU= 5433 -JSUlJQ== 5434 -IHByb2JsZW1z 5435 -aXN0b3I= 5436 -IC49 5437 -IGFyY2g= 5438 -IHdyaXR0ZW4= 5439 -IG1pbGl0 5440 -TUVOVA== 5441 -LmNo 5442 -Y2FwZQ== 5443 -IE11cw== 5444 -X2NvbmZpZw== 5445 -IEFQSQ== 5446 -Zm9vdA== 5447 -IGltYWdlcw== 5448 -ZW5kbA== 5449 -Lklu 5450 -Rmlyc3Q= 5451 -IHBsYXRmb3Jt 5452 -LnByb3Q= 5453 -T3B0aW9u 5454 -c3Rl 5455 -IFRPRE8= 5456 -IGZvcmNl 5457 -LmNvbnQ= 5458 -CWVjaG8= 5459 -IERhdg== 5460 -UHRy 5461 -KEI= 5462 -UlQ= 5463 -IEJhc2U= 5464 -XVsn 5465 -IGFubm91bmM= 5466 -Y29uc29sZQ== 5467 -IFB5 5468 -ZHM= 5469 -LmFz 5470 -IHByZXZlbnQ= 5471 -YXBhbg== 5472 -IHsn 5473 -fTwv 5474 -IFNlcnZpY2U= 5475 -IFNlbg== 5476 -YWRvcg== 5477 -cHJvZmlsZQ== 5478 -VG9w 5479 -IGl0ZXI= 5480 -cG8= 5481 -SUVT 5482 -SlNPTg== 5483 -SUU= 5484 -aWFudA== 5485 -44CB 5486 -X2o= 5487 -IFNlcHQ= 5488 -X21hcA== 5489 -YnVt 5490 -KGNvbnRleHQ= 5491 -IEhvbWU= 5492 -aWFucw== 5493 -R0I= 5494 -NjM= 5495 -IGxpdmluZw== 5496 -IHBhdHRlcm4= 5497 -KGlucHV0 5498 -aWNpZW50 5499 -OTk5 5500 -Q29yZQ== 5501 -IGVudGl0eQ== 5502 -IGludGVn 5503 -Q2hhbmdlZA== 5504 -IHVzZWZ1bA== 5505 -LmluZm8= 5506 -IHRvb2w= 5507 -KGl0ZW0= 5508 -IG9r 5509 -IGZlZWQ= 5510 -SVg= 5511 -w6lz 5512 -IE5ld3M= 5513 -cmVtb3Zl 5514 -ZXJyeQ== 5515 -CQkJCQkJCQkJ 5516 -aXBtZW50 5517 -YXJlcw== 5518 -RG8= 5519 -Q3VycmVudA== 5520 -LmNvbnRlbnQ= 5521 -Lkdyb3Vw 5522 -dXN0cmFs 5523 -INGB 5524 -fSk= 5525 -IHBvcHVsYXI= 5526 -IHN0cmU= 5527 -IG1ldGhvZHM= 5528 -X0VSUk9S 5529 -TGVmdA== 5530 -Y2Fs 5531 -YnNw 5532 -LlRvU3RyaW5n 5533 -IGRpcg== 5534 -IGFsbG93ZWQ= 5535 -IGltcGFjdA== 5536 -IildCg== 5537 -NjI= 5538 -LmNvbmZpZw== 5539 -IGVsZW1lbnRz 5540 -IHByb3Rl 5541 -IHRyYWlu 5542 -LnRy 5543 -cnM= 5544 -IFJlcHVibGlj 5545 -IFRhc2s= 5546 -NjE= 5547 -YXJpZXM= 5548 -KEQ= 5549 -KGdldA== 5550 -4oCmCgo= 5551 -IHJlbGF0ZWQ= 5552 -IHZlcnM= 5553 -IHNpbA== 5554 -ICIiOwo= 5555 -IGNtZA== 5556 -IHRlY2hub2xvZ3k= 5557 -LndpZHRo 5558 -RmxvYXQ= 5559 -IFVzZQ== 5560 -Qm9keQ== 5561 -c2hvdWxk 5562 -LmpvaW4= 5563 -Rm9udA== 5564 -bGx1bQ== 5565 -eWNsZQ== 5566 -IEJyaXQ= 5567 -IG1pdA== 5568 -IHNjYWxl 5569 -IChf 5570 -ZXJuZWw= 5571 -IikpCg== 5572 -IHNjb3Jl 5573 -L3Y= 5574 -IHN0dWRlbnQ= 5575 -VUM= 5576 -LnNob3c= 5577 -IGF2ZXJhZ2U= 5578 -RW5hYmxlZA== 5579 -KGV4 5580 -Y29tbW9u 5581 -aW1hdGlvbg== 5582 -OkAi 5583 -Y2hpZQ== 5584 -IC4uLgoK 5585 -cml2ZXI= 5586 -IE1hcmNo 5587 -Y2F0ZWdvcnk= 5588 -Zmlu 5589 -IGNvdXJ0 5590 -0LI= 5591 -U2VydmVy 5592 -IGNvbnRhaW5lcg== 5593 -LXN0 5594 -X2Zvcg== 5595 -IHBhcnRz 5596 -IGRlY2lzaW9u 5597 -b2Jz 5598 -b3Vi 5599 -bWl0dGVk 5600 -ICQoJyM= 5601 -IHNhdw== 5602 -IGFwcHJvYWNo 5603 -SUNF 5604 -IHNheWluZw== 5605 -IGFueW9uZQ== 5606 -bWV0YQ== 5607 -U0Q= 5608 -IHNvbmc= 5609 -ZGlzcGxheQ== 5610 -T3Blcg== 5611 -b3V0ZXM= 5612 -IGNoYW5uZWw= 5613 -IGNoYW5nZWQ= 5614 -w6o= 5615 -IGZpbmFsbHk= 5616 -X251bWJlcg== 5617 -UGxlYXNl 5618 -4KQ= 5619 -b3Jpbmc= 5620 -LXJl 5621 -IGtpbGw= 5622 -IGRydWc= 5623 -d2luZG93 5624 -IGNvbnZlcnQ= 5625 -b21icmU= 5626 -IHdheXM= 5627 -SGVscGVy 5628 -IEZpcnN0 5629 -KF9f 5630 -dXJpdHk= 5631 -IFdpbmRvd3M= 5632 -ZWVz 5633 -IG1hdA== 5634 -cmFwcGVy 5635 -IHBsdXM= 5636 -YW5nZXM= 5637 -Il0u 5638 -YXpvbg== 5639 -L3Q= 5640 -bGF0 5641 -YXN0ZQ== 5642 -IHByb2ZpbGU= 5643 -IHJlYWR5 5644 -I2lmbmRlZg== 5645 -cm90ZQ== 5646 -IHNlbnNl 5647 -R2VuZXI= 5648 -IENvbmZpZw== 5649 -b215 5650 -IEp1bmU= 5651 -IGxhdGVzdA== 5652 -IHNhZg== 5653 -IHJlZ2lvbg== 5654 -IGRlZXA= 5655 -d2l0Y2g= 5656 -IFBhcms= 5657 -fWA= 5658 -IEZyb20= 5659 -SUk= 5660 -IGN2 5661 -IHJlYWNo 5662 -IGNvdW50ZXI= 5663 -IFdvcms= 5664 -IFVSTA== 5665 -IFVwZGF0ZQ== 5666 -JywNCg== 5667 -IGltbWVkaQ== 5668 -Y2xvc2U= 5669 -YWRvcw== 5670 -ZmVycmVk 5671 -IHdlZWtz 5672 -dXJn 5673 -IGRhbWFnZQ== 5674 -IGxvc3Q= 5675 -YW5p 5676 -X2xv 5677 -IGhpbXNlbGY= 5678 -IGRvZw== 5679 -KV0K 5680 -778= 5681 -cGly 5682 -dHQ= 5683 -IHBhcGVy 5684 -IHRoZW1z 5685 -c2Vjb25k 5686 -IHN0YWZm 5687 -IElucHV0 5688 -Iis= 5689 -IEZhY2Vib29r 5690 -IGFsbG9j 5691 -IHNjaGVk 5692 -QUNF 5693 -IHRoZW1zZWx2ZXM= 5694 -IENvbXBvbmVudA== 5695 -IGRyaXZlcg== 5696 -amE= 5697 -KHBhdGg= 5698 -IGNhdGVnb3J5 5699 -YWxscw== 5700 -cHU= 5701 -bGx1bWluYXRl 5702 -IEFjdGlvbg== 5703 -LmJ1dHRvbg== 5704 -IEdM 5705 -aXN0aWNz 5706 -IG9pbA== 5707 -IHN0b2Nr 5708 -Pic= 5709 -IGRlYWQ= 5710 -VkFM 5711 -UVVF 5712 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 5713 -IGNoYXJn 5714 -UmV0dXJu 5715 -IGZ1bA== 5716 -ZG9t 5717 -IHJ1bGVz 5718 -IG1vZGlmeQ== 5719 -IGV2YWw= 5720 -aGFt 5721 -YXRlbWVudA== 5722 -XDw= 5723 -dWxh 5724 -PUZhbHNl 5725 -UkE= 5726 -IGNvbnRhaW5z 5727 -NzQ= 5728 -IHN0YWNr 5729 -bWFy 5730 -IHt9Cg== 5731 -IHVuZGVmaW5lZA== 5732 -QXNz 5733 -IENoaW5h 5734 -dmV5 5735 -Kgo= 5736 -IHBsYXlpbmc= 5737 -KS8= 5738 -YWN0b3I= 5739 -IGJvdHRvbQ== 5740 -bGllcg== 5741 -IE51bWJlcg== 5742 -IGNvdXBsZQ== 5743 -REM= 5744 -IFNP 5745 -Z29y 5746 -LnNldFRleHQ= 5747 -c3VjY2Vzcw== 5748 -Y29tbWFuZA== 5749 -RmlsdGVy 5750 -IE91cg== 5751 -X2l0ZW0= 5752 -IGN0eA== 5753 -IHJvYWQ= 5754 -VmVyc2lvbg== 5755 -Y2FzZQ== 5756 -dXJ0 5757 -YXZpb3I= 5758 -eWNo 5759 -c2VtYmx5 5760 -IFByb2R1Y3Q= 5761 -IGhlbGQ= 5762 -YWZl 5763 -IGluY2x1ZGVz 5764 -PHF1b3Rl 5765 -IGF2b2lk 5766 -IEZpbg== 5767 -IE1vZA== 5768 -IHRhYg== 5769 -YW5v 5770 -w7E= 5771 -aXBwaW5n 5772 -LWU= 5773 -IGluc2VydA== 5774 -dGFyZ2V0 5775 -Y2hhbg== 5776 -Lk1vZGVs 5777 -SU1F 5778 -XAo= 5779 -IG1hY2hpbmU= 5780 -YXZ5 5781 -IE5P 5782 -IEludGVy 5783 -IG9wZXJhdGlvbg== 5784 -bW9kYWw= 5785 -VGFn 5786 -XTo= 5787 -IHByb2R1Y3Rpb24= 5788 -IGFyZWFz 5789 -IHJlbg== 5790 -X2Zyb20= 5791 -bmJzcA== 5792 -IG9wZXJhdG9y 5793 -bWVu 5794 -YXBwZWQ= 5795 -X3Blcg== 5796 -emVu 5797 -KCIu 5798 -LnNhdmU= 5799 -PSJ7ew== 5800 -IHRvcg== 5801 -KHJlc3BvbnNl 5802 -IGNhbmRpZA== 5803 -IGNvbnY= 5804 -YWlsZWQ= 5805 -IExpYg== 5806 -Y29tcA== 5807 -dXJh 5808 -77+9 5809 -IEhlcmU= 5810 -IGFyZ3VtZW50 5811 -aG9vZA== 5812 -IGVzdGFibGlzaA== 5813 -b2dyYXBoeQ== 5814 -IG9uQ2xpY2s= 5815 -YW1iZGE= 5816 -IHNjaA== 5817 -IG1vdmll 5818 -IHNlYw== 5819 -IGFjdGl2aXR5 5820 -2Kc= 5821 -IHNxbA== 5822 -X2FsbA== 5823 -aW5jaXA= 5824 -IHByb3ZpZGVz 5825 -IHN5cw== 5826 -YWNrZXQ= 5827 -IHdhc24= 5828 -IHVzZXM= 5829 -IEZ1bmN0aW9u 5830 -Lmdvb2dsZQ== 5831 -IFJlc3VsdA== 5832 -ODQ= 5833 -VmlzaWJsZQ== 5834 -YWdtYQ== 5835 -ZWxjb21l 5836 -IFN5 5837 -IENlbnQ= 5838 -QUxTRQ== 5839 -YWNpw7Nu 5840 -RVhU 5841 -IGxpY2Vuc2U= 5842 -IExvbmc= 5843 -IGFjY29t 5844 -IGFiaWxpdHk= 5845 -LmhlaWdodA== 5846 -QWN0aXZl 5847 -b2xvZ2ljYWw= 5848 -b2x5 5849 -KSks 5850 -LlNl 5851 -IHBhcmFtZXRlcg== 5852 -cHJpdGU= 5853 -QUJJTElUWQ== 5854 -LnNlcnZpY2U= 5855 -IEdyb3Vw 5856 -X3F1ZXJ5 5857 -IEl0ZW0= 5858 -aW5pbmc= 5859 -IGp1ZA== 5860 -aW1z 5861 -Zml4 5862 -aW5kZXI= 5863 -YWdyYW0= 5864 -IGZ1bmN0aW9ucw== 5865 -IGV4cGVyaQ== 5866 -IEVt 5867 -IHJvdA== 5868 -IHBlbg== 5869 -LmJ0bg== 5870 -IEFT 5871 -I2lmZGVm 5872 -IGNob2ljZQ== 5873 -IFBhZ2U= 5874 -X1BSTw== 5875 -UVU= 5876 -5Y8= 5877 -YW50aXR5 5878 -wq0= 5879 -d29yZHM= 5880 -IHJlYWRvbmx5 5881 -IGZsZXg= 5882 -cHJvdGVjdGVk 5883 -IEFueQ== 5884 -IGNoYXJhY3RlcnM= 5885 -ZW5jZWQ= 5886 -IEp1bHk= 5887 -aWxlcg== 5888 -Q2FyZA== 5889 -dXJhbmNl 5890 -IHJldg== 5891 -LmV2ZW50 5892 -YWx5 5893 -MTMw 5894 -IHdvbmRlcg== 5895 -IFBvcnQ= 5896 -IGxlZ2Fs 5897 -cm9sZQ== 5898 -IHRlbg== 5899 -IGdvZXM= 5900 -TVA= 5901 -d2hpdGU= 5902 -KToNCg== 5903 -KSkNCg== 5904 -IHJlZmVyZW5jZQ== 5905 -IG1pcw== 5906 -IFByb2plY3Q= 5907 -aWNrcw== 5908 -PiY= 5909 -Q09O 5910 -IHJlcGw= 5911 -IHJlZ3VsYXI= 5912 -U3RvcmFnZQ== 5913 -cmFtZXdvcms= 5914 -IGdvYWw= 5915 -IHRvdWNo 5916 -LndpZGdldA== 5917 -IGJ1aWx0 5918 -ZGVz 5919 -UGFydA== 5920 -KHJl 5921 -IHdvcnRo 5922 -aGli 5923 -Z2FtZQ== 5924 -OTE= 5925 -MTky 5926 -INCy 5927 -YWNpb24= 5928 -IFdoaXRl 5929 -KHR5cGU= 5930 -KGA= 5931 -ODE= 5932 -IG5hdHVyYWw= 5933 -IGluag== 5934 -IGNhbGN1bA== 5935 -IEFwcmls 5936 -Lkxpc3Q= 5937 -IGFzc29jaWF0ZWQ= 5938 -CVN5c3RlbQ== 5939 -fn4= 5940 -PVs= 5941 -IHN0b3JhZ2U= 5942 -IGJ5dGVz 5943 -IHRyYXZlbA== 5944 -IHNvdQ== 5945 -IHBhc3NlZA== 5946 -IT0= 5947 -YXNjcmlwdA== 5948 -Lm9wZW4= 5949 -IGdyaWQ= 5950 -IGJ1cw== 5951 -IHJlY29nbg== 5952 -QWI= 5953 -IGhvbg== 5954 -IENlbnRlcg== 5955 -IHByZWM= 5956 -YnVpbGQ= 5957 -NzM= 5958 -SFRNTA== 5959 -IFNhbg== 5960 -IGNvdW50cmllcw== 5961 -YWxlZA== 5962 -dG9rZW4= 5963 -a3Q= 5964 -IHF1YWw= 5965 -TGFzdA== 5966 -YWRvdw== 5967 -IG1hbnVmYWN0 5968 -aWRhZA== 5969 -amFuZ28= 5970 -TmV4dA== 5971 -eGY= 5972 -LmE= 5973 -IHBvcm5v 5974 -IFBN 5975 -ZXJ2ZQ== 5976 -aXRpbmc= 5977 -X3Ro 5978 -Y2k= 5979 -PU5vbmU= 5980 -Z3M= 5981 -IGxvZ2lu 5982 -YXRpdmVz 5983 -J10pOwo= 5984 -xIU= 5985 -IGlsbA== 5986 -SUE= 5987 -Y2hpbGRyZW4= 5988 -RE8= 5989 -IGxldmVscw== 5990 -IHt7 5991 -IGxvb2tz 5992 -ICIj 5993 -VG9TdHJpbmc= 5994 -IG5lY2Vzc2FyeQ== 5995 -ICAgCg== 5996 -Y2VsbA== 5997 -RW50cnk= 5998 -ICcj 5999 -IGV4dHJlbQ== 6000 -U2VsZWN0b3I= 6001 -IHBsYWNlaG9sZGVy 6002 -TG9hZA== 6003 -IHJlbGVhc2Vk 6004 -T1JF 6005 -RW51bWVy 6006 -IFRW 6007 -U0VU 6008 -aW5x 6009 -UHJlc3M= 6010 -IERlcGFydG1lbnQ= 6011 -IHByb3BlcnRpZXM= 6012 -IHJlc3BvbmQ= 6013 -U2VhcmNo 6014 -YWVs 6015 -IHJlcXU= 6016 -IEJvb2s= 6017 -Lwo= 6018 -KHN0 6019 -IGZpbmFuY2lhbA== 6020 -aWNrZXQ= 6021 -X2lucHV0 6022 -IHRocmVhdA== 6023 -KGlu 6024 -U3RyaXA= 6025 -7J0= 6026 -w6fDo28= 6027 -NzE= 6028 -IGV2aWRlbmNl 6029 -KSk7 6030 -IEJybw== 6031 -IFtdOwo= 6032 -IG91 6033 -YnVm 6034 -U2NyaXB0 6035 -ZGF0 6036 -IHJ1bGU= 6037 -I2ltcG9ydA== 6038 -PSIv 6039 -U2VyaWFs 6040 -IHN0YXJ0aW5n 6041 -W2luZGV4 6042 -YWU= 6043 -IGNvbnRyaWI= 6044 -c2Vzc2lvbg== 6045 -X25ldw== 6046 -dXRhYmxl 6047 -b2Jlcg== 6048 -ICIuLw== 6049 -IGxvZ2dlcg== 6050 -IHJlY2VudGx5 6051 -IHJldHVybmVk 6052 -DQ0K 6053 -KSkpCg== 6054 -aXRpb25z 6055 -IHNlZWs= 6056 -IGNvbW11bmlj 6057 -ICIu 6058 -IHVzZXJuYW1l 6059 -RUNU 6060 -RFM= 6061 -IG90aGVyd2lzZQ== 6062 -IEdlcm1hbg== 6063 -LmF3 6064 -QWRhcHRlcg== 6065 -aXhlbA== 6066 -IHN5c3RlbXM= 6067 -IGRyb3A= 6068 -ODM= 6069 -IHN0cnVjdHVyZQ== 6070 -ICQoIiM= 6071 -ZW5jaWVz 6072 -YW5uaW5n 6073 -IExpbms= 6074 -IFJlc3BvbnNl 6075 -IHN0cmk= 6076 -xbw= 6077 -IERC 6078 -5pc= 6079 -YW5kcm9pZA== 6080 -c3VibWl0 6081 -b3Rpb24= 6082 -OTI= 6083 -KEA= 6084 -LnRlc3Q= 6085 -ODI= 6086 -CgoKCgoKCgo= 6087 -XTsNCg== 6088 -IGRpcmVjdGx5 6089 -ICIl 6090 -cmlz 6091 -ZWx0YQ== 6092 -QUlM 6093 -KXsNCg== 6094 -bWluZQ== 6095 -ICAgICAgICAgICAgICAgICAgICAgICAgICA= 6096 -KGs= 6097 -Ym9u 6098 -YXNpYw== 6099 -cGl0ZQ== 6100 -X19f 6101 -TWF4 6102 -IGVycm9ycw== 6103 -IFdoaWxl 6104 -IGFyZ3VtZW50cw== 6105 -IGVuc3VyZQ== 6106 -UmlnaHQ= 6107 -LWJhc2Vk 6108 -V2Vi 6109 -IC09 6110 -IGludHJvZHU= 6111 -IEluc3Q= 6112 -IFdhc2g= 6113 -b3JkaW4= 6114 -am9pbg== 6115 -RGF0YWJhc2U= 6116 -IGdyYWQ= 6117 -IHVzdWFsbHk= 6118 -SVRF 6119 -UHJvcHM= 6120 -Pz4K 6121 -IEdv 6122 -QE92ZXJyaWRl 6123 -UkVG 6124 -IGlw 6125 -IEF1c3RyYWw= 6126 -IGlzdA== 6127 -Vmlld0J5SWQ= 6128 -IHNlcmlvdXM= 6129 -IGN1c3RvbWVy 6130 -LnByb3RvdHlwZQ== 6131 -b2Rv 6132 -Y29y 6133 -IGRvb3I= 6134 -IFdJVEhPVVQ= 6135 -IHBsYW50 6136 -IGJlZ2Fu 6137 -IGRpc3RhbmNl 6138 -KCkpLg== 6139 -IGNoYW5jZQ== 6140 -IG9yZA== 6141 -Y2FtZQ== 6142 -cHJhZ21h 6143 -IHByb3RlY3Q= 6144 -cmFnbWVudA== 6145 -IE5vZGU= 6146 -ZW5pbmc= 6147 -0Yc= 6148 -IHJvdXRl 6149 -IFNjaG9vbA== 6150 -aGk= 6151 -IG5laWdoYg== 6152 -QWZ0ZXI= 6153 -bGljaXQ= 6154 -IGNvbnRy 6155 -IHByaW1hcnk= 6156 -QUE= 6157 -LldyaXRlTGluZQ== 6158 -dXRpbHM= 6159 -IGJp 6160 -UmVk 6161 -LkxpbnE= 6162 -Lm9iamVjdA== 6163 -IGxlYWRlcnM= 6164 -dW5pdGllcw== 6165 -IGd1bg== 6166 -b250aA== 6167 -IERldg== 6168 -RklMRQ== 6169 -IGNvbW1lbnRz 6170 -X2xlbg== 6171 -YXJyb3c= 6172 -YW1vdW50 6173 -UmFuZ2U= 6174 -c2VydA== 6175 -R3JpZFZpZXc= 6176 -IHVwZGF0ZWQ= 6177 -IE1v 6178 -IGluZm9ybQ== 6179 -b2NpZXR5 6180 -YWxh 6181 -QWNjZXNz 6182 -IGhhYg== 6183 -IGNyZWF0 6184 -X2FyZw== 6185 -IEphbnVhcnk= 6186 -IERheQ== 6187 -IikNCg== 6188 -dXBsZQ== 6189 -ZG9jdW1lbnQ= 6190 -Z29yaXRo 6191 -bWVudQ== 6192 -IE92ZXI= 6193 -YmI= 6194 -LnRpdGxl 6195 -X291dA== 6196 -IGxlZA== 6197 -dXJp 6198 -ID8+PC8= 6199 -Z2w= 6200 -IGJhbms= 6201 -YXltZW50 6202 -CXByaW50Zg== 6203 -TUQ= 6204 -IHNhbXBsZQ== 6205 -IGhhbmRz 6206 -IFZlcnNpb24= 6207 -dWFyaW8= 6208 -IG9mZmVycw== 6209 -aXR5RW5naW5l 6210 -IHNoYXBl 6211 -IHNsZWVw 6212 -X3BvaW50 6213 -U2V0dGluZ3M= 6214 -IGFjaGll 6215 -IHNvbGQ= 6216 -b3Rh 6217 -LmJpbmQ= 6218 -QW0= 6219 -IHNhZmU= 6220 -U3RvcmU= 6221 -IHNoYXJlZA== 6222 -IHByaXY= 6223 -X1ZBTA== 6224 -IHNlbnM= 6225 -KXs= 6226 -IHJlbWVtYmVy 6227 -c2hhcmVk 6228 -ZWxlbWVudA== 6229 -IHNob290 6230 -VmVydA== 6231 -Y291dA== 6232 -IGVudg== 6233 -X2xhYmVs 6234 -ID4K 6235 -cnVu 6236 -IHNjZW5l 6237 -KGFycmF5 6238 -ZGV2aWNl 6239 -X3RpdGxl 6240 -YWdvbg== 6241 -XQ0K 6242 -YWJ5 6243 -IGJlY2FtZQ== 6244 -Ym9vbGVhbg== 6245 -IHBhcms= 6246 -IENvZGU= 6247 -dXBsb2Fk 6248 -cmlkYXk= 6249 -IFNlcHRlbWJlcg== 6250 -RmU= 6251 -IHNlbg== 6252 -Y2luZw== 6253 -Rkw= 6254 -Q29s 6255 -dXRz 6256 -X3BhZ2U= 6257 -aW5u 6258 -IGltcGxpZWQ= 6259 -YWxpbmc= 6260 -IHlvdXJzZWxm 6261 -LkNvdW50 6262 -Y29uZg== 6263 -IGF1ZA== 6264 -X2luaXQ= 6265 -Lik= 6266 -IHdyb3Rl 6267 -MDAz 6268 -Tkc= 6269 -LkVycm9y 6270 -5Ls= 6271 -LmZvcg== 6272 -IGVxdWFs 6273 -IFJlcXVlc3Q= 6274 -IHNlcmlhbA== 6275 -IGFsbG93cw== 6276 -WFg= 6277 -IG1pZGRsZQ== 6278 -Y2hvcg== 6279 -MTk1 6280 -OTQ= 6281 -w7g= 6282 -ZXJ2YWw= 6283 -LkNvbHVtbg== 6284 -cmVhZGluZw== 6285 -IGVzY29ydA== 6286 -IEF1Z3VzdA== 6287 -IHF1aWNrbHk= 6288 -IHdlYXA= 6289 -IENH 6290 -cm9wcmk= 6291 -aG8= 6292 -IGNvcA== 6293 -KHN0cnVjdA== 6294 -IEJpZw== 6295 -IHZz 6296 -IGZyZXF1 6297 -LlZhbHVl 6298 -IGFjdGlvbnM= 6299 -IHByb3Blcg== 6300 -IGlubg== 6301 -IG9iamVjdHM= 6302 -IG1hdHJpeA== 6303 -YXZhc2NyaXB0 6304 -IG9uZXM= 6305 -Lmdyb3Vw 6306 -IGdyZWVu 6307 -IHBhaW50 6308 -b29scw== 6309 -eWNs 6310 -ZW5jb2Rl 6311 -b2x0 6312 -Y29tbWVudA== 6313 -LmFwaQ== 6314 -RGly 6315 -IHVuZQ== 6316 -aXpvbnQ= 6317 -LnBvc2l0aW9u 6318 -IGRlc2lnbmVk 6319 -X3ZhbA== 6320 -YXZp 6321 -aXJpbmc= 6322 -dGFi 6323 -IGxheWVy 6324 -IHZpZXdz 6325 -IHJldmU= 6326 -cmFlbA== 6327 -IE9O 6328 -cmljcw== 6329 -MTYw 6330 -bnA= 6331 -IGNvcmU= 6332 -KCkpOw0K 6333 -TWFpbg== 6334 -IGV4cGVydA== 6335 -CQkNCg== 6336 -X2Vu 6337 -IC8+ 6338 -dXR0ZXI= 6339 -SUFM 6340 -YWlscw== 6341 -IEtpbmc= 6342 -Ki8KCg== 6343 -IE1ldA== 6344 -X2VuZA== 6345 -YWRkcg== 6346 -b3Jh 6347 -IGly 6348 -TWlu 6349 -IHN1cnBy 6350 -IHJlcGU= 6351 -IGRpcmVjdG9yeQ== 6352 -UFVU 6353 -LVM= 6354 -IGVsZWN0aW9u 6355 -aGFwcw== 6356 -LnByZQ== 6357 -Y20= 6358 -VmFsdWVz 6359 -ICIK 6360 -Y29sdW1u 6361 -aXZpbA== 6362 -TG9naW4= 6363 -aW51ZQ== 6364 -OTM= 6365 -IGJlYXV0aWZ1bA== 6366 -IHNlY3JldA== 6367 -KGV2ZW50 6368 -IGNoYXQ= 6369 -dW1z 6370 -IG9yaWdpbg== 6371 -IGVmZmVjdHM= 6372 -IG1hbmFnZW1lbnQ= 6373 -aWxsYQ== 6374 -dGs= 6375 -IHNldHRpbmc= 6376 -IENvdXI= 6377 -IG1hc3NhZ2U= 6378 -CWVuZA== 6379 -IGhhcHB5 6380 -IGZpbmlzaA== 6381 -IGNhbWVyYQ== 6382 -IFZlcg== 6383 -IERlbW9jcg== 6384 -IEhlcg== 6385 -KFE= 6386 -Y29ucw== 6387 -aXRh 6388 -ICcu 6389 -e30= 6390 -CUM= 6391 -IHN0dWZm 6392 -MTk0 6393 -IDoK 6394 -IEFS 6395 -VGFzaw== 6396 -aGlkZGVu 6397 -ZXJvcw== 6398 -SUdO 6399 -YXRpbw== 6400 -IEhlYWx0aA== 6401 -b2x1dGU= 6402 -RW50ZXI= 6403 -Jz4= 6404 -IFR3aXR0ZXI= 6405 -IENvdW50eQ== 6406 -c2NyaWJl 6407 -ID0+Cg== 6408 -IGh5 6409 -Zml0 6410 -IG1pbGl0YXJ5 6411 -IHNhbGU= 6412 -cmVxdWlyZWQ= 6413 -bm9u 6414 -Ym9vdHN0cmFw 6415 -aG9sZA== 6416 -cmlt 6417 -LW9sZA== 6418 -IERvd24= 6419 -IG1lbnRpb24= 6420 -Y29udGFjdA== 6421 -X2dyb3Vw 6422 -b2RheQ== 6423 -IHRvd24= 6424 -IHNvbHV0aW9u 6425 -dWF0ZQ== 6426 -ZWxsaW5n 6427 -XS0+ 6428 -b3Rlcw== 6429 -ZW50YWw= 6430 -b21lbg== 6431 -b3NwaXRhbA== 6432 -IFN1cA== 6433 -X0VO 6434 -IHNsb3c= 6435 -U0VTU0lPTg== 6436 -IGJsdWU= 6437 -YWdv 6438 -IGxpdmVz 6439 -IF4= 6440 -LnVu 6441 -aW5zdA== 6442 -ZW5nZQ== 6443 -IGN1c3RvbWVycw== 6444 -IGNhc3Q= 6445 -dWRnZXQ= 6446 -77yB 6447 -aWNlbnM= 6448 -IGRldGVybWlu 6449 -U2VsZWN0ZWQ= 6450 -X3Bs 6451 -dWV1ZQ== 6452 -IGRhcms= 6453 -Ly8KCg== 6454 -c2k= 6455 -dGhlcm4= 6456 -IEphcGFu 6457 -L3c= 6458 -UFU= 6459 -IEVhc3Q= 6460 -b3ZpZQ== 6461 -IHBhY2thZ2U= 6462 -IG5vcg== 6463 -IGFwaQ== 6464 -Ym90 6465 -Il07Cg== 6466 -X3Bvc3Q= 6467 -dWxhdGU= 6468 -IGNsdWI= 6469 -JykpOwo= 6470 -IGxvb3A= 6471 -UElP 6472 -aW9uZQ== 6473 -c2hvdA== 6474 -SW5pdGlhbA== 6475 -IHBsYXllZA== 6476 -cmVnaXN0ZXI= 6477 -cm91Z2h0 6478 -X21heA== 6479 -YWNlbWVudA== 6480 -bWF0Y2g= 6481 -cmFwaGljcw== 6482 -QVNU 6483 -IGV4aXN0aW5n 6484 -IGNvbXBsZXg= 6485 -REE= 6486 -LkNo 6487 -LmNvbW1vbg== 6488 -bW8= 6489 -ICcuLi8uLi8= 6490 -aXRv 6491 -IGFuYWx5c2lz 6492 -IGRlbGl2ZXI= 6493 -ICAgICAgICAgICAgICAgIAo= 6494 -aWR4 6495 -w6A= 6496 -b25nbw== 6497 -IEVuZ2xpc2g= 6498 -PCEtLQ== 6499 -IGNvbXB1dGVy 6500 -RU5TRQ== 6501 -IHBhcw== 6502 -IHJhaXM= 6503 -SGFzaA== 6504 -IG1vYmlsZQ== 6505 -IG93bmVy 6506 -RklH 6507 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 6508 -dGhlcw== 6509 -IGF0dHI= 6510 -d2Q= 6511 -LnRpbWU= 6512 -YXdu 6513 -IHRyZWF0bWVudA== 6514 -IEFj 6515 -LlZpZXc= 6516 -aW1wbA== 6517 -bW9yZQ== 6518 -cGFzcw== 6519 -IGhh 6520 -LmZyb20= 6521 -IGxlYWRpbmc= 6522 -RkZGRg== 6523 -KGVycm9y 6524 -LnVp 6525 -YXRhcg== 6526 -YWRlcnM= 6527 -ZGF0ZXM= 6528 -IHp1 6529 -IGZsb3c= 6530 -VGFyZ2V0 6531 -IGludm9sdmVk 6532 -IGlv 6533 -cGFyc2U= 6534 -JF8= 6535 -aGVzdA== 6536 -LmludA== 6537 -LWl0ZW0= 6538 -YXN5 6539 -U3A= 6540 -IHNoaWZ0 6541 -TlQ= 6542 -IHRm 6543 -X1RS 6544 -LndlYg== 6545 -Q1M= 6546 -IH0p 6547 -IGV5ZXM= 6548 -MTI1 6549 -MTA1 6550 -X3o= 6551 -Jyk7DQo= 6552 -aWZvcm4= 6553 -IHtA 6554 -IG5pY2U= 6555 -Lmxpc3Q= 6556 -ICAgIA0K 6557 -IGZsb29y 6558 -IHJlZGlyZWN0 6559 -IFVL 6560 -KFsn 6561 -IHdpc2g= 6562 -IGNhcHQ= 6563 -bGVnYWw= 6564 -IElP 6565 -IHN0YWdl 6566 -LlN0cmluZw== 6567 -IEFmcg== 6568 -aWdlbg== 6569 -IFNI 6570 -RGVsZXRl 6571 -ZWxscw== 6572 -IHNvbGlk 6573 -IG1lZXRpbmc= 6574 -IHdvcmtlZA== 6575 -IGVkaXRvcg== 6576 -aW55 6577 -0Lw= 6578 -X3JlYWQ= 6579 -Lklk 6580 -ZWZm 6581 -T2Zmc2V0 6582 -Y2hh 6583 -VVNFUg== 6584 -CQkgICA= 6585 -aXBwZWQ= 6586 -IGRpY3Q= 6587 -IFJ1bg== 6588 -LmhwcA== 6589 -IGFuZw== 6590 -eG1s 6591 -aW1wbGU= 6592 -IG1lZGljYWw= 6593 -X3Rva2Vu 6594 -Y29ubmVjdA== 6595 -IGhvdXI= 6596 -IGNvbnRyb2xsZXI= 6597 -X21lc3NhZ2U= 6598 -VUlE 6599 -R3I= 6600 -YW5kZWQ= 6601 -X0NI 6602 -IGJvb2tz 6603 -IHNwZWFr 6604 -YW1pbmc= 6605 -IG1vdW50 6606 -UmVjb3Jk 6607 -CXN0cnVjdA== 6608 -LldlYg== 6609 -b25kb24= 6610 -IC8vCg== 6611 -IGZlbHQ= 6612 -LkF1dG8= 6613 -aWRnZQ== 6614 -X3Bvcw== 6615 -UFI= 6616 -IG1vZGVybg== 6617 -Q29sbGVjdGlvbg== 6618 -X21zZw== 6619 -Q0Q= 6620 -IExv 6621 -IHNlY29uZHM= 6622 -aWJseQ== 6623 -LmVxdWFscw== 6624 -IGludGVybmF0aW9uYWw= 6625 -I3ByYWdtYQ== 6626 -b290aA== 6627 -V3JpdGVy 6628 -aWF0ZQ== 6629 -IGNlbGU= 6630 -IEJpdA== 6631 -aXZv 6632 -aXZlcnk= 6633 -cmQ= 6634 -SEVDSw== 6635 -IGNhY2hl 6636 -LmNvdW50 6637 -IHJvbGw= 6638 -LlJlYWQ= 6639 -MTA4 6640 -UkVE 6641 -IHNldHVw 6642 -aXpvbnRhbA== 6643 -bW9kZWxz 6644 -YXJndg== 6645 -IGNvbnNpZGVyZWQ= 6646 -PSIuLi8= 6647 -c2V0dGluZ3M= 6648 -IFJlbA== 6649 -IGdyb3d0aA== 6650 -IG1peA== 6651 -IFdhc2hpbmd0b24= 6652 -IHBsdA== 6653 -IElN 6654 -4bo= 6655 -IHR1cm5lZA== 6656 -IERhdGVUaW1l 6657 -IFdlZA== 6658 -KHVybA== 6659 -ICIt 6660 -IGxldHRlcg== 6661 -QXN5bmM= 6662 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 6663 -IE9jdG9iZXI= 6664 -X2xpbmU= 6665 -IGF0dGVudGlvbg== 6666 -IGNvbGxlY3Q= 6667 -IEhhc2g= 6668 -IGltYWc= 6669 -VHJlZQ== 6670 -IHNpdHVhdGlvbg== 6671 -ZXR0ZQ== 6672 -X25v 6673 -SVZF 6674 -IHZvbg== 6675 -LnRhcmdldA== 6676 -IGtub3dsZWRnZQ== 6677 -IGRyaXZl 6678 -LnBvc3Q= 6679 -IGJsb29k 6680 -IGNpdA== 6681 -cHJpbWFyeQ== 6682 -IGNvbmZpZ3VyYXRpb24= 6683 -dGVl 6684 -IHBob3Rv 6685 -aXNvZGU= 6686 -VHJhY2U= 6687 -IGdhdmU= 6688 -IHNob3Q= 6689 -IEFpcg== 6690 -IG1vdGhlcg== 6691 -cHJpY2U= 6692 -IG1vcm5pbmc= 6693 -KSl7Cg== 6694 -LXg= 6695 -IHRyYWRl 6696 -IGRlc2M= 6697 -ICYmCg== 6698 -IHBhcmVudHM= 6699 -QXBp 6700 -5Yg= 6701 -dGVk 6702 -d2Vy 6703 -IOY= 6704 -IHN5 6705 -IEtl 6706 -UGFyc2Vy 6707 -5YU= 6708 -YW5jeQ== 6709 -IHBpZWNl 6710 -aWZvcm5pYQ== 6711 -dG9TdHJpbmc= 6712 -cmFu 6713 -aWRpbmc= 6714 -UFRJT04= 6715 -Y29tZXM= 6716 -L2xpYw== 6717 -LmNsaWVudA== 6718 -RWw= 6719 -TG9uZw== 6720 -IHByb2Zlc3Npb25hbA== 6721 -cnVwdA== 6722 -dmE= 6723 -IGNvbXBsZXRlbHk= 6724 -IHByYWN0aWNl 6725 -MDAy 6726 -IHNlbGVjdGlvbg== 6727 -UmVt 6728 -aW5p 6729 -IGNhbQ== 6730 -UkVF 6731 -IHNpdGVz 6732 -cGE= 6733 -QVRVUw== 6734 -0YHRgg== 6735 -YXJyYW50 6736 -Kig= 6737 -X0tFWQ== 6738 -IEJ1dHRvbg== 6739 -IEZyaWRheQ== 6740 -c2VxdQ== 6741 -IHJlYWRlcg== 6742 -IG1lc3NhZ2Vz 6743 -6K8= 6744 -IGJ1Zg== 6745 -S2U= 6746 -IG5vdg== 6747 -SFA= 6748 -TXNn 6749 -YWxpZ24= 6750 -YXJpbHk= 6751 -ICcs 6752 -X3dpdGg= 6753 -IGRhcw== 6754 -IGhlYXJk 6755 -YXRvbWlj 6756 -cmlhbA== 6757 -KVs= 6758 -IGRpc2U= 6759 -QGVuZA== 6760 -IGdvbGQ= 6761 -IGZhaXI= 6762 -IHNhbGVz 6763 -LkJ1dHRvbg== 6764 -c3RyaWN0 6765 -c2F2ZQ== 6766 -IG1lYXN1cmU= 6767 -ICIr 6768 -ZWNhdXNl 6769 -Vmlld0NvbnRyb2xsZXI= 6770 -IFRhYmxl 6771 -LnBhcmFt 6772 -IGRlY2lkZWQ= 6773 -KCgo 6774 -SU5GTw== 6775 -IG9wcG9ydHVuaXR5 6776 -VGU= 6777 -SUNFTlNF 6778 -Y2NvcmRpbmc= 6779 -a2k= 6780 -IFVO 6781 -IGNvbnRhaW4= 6782 -IG1hbmFnZXI= 6783 -IHBhaW4= 6784 -IEZpcmU= 6785 -cm9tZQ== 6786 -IHBsYW5z 6787 -Rm91bmQ= 6788 -bGF5 6789 -IERlY2VtYmVy 6790 -IGluZmx1 6791 -w7o= 6792 -cmVuY2g= 6793 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 6794 -YXppbmc= 6795 -YnJpZWY= 6796 -Y2FsbA== 6797 -d29vZA== 6798 -IGxvYWRlZA== 6799 -IGdyYW5k 6800 -L2Y= 6801 -aW1w 6802 -X1U= 6803 -MTI3 6804 -U1RS 6805 -4oCi 6806 -IGNyZWRpdA== 6807 -LkNvbG9y 6808 -b3JnZQ== 6809 -UVVFU1Q= 6810 -IGRpZmZlcmVuY2U= 6811 -IFBD 6812 -d2FyZ3M= 6813 -IHB1Yg== 6814 -dW5kYXk= 6815 -IGZyYQ== 6816 -Lm1heA== 6817 -IHRyaWVk 6818 -YW5uZWxz 6819 -c2VuZA== 6820 -IHJlcG9ydHM= 6821 -IGFkdWx0 6822 -5Lo= 6823 -IGNvbnNpc3Q= 6824 -IFN0cmVldA== 6825 -IFByb2dyYW0= 6826 -U1FM 6827 -TWF0cml4 6828 -b3VuY2ls 6829 -LUE= 6830 -CXc= 6831 -IHdob3Nl 6832 -IHJlbGln 6833 -IFNleA== 6834 -IGdpdmVz 6835 -bm9uZQ== 6836 -Lm1lc3NhZ2U= 6837 -KEc= 6838 -LmF3dA== 6839 -LXJpZ2h0 6840 -IE5vdmVtYmVy 6841 -ZWxsaWc= 6842 -MzYw 6843 -dXRpdmU= 6844 -xIM= 6845 -b3Zlcm4= 6846 -IGVhc2lseQ== 6847 -IGlkZWFz 6848 -MTA0 6849 -INC9 6850 -L2Nzcw== 6851 -bHlpbmc= 6852 -ZWxsZQ== 6853 -Q2Fu 6854 -X2NvbG9y 6855 -0L7Qsg== 6856 -IHBhaXI= 6857 -bmd0aA== 6858 -IHNwbGl0 6859 -MTQw 6860 -ZHJvcA== 6861 -YXJ0eQ== 6862 -b25h 6863 -IGNhcGl0YWw= 6864 -IGhlYXI= 6865 -IGV4aXN0cw== 6866 -CWxvZw== 6867 -ZW1v 6868 -UnVu 6869 -b2k= 6870 -IHBhcnNlcg== 6871 -IE1ldGhvZA== 6872 -IGVkdWNhdGlvbg== 6873 -W2s= 6874 -IGxpYnJhcnk= 6875 -PiI7Cg== 6876 -X1VO 6877 -CXN0ZA== 6878 -b2RlZA== 6879 -IGNhbGxz 6880 -aGVyZQ== 6881 -UmVs 6882 -IGJyYW5k 6883 -YmFja2dyb3VuZA== 6884 -Z2E= 6885 -X2FkZHJlc3M= 6886 -X3BhcmFtcw== 6887 -Q2F0ZWdvcnk= 6888 -MTAz 6889 -IEluZGlh 6890 -X2V2ZW50 6891 -IGluZw== 6892 -UmVuZGVy 6893 -LmNs 6894 -dW1weQ== 6895 -IHBldA== 6896 -RkM= 6897 -IEFudA== 6898 -RXh0 6899 -IGNoYXJnZQ== 6900 -ZW5lZA== 6901 -Z3JhZA== 6902 -RU8= 6903 -IGRlcGVuZA== 6904 -IC4KCg== 6905 -ZnJhbWU= 6906 -IGRm 6907 -IGh1Z2U= 6908 -IFBBUlQ= 6909 -ZWRz 6910 -Ozs= 6911 -IEFN 6912 -IGJhc2lj 6913 -IExldA== 6914 -bGljaA== 6915 -IGFybQ== 6916 -IHN0YXI= 6917 -IGZlZGVyYWw= 6918 -V29yaw== 6919 -IGNhcnJ5 6920 -IElzcmFlbA== 6921 -KG9iag== 6922 -PXt7 6923 -IHNhdmVk 6924 -IHN5bg== 6925 -IGNvbnN0YW50 6926 -VkVOVA== 6927 -IHBvc2l0aXZl 6928 -IGNvbmR1Y3Q= 6929 -IHNraW4= 6930 -IGVhcmxpZXI= 6931 -IGxheW91dA== 6932 -IElQ 6933 -T1VS 6934 -IHRpbQ== 6935 -c3R5bGVzaGVldA== 6936 -X2Ns 6937 -IENhcmQ= 6938 -Kyspewo= 6939 -IHRlbXBlcg== 6940 -IERhdmlk 6941 -CXRyeQ== 6942 -LmRhcnQ= 6943 -IHdhbnRz 6944 -IHBpY3R1cmU= 6945 -IHZpZGVvcw== 6946 -IENvbW0= 6947 -aXNpb25z 6948 -X01BWA== 6949 -TWFwcGluZw== 6950 -LWNvbnRlbnQ= 6951 -IEVhcg== 6952 -LWRl 6953 -IHByZW0= 6954 -YnJ1YXJ5 6955 -IGNvbXBvbmVudHM= 6956 -IHRocm91Z2hvdXQ= 6957 -IHB1bGw= 6958 -IHBhZ2Vz 6959 -ZW50ZQ== 6960 -cmVzcG9uZA== 6961 -IGdhcw== 6962 -Y3JpcHRvcg== 6963 -IGVkZ2U= 6964 -IGJvdW5k 6965 -QUNU 6966 -KioqKioq 6967 -IGNyZWF0aW5n 6968 -IENI 6969 -IG51bGxwdHI= 6970 -QnI= 6971 -Kyc= 6972 -LmNv 6973 -Pjo6 6974 -IGxlYXJuaW5n 6975 -Lkxlbmd0aA== 6976 -X1NI 6977 -IHBhdGllbnRz 6978 -QUlO 6979 -IGtpZHM= 6980 -IGNvbWZvcnQ= 6981 -IHNob3du 6982 -dWdpbnM= 6983 -IEJhY2s= 6984 -ZWxsYQ== 6985 -X0NM 6986 -IGxhdA== 6987 -IGRpc3BhdGNo 6988 -IGNsYXNzZXM= 6989 -LmF0 6990 -LmJlZ2lu 6991 -IHN1Y2Nlc3NmdWw= 6992 -YmFu 6993 -IG9idGFpbg== 6994 -IFNs 6995 -IGxhY2s= 6996 -aXRlcmF0b3I= 6997 -VGhyZWFk 6998 -KHNpemU= 6999 -IG5vbmU= 7000 -Lmhhcw== 7001 -X1g= 7002 -c29ydA== 7003 -bmFw 7004 -cGV0 7005 -Ymlu 7006 -NzAw 7007 -IENhbmFkYQ== 7008 -VGhleQ== 7009 -IGRhbnM= 7010 -IE1hdA== 7011 -PHRk 7012 -IGhhaXI= 7013 -ICcnLAo= 7014 -IGN1 7015 -IGxhd3M= 7016 -bGV0ZWQ= 7017 -cGVk 7018 -IHBvdw== 7019 -IGtuZXc= 7020 -X0NPTQ== 7021 -Xyw= 7022 -IE1hZw== 7023 -aWRlbnRz 7024 -KHJlcQ== 7025 -ICks 7026 -LWNlbnRlcg== 7027 -MTkw 7028 -IHdpZGU= 7029 -IEF1dGhvcg== 7030 -c3RhbnRz 7031 -IGpvYnM= 7032 -IG1hdGg= 7033 -ZXRpbWVz 7034 -Qm9vbGVhbg== 7035 -IHNjb3Bl 7036 -X2lz 7037 -IG1lYXM= 7038 -IGtleXM= 7039 -ZWxheQ== 7040 -IGV4YWN0bHk= 7041 -Jz0+Jw== 7042 -IFBhdWw= 7043 -bWFz 7044 -CXByaW50 7045 -KGxlbg== 7046 -ZmQ= 7047 -ICk7 7048 -LkV2ZW50 7049 -cWxp 7050 -aXJpdA== 7051 -aWVsZHM= 7052 -b21hbg== 7053 -IFRvcA== 7054 -IHZvdGU= 7055 -IG1hc2s= 7056 -IHRoZW1l 7057 -LQo= 7058 -IHByb3Bz 7059 -IGZpbmU= 7060 -IHdyaXRlcg== 7061 -X29mZnNldA== 7062 -Y2Fy 7063 -IGFsdGVybg== 7064 -IGNvcHlyaWdodA== 7065 -IGRlc3Ryb3k= 7066 -cHBlcg== 7067 -IGdlbmVyYXRl 7068 -cHBlZA== 7069 -4oCZZA== 7070 -ICAgICAgCg== 7071 -bWFrZQ== 7072 -IFNob3c= 7073 -IGJyb3dzZXI= 7074 -IGZhdm9yaXRl 7075 -IGNhcmVlcg== 7076 -IGhhcHBlbmVk 7077 -KGNoYXI= 7078 -IHJlY29tbWVuZA== 7079 -IGxpdGVy 7080 -LmZpbHRlcg== 7081 -Z3JhZGU= 7082 -IMKj 7083 -UGhvbmU= 7084 -b21z 7085 -IG5hbWVk 7086 -LWxhYmVs 7087 -aXBv 7088 -IE90aGVy 7089 -IHBhbmVs 7090 -IHJvY2s= 7091 -U2NhbGU= 7092 -CWFzc2VydA== 7093 -0LQ= 7094 -IHRydXN0 7095 -ZnJvbnQ= 7096 -IGRlbW9u 7097 -QXI= 7098 -TmV0 7099 -IGVjb25vbWlj 7100 -Zm9vdGVy 7101 -IHJhY2U= 7102 -KG5vZGU= 7103 -IE9wdGlvbg== 7104 -c3BsaXQ= 7105 -IHBoeXNpY2Fs 7106 -aWZlc3Q= 7107 -IHJlbW92ZWQ= 7108 -Lmh0dHA= 7109 -KSksCg== 7110 -IGxvb2tlZA== 7111 -Jzs= 7112 -ZGluZw== 7113 -Z2VzdA== 7114 -YXR1cmRheQ== 7115 -L2xpY2Vuc2Vz 7116 -UHJpY2U= 7117 -IGRybw== 7118 -IHRvd2FyZHM= 7119 -IHVucw== 7120 -IENM 7121 -CXN0YXRpYw== 7122 -IHJvd3M= 7123 -IGRlZmluZQ== 7124 -LnJlcGxhY2U= 7125 -IGZhdGhlcg== 7126 -IERlc2lnbg== 7127 -YXNzaWdu 7128 -bXV0 7129 -RGV2aWNl 7130 -RGlk 7131 -JykpCg== 7132 -b21ldHJ5 7133 -YXlsb2Fk 7134 -IGhpc3Rvcg== 7135 -IFBhcmFt 7136 -IEJvb2xlYW4= 7137 -IG5hdHVyZQ== 7138 -IGpz 7139 -IG5hdGlvbg== 7140 -aWg= 7141 -IGRpc2NvdmVy 7142 -c2Vt 7143 -SGFuZGxl 7144 -CXI= 7145 -IFRlY2hu 7146 -IHdhbGw= 7147 -eyQ= 7148 -QHByb3BlcnR5 7149 -ICIuLi8= 7150 -IGV4YW0= 7151 -LmRyYXc= 7152 -b3BwaW5n 7153 -IG5lYXJseQ== 7154 -IGNvb2w= 7155 -IGluZGVwZW5k 7156 -UkVT 7157 -IGhhbmRsZXI= 7158 -IE1vbmRheQ== 7159 -IHN1bg== 7160 -U3R5bGVz 7161 -b3VzbHk= 7162 -IAk= 7163 -dmVzdA== 7164 -RGlzcGxheQ== 7165 -KHk= 7166 -YXRpY2FsbHk= 7167 -IHByZWRpY3Q= 7168 -eWluZw== 7169 -IHNvbWV0aW1lcw== 7170 -Il0K 7171 -IGRyaW5r 7172 -IGJ1bA== 7173 -aWZpY2F0aW9ucw== 7174 -Lmluc2VydA== 7175 -LnJlZw== 7176 -IHRlc3Rz 7177 -QWxpZ25tZW50 7178 -IGFsbGVn 7179 -IGF0dHJpYnV0ZQ== 7180 -IE5vdGU= 7181 -IG15c2VsZg== 7182 -YXJ0cw== 7183 -Tm93 7184 -IGludGVyZXN0aW5n 7185 -bGllbnRz 7186 -IHBvcHVsYXRpb24= 7187 -IENhbGlmb3JuaWE= 7188 -Ikk= 7189 -5bk= 7190 -IGdyZWF0ZXI= 7191 -dWVzZGF5 7192 -IHRob3Vz 7193 -IGNvc3Rz 7194 -IGxhdW5jaA== 7195 -XEh0dHA= 7196 -a2Vy 7197 -YmFuZA== 7198 -IFBsYXk= 7199 -IGJhbmQ= 7200 -LnNoYXBl 7201 -ZXNvbWU= 7202 -YXJ0aWNsZQ== 7203 -LnJm 7204 -IHdlcg== 7205 -w6Fz 7206 -ZW1iZXJz 7207 -dXNy 7208 -QkE= 7209 -aWNhbg== 7210 -ZXR0 7211 -dmFsaWRhdGU= 7212 -dWx0aQ== 7213 -IGltbWVkaWF0ZWx5 7214 -emVy 7215 -IGZpZ3VyZQ== 7216 -b2Vz 7217 -ZWxsZXI= 7218 -aXJjbGU= 7219 -IFNpZ24= 7220 -LmRi 7221 -IHJhbms= 7222 -Qnl0ZXM= 7223 -IHByb2plY3Rz 7224 -X3JlYw== 7225 -VUxBUg== 7226 -QVBJ 7227 -IExpbmU= 7228 -UG9ydA== 7229 -IHBvbGw= 7230 -IGdpdmluZw== 7231 -aWRlbmNl 7232 -LS0K 7233 -IHBsb3Q= 7234 -aWNpYWw= 7235 -IHdhcnJhbnQ= 7236 -SVRJT04= 7237 -IERvdWJsZQ== 7238 -IGJpbGxpb24= 7239 -Z29yaXRobQ== 7240 -IGVxdWlwbWVudA== 7241 -REFURQ== 7242 -IEAi 7243 -RUU= 7244 -IHBsZQ== 7245 -aWF0aW9u 7246 -IGhlYWRlcnM= 7247 -IHByb2NlZA== 7248 -LkNvbXBvbmVudE1vZGVs 7249 -IE9iYW1h 7250 -IHBh 7251 -IEJlc3Q= 7252 -aW1hdGVseQ== 7253 -LmdldFN0cmluZw== 7254 -Llw= 7255 -bXBsb3k= 7256 -IHJhdw== 7257 -X2Jsb2Nr 7258 -dW5kcmVk 7259 -In0sCg== 7260 -MTEy 7261 -Lkdyb3VwTGF5b3V0 7262 -IGJyb3VnaHQ= 7263 -TlNTdHJpbmc= 7264 -dGhyb3c= 7265 -Y3JlYXRlZA== 7266 -Lk5ldw== 7267 -X3ZpZXc= 7268 -Q1A= 7269 -ZXBz 7270 -T3A= 7271 -IGdyYXRpcw== 7272 -ICci 7273 -IGludGVydmlldw== 7274 -IiIiCg== 7275 -IHBhcnRpYWw= 7276 -IGFyaWE= 7277 -YmluZw== 7278 -QXV0aG9y 7279 -Qm9vaw== 7280 -IFBhdA== 7281 -dW1hbg== 7282 -VXNlcnM= 7283 -cGx1cw== 7284 -MTkz 7285 -IERpcmVjdA== 7286 -dmVudWU= 7287 -YWxwaGE= 7288 -VUNDRVNT 7289 -IENhbGw= 7290 -ICk7DQo= 7291 -aW1hdGVk 7292 -IHJlbWFpbg== 7293 -IGFudGk= 7294 -IExvbmRvbg== 7295 -IHNhZmV0eQ== 7296 -UE9TRQ== 7297 -b2xlcw== 7298 -Y29udHJvbGxlcg== 7299 -Qnl0ZQ== 7300 -IENvdXJ0 7301 -IFBoaWw= 7302 -IEFzc29jaQ== 7303 -ZW5h 7304 -5ZA= 7305 -X1NUUg== 7306 -Y29pbg== 7307 -cmVzaG9sZA== 7308 -IGJhdGNo 7309 -X0NsaWNr 7310 -ZW50aWNhdGlvbg== 7311 -Pic7Cg== 7312 -ZW50eQ== 7313 -IGJlZ2lubmluZw== 7314 -IHplcm8= 7315 -IENvbnZlcnQ= 7316 -IHRlcnI= 7317 -IHBhaWQ= 7318 -IGluY3JlYXNlZA== 7319 -Y2F0Y2g= 7320 -LXNpemU= 7321 -MTE1 7322 -YWN0aXZpdHk= 7323 -ZXF1YWxz 7324 -IHF1ZXVl 7325 -ICIn 7326 -IEludGVybmF0aW9uYWw= 7327 -IGbDvHI= 7328 -dXJzZGF5 7329 -IHNjaWVudA== 7330 -YWxsb3c= 7331 -YXhpcw== 7332 -IGFwcHJvcHJp 7333 -ZWRnZQ== 7334 -IGlkeA== 7335 -U3VjY2Vzcw== 7336 -ZW50aWZpZXI= 7337 -Olw= 7338 -eGlz 7339 -IG1heGltdW0= 7340 -YXJrcw== 7341 -IGJpcnRo 7342 -KGluZGV4 7343 -IG1heWJl 7344 -LnB5 7345 -ZmlsZXM= 7346 -IGxpbWl0ZWQ= 7347 -X2NoZWNr 7348 -bG9vaw== 7349 -cGxpZXM= 7350 -IG1vdmVtZW50 7351 -J10u 7352 -IGJyb2Fk 7353 -IEJF 7354 -IFVuaXR5RW5naW5l 7355 -LmNwcA== 7356 -IEV2ZXJ5 7357 -QWRtaW4= 7358 -IGZhbnM= 7359 -cGFyZWQ= 7360 -CiAgICAK 7361 -IGZvcmVpZ24= 7362 -IHBhbg== 7363 -IHRvdXI= 7364 -IE9yZGVy 7365 -IG1vdmluZw== 7366 -IGF1Zg== 7367 -Q2FsbA== 7368 -Y2I= 7369 -xZ8= 7370 -dmVudG9yeQ== 7371 -IFNxbA== 7372 -IGZ1bGx5 7373 -Q2xpY2tMaXN0ZW5lcg== 7374 -V09SRA== 7375 -IGFubm91bmNlZA== 7376 -KQ0KDQo= 7377 -IGFncmVlZA== 7378 -cmll 7379 -IGVhcm4= 7380 -X2xpbms= 7381 -LmFycmF5 7382 -KHRleHQ= 7383 -IG1hdGVyaWFscw== 7384 -LHA= 7385 -ZmZmZg== 7386 -dmc= 7387 -IMKp 7388 -IHVubGVzcw== 7389 -YWpheA== 7390 -TE9H 7391 -IHNleHVhbA== 7392 -IFwi 7393 -LXRpbWU= 7394 -IGNvYWNo 7395 -IHN1cHBvcnRlZA== 7396 -IHBob3Rvcw== 7397 -aWZvcm0= 7398 -LkNyZWF0ZQ== 7399 -KV0= 7400 -cmllcg== 7401 -IGRpYWxvZw== 7402 -YXZlcg== 7403 -aWdl 7404 -KSs= 7405 -X2lkeA== 7406 -Ols= 7407 -X21pbg== 7408 -IENvbmc= 7409 -IHByZXNzdXJl 7410 -IHRlYW1z 7411 -U2lnbg== 7412 -YmVnaW4= 7413 -cmlhbg== 7414 -TkVTUw== 7415 -TFM= 7416 -IGltcHJvdmU= 7417 -IFN1bmRheQ== 7418 -IGRlZmluaXRpb24= 7419 -aWdlcg== 7420 -cm9sbGVycw== 7421 -IHRoaW5raW5n 7422 -VGVtcGxhdGU= 7423 -LUY= 7424 -IGVtZXJn 7425 -cGxhdGVz 7426 -IFVTQQ== 7427 -LnNldFN0YXRl 7428 -IEFsc28= 7429 -cmV2 7430 -IGVuYWJsZQ== 7431 -IENP 7432 -UEVDVA== 7433 -IGNvbmNlcHQ= 7434 -KS0= 7435 -IOKAog== 7436 -IHNldHM= 7437 -IG1lYW5pbmc= 7438 -ZW1vbg== 7439 -IENvbnM= 7440 -Y21w 7441 -ZWRlcg== 7442 -YW5uZWQ= 7443 -aWNlbnNlZA== 7444 -IFN1cGVy 7445 -IGRhaWx5 7446 -IG11bHRp 7447 -X3U= 7448 -IGNoYWxsZW5n 7449 -X21vZGU= 7450 -IFByb21pc2U= 7451 -IHN0cmljdA== 7452 -am8= 7453 -aW50b24= 7454 -KGxpc3Q= 7455 -T25seQ== 7456 -Pns= 7457 -IHZlaGljbGU= 7458 -7ZU= 7459 -IFBsYXllcg== 7460 -MTA2 7461 -IERlbA== 7462 -IHBvb2w= 7463 -LnVybA== 7464 -bmVzZGF5 7465 -KCk7DQoNCg== 7466 -OTAw 7467 -ICIpOwo= 7468 -TG9jYWw= 7469 -LiIpOwo= 7470 -IG9yZ2FuaXphdGlvbg== 7471 -cmVuZGVy 7472 -IEFwcGxpY2F0aW9u 7473 -IHN1bW1lcg== 7474 -ZXhwZWN0ZWQ= 7475 -TkE= 7476 -IHJhcA== 7477 -X29iag== 7478 -IHN1cmZhY2U= 7479 -IFBVUg== 7480 -IH0sCgo= 7481 -IHZhcmlhYmxlcw== 7482 -KG1lc3NhZ2U= 7483 -IG9waW4= 7484 -LmJhY2s= 7485 -0LDQvQ== 7486 -IHdvcmtlcnM= 7487 -dm0= 7488 -Q28= 7489 -dWdodGVy 7490 -IG1hc3Rlcg== 7491 -ICIiLA== 7492 -IHN0b3JpZXM= 7493 -LlVzZXI= 7494 -IGNlbGVicg== 7495 -aW5lc2U= 7496 -QlM= 7497 -IENvbW1hbmQ= 7498 -YXNoYm9hcmQ= 7499 -IG9n 7500 -a2c= 7501 -LmltYWdl 7502 -LnN0eWxl 7503 -IHN0ZXBz 7504 -IEJlbg== 7505 -KGFyZ3M= 7506 -NDA0 7507 -IFBlcnNvbg== 7508 -LHk= 7509 -IG9mZmljaWFscw== 7510 -fAo= 7511 -IHNraWxscw== 7512 -dmM= 7513 -IGJ1aWxkZXI= 7514 -IGdhcg== 7515 -QWNjb3VudA== 7516 -IEF1dGg= 7517 -55Q= 7518 -J10pCg== 7519 -IEFU 7520 -bm4= 7521 -LkludA== 7522 -U1NFUlQ= 7523 -IGVmZmVjdGl2ZQ== 7524 -TEVURQ== 7525 -IHRvb2xz 7526 -QVJE 7527 -IGRpZ2l0YWw= 7528 -MTkx 7529 -RG91Ymxl 7530 -IEZpbmQ= 7531 -UkM= 7532 -IGlubGluZQ== 7533 -L3I= 7534 -QVJBTQ== 7535 -QVNL 7536 -IGludGVudA== 7537 -YWlnaHQ= 7538 -X2FkZHI= 7539 -IHJlcXVlc3Rz 7540 -LmZpcnN0 7541 -IGRlYnVn 7542 -IHNwZW50 7543 -KCkpKTsK 7544 -xZs= 7545 -IHByaW5jaXA= 7546 -TG9nZ2Vy 7547 -Y2x1ZGVz 7548 -LnVzZQ== 7549 -IHN1cnY= 7550 -bWVkaWE= 7551 -IEZlYnJ1YXJ5 7552 -IE1hYw== 7553 -IG1pc3Npbmc= 7554 -IHdpZmU= 7555 -IHRhbGtpbmc= 7556 -IE1ha2U= 7557 -IGNhcnQ= 7558 -IGxvY2F0ZWQ= 7559 -RW5j 7560 -LWE= 7561 -Y2hyb24= 7562 -IGNhcmRz 7563 -IGd1eQ== 7564 -IHBlcnM= 7565 -IFllcw== 7566 -YXRldmVy 7567 -IEFuZw== 7568 -b2xhcg== 7569 -IEV2ZW4= 7570 -IGFjY3Vy 7571 -IFBvd2Vy 7572 -IEdvbGQ= 7573 -Y2xlYXI= 7574 -UHJvY2Vzcw== 7575 -IHJlY29yZHM= 7576 -IGtpbGxlZA== 7577 -LmNsZWFy 7578 -IFdBUlJBTlRJRVM= 7579 -IHB1cnBvc2U= 7580 -cGFuZWw= 7581 -SkVDVA== 7582 -w61h 7583 -IGV4ZXJj 7584 -V1M= 7585 -L0w= 7586 -LmV4cG9ydHM= 7587 -IF9fXw== 7588 -IHNpbg== 7589 -U2VydmxldA== 7590 -IGTDqQ== 7591 -LmRlbGV0ZQ== 7592 -cm9rZQ== 7593 -U2w= 7594 -dWdo 7595 -ZWFycw== 7596 -IHBvaW50ZXI= 7597 -IGhvcA== 7598 -YWxsZXJ5 7599 -IG9icw== 7600 -Y292ZXJ5 7601 -CWNoYXI= 7602 -CQkJCQkJCQkJCQ== 7603 -CWRlZg== 7604 -b2NpdHk= 7605 -aXRjaGVu 7606 -dWxhdGlvbnM= 7607 -IEZJVA== 7608 -ICku 7609 -c3RyYWludHM= 7610 -dmVudGlvbg== 7611 -IHJlcXVpcmVz 7612 -IE9wZXI= 7613 -TUU= 7614 -T1VOVA== 7615 -YWxsZXQ= 7616 -IG5vcm0= 7617 -SVJF 7618 -ZXhhcw== 7619 -IHByb2dyYW1z 7620 -IHdlYWs= 7621 -Jy4k 7622 -dWluZw== 7623 -CSAgICAgICA= 7624 -IG1pbA== 7625 -IGZpcm0= 7626 -aW5pdGVseQ== 7627 -X1ZBTFVF 7628 -YXBzZQ== 7629 -YXRpc2Y= 7630 -IGRlbWFuZA== 7631 -X21vZA== 7632 -IGRlc2NyaWJlZA== 7633 -IHBsYWNlcw== 7634 -VklE 7635 -IGFsb25l 7636 -IGV4cG9ydA== 7637 -IHZlYw== 7638 -IE1heA== 7639 -IGFjdGl2aXRpZXM= 7640 -aWN0dXJlcw== 7641 -Z2VuZXI= 7642 -IG1h 7643 -gqw= 7644 -IGV4cHJlc3Npb24= 7645 -Q2FsbGJhY2s= 7646 -X2NvbnRlbnQ= 7647 -IE1vc3Q= 7648 -IHRlc3Rpbmc= 7649 -RUM= 7650 -Q0hBTlQ= 7651 -IGFkanVzdA== 7652 -LlRocmVhZGluZw== 7653 -KGN0eA== 7654 -IGFncmVl 7655 -aWdoZXN0 7656 -IHVp 7657 -IExhdw== 7658 -Llk= 7659 -Pjw/ 7660 -IHBvZA== 7661 -LWxn 7662 -4oCdCgo= 7663 -IGRlc2NyaWJl 7664 -IEV1cm9wZWFu 7665 -LXNo 7666 -IFBVUlBPU0U= 7667 -T1JZ 7668 -IGNvbnZlcnM= 7669 -IElsbHVtaW5hdGU= 7670 -IEF2 7671 -KGNo 7672 -PyI= 7673 -Y2hlbg== 7674 -aW1h 7675 -RG9jdW1lbnQ= 7676 -IG9wZXJhdGlvbnM= 7677 -d2lu 7678 -CWZ1bmN0aW9u 7679 -LkltYWdl 7680 -IHNjZW4= 7681 -L2g= 7682 -IFND 7683 -IGV4cGxv 7684 -OiU= 7685 -LyoqDQo= 7686 -TkFNRQ== 7687 -5og= 7688 -KHZhcg== 7689 -IGRpcmVjdG9y 7690 -T05H 7691 -IHlpZWxk 7692 -IGZlZXQ= 7693 -IFNlYXJjaA== 7694 -IEls 7695 -IHJlc3RhdXI= 7696 -ZHVj 7697 -IGludGVnZXI= 7698 -MTA3 7699 -ICcnOwo= 7700 -IGhpZ2hseQ== 7701 -Y2hlY2tlZA== 7702 -IFBBUlRJQw== 7703 -RVJDSEFOVA== 7704 -77yJ 7705 -IG9wdGlt 7706 -UXVldWU= 7707 -IExJ 7708 -aXRhdGlvbg== 7709 -IHRyYW5zcG9ydA== 7710 -aXNzaW9u 7711 -ZmlsbA== 7712 -dXNpb24= 7713 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 7714 -CWJvb2w= 7715 -LXRo 7716 -dXB0 7717 -IGVzc2VudGlhbA== 7718 -YW50ZWQ= 7719 -IGJlbmVmaXRz 7720 -CVM= 7721 -JzsNCg== 7722 -aWtp 7723 -IGdpcmxz 7724 -aWNlZA== 7725 -YnVmZmVy 7726 -XSs= 7727 -IHNvY2tldA== 7728 -IHByaWNlcw== 7729 -IEZyZQ== 7730 -IHNhdA== 7731 -IHdvb2Q= 7732 -TWVudUl0ZW0= 7733 -QVJH 7734 -IEFkbWlu 7735 -T1dO 7736 -ZGs= 7737 -IHJlc2V0 7738 -IGZvcm1z 7739 -INC4 7740 -5pY= 7741 -IFR1ZXNkYXk= 7742 -MTA5 7743 -IEluaXRpYWxpemVk 7744 -X3RyYWlu 7745 -b3Jhcnk= 7746 -YXRlZ29y 7747 -IGR0 7748 -VG90YWw= 7749 -Y29uc3RydWN0 7750 -aWxpZXM= 7751 -IGd1eXM= 7752 -0LXRgA== 7753 -IGluc3RydWN0aW9u 7754 -MDEw 7755 -eWxlZA== 7756 -IGludGVybmV0 7757 -ZXRhZGF0YQ== 7758 -YWR5 7759 -ZmFjZXM= 7760 -amVjdGlvbg== 7761 -IEphY2s= 7762 -IHJlY3Q= 7763 -Wy0= 7764 -IExlZw== 7765 -IGRldmljZXM= 7766 -T0M= 7767 -ICoNCg== 7768 -b3JhdGlvbg== 7769 -ZXJ0YWlu 7770 -IGd1YXJk 7771 -b3N0cmVhbQ== 7772 -IGVudW0= 7773 -LmxheW91dA== 7774 -ICI7Cg== 7775 -dm9rZQ== 7776 -IE9r 7777 -SG9tZQ== 7778 -KHRy 7779 -RVRI 7780 -IGRlbGF5 7781 -IHB1cmNoYXNl 7782 -ZGM= 7783 -IGFyZW4= 7784 -X29uY2U= 7785 -CQkJCQo= 7786 -cm9y 7787 -ZHJhdw== 7788 -LnJ1bg== 7789 -KG1vZGVs 7790 -VGltZW91dA== 7791 -bGlr 7792 -IEFyZw== 7793 -LmVu 7794 -IGZpc2g= 7795 -Y3B5 7796 -X2Zl 7797 -RVJDSEFOVEFCSUxJVFk= 7798 -KFg= 7799 -X291dHB1dA== 7800 -Pz8= 7801 -IGpv 7802 -YW5kYXJk 7803 -IGRvbGw= 7804 -ZXJyb3Jz 7805 -X2Jhc2U= 7806 -IFBBUlRJQ1VMQVI= 7807 -IGxlYWRlcg== 7808 -IGNvbXBhcg== 7809 -IGRvdWI= 7810 -IFZpcw== 7811 -U3RhY2tUcmFjZQ== 7812 -LUM= 7813 -IFN0dWQ= 7814 -c3RpdHV0ZQ== 7815 -TW9yZQ== 7816 -IERlc2NyaXB0aW9u 7817 -V0FSRQ== 7818 -YWRz 7819 -INC6 7820 -YmluZA== 7821 -PXNlbGY= 7822 -ZW1wbG95 7823 -W24= 7824 -LmFsbA== 7825 -LUI= 7826 -JiY= 7827 -YWxt 7828 -IGN1bHR1cmU= 7829 -aG91c2U= 7830 -IHN1ZmZlcg== 7831 -ICcl 7832 -IHN0cmFpZ2h0 7833 -IFN0YXI= 7834 -dWRv 7835 -IGRlZA== 7836 -IENPTQ== 7837 -IGNvbmZpcm0= 7838 -IEdvb2Q= 7839 -LnNj 7840 -X19fX19fX19fX19fX19fXw== 7841 -RFI= 7842 -Q29uZmlndXJhdGlvbg== 7843 -RGF0ZVRpbWU= 7844 -IGFkdmVydA== 7845 -IGNvdWxkbg== 7846 -YXN5bmM= 7847 -c3RhY2s= 7848 -JykNCg== 7849 -S2l0 7850 -IGhvdXM= 7851 -IG1lY2hhbg== 7852 -cmF0ZQ== 7853 -MjA0 7854 -IGF1ZGlv 7855 -CWNvdXQ= 7856 -Y29yZXM= 7857 -IHNwb3Q= 7858 -IGluY3JlYXNpbmc= 7859 -ICMj 7860 -KSkp 7861 -cG9pbnRz 7862 -IGNvbXBhcmVk 7863 -bGln 7864 -IGJlaGF2aW9y 7865 -IEJZ 7866 -IEF0dA== 7867 -Y3JhZnQ= 7868 -aGVhZGVycw== 7869 -ZXRl 7870 -ZW5kcmVnaW9u 7871 -IGRldGFpbA== 7872 -VUxF 7873 -IENvbW1vbg== 7874 -CXByb3RlY3RlZA== 7875 -c3Rvbg== 7876 -IEZJVE5FU1M= 7877 -IGZyZXNo 7878 -Ij4KCg== 7879 -LmV4YW1wbGU= 7880 -YmVyZw== 7881 -IG1vdmVk 7882 -CWU= 7883 -IFNhdHVyZGF5 7884 -IHBheWxvYWQ= 7885 -xIc= 7886 -KToKCg== 7887 -IGJleQ== 7888 -dXJlcg== 7889 -PHNjcmlwdA== 7890 -IHN5bWJvbA== 7891 -IGFzc3Vt 7892 -IHB1bA== 7893 -RWZmZWN0 7894 -IGh1bmRyZWQ= 7895 -VG9vbA== 7896 -YWtlZA== 7897 -Y29ubmVjdGlvbg== 7898 -IHZvaWNl 7899 -IHBk 7900 -IHRyYW5zYWN0aW9u 7901 -IGxpbmtz 7902 -RXJy 7903 -IEluZGlhbg== 7904 -VEM= 7905 -YXRhbG9n 7906 -bmk= 7907 -c2lnbg== 7908 -PDwi 7909 -amk= 7910 -eWE= 7911 -IGRlbW9uc3Ry 7912 -dWxhdGVk 7913 -LlN0 7914 -IGluc3RpdA== 7915 -IGJvb3N0 7916 -IGNlbGxz 7917 -b2xpYw== 7918 -LlBybw== 7919 -Ojwv 7920 -RXZlbnRMaXN0ZW5lcg== 7921 -aWZ5aW5n 7922 -IERp 7923 -b3Jyb3c= 7924 -LmV4ZWN1dGU= 7925 -IGNvbGxlZ2U= 7926 -WW91cg== 7927 -IGxhcmdlc3Q= 7928 -LmRpcw== 7929 -IHF1aQ== 7930 -IGluZGl2aWR1YWxz 7931 -X2J1ZmZlcg== 7932 -IG5n 7933 -U0E= 7934 -IENvbnRyb2w= 7935 -IHNpbmc= 7936 -IHN1aXQ= 7937 -ICAgIAk= 7938 -U0c= 7939 -IGp1bXA= 7940 -IHNtYXJ0 7941 -b21h 7942 -IEV4cA== 7943 -ICct 7944 -IGFzc2lzdA== 7945 -IHN1Y2Nlc3NmdWxseQ== 7946 -c3lz 7947 -IENyZQ== 7948 -X3JlZg== 7949 -IFRodXJzZGF5 7950 -IGJ1cg== 7951 -INC0 7952 -IGJleW9uZA== 7953 -IG5vZGVz 7954 -RGV0YWlscw== 7955 -aW5jdA== 7956 -IEphbWVz 7957 -IGFmZmVjdA== 7958 -ZXhjZXB0aW9u 7959 -IHR5cGVvZg== 7960 -KA0K 7961 -LXNl 7962 -IGZldGNo 7963 -YCw= 7964 -IGNydXNoZXI= 7965 -fS4= 7966 -IEJP 7967 -U2hvdw== 7968 -IHJhdGVz 7969 -IGJvbg== 7970 -LWljb24= 7971 -IE1lZGlh 7972 -UkVTUw== 7973 -IFZhbGlk 7974 -0L7Quw== 7975 -IGZ1Y2s= 7976 -YWNrcw== 7977 -IHN0dWRpZXM= 7978 -TWU= 7979 -IG93bmVycw== 7980 -fWVsc2U= 7981 -IGdyb3dpbmc= 7982 -VmFyaWFibGU= 7983 -IEJlbA== 7984 -LnJhbmRvbQ== 7985 -dmVtZW50 7986 -b255bQ== 7987 -KEY= 7988 -IEZBTFNF 7989 -IHRvcmNo 7990 -KHJvdw== 7991 -aWdv 7992 -c3RydWN0dXJl 7993 -MTIx 7994 -IGNlcnRhaW5seQ== 7995 -RGVw 7996 -IEdyZWVu 7997 -cXVlc3Rpb24= 7998 -IGFkZGluZw== 7999 -IERldmVsb3A= 8000 -X2RlZg== 8001 -IG1hY2g= 8002 -PSU= 8003 -CQkg 8004 -Y29uZHM= 8005 -UHJvamVjdA== 8006 -IHJlamVjdA== 8007 -IM4= 8008 -IHBvb3I= 8009 -IGF3YXJl 8010 -MTE0 8011 -IEJ1aWxk 8012 -IEJyaXRpc2g= 8013 -IE5F 8014 -IG51bWVy 8015 -cmVlcw== 8016 -Y2xhaW0= 8017 -IG1vY2s= 8018 -IG9t 8019 -IHNjcmU= 8020 -T0xE 8021 -LnBs 8022 -ZWxlcg== 8023 -IGNvcnJlc3BvbmQ= 8024 -X0hF 8025 -IGJpbmFyeQ== 8026 -MTE2 8027 -X29yZGVy 8028 -IFNRTA== 8029 -IGFkdmFudA== 8030 -IHByZXY= 8031 -Lls= 8032 -LmFzc2VydEVxdWFs 8033 -cGxpZXI= 8034 -YXJw 8035 -IGNsb3NlZA== 8036 -IGVuY291cg== 8037 -IFFTdHJpbmc= 8038 -YXVk 8039 -IGRldmVsb3BlZA== 8040 -IHBlcm1pc3Npb24= 8041 -LmRlYnVn 8042 -b3BlcmF0b3I= 8043 -ICcK 8044 -IHN5bQ== 8045 -YXRpdmVseQ== 8046 -w6ll 8047 -LWNvbG9y 8048 -IEdFVA== 8049 -a3k= 8050 -IGFsdGhvdWdo 8051 -X3JlcXVlc3Q= 8052 -X2VsZW1lbnQ= 8053 -Li4uLi4uLi4uLi4uLi4uLg== 8054 -X0RBVEE= 8055 -IGFtYXppbmc= 8056 -IHNi 8057 -IERlZmF1bHQ= 8058 -RXZlbnRz 8059 -IGZhaWx1cmU= 8060 -YWNsZQ== 8061 -UHJvcGVydGllcw== 8062 -IGRyZWFt 8063 -IGRpc3Ry 8064 -IGF1 8065 -IGdlbmVyYXRlZA== 8066 -5pU= 8067 -IFRlYW0= 8068 -VVNF 8069 -IGluY29tZQ== 8070 -IGV5ZQ== 8071 -X25vdA== 8072 -Il0s 8073 -X2Zvcm0= 8074 -U3VwcG9ydA== 8075 -b3JkZXJz 8076 -LlByaW50 8077 -dmlsbGU= 8078 -IFdlZG5lc2RheQ== 8079 -b2x2ZXI= 8080 -IG9wcG9z 8081 -aXNhdGlvbg== 8082 -b2xh 8083 -Q2xvc2U= 8084 -PHA= 8085 -X3dpZHRo 8086 -SW52YWxpZA== 8087 -eGI= 8088 -IHN0cnVnZw== 8089 -X2FjdGlvbg== 8090 -IHR4dA== 8091 -IFBhdGg= 8092 -YWxhcg== 8093 -IE1FUkNIQU5UQUJJTElUWQ== 8094 -c2VydmljZQ== 8095 -IE1pY2hhZWw= 8096 -YWJsZVZpZXc= 8097 -RGVidWc= 8098 -b2tlcw== 8099 -U2hl 8100 -IGd1ZXNz 8101 -IEphdmE= 8102 -X1BBVEg= 8103 -IHBhcnRpY3VsYXJseQ== 8104 -IElJ 8105 -IGRvbWFpbg== 8106 -5bm0 8107 -IHJlZHVjZQ== 8108 -LWxlZnQ= 8109 -cmVhbA== 8110 -IGFwcGVhcnM= 8111 -IGNvbW8= 8112 -IFVuaXQ= 8113 -IEdvdmVybg== 8114 -YWxp 8115 -YWxsZWw= 8116 -IEpldw== 8117 -X0k= 8118 -IGNvcw== 8119 -LmNvbG9y 8120 -IEdsb2JhbA== 8121 -IHRlbGU= 8122 -YmVu 8123 -X3RyYW5z 8124 -IHJlYXNvbnM= 8125 -IGVtYg== 8126 -ZW5zaXR5 8127 -bGluZXM= 8128 -b21pbg== 8129 -U2NyZWVu 8130 -0LDRgg== 8131 -cGVjdHM= 8132 -Y2xpcA== 8133 -Zm9v 8134 -cmVudA== 8135 -IGFm 8136 -IGRhbmdlcg== 8137 -aWxpbmc= 8138 -TmFtZXM= 8139 -T3Vy 8140 -IGRpc3RyaWJ1dGlvbg== 8141 -V2hpbGU= 8142 -U0w= 8143 -V3JpdGU= 8144 -IGdvdG8= 8145 -IGNvbG9ycw== 8146 -IHBvd2VyZnVs 8147 -a2lu 8148 -IGRlcHRo 8149 -ZXJjaWFs 8150 -IENvbmdyZXNz 8151 -IE1hcmtldA== 8152 -RGI= 8153 -dW5kZXI= 8154 -IExhc3Q= 8155 -w58= 8156 -Z3JlZw== 8157 -IHBvc3Rz 8158 -X1VSTA== 8159 -b3Rvcw== 8160 -RG9u 8161 -IG1pY3Jv 8162 -IGFycmVzdA== 8163 -0L8= 8164 -IChA 8165 -IEhvdA== 8166 -IEluZGV4 8167 -OyY= 8168 -IyE= 8169 -IE5vcg== 8170 -IENhcA== 8171 -LSg= 8172 -IGludGVyZXN0ZWQ= 8173 -cGVhcg== 8174 -IHJlbnQ= 8175 -IGFsYnVt 8176 -b2xpY3k= 8177 -Lmxhbmc= 8178 -LnRyYW5z 8179 -LmZvcm1hdA== 8180 -IHsNCg0K 8181 -cGhlcmU= 8182 -IGF4aXM= 8183 -IEJ1c2luZXNz 8184 -ZXJzaXN0ZW5jZQ== 8185 -dXJy 8186 -IG1pbmltdW0= 8187 -ZW5kb3I= 8188 -IFNE 8189 -MTEz 8190 -IEludGVybmV0 8191 -5aQ= 8192 -RXhw 8193 -aXZlcnNl 8194 -TU0= 8195 -IG9idmlvdXM= 8196 -IGJhc2lz 8197 -IHNjaWVuY2U= 8198 -IGJ1ZGdldA== 8199 -aXphdGlvbnM= 8200 -UEE= 8201 -IGZsYWdz 8202 -cHJldA== 8203 -TE9DSw== 8204 -IHZhcmlldHk= 8205 -IHRydXRo 8206 -ZHQ= 8207 -IGdvbmU= 8208 -IGJhdHRsZQ== 8209 -PHN0ZA== 8210 -IFNpbA== 8211 -cmY= 8212 -dWRh 8213 -IGVyb3Q= 8214 -IENhbQ== 8215 -IHN0YXRpb24= 8216 -ICc8Lw== 8217 -Y2hlbWU= 8218 -IFN1bg== 8219 -IGZpbmlzaGVk 8220 -IHNob3A= 8221 -IEtvcmU= 8222 -IGVpZ2h0 8223 -X1JFRw== 8224 -TkQ= 8225 -Piw= 8226 -Ij48Pw== 8227 -KG51bQ== 8228 -CWlubGluZQ== 8229 -VHJhbnNhY3Rpb24= 8230 -Lk9u 8231 -IG1haWw= 8232 -cmV5 8233 -cmVzdWx0cw== 8234 -IG5hdg== 8235 -SU1JVA== 8236 -X2lkcw== 8237 -TWFrZQ== 8238 -5Yo= 8239 -TW9kYWw= 8240 -IExPRw== 8241 -IFN1cg== 8242 -IGluc3RhbmNlb2Y= 8243 -IG92ZXJhbGw= 8244 -IEluZm9ybWF0aW9u 8245 -IGNvbnN0cnVjdGlvbg== 8246 -X0ZJTEU= 8247 -YnV0 8248 -IG1lZGlj 8249 -IGR1cmF0aW9u 8250 -aXRuZXNz 8251 -YWdlbnQ= 8252 -QVY= 8253 -IHNldmVu 8254 -b2xm 8255 -IH19Cg== 8256 -Il0sCg== 8257 -MTcw 8258 -MTIy 8259 -IGNhbGxpbmc= 8260 -IGFucw== 8261 -dGhyb3dz 8262 -b3Jpem9udGFs 8263 -IHVzZVN0YXRl 8264 -LmZs 8265 -IFN0YXR1cw== 8266 -IE9ubGluZQ== 8267 -UlI= 8268 -IFJpY2g= 8269 -IEhpbGw= 8270 -IGJyYWlu 8271 -IGZvbGxvd2Vk 8272 -MjQw 8273 -ZW1pYw== 8274 -IHNsaWdodA== 8275 -IGluc3VyYW5jZQ== 8276 -LkFycmF5 8277 -IGFic3RyYWN0 8278 -IFN1bQ== 8279 -cmVkaXJlY3Q= 8280 -b3duZXI= 8281 -KG1zZw== 8282 -IENsaW50b24= 8283 -Tm9u 8284 -CWV4 8285 -IHZvbHVtZQ== 8286 -IEV2ZW50QXJncw== 8287 -LUw= 8288 -IERpbQ== 8289 -IE1hcnQ= 8290 -IGN1cnNvcg== 8291 -IGltcGxlbWVudGF0aW9u 8292 -dXJyZWQ= 8293 -IGxhcmdlcg== 8294 -KTsKCgo= 8295 -Jys= 8296 -LnRyYW5zZm9ybQ== 8297 -IHVwbG9hZA== 8298 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 8299 -RHJhdw== 8300 -bmVs 8301 -CWZsb2F0 8302 -cXJ0 8303 -IE5ldHdvcms= 8304 -IHRpdA== 8305 -QXhpcw== 8306 -LmFuZHJvaWQ= 8307 -IGNvbXBsZXRlZA== 8308 -IG11cg== 8309 -IGNvbHVtbnM= 8310 -eGM= 8311 -IHN1cHBseQ== 8312 -aW1pbmFs 8313 -IHNwcg== 8314 -PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ== 8315 -IHVuaXRz 8316 -KHU= 8317 -bWk= 8318 -cmVwbGFjZQ== 8319 -W2tleQ== 8320 -4Lk= 8321 -YW50aWM= 8322 -IHBheW1lbnQ= 8323 -LEI= 8324 -IEFwcGxl 8325 -Z2lu 8326 -UmVxdWlyZWQ= 8327 -Iys= 8328 -bGFuZHM= 8329 -IHNxdQ== 8330 -IGZhY3Rvcg== 8331 -ZGVj 8332 -IHN0cmVuZ3Ro 8333 -IGJveQ== 8334 -IGJhbGFuY2U= 8335 -IHNvdXJjZXM= 8336 -c2NyZWVu 8337 -LXRvcA== 8338 -IEFtYXpvbg== 8339 -IGhpZGRlbg== 8340 -0LXRgg== 8341 -X2NsaWVudA== 8342 -IGVhdA== 8343 -LmRpc3BsYXk= 8344 -IMK7 8345 -IHRyaWdnZXI= 8346 -YW5hZ2Vy 8347 -IHRybw== 8348 -IGNsYWltcw== 8349 -Zm9yZA== 8350 -IENvbXBhbnk= 8351 -IGdpZnQ= 8352 -LDo= 8353 -X2FwcA== 8354 -aGFuZGxl 8355 -IHByb2R1Y2U= 8356 -L2xpYg== 8357 -NTEy 8358 -IC0q 8359 -CXNldA== 8360 -J107 8361 -YXJj 8362 -YW5kZXI= 8363 -IEVuZ2luZQ== 8364 -IGF0dHJpYnV0ZXM= 8365 -dGFzaw== 8366 -PD0= 8367 -KE4= 8368 -IHdhcm0= 8369 -d2hpY2g= 8370 -IEZvcmU= 8371 -YWdub3N0 8372 -bXlz 8373 -IHRhbA== 8374 -IFNhbA== 8375 -Z2k= 8376 -IFByaW50 8377 -IFRSVUU= 8378 -INC+ 8379 -LlVJ 8380 -IGZsYXNo 8381 -cm9wZXJ0eQ== 8382 -LmxvY2F0aW9u 8383 -IE1pbGw= 8384 -Ymk= 8385 -Y29udHI= 8386 -LnJlcXVlc3Q= 8387 -IFNhbQ== 8388 -IG5lZ2F0aXZl 8389 -a2l0 8390 -IHNldHQ= 8391 -LnByaW50U3RhY2tUcmFjZQ== 8392 -YWJl 8393 -CWk= 8394 -IGJ1cm4= 8395 -IHNvY2lldHk= 8396 -Q2FjaGU= 8397 -IFNlY3VyaXR5 8398 -Lm1vZGVscw== 8399 -IFdBUlJBTlRZ 8400 -X3Vw 8401 -Y2VpdmU= 8402 -IGNsaWVudHM= 8403 -LlRy 8404 -IHByb3ZpZGluZw== 8405 -IHJvdXQ= 8406 -bWF0ZXJpYWw= 8407 -IHx8Cg== 8408 -IFNlcg== 8409 -IE9mZmljZQ== 8410 -RlRXQVJF 8411 -ICck 8412 -IGZvYw== 8413 -IGV4Y2VsbA== 8414 -IGNhdA== 8415 -bm9ybWFs 8416 -IGRldGVybWluZQ== 8417 -CXVpbnQ= 8418 -UGFuZQ== 8419 -IGVtcGxveWVlcw== 8420 -IFRleGFz 8421 -IHRyYWZm 8422 -IFJlcG9ydA== 8423 -YW50YQ== 8424 -IEJveA== 8425 -IGRqYW5nbw== 8426 -IHBhcnRuZXI= 8427 -RUI= 8428 -TElORQ== 8429 -IGZlZWxpbmc= 8430 -IGNpdmls 8431 -KGZsb2F0 8432 -U3Fs 8433 -IHdvdWxkbg== 8434 -LmluaXQ= 8435 -LmxlZnQ= 8436 -LXY= 8437 -X2xldmVs 8438 -J30= 8439 -QUY= 8440 -IGxvYWRpbmc= 8441 -IE9ubHk= 8442 -IGNvb2tpZXM= 8443 -IEds 8444 -Q08= 8445 -IHN0cmF0ZWd5 8446 -KCcuLw== 8447 -IHNoaXA= 8448 -cG9zZXM= 8449 -IHNpZ25hbA== 8450 -IGFscGhh 8451 -LnBvcA== 8452 -UmFkaXVz 8453 -IHJlcGxhY2U= 8454 -X0RJUg== 8455 -Y291bnRlcg== 8456 -YnNlcnZhYmxl 8457 -ZWxh 8458 -V2VpZ2h0 8459 -aGFzaA== 8460 -Ym9zZQ== 8461 -Zng= 8462 -IEVtYWls 8463 -IHJlZmVy 8464 -bG9jYWxob3N0 8465 -X1JP 8466 -aXF1ZXM= 8467 -U3RlcA== 8468 -IGFoZWFk 8469 -KFZpZXc= 8470 -IFNlcnZpY2Vz 8471 -IEpzb24= 8472 -ZXNzb3I= 8473 -IHB1bg== 8474 -IGFwcHJvcHJpYXRl 8475 -YWtlcnM= 8476 -b3Nlbg== 8477 -cG9zaW5n 8478 -IGFnZW50 8479 -ZmM= 8480 -IHRyYW5zZmVy 8481 -IGludmFsaWQ= 8482 -IFJlc2VhcmNo 8483 -VmVydGV4 8484 -IGdheQ== 8485 -IGpvdXJuYWw= 8486 -W3g= 8487 -ICIiLAo= 8488 -IFdlbGw= 8489 -LlRhc2tz 8490 -U3BlYw== 8491 -IG9s 8492 -IHNwZW5k 8493 -IEF1c3RyYWxpYQ== 8494 -TWF0Y2g= 8495 -Lmp1bml0 8496 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 8497 -IE1BWA== 8498 -aXphYmxl 8499 -Y2x1c2l2ZQ== 8500 -X3ZhbGlk 8501 -IHF1YXJ0ZXI= 8502 -eWFu 8503 -MDA1 8504 -IEVkaXQ= 8505 -YXJkZW4= 8506 -PW5ldw== 8507 -IGZyYWc= 8508 -Qml0 8509 -emk= 8510 -YWluZQ== 8511 -dWRk 8512 -Lk9iamVjdA== 8513 -ZGVidWc= 8514 -IGNhc2g= 8515 -X0lN 8516 -IGVlbg== 8517 -IGNvbW1lcmNpYWw= 8518 -IFZpZGVv 8519 -bG9hZGVy 8520 -IGZpeGVk 8521 -IGFwcGxpY2F0aW9ucw== 8522 -IF8s 8523 -IFJ1c3NpYQ== 8524 -aXRlY3Q= 8525 -Xyg= 8526 -IEJsb2Nr 8527 -IHNhbg== 8528 -IFRvbQ== 8529 -IHBlcmhhcHM= 8530 -IHNpZw== 8531 -bGV2YW50 8532 -IGNvcnBvcg== 8533 -YXRhc2V0 8534 -cm9uaWM= 8535 -eGU= 8536 -IGV0aA== 8537 -U29tZQ== 8538 -cG9w 8539 -X09L 8540 -IHRlbmQ= 8541 -LlJlcw== 8542 -X2FuZA== 8543 -IHJldmlld3M= 8544 -IHdpbGQ= 8545 -MTE3 8546 -IGRlZ3JlZQ== 8547 -Lk8= 8548 -Lm9iamVjdHM= 8549 -X2FyZ3M= 8550 -bmls 8551 -IGRpc2FibGVk 8552 -UGFyZW50 8553 -IG5vdGVz 8554 -ICIiCg== 8555 -KHN0YXRl 8556 -aXN0cmljdA== 8557 -IGxvZ2dpbmc= 8558 -LklP 8559 -IE1hbA== 8560 -RE0= 8561 -IHhtbA== 8562 -IFJvYmVydA== 8563 -ZWxlbg== 8564 -bGF5b3V0 8565 -Zm9s 8566 -J10pKQ== 8567 -LGI= 8568 -IEplcg== 8569 -ZmlsZW5hbWU= 8570 -IGZhbg== 8571 -IEN1c3RvbQ== 8572 -PSIi 8573 -IERpZQ== 8574 -QnVuZGxl 8575 -LnV0aWxz 8576 -IHRyaXA= 8577 -TUI= 8578 -IHNvZnQ= 8579 -X01PREU= 8580 -IGFwcGxpY2FibGU= 8581 -IHVwcGVy 8582 -RVJWRVI= 8583 -X2Fs 8584 -X0xPRw== 8585 -SGVyZQ== 8586 -d3A= 8587 -IFNlcnZlcg== 8588 -IENsaWVudA== 8589 -IGNoZW0= 8590 -U2Nyb2xs 8591 -IGhpZ2hlc3Q= 8592 -IFNlbGVjdA== 8593 -ICJA 8594 -IFdoeQ== 8595 -U2Vj 8596 -aGVlbA== 8597 -T3BlcmF0aW9u 8598 -IGNvbm5lY3RlZA== 8599 -aXJtZWQ= 8600 -IGNpdGl6 8601 -IENoZQ== 8602 -IGZvcmNlcw== 8603 -IHd3dw== 8604 -Um9vdA== 8605 -QU5DRQ== 8606 -TWFueQ== 8607 -aWNpcA== 8608 -cmdhbg== 8609 -MjIw 8610 -IFRvcg== 8611 -IFByZXNz 8612 -IE1vcg== 8613 -LWxpbmU= 8614 -dWxlZA== 8615 -Plw= 8616 -IHRodXM= 8617 -IFJlZ2lzdGVy 8618 -aG9s 8619 -IENoaW5lc2U= 8620 -IHBvc3RlZA== 8621 -IG1hZ24= 8622 -YWJpbGl0aWVz 8623 -IGRpc2Vhc2U= 8624 -IHJlbWFpbnM= 8625 -IFByb2Y= 8626 -LWZvcm0= 8627 -IGNpbg== 8628 -b3JnYW4= 8629 -aWNhdGU= 8630 -IHN0cmVzcw== 8631 -XSo= 8632 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 8633 -X2NvbnRleHQ= 8634 -b3JyeQ== 8635 -IGRpZWQ= 8636 -bWF0 8637 -IHN0YXJ0cw== 8638 -Lk1lc3NhZ2U= 8639 -IHJ1bnM= 8640 -IGd1aWRl 8641 -IHdhcnJhbnR5 8642 -ZW50aWFscw== 8643 -ZGljdA== 8644 -IFNpemU= 8645 -dWxlcg== 8646 -IHJlc3BvbnNpYmxl 8647 -X1NFVA== 8648 -IGNvbnRhaW5pbmc= 8649 -IFByaWNl 8650 -fHw= 8651 -MzUw 8652 -RlM= 8653 -IGVtcA== 8654 -X2J1dHRvbg== 8655 -KHVpbnQ= 8656 -IHN1ZmY= 8657 -cHRo 8658 -IGRlZmluaXRlbHk= 8659 -cHV0ZQ== 8660 -IG1hcmtldGluZw== 8661 -IFdI 8662 -IFNpZQ== 8663 -Kz0= 8664 -T0xPUg== 8665 -IGNvbnN1bHQ= 8666 -IHNpZ25lZA== 8667 -IHNlcXVlbmNl 8668 -bGVl 8669 -IHJlcXVpcmVtZW50cw== 8670 -aHk= 8671 -RXhwcmVzcw== 8672 -TVQ= 8673 -c2V5 8674 -IHVsdA== 8675 -5a4= 8676 -ZWxsaWdlbmNl 8677 -IGFuYWx5 8678 -IGRyZXNz 8679 -ZW5naW5l 8680 -IEdyZWF0 8681 -IEFuZHJvaWQ= 8682 -IEFsZXg= 8683 -bW9kZQ== 8684 -RGljdGlvbmFyeQ== 8685 -LkRhdGU= 8686 -5L0= 8687 -VklDRQ== 8688 -IGZhbWlsaWVz 8689 -IFJ1c3NpYW4= 8690 -IFRpbWVz 8691 -LmNhbGw= 8692 -JCg= 8693 -UHJvZmlsZQ== 8694 -IGZvbGRlcg== 8695 -Y2hlcw== 8696 -IGxlZ2lz 8697 -X3Jvdw== 8698 -dW5lcw== 8699 -2YQ= 8700 -IH0pLg== 8701 -QXNzZXJ0 8702 -YWdlbg== 8703 -IEhhbmQ= 8704 -SXRlcg== 8705 -IGJpZ2dlc3Q= 8706 -b3JlYWNo 8707 -IHBvbGlj 8708 -IHBlcm1pc3Npb25z 8709 -IHNob3dlZA== 8710 -IEVsZW1lbnQ= 8711 -IHRvcGlj 8712 -4oCU4oCU 8713 -cm9hZA== 8714 -IEJhbms= 8715 -cmVjb3Jk 8716 -IHBhcnRuZXJz 8717 -IFJlZg== 8718 -ZXNzaW9ucw== 8719 -IGFzc2Vzcw== 8720 -VVNU 8721 -IFBhcnR5 8722 -cHJvZHU= 8723 -TEM= 8724 -IHVs 8725 -LmZvcm0= 8726 -aGlkZQ== 8727 -Y29weQ== 8728 -VVRG 8729 -IFNPRlRXQVJF 8730 -DQoNCg0K 8731 -IExpbg== 8732 -dW5h 8733 -dWdhcg== 8734 -IGFkbWluaXN0cmF0aW9u 8735 -IG9wZW5pbmc= 8736 -IHNjYW4= 8737 -IGNvbnRpbnVlZA== 8738 -Y29tcG9uZW50 8739 -LnNw 8740 -IGhhcHBlbnM= 8741 -dW1teQ== 8742 -IFBS 8743 -LkZpbGU= 8744 -IERvd25sb2Fk 8745 -TG9hZGluZw== 8746 -ZGk= 8747 -IHdhaXRpbmc= 8748 -X0FERA== 8749 -VGFi 8750 -LnF1ZXJ5U2VsZWN0b3I= 8751 -IGVjb25vbXk= 8752 -IEZyZW5jaA== 8753 -dHh0 8754 -IGZhbnQ= 8755 -XzsK 8756 -SG9sZGVy 8757 -U0g= 8758 -MDA0 8759 -IG51bXB5 8760 -IHN0cmVldA== 8761 -IG1hbGU= 8762 -XE1vZGVs 8763 -YW5naW5n 8764 -MzMz 8765 -IEJpbGw= 8766 -IHByZXZpb3VzbHk= 8767 -Qkk= 8768 -IFNlY3JldA== 8769 -IG1pc3Q= 8770 -IEZpZWxk 8771 -dXBz 8772 -IFByb2Nlc3M= 8773 -IGtlcHQ= 8774 -IE9U 8775 -IHRyYWRpdGlvbmFs 8776 -Lmk= 8777 -YW1pbg== 8778 -IGhlbHBz 8779 -QW55 8780 -b3JpZ2lu 8781 -aWx0ZXJz 8782 -anU= 8783 -ZGVzYw== 8784 -IEFjY291bnQ= 8785 -ICkNCg== 8786 -a3RvcA== 8787 -b2xseQ== 8788 -IGZz 8789 -IOo= 8790 -IHV0 8791 -IGNlbnRyYWw= 8792 -KHRlc3Q= 8793 -LkFu 8794 -IHNhdGlzZg== 8795 -R1I= 8796 -IEZ1bGw= 8797 -IGhlYXQ= 8798 -aWJlcg== 8799 -IG9udG8= 8800 -bW9z 8801 -U2NoZW1h 8802 -IGZhY3Rvcnk= 8803 -Ii4k 8804 -YXdz 8805 -U3RhdGVtZW50 8806 -KHRhcmdldA== 8807 -CW5ldw== 8808 -LmJl 8809 -IGd1ZXN0 8810 -IG1hbA== 8811 -QVJZ 8812 -IHJlYWNoZWQ= 8813 -IG1vdXNl 8814 -IGNoYWxsZW5nZQ== 8815 -CWRvdWJsZQ== 8816 -IFRlbQ== 8817 -IHRlcnJvcg== 8818 -IGV4dHJhY3Q= 8819 -X1RP 8820 -IHNlcGFyYXRl 8821 -IG1pcg== 8822 -aGVscA== 8823 -IGNhcGFjaXR5 8824 -IFByb3BlcnR5 8825 -a2Fu 8826 -X2NyZWF0ZQ== 8827 -IExpZ2h0 8828 -LnBhcmVudA== 8829 -IHVuZGVyc3RhbmRpbmc= 8830 -IGVhc2llcg== 8831 -IHw9 8832 -IGVuaA== 8833 -IGZhdA== 8834 -IHByb3Rlc3Q= 8835 -YW1t 8836 -X0FU 8837 -LW9m 8838 -aWxz 8839 -IE9o 8840 -IHBzeWNo 8841 -ICQu 8842 -aW5kcw== 8843 -IHJlbGF0aXZl 8844 -c2hvcA== 8845 -c2hvcnQ= 8846 -IFNhbmQ= 8847 -MjEw 8848 -dWVzdGlvbg== 8849 -IGZlYXI= 8850 -LwoK 8851 -LmNvbnRleHQ= 8852 -IHNjaG9vbHM= 8853 -IHNlcnZl 8854 -em9uZQ== 8855 -X2Ri 8856 -IG1ham9yaXR5 8857 -ZXhhbXBsZQ== 8858 -IGxhbmc= 8859 -CSAg 8860 -UmVnaXN0ZXI= 8861 -ZW5kbw== 8862 -IHByb2Nlc3Npbmc= 8863 -X3RlbXBsYXRl 8864 -LXVzZXI= 8865 -IGVn 8866 -Q09N 8867 -IEJsdWU= 8868 -aXJv 8869 -IHJlbW90ZQ== 8870 -IElU 8871 -IyEv 8872 -IHJlZGlzdHJpYg== 8873 -MTI0 8874 -cmF6 8875 -IFNpbmNl 8876 -IFR1cg== 8877 -MTM1 8878 -QmFja2dyb3VuZA== 8879 -PT09 8880 -IHJlZmxlY3Q= 8881 -IHByb3M= 8882 -Y21k 8883 -IHdob20= 8884 -Q29tcGF0 8885 -IEFyZQ== 8886 -SWRlbnRpZmllcg== 8887 -IFRob20= 8888 -X3BvcnQ= 8889 -Z3U= 8890 -IG1vbml0b3I= 8891 -cm0= 8892 -IHBhdGllbnQ= 8893 -dmVydGVy 8894 -IGdhaW4= 8895 -LXVp 8896 -SW5zdA== 8897 -IGRpZXM= 8898 -MTE4 8899 -QXJlYQ== 8900 -X2ZpbHRlcg== 8901 -IGdyYXQ= 8902 -IHJlYWxpdHk= 8903 -b3JkaW5hdGU= 8904 -b2x2ZWQ= 8905 -Q29udGFjdA== 8906 -IGNvbXBsaWFuY2U= 8907 -X29y 8908 -IFZhcg== 8909 -ZGw= 8910 -IGFwcGVuZA== 8911 -R0VS 8912 -KG1heA== 8913 -LnJlbmRlcg== 8914 -IGR5bmFtaWM= 8915 -b3JkaW5hdGVz 8916 -X29wdGlvbnM= 8917 -X2NvbHVtbg== 8918 -IGJhdHRlcg== 8919 -c3BhY2U= 8920 -TGE= 8921 -IFNvdXJjZQ== 8922 -L2Jpbg== 8923 -IGRvcw== 8924 -IEJvYXJk 8925 -IFRocmVhZA== 8926 -IEFM 8927 -KGNvbmZpZw== 8928 -MTQ0 8929 -IE1lcg== 8930 -IG1pbGVz 8931 -X2hlYWRlcg== 8932 -RVRIT0Q= 8933 -aXp6 8934 -IGJlbmVmaXQ= 8935 -IGludGVncg== 8936 -KGN1cnJlbnQ= 8937 -dWxv 8938 -LmRlZmF1bHQ= 8939 -IERpdg== 8940 -IHRvbg== 8941 -b3Ro 8942 -ZXJ2YXRpb24= 8943 -ZWRvbQ== 8944 -IGJhYnk= 8945 -Y2VpdmVk 8946 -LnRvcA== 8947 -cmlvcml0eQ== 8948 -IExvY2Fs 8949 -cmlhZ2U= 8950 -IGF0dGFja3M= 8951 -IGhvc3BpdGFs 8952 -MTY4 8953 -IGZlbWFsZQ== 8954 -IExvZ2lu 8955 -IEZsb3I= 8956 -IGNoYWlu 8957 -YXNoaW9u 8958 -VGV4dHVyZQ== 8959 -U2F2ZQ== 8960 -IGZhcm0= 8961 -LmNvbnRhaW5z 8962 -LlRlc3Q= 8963 -IGtub3dz 8964 -IGdlbmVyYWxseQ== 8965 -aXBlbGluZQ== 8966 -IG1lYW50 8967 -ZW5jaWE= 8968 -IG5pY2h0 8969 -IGNvbnRlbnRz 8970 -UE0= 8971 -Y2hlZHVsZQ== 8972 -KGxpbmU= 8973 -Q0c= 8974 -am9i 8975 -IFJlYWw= 8976 -dWVy 8977 -ZmlybQ== 8978 -INg= 8979 -ZXRybw== 8980 -ImAK 8981 -IHNwZWVjaA== 8982 -IHRocg== 8983 -Zm9yZWFjaA== 8984 -IHdhcm4= 8985 -CWw= 8986 -IGhlYXZ5 8987 -PGxp 8988 -TmU= 8989 -IGludmVzdGlnYXRpb24= 8990 -TWF0aA== 8991 -LXRpdGxl 8992 -IGNodXJjaA== 8993 -IGRlc3BpdGU= 8994 -Y2hhaW4= 8995 -IHdoYXRldmVy 8996 -YXJpYW4= 8997 -Zm4= 8998 -IG1ldGE= 8999 -fSkKCg== 9000 -VUZG 9001 -IHJlZ2FyZGluZw== 9002 -X1NVQ0NFU1M= 9003 -bWVz 9004 -IEludGVudA== 9005 -IHJlc29sdmU= 9006 -cG9zcw== 9007 -aXJh 9008 -Zm9yY2U= 9009 -b2ljZQ== 9010 -w6I= 9011 -IHBt 9012 -IHVwZGF0ZXM= 9013 -QXJy 9014 -INE= 9015 -dGVzdGluZw== 9016 -IHRvd2FyZA== 9017 -bnRheA== 9018 -64s= 9019 -IGxpc3Rlbg== 9020 -IGdvYWxz 9021 -SW5zdGFuY2VTdGF0ZQ== 9022 -RHI= 9023 -IHJhcmU= 9024 -IHRyYWls 9025 -S2V5cw== 9026 -Q2Fs 9027 -Q2Fy 9028 -IFBlb3BsZQ== 9029 -CWxvY2Fs 9030 -Y2xhc3Nlcw== 9031 -UmVmZXJlbmNl 9032 -LmZvckVhY2g= 9033 -ZW1i 9034 -YWN0aXY= 9035 -IHByaW0= 9036 -cmVkaWN0 9037 -IHJhZA== 9038 -5pWw 9039 -LkJhY2s= 9040 -IHNwcmVhZA== 9041 -IGNsb2Nr 9042 -IHZpcg== 9043 -ZWRpdG9y 9044 -IGVmZm9ydHM= 9045 -IGJyYW5jaA== 9046 -IGluZHVzdA== 9047 -IG1vdG9y 9048 -IGFtYg== 9049 -IGRhdGV0aW1l 9050 -IHJlbmNvbnQ= 9051 -IENocmlzdGlhbg== 9052 -IEFtZXJpY2Fucw== 9053 -ZnVsbA== 9054 -IGZtdA== 9055 -Lm1haW4= 9056 -IGNhdXNlZA== 9057 -X3VwZGF0ZQ== 9058 -IENvbnRlbnQ= 9059 -QVRDSA== 9060 -IGJhdGg= 9061 -IEVhY2g= 9062 -IHJhZGlv 9063 -YWNobWVudA== 9064 -dXp6 9065 -U3VibWl0 9066 -IHJlc3RyaWN0 9067 -YWJpbg== 9068 -IExvYWQ= 9069 -IGV4dGVuc2lvbg== 9070 -IGVzc2F5 9071 -IGhhdA== 9072 -YXZpb3Vy 9073 -dG9CZQ== 9074 -Ijpb 9075 -IG9mZmVyZWQ= 9076 -IHZpbGw= 9077 -KGRvdWJsZQ== 9078 -MTE5 9079 -5pel 9080 -YmM= 9081 -X2ZyZWU= 9082 -IE1pc3M= 9083 -IEJlcg== 9084 -IOg= 9085 -IExpa2U= 9086 -IGhlbHBlZA== 9087 -LmdldE5hbWU= 9088 -X0FM 9089 -IHNwaXJpdA== 9090 -IEFwYWNoZQ== 9091 -d3M= 9092 -IHRoZXJlZm9yZQ== 9093 -KHBhcmFtcw== 9094 -X2ltZw== 9095 -IHBlYWNl 9096 -IGluY29y 9097 -IEVYUEVDVA== 9098 -IG1pbm9y 9099 -aXBlcw== 9100 -CWRhdGE= 9101 -c2VsZWN0b3I= 9102 -Y2l0eQ== 9103 -dHJpZQ== 9104 -LmJhc2U= 9105 -X2ZyYW1l 9106 -IG9wZW5lZA== 9107 -L2pzb24= 9108 -TFk= 9109 -bnU= 9110 -LkRl 9111 -dGY= 9112 -bWFyZ2lu 9113 -LlBhcnNl 9114 -IHBp 9115 -IGVx 9116 -YmQ= 9117 -RmllbGRz 9118 -IFRyZWU= 9119 -IGJhbg== 9120 -aXN0YW4= 9121 -CiAgICAgICAgCg== 9122 -CWds 9123 -IHByb2R1Y2Vk 9124 -c3lzdGVt 9125 -TWFyaw== 9126 -X2hhc2g= 9127 -IGJn 9128 -IGNvbnN0aXQ= 9129 -IExlYWd1ZQ== 9130 -IG1pc3Npb24= 9131 -X2Zvcm1hdA== 9132 -KFsK 9133 -Y2x1c2lvbg== 9134 -ISI= 9135 -0Lc= 9136 -YnJlYWs= 9137 -CXN3aXRjaA== 9138 -IHRoZXI= 9139 -VHJhbnNmb3Jt 9140 -IGZvb3RiYWxs 9141 -LWxpbms= 9142 -cm91dGU= 9143 -LmF1dGg= 9144 -IGJhZw== 9145 -b3ZlcnM= 9146 -IGVuYWJsZWQ= 9147 -IHJhYw== 9148 -KEk= 9149 -Q1I= 9150 -YW5jaW5n 9151 -IG1hbmFnZWQ= 9152 -X3E= 9153 -TkdUSA== 9154 -IG1hYw== 9155 -IEF1dG8= 9156 -YW1lbnRl 9157 -ICcnLA== 9158 -LkFwcGVuZA== 9159 -IHBpbg== 9160 -Lml0ZW0= 9161 -YWNraW5n 9162 -IG9jY2Fz 9163 -cGVyc29u 9164 -IHRp 9165 -LlJlZw== 9166 -IGhhdmVu 9167 -IGdsYXNz 9168 -ICI8Lw== 9169 -IFNpbXBsZQ== 9170 -UHJpbnQ= 9171 -IHN1cnJvdW5k 9172 -Tk8= 9173 -44CCCg== 9174 -ICAgICAgICANCg== 9175 -IE1hbnk= 9176 -ICJf 9177 -IHdlZWtlbmQ= 9178 -IHNvbWV3 9179 -LnBhcmFtcw== 9180 -c21hbGw= 9181 -QVRFRA== 9182 -IHBsdWdpbg== 9183 -ZmllbGRz 9184 -IEluaXRpYWxpemU= 9185 -b29u 9186 -YXRpbGU= 9187 -eWU= 9188 -IHZvdXM= 9189 -TEFH 9190 -IG9sZGVy 9191 -IGdhbQ== 9192 -IGV4dHJlbWVseQ== 9193 -IGhldA== 9194 -ZW51bQ== 9195 -IFNFVA== 9196 -eGZm 9197 -IHRpbWVy 9198 -L2luZGV4 9199 -IGNyaXRpY2Fs 9200 -Um93cw== 9201 -X2FyZ3VtZW50 9202 -IGV4ZWN1dGU= 9203 -IHNob3dpbmc= 9204 -LnhtbA== 9205 -LWxpc3Q= 9206 -Um9sZQ== 9207 -dHlwZW5hbWU= 9208 -X21ldGhvZA== 9209 -dGhhdA== 9210 -Y2hlcg== 9211 -IOKG 9212 -WFQ= 9213 -IHRob3VzYW5kcw== 9214 -CW4= 9215 -IHJlc3A= 9216 -X3ByaWNl 9217 -b2x1dA== 9218 -QWc= 9219 -IFR3bw== 9220 -IGJlY29tZXM= 9221 -IGh1cw== 9222 -LlVzZQ== 9223 -dGhlbWU= 9224 -dXJi 9225 -IC8qCg== 9226 -ZXJpYWxpemU= 9227 -QVJO 9228 -IGxvc2U= 9229 -TG93ZXI= 9230 -IHZlbA== 9231 -IGRlZmVuc2U= 9232 -Y29uZGl0aW9u 9233 -IGJlcw== 9234 -IGRyeQ== 9235 -IHNjcm9sbA== 9236 -LlNob3c= 9237 -SUVM 9238 -0L7RgA== 9239 -IFJlc3Q= 9240 -V2hlcmU= 9241 -b29kcw== 9242 -IEplcw== 9243 -IHdpcmU= 9244 -X0lORk8= 9245 -IHN0cmluZ3M= 9246 -Z21lbnQ= 9247 -IG1hdGNoZXM= 9248 -IGVsZWN0cmlj 9249 -IGV4Y2VsbGVudA== 9250 -IENvdW5jaWw= 9251 -aWRhZGU= 9252 -IHd4 9253 -cHVzaA== 9254 -X2VudHJ5 9255 -IHRhc2tz 9256 -IHJpY2g= 9257 -c2E= 9258 -IFNtaXRo 9259 -VU5DVElPTg== 9260 -UG9pbnRlcg== 9261 -cGVjdGl2ZQ== 9262 -MTMx 9263 -IHdpZGdldA== 9264 -aXN0YQ== 9265 -IGFnZW5jeQ== 9266 -IHNpY2g= 9267 -b2xvZ2llcw== 9268 -IHRyaWFs 9269 -YWx5c2lz 9270 -LmNoZWNr 9271 -QVJL 9272 -IG9uQ2hhbmdl 9273 -YWJvdXQ= 9274 -Jywk 9275 -KHZhbA== 9276 -IHBsYWNlZA== 9277 -X05P 9278 -IGRhbg== 9279 -LmVxdWFs 9280 -CSAgICAg 9281 -IHdlYXRoZXI= 9282 -LmdhbWU= 9283 -IGRlc3RpbmF0aW9u 9284 -X1VTRVI= 9285 -aWVjZQ== 9286 -IHByb3ZpZGVy 9287 -Lmxhc3Q= 9288 -cGxleA== 9289 -Tm90ZQ== 9290 -L2pz 9291 -IHDDpQ== 9292 -IHBsYW5uaW5n 9293 -YXR0cmlidXRl 9294 -UFJP 9295 -YXRjaGVz 9296 -IDwt 9297 -IHNlZWluZw== 9298 -IGNhbmNlbA== 9299 -X2luZA== 9300 -LmtleXM= 9301 -IHZpc3VhbA== 9302 -IEN1cnJlbnQ= 9303 -IENvbGxlZ2U= 9304 -IFJvY2s= 9305 -IGFncmVlbWVudA== 9306 -IFN0b3Jl 9307 -b3Zpbmc= 9308 -IGNvcm5lcg== 9309 -YW1waW9ucw== 9310 -SVNF 9311 -Rmlu 9312 -IHByb3RlY3Rpb24= 9313 -IGZp 9314 -UGxheQ== 9315 -cGx1Z2lu 9316 -KX0= 9317 -LmZyYW1l 9318 -LXo= 9319 -IHRyYW5zaXRpb24= 9320 -aWdpbg== 9321 -IGNhbmRpZGF0ZQ== 9322 -IFVuaW9u 9323 -X3ZhbHVlcw== 9324 -KG1hcA== 9325 -Y2xl 9326 -IHRyZW5k 9327 -d2lkZQ== 9328 -YXJlbg== 9329 -TG9j 9330 -VVRI 9331 -IEJheQ== 9332 -IHNtYWxsZXI= 9333 -aXVz 9334 -MTQx 9335 -d2VsbA== 9336 -IGNyaW1pbmFs 9337 -IGNvbmZsaWM= 9338 -YmVydA== 9339 -X0lOVA== 9340 -IGludmVzdG1lbnQ= 9341 -Y3VzdG9t 9342 -IFNlc3Npb24= 9343 -X3dyaXRl 9344 -YW5pYQ== 9345 -IE1hc3M= 9346 -X0VR 9347 -X05PVA== 9348 -IHZpb2xlbmNl 9349 -QXJndW1lbnQ= 9350 -X2VtYWls 9351 -IGJlbG9uZw== 9352 -X2Z1bmN0aW9u 9353 -IGVuZW15 9354 -ZW1h 9355 -IEFkZHJlc3M= 9356 -LmVtcHR5 9357 -IGlubmVy 9358 -IENvbnRhY3Q= 9359 -TG9hZGVy 9360 -PGlucHV0 9361 -IENB 9362 -bG90 9363 -IHBpY3R1cmVz 9364 -IFN1cHBvcnQ= 9365 -X25hbWVz 9366 -MTg4 9367 -TGF5ZXI= 9368 -IENsaWNr 9369 -U3Vt 9370 -w6Y= 9371 -IExvb2s= 9372 -dW91cw== 9373 -TGli 9374 -RmxhZ3M= 9375 -dGVhbQ== 9376 -RVA= 9377 -MTg5 9378 -aGF0 9379 -b3ZlcnJpZGU= 9380 -YXBzZWQ= 9381 -IGxhYmVscw== 9382 -cXVpcw== 9383 -IFN0cmVhbQ== 9384 -X2RldmljZQ== 9385 -IENvbW1pdA== 9386 -KHJvb3Q= 9387 -In0= 9388 -LmlzRW1wdHk= 9389 -MTI2 9390 -CU0= 9391 -IGFuZ2xl 9392 -IEJlY2F1c2U= 9393 -JSUlJSUlJSU= 9394 -IGFpbQ== 9395 -IHN0aWNr 9396 -c3RtdA== 9397 -YWdyYXBo 9398 -YW5zd2Vy 9399 -IGNsaW4= 9400 -IElzbA== 9401 -LmV4dA== 9402 -IElOVA== 9403 -IHN0eWxlcw== 9404 -IGJvcm4= 9405 -IHNjcg== 9406 -IGV4cGFuZA== 9407 -IHJhaXNlZA== 9408 -VGV4dEJveA== 9409 -SUxM 9410 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 9411 -SFRUUA== 9412 -MTMy 9413 -Pik= 9414 -X2NoYXI= 9415 -cmVzb3VyY2U= 9416 -IGVwaXNvZGU= 9417 -ICdf 9418 -IEVz 9419 -IEVhcnRo 9420 -wqDCoA== 9421 -VVBEQVRF 9422 -MTMz 9423 -IFNvdQ== 9424 -dWlz 9425 -dHlwZXM= 9426 -IG1hcw== 9427 -IGZhdg== 9428 -IGNvbnN0cnVjdA== 9429 -X3JhdGU= 9430 -ZXJhcw== 9431 -IHwK 9432 -cm9wZXJ0aWVz 9433 -IGV4dGVybmFs 9434 -IGFwcGxpZWQ= 9435 -IHByZWZpeA== 9436 -b3RlZA== 9437 -bGVycw== 9438 -IGNvbGQ= 9439 -IFNQ 9440 -IENodXJjaA== 9441 -IE91dHB1dA== 9442 -bG9zZWQ= 9443 -55o= 9444 -aWZpY2F0ZQ== 9445 -b3BlcmF0aW9u 9446 -aGVyaXQ= 9447 -eEZG 9448 -LmVudg== 9449 -X2Vycg== 9450 -b3No 9451 -RGlyZWN0aW9u 9452 -Q2FuY2Vs 9453 -IEZyYW5r 9454 -IGZpbmRpbmc= 9455 -LikKCg== 9456 -IHJvdXRlcg== 9457 -44O7 9458 -c2Vz 9459 -IGNyb3c= 9460 -PT0n 9461 -IHNhbmQ= 9462 -IHJpZA== 9463 -aXR1cmU= 9464 -IGVudHJl 9465 -IG9ic2Vydg== 9466 -IHZhYw== 9467 -8J8= 9468 -LVQ= 9469 -QXJ0 9470 -bmlnaHQ= 9471 -LnNlYXJjaA== 9472 -IGV4Y2hhbmdl 9473 -IGRpc3RyaWN0 9474 -Lm9z 9475 -IGRlcGFydG1lbnQ= 9476 -IGRvY3VtZW50cw== 9477 -IGNlbnR1cnk= 9478 -IE5leHQ= 9479 -SG9zdA== 9480 -IEtJTkQ= 9481 -IHN1c3A= 9482 -LVA= 9483 -cmVuZA== 9484 -LmVt 9485 -dWl0ZQ== 9486 -aXN0ZXJz 9487 -KGpzb24= 9488 -IEFubg== 9489 -d3Q= 9490 -YXRp 9491 -IEhUTUw= 9492 -d2hlbg== 9493 -RGlyZWN0b3J5 9494 -IHNodXQ= 9495 -PGE= 9496 -ZWR5 9497 -IGhlYWx0aHk= 9498 -IHRlbXBlcmF0dXJl 9499 -IEdlbg== 9500 -IG1ldGFs 9501 -IHN1Ym1pdA== 9502 -IERP 9503 -IGF0dHJhY3Q= 9504 -IHt9Owo= 9505 -IFdvcmQ= 9506 -IGxs 9507 -IHNlZW1lZA== 9508 -a28= 9509 -SUVE 9510 -IGxhYm9y 9511 -LkNvbnRleHQ= 9512 -IGFzc2V0 9513 -eW91 9514 -IGNhcnM= 9515 -IENvbHVtbg== 9516 -IHLDqQ== 9517 -IHNxdWFyZQ== 9518 -IE5TU3RyaW5n 9519 -4oCdLA== 9520 -YXBlcw== 9521 -Li4uCg== 9522 -IHRoYW5rcw== 9523 -KHByb3Bz 9524 -IHRpY2s= 9525 -IGV4cGVyaW1lbnQ= 9526 -IHByaXNvbg== 9527 -dHJlZQ== 9528 -LXRleHQ= 9529 -IElPRXhjZXB0aW9u 9530 -LXdpZHRo 9531 -X1NUQVRVUw== 9532 -ZmFzdA== 9533 -LWJvZHk= 9534 -LWhlYWRlcg== 9535 -IGd1YXI= 9536 -Y3JldGU= 9537 -IFRpbQ== 9538 -IGNsZWFybHk= 9539 -IFJlcHVibGljYW4= 9540 -IGp1c3RpZnk= 9541 -0LjRgg== 9542 -CSAgICA= 9543 -Y2FjaGU= 9544 -Oy8v 9545 -IHByZXNlbmNl 9546 -IGZhY3RvcnM= 9547 -IGVtcGxveWVl 9548 -XSkp 9549 -TWVtYmVy 9550 -IHNlbGVjdG9y 9551 -Ym9y 9552 -IE1leA== 9553 -55qE 9554 -dXRleA== 9555 -X3RhZw== 9556 -YWlsdXJl 9557 -IE5ldA== 9558 -IHJlbGk= 9559 -RUc= 9560 -IGZwcmludGY= 9561 -IHRlZW4= 9562 -bG9zcw== 9563 -IGxlYXZpbmc= 9564 -MTM0 9565 -RGVsZWdhdGU= 9566 -IGJlYXQ= 9567 -IG1pbnV0ZQ== 9568 -c3Vic2NyaWJl 9569 -IHJlZGlzdHJpYnV0ZQ== 9570 -Q29uc3RhbnRz 9571 -IGNhbmNlcg== 9572 -L3s= 9573 -Qkw= 9574 -IHNwYW4= 9575 -IENoaWxk 9576 -Q2VudGVy 9577 -IGVhcnRo 9578 -WVM= 9579 -IExldmVs 9580 -IHNlYQ== 9581 -LnN1cHBvcnQ= 9582 -LmlubmVy 9583 -Lkl0ZW0= 9584 -aWxsaW5n 9585 -ICAgIAogICAgCg== 9586 -IExhYmVs 9587 -MzIw 9588 -IEVzdA== 9589 -KGFyZw== 9590 -MTQ1 9591 -Ym9Cb3g= 9592 -CWZvcmVhY2g= 9593 -Y29z 9594 -RmFpbGVk 9595 -c3dlcnM= 9596 -RWRpdG9y 9597 -cm9udA== 9598 -IE1Q 9599 -ZXhwcg== 9600 -IExpZmU= 9601 -ID8/ 9602 -w7Zy 9603 -IGF0dGVuZA== 9604 -IFF1ZQ== 9605 -IHNwZWNpZXM= 9606 -LUQ= 9607 -IGF1cw== 9608 -U3RydWN0 9609 -IGFkdmFudGFnZQ== 9610 -b3N0b24= 9611 -LWJsb2Nr 9612 -aW5pdGlhbA== 9613 -Q1JF 9614 -IHRydWx5 9615 -IGNvbXBhcmU= 9616 -b3JuZXk= 9617 -IHNwZWN0 9618 -RnVsbA== 9619 -YmVz 9620 -IHZpc2libGU= 9621 -IG1lc3M= 9622 -c3RhbmNlcw== 9623 -IGNsb3Vk 9624 -X3ZlcnNpb24= 9625 -IGZ1cm4= 9626 -aWNhZ28= 9627 -TE9X 9628 -IHRyYWZmaWM= 9629 -IGZvbA== 9630 -cnlwdG8= 9631 -IGRlY2xhcg== 9632 -IHNsb3Q= 9633 -IEV4dA== 9634 -IEVuZ2xhbmQ= 9635 -IFVuZGVy 9636 -IHRh 9637 -bGV0dGVy 9638 -MjAz 9639 -IG9mZmljZXI= 9640 -IERvbmFsZA== 9641 -WWVz 9642 -X2pzb24= 9643 -SVRhYmxlVmlldw== 9644 -IFVTRQ== 9645 -bXBsb3llZQ== 9646 -IG9waW5pb24= 9647 -IEF1dA== 9648 -Ym9yZGVy 9649 -IGFkdmljZQ== 9650 -IGF1dG9tYXRpY2FsbHk= 9651 -aXNjbw== 9652 -IG1t 9653 -LnZpcw== 9654 -YW1s 9655 -IGluaXRpYWxpemU= 9656 -ICh7 9657 -IDsKCg== 9658 -IGdlbmVyYXRpb24= 9659 -IGJpdHM= 9660 -Y2xpcHNl 9661 -IHVuZg== 9662 -dXRvcnM= 9663 -cGx0 9664 -IGRlbHRh 9665 -ZXN0cm95 9666 -aXNpcw== 9667 -PGJy 9668 -IGxpbWl0YXRpb25z 9669 -IGVuZGVk 9670 -IE1hZA== 9671 -aWxt 9672 -VGhlc2U= 9673 -MTg3 9674 -IE1pbmlzdGVy 9675 -IGNoYXJ0 9676 -RnJhZ21lbnQ= 9677 -IGluZGVwZW5kZW50 9678 -WWVhcg== 9679 -IGluc3Ry 9680 -IHRhZ3M= 9681 -QVZF 9682 -IEFyY2g= 9683 -c3RvcA== 9684 -UHJvZ3Jlc3M= 9685 -IG1p 9686 -IGxlYXJuZWQ= 9687 -R2U= 9688 -IGhvdGVs 9689 -MTUx 9690 -U00= 9691 -VFlQRQ== 9692 -IGN5 9693 -RVJTSU9O 9694 -dW5hdGVseQ== 9695 -bGltaXQ= 9696 -c2Vs 9697 -IG1vdmllcw== 9698 -IHN0ZWVs 9699 -b3o= 9700 -Z2I= 9701 -IENhbXA= 9702 -c2l0ZQ== 9703 -IExvZ2dlcg== 9704 -UExF 9705 -0L7QtA== 9706 -LnJpZ2h0 9707 -IENvcmU= 9708 -IG1peGVk 9709 -c3RlcA== 9710 -IHB1dHM= 9711 -c3VwZXI= 9712 -Um91dGVy 9713 -MTg2 9714 -Lkh0dHA= 9715 -MjIy 9716 -bHlwaA== 9717 -IENvbG9ycw== 9718 -IGFuZHJvaWR4 9719 -LnN0cg== 9720 -IGlubm92 9721 -IGRlY2s= 9722 -Jz4K 9723 -YXBlcnM= 9724 -XSg= 9725 -Y29udGludWU= 9726 -c3BlYw== 9727 -IFJvYWQ= 9728 -QVNI 9729 -aWxpYXI= 9730 -IGNvbnRpbnVlcw== 9731 -IGFwcG9pbnQ= 9732 -ICMK 9733 -IFZpcg== 9734 -ID8+Ig== 9735 -IGJpbg== 9736 -fSIs 9737 -Z29pbmc= 9738 -ZWFjaA== 9739 -QkQ= 9740 -MTg1 9741 -IEFjY2Vzcw== 9742 -RG9j 9743 -IE1hbmFnZW1lbnQ= 9744 -QkVS 9745 -YXNrZXQ= 9746 -LmdldEluc3RhbmNl 9747 -MTI5 9748 -IGVzdGFibGlzaGVk 9749 -c29ja2V0 9750 -SU5T 9751 -CXZpcnR1YWw= 9752 -CXJlc3VsdA== 9753 -UkVBRA== 9754 -X2hlaWdodA== 9755 -MTUy 9756 -IEZvbnQ= 9757 -ICgpOwo= 9758 -X2h0bWw= 9759 -IG5laWdoYm9y 9760 -bG9y 9761 -IGdhdGhlcg== 9762 -IH0pCgo= 9763 -IGlkZW50aXR5 9764 -IGZhYg== 9765 -cGFkZGluZw== 9766 -IFJvdXRl 9767 -RW51bWVyYWJsZQ== 9768 -w7Q= 9769 -IGZvcmNlZA== 9770 -L2pxdWVyeQ== 9771 -LgoKCgoKCg== 9772 -cmVzZW50cw== 9773 -X2xlZnQ= 9774 -LlBhcmFt 9775 -CXRocm93 9776 -IEhhbQ== 9777 -IGV2ZW50dWFsbHk= 9778 -YWNlcg== 9779 -cHVi 9780 -IHRyYQ== 9781 -dW5pcXVl 9782 -ZGVs 9783 -IEZsb3JpZGE= 9784 -IENsZWFu 9785 -eGE= 9786 -IMK3 9787 -IHZhbGlkYXRl 9788 -VmlzdWFs 9789 -RXhwcmVzc2lvbg== 9790 -X2Z1bmM= 9791 -bWVtYmVy 9792 -CWg= 9793 -dHJs 9794 -MTM2 9795 -CUc= 9796 -bmFwc2hvdA== 9797 -IFByb3BUeXBlcw== 9798 -dmlu 9799 -MTUz 9800 -XSkKCg== 9801 -b3ds 9802 -aWZpZXM= 9803 -ICQoJy4= 9804 -IENvbnRleHQ= 9805 -IFRvYXN0 9806 -LktleQ== 9807 -IG9mZmljZXJz 9808 -L24= 9809 -c24= 9810 -dW5kZWZpbmVk 9811 -Lml0ZW1z 9812 -dXRvdw== 9813 -YW1hZ2U= 9814 -IGFjY291bnRz 9815 -b29raWU= 9816 -U2VjdGlvbg== 9817 -aWNpYW5z 9818 -IGFkdmlz 9819 -KGlz 9820 -Wzos 9821 -IEZyYW5jZQ== 9822 -RnVuYw== 9823 -aWNpb3Vz 9824 -IHRvaw== 9825 -Q2hhbm5lbA== 9826 -IEFE 9827 -X05VTQ== 9828 -IHRpbWVvdXQ= 9829 -bGVtbWE= 9830 -cmVtZQ== 9831 -dWo= 9832 -LkFs 9833 -dWNsZWFy 9834 -KG9z 9835 -KCI8 9836 -Wwo= 9837 -ZmV0Y2g= 9838 -IGJhbA== 9839 -IGd1aWQ= 9840 -LWFsaWdu 9841 -IFdyaXRl 9842 -IE9uY2U= 9843 -dXRvd2lyZWQ= 9844 -T0RVTEU= 9845 -IHBpdGNo 9846 -Q0Y= 9847 -Ynl0ZXM= 9848 -IENvbW1pc3Npb24= 9849 -IGluY3JlZA== 9850 -UEVS 9851 -X3Jlc3BvbnNl 9852 -IExvcw== 9853 -cGFyc2Vy 9854 -IGFzc3VtZQ== 9855 -LlJlcXVlc3Q= 9856 -IFRva2Vu 9857 -X3Bvc2l0aW9u 9858 -IG5vbQ== 9859 -LXRlcm0= 9860 -IHJlbWFpbmluZw== 9861 -aW9zdHJlYW0= 9862 -IHBpZWNlcw== 9863 -YXB5 9864 -IExlc3M= 9865 -cmFuZ2U= 9866 -dW1ibg== 9867 -cHJpc2U= 9868 -X29wdGlvbg== 9869 -MjMw 9870 -SW1wbA== 9871 -a3dhcmdz 9872 -IGJ1c2luZXNzZXM= 9873 -QWxlcnQ= 9874 -IHBhcnRpZXM= 9875 -IENvbnRhaW5lcg== 9876 -IFByaXZhdGU= 9877 -IFBsYW4= 9878 -IHJlZ2lzdGVyZWQ= 9879 -IGpvdXI= 9880 -YWNrZXI= 9881 -0LXQvdC4 9882 -Lz4= 9883 -Y2hhdA== 9884 -c2VjdA== 9885 -IGNyZWF0aW9u 9886 -b2x1dGVseQ== 9887 -IGluc3RhbnQ= 9888 -IGRlbGl2ZXJ5 9889 -aWNrZW4= 9890 -eWVz 9891 -MTYz 9892 -IEZyYW5j 9893 -Ymxpbmc= 9894 -ZW5kYQ== 9895 -Wyg= 9896 -X3Jhbmdl 9897 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 9898 -IHNjaGVkdWxl 9899 -Q29ubg== 9900 -IHRoYW5r 9901 -eGQ= 9902 -IGhvb2s= 9903 -IGRvY3VtZW50YXRpb24= 9904 -UGFyYW1ldGVycw== 9905 -SGVsbG8= 9906 -dnQ= 9907 -IGFydGljbGVz 9908 -IHdlc3Q= 9909 -ZGVmaW5lZA== 9910 -LnNlbGVjdA== 9911 -b2tlbnM= 9912 -IFZBTA== 9913 -LmZpbGU= 9914 -cmVzZXQ= 9915 -IG15cw== 9916 -IE1B 9917 -XSks 9918 -IGNpdGllcw== 9919 -cmVsYXRlZA== 9920 -5Zs= 9921 -IGFwcGVhcmVk 9922 -IHdpZA== 9923 -LnBhbmVs 9924 -IElucw== 9925 -LmVudGl0eQ== 9926 -IGRlY3Jl 9927 -IExvdQ== 9928 -KHRpbWU= 9929 -IFRoYW5r 9930 -LmNyZWF0ZUVsZW1lbnQ= 9931 -IG1lbnRpb25lZA== 9932 -b3VuY2U= 9933 -IFRyeQ== 9934 -IFdhbGw= 9935 -L2ltYWdlcw== 9936 -IE1lbnU= 9937 -Jw0K 9938 -IEVy 9939 -IGNyaXRpYw== 9940 -IFllYXI= 9941 -KHBhcmFt 9942 -IGZsbw== 9943 -Tk4= 9944 -b290ZXI= 9945 -IF07Cg== 9946 -IEFmZg== 9947 -ImdpdGh1Yg== 9948 -cm9vbXM= 9949 -IGh5cA== 9950 -Z2xvYmFs 9951 -IGF2ZWM= 9952 -5pyI 9953 -IGNvbXBsZXRpb24= 9954 -IGNvbmQ= 9955 -b255bW91cw== 9956 -KHRlbXA= 9957 -IHN0YXJz 9958 -IHJlbGV2YW50 9959 -IGNvdmVyZWQ= 9960 -IGVsaW0= 9961 -X3R5cGVz 9962 -KGJvb2w= 9963 -IHR1 9964 -X2V4aXN0cw== 9965 -IHNlY3VyZQ== 9966 -IHN0b3JlZA== 9967 -XS8= 9968 -eEY= 9969 -IENvbnRyb2xsZXI= 9970 -IG1pZ3I= 9971 -TUk= 9972 -IERlbg== 9973 -IGFubnVhbA== 9974 -VUlM 9975 -LWFuZA== 9976 -IGNyaW1l 9977 -YmVs 9978 -IGtpdGNoZW4= 9979 -QGc= 9980 -X3Bo 9981 -b3VybmFtZW50 9982 -IFNvY2lhbA== 9983 -IFNwZWNpYWw= 9984 -bG9nZ2Vy 9985 -IHRhaWw= 9986 -IHVua25vd24= 9987 -ZGVk 9988 -IGFwcHJlYw== 9989 -KGRi 9990 -Y2Y= 9991 -MTU1 9992 -IGFzc2lnbg== 9993 -LW91dA== 9994 -IE1vbnQ= 9995 -ZHA= 9996 -d2lkZ2V0 9997 -IHN0b25l 9998 -LXByaW1hcnk= 9999 -LmdyaWQ= 10000 -UmVzdWx0cw== 10001 -YXp6 10002 -IGRhdWdodGVy 10003 -IGN1cnI= 10004 -MTc1 10005 -IGxpbg== 10006 -IHNvdXRo 10007 -Zm9ybXM= 10008 -IE9VVA== 10009 -bGV0dGU= 10010 -YWtz 10011 -aWd1cmU= 10012 -IEVV 10013 -dmFyaWFibGU= 10014 -IGJyaWVm 10015 -IFNjb3R0 10016 -IGNvbmZlcmVuY2U= 10017 -YW5kYQ== 10018 -X2xvY2s= 10019 -b3JhbA== 10020 -IGVpbmU= 10021 -T1JT 10022 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLw== 10023 -ZXNzbw== 10024 -IHJpcw== 10025 -IGdlbmRlcg== 10026 -ZXN0aWM= 10027 -TGljZW5zZQ== 10028 -KG91dA== 10029 -IG1z 10030 -U2Vl 10031 -IHdpbGxpbmc= 10032 -YXpl 10033 -IHNwb3J0cw== 10034 -IHllcw== 10035 -bHU= 10036 -IHB1cnM= 10037 -L2phdmFzY3JpcHQ= 10038 -LXBybw== 10039 -bmF2YmFy 10040 -X3Byb2R1Y3Q= 10041 -L2Jvb3RzdHJhcA== 10042 -IGRyaXZpbmc= 10043 -IMQ= 10044 -IHByb3Bvcw== 10045 -dWx0aXA= 10046 -dXBsaWM= 10047 -LmVtYWls 10048 -IGFwcHJveA== 10049 -KGNs 10050 -IHdlYXI= 10051 -IHJlcGx5 10052 -YXNzZXQ= 10053 -IGljZQ== 10054 -IHR4 10055 -a3I= 10056 -IEdlcm1hbnk= 10057 -IEdlb3JnZQ== 10058 -IGNi 10059 -CWVycg== 10060 -TW92ZQ== 10061 -IHBvbHk= 10062 -dm9pY2U= 10063 -fSI= 10064 -IGFuaW1hbA== 10065 -QXY= 10066 -IExvY2F0aW9u 10067 -IG5hdGl2ZQ== 10068 -XVsi 10069 -PGRvdWJsZQ== 10070 -IG1haXM= 10071 -LGludA== 10072 -IHByZXBhcg== 10073 -IGludGVydmFs 10074 -cGxlbWVudGF0aW9u 10075 -X0VSUg== 10076 -IGJ1Zw== 10077 -PiI= 10078 -c3RhdA== 10079 -IH0sDQo= 10080 -PHNwYW4= 10081 -IGZhaXRo 10082 -IHJvbQ== 10083 -cHJldg== 10084 -IEVsZWN0 10085 -RmluZA== 10086 -IGdvZA== 10087 -b3Rvcg== 10088 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 10089 -b3JpZ2luYWw= 10090 -Q3Bw 10091 -IFNlbmF0ZQ== 10092 -IHBvc2l0aW9ucw== 10093 -IHdlYXBvbnM= 10094 -IGNvZmY= 10095 -IHB1cnBvc2Vz 10096 -cG9s 10097 -IGltcHJlc3M= 10098 -IGFuaW1hbHM= 10099 -LkVudGl0eQ== 10100 -KG5w 10101 -IG11cmRlcg== 10102 -IGBg 10103 -ZmxhZw== 10104 -IHNvbHV0aW9ucw== 10105 -IEFjdGl2ZQ== 10106 -IGJyaWdodA== 10107 -LmRhdGU= 10108 -IHNpdHU= 10109 -77yI 10110 -LklE 10111 -IHNpZQ== 10112 -KSwNCg== 10113 -YWt0 10114 -U3BhY2U= 10115 -LmRhdA== 10116 -LmluZGV4T2Y= 10117 -aGFu 10118 -YXppbmU= 10119 -IFpl 10120 -IGNyYXNo 10121 -KC8= 10122 -Pj0= 10123 -0LE= 10124 -MTM5 10125 -aXZh 10126 -LkF1dG9TaXpl 10127 -IExhdA== 10128 -X2V4dA== 10129 -SW5pdGlhbGl6ZQ== 10130 -LnJlZ2lzdGVy 10131 -MTU2 10132 -T1BZ 10133 -IHJldmVyc2U= 10134 -X2Rpcw== 10135 -J11b 10136 -IHByb21wdA== 10137 -b250bw== 10138 -IEpvdXJuYWw= 10139 -cm91dGVy 10140 -IG15c3FsaQ== 10141 -I2Vsc2U= 10142 -KSI= 10143 -LXhz 10144 -bGV0cw== 10145 -cGhhbg== 10146 -LkxF 10147 -MTM3 10148 -V2lsbA== 10149 -IGFmZm9yZA== 10150 -IHNraWxs 10151 -LXRvZ2dsZQ== 10152 -TkM= 10153 -QmluZA== 10154 -VFM= 10155 -SnVzdA== 10156 -aXRlcmFs 10157 -WVA= 10158 -CXVuc2lnbmVk 10159 -IHdpbmQ= 10160 -MTQ5 10161 -KSk6Cg== 10162 -IHdhcm5pbmc= 10163 -IFdhdGVy 10164 -IGRyYWZ0 10165 -IGNt 10166 -IHNhbQ== 10167 -IGhvbGRpbmc= 10168 -emlw 10169 -IFNjaWVuY2U= 10170 -IHN1cHBvc2Vk 10171 -R2Vu 10172 -IGRpZXQ= 10173 -PGg= 10174 -IFBhc3M= 10175 -dmk= 10176 -IGh1c2JhbmQ= 10177 -77+977+9 10178 -bm90ZQ== 10179 -IEFib3V0 10180 -IEluc3RpdHV0ZQ== 10181 -IGNsaW1hdGU= 10182 -LkZvcm1hdA== 10183 -IG51dA== 10184 -ZXN0ZWQ= 10185 -IGFwcGFyZW50 10186 -IGhvbGRz 10187 -Zmk= 10188 -bmV3cw== 10189 -Q00= 10190 -dmlkZW8= 10191 -Jzon 10192 -RElUSU9O 10193 -cGluZw== 10194 -IHNlbmlvcg== 10195 -d2E= 10196 -LS0+Cg== 10197 -X2RlZmF1bHQ= 10198 -IERhdGFiYXNl 10199 -cmVw 10200 -RVNT 10201 -bmVyZ3k= 10202 -LkZpbmQ= 10203 -X21hc2s= 10204 -IHJpc2U= 10205 -IGtlcm5lbA== 10206 -Ojok 10207 -LlE= 10208 -IG9mZmVyaW5n 10209 -ZGVjbA== 10210 -IENT 10211 -IGxpc3RlZA== 10212 -IG1vc3RseQ== 10213 -ZW5nZXI= 10214 -IGJsb2Nrcw== 10215 -b2xv 10216 -IGdvdmVybmluZw== 10217 -XEY= 10218 -IGNvbmNlbnQ= 10219 -LmdldFRleHQ= 10220 -IG1i 10221 -IG9jY3VycmVk 10222 -IGNoYW5naW5n 10223 -U2NlbmU= 10224 -X0NPREU= 10225 -QmVo 10226 -IlRoZQ== 10227 -IHRpbGU= 10228 -IEFzc29jaWF0aW9u 10229 -CVA= 10230 -YWx0eQ== 10231 -X2Fk 10232 -b2RpZXM= 10233 -aWF0ZWQ= 10234 -IHByZXBhcmVk 10235 -cG9zc2libGU= 10236 -IG1vcnQ= 10237 -VEVTVA== 10238 -MTQy 10239 -IGlnbm9yZQ== 10240 -IGNhbGM= 10241 -IHJz 10242 -IGFzc2VydEVxdWFscw== 10243 -IHN6 10244 -IFRISVM= 10245 -LiIK 10246 -IGNhbnZhcw== 10247 -amF2YQ== 10248 -IGR1dA== 10249 -VkFMSUQ= 10250 -LnNxbA== 10251 -LmlucHV0 10252 -IGF1eA== 10253 -U3Vw 10254 -IGFydGlzdA== 10255 -VmVj 10256 -X1RJTUU= 10257 -LnN0cmluZ2lmeQ== 10258 -ZXR3ZWVu 10259 -IENhdGVnb3J5 10260 -IFst 10261 -IERldkV4cHJlc3M= 10262 -IEp1bA== 10263 -IHJpbmc= 10264 -LmVk 10265 -WVk= 10266 -TGV0 10267 -VGV4dEZpZWxk 10268 -IGZsYXQ= 10269 -X3ByaW50 10270 -IE9USEVS 10271 -YWRpYW4= 10272 -IGNoZWNrZWQ= 10273 -ZWxl 10274 -QWxpZ24= 10275 -c3RhbmRpbmc= 10276 -IFtdLA== 10277 -IGxhYg== 10278 -dWNreQ== 10279 -IENocmlzdG1hcw== 10280 -KGltYWdl 10281 -Lm1vZHVsZQ== 10282 -IGxvdHM= 10283 -IHNsaWdodGx5 10284 -KGZpbmFs 10285 -ZXJnZQ== 10286 -6L8= 10287 -MTQ3 10288 -IFBvbGljZQ== 10289 -MTQz 10290 -IFJpZ2h0 10291 -IGF3YXJk 10292 -IE9T 10293 -IHt9Cgo= 10294 -IHB0cg== 10295 -b3Zlcw== 10296 -aWNhdGVk 10297 -0LXQvA== 10298 -IG1hbmFnZQ== 10299 -b2xpZGF5 10300 -QW1vdW50 10301 -b29sU3RyaXA= 10302 -dGJvZHk= 10303 -TmF2 10304 -d3JhcA== 10305 -QkI= 10306 -IHdhdGNoaW5n 10307 -YXJpb3M= 10308 -IG9wdGlvbmFs 10309 -X0s= 10310 -IExpY2Vuc2Vk 10311 -Lk1hcA== 10312 -VGltZXI= 10313 -IEFQ 10314 -IFJldg== 10315 -KG8= 10316 -LGM= 10317 -dW1pbg== 10318 -ZXRhaWxlZA== 10319 -IEh5 10320 -IGJsYW5r 10321 -YWdnZXI= 10322 -IFNlbGY= 10323 -KClb 10324 -Lm1ha2U= 10325 -ZWFybg== 10326 -Y2hhbm5lbA== 10327 -PHByZQ== 10328 -YmxlbQ== 10329 -X3Bhc3N3b3Jk 10330 -X3Nw 10331 -aWNpbmc= 10332 -ZXo= 10333 -IHRoZW9yeQ== 10334 -IFRlcg== 10335 -MTg0 10336 -LG4= 10337 -bG9nbw== 10338 -IEhUVFA= 10339 -KCkpKQ== 10340 -LmhhbmRsZQ== 10341 -PjsK 10342 -V29ybGQ= 10343 -IHB5dGhvbg== 10344 -IGxpZg== 10345 -IHRyYXY= 10346 -IGNvbnZlbg== 10347 -Y29tcGFueQ== 10348 -IENsdWI= 10349 -MTM4 10350 -VmVy 10351 -QnRu 10352 -IHpvbmU= 10353 -cHJvZHVjdHM= 10354 -IEVkdWM= 10355 -IHZlcmlmeQ== 10356 -IE1pbA== 10357 -b25v 10358 -XSk7Cgo= 10359 -RU5DRQ== 10360 -IHBhY2tldA== 10361 -IGNlcg== 10362 -IGVudW1lcg== 10363 -IHBhcnM= 10364 -Zm9ybWVk 10365 -IG9jY3Vw 10366 -dHJl 10367 -IGV4ZXJjaXNl 10368 -RGF5 10369 -X3N1bQ== 10370 -IGFza2luZw== 10371 -YXB0aW9u 10372 -IG9yZGVycw== 10373 -IHNwZW5kaW5n 10374 -IEVSUg== 10375 -LkRpcw== 10376 -IFV0aWw= 10377 -4oCcSQ== 10378 -XCc= 10379 -Pyk= 10380 -Lz4K 10381 -IGVtb3Q= 10382 -IGluZmx1ZW5jZQ== 10383 -IEFmcmljYQ== 10384 -YXR0ZXJz 10385 -2YU= 10386 -LnNlc3Npb24= 10387 -IGNoaWVm 10388 -CQkJCQkJCQkJCQk= 10389 -IHRvbQ== 10390 -Y2x1ZGVk 10391 -c2VyaWFs 10392 -X2hhbmRsZXI= 10393 -LlR5cGU= 10394 -YXBlZA== 10395 -IHBvbGljaWVz 10396 -LWV4 10397 -LXRy 10398 -Ymxhbms= 10399 -bWVyY2U= 10400 -IGNvdmVyYWdl 10401 -IHJj 10402 -X21hdHJpeA== 10403 -X2JveA== 10404 -IGNoYXJnZXM= 10405 -IEJvc3Rvbg== 10406 -UGU= 10407 -IGNpcmN1bQ== 10408 -IGZpbGxlZA== 10409 -MTQ4 10410 -IG5vcnRo 10411 -aWN0dXJlQm94 10412 -CXJlcw== 10413 -6K4= 10414 -IHRlcm1pbg== 10415 -IFvigKY= 10416 -SVJFQ1Q= 10417 -IGJlcg== 10418 -ICIuLi8uLi8= 10419 -cmV0Y2g= 10420 -LmNvZGU= 10421 -X2NvbA== 10422 -IEdvdmVybm1lbnQ= 10423 -IGFyZ3Y= 10424 -IExvcmQ= 10425 -YXNp 10426 -RXhlYw== 10427 -CWxldA== 10428 -dmVydGlz 10429 -IGRpc2N1c3Npb24= 10430 -ZW5hbmNl 10431 -b3V0dWJl 10432 -dHlwZW9m 10433 -IHNlcnZlZA== 10434 -IFB1dA== 10435 -CXg= 10436 -IHN3ZWV0 10437 -QmVmb3Jl 10438 -YXRlZ3k= 10439 -Lm9m 10440 -IE1hdGVyaWFs 10441 -U29ydA== 10442 -T05U 10443 -aWdpdGFs 10444 -V2h5 10445 -IHN1c3Q= 10446 -IOc= 10447 -YWJldA== 10448 -IHNlZ21lbnQ= 10449 -IFtdLAo= 10450 -IE11c2xpbQ== 10451 -IGZpbmRWaWV3QnlJZA== 10452 -Y3V0 10453 -X1RFWFQ= 10454 -IE1hcnk= 10455 -IGxvdmVk 10456 -IGxpZQ== 10457 -IEpP 10458 -IGlzc2V0 10459 -bW9udGg= 10460 -IHByaW1l 10461 -dGk= 10462 -IENhcm9s 10463 -VXNl 10464 -MTQ2 10465 -IFBvcA== 10466 -IFNhdmU= 10467 -SW50ZXJ2YWw= 10468 -ZXhlY3V0ZQ== 10469 -ZHk= 10470 -IElyYW4= 10471 -X2NvbnQ= 10472 -CVQ= 10473 -IHBoYXNl 10474 -Y2hlY2tib3g= 10475 -d2Vlaw== 10476 -IGhpZGU= 10477 -IHRpbA== 10478 -IGp1 10479 -Q3VzdG9t 10480 -YnVyZw== 10481 -L00= 10482 -VE9O 10483 -IHF1YW50 10484 -IHJ1Yg== 10485 -aXhlbHM= 10486 -IGluc3RhbGxlZA== 10487 -IGR1bXA= 10488 -IHByb3Blcmx5 10489 -KExpc3Q= 10490 -IGRlY2lkZQ== 10491 -YXBwbHk= 10492 -SGFz 10493 -IGtlZXBpbmc= 10494 -IGNpdGl6ZW5z 10495 -IGpvaW50 10496 -cG9vbA== 10497 -U29ja2V0 10498 -X29w 10499 -IHdlYXBvbg== 10500 -Z25vcmU= 10501 -IEV4ZWM= 10502 -b3R0ZW4= 10503 -IE1T 10504 -ICgt 10505 -IFJldmlldw== 10506 -IGV4YW1wbGVz 10507 -IHRpZ2h0 10508 -ISg= 10509 -RFA= 10510 -IE1lc3NhZ2VCb3g= 10511 -IHBob3RvZ3JhcGg= 10512 -MTY0 10513 -VVJJ 10514 -w6l0 10515 -bG93 10516 -IEdyYW5k 10517 -LnBlcnNpc3RlbmNl 10518 -IG1haW50YWlu 10519 -IG51bXM= 10520 -IHppcA== 10521 -aWFscw== 10522 -IEdldHM= 10523 -cGVn 10524 -IEJ1ZmZlcg== 10525 -fn5+fg== 10526 -cmFzdHJ1Y3R1cmU= 10527 -IFBM 10528 -dWVu 10529 -b2JieQ== 10530 -c2l6ZW9m 10531 -IHBpYw== 10532 -IHNlZWQ= 10533 -IGV4cGVyaWVuY2Vk 10534 -IG9kZA== 10535 -IGtpY2s= 10536 -IHByb2NlZHVyZQ== 10537 -YXZpZ2F0b3I= 10538 -LW9u 10539 -LGo= 10540 -IEFsdGhvdWdo 10541 -IHVzZXJJZA== 10542 -YWNjZXB0 10543 -Qmx1ZQ== 10544 -SUNvbG9y 10545 -bGF5ZXI= 10546 -YXZhaWxhYmxl 10547 -IGVuZHM= 10548 -LnRhYmxl 10549 -IGRhdGFzZXQ= 10550 -YnVz 10551 -IGV4cGxhaW4= 10552 -KHBybw== 10553 -IENvbW1pdHRlZQ== 10554 -IG5vdGVk 10555 -XToK 10556 -RGlt 10557 -c3RkaW8= 10558 -MTU0 10559 -LiIsCg== 10560 -X3NvdXJjZQ== 10561 -MTgx 10562 -IFdlZWs= 10563 -IEVkZ2U= 10564 -IG9wZXJhdGluZw== 10565 -IGVzdGU= 10566 -aXBs 10567 -MzMw 10568 -YWdpbmF0aW9u 10569 -IHByb2NlZWQ= 10570 -IGFuaW1hdGlvbg== 10571 -Lk1vZGVscw== 10572 -IFdhdGNo 10573 -aWF0 10574 -IG9wcG9u 10575 -L0E= 10576 -UmVwb3J0 10577 -IHNvdW5kcw== 10578 -X2J1Zg== 10579 -SUVMRA== 10580 -IGJ1bmQ= 10581 -CWdldA== 10582 -LnBy 10583 -KHRtcA== 10584 -IGtpZA== 10585 -PgoKCg== 10586 -IHlhbmc= 10587 -Tm90Rm91bmQ= 10588 -0YY= 10589 -bWF0aA== 10590 -QGdtYWls 10591 -IExJTUlU 10592 -cmVkaWVudHM= 10593 -IHZlbnQ= 10594 -YXZpZ2F0ZQ== 10595 -TG9vaw== 10596 -IHJlbGlnaW91cw== 10597 -IHJhbmQ= 10598 -cmlv 10599 -KEdM 10600 -X2lw 10601 -dWFu 10602 -aWNpZW5jeQ== 10603 -IENoYW5nZQ== 10604 -Pg0KDQo= 10605 -IEVudGl0eQ== 10606 -IHJlbmNvbnRyZQ== 10607 -IFJldA== 10608 -cGxhbg== 10609 -w6lu 10610 -Qk9PTA== 10611 -dXJpZXM= 10612 -dHJhaW4= 10613 -RGVmaW5pdGlvbg== 10614 -PT09PT09PT09PT09 10615 -eno= 10616 -NDUw 10617 -QW5pbWF0aW9u 10618 -IE9L 10619 -X21lbnU= 10620 -LmJs 10621 -X3Njb3Jl 10622 -IGFjYWQ= 10623 -KFN5c3RlbQ== 10624 -IHJlZnJlc2g= 10625 -Jz0+JA== 10626 -LkdyYXBoaWNz 10627 -YW1lbnRv 10628 -cGlk 10629 -dGM= 10630 -IHRpcHM= 10631 -IGhvbWVz 10632 -IGZ1ZWw= 10633 -4pY= 10634 -X2hlbHBlcg== 10635 -ICANCg== 10636 -IFJvb20= 10637 -LkNsb3Nl 10638 -X2F0dHI= 10639 -IE1vdW50 10640 -IEV2 10641 -YXJzZXI= 10642 -X3RvcA== 10643 -ZWFo 10644 -IERlbGV0ZQ== 10645 -44CN 10646 -dWtl 10647 -IHVzYWdl 10648 -YXJpYQ== 10649 -X2Rldg== 10650 -IHRleHR1cmU= 10651 -IGNvbnZlcnNhdGlvbg== 10652 -ZXBlcg== 10653 -QmVhbg== 10654 -ZG9uZQ== 10655 -bm9uYXRvbWlj 10656 -IFNlY29uZA== 10657 -IHNob290aW5n 10658 -X3ByZQ== 10659 -Q29tcG9uZW50cw== 10660 -IF0KCg== 10661 -X18s 10662 -c3RpdHV0aW9u 10663 -LkNoYXI= 10664 -PigpOwoK 10665 -IHByZXNlbnRlZA== 10666 -IHdh 10667 -b2tlcg== 10668 -LQoK 10669 -aW5lcg== 10670 -IGJlY29taW5n 10671 -IGluY2lkZW50 10672 -QXR0 10673 -MTYy 10674 -IHJldmVhbGVk 10675 -Zm9yYw== 10676 -IGJvb3Q= 10677 -LnBhZ2U= 10678 -RW51bWVyYXRvcg== 10679 -MTY1 10680 -Xy0+ 10681 -UGhvdG8= 10682 -IHNwcmluZw== 10683 -LiIs 10684 -IERpY3Rpb25hcnk= 10685 -QkpFQ1Q= 10686 -IGxvY2F0aW9ucw== 10687 -IHNhbXBsZXM= 10688 -SW5wdXRTdHJlYW0= 10689 -IEJyb3du 10690 -IHN0YXRz 10691 -cXVhbGl0eQ== 10692 -0YU= 10693 -LWRpcw== 10694 -IGhlbHBpbmc= 10695 -IHBlZA== 10696 -MjI0 10697 -KHNl 10698 -IFdobw== 10699 -YWxpYW4= 10700 -aW50ZXJuYWw= 10701 -IGZ0 10702 -PigpLg== 10703 -LT57 10704 -IG1pbmU= 10705 -IHNlY3Rvcg== 10706 -IGdybw== 10707 -IG9wcG9ydHVuaXRpZXM= 10708 -IMO8 10709 -IG1w 10710 -IGFsbGVnZWQ= 10711 -IGRvdWJ0 10712 -TW91c2U= 10713 -QWJvdXQ= 10714 -X3BhcnQ= 10715 -IGNoYWly 10716 -IHN0b3BwZWQ= 10717 -MTYx 10718 -bG9vcA== 10719 -ZW50aXRpZXM= 10720 -IGFwcHM= 10721 -YW5zaW9u 10722 -IG1lbnRhbA== 10723 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 10724 -RlI= 10725 -IGRlZmVuZA== 10726 -Y2FyZQ== 10727 -IGlkZWFs 10728 -L2FwaQ== 10729 -dXJmYWNl 10730 -MDEx 10731 -IGVsZQ== 10732 -dWxhdG9y 10733 -IFJpZ2h0cw== 10734 -YW5ndWFnZXM= 10735 -IGZ1bmRz 10736 -IGFkYXB0 10737 -QXR0cmlidXRlcw== 10738 -IGRlcGxveQ== 10739 -b3B0cw== 10740 -IHZhbGlkYXRpb24= 10741 -IGNvbmNlcm5z 10742 -dWNl 10743 -Lm51bQ== 10744 -dWx0dXJl 10745 -aWxh 10746 -IGN1cA== 10747 -IHB1cmU= 10748 -LkZvcmU= 10749 -MTgz 10750 -IEhhc2hNYXA= 10751 -LnZhbHVlT2Y= 10752 -YXNt 10753 -TU8= 10754 -IGNz 10755 -IHN0b3Jlcw== 10756 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 10757 -IGNvbW11bmljYXRpb24= 10758 -bWVt 10759 -LkV2ZW50SGFuZGxlcg== 10760 -LlN0YXR1cw== 10761 -X3JpZ2h0 10762 -LnNldE9u 10763 -U2hlZXQ= 10764 -IGlkZW50aWZ5 10765 -ZW5lcmF0ZWQ= 10766 -b3JkZXJlZA== 10767 -ICJb 10768 -IHN3ZQ== 10769 -Q29uZGl0aW9u 10770 -IEFjY29yZGluZw== 10771 -IHByZXBhcmU= 10772 -IHJvYg== 10773 -UG9vbA== 10774 -IHNwb3J0 10775 -cnY= 10776 -IFJvdXRlcg== 10777 -IGFsdGVybmF0aXZl 10778 -KFtd 10779 -IENoaWNhZ28= 10780 -aXBoZXI= 10781 -aXNjaGU= 10782 -IERpcmVjdG9y 10783 -a2w= 10784 -IFdpbA== 10785 -a2V5cw== 10786 -IG15c3Fs 10787 -IHdlbGNvbWU= 10788 -a2luZw== 10789 -IE1hbmFnZXI= 10790 -IGNhdWdodA== 10791 -KX0K 10792 -U2NvcmU= 10793 -X1BS 10794 -IHN1cnZleQ== 10795 -aGFi 10796 -SGVhZGVycw== 10797 -QURFUg== 10798 -IGRlY29y 10799 -IHR1cm5z 10800 -IHJhZGl1cw== 10801 -ZXJydXB0 10802 -Q29y 10803 -IG1lbA== 10804 -IGludHI= 10805 -KHE= 10806 -IEFD 10807 -YW1vcw== 10808 -TUFY 10809 -IEdyaWQ= 10810 -IEplc3Vz 10811 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 10812 -LkRF 10813 -IHRz 10814 -IGxpbmtlZA== 10815 -ZnJlZQ== 10816 -IFF0 10817 -IC8qKg0K 10818 -IGZhc3Rlcg== 10819 -Y3Ry 10820 -X0o= 10821 -RFQ= 10822 -LkNoZWNr 10823 -IGNvbWJpbmF0aW9u 10824 -IGludGVuZGVk 10825 -LXRoZQ== 10826 -LXR5cGU= 10827 -MTgy 10828 -ZWN0b3Jz 10829 -YW1p 10830 -dXRpbmc= 10831 -IHVtYQ== 10832 -WE1M 10833 -VUNU 10834 -QXA= 10835 -IFJhbmRvbQ== 10836 -IHJhbg== 10837 -LnNvcnQ= 10838 -IHNvcnRlZA== 10839 -LlVu 10840 -NDAx 10841 -X1BFUg== 10842 -aXRvcnk= 10843 -IHByaW9yaXR5 10844 -IEdhbA== 10845 -IE9sZA== 10846 -aG90 10847 -IERpc3BsYXk= 10848 -KHN1Yg== 10849 -X1RI 10850 -X1k= 10851 -IENhcmU= 10852 -bG9hZGluZw== 10853 -S2luZA== 10854 -X2hhbmRsZQ== 10855 -LCw= 10856 -cmFzZQ== 10857 -X3JlcGxhY2U= 10858 -LmFkZEV2ZW50TGlzdGVuZXI= 10859 -IFJU 10860 -MTcy 10861 -IGVudGVyZWQ= 10862 -Z2Vycw== 10863 -IGljaA== 10864 -KHN0YXJ0 10865 -MjA1 10866 -L2FwcA== 10867 -IGJyb3RoZXI= 10868 -TWVtb3J5 10869 -T3V0bGV0 10870 -IHV0Zg== 10871 -cHJlYw== 10872 -IG5hdmlnYXRpb24= 10873 -T1JL 10874 -IGRzdA== 10875 -RGV0YWls 10876 -IGF1ZGllbmNl 10877 -IGR1cg== 10878 -IGNsdXN0ZXI= 10879 -dW5jaGVk 10880 -IF0s 10881 -IGNvbWZvcnRhYmxl 10882 -LnZhbHVlcw== 10883 -IFRvdGFs 10884 -IHNuYXA= 10885 -IHN0YW5kYXJkcw== 10886 -IHBlcmZvcm1lZA== 10887 -aGFuZA== 10888 -KCJA 10889 -5a0= 10890 -IHBoaWw= 10891 -aWJy 10892 -dHJpbQ== 10893 -IGZvcmdldA== 10894 -MTU3 10895 -IGRvY3Rvcg== 10896 -LlRleHRCb3g= 10897 -Mzc3 10898 -aWNvbnM= 10899 -LHM= 10900 -IE9w 10901 -U20= 10902 -U3RvcA== 10903 -CUxpc3Q= 10904 -CXU= 10905 -Q29tbWVudA== 10906 -X1ZFUlNJT04= 10907 -Llh0cmE= 10908 -UGVyc29u 10909 -cmI= 10910 -TE9C 10911 -ICAgICAgICAgICAgICAgICAgICAK 10912 -IENlbnRyYWw= 10913 -Mjcw 10914 -SUNL 10915 -cmFx 10916 -IHB1dHRpbmc= 10917 -IG1k 10918 -IExvdmU= 10919 -UHJvZ3JhbQ== 10920 -Qm9yZGVy 10921 -b29y 10922 -IGFsbG93aW5n 10923 -YWZ0ZXI= 10924 -IGVudHJpZXM= 10925 -IE1heWJl 10926 -XSku 10927 -IFNob3J0 10928 -KVw= 10929 -Lm5vdw== 10930 -ZnJpZW5k 10931 -IHByZWZlcg== 10932 -IEdQSU8= 10933 -b3Npcw== 10934 -IEdhbWVPYmplY3Q= 10935 -IHNraXA= 10936 -IGNvbXBldGl0aW9u 10937 -X21hdGNo 10938 -bGljYXRpb25z 10939 -X0NPTlQ= 10940 -Lmdyb3VwQm94 10941 -IGFscw== 10942 -NjY2 10943 -Ildl 10944 -X2Vx 10945 -bGFu 10946 -X3NlYXJjaA== 10947 -IE11c2lj 10948 -YXNpcw== 10949 -IGJpbmQ= 10950 -IElzbGFuZA== 10951 -cnVt 10952 -KEU= 10953 -IHNlYXQ= 10954 -VmlkZW8= 10955 -IGFjaw== 10956 -cmVlaw== 10957 -PXsoKQ== 10958 -IHJhdGluZw== 10959 -IHJlc3RhdXJhbnQ= 10960 -NDU2 10961 -REVY 10962 -KGJ1Zg== 10963 -cHBpbmc= 10964 -dWFsaXR5 10965 -IGxlYWd1ZQ== 10966 -MTc2 10967 -IGZvY3VzZWQ= 10968 -YXBvbg== 10969 -JGRhdGE= 10970 -Q0xVRA== 10971 -Q0xVRElORw== 10972 -IGFic29sdXRl 10973 -KHF1ZXJ5 10974 -IHRlbGxz 10975 -QW5n 10976 -IGNvbW11bml0aWVz 10977 -IGhvbmVzdA== 10978 -b2tpbmc= 10979 -IGFwYXJ0 10980 -YXJpdHk= 10981 -LyQ= 10982 -X21vZHVsZQ== 10983 -IEVuYw== 10984 -LmFu 10985 -LkNvbmZpZw== 10986 -Q3Jl 10987 -IHNob2Nr 10988 -IEFyYWI= 10989 -SUVOVA== 10990 -L3Jl 10991 -IHJldHJpZQ== 10992 -eWNsZXI= 10993 -aXNh 10994 -IE9yZ2Fu 10995 -LmdyYXBo 10996 -IO0= 10997 -IEJBUw== 10998 -RW51bQ== 10999 -IHBvc3NpYmx5 11000 -0YDQsNA= 11001 -IEphcGFuZXNl 11002 -IGNyYWZ0 11003 -IFBsYWNl 11004 -IHRhbGVudA== 11005 -IGZ1bmRpbmc= 11006 -IGNvbmZpcm1lZA== 11007 -IGN5Y2xl 11008 -L3g= 11009 -R0U= 11010 -IGhlYXJpbmc= 11011 -IHBsYW50cw== 11012 -IG1vdXRo 11013 -cGFnZXM= 11014 -b3JpYQ== 11015 -IFJlbW92ZQ== 11016 -X3RvdGFs 11017 -IG9k 11018 -b2xsYXBzZQ== 11019 -ZG9vcg== 11020 -IGJvdWdodA== 11021 -IGFkZHI= 11022 -QVJDSA== 11023 -X2RpbQ== 11024 -ZGRlbg== 11025 -IGRlY2FkZXM= 11026 -UkVRVUVTVA== 11027 -IHZlcnNpb25z 11028 -ZmlyZQ== 11029 -MDA2 11030 -IG1vdmVz 11031 -ZmI= 11032 -IGNvZmZlZQ== 11033 -LmNvbm5lY3Q= 11034 -IFJvdw== 11035 -IHNjaGVtYQ== 11036 -U2NvcGU= 11037 -LVR5cGU= 11038 -IGZpZ2h0aW5n 11039 -IHJldGFpbA== 11040 -IG1vZGlmaWVk 11041 -VEY= 11042 -RmlsZXM= 11043 -bmll 11044 -X2NvbW1hbmQ= 11045 -c3RvbmU= 11046 -INGC 11047 -X3RocmVhZA== 11048 -IGJvbmQ= 11049 -IERldmVsb3BtZW50 11050 -IHB0 11051 -Rk9STQ== 11052 -cGxldA== 11053 -IGlkZW50aWZpZWQ= 11054 -Y3Bw 11055 -MjA2 11056 -MjI1 11057 -IGNvZGluZw== 11058 -b2tlZA== 11059 -IE1hc3Rlcg== 11060 -SURUSA== 11061 -IHJlc2lkZW50cw== 11062 -cmVkaXQ= 11063 -IFBob3Rv 11064 -PS0= 11065 -dW50ZQ== 11066 -YXRldXI= 11067 -MTU5 11068 -X1NUQVRF 11069 -IFNpbmc= 11070 -IHNoZWV0 11071 -LnZhbA== 11072 -b3JzZQ== 11073 -IGhlcnM= 11074 -IGRldGVybWluZWQ= 11075 -Q29tbW9u 11076 -IHdlZA== 11077 -X3F1ZXVl 11078 -UEg= 11079 -IEF0bA== 11080 -Y3JlZA== 11081 -L0xJQ0VOU0U= 11082 -IG1lcw== 11083 -IGFkdmFuY2Vk 11084 -LmphdmE= 11085 -LlNo 11086 -R28= 11087 -a2lsbA== 11088 -ZnA= 11089 -X3NldHRpbmdz 11090 -IHBhbA== 11091 -IHRydWNr 11092 -IGNvbWJpbmVk 11093 -ICIkew== 11094 -IENvcnBvcg== 11095 -IGpvaW5lZA== 11096 -IEpvc2U= 11097 -IEN1cA== 11098 -dW5z 11099 -ZXN0aXZhbA== 11100 -bGV2aXNpb24= 11101 -IGJyb2tlbg== 11102 -IG1hcnJpYWdl 11103 -IFdlc3Rlcm4= 11104 -IHJlcHJlc2VudHM= 11105 -IFRpdGxl 11106 -IHNz 11107 -LkFzcw== 11108 -b25nb29zZQ== 11109 -aWVudG8= 11110 -PD4oKTsK 11111 -IGFic29sdXRlbHk= 11112 -IHNtb290aA== 11113 -VEVSTg== 11114 -IFVubGVzcw== 11115 -V29yZA== 11116 -IG1lcmdl 11117 -aWdhbg== 11118 -IFZvbA== 11119 -IG5u 11120 -LmdldElk 11121 -INC3 11122 -MTcx 11123 -IHNleHk= 11124 -IHNlZWtpbmc= 11125 -U2luZ2xl 11126 -LnRoaXM= 11127 -MTc5 11128 -IGtvbQ== 11129 -Ym91bmQ= 11130 -OyI= 11131 -IGZvbnRTaXpl 11132 -X2Rm 11133 -IGluanVyeQ== 11134 -KEg= 11135 -IGlzc3VlZA== 11136 -X0VORA== 11137 -OnNlbGY= 11138 -MDIw 11139 -IHBhdGNo 11140 -IGxlYXZlcw== 11141 -IGFkb3B0 11142 -RmlsZU5hbWU= 11143 -44CQ 11144 -IGV4ZWN1dGl2ZQ== 11145 -IEJ5dGU= 11146 -XSkpCg== 11147 -IG51 11148 -b3V0aW5n 11149 -Y2x1ZGluZw== 11150 -LVI= 11151 -Lm9wdGlvbnM= 11152 -IHN1YnN0YW50 11153 -YXZheA== 11154 -IEJVVA== 11155 -IHRlY2huaWNhbA== 11156 -IHR3aWNl 11157 -IG3DoXM= 11158 -IHVuaXZlcnM= 11159 -eXI= 11160 -IGRyYWc= 11161 -IERD 11162 -IHNlZA== 11163 -IGJvdA== 11164 -IFBhbA== 11165 -IEhhbGw= 11166 -Zm9yY2VtZW50 11167 -IGF1Y2g= 11168 -Lm1vZA== 11169 -bm90YXRpb24= 11170 -X2ZpbGVz 11171 -LmxpbmU= 11172 -X2ZsYWc= 11173 -W25hbWU= 11174 -IHJlc29sdXRpb24= 11175 -IGJvdHQ= 11176 -KCJb 11177 -ZW5kZQ== 11178 -KGFycg== 11179 -RnJlZQ== 11180 -KEAi 11181 -IERpc3RyaWN0 11182 -UEVD 11183 -Oi0= 11184 -UGlja2Vy 11185 -IEpv 11186 -ICAgICAK 11187 -IFJpdmVy 11188 -X3Jvd3M= 11189 -IGhlbHBmdWw= 11190 -IG1hc3NpdmU= 11191 -LS0tCg== 11192 -IG1lYXN1cmVz 11193 -MDA3 11194 -IFJ1bnRpbWU= 11195 -IHdvcnJ5 11196 -IFNwZWM= 11197 -CUQ= 11198 -44CR 11199 -ICl7Cg== 11200 -IHdvcnNl 11201 -KGZpbGVuYW1l 11202 -IGxheQ== 11203 -IG1hZ2lj 11204 -IFRoZWly 11205 -b3Vs 11206 -c3Ryb3k= 11207 -IFdoZXJl 11208 -Mjgw 11209 -IHN1ZGRlbg== 11210 -IGRlZmU= 11211 -IGJpbmRpbmc= 11212 -IGZsaWdodA== 11213 -IE9uSW5pdA== 11214 -IFdvbWVu 11215 -IFBvbGljeQ== 11216 -IGRydWdz 11217 -aXNoaW5n 11218 -KCcuLi8= 11219 -IE1lbA== 11220 -cGVhdA== 11221 -dG9y 11222 -IHByb3Bvc2Vk 11223 -IHN0YXRlZA== 11224 -X1JFUw== 11225 -IGVhc3Q= 11226 -MjEy 11227 -IENPTkRJVElPTg== 11228 -X2Rlc2M= 11229 -IHdpbm5pbmc= 11230 -Zm9saW8= 11231 -TWFwcGVy 11232 -IFBhbg== 11233 -IEFuZ2U= 11234 -LnNlcnZsZXQ= 11235 -IGNvcGllcw== 11236 -TE0= 11237 -IHZt 11238 -5Y0= 11239 -IGRpY3Rpb25hcnk= 11240 -U2Vn 11241 -MTc3 11242 -ZWxpbmVz 11243 -IFNlbmQ= 11244 -IGlyb24= 11245 -IEZvcnQ= 11246 -MTY2 11247 -LmRvbWFpbg== 11248 -IGRlYmF0ZQ== 11249 -Tm90TnVsbA== 11250 -ZXE= 11251 -YWNoZXI= 11252 -bGY= 11253 -CWZtdA== 11254 -IGxhd3k= 11255 -MTc4 11256 -xJ8= 11257 -IE1lbg== 11258 -IHRyaW0= 11259 -KE5VTEw= 11260 -ICEh 11261 -IHBhZA== 11262 -IGZvbGxvd3M= 11263 -Il1bIg== 11264 -cmVxdQ== 11265 -IEVw 11266 -LmdpdGh1Yg== 11267 -KGltZw== 11268 -ZXRv 11269 -KCdc 11270 -U2VydmljZXM= 11271 -dW1ibmFpbA== 11272 -X21haW4= 11273 -cGxldGVk 11274 -Zm9ydHVuYXRlbHk= 11275 -IHdpbmRvd3M= 11276 -IHBsYW5l 11277 -IENvbm5lY3Rpb24= 11278 -LmxvY2Fs 11279 -dWFyZA== 11280 -fVw= 11281 -PT0i 11282 -YW5kb24= 11283 -IFJveQ== 11284 -d2VzdA== 11285 -MTU4 11286 -aWdpbmFs 11287 -ZW1pZXM= 11288 -aXR6 11289 -Jyk6Cg== 11290 -IFBldGVy 11291 -IHRvdWdo 11292 -IHJlZHVjZWQ= 11293 -IGNhbGN1bGF0ZQ== 11294 -IHJhcGlk 11295 -Y3VzdG9tZXI= 11296 -IGVmZmljaWVudA== 11297 -IG1lZGl1bQ== 11298 -IGZlbGw= 11299 -LnJlZg== 11300 -IENhcw== 11301 -IGZlZWRiYWNr 11302 -U3BlZWQ= 11303 -KG91dHB1dA== 11304 -YWpl 11305 -IGNhdGVnb3JpZXM= 11306 -IGZlZQ== 11307 -fTs= 11308 -IGRlbGV0ZWQ= 11309 -cmVo 11310 -IHByb29m 11311 -RGVzYw== 11312 -QnVpbGQ= 11313 -IHNpZGVz 11314 -LkFycmF5TGlzdA== 11315 -LSU= 11316 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 11317 -2LE= 11318 -Lm1hdGNo 11319 -0LvQuA== 11320 -IGZlZWxz 11321 -IGFjaGlldmU= 11322 -IGNsaW0= 11323 -X09O 11324 -IENE 11325 -IHRlYWNoZXI= 11326 -X2N1cnJlbnQ= 11327 -Ym4= 11328 -X1BM 11329 -aXN0aW5n 11330 -RW5hYmxl 11331 -R0VO 11332 -IHR2 11333 -IHNvY2s= 11334 -IHBsYXlz 11335 -IGRpc2NvdW50 11336 -IEtF 11337 -IERlYnVn 11338 -Rm9yZQ== 11339 -IElyYXE= 11340 -IGFwcGVhcmFuY2U= 11341 -TW9u 11342 -IHN0eWxlZA== 11343 -IEh1bWFu 11344 -aW90 11345 -IEhpc3Rvcnk= 11346 -IHNhYw== 11347 -IENvbGxlY3Rpb24= 11348 -IHJlY29tbWVuZGVk 11349 -LlNlbGVjdGVk 11350 -IG9yZ2FuaXphdGlvbnM= 11351 -IGRpc2NvdmVyZWQ= 11352 -Y29ob2w= 11353 -YWRhcw== 11354 -IFRob21hcw== 11355 -TWF5 11356 -IGNvbnNlcnY= 11357 -IGRvbWlu 11358 -IEZvbGxvdw== 11359 -IFNlY3Rpb24= 11360 -IFRoYW5rcw== 11361 -VXNlcm5hbWU= 11362 -IHJlY2lwZQ== 11363 -IHdvbmRlcmZ1bA== 11364 -LnNsZWVw 11365 -X2lm 11366 -CQoJCg== 11367 -b3Jubw== 11368 -IHJ1 11369 -X3RhcmdldA== 11370 -LiIi 11371 -4KY= 11372 -RXZlbnRBcmdz 11373 -IGlucHV0cw== 11374 -IGZpZg== 11375 -IHZpc2lvbg== 11376 -Y3k= 11377 -IFNlcmllcw== 11378 -KSgoKA== 11379 -IHRyYWRpbmc= 11380 -IG1hcmtlcg== 11381 -QmVnaW4= 11382 -IHR5cGljYWxseQ== 11383 -IGNhdXNlcw== 11384 -ZHJvcGRvd24= 11385 -X0RFQlVH 11386 -MjYw 11387 -IGRldGVjdA== 11388 -Y291bnRyeQ== 11389 -ISIpOwo= 11390 -CVI= 11391 -YXBweQ== 11392 -IGNyZWY= 11393 -KCc8 11394 -Ij0+ 11395 -IExF 11396 -cmVhZGVy 11397 -IGFkbWluaXN0cg== 11398 -w7U= 11399 -dWNrZXQ= 11400 -IGZhc2hpb24= 11401 -LmNoYXI= 11402 -aXphcg== 11403 -IGRpc2FibGU= 11404 -IHN1Yw== 11405 -IExpdmU= 11406 -aXNzdWU= 11407 -IG1ldGFkYXRh 11408 -ZmxhZ3M= 11409 -IPCf 11410 -IGNvbW1pdHRlZA== 11411 -IHZh 11412 -IHJvdWdo 11413 -ICcnJwo= 11414 -IGhpZ2hsaWdodA== 11415 -X3ZhcnM= 11416 -Vk8= 11417 -IGVuY29kaW5n 11418 -LVo= 11419 -X3NpZ24= 11420 -JCgiIw== 11421 -IHJhaW4= 11422 -cmVhdGVzdA== 11423 -IEVORA== 11424 -U2VsZWN0aW9u 11425 -IGNhbmRpZGF0ZXM= 11426 -IHNhdg== 11427 -LkVtcHR5 11428 -IGRlY2lzaW9ucw== 11429 -IGNvbGxhYm9y 11430 -cmlkZ2U= 11431 -ZmVlZA== 11432 -cmVzc2lvbg== 11433 -IHBlcnNvbnM= 11434 -Vk0= 11435 -MDA4 11436 -ZWdh 11437 -X0JJVA== 11438 -QWNjb3JkaW5n 11439 -YWNrZWQ= 11440 -IGRvbGxhcnM= 11441 -X2xvc3M= 11442 -IENvc3Q= 11443 -fSIK 11444 -Tm90aWZpY2F0aW9u 11445 -IHByb3N0aXQ= 11446 -IGF1dGhvcml0eQ== 11447 -LnJlYw== 11448 -IHNwb2tlcw== 11449 -IFRvZGF5 11450 -aXN0YW50 11451 -IEhlYWQ= 11452 -4oCdLg== 11453 -ZXJ0YWlubWVudA== 11454 -Y2Vhbg== 11455 -Y3VsYXRl 11456 -IHZlbg== 11457 -SG93ZXZlcg== 11458 -X2Fycg== 11459 -IHRva2Vucw== 11460 -R3JhcGg= 11461 -IEp1ZA== 11462 -IFZpcmdpbg== 11463 -IFNlcmlhbA== 11464 -dW5uaW5n 11465 -TXV0YWJsZQ== 11466 -YWdlcnM= 11467 -LmNzdg== 11468 -IGRldmVsb3Bpbmc= 11469 -IGluc3RydWN0aW9ucw== 11470 -IHByb21pc2U= 11471 -IHJlcXVlc3RlZA== 11472 -X2VuY29kZQ== 11473 -LyI= 11474 -IEljb24= 11475 -dWlsdA== 11476 -LWRheQ== 11477 -IGludGVsbGlnZW5jZQ== 11478 -LklT 11479 -IE9ic2VydmFibGU= 11480 -IEhhcmQ= 11481 -Qm9vbA== 11482 -MjEx 11483 -aWRlbnRpYWw= 11484 -LkFuY2hvcg== 11485 -IHNlbGxpbmc= 11486 -Q0k= 11487 -QUdFUw== 11488 -dGxl 11489 -YnVy 11490 -VUZGRVI= 11491 -Ulk= 11492 -IGJpZ2dlcg== 11493 -IHJhdA== 11494 -IGZhbW91cw== 11495 -IHR5cGVuYW1l 11496 -IGV4cGxhaW5lZA== 11497 -fX0K 11498 -IG51Y2xlYXI= 11499 -LU4= 11500 -IGNyaXNpcw== 11501 -IEVudGVy 11502 -IGFuc3dlcnM= 11503 -LyR7 11504 -L3Bs 11505 -IHNlcXU= 11506 -X25leHQ= 11507 -bWFzaw== 11508 -IHN0YW5kaW5n 11509 -IHBsZW50eQ== 11510 -IENyb3Nz 11511 -CXJldA== 11512 -ZHJv 11513 -IENhc3Q= 11514 -MTY3 11515 -PXRydWU= 11516 -IENocmlz 11517 -aWNpbw== 11518 -IE1pa2U= 11519 -RGVjaW1hbA== 11520 -YWRkQ29tcG9uZW50 11521 -TGVu 11522 -IGNvY2s= 11523 -ICN7 11524 -VVJO 11525 -PHRy 11526 -IGF1dGhvcml0aWVz 11527 -UmVzb3VyY2Vz 11528 -LUg= 11529 -Qm90dG9t 11530 -MDEy 11531 -X3F1 11532 -cHV0ZXI= 11533 -ZXN0ZXJkYXk= 11534 -RGlzcGF0Y2g= 11535 -c2luY2U= 11536 -IGZhbWlsaWFy 11537 -LGk= 11538 -VkM= 11539 -IG1lbnQ= 11540 -LEM= 11541 -IGZyZWVkb20= 11542 -IHJvdXRlcw== 11543 -IEJ1eQ== 11544 -IGNvbW1hbmRz 11545 -IG1lc2g= 11546 -L0M= 11547 -IFNldHRpbmdz 11548 -LXN0eWxl 11549 -IHdpdG5lc3M= 11550 -IGNsZQ== 11551 -IHVuaW9u 11552 -ZWZhdWx0 11553 -YXJldA== 11554 -IHRob3VnaHRz 11555 -IC0tLS0= 11556 -X3Byb2Nlc3M= 11557 -X3Vz 11558 -aW5nbHk= 11559 -VUVT 11560 -VG91Y2g= 11561 -INC8 11562 -X29wZW4= 11563 -IFZlYw== 11564 -IHJld2FyZA== 11565 -LkNsaWNr 11566 -Lzo= 11567 -IG5pZQ== 11568 -Q2hhbmdlcw== 11569 -TW9udGg= 11570 -77yf 11571 -IGV4ZWN1dGlvbg== 11572 -IGJlYWNo 11573 -KEludGVnZXI= 11574 -CWE= 11575 -Lyc= 11576 -LkZvbnRTdHlsZQ== 11577 -IGFib3J0 11578 -IFNpbmdsZQ== 11579 -KGlzc2V0 11580 -IGRw 11581 -IH19PC8= 11582 -IE1h 11583 -MjE0 11584 -LlJvd3M= 11585 -IFBldA== 11586 -JSk= 11587 -cmFuZA== 11588 -6YA= 11589 -UnVsZQ== 11590 -IGhlbA== 11591 -MDIx 11592 -UklURQ== 11593 -IHF1aWV0 11594 -IHJhdGlv 11595 -IENPTkRJVElPTlM= 11596 -b3NvcGg= 11597 -IElM 11598 -IGFkdmVudA== 11599 -Y2Fw 11600 -Ozwv 11601 -IFVTQg== 11602 -RHJpdmVy 11603 -IG91cnM= 11604 -IEpvaG5zb24= 11605 -Lks= 11606 -X2RlbGV0ZQ== 11607 -LnE= 11608 -CXN0cg== 11609 -L2NvbW1vbg== 11610 -CXN0cmluZw== 11611 -IFBERg== 11612 -YWN0cw== 11613 -LkFjdGlvbg== 11614 -IFF1ZXJ5 11615 -LnJlc3BvbnNl 11616 -IEdpcmw= 11617 -IHByb2Nlc3Nlcw== 11618 -PEludGVnZXI= 11619 -aW1v 11620 -IGFkZHM= 11621 -IGVudGlyZWx5 11622 -IHdhc2g= 11623 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 11624 -IGFuaW1hdGVk 11625 -IHByb2ZpdA== 11626 -ZW5jaW5n 11627 -L1M= 11628 -IFN5bQ== 11629 -IG1hbnVhbA== 11630 -RG93bmxvYWQ= 11631 -ICghJA== 11632 -IG1vdGlvbg== 11633 -d2VicGFjaw== 11634 -LWJvdHRvbQ== 11635 -IGdyYXR1aXQ= 11636 -UEc= 11637 -KDos 11638 -IGVyYQ== 11639 -IGhv 11640 -IEppbQ== 11641 -cXVpcg== 11642 -IEJBU0lT 11643 -w6Fu 11644 -REVS 11645 -IGV4cGVuc2l2ZQ== 11646 -X2Nv 11647 -Qm91bmRz 11648 -V2VsbA== 11649 -IERlbW9jcmF0aWM= 11650 -IOKGkg== 11651 -LlJlbQ== 11652 -X1NZ 11653 -bmFtZXM= 11654 -IFZp 11655 -IGlzaW5zdGFuY2U= 11656 -XCI+ 11657 -ICo9 11658 -IFBT 11659 -IGRhbmdlcm91cw== 11660 -W3A= 11661 -T01F 11662 -T3RoZXI= 11663 -IFN0cmluZ0J1aWxkZXI= 11664 -UG9pbnRz 11665 -aGVhZGluZw== 11666 -IGN1cnJlbmN5 11667 -IHBlcmNlbnRhZ2U= 11668 -X0FQSQ== 11669 -IGNsYXNzaWM= 11670 -dGhlYWQ= 11671 -IE1P 11672 -RkU= 11673 -SWR4 11674 -YXdhaXQ= 11675 -IMOo 11676 -IGFjY2lkZW50 11677 -IHZhcmlhbnQ= 11678 -IG15c3Q= 11679 -IExhbmQ= 11680 -IEJyZQ== 11681 -IGhhcm0= 11682 -IEFjYw== 11683 -IGNoYXJnZWQ= 11684 -aW9uZXM= 11685 -VmlzaWJpbGl0eQ== 11686 -YXJyeQ== 11687 -IExhbmd1YWdl 11688 -IHdhbGtpbmc= 11689 -Ii4KCg== 11690 -aWZlcg== 11691 -IGxlYWRlcnNoaXA= 11692 -LkZyb20= 11693 -eW5hbQ== 11694 -IHRpbWVzdGFtcA== 11695 -aXB0 11696 -IEhhcw== 11697 -UkVGRVI= 11698 -IEl0cw== 11699 -IGxpc3RlbmVy 11700 -VVRF 11701 -MjEz 11702 -X2Rlc2NyaXB0aW9u 11703 -IGV4cGVyaWVuY2Vz 11704 -IGNyZWF0ZXM= 11705 -UlM= 11706 -Y2FydA== 11707 -YmxhY2s= 11708 -IGNob2ljZXM= 11709 -d2Fy 11710 -NzUw 11711 -ICcnJw== 11712 -IG9yZGVyZWQ= 11713 -IGV2ZW5pbmc= 11714 -IHBpbA== 11715 -IHR1bg== 11716 -IEJhZA== 11717 -KGFwcA== 11718 -cmFuZG9t 11719 -IGV4cGxpY2l0 11720 -IGFycml2ZWQ= 11721 -IGZseQ== 11722 -IGVjb25vbQ== 11723 -LW1haWw= 11724 -IGxpc3Rz 11725 -IGFyY2hpdGVjdA== 11726 -MjM0 11727 -IFBheQ== 11728 -IGRz 11729 -IFNvbA== 11730 -IHZlaGljbGVz 11731 -SHo= 11732 -LWNvbQ== 11733 -IGtpbmc= 11734 -X2VxdWFs 11735 -IEhlbHA= 11736 -IGFidXNl 11737 -NDgw 11738 -MTY5 11739 -LS07Cg== 11740 -IGV4dHI= 11741 -IGNoZW1pY2Fs 11742 -5L8= 11743 -IG9yaWVudA== 11744 -IGJyZWF0aA== 11745 -IFNwYWNl 11746 -KGVsZW1lbnQ= 11747 -d2FpdA== 11748 -REVE 11749 -aWdtYQ== 11750 -IGVudHI= 11751 -IHNvYg== 11752 -LW5hbWU= 11753 -IGFmZmVjdGVk 11754 -aWth 11755 -IGNvYWw= 11756 -X3dvcms= 11757 -IGh1bmRyZWRz 11758 -IHBvbGl0aWNz 11759 -c3ViamVjdA== 11760 -IGNvbnN1bWVy 11761 -QU5HRQ== 11762 -IHJlcGVhdGVk 11763 -U2VuZA== 11764 -ICNb 11765 -IHByb3RvY29s 11766 -IGxlYWRz 11767 -dXNldW0= 11768 -RXZlcnk= 11769 -ODA4 11770 -MTc0 11771 -SW1wb3J0 11772 -KGNvdW50 11773 -IGNoYWxsZW5nZXM= 11774 -IG5vdmVs 11775 -IGRlcGFydA== 11776 -Yml0cw== 11777 -LkN1cnJlbnQ= 11778 -IGAkew== 11779 -b3Rpbmc= 11780 -KFw= 11781 -IGNyZWF0aXZl 11782 -IGJ1ZmY= 11783 -IGludHJvZHVjZWQ= 11784 -dXNpYw== 11785 -bW9kdWxlcw== 11786 -QXJl 11787 -LWRvYw== 11788 -bGFuZ3VhZ2U= 11789 -X2NhY2hl 11790 -IHRvZA== 11791 -Pz48Lw== 11792 -b21ldGhpbmc= 11793 -IGh1bg== 11794 -5bo= 11795 -YXRlcnM= 11796 -SW50ZW50 11797 -IGltcGxlbWVudGVk 11798 -IENhc2U= 11799 -Q2hpbGRyZW4= 11800 -IG5vdGlmaWNhdGlvbg== 11801 -UmVuZGVyZXI= 11802 -V3JhcHBlcg== 11803 -T2JqZWN0cw== 11804 -dGw= 11805 -LkNvbnRhaW5z 11806 -UGx1Z2lu 11807 -LnJvdw== 11808 -IGZvcmc= 11809 -IHBlcm1pdA== 11810 -IHRhcmdldHM= 11811 -IElG 11812 -IHRpcA== 11813 -c2V4 11814 -IHN1cHBvcnRz 11815 -IGZvbGQ= 11816 -cGhvdG8= 11817 -fSwNCg== 11818 -IGdvb2dsZQ== 11819 -JCgnIw== 11820 -IHNoYXJpbmc= 11821 -IGdvb2Rz 11822 -dnM= 11823 -IERhbg== 11824 -UmF0ZQ== 11825 -IE1hcnRpbg== 11826 -IG1hbm5lcg== 11827 -bGll 11828 -LlRoZQ== 11829 -SW50ZXJuYWw= 11830 -IENPTlRS 11831 -TW9jaw== 11832 -UklHSFQ= 11833 -ICd7 11834 -IGNvbnRyb2xz 11835 -TWF0 11836 -IG1hbmQ= 11837 -IGV4dGVuZGVk 11838 -T2s= 11839 -IGVtYmVk 11840 -IHBsYW5ldA== 11841 -IE5vbg== 11842 -LWNo 11843 -KSIs 11844 -ZXBhcg== 11845 -IGJlbGlldmVk 11846 -IEVudmlyb25tZW50 11847 -IEZyaWVuZA== 11848 -LXJlcw== 11849 -IGhhbmRsaW5n 11850 -bmlj 11851 -LWxldmVs 11852 -c2NyaQ== 11853 -WG1s 11854 -QkU= 11855 -dW5nZW4= 11856 -IGFsdGVy 11857 -W2lkeA== 11858 -UG9w 11859 -Y2Ft 11860 -ICgoKA== 11861 -IHNoaXBwaW5n 11862 -IGJhdHRlcnk= 11863 -aWRkbGV3YXJl 11864 -TUM= 11865 -IGltcGw= 11866 -b3RhdGlvbg== 11867 -IExhYg== 11868 -PGZvcm0= 11869 -CW5hbWU= 11870 -IEdhbWVz 11871 -cmF5 11872 -RXh0cmE= 11873 -VHdv 11874 -KHBsYXllcg== 11875 -IExlcw== 11876 -wrA= 11877 -IGNoYXJzZXQ= 11878 -IGpvdXJuZXk= 11879 -ZXRpbmc= 11880 -5pg= 11881 -4pQ= 11882 -55So 11883 -IGRpbg== 11884 -IHBlcm1hbg== 11885 -IHNvbHZl 11886 -IGxhdW5jaGVk 11887 -IG5pbmU= 11888 -IHNlbmRpbmc= 11889 -IHRlbGxpbmc= 11890 -LnBhc3N3b3Jk 11891 -IE1hdHJpeA== 11892 -ZXJpYw== 11893 -IGdyYWI= 11894 -LnU= 11895 -IExpYnJhcnk= 11896 -IGRlYnQ= 11897 -SU5L 11898 -LmZpbmRWaWV3QnlJZA== 11899 -IGZyZXF1ZW5jeQ== 11900 -LmFk 11901 -X1RFU1Q= 11902 -IG5lZ290 11903 -IEFmcmljYW4= 11904 -c2VuZGVy 11905 -xaE= 11906 -R2xvYmFs 11907 -MTcz 11908 -IGV4cGVydHM= 11909 -KyspDQo= 11910 -IGRlcGVuZGluZw== 11911 -Z3JheQ== 11912 -IGp1ZGdl 11913 -IHNlbnRlbmNl 11914 -bG9zdXJl 11915 -QWM= 11916 -IHRyYWNl 11917 -RWRnZQ== 11918 -IGZyaWVuZGx5 11919 -IGNvbmNlcm5lZA== 11920 -YmxvZw== 11921 -IGNsYWltZWQ= 11922 -fSc= 11923 -aW50ZWdlcg== 11924 -X3RyZWU= 11925 -CWNvbnRpbnVl 11926 -eGk= 11927 -IGFjY2VwdGVk 11928 -X29uZQ== 11929 -IEVkdWNhdGlvbg== 11930 -dWJsaXNoZWQ= 11931 -Z29u 11932 -YXBwb2ludA== 11933 -b3V0cw== 11934 -IG1pbmluZw== 11935 -IHNvbmdz 11936 -IGhlcnNlbGY= 11937 -IGdyYW50ZWQ= 11938 -IHBhc3Npb24= 11939 -IExha2U= 11940 -IGxvYW4= 11941 -dWVudA== 11942 -Y2hhbnQ= 11943 -IGRldGFpbGVk 11944 -ZXhjZXB0 11945 -X2NtZA== 11946 -IEhF 11947 -UmVsYXRlZA== 11948 -enQ= 11949 -J30sCg== 11950 -IHNwZWNpZmljYWxseQ== 11951 -U3RhdGlj 11952 -IGNhcnJpZWQ= 11953 -QU5T 11954 -XCI6 11955 -Q3JlYXRlZA== 11956 -IGN1bA== 11957 -XS0= 11958 -X2FwaQ== 11959 -RlA= 11960 -IHNpdHRpbmc= 11961 -ICIiKQ== 11962 -CWdvdG8= 11963 -IEVxdQ== 11964 -IGFzc2F1bHQ= 11965 -a2lucw== 11966 -YW5jZXI= 11967 -b2dlbg== 11968 -IHZvdGVycw== 11969 -IFByb3Q= 11970 -RGVzY3JpcHRvcg== 11971 -44O8 11972 -LkFzc2VydA== 11973 -YnNpdGVz 11974 -b3N0ZXI= 11975 -LW1lbnU= 11976 -IGFybXM= 11977 -LkNsaWVudA== 11978 -LmJhY2tncm91bmQ= 11979 -YXZpdHk= 11980 -IHZ1bA== 11981 -X01BU0s= 11982 -IGhvdXNpbmc= 11983 -IGJlYXI= 11984 -X2l0ZXI= 11985 -cGlyZWQ= 11986 -IG1hcmtldHM= 11987 -IFN0dWRlbnQ= 11988 -IHRpY2tldA== 11989 -IG1pbGxpb25z 11990 -ZmxhdGVy 11991 -KT0= 11992 -IHJlY292ZXI= 11993 -IEZvcmNl 11994 -IEJvdGg= 11995 -IHZpY3RpbQ== 11996 -IERpc2M= 11997 -cmVwb3J0 11998 -IGZvdXJ0aA== 11999 -IEFzc2VtYmx5 12000 -L3VzZXI= 12001 -TnVsbE9y 12002 -dGV4dGFyZWE= 12003 -IGF0aA== 12004 -IChb 12005 -IGNoYW5uZWxz 12006 -IEp1c3RpY2U= 12007 -Y2hvaWNl 12008 -TE9CQUw= 12009 -ZXhlYw== 12010 -ZW1hbGU= 12011 -IGVsZW0= 12012 -X2xl 12013 -IHJlc3BvbnNpYmlsaXR5 12014 -IFR3 12015 -SUNBVElPTg== 12016 -IGVsc2VpZg== 12017 -IGZv 12018 -YXN0cw== 12019 -IHRyZWF0ZWQ= 12020 -c2Vu 12021 -IFZpY3Q= 12022 -c3VtZXI= 12023 -X0JBU0U= 12024 -IGFzdA== 12025 -Pnt7 12026 -IFJlc291cmNl 12027 -IFN0YW5kYXJk 12028 -IFByZW0= 12029 -dXBkYXRlZA== 12030 -aXZhbGVudA== 12031 -IGFzc2V0cw== 12032 -X3RlbXA= 12033 -IGludGVyZXN0cw== 12034 -IGhhcmR3YXJl 12035 -IFJvbQ== 12036 -IFNoYXJl 12037 -ICcnCg== 12038 -ICos 12039 -IFRha2U= 12040 -IEltYWdlcw== 12041 -X0NIRUNL 12042 -KHR5cGVvZg== 12043 -IEp1bg== 12044 -XDxe 12045 -IGxpcXU= 12046 -IHdvcnN0 12047 -eW1ib2xz 12048 -CQkJICAg 12049 -IGRyaXZlcnM= 12050 -IERvY3VtZW50 12051 -ZW5v 12052 -IFRlY2hub2xvZ3k= 12053 -IGFwcHJvdmVk 12054 -dW1wcw== 12055 -IHNub3c= 12056 -Zm9ybWFuY2U= 12057 -X0FTU0VSVA== 12058 -dWl0cw== 12059 -MjA3 12060 -2YY= 12061 -IGRpZmZlcmVuY2Vz 12062 -LlZpc2libGU= 12063 -CQkJDQo= 12064 -IFBz 12065 -X2ZldGNo 12066 -IHRvZG8= 12067 -LicsCg== 12068 -IHNlbA== 12069 -dXJlcnM= 12070 -aW52YWxpZA== 12071 -IHR3ZWV0 12072 -VkVM 12073 -IHJlc2VhcmNoZXJz 12074 -IHNwcmludGY= 12075 -IFJP 12076 -IHBlbA== 12077 -LlRyYW5z 12078 -IGlsbGVnYWw= 12079 -ZGlhbG9n 12080 -c21hcnR5 12081 -bGc= 12082 -X01JTg== 12083 -IGhlcm8= 12084 -ZmluYWw= 12085 -IHBw 12086 -Lkxl 12087 -IGNp 12088 -CVJU 12089 -IHN1Z2dlc3RlZA== 12090 -cGRm 12091 -YWNoaW5n 12092 -IFJv 12093 -IFByb3BlcnRpZXM= 12094 -IFNp 12095 -IGJ1eWluZw== 12096 -IG11 12097 -IGxhbmRz 12098 -aWZpZXJz 12099 -IEZJTEU= 12100 -Uk9VUA== 12101 -IGhvbGRlcg== 12102 -IFNvbg== 12103 -IHN5bXB0 12104 -LnJvdXRl 12105 -KT8= 12106 -IGFyZ2M= 12107 -IGZvcnQ= 12108 -IGNhc2lubw== 12109 -X2NhdGVnb3J5 12110 -IGZvcnVt 12111 -MjE1 12112 -cHJlZml4 12113 -YXB0dXJl 12114 -VHViZQ== 12115 -ZW1z 12116 -aW1pemU= 12117 -IG51ZQ== 12118 -YXVz 12119 -Y291cnNl 12120 -QVRPUg== 12121 -KCkpLA== 12122 -QWR2ZXJ0aXM= 12123 -SU5HUw== 12124 -IGFja25vdw== 12125 -IEtvcmVh 12126 -cGxpbmc= 12127 -IHdvcmtlcg== 12128 -UExJRUQ= 12129 -aGFs 12130 -IFJpY2hhcmQ= 12131 -RWxlbWVudHM= 12132 -CQkJIA== 12133 -c3Rhcg== 12134 -IHJlbGF0aW9uc2hpcHM= 12135 -IGNoZWFw 12136 -QUNI 12137 -IFhNTA== 12138 -LCY= 12139 -IExvdWlz 12140 -IHJpZGU= 12141 -X0ZBSUw= 12142 -IGNodW5r 12143 -W3M= 12144 -X09VVA== 12145 -IGNob3Nlbg== 12146 -X1s= 12147 -Lyg= 12148 -IEplZmY= 12149 -X3Ns 12150 -cHJpdg== 12151 -IENhbmFkaWFu 12152 -IHVuYWJsZQ== 12153 -X0ZMQUc= 12154 -IG5vcw== 12155 -aGlnaA== 12156 -IGxpZnQ= 12157 -ZnVu 12158 -KCl7 12159 -ZWxseQ== 12160 -eWNsZXJWaWV3 12161 -X2Fz 12162 -X0xJU1Q= 12163 -IHJhZGk= 12164 -LmdldFZhbHVl 12165 -MzA0 12166 -IEFuZ2VsZXM= 12167 -IFNwYW4= 12168 -X2luc3RhbmNl 12169 -aXRvcnM= 12170 -MjA4 12171 -IG1pZ3JhdGlvbg== 12172 -QUs= 12173 -T2g= 12174 -wq4= 12175 -LnNlbGVjdGVk 12176 -IEdU 12177 -IGFkdmFuY2U= 12178 -IFN0eWxl 12179 -LkRhdGFHcmlkVmlldw== 12180 -ZWN0aW9u 12181 -0Y4= 12182 -cGlv 12183 -cm9n 12184 -IHNob3BwaW5n 12185 -IFJlY3Q= 12186 -SWxsdW1pbmF0ZQ== 12187 -T1U= 12188 -CWFycmF5 12189 -IHN1YnN0YW50aWFs 12190 -IHByZWdu 12191 -IHByb21vdGU= 12192 -SUVX 12193 -LkxheW91dA== 12194 -IHNpZ25z 12195 -Ly4= 12196 -IGxldHRlcnM= 12197 -Qm9hcmQ= 12198 -Y3RybA== 12199 -Ilw= 12200 -IEpvbmVz 12201 -IHZlcnRleA== 12202 -IGph 12203 -IGFmZmlsaQ== 12204 -IHdlYWx0aA== 12205 -CWRlZmF1bHQ= 12206 -IHNpZ25pZmljYW50bHk= 12207 -IGVj 12208 -IHhz 12209 -YWN0dWFs 12210 -LnBlcg== 12211 -X3N0ZXA= 12212 -YW52YXM= 12213 -bWFj 12214 -IHRyYW5zbA== 12215 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 12216 -SXRlcmF0b3I= 12217 -IG9jaA== 12218 -YWdub3N0aWM= 12219 -IER1cmluZw== 12220 -IERFRkFVTFQ= 12221 -IHRpbGw= 12222 -IHNpZ25hdHVyZQ== 12223 -IGJpcmQ= 12224 -IE9s 12225 -MzEw 12226 -IEly 12227 -SFM= 12228 -YXZhdGFy 12229 -RVNTQUdF 12230 -IGVsZXY= 12231 -IG10 12232 -IE5hdg== 12233 -IHJlbGF4 12234 -IHBsYXRl 12235 -SVRFTQ== 12236 -KGRhdGU= 12237 -Lm5vdA== 12238 -IGdyYWRl 12239 -IH0pLAo= 12240 -PyIKCg== 12241 -aWVuY2Vz 12242 -SGlnaA== 12243 -IERJUw== 12244 -MjMx 12245 -ZGlzYWJsZWQ= 12246 -UVVJ 12247 -IG5vaXNl 12248 -YXV4 12249 -IFVQ 12250 -ODg4 12251 -b3Nh 12252 -IHZvYw== 12253 -ICkp 12254 -b2NvbQ== 12255 -X09GRg== 12256 -IERi 12257 -TG9jaw== 12258 -LmVjbGlwc2U= 12259 -LGQ= 12260 -IERyYXc= 12261 -ICIo 12262 -IHZpc2l0ZWQ= 12263 -IOKI 12264 -IHN1Y2NlZWQ= 12265 -IGltcG9zc2libGU= 12266 -YWlyZQ== 12267 -IFR1cm4= 12268 -IGRpc2g= 12269 -Rkc= 12270 -IHNlbnNvcg== 12271 -QU5O 12272 -YWJh 12273 -IHN1cmc= 12274 -XSk7DQo= 12275 -IGZw 12276 -X2Fu 12277 -LUo= 12278 -LUc= 12279 -IEpvYg== 12280 -Q29udmVydA== 12281 -IEtFWQ== 12282 -IGF1dGhvcnM= 12283 -X3NlcnZlcg== 12284 -XHI= 12285 -IC0qLQ== 12286 -ZmxleA== 12287 -IHNvYw== 12288 -UmV0 12289 -IHNhbHQ= 12290 -IOKApgoK 12291 -IENsZWFy 12292 -KHBhZ2U= 12293 -LWRhbmdlcg== 12294 -IHJvb21z 12295 -Y29udg== 12296 -I3s= 12297 -Lm9w 12298 -IEFyZWE= 12299 -X1ND 12300 -aGVu 12301 -IGJlZ2lucw== 12302 -LXk= 12303 -IGV4Y2l0ZWQ= 12304 -IGlnbm9yZWQ= 12305 -IGJvbnVz 12306 -c3R1ZGVudA== 12307 -IE1lbWJlcg== 12308 -IHJlbGF0aXZlbHk= 12309 -IExvdw== 12310 -IFByb2R1 12311 -YXRld2F5 12312 -cG9zdXJl 12313 -IHRoaWNr 12314 -YW5pZWw= 12315 -KHZpZXc= 12316 -IENydXNo 12317 -RXh0ZW5zaW9u 12318 -SWw= 12319 -ZWVk 12320 -TE9D 12321 -Lmlt 12322 -Lkl0ZW1z 12323 -IGNvbmZsaWN0 12324 -LnByZXZlbnQ= 12325 -MjUy 12326 -IG9uQ3JlYXRl 12327 -dXY= 12328 -aXNlcg== 12329 -IHdhdmU= 12330 -TWFy 12331 -IENvbW11bml0eQ== 12332 -aWNoZQ== 12333 -IE5vdGhpbmc= 12334 -W20= 12335 -IExlZQ== 12336 -cmllbmRz 12337 -MjMy 12338 -w6hyZQ== 12339 -ISEh 12340 -YW56 12341 -LnJlc3VsdA== 12342 -IFNL 12343 -X1BBUkFN 12344 -IGRlbW9jcg== 12345 -QmFja0NvbG9y 12346 -LmV4aXN0cw== 12347 -Ikl0 12348 -KG9wdGlvbnM= 12349 -cmF6eQ== 12350 -YXNlcg== 12351 -XERhdGFiYXNl 12352 -YWxlbmRhcg== 12353 -X2Fzcw== 12354 -O30K 12355 -dmVydGV4 12356 -aW5lY3JhZnQ= 12357 -V2FybmluZw== 12358 -YXJnbw== 12359 -IGFjdG9y 12360 -IEluc3RlYWQ= 12361 -IFVzaW5n 12362 -U2VsZg== 12363 -QGludGVyZmFjZQ== 12364 -IHNwZWFraW5n 12365 -IFBhcmlz 12366 -IExJQ0VOU0U= 12367 -Lm5vZGU= 12368 -IEZvb2Q= 12369 -RUlG 12370 -IEJp 12371 -LlN0YXJ0 12372 -IElC 12373 -IHVuaXZlcnNpdHk= 12374 -MjU0 12375 -IEhlYWRlcg== 12376 -LnByb2R1Y3Q= 12377 -NDA5 12378 -Q29weQ== 12379 -ZXRj 12380 -cmljYWw= 12381 -ID4+Pg== 12382 -Ym9va3M= 12383 -IGFsZ29yaXRobQ== 12384 -ICdfXw== 12385 -KGphdmF4 12386 -IG51bWVyb3Vz 12387 -U2hhcmU= 12388 -SGF2ZQ== 12389 -IHJlY3J1 12390 -IHByb3Zl 12391 -LnN1YnN0cmluZw== 12392 -aGVhbHRo 12393 -0LXQuw== 12394 -IGRlY2ltYWw= 12395 -IGNvbW1pc3Npb24= 12396 -c2NyaXB0aW9u 12397 -eEM= 12398 -IHN1bW1hcnk= 12399 -YXR0ZWQ= 12400 -IGNsb3Nlcg== 12401 -ZmluaXNoZWQ= 12402 -KCkpewo= 12403 -IFdvb2Q= 12404 -MzAx 12405 -X2ZpZWxkcw== 12406 -a3U= 12407 -X2l0ZW1z 12408 -RmxhZw== 12409 -IGNvbmZpZGVuY2U= 12410 -IEZlZGVyYWw= 12411 -ZHV4 12412 -IGNvbXBhdA== 12413 -IHZlcnRpY2Fs 12414 -0Lk= 12415 -w6hz 12416 -OyI+Cg== 12417 -X21hbmFnZXI= 12418 -KCkpKQo= 12419 -SURF 12420 -OiIs 12421 -MjM1 12422 -X18K 12423 -IFdheQ== 12424 -MjIx 12425 -0Yg= 12426 -VGVtcA== 12427 -IFNUUg== 12428 -cml0dGVu 12429 -U3luYw== 12430 -IEFW 12431 -IENFTw== 12432 -IEd1aWQ= 12433 -IGVudmlyb25tZW50YWw= 12434 -IGNvcnJlc3BvbmRpbmc= 12435 -CWNvbnNvbGU= 12436 -IGp1c3RpY2U= 12437 -IEpT 12438 -IGxpdmVk 12439 -Z2Fy 12440 -IEdyYXBo 12441 -IFN0YXQ= 12442 -IGlQaG9uZQ== 12443 -LmFs 12444 -IEhE 12445 -IG9jY3Vy 12446 -IHRocmVzaG9sZA== 12447 -NTA5 12448 -IG9uY2xpY2s= 12449 -UkVH 12450 -LkdyYXBoaWNzVW5pdA== 12451 -TWV0YQ== 12452 -xb4= 12453 -IGN1bQ== 12454 -LmdudQ== 12455 -w6s= 12456 -IG9idGFpbmVk 12457 -IGNvbXBsYWludA== 12458 -IGVhdGluZw== 12459 -IHRhcg== 12460 -X3Rhc2s= 12461 -IG9wdHM= 12462 -MjE2 12463 -KHRv 12464 -UGFzcw== 12465 -IHBsYXN0aWM= 12466 -dGlsaXR5 12467 -IFdpbg== 12468 -LnByZXZlbnREZWZhdWx0 12469 -cGlsZQ== 12470 -IEdhcg== 12471 -IHF1YW50aXR5 12472 -X2xhc3Q= 12473 -IGdyZWF0ZXN0 12474 -RGFv 12475 -X0RJUw== 12476 -IFVzZWQ= 12477 -IEhQ 12478 -cml0aW5n 12479 -U0lPTg== 12480 -Ymx1ZQ== 12481 -ZG9tYWlu 12482 -IHNjb3Jlcw== 12483 -Tm9ybWFs 12484 -X2FkbWlu 12485 -IEFTU0VSVA== 12486 -VGhlbg== 12487 -Kioq 12488 -ZGlzdA== 12489 -bG9u 12490 -IGhhdGU= 12491 -c2hhbA== 12492 -SW1hZ2VWaWV3 12493 -ZGF0YWJhc2U= 12494 -IHBhbmQ= 12495 -IGxvZ2lj 12496 -PWZhbHNl 12497 -Ymc= 12498 -IENvbmZpZ3VyYXRpb24= 12499 -IG51cg== 12500 -T0c= 12501 -IG1hcnJpZWQ= 12502 -Ois= 12503 -IGRyb3BwZWQ= 12504 -MDQw 12505 -IHJlZ2lzdHJhdGlvbg== 12506 -0L7QvA== 12507 -dWx0aXBsZQ== 12508 -aXplcnM= 12509 -c2hhcGU= 12510 -LmNvcHk= 12511 -IHdlYXJpbmc= 12512 -IENhdGg= 12513 -IGRlZGljYXRlZA== 12514 -IC4uLgo= 12515 -IGFkdm9j 12516 -IEZhbWlseQ== 12517 -IHN0YXRlbWVudHM= 12518 -ZW1hdGlj 12519 -YW1waW9uc2hpcA== 12520 -IG1vdGl2 12521 -IEhhdmU= 12522 -IGJsb3c= 12523 -Sm9i 12524 -Y2VydA== 12525 -X3ZlY3Rvcg== 12526 -aW5zdGFsbA== 12527 -IENPUFk= 12528 -ZW1iZWQ= 12529 -RElS 12530 -IFNwcmluZw== 12531 -IGV4aGli 12532 -MjIz 12533 -Y2Ru 12534 -IENvbW1lbnQ= 12535 -IE9wdGlvbmFs 12536 -LnBsYXllcg== 12537 -IERhcms= 12538 -KHBvcw== 12539 -IFNob3VsZA== 12540 -IGNlbnRyZQ== 12541 -IEd1YXJk 12542 -w7N3 12543 -IHRyb3VibGU= 12544 -RU5FUg== 12545 -KHVuc2lnbmVk 12546 -X3NlcnZpY2U= 12547 -IG5z 12548 -dWxpbmc= 12549 -IE1leGljbw== 12550 -IE5Z 12551 -bXlzcWw= 12552 -IGxpYw== 12553 -5Zw= 12554 -TXI= 12555 -LWZs 12556 -IEN1c3RvbWVy 12557 -aWRp 12558 -ID8+Cgo= 12559 -cmlibGU= 12560 -INC/0YA= 12561 -IHNpemVz 12562 -X1NUUklORw== 12563 -dmFsaWRhdGlvbg== 12564 -IEpvbg== 12565 -KEh0dHA= 12566 -YWRkQ2xhc3M= 12567 -Tm9kZXM= 12568 -IGZyYWdtZW50 12569 -IHNwb2tl 12570 -IHdhc3Rl 12571 -Sm9pbg== 12572 -IGlsbHVzdHI= 12573 -ZWxp 12574 -Y2llbnQ= 12575 -IGFpZA== 12576 -IHByb3NlYw== 12577 -Jyl7Cg== 12578 -IHBhc3Npbmc= 12579 -IGZhY2Vz 12580 -U2hhcGU= 12581 -X1o= 12582 -aXRp 12583 -IGFsbGU= 12584 -IHJvYm90 12585 -ICAgICAgIAo= 12586 -IFNwZQ== 12587 -IHJlY2VpdmluZw== 12588 -IERldGFpbHM= 12589 -ICIp 12590 -bWc= 12591 -X1JFRg== 12592 -IGNvbXBhcmlzb24= 12593 -Kiw= 12594 -IEZvdW5k 12595 -X3Nlc3Npb24= 12596 -KFU= 12597 -L0Y= 12598 -IHh4eA== 12599 -TmV0d29yaw== 12600 -ZGVycw== 12601 -IGNhcHR1cmU= 12602 -IGNvcnJl 12603 -IEx0ZA== 12604 -IEFkdg== 12605 -W0A= 12606 -IGNsaXA= 12607 -TWlsbA== 12608 -IFByb2ZpbGU= 12609 -IGVuZGlm 12610 -IG9ibGln 12611 -ZGVzY3JpYmU= 12612 -LmVsZW1lbnQ= 12613 -cml0ZXJpb24= 12614 -TEQ= 12615 -ZXJlZA== 12616 -IGZhdm91cg== 12617 -c2NvcmU= 12618 -IEZpbHRlcg== 12619 -YXR0cmlidXRlcw== 12620 -IGNoZWNrcw== 12621 -SW5mbGF0ZXI= 12622 -IFBsdXM= 12623 -IHNjaWVudGlmaWM= 12624 -IHByaXZhY3k= 12625 -SGVhZA== 12626 -IGZlYXQ= 12627 -IGRlZ3JlZXM= 12628 -IFBhbGU= 12629 -OyI+ 12630 -IGZpbG1z 12631 -IEF1ZGlv 12632 -IFRhZw== 12633 -IEVuZXJneQ== 12634 -aXRhcg== 12635 -cGFyYXRvcg== 12636 -IGZlbGxvdw== 12637 -IGV2dA== 12638 -IFRyaQ== 12639 -IERBTQ== 12640 -Y2xvdWQ= 12641 -IFBhc3N3b3Jk 12642 -IERlbW9jcmF0cw== 12643 -IEFjYWQ= 12644 -JGxhbmc= 12645 -IHJlYg== 12646 -KCkpCgo= 12647 -0L3Riw== 12648 -IEJ1cg== 12649 -cmVhZGNy 12650 -IGhleA== 12651 -MjA5 12652 -Q29uc29sZQ== 12653 -Y3Rs 12654 -b3VzZWw= 12655 -IFdpbGxpYW0= 12656 -IGF6 12657 -X1BPUlQ= 12658 -IHByYWN0aWNlcw== 12659 -IGFueXdoZXJl 12660 -IFBvc2l0aW9u 12661 -IC0+Cg== 12662 -aWFtcw== 12663 -LnVzZXJuYW1l 12664 -cGxhY2Vob2xkZXI= 12665 -IG9kZXI= 12666 -IFNlY3JldGFyeQ== 12667 -IGlU 12668 -bW9uZA== 12669 -ZXZlbnRz 12670 -P+KAnQ== 12671 -LlN1Yg== 12672 -IGF0dGFjaGVk 12673 -IG7Do28= 12674 -IGVzdGF0ZQ== 12675 -MzY1 12676 -LmFjdGlvbg== 12677 -IGZpZ3VyZXM= 12678 -IH0pOw0K 12679 -IHN1YnNjcmk= 12680 -LnRhZw== 12681 -bmFt 12682 -LnBsb3Q= 12683 -bm9vbg== 12684 -bGlhbWVudA== 12685 -Q2hhcmFjdGVy 12686 -LnRhYg== 12687 -IHdpbnRlcg== 12688 -IFZhcmlhYmxl 12689 -IHRyZWVz 12690 -IHByb3Vk 12691 -KFY= 12692 -X2xvYWQ= 12693 -IGhpZXI= 12694 -IEVjb24= 12695 -IGZk 12696 -IHZpY3RpbXM= 12697 -UmVzdA== 12698 -aWFuYQ== 12699 -IGZha2U= 12700 -LlByaW50bG4= 12701 -IHN0cmxlbg== 12702 -IHNhZA== 12703 -IGJsZQ== 12704 -UHJvdA== 12705 -IGJ1dHRvbnM= 12706 -IHRlbGV2aXNpb24= 12707 -IGxvZ28= 12708 -ZXh0ZW5zaW9u 12709 -CWo= 12710 -c3RlaW4= 12711 -YWNpb25lcw== 12712 -ICIiIgoK 12713 -IHNpbXA= 12714 -IHJlY29yZGVk 12715 -IGJyaW5ncw== 12716 -IHByaW5jaXBhbA== 12717 -IGZlZXM= 12718 -KHNvdXJjZQ== 12719 -a2Rpcg== 12720 -IHV0aWxz 12721 -IGNvcnJlY3RseQ== 12722 -Zmls 12723 -IHdlbA== 12724 -UGFpcg== 12725 -LWJ1dHRvbg== 12726 -c2NhbGU= 12727 -dmVyaWZ5 12728 -W2M= 12729 -IC0tLQ== 12730 -IGVzY2FwZQ== 12731 -aWtlcw== 12732 -TG93ZXJDYXNl 12733 -aWNpYW4= 12734 -IGNoYXB0ZXI= 12735 -IFRZUEU= 12736 -IHNoYWRvdw== 12737 -IGF3ZXNvbWU= 12738 -V0U= 12739 -ZWxpZg== 12740 -IGxhbWJkYQ== 12741 -IGRpc3RpbmN0 12742 -IGJhcmU= 12743 -LW9mZg== 12744 -IGNvbG91cg== 12745 -LmFwcGVuZENoaWxk 12746 -b2xlYw== 12747 -YWdh 12748 -LmZpbGw= 12749 -CXN1cGVy 12750 -IGFkag== 12751 -KHBvc2l0aW9u 12752 -LmdldEl0ZW0= 12753 -MjQy 12754 -U2hvcnQ= 12755 -IHRvdGFsbHk= 12756 -VkQ= 12757 -IFRyZQ== 12758 -X2Vw 12759 -dmVtZW50cw== 12760 -IFNvbHV0aW9u 12761 -IGZ1bmRhbWVudA== 12762 -Rm9sbG93 12763 -IGZhY2lsaXR5 12764 -IGhhcHBlbmluZw== 12765 -T0Y= 12766 -LnRleHRCb3g= 12767 -U3Bhbg== 12768 -IMKr 12769 -aWRlbg== 12770 -IGV4Y2VlZA== 12771 -KHBhcmVudA== 12772 -IGNw 12773 -57s= 12774 -IGhhc24= 12775 -IHByaQ== 12776 -IGNvbnNlcXU= 12777 -bmVu 12778 -IElOVE8= 12779 -SWdub3Jl 12780 -IEZ1dHVyZQ== 12781 -IGNhcmJvbg== 12782 -IFN0ZWVs 12783 -Zm10 12784 -b2tpZQ== 12785 -IHNwbA== 12786 -KHRpdGxl 12787 -LWluZm8= 12788 -IGRlYWxz 12789 -IGZpeHR1cmU= 12790 -ZWE= 12791 -RGl2 12792 -IHRlc3RlZA== 12793 -X3JldHVybg== 12794 -KQoKCgo= 12795 -dXBwb3J0ZWQ= 12796 -IENvb2s= 12797 -IHBheWluZw== 12798 -IElsbA== 12799 -IGFycmVzdGVk 12800 -IFByaW1l 12801 -X2NhbGxiYWNr 12802 -PiwK 12803 -ZHJpdmVy 12804 -T25jZQ== 12805 -YWJi 12806 -X2J5dGVz 12807 -IFNldHM= 12808 -KE9iamVjdA== 12809 -IGNj 12810 -IHNoZWxs 12811 -YWxv 12812 -KTsvLw== 12813 -KGxvZw== 12814 -MjY0 12815 -Y3RvcnM= 12816 -KTwv 12817 -IG5laWdoYm9yaG9vZA== 12818 -NDIw 12819 -YWlsYWJpbGl0eQ== 12820 -dm9s 12821 -IHlvdXRo 12822 -IHRlY2huaXF1ZXM= 12823 -IFNjaGVtYQ== 12824 -dWg= 12825 -bWVudGU= 12826 -IHJlcG9zaXRvcnk= 12827 -aW1t 12828 -IGNvb2tpZQ== 12829 -SlM= 12830 -b3ZpZXM= 12831 -Ons= 12832 -Q29tcGxldGU= 12833 -U2luY2U= 12834 -IGxhdWdo 12835 -X0JP 12836 -ZW5hYmxl 12837 -IERvZXM= 12838 -IFdhbGs= 12839 -d2hhdA== 12840 -a2Vz 12841 -IG11bHRpcA== 12842 -aW1lbnRz 12843 -ZXVy 12844 -IHZpY3Rvcnk= 12845 -R2VuZXJhdG9y 12846 -IE1vcw== 12847 -cm92ZXJz 12848 -IGNvbXB1dGU= 12849 -IHByb3ZpZGVycw== 12850 -IE1lZGlj 12851 -TFA= 12852 -X0NPTkZJRw== 12853 -IHZldGVy 12854 -c3RlcnM= 12855 -X3dpbmRvdw== 12856 -dW1lcmlj 12857 -CQkJCQkK 12858 -LlJlc3BvbnNl 12859 -IHJlcGxhY2Vk 12860 -LnJvb3Q= 12861 -LWZyZWU= 12862 -LWNvbnRhaW5lcg== 12863 -IG1hdGNoaW5n 12864 -IEVkaXRvcg== 12865 -PSR7 12866 -IFNhZg== 12867 -IHNpbmQ= 12868 -KGJ1ZmZlcg== 12869 -5Yc= 12870 -LmVkdQ== 12871 -KV07Cg== 12872 -IE5GTA== 12873 -YXlh 12874 -IGRvZ3M= 12875 -IGRlc2lyZQ== 12876 -IE1pZGRsZQ== 12877 -Q2FydA== 12878 -MzA2 12879 -VGhlbWU= 12880 -IG1vYg== 12881 -IGRpc3BsYXllZA== 12882 -aWdpdA== 12883 -IGFkdWx0cw== 12884 -IiIi 12885 -IGRlbGl2ZXJlZA== 12886 -dmlzaWJsZQ== 12887 -Ijp7Cg== 12888 -PDw8 12889 -IEdP 12890 -c2Nyb2xs 12891 -eEU= 12892 -IGFzc2lnbmVk 12893 -IEJvb2w= 12894 -IHdw 12895 -IGNvbWJhdA== 12896 -IEhhdw== 12897 -Li0= 12898 -IHN1cHBvcnRpbmc= 12899 -LkNvbnRlbnQ= 12900 -MzQ1 12901 -aXJjcmFmdA== 12902 -IHNwaW4= 12903 -IENS 12904 -Lm15 12905 -4KU= 12906 -dHBs 12907 -IHNwYWNlcw== 12908 -Pyw= 12909 -Mzg0 12910 -IFN5cmlh 12911 -IHBhdHRlcm5z 12912 -LWJveA== 12913 -IGZyYW1ld29yaw== 12914 -LyU= 12915 -KGxvbmc= 12916 -IHRlYWNoaW5n 12917 -QVJOSU5H 12918 -X2tleXM= 12919 -IHRhYmxlcw== 12920 -VU5D 12921 -aW5hdGlvbnM= 12922 -LXdlaWdodA== 12923 -cmFkaW8= 12924 -IFBhYw== 12925 -LnNlcnZlcg== 12926 -LkNoYXJGaWVsZA== 12927 -cmluZw== 12928 -IHF1b3Rl 12929 -YW5uYQ== 12930 -IHdlcmRlbg== 12931 -IGNyZWFt 12932 -IG1hY2hpbmVz 12933 -LWs= 12934 -Mzc1 12935 -IHN0aW0= 12936 -IFN0b2Nr 12937 -cmljaw== 12938 -IGltcG9ydGFuY2U= 12939 -cng= 12940 -w7Vlcw== 12941 -2Yg= 12942 -IHN0cm9rZQ== 12943 -YWdyYQ== 12944 -IHRhc3Rl 12945 -IERFQlVH 12946 -VGhhbmtz 12947 -IFJlcXVpcmVk 12948 -b3Zh 12949 -TWVkaWE= 12950 -IHNpxJk= 12951 -KGJhc2U= 12952 -cG9zdHM= 12953 -IGZpbGVOYW1l 12954 -Q2hlY2tlZA== 12955 -IGludGVycnVwdA== 12956 -ICgpCg== 12957 -cHl0aG9u 12958 -cGFpcg== 12959 -IGNpcmNsZQ== 12960 -IGluaXRp 12961 -X3N0cmVhbQ== 12962 -IGNvbXByZWg= 12963 -bGVhcm4= 12964 -UHVibGlj 12965 -IGh1bWFucw== 12966 -IGJyaW5naW5n 12967 -b2dyYXBoaWM= 12968 -X2xheWVy 12969 -LWxpa2U= 12970 -dXBwb3J0SW5pdGlhbGl6ZQ== 12971 -aWRlYmFy 12972 -IHZvdGVz 12973 -IGRlc2lyZWQ= 12974 -TWFzaw== 12975 -IHJlbGF0aW9u 12976 -Lkluc3RhbmNl 12977 -SGVscA== 12978 -IGluc3Bpcg== 12979 -IE1vbm8= 12980 -Vmlld01vZGVs 12981 -b21ldGltZXM= 12982 -IGJhY2tncm91bmRDb2xvcg== 12983 -IHJvdGF0aW9u 12984 -IG1hcmk= 12985 -L3Rlc3Q= 12986 -SU5TRVJU 12987 -U3Rhcg== 12988 -cGh5 12989 -SWRz 12990 -X0dFVA== 12991 -IGluY3JlYXNlcw== 12992 -X2Nsb3Nl 12993 -MjMz 12994 -X0ZPUk0= 12995 -IFvigKZdCgo= 12996 -YXph 12997 -VEVYVA== 12998 -IMOk 12999 -IFZhbg== 13000 -IGxpZ2h0cw== 13001 -IEd1aWRl 13002 -IGRhdGVz 13003 -LkNvbW1hbmQ= 13004 -YW1hbg== 13005 -IHBhdGhz 13006 -LmVkaXQ= 13007 -CWFkZA== 13008 -ZHg= 13009 -IHJlYWN0aW9u 13010 -IEJlYWNo 13011 -LmdldE1lc3NhZ2U= 13012 -RW52aXJvbm1lbnQ= 13013 -aW50ZXJlc3Q= 13014 -IG1pbmlzdGVy 13015 -IHJlYWRlcnM= 13016 -CUY= 13017 -IGRvbWVzdGlj 13018 -IGZpbGVk 13019 -Q2l0eQ== 13020 -IG1hcHBpbmc= 13021 -IERFUw== 13022 -IHJlcGFpcg== 13023 -dGljcw== 13024 -aXh0dXJl 13025 -IG5vbWJyZQ== 13026 -LklTdXBwb3J0SW5pdGlhbGl6ZQ== 13027 -em8= 13028 -LklzTnVsbE9y 13029 -IENhcm9saW5h 13030 -IERlcg== 13031 -IEVWRU5U 13032 -IGdlc3Q= 13033 -IGhpc3Q= 13034 -cmVzb3VyY2Vz 13035 -IG9ycGhhbg== 13036 -LkFyZQ== 13037 -IEludmVzdA== 13038 -UkVGRVJSRUQ= 13039 -LkxvZ2dlcg== 13040 -IFJvbWFu 13041 -IGN1bHR1cmFs 13042 -ZmVhdHVyZQ== 13043 -cHRz 13044 -YnQ= 13045 -IGRvdA== 13046 -IGRpYW0= 13047 -dXNwZW5k 13048 -X2FjY2Vzcw== 13049 -KCl7DQo= 13050 -IHN1cnByaXNl 13051 -YWJpbA== 13052 -IHZpcnQ= 13053 -IGJvbWI= 13054 -YXJvbg== 13055 -X0lT 13056 -IHZhc3Q= 13057 -UmVhbA== 13058 -ZXBlbmQ= 13059 -aWN0ZWQ= 13060 -IHBpY2tlZA== 13061 -IEZM 13062 -IFJlcHVibGljYW5z 13063 -Lnplcm9z 13064 -UHJlc3NlZA== 13065 -c3Vw 13066 -LkNvcmU= 13067 -TWljcm9zb2Z0 13068 -c2VydmljZXM= 13069 -YWdpYw== 13070 -aXZlbmVzcw== 13071 -IHBkZg== 13072 -IHJvbGVz 13073 -NDAz 13074 -cmFz 13075 -IGluZHVzdHJpYWw= 13076 -IGZhY2lsaXRpZXM= 13077 -MjQ1 13078 -6KE= 13079 -IG5p 13080 -IGJh 13081 -IGNscw== 13082 -CUI= 13083 -Q3VzdG9tZXI= 13084 -IGltYWdpbmU= 13085 -IGV4cG9ydHM= 13086 -T3V0cHV0U3RyZWFt 13087 -IG1hZA== 13088 -KGRl 13089 -KXsKCg== 13090 -IGZybw== 13091 -aHVz 13092 -IGNvbW1pdHRlZQ== 13093 -7J20 13094 -LHg= 13095 -IGRpdmlzaW9u 13096 -KGNsaWVudA== 13097 -KGphdmE= 13098 -b3B0aW9uYWw= 13099 -LkVxdWFs 13100 -IFBoeXM= 13101 -aW5ndQ== 13102 -MDMz 13103 -NzIw 13104 -IHN5bmM= 13105 -IE5h 13106 -fX08Lw== 13107 -T0xVTQ== 13108 -aXTDqQ== 13109 -IGlkZW50aWZpZXI= 13110 -b3dlZA== 13111 -IGV4dGVudA== 13112 -IGh1cg== 13113 -VkE= 13114 -Y2xhcg== 13115 -IGVkZ2Vz 13116 -Q3JpdGVyaWE= 13117 -IGluZGVlZA== 13118 -aW5oZXJpdA== 13119 -IE5pZ2h0 13120 -MzAy 13121 -IHJlcG9ydGluZw== 13122 -IGVuY291bnRlcg== 13123 -IGtpbmRz 13124 -X3ByZWQ= 13125 -IGNvbnNpZGVyaW5n 13126 -Lig= 13127 -IHByb3RlaW4= 13128 -VHlw 13129 -Z3JpY3VsdA== 13130 -IEJhbGw= 13131 -QENvbXBvbmVudA== 13132 -IEVzcw== 13133 -IFJ1Yg== 13134 -ODAy 13135 -dWxw 13136 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 13137 -aXR1ZA== 13138 -LmF0dHI= 13139 -aWVudGU= 13140 -IHNwZWxs 13141 -IEpvZQ== 13142 -RU5URVI= 13143 -X2hvc3Q= 13144 -aXRhbg== 13145 -IG1hdHRlcnM= 13146 -IGVtZXJnZW5jeQ== 13147 -dWF0ZWQ= 13148 -IENoYXQ= 13149 -PXsn 13150 -Y29udHJp 13151 -YXJrZXI= 13152 -5oiQ 13153 -aXBlcg== 13154 -IHNjaGVtZQ== 13155 -KHN0ZGVycg== 13156 -ICoo 13157 -Y2VpdmVy 13158 -LmNvbHVtbg== 13159 -IG1hcmtlZA== 13160 -X0FUVFI= 13161 -IGJvZGllcw== 13162 -IElNUExJRUQ= 13163 -R2Fw 13164 -IFBPU1Q= 13165 -IGNvcnBvcmF0ZQ== 13166 -IGRpbWVuc2lvbg== 13167 -IGNvbnRyYXN0 13168 -ZXJ2aWV3 13169 -IEVSUk9S 13170 -IGNhcGFibGU= 13171 -IGFkdmVydGlzaW5n 13172 -dXJjaGFzZQ== 13173 -IFBB 13174 -IEZyYW5jaXNjbw== 13175 -IGZhY2luZw== 13176 -44CM 13177 -Z2l0 13178 -IGJlZXI= 13179 -IHNreQ== 13180 -ZG93bmxvYWQ= 13181 -IEN1cg== 13182 -bWM= 13183 -YW5ueQ== 13184 -LmZsb29y 13185 -IGNyaXRlcmlh 13186 -IHBhcnNlSW50 13187 -YCwK 13188 -IGFzcGVjdA== 13189 -IGJ1bmRsZQ== 13190 -Q291bGQ= 13191 -IHRhbms= 13192 -LWlk 13193 -IGh1cnQ= 13194 -IGJyb2FkY2FzdA== 13195 -T0tFTg== 13196 -b3dudA== 13197 -bnVsbGFibGU= 13198 -Q2Fw 13199 -IGFsY29ob2w= 13200 -IENvbGw= 13201 -IEhlbHBlcg== 13202 -IEFm 13203 -Lm1ldGhvZA== 13204 -IHBsYW5uZWQ= 13205 -cGxlcg== 13206 -IFNpdGU= 13207 -IHJlc2M= 13208 -b21lbnQ= 13209 -IEphdmFTY3JpcHQ= 13210 -U0VSVkVS 13211 -IHJocw== 13212 -ZXJlcw== 13213 -KCIs 13214 -aWZp 13215 -LmZpZWxkcw== 13216 -IHBhcmtpbmc= 13217 -IGlzbGFuZA== 13218 -IHNpc3Rlcg== 13219 -Xwo= 13220 -Q29uc3RyYWludHM= 13221 -IEF1c3Q= 13222 -ZGlt 13223 -X3BvaW50cw== 13224 -IGdhcA== 13225 -X2FjdGl2ZQ== 13226 -IHZvb3I= 13227 -IFBP 13228 -QmFn 13229 -LXNjYWxl 13230 -bGFtYmRh 13231 -LkRpc3Bvc2U= 13232 -cnVsZQ== 13233 -IG93bmVk 13234 -IE1lZGljYWw= 13235 -MzAz 13236 -ZW50cmllcw== 13237 -IHNvbGFy 13238 -IHJlc3VsdGluZw== 13239 -IGVzdGltYXRlZA== 13240 -IGltcHJvdmVk 13241 -RHVyYXRpb24= 13242 -ZW1wbG95ZWU= 13243 -JC4= 13244 -QWN0aW9ucw== 13245 -TGlrZQ== 13246 -LCg= 13247 -KFJlcXVlc3Q= 13248 -JXM= 13249 -Lk9wZW4= 13250 -KSIK 13251 -IHBpeGVs 13252 -IGFkYXB0ZXI= 13253 -IHJldmVudWU= 13254 -b2dyYW0= 13255 -IExB 13256 -IE1hY2hpbmU= 13257 -INin 13258 -IGZsZQ== 13259 -IGJpa2U= 13260 -SW5zZXRz 13261 -IGRpc3A= 13262 -IGNvbnNpc3RlbnQ= 13263 -YcOnw6Nv 13264 -Z2VuZGVy 13265 -IFRob3Nl 13266 -cGVyaWVuY2U= 13267 -LkJhY2tDb2xvcg== 13268 -LnBsYXk= 13269 -IHJ1c2g= 13270 -IGF4aW9z 13271 -IG5lY2s= 13272 -X21lbQ== 13273 -LlBSRUZFUlJFRA== 13274 -X2ZpcnN0 13275 -Q0I= 13276 -IFdpZGdldA== 13277 -IHNlcQ== 13278 -aGFy 13279 -IGhpdHM= 13280 -IOKCrA== 13281 -IGNvbnRhaW5lZA== 13282 -cmllbnQ= 13283 -d2F0ZXI= 13284 -TE9BRA== 13285 -IFZpcmdpbmlh 13286 -IEFybQ== 13287 -IC4v 13288 -wrs= 13289 -X3Jvb3Q= 13290 -IGFzc2lzdGFuY2U= 13291 -W10s 13292 -c3luYw== 13293 -IHZlZ2V0 13294 -ZXNjYXBl 13295 -aWNlcg== 13296 -Ym9vc3Q= 13297 -IEZsb2F0 13298 -LVc= 13299 -Ki8NCg== 13300 -Kj4= 13301 -MjE4 13302 -ICQoIi4= 13303 -LnBvcw== 13304 -IGJveXM= 13305 -IHdlZGRpbmc= 13306 -IGFnZW50cw== 13307 -PSJf 13308 -IEFybXk= 13309 -IGhpbnQ= 13310 -dmlzaW9u 13311 -IHRlY2g= 13312 -IENvbm5lY3Q= 13313 -IGxlZ2VuZA== 13314 -IEJldA== 13315 -LkJhc2U= 13316 -U3ViamVjdA== 13317 -IGxpdA== 13318 -UmVtb3Zl 13319 -ICI6 13320 -IEZpbmFs 13321 -cGVhcmFuY2U= 13322 -IGlUdW5lcw== 13323 -IHBhcnRpY2lwYW50cw== 13324 -IFB5dGhvbg== 13325 -IGJ1c3k= 13326 -aWVs 13327 -dmVydGljZXM= 13328 -IHRlbXBsYXRlVXJs 13329 -IENsb3Nl 13330 -SW1n 13331 -IENvcnBvcmF0aW9u 13332 -dGltZXN0YW1w 13333 -IGV4dGVuZA== 13334 -IHdlYnNpdGVz 13335 -IHBvc3NpYmlsaXR5 13336 -0L7Rgg== 13337 -IGvDtg== 13338 -IG1lYXQ= 13339 -IHJlcHJlc2VudGF0aW9u 13340 -MjQx 13341 -IAkJ 13342 -X1NUQVJU 13343 -LmFwcGx5 13344 -IFZhbGxleQ== 13345 -IFN1Y2Nlc3M= 13346 -SGk= 13347 -IG5vYg== 13348 -IElFbnVtZXJhYmxl 13349 -X3NlbGVjdA== 13350 -Z2Vv 13351 -LiIpCg== 13352 -IHR1cm5pbmc= 13353 -IGZhYnJpYw== 13354 -KCIiKTsK 13355 -IHBlcnNwZWN0aXZl 13356 -6Zc= 13357 -IFNu 13358 -VGhhbms= 13359 -O2o= 13360 -LlBhcmFtZXRlcnM= 13361 -CSAgICAgICAgICAg 13362 -IGZhY3Rz 13363 -MzA1 13364 -IHVudA== 13365 -Lmluc3RhbmNl 13366 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 13367 -LWVuZA== 13368 -IEpPSU4= 13369 -IEhlbg== 13370 -IHVyaQ== 13371 -5ZCN 13372 -INC90LA= 13373 -IEluZm8= 13374 -IGNvbmR1Y3RlZA== 13375 -IMOl 13376 -T1VSQ0U= 13377 -IHdpbmU= 13378 -Sm9obg== 13379 -LkVycm9yZg== 13380 -IEFnZQ== 13381 -b3VuZGVk 13382 -IHJlYWxpemU= 13383 -MzEy 13384 -IF07 13385 -IHN1YnNlcXU= 13386 -LG0= 13387 -KFVzZXI= 13388 -aWFubw== 13389 -IGFjY29tcGw= 13390 -aXNw 13391 -LnN0ZA== 13392 -6Yc= 13393 -IEJlZA== 13394 -LnNldEF0dHJpYnV0ZQ== 13395 -QlI= 13396 -a2VlcA== 13397 -IEFMTA== 13398 -IGlzb2w= 13399 -YW1tYQ== 13400 -UGFja2FnZQ== 13401 -IG9jY2FzaW9u 13402 -LXN1Y2Nlc3M= 13403 -0LXQtA== 13404 -IExJTUlURUQ= 13405 -c3RyaXA= 13406 -KCkKCgo= 13407 -aXN0cmlidXRpb24= 13408 -Q29sb3Jz 13409 -ICs6Kw== 13410 -RGlkTG9hZA== 13411 -YWxlcg== 13412 -IHRpZA== 13413 -IExFRA== 13414 -IExpbmtlZA== 13415 -IENhcnQ= 13416 -KCkpDQo= 13417 -X1JFQUQ= 13418 -IGtpbGxpbmc= 13419 -IFBIUA== 13420 -ZmVjdGlvbg== 13421 -IGluc3RhbmNlcw== 13422 -Y3Y= 13423 -Ii8+ 13424 -IHNm 13425 -IHRheGVz 13426 -X2xvY2F0aW9u 13427 -IEJpdGNvaW4= 13428 -dWFibGU= 13429 -cmFuaw== 13430 -aWdub3Jl 13431 -dHJhY2s= 13432 -0LrQsA== 13433 -IHNob3VsZG4= 13434 -IE9Q 13435 -PT57Cg== 13436 -IGtt 13437 -IGhlbHBlcg== 13438 -X2hlYWQ= 13439 -IFdoZXRoZXI= 13440 -b2Nv 13441 -X2Js 13442 -IHN0YXRpc3RpY3M= 13443 -IGJlYXV0eQ== 13444 -IHRvZw== 13445 -dGlw 13446 -64uk 13447 -IGNzdg== 13448 -KHNxbA== 13449 -c3RkbGli 13450 -d2Vhaw== 13451 -IGxpa2Vz 13452 -xI0= 13453 -IHJlcGVhdA== 13454 -IGFwYXJ0bWVudA== 13455 -IGVtcGg= 13456 -X2VkaXQ= 13457 -IHZpdA== 13458 -CXR5cGU= 13459 -MjE3 13460 -RXZlbg== 13461 -dXRlbg== 13462 -IGNpcmN1bXN0YW5jZXM= 13463 -Ymlhbg== 13464 -IHN1Z2Fy 13465 -V2luZG93cw== 13466 -7J4= 13467 -IG9ic2VydmVk 13468 -L2RhdGE= 13469 -IGNhbGVuZGFy 13470 -IHN0cmlrZQ== 13471 -IFJFUw== 13472 -X3Nj 13473 -Zm9ueQ== 13474 -b3JlbQ== 13475 -KHo= 13476 -cG93ZXI= 13477 -ZXRlY3Q= 13478 -IFNhdA== 13479 -LmRlc2NyaXB0aW9u 13480 -IGdhbmc= 13481 -IFNwb3J0cw== 13482 -b25ncw== 13483 -IEJ1bmRsZQ== 13484 -LnN1bQ== 13485 -b25jZQ== 13486 -IGFjY3VzZWQ= 13487 -IGV4cGxvcmU= 13488 -IGFwcHJveGltYXRlbHk= 13489 -IGxvc2luZw== 13490 -dGhlc2lz 13491 -IEZ1bmQ= 13492 -IGRpYWdu 13493 -QXV0b3dpcmVk 13494 -cHJvcGVydGllcw== 13495 -IF8u 13496 -IGNudA== 13497 -Y2VkdXJl 13498 -IHl5 13499 -IGdyYW50 13500 -c29jaw== 13501 -LmlubmVySFRNTA== 13502 -IF0pOwo= 13503 -IENPTkZJRw== 13504 -PSck 13505 -NTUw 13506 -XV07Cg== 13507 -VU5E 13508 -IGdsb2I= 13509 -IGRpcmU= 13510 -dWZmbGU= 13511 -X01FTQ== 13512 -IGF1dGhlbnRpYw== 13513 -Pigi 13514 -IGRlY2FkZQ== 13515 -IEltcG9ydA== 13516 -IG9yaWdpbmFsbHk= 13517 -IGpRdWVyeQ== 13518 -IGluZGljYXRl 13519 -IG91cnNlbHZlcw== 13520 -U3c= 13521 -LmxibA== 13522 -ZW5lcmF0ZQ== 13523 -IGJhc2ljYWxseQ== 13524 -IEhvbQ== 13525 -ICsjKw== 13526 -IEJyaXRhaW4= 13527 -IEthcg== 13528 -dG9FcXVhbA== 13529 -LnN0b3A= 13530 -IG1vZGFs 13531 -aXNp 13532 -IHN1Z2dlc3Rz 13533 -IGR0eXBl 13534 -IHR1cg== 13535 -YmY= 13536 -IGNvbm5lY3Rpb25z 13537 -IEJlZm9yZQ== 13538 -aXN0ZWQ= 13539 -bW91c2U= 13540 -IHB1bGxlZA== 13541 -LmJ1aWxk 13542 -IGxlZ2lzbGF0aW9u 13543 -IGZvcnRo 13544 -cGFk 13545 -ZWdv 13546 -Lk5vdw== 13547 -IGV4Y2l0aW5n 13548 -fQoKCgo= 13549 -IGNvbXBy 13550 -IHNoYXJlcw== 13551 -IHJpZw== 13552 -Z3JlZW4= 13553 -X3ZlYw== 13554 -IGVudW1lcmF0ZQ== 13555 -QXV0bw== 13556 -aWNhdG9y 13557 -IFJheQ== 13558 -YXNzZQ== 13559 -IGhvbGlkYXk= 13560 -IG51bGxhYmxl 13561 -Z3Vu 13562 -X2RldGFpbHM= 13563 -IHdyYXBwZXI= 13564 -c2Vx 13565 -IFlvdW5n 13566 -anVhbmE= 13567 -ICJfXw== 13568 -bGljZW5zZQ== 13569 -c2VydmU= 13570 -Xig= 13571 -aWRlcnM= 13572 -LlJlbW92ZQ== 13573 -cm9wZG93bg== 13574 -J1M= 13575 -cGlu 13576 -KHRva2Vu 13577 -LkRlZmF1bHQ= 13578 -IHJlYXNvbmFibGU= 13579 -YW1waW9u 13580 -IFNvY2lldHk= 13581 -IGJlaQ== 13582 -ZXJ2ZXM= 13583 -cmFk 13584 -IEZveA== 13585 -X2ltYWdlcw== 13586 -IHdoZWVs 13587 -Jylb 13588 -IGNmZw== 13589 -KEJ5 13590 -Q29uc3RydWN0b3I= 13591 -IHZhcnk= 13592 -LnN3aWZ0 13593 -IHByb3h5 13594 -CUg= 13595 -IEFub3RoZXI= 13596 -IFBlbg== 13597 -IGNoZWNraW5n 13598 -IGplc3Q= 13599 -bWFuYWdlcg== 13600 -T3JpZ2lu 13601 -dWdz 13602 -b2ly 13603 -PjwhLS0= 13604 -IGV4cHJlc3NlZA== 13605 -IG1vZGVy 13606 -IGFnZW5jaWVz 13607 -IGlo 13608 -LWhpZGRlbg== 13609 -aW91c2x5 13610 -IFJvZA== 13611 -IHNvbGU= 13612 -TWVk 13613 -LkFueQ== 13614 -IHBj 13615 -YmFs 13616 -RXhhbXBsZQ== 13617 -IFNhbGU= 13618 -IHN0cmlw 13619 -IENvbXA= 13620 -IHByZXNpZGVudGlhbA== 13621 -TW9zdA== 13622 -cHV0YXRpb24= 13623 -KHJlZg== 13624 -IEZvdXI= 13625 -X2ZpbGVuYW1l 13626 -IGVuZm9yY2VtZW50 13627 -2K8= 13628 -IEdlb3Jn 13629 -d2VpZ2h0cw== 13630 -L2w= 13631 -IGFnZ3Jlc3M= 13632 -IGRyYXdpbmc= 13633 -YW5keQ== 13634 -PEk= 13635 -LWo= 13636 -YWth 13637 -aHJlZg== 13638 -IHRlYWNoZXJz 13639 -X1E= 13640 -KGl0 13641 -IE1C 13642 -IHRlbXBvcmFyeQ== 13643 -aXJlYmFzZQ== 13644 -c3RyYQ== 13645 -5pe2 13646 -6LQ= 13647 -KGxhYmVs 13648 -b3Vw 13649 -IHRvcGljcw== 13650 -IHBvcnRpb24= 13651 -aWRvcw== 13652 -IEpld2lzaA== 13653 -IHJlY292ZXJ5 13654 -NjUw 13655 -IHN0YW5kcw== 13656 -I1s= 13657 -IGFmdGVybm9vbg== 13658 -IEFydGljbGU= 13659 -X2F0dA== 13660 -IGV4cGxhbg== 13661 -IFBhaw== 13662 -LnNldE9uQ2xpY2tMaXN0ZW5lcg== 13663 -LmNoaWxkcmVu 13664 -IGlr 13665 -Kyg= 13666 -bGFn 13667 -IGRpc2s= 13668 -IGNvbnRyb3ZlcnM= 13669 -Ij4m 13670 -YXNw 13671 -IHdpZQ== 13672 -IEF1c3RyYWxpYW4= 13673 -IFlvdVR1YmU= 13674 -QXR0cg== 13675 -Y29udGFpbnM= 13676 -ZHVjZQ== 13677 -IE1hdHQ= 13678 -MzQw 13679 -YXRlcm4= 13680 -IHZvbHVudGU= 13681 -IG5ld3Nw 13682 -VlA= 13683 -b2x0aXA= 13684 -IGRlbGVnYXRl 13685 -X21ldGE= 13686 -IGFjY3VyYXRl 13687 -IEV4YW1wbGU= 13688 -JSw= 13689 -IERhaWx5 13690 -IGNhYmlu 13691 -IFNX 13692 -IGxpbWl0cw== 13693 -a2lw 13694 -IGFybXk= 13695 -IGVuZGluZw== 13696 -IGJvc3M= 13697 -IERpYWxvZw== 13698 -QWxzbw== 13699 -PSIjIg== 13700 -b3JkYW4= 13701 -cm93c2U= 13702 -LW1pbg== 13703 -ICIm 13704 -X2xvYw== 13705 -VVg= 13706 -IGRldmVsb3BlcnM= 13707 -IGFjY3VyYWN5 13708 -IG1haW50ZW5hbmNl 13709 -IGhlYXY= 13710 -IGZpbHRlcnM= 13711 -LlRvb2xTdHJpcA== 13712 -IG5hcnI= 13713 -IEVtcA== 13714 -T1JERVI= 13715 -IE1vYmlsZQ== 13716 -LlNlcmlhbA== 13717 -Lm91dHB1dA== 13718 -MjQ0 13719 -LmNvbA== 13720 -TWF0ZXJpYWw= 13721 -dW1h 13722 -IGNvbnN1bWVycw== 13723 -c2hpZnQ= 13724 -IHB1ZWQ= 13725 -IG1pbmk= 13726 -Y29sbGVjdGlvbg== 13727 -IGthbg== 13728 -LmNlbnRlcg== 13729 -SGlzdG9yeQ== 13730 -IGJlbmNo 13731 -KCkpOw== 13732 -aXRvcmllcw== 13733 -IGNyb3dk 13734 -X2NhbGw= 13735 -IHBvd2Vycw== 13736 -LUU= 13737 -IGRpc21pc3M= 13738 -IHRhbGtz 13739 -IENoYW5uZWw= 13740 -Zm9yd2FyZA== 13741 -X2NvbnRyb2w= 13742 -L3NyYw== 13743 -aWVzdA== 13744 -KioqKioqKioqKioqKioqKioqKioqKioq 13745 -IGJldGE= 13746 -KGNvbG9y 13747 -X09CSkVDVA== 13748 -IEFwaQ== 13749 -IGVmZmVjdGl2ZWx5 13750 -Q2FtZXJh 13751 -c2Q= 13752 -dXNzeQ== 13753 -Mjkw 13754 -RGljdA== 13755 -IEVmZmVjdA== 13756 -aWJpbGl0aWVz 13757 -IHJldHVybmluZw== 13758 -IEZhcg== 13759 -ICcnKQ== 13760 -IG1vZHVsZXM= 13761 -MjE5 13762 -aWxhdGlvbg== 13763 -ICgl 13764 -VFJHTA== 13765 -IHN0b3Jt 13766 -b25uYQ== 13767 -IEVYUA== 13768 -IHNwb25z 13769 -IGRpc3Bs 13770 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 13771 -ZmFsbA== 13772 -5Yw= 13773 -aWduS2V5 13774 -X1VT 13775 -ZXRyaWNz 13776 -IGhhbmRsZXM= 13777 -VEw= 13778 -X2Ftb3VudA== 13779 -b3dh 13780 -YnJhbmQ= 13781 -IFRvb2w= 13782 -IHVzdWFs 13783 -Llo= 13784 -Y3JlbWVudA== 13785 -YWRpdW0= 13786 -c3RvY2s= 13787 -IHNlcnZpbmc= 13788 -IEJvbg== 13789 -IGxpbmVhcg== 13790 -IFRhcmdldA== 13791 -IFJhZGlv 13792 -SEw= 13793 -U2hhZGVy 13794 -b21hdGlj 13795 -YWd1ZXM= 13796 -aW5pdHk= 13797 -ZGlmZg== 13798 -X2l0ZXJhdG9y 13799 -cXVvdA== 13800 -ICwK 13801 -Y2FsbGJhY2s= 13802 -IHN5bXB0b21z 13803 -W18= 13804 -IEJ1bA== 13805 -IEZlYg== 13806 -dW5kbw== 13807 -X2FjY291bnQ= 13808 -IHR5cGVkZWY= 13809 -0LjRgQ== 13810 -dHJhcw== 13811 -VXNlcklk 13812 -IFBlbm4= 13813 -IFN1cHJlbWU= 13814 -fT4= 13815 -dXNlcklk 13816 -MzI3 13817 -IEtpbQ== 13818 -IGdh 13819 -IGFydGlzdHM= 13820 -5bg= 13821 -IEFic3RyYWN0 13822 -b2tlbW9u 13823 -IGhhbQ== 13824 -b3ZhbA== 13825 -IGNoYQ== 13826 -YXRlbg== 13827 -5YY= 13828 -Rml4ZWQ= 13829 -IHZ1bG5lcg== 13830 -IFBhcmFtZXRlcnM= 13831 -cXVhbnRpdHk= 13832 -LkNsZWFy 13833 -U2VydmxldFJlcXVlc3Q= 13834 -IHlh 13835 -IHNvdWw= 13836 -MDgw 13837 -dHJhbnNhY3Rpb24= 13838 -IHNvbG8= 13839 -IHBhaXJz 13840 -5pQ= 13841 -IEdyZQ== 13842 -X3dvcmQ= 13843 -IEND 13844 -IGdp 13845 -emll 13846 -IHNjaGVkdWxlZA== 13847 -cm90YXRpb24= 13848 -Z3lwdA== 13849 -dWxvdXM= 13850 -Ojpf 13851 -IEVsbA== 13852 -PCE= 13853 -CQkgIA== 13854 -bHA= 13855 -YWhh 13856 -Q29weXJpZ2h0 13857 -MDA5 13858 -IGRyYW0= 13859 -MjUx 13860 -IGRpYWdyYW0= 13861 -IE1lbQ== 13862 -IGdhcmRlbg== 13863 -Q29tcA== 13864 -IGF0dGVtcHRz 13865 -dWZmaXg= 13866 -Pigp 13867 -IHBoaWxvc29waA== 13868 -X3JlbA== 13869 -5bw= 13870 -IHN2 13871 -LnNlY29uZA== 13872 -YW50bw== 13873 -Lkpzb24= 13874 -IFRlbGU= 13875 -X2xvY2Fs 13876 -X3NlbmQ= 13877 -IGFzcGVjdHM= 13878 -7Jc= 13879 -SUJMRQ== 13880 -IHJhaWw= 13881 -IHdpZGVseQ== 13882 -YXNoZWQ= 13883 -aWFy 13884 -aW5m 13885 -dXBwZXI= 13886 -ZGphbmdv 13887 -X3Jlc3VsdHM= 13888 -aXNzaW5n 13889 -IGVxdWl2YWxlbnQ= 13890 -T1VORA== 13891 -IHR5 13892 -IHBvdGVudGlhbGx5 13893 -QWR2ZXJ0aXNlbWVudA== 13894 -MjM4 13895 -IFJlY29yZA== 13896 -Mzgw 13897 -cmVzZW50YXRpb24= 13898 -X3dpZGdldA== 13899 -b3VuZGluZw== 13900 -IHJlbGlnaW9u 13901 -IGNvbnNj 13902 -IExpbQ== 13903 -LmFt 13904 -SHRtbA== 13905 -ICc6 13906 -UEFUSA== 13907 -X3NwZWM= 13908 -b3J0ZWQ= 13909 -aWRhZGVz 13910 -X3NoYXBl 13911 -IGtlZXBz 13912 -LlNhdmU= 13913 -IExvYw== 13914 -b3Jp 13915 -IFRFU1Q= 13916 -dW5pY2lw 13917 -IHJlZ2lvbnM= 13918 -IGJlbGlldmVz 13919 -L2Vu 13920 -cG9zaXRl 13921 -eyc= 13922 -cHJlcGFyZQ== 13923 -X2NvbnN0 13924 -c2FtcGxl 13925 -IFdpbGxpYW1z 13926 -IHN0cnQ= 13927 -X0dldA== 13928 -IEFuZHJldw== 13929 -LmFjdGl2ZQ== 13930 -IGxheWVycw== 13931 -VmlzdWFsU3R5bGU= 13932 -YXp5 13933 -IEtu 13934 -IGFjaWQ= 13935 -IEFzaWE= 13936 -IGV4Y2Vzcw== 13937 -CW15 13938 -IGtleWJvYXJk 13939 -ZW5zdXM= 13940 -IGNyZXc= 13941 -IG1pc3NlZA== 13942 -bWFzdGVy 13943 -IFdpbGQ= 13944 -IG5ld2x5 13945 -IHdpbm5lcg== 13946 -IHN0dWI= 13947 -aWNvZGU= 13948 -Lm1vdmU= 13949 -RG9tYWlu 13950 -IFNhcg== 13951 -IGZvcmVzdA== 13952 -TEVE 13953 -Y2xhaW1lcg== 13954 -LmV4aXQ= 13955 -IFdpbmRvdw== 13956 -IHJlc2lzdGFuY2U= 13957 -IENIRUNL 13958 -KCIt 13959 -IFJ5YW4= 13960 -IHBpcGU= 13961 -IGNvYXN0 13962 -REVG 13963 -Ly8h 13964 -X29mZg== 13965 -ZXhpdA== 13966 -IHVsdGltYXRlbHk= 13967 -aW1pdGl2ZQ== 13968 -IEtlZXA= 13969 -IGhpc3RvcmljYWw= 13970 -IGFueXdheQ== 13971 -IEphY2tzb24= 13972 -b2NrZXI= 13973 -RVJO 13974 -IFVJTlQ= 13975 -eW50YXg= 13976 -RVJZ 13977 -aXNtcw== 13978 -IGNu 13979 -IG9jY3Vycw== 13980 -IDs7 13981 -VGV4dFZpZXc= 13982 -QUU= 13983 -L2ltZw== 13984 -IHllc3RlcmRheQ== 13985 -LWRlZmF1bHQ= 13986 -IHRpbnk= 13987 -IHByb2M= 13988 -IGFsaXZl 13989 -IFJFRw== 13990 -LnRo 13991 -ZWFyaW5n 13992 -LmdldExvZ2dlcg== 13993 -PGxpbms= 13994 -X2xvZ2lu 13995 -Rm9sZGVy 13996 -YWJj 13997 -bHlwaGljb24= 13998 -0L3Qvg== 13999 -IG5vdGljZWQ= 14000 -b2RpZ28= 14001 -IGVkaXRpb24= 14002 -aW1hdG9y 14003 -LkVuYWJsZWQ= 14004 -LnBhcnNlSW50 14005 -IHlhcmRz 14006 -CQkJCQkJCQkJCQkJ 14007 -IHZlcmJvc2U= 14008 -0LvRjw== 14009 -X0JZ 14010 -LmxvZ2lu 14011 -Lio7Cg== 14012 -IE1pZA== 14013 -w6llcw== 14014 -IGdsbw== 14015 -IGJ1aWxkaW5ncw== 14016 -IHpl 14017 -IEl0ZXI= 14018 -IHR1YmU= 14019 -IFBvdA== 14020 -XE0= 14021 -MjUz 14022 -PHRo 14023 -YnJpZGdl 14024 -IFNjcmlwdA== 14025 -IE1vZHVsZQ== 14026 -IHZhY2M= 14027 -IGluc3RhbGxhdGlvbg== 14028 -dnk= 14029 -VmlzdWFsU3R5bGVCYWNrQ29sb3I= 14030 -IFNN 14031 -LnRvdGFs 14032 -NjQw 14033 -YmF0 14034 -IGZpbmRz 14035 -IGF0bW9z 14036 -U3Vidmlldw== 14037 -aXphcmQ= 14038 -IHJlcGxhY2VtZW50 14039 -bGljYXRlZA== 14040 -YXBpcw== 14041 -IGxvZ2dlZA== 14042 -IExlZnQ= 14043 -R3Vp 14044 -X1R5cGU= 14045 -dG0= 14046 -UGFk 14047 -IGhvdXNlaG9sZA== 14048 -IHJlbGU= 14049 -IHByb3Bvc2Fs 14050 -X0NMQVNT 14051 -MjQz 14052 -Ojo6Og== 14053 -IGluZnJhc3RydWN0dXJl 14054 -SW5qZWN0 14055 -L2h0bWw= 14056 -MjI2 14057 -IGFkcw== 14058 -aXp6YQ== 14059 -IG1n 14060 -Y3RyaW5l 14061 -JQo= 14062 -PGh0bWw= 14063 -LWltYWdl 14064 -IGF0dG9ybmV5 14065 -PG0= 14066 -KCcs 14067 -IGNhbm4= 14068 -IHByaW50bG4= 14069 -b29zZQ== 14070 -IHllbGxvdw== 14071 -LmV4cA== 14072 -cGF5bWVudA== 14073 -IHRhYmxlVmlldw== 14074 -YXdheQ== 14075 -IG9wcG9zaXRpb24= 14076 -IEFnYWlu 14077 -IEhhbmRsZQ== 14078 -IGV4Y2x1c2l2ZQ== 14079 -aW5hcg== 14080 -w6ly 14081 -0L7QsQ== 14082 -IENPREU= 14083 -ZW1wb3Jhcnk= 14084 -IHJlYWN0 14085 -cGlwZQ== 14086 -MjM2 14087 -Y3o= 14088 -LmFjdGl2aXR5 14089 -IGxhcmdlbHk= 14090 -IGRpc3M= 14091 -YXh5 14092 -ZXNpcw== 14093 -IFJlbg== 14094 -IGNvcm4= 14095 -LlVzZVZpc3VhbFN0eWxlQmFja0NvbG9y 14096 -ZGF5cw== 14097 -IGZydWl0 14098 -SW5zZXJ0 14099 -X2VuYw== 14100 -RXN0 14101 -X2RlYw== 14102 -IEx1Yw== 14103 -IMO8YmVy 14104 -cGFyYW1ldGVycw== 14105 -UEVSVA== 14106 -ZXhwcmVzcw== 14107 -X3Byb2ZpbGU= 14108 -VW5rbm93bg== 14109 -IHJldm9sdXRpb24= 14110 -LmFkZHJlc3M= 14111 -X3JlcXVpcmU= 14112 -IHVuaWZvcm0= 14113 -IFBhY2s= 14114 -bGFy 14115 -IFVJVGFibGVWaWV3 14116 -IGRlcGVuZHM= 14117 -VmFsaWRhdGlvbg== 14118 -Y29uZmlybQ== 14119 -T3duZXI= 14120 -IHRyaWI= 14121 -aGV0 14122 -IElkZQ== 14123 -YW5zYXM= 14124 -MjQ3 14125 -TGFuZ3VhZ2U= 14126 -dWV0 14127 -IFBv 14128 -IFN0ZXZl 14129 -IGNvbnRlc3Q= 14130 -X0RFRkFVTFQ= 14131 -IGFwcGFyZW50bHk= 14132 -UkVFTg== 14133 -IGZyZXF1ZW50bHk= 14134 -IHRyYWRpdGlvbg== 14135 -b2NvbGF0ZQ== 14136 -U0k= 14137 -IEFyZ3VtZW50 14138 -Rm9jdXM= 14139 -ZXJ0ZQ== 14140 -IExheW91dA== 14141 -IGR4 14142 -IGdlbmVyYXRvcg== 14143 -IFdhaXQ= 14144 -UG9saWN5 14145 -bGlnaHRz 14146 -LkV4ZWN1dGU= 14147 -NTU1 14148 -UHk= 14149 -IGJlZHJvb20= 14150 -ZWRh 14151 -cmFpZA== 14152 -CXNpemU= 14153 -IGFuY2llbnQ= 14154 -IHB1bXA= 14155 -IGR3 14156 -ICghKA== 14157 -IHNwZWNpZnk= 14158 -KHN0YXR1cw== 14159 -IEZCSQ== 14160 -LmV4Y2VwdGlvbg== 14161 -IHJlbWFyaw== 14162 -bHltcA== 14163 -YW50ZWU= 14164 -VXBsb2Fk 14165 -ZXJuZXQ= 14166 -6aE= 14167 -aW5lbnQ= 14168 -IFJlbmRlcg== 14169 -ZG0= 14170 -IE1lbW9yeQ== 14171 -cmljaA== 14172 -IFRvb2xz 14173 -IGtuZQ== 14174 -IHBlcm0= 14175 -YmFk 14176 -IGRpbm5lcg== 14177 -LnJlc2V0 14178 -IGpMYWJlbA== 14179 -RmVhdHVyZQ== 14180 -LlNlcnZpY2U= 14181 -ICh7Cg== 14182 -IHJlZmVycmVk 14183 -LmNsYXNzTGlzdA== 14184 -MjQ4 14185 -IGluaXRXaXRo 14186 -IFRleHRWaWV3 14187 -IG5laXRoZXI= 14188 -IGNvdW50eQ== 14189 -ICJ7 14190 -56c= 14191 -IHRhY2s= 14192 -Y2xhc3NOYW1l 14193 -IFVTRVI= 14194 -IHJlbmV3 14195 -YGA= 14196 -Z2V0TmFtZQ== 14197 -IGJyb3du 14198 -RXJyb3Jz 14199 -ZXJ0bw== 14200 -IHN1c3RhaW4= 14201 -U08= 14202 -bGV0ZXM= 14203 -IEludmFsaWQ= 14204 -MjQ2 14205 -MjI3 14206 -IGVuZW1pZXM= 14207 -dW5nZQ== 14208 -IGV4aXN0ZW5jZQ== 14209 -ZXJyYQ== 14210 -CiAgCg== 14211 -dXRvcmlhbA== 14212 -I2E= 14213 -cGF5 14214 -Y2hhcmdl 14215 -IElyZQ== 14216 -YXRlc3Q= 14217 -IGV4cGxvcw== 14218 -IGZpcmVk 14219 -TkVS 14220 -IFR5 14221 -aWNpb24= 14222 -VXJp 14223 -IG9idmlvdXNseQ== 14224 -IENvbHVt 14225 -ICcr 14226 -IERldmljZQ== 14227 -LXJlbGF0ZWQ= 14228 -X0FSRw== 14229 -IHZvcg== 14230 -IExlc3Nlcg== 14231 -X09Q 14232 -U2VyaWFsaXplcg== 14233 -IHVwZ3JhZGU= 14234 -TGlnaHQ= 14235 -IGNvZGVz 14236 -Kys7DQo= 14237 -IHdyaXRlcw== 14238 -Zm9vZA== 14239 -IMOpdA== 14240 -QHNlY3Rpb24= 14241 -IHRyYWNrcw== 14242 -IHNlcmlvdXNseQ== 14243 -Y2h0 14244 -NDMw 14245 -KHNpemVvZg== 14246 -IGltbWVkaWF0ZQ== 14247 -IHNjaWVudGlzdHM= 14248 -IHsk 14249 -X25l 14250 -LkFuY2hvclN0eWxlcw== 14251 -IGFjY29tbW9k 14252 -IEhhcnJ5 14253 -IHNpZ2h0 14254 -IFBhbGVzdA== 14255 -ZXJzaXN0ZW50 14256 -INGD 14257 -LWlucHV0 14258 -IGNvb3JkaW5hdGVz 14259 -wrc= 14260 -MjI4 14261 -V2VsY29tZQ== 14262 -LmNvbmY= 14263 -IGdyZXc= 14264 -IGJvbGQ= 14265 -IENQVQ== 14266 -KG15 14267 -IHBlcmZlY3RseQ== 14268 -IG1vbWVudHM= 14269 -IE1vdmll 14270 -LWRhdGE= 14271 -eXN0YWw= 14272 -X1dJRFRI 14273 -MjYy 14274 -IFNjcmVlbg== 14275 -5p0= 14276 -IGRpc2Fw 14277 -IHJlZHVjdGlvbg== 14278 -LkdldENvbXBvbmVudA== 14279 -X01PRFVMRQ== 14280 -IGdlbmVyaWM= 14281 -IGR5 14282 -YWxsZXI= 14283 -IGN1cmw= 14284 -IEJvZHk= 14285 -IGJhbmtz 14286 -LHQ= 14287 -YXZn 14288 -IGV2aWw= 14289 -IG1hbnVmYWN0dXJlcg== 14290 -IHJlY2VpdmVy 14291 -Q29sdW1ucw== 14292 -IGluZ3JlZGllbnRz 14293 -CW91dA== 14294 -cXVlcw== 14295 -LkxvYWQ= 14296 -IHNsb3dseQ== 14297 -IFRvd24= 14298 -IENlbGw= 14299 -X25vcm1hbA== 14300 -X3ByZWZpeA== 14301 -IEFsZXJ0 14302 -KCJ7 14303 -w6Ry 14304 -4oCcVGhl 14305 -IE1E 14306 -IGNvdXJzZXM= 14307 -YXRoYW4= 14308 -6Zk= 14309 -b2Nj 14310 -IFNFUg== 14311 -ZXNpZ24= 14312 -QWRkcg== 14313 -PVsn 14314 -KCIuLw== 14315 -XX0= 14316 -LmZvbnQ= 14317 -IEluc3RhZ3JhbQ== 14318 -IEJvcmRlcg== 14319 -b2Rh 14320 -IGhhbGw= 14321 -IHJ1bQ== 14322 -X2JpdA== 14323 -IHNhdmluZw== 14324 -X2Rvd24= 14325 -UmFuZG9t 14326 -X3JlZ2lzdGVy 14327 -KENvbnRleHQ= 14328 -IG9wcG9zaXRl 14329 -Um9vbQ== 14330 -WUVT 14331 -0LDQvdC4 14332 -IGVuam95ZWQ= 14333 -X3J1bg== 14334 -Q2xlYXI= 14335 -4oCY 14336 -IEZvcmQ= 14337 -b25pYw== 14338 -b3N0ZW4= 14339 -Il0p 14340 -X2F1dGg= 14341 -Ly8NCg== 14342 -IHN1ZmZpY2llbnQ= 14343 -TEVT 14344 -IHBoZW4= 14345 -IG9o 14346 -X2Nzdg== 14347 -IHJvdXRpbmU= 14348 -LkFyZUVxdWFs 14349 -YXlsb3I= 14350 -IGJhc2tldA== 14351 -X0NPTU0= 14352 -cnlwdGVk 14353 -U2lt 14354 -IFNob3A= 14355 -IHN0dWRpbw== 14356 -YXRvcw== 14357 -KFc= 14358 -W3N0cmluZw== 14359 -w6R0 14360 -b2dh 14361 -IHNocg== 14362 -IHNpY2s= 14363 -QW5vdGhlcg== 14364 -IGRvb3Jz 14365 -X05F 14366 -IFRIUkVF 14367 -Lm9yZGVy 14368 -cmF6aWw= 14369 -IG1hcHM= 14370 -X1RSVUU= 14371 -dHJhbnNsYXRl 14372 -IG5lYXJieQ== 14373 -MjY1 14374 -IG5hY2g= 14375 -TE9BVA== 14376 -YmF0Y2g= 14377 -MjI5 14378 -IGx1eA== 14379 -YXNoZXM= 14380 -YW5nZXJz 14381 -4oCm4oCm 14382 -X0VWRU5U 14383 -X1VQ 14384 -IGFjdHM= 14385 -aW52 14386 -X01FVEhPRA== 14387 -Y2Npb24= 14388 -IHJldGFpbg== 14389 -dXRjaA== 14390 -INCx 14391 -IGtub3dpbmc= 14392 -IHJlcHJlc2VudGluZw== 14393 -Tk9U 14394 -cG5n 14395 -Q29udHJhY3Q= 14396 -IHRyaWNr 14397 -IEVkaXRpb24= 14398 -dXBsaWNhdGU= 14399 -IGNvbnRyb2xsZWQ= 14400 -Y2Zn 14401 -amF2YXNjcmlwdA== 14402 -IG1pbGs= 14403 -V2hpdGU= 14404 -U2VxdWVuY2U= 14405 -YXdh 14406 -IGRpc2N1c3NlZA== 14407 -NTAx 14408 -IEJ1c2g= 14409 -IFlFUw== 14410 -LmZhY3Rvcnk= 14411 -dGFncw== 14412 -IHRhY3Q= 14413 -IHNpZA== 14414 -JCQ= 14415 -IEVudW0= 14416 -Mjc1 14417 -IGZyYW1lcw== 14418 -fSk7 14419 -IHJlZ3Vs 14420 -J107DQo= 14421 -UmVnaW9u 14422 -MzIx 14423 -ZmZm 14424 -IGNybw== 14425 -KGNvbQ== 14426 -PSIr 14427 -U3R1ZGVudA== 14428 -IGRpc2FwcG9pbnQ= 14429 -UkVTVUxU 14430 -Q291bnRlcg== 14431 -IGJ1dHRlcg== 14432 -IEhh 14433 -IERpZ2l0YWw= 14434 -IGJpZA== 14435 -Ij57ew== 14436 -aW5nZXJz 14437 -IENvdW50cnk= 14438 -X3RwbA== 14439 -Il0pCg== 14440 -L2s= 14441 -ZGF0aW5n 14442 -OiM= 14443 -IERBVEE= 14444 -eW5jaHJvbg== 14445 -X2JvZHk= 14446 -b2xseXdvb2Q= 14447 -IHZhbG9y 14448 -aXBpZW50 14449 -b2Z0 14450 -VUJM 14451 -ZG9jcw== 14452 -IHN5bmNocm9u 14453 -IGZvcm1lZA== 14454 -cnVwdGlvbg== 14455 -IGxpc3Rh 14456 -UmVxdWVzdE1hcHBpbmc= 14457 -IHZpbGxhZ2U= 14458 -IGtub2Nr 14459 -b2Nz 14460 -Ins= 14461 -X2ZsYWdz 14462 -IHRyYW5zYWN0aW9ucw== 14463 -IGhhYml0 14464 -IEpl 14465 -ZWRlbg== 14466 -IGFpcmNyYWZ0 14467 -aXJr 14468 -IEFC 14469 -IGZhaXJseQ== 14470 -LmludGVy 14471 -LkFjdA== 14472 -IGluc3RydW1lbnQ= 14473 -cmVtb3ZlQ2xhc3M= 14474 -LmNvbW1hbmQ= 14475 -0Yk= 14476 -CW1lbQ== 14477 -KG1pbg== 14478 -IG90 14479 -IGNvbGxl 14480 -PXM= 14481 -dGltZW91dA== 14482 -IGlkcw== 14483 -IE1hdGNo 14484 -aWpu 14485 -emVybw== 14486 -NDEw 14487 -IG5ldHdvcmtz 14488 -Lmdvdg== 14489 -IGludGVs 14490 -IHNlY3Rpb25z 14491 -b3V0aW5l 14492 -KGNtZA== 14493 -KGRpcg== 14494 -IExJQUJJTElUWQ== 14495 -IEJsb2c= 14496 -IGJyaWRnZQ== 14497 -MzA4 14498 -IENW 14499 -Y29udmVydA== 14500 -ICIpCg== 14501 -IEJlcm4= 14502 -X1BP 14503 -ZXZhbA== 14504 -KHNldA== 14505 -dG9vbA== 14506 -IHBheW1lbnRz 14507 -QmVoYXZpb3Vy 14508 -IGNvbmNyZXRl 14509 -IGVsaWc= 14510 -IGFjY2VsZXI= 14511 -IGhvbGU= 14512 -X28= 14513 -VEVHRVI= 14514 -IGdyYXBoaWNz 14515 -T3du 14516 -Rm9ybWF0dGVy 14517 -b25kZXI= 14518 -IHBhY2thZ2Vz 14519 -L2E= 14520 -IEtub3c= 14521 -T3JEZWZhdWx0 14522 -IGR1dHk= 14523 -V2FpdA== 14524 -0L3QsA== 14525 -X3JlY29yZA== 14526 -W3Q= 14527 -TWVzaA== 14528 -IG9uZ29pbmc= 14529 -LmJlYW5z 14530 -IHRhbg== 14531 -IGludGVycHJldA== 14532 -YXN0ZXJz 14533 -UVVBTA== 14534 -IGxlZ3M= 14535 -XFJlcXVlc3Q= 14536 -LWZpbGU= 14537 -X211dGV4 14538 -IFNhaW50 14539 -Ly8j 14540 -IHByb2hpYg== 14541 -KGluZm8= 14542 -Oj0= 14543 -bGludXg= 14544 -IGJsbw== 14545 -b3RpYw== 14546 -CWZpbmFs 14547 -X2V4cA== 14548 -IFN0b3A= 14549 -YXBpbmc= 14550 -KHNhdmVk 14551 -X3B1c2g= 14552 -IGVhc2U= 14553 -X0ZS 14554 -cG9uc2l2ZQ== 14555 -c3RyY21w 14556 -OgoKCgo= 14557 -5Lu2 14558 -b2xp 14559 -IGV4dHJlbWU= 14560 -IHByb2Zlc3Nvcg== 14561 -SW1hZ2Vz 14562 -LklPRXhjZXB0aW9u 14563 -IGFkZHJlc3Nlcw== 14564 -cGxlbWVudGVk 14565 -IGluY29ycG9y 14566 -IHVzZUVmZmVjdA== 14567 -X09G 14568 -IERh 14569 -bm9tYnJl 14570 -SVJTVA== 14571 -IGRpc2NyaW0= 14572 -IGNvbXBlbnM= 14573 -Z3JlZ2F0ZQ== 14574 -YW5jZWxs 14575 -YWNoZXM= 14576 -IENyaXRlcmlh 14577 -JHJlc3VsdA== 14578 -RGVzdHJveQ== 14579 -IHNlY29uZGFyeQ== 14580 -V2F0Y2g= 14581 -IFNlbQ== 14582 -IE1jQw== 14583 -IGFjYWRlbWlj 14584 -VXBwZXI= 14585 -Ojp+ 14586 -dXRyYWw= 14587 -IERvZw== 14588 -YWRlZA== 14589 -MjM3 14590 -VmFsaWRhdG9y 14591 -IGRlcml2ZWQ= 14592 -IHNldFRpbWVvdXQ= 14593 -IEtlbg== 14594 -IHR5cGljYWw= 14595 -IEJvYg== 14596 -IGJvdW5kcw== 14597 -IFNlYXNvbg== 14598 -IGNyYXp5 14599 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 14600 -LXJvdXRlcg== 14601 -aXR0ZXN0 14602 -IE1pcg== 14603 -IGVtb3Rpb25hbA== 14604 -LHY= 14605 -Y24= 14606 -L3N0 14607 -5b0= 14608 -b25vbQ== 14609 -IGRlY2xhcmVk 14610 -Pi4= 14611 -YWlsaW5n 14612 -IC8qPDw8 14613 -IG5vcm1hbGx5 14614 -KE1l 14615 -ZXZpbg== 14616 -bGlrZWx5 14617 -IHBvaW50ZWQ= 14618 -IFN0YWNr 14619 -IHdhbGxz 14620 -LlZlY3Rvcg== 14621 -bWVhbg== 14622 -XV0K 14623 -IGxpc3RlbmluZw== 14624 -YWR2 14625 -IHN3YXA= 14626 -SUZU 14627 -2Ko= 14628 -LmFyZ3Y= 14629 -dWxz 14630 -PG9wdGlvbg== 14631 -bm90YXRpb25z 14632 -IGVtYWlscw== 14633 -IFVrcg== 14634 -YXN0YQ== 14635 -IFRodXM= 14636 -IFN0b25l 14637 -IGFwcGVhbA== 14638 -LuKAmQ== 14639 -IHJlZ3VsYXRpb25z 14640 -UHJlZmVyZW5jZXM= 14641 -IFBob25l 14642 -dWxm 14643 -IERS 14644 -IHRlY2hub2xvZ2llcw== 14645 -IHBhcmFncmFwaA== 14646 -IG5lY2Vzc2FyaWx5 14647 -Mzcw 14648 -MDMw 14649 -LmVhY2g= 14650 -PGZsb2F0 14651 -cmVzYQ== 14652 -IHVuZGVyc3Q= 14653 -IGZpbmdlcg== 14654 -cHJlc3NlZA== 14655 -LWJ5 14656 -aWZmZXI= 14657 -d2F0Y2g= 14658 -IEJh 14659 -QUlN 14660 -IHdlaWdodHM= 14661 -IFJvbg== 14662 -Jyl9fQ== 14663 -W3NlbGY= 14664 -LS0tLS0tLS0tLQo= 14665 -cGVyaW1lbnQ= 14666 -IHRvU3RyaW5n 14667 -eGlj 14668 -IENhbWVyYQ== 14669 -IQoKCgo= 14670 -YXVyYW50 14671 -UHJlZml4 14672 -IGluc3RpdHV0aW9ucw== 14673 -OmludA== 14674 -IGV4cG9zdXJl 14675 -cGF0dGVybg== 14676 -IExpbnV4 14677 -Lm51bWJlcg== 14678 -cmVkaWVudA== 14679 -QXJndW1lbnRFeGNlcHRpb24= 14680 -IENoaWVm 14681 -In0s 14682 -IGVsZWN0cm9uaWM= 14683 -cm9uZw== 14684 -ZXJk 14685 -c3BOZXQ= 14686 -cmFpdA== 14687 -Lycs 14688 -IE9oaW8= 14689 -Q29udHJvbGxlcnM= 14690 -IGNvbnRpbnVpbmc= 14691 -IFRlbXBsYXRl 14692 -IEV0aA== 14693 -c3o= 14694 -L2Vudg== 14695 -RW52 14696 -JS4= 14697 -YXJ0ZXJz 14698 -KSgo 14699 -IFRBQkxF 14700 -IMOu 14701 -cGVyYXR1cmU= 14702 -cHJvZ3Jlc3M= 14703 -UHJlcw== 14704 -6rA= 14705 -aW1wbGVtZW50YXRpb24= 14706 -IGJpZW4= 14707 -IHN0cmVldHM= 14708 -X01TRw== 14709 -TmV3cw== 14710 -IyMj 14711 -Oi8= 14712 -IGN1dHRpbmc= 14713 -eEI= 14714 -cmVzc2Vk 14715 -X0VOQUJMRQ== 14716 -bGFi 14717 -IGNhdXNpbmc= 14718 -XSkpOwo= 14719 -YnJh 14720 -eEZGRkY= 14721 -aWxseQ== 14722 -cGxldGlvbg== 14723 -d2lsbA== 14724 -X2Jhcg== 14725 -IHN0cnVjdHVyZXM= 14726 -IEltcA== 14727 -24w= 14728 -IDw+ 14729 -IC0tLS0tLS0tLS0tLS0tLS0= 14730 -X0JVRkZFUg== 14731 -LmRpcg== 14732 -IHBsYWlu 14733 -IHBlZXI= 14734 -MjQ5 14735 -Z2c= 14736 -b2ludHM= 14737 -IHNvbWV3aGF0 14738 -IHdldA== 14739 -IGVtcGxveW1lbnQ= 14740 -IHRpY2tldHM= 14741 -aXJtcw== 14742 -IHR1cGxl 14743 -c2lz 14744 -JHNxbA== 14745 -cmln 14746 -IGNvbnZlcnNpb24= 14747 -IGdlcw== 14748 -IGNvbmZpZ3VyZQ== 14749 -ZWdy 14750 -IENh 14751 -IF9fKCc= 14752 -b3VzdG9u 14753 -LnRva2Vu 14754 -QmxhY2s= 14755 -IG1hZ2F6aW5l 14756 -QVc= 14757 -LklO 14758 -b3Npbmc= 14759 -IGJyb2tl 14760 -IENydQ== 14761 -REVMRVRF 14762 -IGRlc3Ryb3llZA== 14763 -KE1hdGg= 14764 -IGFwcHJvdmFs 14765 -LWRvbQ== 14766 -IElJSQ== 14767 -dGFibGVWaWV3 14768 -IGRlc2lnbnM= 14769 -IGNydXNoaW5n 14770 -IGNvbnNlbnQ= 14771 -ZGlybmFtZQ== 14772 -b21w 14773 -IGNyeXB0 14774 -Pyg= 14775 -b3JvdWdo 14776 -MzA3 14777 -Lm8= 14778 -CWxpc3Q= 14779 -YW1zdW5n 14780 -LiIiIgo= 14781 -ZXJyaW5n 14782 -R29vZ2xl 14783 -X3BhaXI= 14784 -X0lOSVQ= 14785 -cmVtYXJrcw== 14786 -IGdlYXI= 14787 -RmlsbA== 14788 -bGlmZQ== 14789 -fSIpCg== 14790 -IHN1aXRhYmxl 14791 -IHN1cnByaXNlZA== 14792 -X1JFUVVFU1Q= 14793 -IG1hbmlmZXN0 14794 -YXR0ZW4= 14795 -IGZydXN0cg== 14796 -b3ZlbWVudA== 14797 -LmNsaWNr 14798 -IGlp 14799 -IGV4cGFuc2lvbg== 14800 -aWdz 14801 -UGFyc2U= 14802 -LlJlZ3VsYXI= 14803 -Um9i 14804 -X2xheW91dA== 14805 -7KA= 14806 -IHRyYW5zbGF0aW9u 14807 -IEJlYXV0 14808 -QmVzdA== 14809 -X0NPTE9S 14810 -PGxhYmVs 14811 -IGxpcXVpZA== 14812 -SVRT 14813 -IHByb2Q= 14814 -MjM5 14815 -IG9wZXJhdGU= 14816 -VUlLaXQ= 14817 -IG5hdHVy 14818 -YXJndW1lbnQ= 14819 -X2RldGFpbA== 14820 -IENlbnRyZQ== 14821 -ICItLQ== 14822 -IH19Ig== 14823 -bG9jYWxl 14824 -LnR2 14825 -X3NlcQ== 14826 -IHVwY29taW5n 14827 -Q2hhcnQ= 14828 -IERpdmlzaW9u 14829 -IGNsaW5pY2Fs 14830 -Q29tcGFueQ== 14831 -U2VwYXI= 14832 -bGFz 14833 -IEh1bg== 14834 -OnM= 14835 -IGhlYWRpbmc= 14836 -0L7Qsw== 14837 -ICIiKTsK 14838 -W2lk 14839 -Ymlh 14840 -IHN0cmV0Y2g= 14841 -aWNpZGU= 14842 -IHJlcHJvZHU= 14843 -LnByb2plY3Q= 14844 -bGVnZW5k 14845 -ZW5kZXJz 14846 -IHJlc3BvbnNlcw== 14847 -IG9udA== 14848 -cml0aWNhbA== 14849 -IHJlZnVnZQ== 14850 -IExp 14851 -IDoKCg== 14852 -IFRocmVl 14853 -LmNvbnRyb2xsZXI= 14854 -X0lOREVY 14855 -X0ZPUg== 14856 -XE1vZGVscw== 14857 -amF4 14858 -CWV4aXQ= 14859 -IOKW 14860 -IGNvdmVycw== 14861 -CXk= 14862 -LS4= 14863 -SU5ET1c= 14864 -IGZhaWxz 14865 -aW5jbHVkZXM= 14866 -IGZhdWx0 14867 -NDQw 14868 -IGx5 14869 -NDQ0 14870 -w7Fv 14871 -LnNsaWNl 14872 -SUxFRA== 14873 -IFB1cg== 14874 -IEFzaWFu 14875 -X2JhdGNo 14876 -Lk1heA== 14877 -dmw= 14878 -IENPUFlSSUdIVA== 14879 -IGdpYW50 14880 -IE1hbnVhbA== 14881 -IENvcHk= 14882 -Q2xhc3NOYW1l 14883 -SGVhbHRo 14884 -Q3Vyc29y 14885 -SUJPdXRsZXQ= 14886 -IHR3ZQ== 14887 -5rM= 14888 -X2xhYmVscw== 14889 -IGNvbGxlY3RlZA== 14890 -IGZ1cm5pdHVyZQ== 14891 -IGRlYWxpbmc= 14892 -Q29udHJvbHM= 14893 -IEhvdGVs 14894 -Y2tz 14895 -IGNob3Nl 14896 -4pSA 14897 -b2Rk 14898 -U1I= 14899 -2Yo= 14900 -7IQ= 14901 -IGFjY29yZA== 14902 -IE1vdmU= 14903 -IE1vZGU= 14904 -IE1vY2s= 14905 -IHRocmVhZHM= 14906 -KysrKw== 14907 -IE9wdGlvbnM= 14908 -UmVmcmVzaA== 14909 -IERpZA== 14910 -J10tPg== 14911 -dWNj 14912 -X2NoYW5uZWw= 14913 -LmFicw== 14914 -IHt9LAo= 14915 -IFdhbA== 14916 -ZXJpb3I= 14917 -IG1haW5seQ== 14918 -IERyaXZlcg== 14919 -Tm90Rm91bmRFeGNlcHRpb24= 14920 -IGNvdW50cw== 14921 -ZWFt 14922 -ICY9 14923 -UXVlc3Rpb24= 14924 -IEFsaQ== 14925 -IGFueW1vcmU= 14926 -ZGV0YWls 14927 -dGFpbA== 14928 -IG1pbGU= 14929 -IEZhaXI= 14930 -IHNvcnJ5 14931 -IHN1cnJvdW5kaW5n 14932 -IGFkbQ== 14933 -RGV2 14934 -IG1hcmlqdWFuYQ== 14935 -IFNvdW5k 14936 -IEFzaA== 14937 -RkQ= 14938 -VGVhbQ== 14939 -LnBvcnQ= 14940 -IFtdCgo= 14941 -dWJibGU= 14942 -IGFzYw== 14943 -IGludGVudGlvbg== 14944 -QWNj 14945 -Y2hp 14946 -dXN0ZXJz 14947 -IGluc3BpcmVk 14948 -c2Vn 14949 -Q0xV 14950 -IG1hbmlw 14951 -TWV0YWRhdGE= 14952 -Q29ubmVjdA== 14953 -IEJlaA== 14954 -IGZpbmRpbmdz 14955 -IGFzc2VtYmx5 14956 -d29ybGQ= 14957 -IHJlbWFpbmVk 14958 -IHVpZA== 14959 -KC4= 14960 -IG14 14961 -TG9vcA== 14962 -CgoKCgo= 14963 -IGZhbnRhc3RpYw== 14964 -d2hv 14965 -YWtp 14966 -IEJhc2lj 14967 -IFlldA== 14968 -IFVzZXJz 14969 -aWtpcA== 14970 -IGhlYWRz 14971 -IE1pY2hpZ2Fu 14972 -X2l0 14973 -IFRvcm9udG8= 14974 -IHJlY29yZGluZw== 14975 -IHN1Ym1pdHRlZA== 14976 -X3ZhcmlhYmxl 14977 -bWVkaWF0ZQ== 14978 -LmdyYXBoaWNz 14979 -IHN0b29k 14980 -IHJlYXI= 14981 -dmVsb2NpdHk= 14982 -X01FU1NBR0U= 14983 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 14984 -cm9sZXM= 14985 -IFRvdXI= 14986 -X3llYXI= 14987 -ZW5kbWVudA== 14988 -YW1wcw== 14989 -IElyZWxhbmQ= 14990 -bWFs 14991 -IHlvdW5nZXI= 14992 -IHN0cnVnZ2xl 14993 -IGNhYmxl 14994 -IFNETA== 14995 -KCct 14996 -YW5lcw== 14997 -IE5lZWQ= 14998 -LlJvdw== 14999 -UG9s 15000 -IFBI 15001 -X3NjcmlwdA== 15002 -YWdlbQ== 15003 -IEJhcw== 15004 -X3NwYWNl 15005 -LmxvYw== 15006 -Omk= 15007 -YWRy 15008 -IGVuZ2luZWVyaW5n 15009 -aXRlbg== 15010 -KSY= 15011 -IHVr 15012 -IExpdHRsZQ== 15013 -X0NPVU5U 15014 -eEE= 15015 -QXJyYXlMaXN0 15016 -5o0= 15017 -ICIiKQo= 15018 -QW5jaG9y 15019 -IGhhbmc= 15020 -dHdpdHRlcg== 15021 -IGNvbXBldGl0aXZl 15022 -LnNyYw== 15023 -44GX 15024 -IHRyYW5zbGF0ZQ== 15025 -IENyZWF0ZXM= 15026 -b29rcw== 15027 -IFJvbGw= 15028 -JycnCg== 15029 -L3No 15030 -c29tZQ== 15031 -RW5jb2Rpbmc= 15032 -LnJlc29sdmU= 15033 -IGRlc2lnbmVy 15034 -IFN0b3JhZ2U= 15035 -IHph 15036 -IE5ldmVy 15037 -IHNvbWV3aGVyZQ== 15038 -IGJveGVz 15039 -LnNvdXJjZQ== 15040 -IHB5Z2FtZQ== 15041 -IGdyb3du 15042 -LnR3 15043 -KCkpLAo= 15044 -JyxbJw== 15045 -IG9wcG9uZW50 15046 -KHNyYw== 15047 -LmxheWVy 15048 -QVBQ 15049 -IEFjdGl2 15050 -IGd1ZXN0cw== 15051 -IFZBTFVFUw== 15052 -fTsKCgo= 15053 -Lm5hdGl2ZQ== 15054 -IGFtb3VudHM= 15055 -LlJF 15056 -IGNsb25l 15057 -IHdlcmVu 15058 -ICI8PA== 15059 -X2Fj 15060 -IGJyZWFraW5n 15061 -IHJlbGlhYmxl 15062 -LlBPU1Q= 15063 -IFNreQ== 15064 -ICcm 15065 -IHNhdmVkSW5zdGFuY2VTdGF0ZQ== 15066 -YXN0aW5n 15067 -aWxsaW9u 15068 -Y29tbWVudHM= 15069 -dWx0eQ== 15070 -Lm1lbnU= 15071 -L2NvbmZpZw== 15072 -IAoKCg== 15073 -VE9ETw== 15074 -IHB1cmNoYXNlZA== 15075 -X2Nvcg== 15076 -CWF1dG8= 15077 -Q29tcGF0QWN0aXZpdHk= 15078 -Y29tcGxldGU= 15079 -X2dyYXBo 15080 -aXNvZGVz 15081 -IHNpdHVhdGlvbnM= 15082 -IEhvcg== 15083 -UmVjZWl2ZQ== 15084 -4oCcV2U= 15085 -IGVudGl0aWVz 15086 -LmFzc2VydEVxdWFscw== 15087 -0L7Qug== 15088 -IFNhbnM= 15089 -dmluY2U= 15090 -cm9tcHQ= 15091 -PQo= 15092 -IC8u 15093 -LlNlbGVjdA== 15094 -eWx2 15095 -IGJhdHQ= 15096 -QXVkaW8= 15097 -IGluY3JlYXNpbmdseQ== 15098 -LkJ1bmRsZQ== 15099 -IGV4cGxhaW5z 15100 -MDYw 15101 -dGhlYXN0 15102 -Lm9mZnNldA== 15103 -IGhhbA== 15104 -IHRlY2huaXF1ZQ== 15105 -X2xpbWl0 15106 -IGRyYXdu 15107 -QVlFUg== 15108 -IGZlYXR1cmVk 15109 -eXl5eQ== 15110 -YXRpbg== 15111 -cGhlbg== 15112 -YWNoZWw= 15113 -IVw= 15114 -bG93ZXI= 15115 -IEdS 15116 -IHBhZw== 15117 -IFBhcnNl 15118 -IHRvdQ== 15119 -5LiA 15120 -RGlzdGFuY2U= 15121 -SW5kZXhQYXRo 15122 -IGhlbGw= 15123 -c2lt 15124 -VVRUT04= 15125 -VXNhZ2U= 15126 -ZWxlbml1bQ== 15127 -IEZhbGw= 15128 -ICIuJA== 15129 -IE11 15130 -IGNydWM= 15131 -IHNvbnQ= 15132 -UkVGSVg= 15133 -MzEx 15134 -IGludGVyaW9y 15135 -IE9seW1w 15136 -LkF1dG9TY2FsZQ== 15137 -cGFyYQ== 15138 -QXhpc0FsaWdubWVudA== 15139 -IHJpdmVy 15140 -RHRv 15141 -IHdpdGhkcmF3 15142 -UmVhY3Q= 15143 -LWNsYXNz 15144 -YmVmb3Jl 15145 -X2FsbG9j 15146 -Q29udGVudHM= 15147 -IFdhcw== 15148 -SUNU 15149 -IGZvcm11bGE= 15150 -IGluZGljYXRlcw== 15151 -ICAgIAoK 15152 -X3N0b3Jl 15153 -aXR0aW5n 15154 -IEl0YWxpYW4= 15155 -X1NldA== 15156 -X3JlcG9ydA== 15157 -IHBpZA== 15158 -X1ZFUg== 15159 -IHdpbnM= 15160 -IENsb3Vk 15161 -Iil7Cg== 15162 -Y2hlc3Rlcg== 15163 -IGRlbmllZA== 15164 -IHdpcmQ= 15165 -IFN0ZXA= 15166 -IGludmVzdG9ycw== 15167 -Ym9sZA== 15168 -X2Rpc3BsYXk= 15169 -b3V2ZXI= 15170 -b3Jlcg== 15171 -UmVzZXQ= 15172 -IHN1cmdlcnk= 15173 -IHN0cmF0ZWdpZXM= 15174 -L21hdGVyaWFs 15175 -X3VuaXQ= 15176 -IGNvdW5jaWw= 15177 -LlBlcg== 15178 -IOKAng== 15179 -IHJlZm9ybQ== 15180 -RnJhbWV3b3Jr 15181 -IGxpc3Rpbmc= 15182 -X2J0bg== 15183 -IGJpcw== 15184 -JWQ= 15185 -ZWdhcw== 15186 -IHN1ZGRlbmx5 15187 -X1NFUg== 15188 -MzE1 15189 -IGFv 15190 -X2RpcmVjdG9yeQ== 15191 -ZmFz 15192 -IHByZW1pdW0= 15193 -IHRyYWNraW5n 15194 -IEJM 15195 -IG1hdHVyZQ== 15196 -IGJhdGhyb29t 15197 -ICcvJw== 15198 -IMSR 15199 -UGVyZm9ybWVk 15200 -IHNvbGRpZXJz 15201 -YXJuaW5ncw== 15202 -IHdhbGtlZA== 15203 -LWNvbg== 15204 -Ym90dG9t 15205 -IHN1cnByaXNpbmc= 15206 -IGdlbmU= 15207 -VXN1YXJpbw== 15208 -LkRFRkFVTFQ= 15209 -IE1JVA== 15210 -Q09ERQ== 15211 -IEVneXB0 15212 -cGlja2Vy 15213 -eXNxbA== 15214 -QVRVUkU= 15215 -ZGV0YWlscw== 15216 -IENvbmZlcmVuY2U= 15217 -SW5mb3JtYXRpb24= 15218 -IE1haWw= 15219 -LWRvd24= 15220 -cmFyaWVz 15221 -YnJv 15222 -IHN1YmplY3Rz 15223 -ICcq 15224 -6K+3 15225 -b3JpZW50 15226 -OkA= 15227 -dmVyYm9zZQ== 15228 -RUY= 15229 -IHRvbGVy 15230 -MzEz 15231 -ZW5nZXJz 15232 -IGVuZHBvaW50 15233 -IHN0cmFuZ2U= 15234 -IGNvbG9u 15235 -IHByZWZlcnJlZA== 15236 -ZGVw 15237 -IEVW 15238 -QVJSQVk= 15239 -IHdoZQ== 15240 -IHB1cA== 15241 -X25vZGVz 15242 -IHRhbGtlZA== 15243 -IGluc3RpdHV0aW9u 15244 -ZGJj 15245 -IGV4cG9zZWQ= 15246 -dGVlbg== 15247 -IEZyb250 15248 -VFQ= 15249 -X05PTkU= 15250 -XC9cLw== 15251 -cHJvZ3JhbQ== 15252 -IGVuY291cmFnZQ== 15253 -LmA= 15254 -c2hpcmU= 15255 -IElzbGFt 15256 -MzI1 15257 -ZWVu 15258 -Tkk= 15259 -JyI= 15260 -LldpZHRo 15261 -IGxpa2Vk 15262 -IHsuLi4= 15263 -IFN5c3RlbXM= 15264 -IHZvdHJl 15265 -IG1hbnVmYWN0dXJpbmc= 15266 -Q29udmVydGVy 15267 -IEluZg== 15268 -7Jo= 15269 -RFRP 15270 -IGluY2hlcw== 15271 -IOCk 15272 -w7k= 15273 -IENoYXJsZXM= 15274 -QlU= 15275 -IikpOwoK 15276 -IExhYm9y 15277 -dW5u 15278 -IGVzdGlt 15279 -bW9iaWxl 15280 -IExlYXJu 15281 -Mjgx 15282 -X0NBTEw= 15283 -4oQ= 15284 -IGluZGljZXM= 15285 -IHR1Yg== 15286 -Mjg4 15287 -aWtpcGVkaWE= 15288 -Q29zdA== 15289 -cm93YWJsZQ== 15290 -66E= 15291 -Z2FnZQ== 15292 -IGZ1bmN0aW9uYWxpdHk= 15293 -dXp6bGU= 15294 -ZW1vcw== 15295 -LmxpYg== 15296 -IGRhc3M= 15297 -0LXQug== 15298 -ZW5uYQ== 15299 -IHNob3Rz 15300 -IHJlc3RvcmU= 15301 -L0Q= 15302 -Rm9yS2V5 15303 -XSxb 15304 -YWxpYXM= 15305 -bGludA== 15306 -LnN0cmVhbQ== 15307 -5qA= 15308 -X0ZPUk1BVA== 15309 -IHNpbHZlcg== 15310 -LnJlcG9zaXRvcnk= 15311 -IGxlZ2lzbA== 15312 -LkJvcmRlcg== 15313 -X2ZlYXR1cmVz 15314 -UGVybWlzc2lvbg== 15315 -IGhvdXNlcw== 15316 -IFdhcnM= 15317 -X0NPTVA= 15318 -IGluanVyaWVz 15319 -IGNvbnN0YW50bHk= 15320 -Zmx1dHRlcg== 15321 -RU5V 15322 -IENvbmY= 15323 -IHJlY29nbml6ZWQ= 15324 -IHByYWN0aWNhbA== 15325 -IGRlY2VudA== 15326 -Qko= 15327 -XSk7 15328 -YXN0eQ== 15329 -IEFjdGl2aXR5 15330 -LW1vZGU= 15331 -IHNsaWRl 15332 -LklzTnVsbE9yRW1wdHk= 15333 -IFlPVQ== 15334 -UG93ZXI= 15335 -aW5kaWNlcw== 15336 -IHF1YWxpZmllZA== 15337 -IHRocm93bg== 15338 -aGVsbG8= 15339 -MzE2 15340 -IE5pY2s= 15341 -bGFo 15342 -YXNzZW1ibHk= 15343 -IFNtYWxs 15344 -b2xkaW5n 15345 -U2hvdWxk 15346 -IFNpbHZlcg== 15347 -KHNhdmVkSW5zdGFuY2VTdGF0ZQ== 15348 -IHRvZ2dsZQ== 15349 -Lk5vdA== 15350 -Q3RybA== 15351 -Om5pbA== 15352 -IENvbnRpbnVl 15353 -IEJvb3Q= 15354 -5ok= 15355 -IE11cg== 15356 -ZG9u 15357 -IEZB 15358 -U25hcHNob3Q= 15359 -IGFzc29jaWF0aW9u 15360 -Zm94 15361 -LGE= 15362 -YXppb25l 15363 -XSkNCg== 15364 -Q1RZUEU= 15365 -IGZhZGU= 15366 -IERhcg== 15367 -Lm5hdmlnYXRpb24= 15368 -IGx1Y2s= 15369 -U0NSSQ== 15370 -IERlYWQ= 15371 -IHRlcm1pbmFs 15372 -X0xFTkdUSA== 15373 -IGVmZmljaWVuY3k= 15374 -IHVudw== 15375 -IG5hcnJvdw== 15376 -aW1lbnRv 15377 -KENvbG9y 15378 -IFNlYQ== 15379 -X2FyZWE= 15380 -LEE= 15381 -X29wdA== 15382 -IEhpbGxhcnk= 15383 -LnRhc2s= 15384 -IEphYw== 15385 -YXN0ZWQ= 15386 -IEFkYW0= 15387 -IElsbGVnYWw= 15388 -IHNlYXJjaGluZw== 15389 -SW5zdGFuY2VPZg== 15390 -SmF2YQ== 15391 -IEZvcm1hdA== 15392 -IHJlYWxpemVk 15393 -IENoaWxkcmVu 15394 -IGtpbA== 15395 -KGZyYW1l 15396 -4oCdLgoK 15397 -IHNjZW5hcmlv 15398 -Il0pOwo= 15399 -IGluY3JlZGlibGU= 15400 -bGl4 15401 -SU9FeGNlcHRpb24= 15402 -IFF1ZXN0 15403 -aWx0eQ== 15404 -IHVubG9jaw== 15405 -4oKs 15406 -IHJlZmVyZW5jZXM= 15407 -IFZlcnQ= 15408 -QmluZGluZw== 15409 -ZWdhdGl2ZQ== 15410 -IHdyYXA= 15411 -LmRhdGFiYXNl 15412 -KGNvbnRlbnQ= 15413 -QnVm 15414 -IFRyYWQ= 15415 -IEF1ZA== 15416 -dHJhY2U= 15417 -Lm1vY2s= 15418 -IHRoZXJhcHk= 15419 -CUw= 15420 -LlRvSW50 15421 -IEtpbmdkb20= 15422 -QnVz 15423 -aGF1c3Q= 15424 -IiIiCgo= 15425 -KGVuZA== 15426 -LmRyYXdhYmxl 15427 -W107Cg== 15428 -IEhvc3BpdGFs 15429 -IHBoYXJt 15430 -LS0tLS0= 15431 -IEFH 15432 -w6lk 15433 -PiIpOwo= 15434 -IHdhbGxldA== 15435 -YXRhYmxl 15436 -KSQ= 15437 -IG1vbnRobHk= 15438 -IGRpYWdub3N0aWM= 15439 -U3ltYm9s 15440 -IGl0ZXJhdG9y 15441 -dW5maW5pc2hlZA== 15442 -IGltbWlncmF0aW9u 15443 -c3I= 15444 -Uk9X 15445 -KGdhbWU= 15446 -IGNsb3RoZXM= 15447 -IFVudA== 15448 -IGFjdGl2YXRpb24= 15449 -X0Nvbg== 15450 -Mjcz 15451 -Lmhhc2g= 15452 -IGluaXRpYWxseQ== 15453 -Lkhhc2g= 15454 -IGN1dHM= 15455 -Zm91bmQ= 15456 -IFN0b3J5 15457 -0YbQuA== 15458 -YWNhbw== 15459 -X1RZUA== 15460 -cHJvdG8= 15461 -ZXN0cg== 15462 -LXBhZ2U= 15463 -YWhy 15464 -IGluY29ycmVjdA== 15465 -IEpvc2VwaA== 15466 -VGV4dEJveENvbHVtbg== 15467 -X3N0eWxl 15468 -IERhbmllbA== 15469 -c2hlZXQ= 15470 -IGxpdg== 15471 -bGluZWQ= 15472 -IHJh 15473 -UnVudGltZQ== 15474 -X2VtcHR5 15475 -c2x1Zw== 15476 -X3N0cnVjdA== 15477 -64o= 15478 -bXU= 15479 -IHBlcm1pdHRlZA== 15480 -IHJlZ2lvbmFs 15481 -IHNvYnJl 15482 -IFN1Y2g= 15483 -IFtf 15484 -IHJvb2Y= 15485 -LkFsaWdubWVudA== 15486 -dGltZXM= 15487 -Lm1zZw== 15488 -IGNoZXN0 15489 -IFRhYg== 15490 -IGVzdGE= 15491 -w6Ru 15492 -IHN1YnNjcmlwdGlvbg== 15493 -KGNvbW1hbmQ= 15494 -c3BlY2lhbA== 15495 -IG1lYWw= 15496 -Iik6Cg== 15497 -X2N0eA== 15498 -IGNsb3NlbHk= 15499 -MzA5 15500 -ZXRyeQ== 15501 -LWJl 15502 -YWRlbA== 15503 -IFJhbQ== 15504 -aWdlc3Q= 15505 -IFNwYW5pc2g= 15506 -IGNvbW1pdG1lbnQ= 15507 -IHdha2U= 15508 -Kj4o 15509 -UEhQ 15510 -X3s= 15511 -Y2tlcg== 15512 -PExpc3Q= 15513 -X251bGw= 15514 -Mzkw 15515 -IFJlc2VydmVk 15516 -IGluaGVy 15517 -LkNvbHVtbnM= 15518 -LkFzcE5ldA== 15519 -X0lOVkFMSUQ= 15520 -IFBhcmFtZXRlcg== 15521 -IGV4cHI= 15522 -fXs= 15523 -Q2VsbFN0eWxl 15524 -IHZhbHVhYmxl 15525 -IGZ1bm55 15526 -SW52 15527 -IHN0YWJsZQ== 15528 -KnQ= 15529 -IHBpbGw= 15530 -Mjk5 15531 -cGxpZXJz 15532 -IENTUw== 15533 -IENvbmRpdGlvbg== 15534 -IFNwZWVk 15535 -dWJsaXNoZXI= 15536 -MjU5 15537 -IG9mZmVuc2l2ZQ== 15538 -Y2VzdA== 15539 -aWNhcw== 15540 -IHNwYXJr 15541 -IFByb3Rl 15542 -c2V0dXA= 15543 -SUZZ 15544 -IFRheA== 15545 -V2hv 15546 -RmFtaWx5 15547 -LWZvcg== 15548 -LnVr 15549 -IGZhc2M= 15550 -c3Zn 15551 -IikpLg== 15552 -IGJpcnRoZGF5 15553 -4paI 15554 -dmVo 15555 -ZWxsZWQ= 15556 -IGltcG9ydHM= 15557 -IElzbGFtaWM= 15558 -VEE= 15559 -IFN0YW4= 15560 -d2VhdGhlcg== 15561 -IHN1c3BlY3Q= 15562 -ZWF0dXJl 15563 -ZW5uZXM= 15564 -V00= 15565 -Lm1pbmVjcmFmdA== 15566 -YXZpZA== 15567 -6L0= 15568 -LnNlY3VyaXR5 15569 -aW5vcw== 15570 -R29vZA== 15571 -IG1hcmNo 15572 -NjU1 15573 -MjU3 15574 -IHBvc3Nlc3M= 15575 -dXN1YXJpbw== 15576 -Q29ucw== 15577 -YW1iZXI= 15578 -Y2hlZHVsZXI= 15579 -IGhvcnNl 15580 -570= 15581 -KGJvZHk= 15582 -IFRyYW5zZm9ybQ== 15583 -X2RlY29kZQ== 15584 -LnN2Zw== 15585 -IGZvbw== 15586 -IGRlbGxh 15587 -ZXh0ZW5kcw== 15588 -YW1lcg== 15589 -IHByb2Nlc3NlZA== 15590 -IEhhcnI= 15591 -IEFJ 15592 -IGtv 15593 -Q0hBUg== 15594 -KCU= 15595 -IHRhcA== 15596 -KHsn 15597 -Y3JvbGw= 15598 -RE9N 15599 -IHRlYQ== 15600 -IHJlaW4= 15601 -MjYx 15602 -IHdvcmxkd2lkZQ== 15603 -X2Zu 15604 -c2hh 15605 -IGJpcg== 15606 -w6fDtWVz 15607 -PSIjIj4= 15608 -IHJlcHJlc2VudGVk 15609 -aWxsZXI= 15610 -KGV4cGVjdGVk 15611 -IGRhbmNl 15612 -IHZpc2l0b3Jz 15613 -LmNvbmNhdA== 15614 -LWJpdA== 15615 -VVJSRQ== 15616 -IFJvZw== 15617 -dnA= 15618 -aXBo 15619 -IExMQw== 15620 -aXRsZWQ= 15621 -aWFtaQ== 15622 -Q29sbA== 15623 -X3JlYWw= 15624 -X3Nob3c= 15625 -X2ZvbGRlcg== 15626 -IGRhcg== 15627 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 15628 -IGxhdHRlcg== 15629 -YXJjaHk= 15630 -IGJvdw== 15631 -IG91dGNvbWU= 15632 -NTEw 15633 -IFBvc3RlZA== 15634 -IHJpc2tz 15635 -IFRoZXJlZm9yZQ== 15636 -IG93bmVyc2hpcA== 15637 -IHBhcmFsbGVs 15638 -IHBlbmRpbmc= 15639 -Z2VvbWV0cnk= 15640 -IHJlY29nbml6ZQ== 15641 -U1RFTQ== 15642 -IENQ 15643 -IGltbWlncg== 15644 -SVRMRQ== 15645 -ICAgIAkJ 15646 -Y29ubmVjdGVk 15647 -IHNtaWxl 15648 -KGRvY3VtZW50 15649 -XENvbXBvbmVudA== 15650 -dmVydGljYWw= 15651 -IGNvbnN1bXB0aW9u 15652 -IHNob2Vz 15653 -LmltcGw= 15654 -dW5rcw== 15655 -LiI7Cg== 15656 -IGZvb2Rz 15657 -Xyk7Cg== 15658 -LmFzc2VydFRydWU= 15659 -IHBpcGVsaW5l 15660 -IGNvbGxlY3Rpb25z 15661 -IGVhcm5lZA== 15662 -IENlcnQ= 15663 -IHBhcnRuZXJzaGlw 15664 -KGFjdGlvbg== 15665 -MjYz 15666 -IGNk 15667 -IFZlcnk= 15668 -T3B0aW9uYWw= 15669 -IHNjcmVlbnM= 15670 -IHRpdGxlcw== 15671 -ZW5lcmF0b3I= 15672 -IGFiYW5kb24= 15673 -a2luZA== 15674 -SUxURVI= 15675 -IGNsb3Npbmc= 15676 -bGljYQ== 15677 -X2ludGVy 15678 -IGNhbXB1cw== 15679 -c2V0dGluZw== 15680 -U3ByaXRl 15681 -44Gv 15682 -X3JlcGx5 15683 -VG9MaXN0 15684 -OlwvXC8= 15685 -ZWRl 15686 -IGZvbGtz 15687 -IGJvYXQ= 15688 -KGFyZ3Y= 15689 -IHBlcm1hbmVudA== 15690 -IGNhcnJ5aW5n 15691 -IGNvbnNlcnZhdGl2ZQ== 15692 -aW1wb3J0YW50 15693 -LmltZw== 15694 -IEltbQ== 15695 -IGRpbWVuc2lvbnM= 15696 -YWxhbmQ= 15697 -c2luZ2xl 15698 -RXhpdA== 15699 -LS0tLS0tLS0tLQ== 15700 -YXJpYW50 15701 -dGVybmFs 15702 -U2Vjb25kcw== 15703 -IEl0YWx5 15704 -b3RsaW4= 15705 -LlJlc3VtZQ== 15706 -PSci 15707 -KT09 15708 -Y2VwdG9y 15709 -IHNjYQ== 15710 -L21haW4= 15711 -U2VjdXJpdHk= 15712 -X2RhdA== 15713 -IGxldHM= 15714 -IGFxdQ== 15715 -IHdoZW5ldmVy 15716 -YmVycnk= 15717 -IGFjdGluZw== 15718 -YW50aQ== 15719 -cGQ= 15720 -Jmd0 15721 -5q0= 15722 -Wm9uZQ== 15723 -VG9kYXk= 15724 -IS4= 15725 -MzIz 15726 -VG9Qcm9wcw== 15727 -YWJpcw== 15728 -aXRhYmxl 15729 -IGdhbA== 15730 -XXs= 15731 -aXpvbmE= 15732 -IGluY29udHJp 15733 -TkVU 15734 -Ly8vCg== 15735 -W2lu 15736 -X3NhdmU= 15737 -IGV4ZW0= 15738 -IEtlbm4= 15739 -IGV2b2x1dGlvbg== 15740 -Mjcy 15741 -dmFycw== 15742 -X3N0YXRz 15743 -LW9ubHk= 15744 -IENvbG9yYWRv 15745 -IHdhdGNoZWQ= 15746 -Ym91cg== 15747 -IHNldmVyZQ== 15748 -IHByb2Zlc3Npb25hbHM= 15749 -cG9ydGlvbg== 15750 -IGd1YXJhbnRl 15751 -0LM= 15752 -IHB1c2hlZA== 15753 -IEdp 15754 -770= 15755 -IHR1bQ== 15756 -IEF6 15757 -IEVkZ2VJbnNldHM= 15758 -IikpOw0K 15759 -aXNzZQ== 15760 -LmFj 15761 -U2V0dGluZw== 15762 -IGFwcHJlY2lhdGU= 15763 -IFZhbHVlRXJyb3I= 15764 -IHN1cnZl 15765 -IFJvbGU= 15766 -LkludGVy 15767 -cGxvdGxpYg== 15768 -amV0 15769 -ZGFt 15770 -IHBsYXRmb3Jtcw== 15771 -dGVsZQ== 15772 -VVRP 15773 -IEludGVybmFs 15774 -Kzo= 15775 -fTsNCg== 15776 -R2VuZXJhbA== 15777 -XEVudGl0eQ== 15778 -IGxhd3llcg== 15779 -cXVpdg== 15780 -IFBvc3Rz 15781 -aXNv 15782 -IGFjY3Vt 15783 -b2Jl 15784 -IG1hcmtz 15785 -IF07Cgo= 15786 -CXRleHQ= 15787 -LnN1Y2Nlc3M= 15788 -Y3Vycg== 15789 -YXNh 15790 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 15791 -IHRoaW4= 15792 -X292ZXI= 15793 -MDE2 15794 -YXJlc3Q= 15795 -IE9z 15796 -KGFkZHJlc3M= 15797 -IHZlbG9jaXR5 15798 -IFtdOwoK 15799 -PSIuLi8uLi8= 15800 -IFByaXY= 15801 -Ym93 15802 -IGd1YXJhbnRlZQ== 15803 -JQoK 15804 -MzIy 15805 -IGV2YWx1YXRl 15806 -LkxFTkdUSA== 15807 -IGludmVudG9yeQ== 15808 -cWE= 15809 -X2RlYnVn 15810 -Lk9uQ2xpY2tMaXN0ZW5lcg== 15811 -IGxpZXM= 15812 -IGFzc2Vzc21lbnQ= 15813 -ZGF0ZXRpbWU= 15814 -LmJhY2tncm91bmRDb2xvcg== 15815 -ICovDQoNCg== 15816 -cmFm 15817 -dW53cmFw 15818 -IEZvb3Q= 15819 -IG5vdGlmeQ== 15820 -IGxvd2VzdA== 15821 -RE9DVFlQRQ== 15822 -IGxhbmd1YWdlcw== 15823 -ZXh0cmE= 15824 -LWJhY2s= 15825 -IGVpbmVu 15826 -dGVtcGxhdGVz 15827 -Mjcx 15828 -X3Bhc3M= 15829 -NTIw 15830 -Nzc3 15831 -IE11c3Q= 15832 -IGVzdMOh 15833 -X2NvcmU= 15834 -IFNjb3Q= 15835 -QUk= 15836 -IGJpYXM= 15837 -YXRpb25zaGlw 15838 -Q29uc3RhbnQ= 15839 -IHByb2dyYW1taW5n 15840 -SW5z 15841 -dXNwZW5kTGF5b3V0 15842 -IFBST1ZJRA== 15843 -YW50ZXM= 15844 -IHNoaXJ0 15845 -aW5hdGVk 15846 -Lk9L 15847 -W2E= 15848 -IHRoaW5rcw== 15849 -PwoKCgo= 15850 -IHJlZ2FyZGxlc3M= 15851 -IE1hZ2lj 15852 -dWxhdGluZw== 15853 -CWNsYXNz 15854 -YWRkR3JvdXA= 15855 -UkVBVEU= 15856 -IFNV 15857 -IHNpbXBs 15858 -Y29weXJpZ2h0 15859 -IGJ1bmNo 15860 -IHVuaXZlcnNl 15861 -OTUw 15862 -IEVycg== 15863 -IHByZXNlbnRhdGlvbg== 15864 -Y2F0ZWdvcmllcw== 15865 -IGF0dGFjaA== 15866 -LnNpZ24= 15867 -X0FD 15868 -IGRpc2NpcGw= 15869 -IHJlZ3VsYXJseQ== 15870 -IHByaW1hcmlseQ== 15871 -aW5rcw== 15872 -W1s= 15873 -LnJhbmQ= 15874 -LnNob3VsZA== 15875 -b3dudG93bg== 15876 -PSIn 15877 -IHNhbnM= 15878 -IHN1cHBvcnRlcnM= 15879 -c2VxdWVuY2U= 15880 -R08= 15881 -Li4KCg== 15882 -IFNwcg== 15883 -IGNhcmVmdWxseQ== 15884 -VUlDb2xvcg== 15885 -ZGVzdHJveQ== 15886 -IHRvZG9z 15887 -IE9SREVS 15888 -b3R0ZWQ= 15889 -IGRvbnQ= 15890 -YXVkaQ== 15891 -X3BsYXllcg== 15892 -Z3Jl 15893 -NjI1 15894 -IE9pbA== 15895 -PGJvZHk= 15896 -X3N0YWNr 15897 -LlBhZGRpbmc= 15898 -IFByb2R1Y3Rz 15899 -IHByaXZpbGU= 15900 -MDE0 15901 -IGluanVyZWQ= 15902 -IEZ1cnRoZXI= 15903 -IGFsaWFz 15904 -LlJlc3VtZUxheW91dA== 15905 -X0xFTg== 15906 -IHNlcw== 15907 -J107Cgo= 15908 -Y3JlZW5z 15909 -IGRpcmVjdGVk 15910 -LlN1c3BlbmRMYXlvdXQ= 15911 -b2RnZQ== 15912 -LkF0 15913 -bWFya3M= 15914 -IFVuaXZlcnM= 15915 -ZXJ0cw== 15916 -IEVzYw== 15917 -IG5hdmJhcg== 15918 -IHV0aWxpdHk= 15919 -YWdub3N0aWNz 15920 -IGluamVjdA== 15921 -IEROQQ== 15922 -ICIsIg== 15923 -YW1hcg== 15924 -IGV1 15925 -IHJlc3RhdXJhbnRz 15926 -X3B1dA== 15927 -dXRlcnM= 15928 -VG9vbFN0cmlw 15929 -dHc= 15930 -aXN0cm8= 15931 -IHpvb20= 15932 -IGxlZ2l0 15933 -cGVjaWZpYw== 15934 -Mjg1 15935 -IENvbWU= 15936 -IGxvY2FsU3RvcmFnZQ== 15937 -IGFic29y 15938 -LlBhbmVs 15939 -IERlc2lnbmVy 15940 -IG93 15941 -SUNBTA== 15942 -X3VyaQ== 15943 -KGZpZWxk 15944 -IHN1cGVydg== 15945 -RXhpc3Rz 15946 -IHJlc3BlY3RpdmVseQ== 15947 -IFN0YW5k 15948 -Q29uZg== 15949 -dXNzaWFu 15950 -MzY0 15951 -IGFyYw== 15952 -IG5k 15953 -dWNrcw== 15954 -IHJlc3Ry 15955 -IHNlYXNvbnM= 15956 -IENoYXB0ZXI= 15957 -IFN3aXRjaA== 15958 -cGlj 15959 -IGhp 15960 -bG9hZGVk 15961 -IGZsdWlk 15962 -LWJ0bg== 15963 -IHJ1bnRpbWU= 15964 -Lml0 15965 -MjU4 15966 -Qk4= 15967 -T3BhY2l0eQ== 15968 -YXNhbnQ= 15969 -cnlwdGlvbg== 15970 -LW5hdGl2ZQ== 15971 -IHRhdWdodA== 15972 -5a8= 15973 -YWdtZW50 15974 -IG11bA== 15975 -UmVnaXN0cnk= 15976 -X2dyaWQ= 15977 -IEJyb29r 15978 -OlNldA== 15979 -IG1vbmdvb3Nl 15980 -QU1FUw== 15981 -aW5uZXJIVE1M 15982 -IHNvY2k= 15983 -IEludGVs 15984 -Z2V0SWQ= 15985 -Q21k 15986 -IGFjY2Vzc2libGU= 15987 -cmFtZXM= 15988 -bGV0b24= 15989 -IF9fKA== 15990 -CWRlbGV0ZQ== 15991 -IFNxdWFyZQ== 15992 -IgoKCg== 15993 -IGJ1Y2tldA== 15994 -YXZvcml0ZQ== 15995 -IEJyZWFr 15996 -Kytd 15997 -IGJydXNo 15998 -MjY2 15999 -IHRlbnNvcg== 16000 -L2h0dHA= 16001 -VGlsZQ== 16002 -IGZ1bmN0aW9uYWw= 16003 -ICIq 16004 -d2hlbA== 16005 -IHRlbnQ= 16006 -IENoYXJhY3Rlcg== 16007 -IHNlZXM= 16008 -LlNU 16009 -Qmln 16010 -IGV4dGVybg== 16011 -VXJscw== 16012 -KSkpKSw= 16013 -IEpy 16014 -LkJ1aWxkZXI= 16015 -Ljs= 16016 -bmw= 16017 -X0luaXQ= 16018 -IEhFUg== 16019 -xbxl 16020 -bXlzcWxp 16021 -X2ljb24= 16022 -dmFu 16023 -IGZlZWxpbmdz 16024 -IGxlYW4= 16025 -IGhvcGluZw== 16026 -VFY= 16027 -PSI8Pz0= 16028 -IGN1cnZl 16029 -X3N0ZA== 16030 -X0xJTkU= 16031 -ZHN0 16032 -IG1vcmFs 16033 -ZW1lcw== 16034 -b2d5 16035 -IHVyYmFu 16036 -MDE1 16037 -IGFzaWRl 16038 -IGVkaXRpbmc= 16039 -QURE 16040 -U2Vjb25k 16041 -VHJhY2s= 16042 -IHZvdGluZw== 16043 -IGhvbm9y 16044 -Lics 16045 -ZWxsZW4= 16046 -Q2hhdA== 16047 -IGltcHJvdmVtZW50 16048 -J10KCg== 16049 -oIE= 16050 -IHBhcnNlZA== 16051 -ICAgICAgICAgCg== 16052 -IGxhenk= 16053 -IGZhbGxpbmc= 16054 -U2VyaWFsaXpl 16055 -IFBh 16056 -X2dy 16057 -IGZvcmV2ZXI= 16058 -LndoaXRl 16059 -LlF1ZXJ5 16060 -QmVk 16061 -IER1 16062 -IHJlc3VtZQ== 16063 -IHBhcGVycw== 16064 -IEluaXQ= 16065 -IHN1ZmZlcmluZw== 16066 -4oCL 16067 -IGRlY2xhcmF0aW9ucw== 16068 -KCkt 16069 -IGV4ZWN1dGVk 16070 -IEhvbA== 16071 -LmJsb2Nr 16072 -44Oz 16073 -U0s= 16074 -IHN0dWNr 16075 -IExvY2s= 16076 -aW5jaXBhbA== 16077 -TnVsbGFibGU= 16078 -IHNlc3Npb25z 16079 -dW5p 16080 -IGNvdXA= 16081 -YXBwcm8= 16082 -Z2hhbg== 16083 -X3Bvb2w= 16084 -Mjgz 16085 -CWlk 16086 -IHNsb3Rz 16087 -IG1lZGljaW5l 16088 -IGdsYWQ= 16089 -IE1vbm9CZWhhdmlvdXI= 16090 -YXRyZQ== 16091 -ICQoJw== 16092 -bWVyaWNhbg== 16093 -YWdn 16094 -IGthbm4= 16095 -X2Nvbm5lY3Q= 16096 -IGJyYW5kcw== 16097 -IHNrZQ== 16098 -IGRpZ2l0 16099 -PG4= 16100 -IGJhY2t1cA== 16101 -IHBlcnNvbmFsbHk= 16102 -LlByb3BlcnR5 16103 -MzE0 16104 -LmNvbW1pdA== 16105 -IGNyeQ== 16106 -X2NvdW50ZXI= 16107 -IG1hbGxvYw== 16108 -IGdyYW4= 16109 -IERyb3A= 16110 -cGxhdGZvcm0= 16111 -cmVkZW50aWFscw== 16112 -aW5raW5n 16113 -IFVJTA== 16114 -dWJz 16115 -IG1s 16116 -bGVzc2x5 16117 -R2VuZXJhdGVk 16118 -ZXJlb3R5cGU= 16119 -IGJhdA== 16120 -TGF5b3V0UGFuZWw= 16121 -TE9U 16122 -Iik7DQoNCg== 16123 -IG11c2NsZQ== 16124 -IGNlcnRpZmljYXRl 16125 -QU5ETEU= 16126 -IGhhcmRlcg== 16127 -IHBpeGVscw== 16128 -KSIsCg== 16129 -LkhlYWRlcg== 16130 -IGRldmVsb3Blcg== 16131 -IExhcw== 16132 -ZWdhbg== 16133 -Ljw= 16134 -IGV4cGxvZGU= 16135 -IHBhcnRpY2lwYXRl 16136 -UGF0dGVybg== 16137 -KHRhYmxl 16138 -IFRFWFQ= 16139 -Y29uc3RhbnRz 16140 -eEQ= 16141 -dGhldw== 16142 -fSwKCg== 16143 -44Gu 16144 -X2Rlcw== 16145 -IHN1YnN0cg== 16146 -IFNtYXJ0 16147 -IHNjYWxh 16148 -Z2VudA== 16149 -LWJhcg== 16150 -ZXNzaW9uYWw= 16151 -dW1icw== 16152 -LmV4ZWM= 16153 -J1w= 16154 -VEs= 16155 -dW5pc3Q= 16156 -cHJvb2Y= 16157 -Y2lhbA== 16158 -cHJvYw== 16159 -PXsi 16160 -LmhyZWY= 16161 -PSQo 16162 -IGx1bmNo 16163 -aXNjYWw= 16164 -IEVudHJ5 16165 -IG91dGRvb3I= 16166 -c2VtYmxl 16167 -IGVzc2VudGlhbGx5 16168 -L0c= 16169 -W10p 16170 -JSI= 16171 -c3Rlbg== 16172 -VVNFRA== 16173 -IGR1c3Q= 16174 -5bA= 16175 -CQoK 16176 -IHJldGlyZQ== 16177 -IGZpYg== 16178 -QWx0aG91Z2g= 16179 -IGxvdmVz 16180 -IHJlYWRz 16181 -eWNsZXM= 16182 -IEhlbA== 16183 -X3VpbnQ= 16184 -ICcuJA== 16185 -X2luaXRpYWw= 16186 -TmFtZWQ= 16187 -IGZ1bmRhbWVudGFs 16188 -QURJTkc= 16189 -IHRvdw== 16190 -IEFERA== 16191 -IEFjYWRlbXk= 16192 -MDUw 16193 -OlN0cmluZw== 16194 -IGNvbXByZWhlbnNpdmU= 16195 -LnNjYWw= 16196 -IE1ldGE= 16197 -TWVzc2FnZXM= 16198 -LmFubm90YXRpb25z 16199 -XFJlc3BvbnNl 16200 -IGFja25vd2xlZA== 16201 -IEFSRQ== 16202 -XT09 16203 -IGNsZWFuaW5n 16204 -6L4= 16205 -RW50aXRpZXM= 16206 -IFNhbGVz 16207 -IFdpcw== 16208 -LmV4dGVuZA== 16209 -YWxsZW5nZQ== 16210 -IGdhbWluZw== 16211 -JHF1ZXJ5 16212 -SUNFUw== 16213 -RVRDSA== 16214 -SG9yaXpvbnRhbA== 16215 -cXVlbnRpYWw= 16216 -ODUw 16217 -QkFDSw== 16218 -ZGV2ZWxvcA== 16219 -aXNvcg== 16220 -KGNvZGU= 16221 -LUs= 16222 -X1BJTg== 16223 -cmVxdWVuY3k= 16224 -IFF1ZXN0aW9u 16225 -X2NvbnRhaW5lcg== 16226 -X21vZHVsZXM= 16227 -IEplcnNleQ== 16228 -X2RpZmY= 16229 -LmVs 16230 -ICooKA== 16231 -Y250 16232 -IFNh 16233 -Q1BQ 16234 -aW5pdGU= 16235 -IHVudXM= 16236 -LXdoaXRl 16237 -ZXRhcnk= 16238 -IGludm9sdmluZw== 16239 -ID8+DQo= 16240 -YmVzdA== 16241 -YWxsYXM= 16242 -ZW50ZWQ= 16243 -ICAgICAgICAgICAgICAgICAgICAgICAgCg== 16244 -X2Nvbm5lY3Rpb24= 16245 -IHJlcG8= 16246 -ZW5hYmxlZA== 16247 -0LDQug== 16248 -IHNoYQ== 16249 -IG1lbWJlcnNoaXA= 16250 -U3RhdHVzQ29kZQ== 16251 -aW5hdGluZw== 16252 -X3Nt 16253 -X2N1c3RvbQ== 16254 -X3dlaWdodA== 16255 -IGNzcw== 16256 -U3RhdA== 16257 -X2Vudg== 16258 -bGlua3M= 16259 -VFJM 16260 -IEhpdA== 16261 -LHI= 16262 -dXBpZA== 16263 -IG9wZW5z 16264 -IGdlbnQ= 16265 -X3Zpcw== 16266 -IGpveQ== 16267 -PHc= 16268 -X2Nvc3Q= 16269 -IFB5T2JqZWN0 16270 -cmVuY2U= 16271 -IEdlb3JnaWE= 16272 -IEJyb2Fk 16273 -bW1h 16274 -4oI= 16275 -cGY= 16276 -ICJcIg== 16277 -ICgm 16278 -b21v 16279 -IGxpdGVyYWxseQ== 16280 -iJg= 16281 -bWV0cmlj 16282 -IGJhcnM= 16283 -emVk 16284 -KHdpbmRvdw== 16285 -IElzcmFlbGk= 16286 -IGZvcm1hbA== 16287 -aWRlbnRpZmllcg== 16288 -LmRhbw== 16289 -IERlYXRo 16290 -JTsK 16291 -IGRlY2xhcmU= 16292 -YXJtcw== 16293 -UkVBTQ== 16294 -UEVSVFk= 16295 -IGNvbnNlcXVlbmNlcw== 16296 -dG9vbHM= 16297 -UGVvcGxl 16298 -IFdoaWNo 16299 -PigpOw0K 16300 -LmRlY29kZQ== 16301 -X0FDVA== 16302 -QnV0dG9ucw== 16303 -LmZsb2F0 16304 -LkZpcnN0 16305 -66U= 16306 -IFBvbGl0 16307 -IFhDVA== 16308 -VGFncw== 16309 -IENHRmxvYXQ= 16310 -PXN0cg== 16311 -IGxlYWY= 16312 -LWNoZWNr 16313 -IElzcw== 16314 -LnN5c3RlbQ== 16315 -bG9nb3V0 16316 -YWNodA== 16317 -QW5nbGU= 16318 -c2lu 16319 -Y2hhcnQ= 16320 -SU5URVI= 16321 -IE5VTQ== 16322 -QmFzaWM= 16323 -LlByb3BlcnRpZXM= 16324 -5Lit 16325 -X2NoYW5nZQ== 16326 -IEJyYXppbA== 16327 -QWJzdHJhY3Q= 16328 -IDorOg== 16329 -X3VzZQ== 16330 -0LDQuw== 16331 -MjY4 16332 -IEx5 16333 -SUJVVA== 16334 -IG91dGVy 16335 -IC0tPg0K 16336 -IHJlbGllZg== 16337 -bGFw 16338 -cXVlcg== 16339 -X3BhcmVudA== 16340 -aGVhcA== 16341 -TE9TRQ== 16342 -IGNvbWJpbmU= 16343 -IFJvc2U= 16344 -b3dlcnM= 16345 -IHByb2NlZHVyZXM= 16346 -IFNvcnQ= 16347 -YW5pbQ== 16348 -dmFyaWFudA== 16349 -ZWhpY2xl 16350 -IHNpZ25pbmc= 16351 -UHJpbWFyeQ== 16352 -Y3VycmVuY3k= 16353 -IHNleGU= 16354 -b2Vu 16355 -dGhldGE= 16356 -ZW1hbg== 16357 -IGltcHJlc3NpdmU= 16358 -KCdf 16359 -CVU= 16360 -IFRleHRTdHlsZQ== 16361 -X2NudA== 16362 -IHNsaWNl 16363 -KCc6 16364 -IHVuZGVyc3Rvb2Q= 16365 -SGlz 16366 -Mjc3 16367 -MDEz 16368 -IGluZm9ybWVk 16369 -IG5pY2s= 16370 -NDI5 16371 -KFRBRw== 16372 -aGQ= 16373 -IGVsZWN0aW9ucw== 16374 -ZXN0dXJl 16375 -IFNhbnRh 16376 -IENvYXN0 16377 -LnBkZg== 16378 -aW5jaXBsZQ== 16379 -LmNsb25l 16380 -Ym9ybg== 16381 -dXRh 16382 -IGxpY2Vuc2Vk 16383 -Q3I= 16384 -IGJyZWFk 16385 -IEhvdXN0b24= 16386 -IG5vZA== 16387 -IGhvcGVz 16388 -IENHUmVjdA== 16389 -IGd1aWx0eQ== 16390 -LmdpZg== 16391 -IHJvc2U= 16392 -LkNvbW1vbg== 16393 -VGlw 16394 -QU5L 16395 -IEZD 16396 -RHVyaW5n 16397 -IFN5bWZvbnk= 16398 -IGRlZmVuc2l2ZQ== 16399 -a20= 16400 -KT4= 16401 -YXJjaGl2ZQ== 16402 -IFVSSQ== 16403 -eWNsaW5n 16404 -LW8= 16405 -IFdlYnNpdGU= 16406 -QU1Q 16407 -NDA1 16408 -aXNobWVudA== 16409 -IGRvY3RvcnM= 16410 -RGlyZWN0 16411 -QVJJ 16412 -IFJlZGlyZWN0 16413 -aWVyZW4= 16414 -OTYw 16415 -X2Rpc3Q= 16416 -eW8= 16417 -IFByb2dyZXNz 16418 -IHp1bQ== 16419 -IG1lbW9y 16420 -IEVE 16421 -IGp1cg== 16422 -5o2u 16423 -X1RBQkxF 16424 -IHV1aWQ= 16425 -RXhwcg== 16426 -LmhlYWQ= 16427 -KCcl 16428 -cG9pbnRlcg== 16429 -IGVzdGltYXRl 16430 -IEdyZWc= 16431 -IGxvYWRlcg== 16432 -IGlPUw== 16433 -IG1lbnM= 16434 -W3k= 16435 -IHJlZnVzZWQ= 16436 -IHByZWNpc2lvbg== 16437 -aXNjaA== 16438 -IEFDVElPTg== 16439 -Q2xvdWQ= 16440 -c1dpdGg= 16441 -KHJldA== 16442 -Mjky 16443 -X0FERFI= 16444 -X2NvbmY= 16445 -KGRm 16446 -IGxvY2tlZA== 16447 -IHJpc2luZw== 16448 -44O744O7 16449 -IE1z 16450 -IHNjZW5lcw== 16451 -X0VYVA== 16452 -X3Jhdw== 16453 -X3RoZQ== 16454 -cGVvcGxl 16455 -IHJlY29u 16456 -IEZ1bg== 16457 -IGJsZXNz 16458 -IFVwZGF0ZWQ= 16459 -NDIy 16460 -w7xu 16461 -ICAgICAgICAgICAgDQo= 16462 -cGVjdGlvbg== 16463 -UmVsZWFzZQ== 16464 -LmxvZ2dlcg== 16465 -IFNZ 16466 -IGNvdW5zZWw= 16467 -dXJk 16468 -X3RydWU= 16469 -IGV2ZXJ5Ym9keQ== 16470 -aXZvdA== 16471 -IGhlbmNl 16472 -IE5BUw== 16473 -Nzg5 16474 -IG9wcG9zZWQ= 16475 -dW5rbm93bg== 16476 -IERFU0M= 16477 -IENoYWly 16478 -ZmFpbGVk 16479 -IElOQ0xVRElORw== 16480 -Mzg2 16481 -MzUy 16482 -IHdyaXRlcnM= 16483 -e30K 16484 -w610 16485 -X2NvcHk= 16486 -fTo= 16487 -IEJhdA== 16488 -IGNvbnZlcnRlZA== 16489 -ZWRpbmc= 16490 -cGxhY2VtZW50 16491 -IEhvc3Q= 16492 -U291bmQ= 16493 -0LjQvA== 16494 -IHNvdWdodA== 16495 -NDAy 16496 -bWlk 16497 -IHNhbGFyeQ== 16498 -b2dn 16499 -4oSi 16500 -YnVs 16501 -IHdpcg== 16502 -dmFsaWRhdG9y 16503 -X1NUQVQ= 16504 -LnN0b3Jl 16505 -IEJhdHRsZQ== 16506 -xLFu 16507 -IC0tPgoK 16508 -VHJ1bXA= 16509 -ZG90 16510 -IENPTlQ= 16511 -LmZldGNo 16512 -IGNvbnRpbnU= 16513 -d2Fz 16514 -IGZyYXVk 16515 -X3RtcA== 16516 -bWl0dGVy 16517 -LnBpY3R1cmVCb3g= 16518 -R0E= 16519 -IHRvdXJuYW1lbnQ= 16520 -LklucHV0 16521 -MzQz 16522 -W3I= 16523 -ZXhpb24= 16524 -Y2VudGFnZQ== 16525 -IEtvcmVhbg== 16526 -dW5kZWY= 16527 -IEF2YWlsYWJsZQ== 16528 -cmVzaGFwZQ== 16529 -IGtpdA== 16530 -IFN0cnVjdA== 16531 -IFNVQg== 16532 -QW5zd2Vy 16533 -X2xpYg== 16534 -LnR3aXR0ZXI= 16535 -IG9yZQ== 16536 -IERyYWdvbg== 16537 -LkV4dA== 16538 -LGs= 16539 -IGV4cGxhbmF0aW9u 16540 -cmVmcw== 16541 -IERyaXZl 16542 -IFRyYWluaW5n 16543 -Mjgy 16544 -Lkhhcw== 16545 -MzQx 16546 -aW50YWdl 16547 -Ymln 16548 -b2xvZ2lzdA== 16549 -ZW5uaXM= 16550 -NDYw 16551 -2Yc= 16552 -IGNoaWNrZW4= 16553 -ICAgICAgICAgIAo= 16554 -55s= 16555 -44Gn 16556 -IHBlYWs= 16557 -IGRyaW5raW5n 16558 -IGVuY29kZQ== 16559 -IE5FVw== 16560 -bWFsbG9j 16561 -CWZwcmludGY= 16562 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09 16563 -aW5jbHVkaW5n 16564 -IHByaW5jaXBsZXM= 16565 -IE1haA== 16566 -MjY3 16567 -c3RvcmFnZQ== 16568 -LWtleQ== 16569 -IGtleXdvcmQ= 16570 -JTs= 16571 -IHRyYWluZWQ= 16572 -LmNvbnRyaWI= 16573 -IGt2 16574 -X18nOgo= 16575 -IEJveQ== 16576 -cGFyYW1ldGVy 16577 -IHN1aXRl 16578 -IHRob3VzYW5k 16579 -IGNvb3JkaW5hdGU= 16580 -LWdlbmVyYXRlZA== 16581 -7ZWY 16582 -Z2VuZXJhdGVk 16583 -IGFkbWl0dGVk 16584 -IHB1c3N5 16585 -I3c= 16586 -IHN3aW0= 16587 -dW5pb24= 16588 -TmE= 16589 -Mjc0 16590 -IFJveWFs 16591 -LmNoYW5uZWw= 16592 -VXBkYXRlZA== 16593 -X1JPT1Q= 16594 -IHZpdGFs 16595 -MzM1 16596 -cmFjdGlvbg== 16597 -IENydXNoZXI= 16598 -IHByZWNlZA== 16599 -IGhvcml6b250YWw= 16600 -Qmx1ZXByaW50 16601 -IGF0dHJz 16602 -IHNtb2tl 16603 -0JI= 16604 -LkVxdWFscw== 16605 -RkI= 16606 -IFJlc291cmNlcw== 16607 -cm9sbGluZw== 16608 -IHBhc3Nlcw== 16609 -IE51bQ== 16610 -cm90YXRl 16611 -ZXR5cGU= 16612 -XCIs 16613 -IHNlbnNpdGl2ZQ== 16614 -IHRhbGw= 16615 -P+KAnQoK 16616 -UHJveHk= 16617 -aXk= 16618 -X3NlY3Rpb24= 16619 -4oCU4oCU4oCU4oCU 16620 -YnJpZA== 16621 -IGNpcmN1aXQ= 16622 -YXRhbg== 16623 -RU5D 16624 -IGRyaXZlbg== 16625 -IHZvdGVk 16626 -IGVkdWNhdGlvbmFs 16627 -IGludGVyYWN0aW9u 16628 -YWJldGVz 16629 -IHRvbmU= 16630 -IEluaXRpYWxpemVDb21wb25lbnQ= 16631 -IG1lcmVseQ== 16632 -IOye 16633 -Y29va2ll 16634 -X2Rpdg== 16635 -IFVJTGFiZWw= 16636 -dmVseQ== 16637 -fSk7DQo= 16638 -X0VOVA== 16639 -IysjKw== 16640 -YXJ0aWNsZXM= 16641 -IFNvdXRoZXJu 16642 -IHN0cm9uZ2Vy 16643 -IEdpdmVu 16644 -IEVyaWM= 16645 -IElS 16646 -YWJzdHJhY3Q= 16647 -VW5kZXI= 16648 -bmFibGU= 16649 -IGluY3JlbWVudA== 16650 -b3Zlbg== 16651 -IGNvaW4= 16652 -X3RpbWVy 16653 -IHN1ZmZlcmVk 16654 -IEZSRUU= 16655 -J10uIg== 16656 -IFF1ZWVu 16657 -c3RhdHM= 16658 -IG1lZXRpbmdz 16659 -Mjc2 16660 -IGVudGVyaW5n 16661 -IGFsb25nc2lkZQ== 16662 -KHNlc3Npb24= 16663 -aXRhbHM= 16664 -IGZvdW5kYXRpb24= 16665 -IENyZWRpdA== 16666 -LmRpdg== 16667 -X0FMTA== 16668 -cGNpb24= 16669 -X3N0YXQ= 16670 -aWNraW5n 16671 -RGVmYXVsdHM= 16672 -X3NyYw== 16673 -IG91dHB1dHM= 16674 -L0I= 16675 -IGVudGh1cw== 16676 -LWJs 16677 -LkZvcmVDb2xvcg== 16678 -CXRlbXA= 16679 -RmFjZQ== 16680 -IGludGVyYWN0 16681 -IHdlaXJk 16682 -TW91bnQ= 16683 -cmVsbA== 16684 -dWRlbnRz 16685 -IHJlcXVpcmVtZW50 16686 -IFN1cw== 16687 -SUVS 16688 -IGVsZWN0ZWQ= 16689 -cmVmZXJlbmNl 16690 -IE1F 16691 -IHNlcnZlcnM= 16692 -LndhaXQ= 16693 -IHNuYXBzaG90 16694 -aWx0b24= 16695 -IHRyaWVz 16696 -IHRpcG8= 16697 -LlRpbWU= 16698 -Pnc= 16699 -IG1vdW50YWlu 16700 -IHBvdW5kcw== 16701 -IFsuLi4= 16702 -ZXhpc3Rz 16703 -IG5nT24= 16704 -X01BUA== 16705 -IGZseWluZw== 16706 -MzMx 16707 -eGlldHk= 16708 -CXZhbHVl 16709 -X0RC 16710 -dW5v 16711 -IHNlYXRz 16712 -VFVSTg== 16713 -LmF1dGhvcg== 16714 -ISk= 16715 -b3JjZQ== 16716 -IGluZGljYXRlZA== 16717 -MzE3 16718 -LnNpbg== 16719 -IGFzc2lnbm1lbnQ= 16720 -aW1pZW50bw== 16721 -IEZyYW1l 16722 -MzI0 16723 -X2dlbg== 16724 -aW5lcnk= 16725 -Xyk= 16726 -bWVzc2FnZXM= 16727 -LnNldHRpbmdz 16728 -IE1lYW4= 16729 -IE11c2V1bQ== 16730 -aXJx 16731 -YXR0YWNo 16732 -IFBhbGVzdGlu 16733 -X1FV 16734 -X3RhZ3M= 16735 -IGNhc3VhbA== 16736 -ZW1lbg== 16737 -QVNTV09SRA== 16738 -NDMy 16739 -JHM= 16740 -IENpcmM= 16741 -0L7QuQ== 16742 -ZXRyaWM= 16743 -L1A= 16744 -MDE4 16745 -IGVwb2No 16746 -PGhlYWQ= 16747 -X0NNRA== 16748 -IGdpdA== 16749 -IHBlbmFsdHk= 16750 -b3JwaA== 16751 -X3VzZXJz 16752 -b3Vyc2Vz 16753 -LkRhdGVUaW1l 16754 -YXRlcm5pb24= 16755 -X3Byb2plY3Q= 16756 -IHN1cGVyaW9y 16757 -IERhbQ== 16758 -IFNlYXR0bGU= 16759 -WFk= 16760 -PlRoZQ== 16761 -IEFr 16762 -IGdyYXNz 16763 -LyoNCg== 16764 -KGRpcw== 16765 -IGd1bnM= 16766 -IHRi 16767 -IEtldmlu 16768 -LmFyZ3M= 16769 -IEFo 16770 -b3BlZA== 16771 -KEo= 16772 -Y29sdW1ucw== 16773 -YXJndW1lbnRz 16774 -IFdpdGhFdmVudHM= 16775 -X2Z1bGw= 16776 -IERlZmVuc2U= 16777 -U2ltcGxl 16778 -IGRlYXRocw== 16779 -Mjk1 16780 -IGV4dGVuc2l2ZQ== 16781 -IFN0aWxs 16782 -IEV4cHJlc3Npb24= 16783 -IEFnZW5jeQ== 16784 -IHBlcmZvcm1pbmc= 16785 -Rlg= 16786 -IHVzdWFyaW8= 16787 -VUFM 16788 -U2lkZQ== 16789 -b2Rvcw== 16790 -YXB0b3A= 16791 -IGNyZWRlbnRpYWxz 16792 -X2NhcA== 16793 -YXRpZW50 16794 -IERpc25leQ== 16795 -IGFp 16796 -IGNoaXA= 16797 -IHZvbHQ= 16798 -Lm1ha2VUZXh0 16799 -JSUlJSUlJSUlJSUlJSUlJQ== 16800 -IGJlbGllZg== 16801 -X0xPQw== 16802 -IENpdmls 16803 -TmF2aWdhdGlvbg== 16804 -IHJldmVhbA== 16805 -IHZpb2xlbnQ= 16806 -IEZpbA== 16807 -IGNhdGFsb2c= 16808 -ZW1lZA== 16809 -c2Nhbg== 16810 -LmNvbnRyb2w= 16811 -IGNvbnN0aXR1dGlvbg== 16812 -Q291bnRyeQ== 16813 -U2VwYXJhdG9y 16814 -X0FQUA== 16815 -dG9waWM= 16816 -dWV0b290aA== 16817 -TUlO 16818 -IGRlc2NyaXB0b3I= 16819 -eXQ= 16820 -RVRIRVI= 16821 -IGRpc3RyaWJ1dGU= 16822 -J30K 16823 -LnRyaW0= 16824 -LkxpbmU= 16825 -IGxibA== 16826 -YXNzZXJ0RXF1YWxz 16827 -IERldA== 16828 -b21ib2s= 16829 -KHdpZHRo 16830 -IHRvcnQ= 16831 -IEVYUFJFU1M= 16832 -YWNv 16833 -VXNpbmc= 16834 -IEJyYW5k 16835 -d2FsbA== 16836 -RU1FTlQ= 16837 -IENvbW11bmlj 16838 -PHVpbnQ= 16839 -IEdVSQ== 16840 -RUdJTg== 16841 -IFJhbmdl 16842 -L2k= 16843 -IFRheWxvcg== 16844 -Y29zdA== 16845 -IHJlc3BvbmRlZA== 16846 -IFRoZW1l 16847 -bmNl 16848 -SVNI 16849 -IGZlYXR1cmluZw== 16850 -UmV0dXJucw== 16851 -IEty 16852 -IC4K 16853 -IG5hbQ== 16854 -X2Ni 16855 -VGVzdGluZw== 16856 -IHt9LA== 16857 -eWFs 16858 -LmZpZWxk 16859 -IC89 16860 -X1NIT1JU 16861 -bWF0ZXM= 16862 -VGVzdENhc2U= 16863 -YWlubGVzcw== 16864 -IGV2YWx1YXRpb24= 16865 -X0lURU0= 16866 -IFBhY2lmaWM= 16867 -CWs= 16868 -IGNhbnQ= 16869 -IFJvcw== 16870 -KXM= 16871 -IGZldA== 16872 -U1RSSU5H 16873 -MzE5 16874 -IERpc3Bvc2U= 16875 -Z2Fs 16876 -IEpvaW4= 16877 -IFBvcm4= 16878 -IENhdGhvbGlj 16879 -QVJHRVQ= 16880 -Y3B1 16881 -56CB 16882 -LnNjcm9sbA== 16883 -MzI4 16884 -SVNJTkc= 16885 -aWZlc3R5bGU= 16886 -YW5jZW1lbnQ= 16887 -IG1lcmM= 16888 -IEJyb3dzZXI= 16889 -ZXRlcm1pbg== 16890 -IG92ZXJmbG93 16891 -QXZhaWxhYmxl 16892 -IGJvdHRsZQ== 16893 -OlVJ 16894 -aWZpY2lhbA== 16895 -IGNvb3Jk 16896 -Y2xhcmF0aW9u 16897 -IGNvbmo= 16898 -R0xPQkFM 16899 -b2t1 16900 -IGt3YXJncw== 16901 -Y29uZGl0aW9ucw== 16902 -dWx1bQ== 16903 -IGdlbnU= 16904 -IEhlcm8= 16905 -5Y4= 16906 -IHVuZXhwZWN0ZWQ= 16907 -IERBTUFHRVM= 16908 -IGth 16909 -IENvdWxk 16910 -VVBQT1JU 16911 -IFBob3Rvcw== 16912 -IGNvbmZpZGVudA== 16913 -IGRldGVjdGVk 16914 -ZGVn 16915 -cmdi 16916 -IHN0cm9uZ2x5 16917 -IH07DQo= 16918 -ICk6 16919 -IGxlY3Q= 16920 -dXJzaXZl 16921 -Uk9M 16922 -IFdlaWdodA== 16923 -IGVudGVydGFpbm1lbnQ= 16924 -ICkpOwo= 16925 -IGdvbm5h 16926 -IGJi 16927 -LmRv 16928 -R1M= 16929 -IG1pc3Rha2U= 16930 -REw= 16931 -IFBST1ZJREVE 16932 -ZWFybmluZw== 16933 -TGltaXQ= 16934 -aXNzaW9ucw== 16935 -W3Y= 16936 -5LiN 16937 -aXJ0eQ== 16938 -RGVs 16939 -IHVuZGVybHlpbmc= 16940 -cHJlbmU= 16941 -IGphdw== 16942 -IERJ 16943 -cGVlcg== 16944 -IG9iamVjdGl2ZQ== 16945 -IGRlcG9zaXQ= 16946 -IGtvbg== 16947 -IGVzcA== 16948 -Mjc4 16949 -LnNldFZpc2liaWxpdHk= 16950 -L2xvZ2lu 16951 -PHR5cGVuYW1l 16952 -IGZyYW5jaA== 16953 -L2U= 16954 -MjY5 16955 -UGFyYWxsZWw= 16956 -IHNjb3JlZA== 16957 -IEhvbg== 16958 -IFZpbGw= 16959 -aWdh 16960 -IGFudGljaXA= 16961 -X2Fzc2VydA== 16962 -IE9wdA== 16963 -IGRlc2NyaWJlcw== 16964 -d2Fu 16965 -bW91bnQ= 16966 -IG1vbml0b3Jpbmc= 16967 -IHRvdXQ= 16968 -64qU 16969 -fSx7 16970 -Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4= 16971 -PWludA== 16972 -IGN1c3Q= 16973 -LS0tLS0t 16974 -IGF0bW9zcGhlcmU= 16975 -UEFS 16976 -b3J0ZQ== 16977 -SVNJQkxF 16978 -IElyb24= 16979 -IE5vdGlmaWNhdGlvbg== 16980 -LmxvZ2dpbmc= 16981 -IEJPT0w= 16982 -LXBvaW50 16983 -IGFmcmFpZA== 16984 -ZW50YQ== 16985 -IHRvbW9ycm93 16986 -QGltcGxlbWVudGF0aW9u 16987 -IGVuZ2FnZQ== 16988 -IEFudGg= 16989 -IEZsb29y 16990 -IFVs 16991 -VG9vbHM= 16992 -IGJhYg== 16993 -IGNhcmVmdWw= 16994 -44GE 16995 -IGNydWNpYWw= 16996 -IGNhbGN1bGF0ZWQ= 16997 -IFNB 16998 -IHd5 16999 -OTEx 17000 -RFg= 17001 -X1RBRw== 17002 -aW5kZWQ= 17003 -IGpldA== 17004 -IEVuZ2luZWVyaW5n 17005 -Lk1BWA== 17006 -ZW56 17007 -dmQ= 17008 -IHB1YmxpY2F0aW9u 17009 -ICMjIw== 17010 -IGZhY2Vk 17011 -cmFoYW0= 17012 -IENhcHQ= 17013 -MzM2 17014 -QXNzZXQ= 17015 -IENvbnN0YW50cw== 17016 -IGxvYW5z 17017 -X0lQ 17018 -IEZpc2g= 17019 -UmVkdWM= 17020 -X21hdA== 17021 -RGF0ZUZvcm1hdA== 17022 -X21l 17023 -W11bXQ== 17024 -IGludGVncml0eQ== 17025 -IENvdXJzZQ== 17026 -bG9iYWxz 17027 -IGZhY2lsaXQ= 17028 -IGVtYnI= 17029 -IE5n 17030 -LlN5c3RlbQ== 17031 -IG1hbnVmYWN0dXJlcnM= 17032 -IHByb3Zlbg== 17033 -Lm9uQ3JlYXRl 17034 -IGFsYXJt 17035 -IMKn 17036 -IGNvbW1vbmx5 17037 -aWNvcw== 17038 -5paw 17039 -IFN0YXRpb24= 17040 -fSku 17041 -IEZpbG0= 17042 -d2k= 17043 -54k= 17044 -IGVuZ2FnZWQ= 17045 -U3RhdHM= 17046 -IGdvdmVybm1lbnRz 17047 -NTQw 17048 -IGFmZm9yZGFibGU= 17049 -X3Byb3BlcnR5 17050 -IGFnZXM= 17051 -KCctLQ== 17052 -IGbDtnI= 17053 -IFByb2Zlc3Nvcg== 17054 -IGh5ZHJv 17055 -UHVzaA== 17056 -IG9yZ2FuaXplZA== 17057 -Mjg0 17058 -QWNjZXB0 17059 -w6lt 17060 -X2NlbGw= 17061 -IG5i 17062 -cGI= 17063 -QXJ0aWNsZQ== 17064 -IHJlbW92YWw= 17065 -IGF1dGhlbnRpY2F0aW9u 17066 -IEZS 17067 -bGlkZQ== 17068 -IHBsZWFzdXJl 17069 -YXBvbA== 17070 -IHBhcnRpdGlvbg== 17071 -IFNpZGU= 17072 -IGNyaW1lcw== 17073 -IGRlbW8= 17074 -aG9sZGVycw== 17075 -IFBha2lzdGFu 17076 -SW5zdHJ1Y3Rpb24= 17077 -IGV4cGVjdGF0aW9ucw== 17078 -MzMy 17079 -LnNjZW5l 17080 -ICcp 17081 -aGVz 17082 -aW5vaXM= 17083 -X1Bybw== 17084 -IG1vbGVj 17085 -YW5kYWw= 17086 -X3Nob3J0 17087 -IGRlZmF1bHRz 17088 -IG5hdGlvbnM= 17089 -aW5lbg== 17090 -IHJ0 17091 -T0NL 17092 -UGFja2V0 17093 -U0I= 17094 -IFNIQUxM 17095 -X2NvbnRlbnRz 17096 -aXNlY29uZHM= 17097 -dmVydHk= 17098 -w6F0 17099 -R3VpZA== 17100 -bm9t 17101 -IGNvbmNsdXNpb24= 17102 -LlVwZGF0ZQ== 17103 -IGxvdmVseQ== 17104 -IGVtaXQ= 17105 -YmVj 17106 -CQkJCSA= 17107 -IGludGVsbGVjdA== 17108 -IGJyZXc= 17109 -ZWN5Y2xl 17110 -RmlyZQ== 17111 -MzU4 17112 -IGFkbWl0 17113 -IGFyYml0 17114 -IGFycmFuZw== 17115 -IE1JTg== 17116 -TWFpbA== 17117 -IE5hdGl2ZQ== 17118 -Q3Vy 17119 -IGNvbnZlbnQ= 17120 -LlJ1bnRpbWU= 17121 -In0K 17122 -LlJ1bg== 17123 -IHByaW50ZWQ= 17124 -IGNvbnZlbmllbnQ= 17125 -LmFy 17126 -bW9jaw== 17127 -IEFkbWluaXN0cmF0aW9u 17128 -44G+ 17129 -IGVsZWN0cm9u 17130 -ZmxhdGU= 17131 -IGxvbWJvaw== 17132 -IGphdmFmeA== 17133 -bmg= 17134 -IHN1cHBsaWVz 17135 -IHZpc2l0aW5n 17136 -YWhs 17137 -IHBvd2Rlcg== 17138 -IHVsdGltYXRl 17139 -IG9yaWVudGF0aW9u 17140 -dXRhcw== 17141 -X3NjYWxl 17142 -Q29uZmlybQ== 17143 -cGhvbmVz 17144 -IE9wZXJhdGlvbg== 17145 -L1Q= 17146 -NDQz 17147 -X0lOVEVS 17148 -IGFpcnBvcnQ= 17149 -IG1ldHJpY3M= 17150 -IHBoZW5vbWVu 17151 -YXVkaW8= 17152 -MzM0 17153 -IG1haQ== 17154 -KEs= 17155 -aHU= 17156 -YWxsaW5n 17157 -cm9kdWN0aW9u 17158 -IFRyYW5zcG9ydA== 17159 -IE5PVEU= 17160 -5paH 17161 -IGZld2Vy 17162 -X1RJTQ== 17163 -7Kc= 17164 -0LrQuA== 17165 -QWdl 17166 -RklO 17167 -Mjk0 17168 -IOyd 17169 -IEF0dHJpYnV0ZQ== 17170 -Z3JvdXBz 17171 -ZXJr 17172 -YXR0bw== 17173 -LmRlZmluZQ== 17174 -LkFzcE5ldENvcmU= 17175 -YXRlZ29yaWE= 17176 -IFNpcg== 17177 -KGZvcm0= 17178 -PFVzZXI= 17179 -LnJvdW5k 17180 -X2RheQ== 17181 -LkFsbA== 17182 -U2VydmxldFJlc3BvbnNl 17183 -Lk5v 17184 -bGFyZ2U= 17185 -SUdI 17186 -cXVlbnQ= 17187 -IHZpcnVz 17188 -IHJldHJv 17189 -IGltcGVy 17190 -Qml0bWFw 17191 -IHZpY2U= 17192 -IG9mZmVuc2U= 17193 -aXN0ZQ== 17194 -IEFVVEg= 17195 -IOqw 17196 -VG9vbFN0cmlwTWVudUl0ZW0= 17197 -R3U= 17198 -IHJhcGU= 17199 -IERhdmlz 17200 -IG92ZXJ3aGVs 17201 -OmZsdXR0ZXI= 17202 -LXRhYmxl 17203 -IENvbnN0cnVjdG9y 17204 -UHJpdmF0ZQ== 17205 -ZXZlbg== 17206 -Y2hy 17207 -IGFwcGxpZXM= 17208 -X2F0dHJpYnV0ZQ== 17209 -IGNvbnRyaWJ1dGU= 17210 -RVZFUg== 17211 -Mjg5 17212 -TGluZXM= 17213 -IEFmZ2hhbg== 17214 -VmlzaXRvcg== 17215 -IFNM 17216 -c2Vhc29u 17217 -Q1U= 17218 -IGludHJvZHVjdGlvbg== 17219 -IG1hdHBsb3RsaWI= 17220 -xZE= 17221 -IG5ld3NwYXBlcg== 17222 -4oCUYW5k 17223 -PHRhZw== 17224 -IGluaQ== 17225 -IGRpdmVyc2U= 17226 -SWdub3JlQ2FzZQ== 17227 -MzUz 17228 -IFVy 17229 -QWdlbnQ= 17230 -IGJ1bGw= 17231 -LmVtaXQ= 17232 -KEV4Y2VwdGlvbg== 17233 -YXJMYXlvdXQ= 17234 -IGluY3JlZGlibHk= 17235 -IFRydXN0 17236 -PXso 17237 -LW5hdg== 17238 -IGVxdWFscw== 17239 -IGxhZHk= 17240 -IFBvZA== 17241 -ZGlzYw== 17242 -YWxhbQ== 17243 -IElW 17244 -4pk= 17245 -aXZpZHVhbA== 17246 -cGhp 17247 -MDE3 17248 -YWRkZWQ= 17249 -IGRpZmZpY3VsdHk= 17250 -IGNvbXBhY3Q= 17251 -NTMw 17252 -IEFjdGlvblJlc3VsdA== 17253 -Y2Vycw== 17254 -X2NsYXNzZXM= 17255 -Tm9uTnVsbA== 17256 -IHF1aXQ= 17257 -IHBvdQ== 17258 -U3dpdGNo 17259 -aXJz 17260 -LXRlc3Q= 17261 -IEtpbmQ= 17262 -IENhbGVuZGFy 17263 -NDA2 17264 -IHN0cmVhbWluZw== 17265 -fScs 17266 -Mjc5 17267 -U1c= 17268 -IHN0ZWFk 17269 -b2Nh 17270 -IHByb3ZpbmNl 17271 -OTc4 17272 -IGNvbHNwYW4= 17273 -IHBlcnNvbm5lbA== 17274 -IEVtcGxveWVl 17275 -IHByb2R1Y2Vy 17276 -IGV2ZXJ5d2hlcmU= 17277 -b2Ri 17278 -0J8= 17279 -YnNvbHV0ZQ== 17280 -YWN0aXZhdGU= 17281 -IGdyaW5kaW5n 17282 -IEJ1aWxkaW5n 17283 -IFNhbmRlcnM= 17284 -KHNj 17285 -IE9mZnNldA== 17286 -Ly8vLy8vLy8vLy8v 17287 -fTsNCg0K 17288 -KHsi 17289 -IHNjYW5m 17290 -IFlZ 17291 -CWRlZmVy 17292 -IGpldw== 17293 -IHJlc3RyaWN0aW9ucw== 17294 -Lm1w 17295 -W2w= 17296 -5LiL 17297 -bGFiZWxz 17298 -cmVkaWNhdGU= 17299 -YXdlc29tZQ== 17300 -IHdhdmVz 17301 -IGNvbmZyb250 17302 -IG1lYXN1cmVk 17303 -IGRhdGFz 17304 -X2V4aXQ= 17305 -MzU1 17306 -b3R0b24= 17307 -IHNob3VsZGVy 17308 -YXNrYQ== 17309 -KyM= 17310 -ICAgICAgICAKICAgICAgICAK 17311 -IHRyb29wcw== 17312 -Mjkz 17313 -IFVuZA== 17314 -X2NhcmQ= 17315 -d2ljaA== 17316 -IG5vdXM= 17317 -ICIvIg== 17318 -c2I= 17319 -IGNvbW11bmljYXRpb25z 17320 -RXhwb3J0 17321 -IGRlY29kZQ== 17322 -dGhz 17323 -aW50ZXJwcmV0 17324 -QnlOYW1l 17325 -IFNwaXJpdA== 17326 -ZWRnZXM= 17327 -T0xF 17328 -IEVN 17329 -dGl0 17330 -IFRocm91Z2g= 17331 -IGJpbw== 17332 -IFBhY2thZ2U= 17333 -b3JuZQ== 17334 -Mjkx 17335 -IH0u 17336 -NDEx 17337 -YDsK 17338 -IG9rYXk= 17339 -IFplYWxhbmQ= 17340 -aWRlbnRpdHk= 17341 -KG5leHQ= 17342 -IEJhbmc= 17343 -TGlicmFyeQ== 17344 -IGhlYXZpbHk= 17345 -aWxvbg== 17346 -IGRpcGw= 17347 -IHJvdGF0ZQ== 17348 -cHV0cw== 17349 -KScsCg== 17350 -IERhdGFUYWJsZQ== 17351 -IG1heW9y 17352 -LnRvTG93ZXJDYXNl 17353 -IHNvbWVob3c= 17354 -IE5vcnRoZXJu 17355 -YWxj 17356 -IGNhcGFiaWxpdGllcw== 17357 -IHZpYnI= 17358 -Kwo= 17359 -IFN1 17360 -Mjg2 17361 -IFJlc2V0 17362 -X21lYW4= 17363 -IGNpZw== 17364 -LmNsb3Vk 17365 -IEJhbmQ= 17366 -IEZhY3Rvcnk= 17367 -IEFyaXpvbmE= 17368 -X2lv 17369 -b3BoZXI= 17370 -IGNvbnNjaW91cw== 17371 -IMO2 17372 -XENvbnRyb2xsZXJz 17373 -X3NwZWVk 17374 -IEZhYw== 17375 -X0NvbQ== 17376 -IEJpYmxl 17377 -d2Vu 17378 -RURJVA== 17379 -IHVubg== 17380 -IFN0YWZm 17381 -IElubg== 17382 -IG1lY2hhbmlzbQ== 17383 -IE1lbWJlcnM= 17384 -IG1pZ3JhdGlvbkJ1aWxkZXI= 17385 -J10uJw== 17386 -LmdldEludA== 17387 -PHZvaWQ= 17388 -CWZyZWU= 17389 -b2lkcw== 17390 -XFN1cHBvcnQ= 17391 -IGF1dG9tYXRpYw== 17392 -IGNoYW5jZXM= 17393 -0LY= 17394 -IGNvbXBsaWNhdGVk 17395 -W3Jvdw== 17396 -YWhvbw== 17397 -IH0KCgoK 17398 -TW9kZWxz 17399 -V2lu 17400 -IHRhcGU= 17401 -aXJ1cw== 17402 -aXpvbg== 17403 -b25vbXk= 17404 -KCJf 17405 -Oi4= 17406 -LnN0ZXJlb3R5cGU= 17407 -Mjk2 17408 -KGVudg== 17409 -X3JlY3Q= 17410 -KHdpdGg= 17411 -IGFzc2VydFRoYXQ= 17412 -IGNvbnN0cmFpbnRz 17413 -cHV0eQ== 17414 -RW1wbG95ZWU= 17415 -NjIw 17416 -VEQ= 17417 -IGd1aXRhcg== 17418 -ODc1 17419 -IEpld3M= 17420 -LnByb2Nlc3M= 17421 -IGZpY3Rpb24= 17422 -IFNoYXJlZA== 17423 -4pSA4pSA 17424 -IHByb3BhZw== 17425 -Lk5ldA== 17426 -IGFjaGlldmVk 17427 -CVE= 17428 -IG51cnM= 17429 -U2hhcmVk 17430 -X0ZBSUxVUkU= 17431 -IGJlaGF2aW91cg== 17432 -IGNvbHM= 17433 -aXNtbw== 17434 -IGZlbWlu 17435 -IGNoYWxsZW5naW5n 17436 -IHBvc3Rpbmc= 17437 -ZW5jaWw= 17438 -IGNhcHR1cmVk 17439 -IERvdQ== 17440 -KHdvcmQ= 17441 -IFR1cmtleQ== 17442 -cGFuaWVz 17443 -IHJlcHV0YXRpb24= 17444 -T1JNQUw= 17445 -IGVsaWdpYmxl 17446 -cHJvdG9jb2w= 17447 -NDE0 17448 -aWRhcw== 17449 -KGZyb20= 17450 -MzQ0 17451 -IGZpbmFuY2U= 17452 -LXBlcg== 17453 -IGdvdHRlbg== 17454 -SEE= 17455 -ZHVyYXRpb24= 17456 -IFBhcmVudA== 17457 -Njc4 17458 -IGludmVudA== 17459 -IHJlc3RhcnQ= 17460 -0L7Qu9GM 17461 -cml0aW9u 17462 -KHJz 17463 -PGJvb2w= 17464 -aWVydA== 17465 -IG1vZGlmaWNhdGlvbg== 17466 -IFRY 17467 -cmVhZGNydW1i 17468 -YmFuaw== 17469 -MzI2 17470 -JC8= 17471 -IE1pbGxlcg== 17472 -XSksCg== 17473 -LkNoZWNrZWQ= 17474 -IHNhY3I= 17475 -c2VjdXJpdHk= 17476 -IHBvc2U= 17477 -IEJyYWQ= 17478 -IGZpdG5lc3M= 17479 -IGFubm91bmNlbWVudA== 17480 -YXRpb25Ub2tlbg== 17481 -IHNlcnZlcw== 17482 -bmVlZA== 17483 -IGdlb21ldHJ5 17484 -QVJT 17485 -5oA= 17486 -YW5kaWRhdGU= 17487 -IHNwcml0ZQ== 17488 -X3NwbGl0 17489 -V2Vlaw== 17490 -YWRpZXM= 17491 -PigK 17492 -Pz4i 17493 -IC8vLwo= 17494 -IGVpbmVy 17495 -IHdlZWtseQ== 17496 -CWxvZ2dlcg== 17497 -X3BvcA== 17498 -X21hbg== 17499 -IG1pZ3JhdGlvbnM= 17500 -IGFza3M= 17501 -IGJz 17502 -IGZhbGxz 17503 -LldoZXJl 17504 -LWhlaWdodA== 17505 -X2ZlYXR1cmU= 17506 -Lk1pbg== 17507 -IGh5cGVy 17508 -IHZvbGF0aWxl 17509 -IHR3ZW50eQ== 17510 -VHlwb2dyYXBoeQ== 17511 -VW5hYmxl 17512 -RGV0 17513 -LGY= 17514 -LW1vZA== 17515 -IHNldHRsZW1lbnQ= 17516 -IGNvbnRyYWN0cw== 17517 -bm9tZQ== 17518 -QmFk 17519 -IEJyaWFu 17520 -NzY4 17521 -KHVzZXJuYW1l 17522 -ISEhIQ== 17523 -IGhhY2s= 17524 -LkZpZWxk 17525 -SFI= 17526 -IEpvcmRhbg== 17527 -aXph 17528 -IMKg 17529 -IFNoZXI= 17530 -LmhlYWRlcg== 17531 -KG90aGVy 17532 -IER1Yg== 17533 -KG9w 17534 -IFJvdW5k 17535 -IHZpZQ== 17536 -IGFwcGw= 17537 -CUo= 17538 -IEluc2VydA== 17539 -IExQ 17540 -cmVnb24= 17541 -IE1QSQ== 17542 -IGFuY2hvcg== 17543 -YWNh 17544 -w7hy 17545 -IGFkZQ== 17546 -YW5jaG9y 17547 -cXVlZQ== 17548 -IFRyZWVOb2Rl 17549 -IHRhcmdldGVk 17550 -IGxhaWQ= 17551 -QUJFTA== 17552 -dmV0 17553 -IE9yaWdpbg== 17554 -QW50 17555 -LicpOwo= 17556 -ZXhwZWN0 17557 -ZWRSZWFkZXI= 17558 -IE1ham9y 17559 -IGluY2g= 17560 -Q29tcGFy 17561 -IHByZXZpZXc= 17562 -IGlsbG5lc3M= 17563 -IENPTlRSQUNU 17564 -IEluZGVwZW5k 17565 -dXVpZA== 17566 -IG5vbWU= 17567 -IHRj 17568 -IEF2ZW51ZQ== 17569 -aXNhbg== 17570 -IHBocmFzZQ== 17571 -X21vdmU= 17572 -Iilb 17573 -NDEy 17574 -IHByb3Zpc2lvbg== 17575 -IGNvbmNlbnRy 17576 -X0lS 17577 -IFV0 17578 -KCkr 17579 -IG5hcw== 17580 -ISw= 17581 -IFJvYmlu 17582 -aWF0aW9ucw== 17583 -YXRpdHVkZQ== 17584 -IHB4 17585 -IFdpdGhvdXQ= 17586 -L2Jhc2g= 17587 -ZWt0 17588 -cmVlbWVudA== 17589 -MzQy 17590 -T2JzZXJ2ZXI= 17591 -MzE4 17592 -IFJlZ2lvbg== 17593 -VUJMSUM= 17594 -IHsvLw== 17595 -S04= 17596 -5bc= 17597 -R2FtZU9iamVjdA== 17598 -5b4= 17599 -ZW5jb2Rpbmc= 17600 -ICoqKg== 17601 -cHJvamVjdHM= 17602 -IHRr 17603 -IGNoZWVzZQ== 17604 -RU1QTA== 17605 -YXJv 17606 -INin2YQ= 17607 -NjEw 17608 -MzM3 17609 -IGNvbnNpc3Rz 17610 -cmVmcmVzaA== 17611 -dXJlYXU= 17612 -IFNjYW5uZXI= 17613 -IHNvaWw= 17614 -IGZsYXZvcg== 17615 -RGF0YVNvdXJjZQ== 17616 -RXhlY3V0ZQ== 17617 -0LXQvdC40LU= 17618 -IHNoaXQ= 17619 -5YiG 17620 -PGFueQ== 17621 -IHJldHJpZXZl 17622 -IGJlbG9uZ3M= 17623 -LnN0cmlw 17624 -YWJzb2x1dGU= 17625 -IGV4cGFuZGVk 17626 -Ym95 17627 -KTot 17628 -IHJlc2N1ZQ== 17629 -LkpMYWJlbA== 17630 -IHJlbHk= 17631 -IGFsaWdubWVudA== 17632 -LWZhbWlseQ== 17633 -IHJlbmQ= 17634 -T0xVTU4= 17635 -IGJvcnJvdw== 17636 -IHF1b3Rlcw== 17637 -IExldw== 17638 -IHNob3dlcg== 17639 -IERFTEVURQ== 17640 -X2xvb3A= 17641 -ISIKCg== 17642 -CXJl 17643 -IGF0dGVtcHRlZA== 17644 -YXZlcmFnZQ== 17645 -IFBhaW50 17646 -cXVpc2l0aW9u 17647 -b2xlbg== 17648 -IGxpdGVyYXR1cmU= 17649 -IFJlZmVyZW5jZQ== 17650 -X1RFWFRVUkU= 17651 -IFNlZw== 17652 -IEluZHVzdA== 17653 -Y3R5cGU= 17654 -RFVDVA== 17655 -X0hPU1Q= 17656 -IFRyYWRl 17657 -IHBsdWdpbnM= 17658 -IGJyZWFzdA== 17659 -dWxzZQ== 17660 -IGNyZWF0dXJl 17661 -Mzcy 17662 -44GZ 17663 -IFdp 17664 -IHN1cHBsaWVk 17665 -Y29sbA== 17666 -ISgi 17667 -IGZ1Y2tpbmc= 17668 -IENocm9tZQ== 17669 -IFVyaQ== 17670 -IE5hdGlvbg== 17671 -IHZlcnRpY2Vz 17672 -VEhF 17673 -IE9yaWdpbmFs 17674 -b25kZQ== 17675 -IHNoYXJw 17676 -IGNvb2tpbmc= 17677 -MzQ3 17678 -IHsvKg== 17679 -IFBzeWNo 17680 -IEhvbGx5d29vZA== 17681 -PSRf 17682 -LkRvY2s= 17683 -IGdlcg== 17684 -IGJvbmU= 17685 -X2Nvbm4= 17686 -X3NlYw== 17687 -eXNpY3M= 17688 -ID0i 17689 -Mjk4 17690 -U2Fs 17691 -c2Y= 17692 -IGRlZXBseQ== 17693 -YW5nbGVz 17694 -VGVybQ== 17695 -YmVsbA== 17696 -IFF1aWNr 17697 -NTYw 17698 -ZW5lcmF0aW9u 17699 -YWRpb0J1dHRvbg== 17700 -5YWl 17701 -fQ0KDQoNCg== 17702 -IGNhcHRpb24= 17703 -bGM= 17704 -IEVM 17705 -LFs= 17706 -ICAgICAgDQo= 17707 -cmV0dA== 17708 -KG1ldGhvZA== 17709 -IEZsYXNo 17710 -NDcw 17711 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 17712 -V0lTRQ== 17713 -LnNjYWxl 17714 -IHJvdWdobHk= 17715 -X2NoaWxk 17716 -bWVtb3J5 17717 -YXlpbmc= 17718 -IGluaXRpYWxpemVk 17719 -aW5hdG9y 17720 -0LDRgA== 17721 -IHNjYWxhcg== 17722 -IEhv 17723 -YWlyZXM= 17724 -KGNvbHVtbg== 17725 -LmRlc3Ryb3k= 17726 -UEFDSw== 17727 -IGhlbQ== 17728 -YW5nZWw= 17729 -X1NVQg== 17730 -LnF1 17731 -INc= 17732 -REVGQVVMVA== 17733 -cG9zaXRvcmllcw== 17734 -NTAz 17735 -IExlbmd0aA== 17736 -IEZhc3Q= 17737 -IHNpZ25hbHM= 17738 -IC8vJA== 17739 -cmllcnM= 17740 -IGR1bW15 17741 -QU5Z 17742 -IHBlcnNvbmFsaXR5 17743 -IGFncmljdWx0 17744 -UGxhdGZvcm0= 17745 -RVJP 17746 -IFRyYQ== 17747 -IGVub3Jt 17748 -CVc= 17749 -QWN0aW9uUmVzdWx0 17750 -IGF2ZXI= 17751 -W3N0cg== 17752 -ICctLQ== 17753 -LlNwcmludGY= 17754 -IGRlYnV0 17755 -INGH 17756 -aGV4 17757 -X3V0aWxz 17758 -IHBi 17759 -VUlUYWJsZVZpZXc= 17760 -IHp1cg== 17761 -LmVuY29kZQ== 17762 -NDE2 17763 -IHZhZw== 17764 -LmVycm9ycw== 17765 -0L7QvQ== 17766 -IG1y 17767 -IEF3YXJk 17768 -IGNwdQ== 17769 -IHByZXNzZWQ= 17770 -J2VzdA== 17771 -IEZlc3RpdmFs 17772 -J1Q= 17773 -IGFr 17774 -cmVzb2x2ZQ== 17775 -MDQz 17776 -Lm1l 17777 -IG5pYw== 17778 -IGdlbnJl 17779 -IGF0dHJpYg== 17780 -IE1vb24= 17781 -IGFycml2ZQ== 17782 -IERhdGluZw== 17783 -IHRt 17784 -LkNvbmZpZ3VyYXRpb24= 17785 -NTA1 17786 -LnJlZA== 17787 -IGdsbQ== 17788 -IHN0YXRpb25z 17789 -c3dpdGNo 17790 -IHRpZWQ= 17791 -5Lq6 17792 -IC8+PC8= 17793 -UXVhbnRpdHk= 17794 -cXVpcnk= 17795 -X3RhYg== 17796 -IGFsZw== 17797 -VG9hc3Q= 17798 -cmVzaXpl 17799 -cXVlc3Rpb25z 17800 -c2NoZW1h 17801 -TGl0ZXJhbA== 17802 -KGVudGl0eQ== 17803 -TkVDVElPTg== 17804 -Y2hhbmdlZA== 17805 -X0ZJRUxE 17806 -X0hFSUdIVA== 17807 -IG9yZ2FuaWM= 17808 -UFJF 17809 -IENhdA== 17810 -LkRyYXc= 17811 -RXM= 17812 -IGxvdWQ= 17813 -Njgw 17814 -ICAgICAgICAJ 17815 -IEthdA== 17816 -IGhlYXA= 17817 -4oCcSXQ= 17818 -MDcw 17819 -ZXRy 17820 -IHVubGlrZWx5 17821 -ZXJhbHM= 17822 -L2F1dGg= 17823 -NTAy 17824 -dG9kbw== 17825 -UGxhY2U= 17826 -UG9zdGVk 17827 -Q29tbWVudHM= 17828 -IFRlY2g= 17829 -IEZpbmFsbHk= 17830 -ZWdyYXRpb24= 17831 -IG1pbmltYWw= 17832 -IEZpbGVz 17833 -IHRhbWI= 17834 -66Gc 17835 -IFJlbGVhc2U= 17836 -NDI1 17837 -LnJlc2l6ZQ== 17838 -IM8= 17839 -Y29sbGVjdA== 17840 -PXA= 17841 -IExJQUJMRQ== 17842 -IHByb2R1Y2luZw== 17843 -LXdyYXBwZXI= 17844 -IHNpbmdsZXM= 17845 -IE5CQQ== 17846 -b3Jy 17847 -ZXJlbg== 17848 -LmFkZEFjdGlvbg== 17849 -IHRoZXNpcw== 17850 -ZG4= 17851 -UFRZ 17852 -LmRlcw== 17853 -IGJhY3Rlcg== 17854 -IEV4cHJlc3M= 17855 -ICopCg== 17856 -5ZE= 17857 -L2FkbWlu 17858 -c2Vjb25kcw== 17859 -5Yqf 17860 -dXNzaW9u 17861 -YWJldGg= 17862 -IENvbXB1dGVy 17863 -IHJ1bGluZw== 17864 -KCIuLi8= 17865 -LkdFVA== 17866 -IE1lZGFs 17867 -aXRpb25hbGx5 17868 -Y29tbWl0 17869 -Zm9jdXM= 17870 -X0xFVkVM 17871 -aW5kYQ== 17872 -RmFjdA== 17873 -PW5w 17874 -PSIiPgo= 17875 -IHN1YnNlcXVlbnQ= 17876 -cG9zYWJsZQ== 17877 -LWZsdWlk 17878 -IHRob3JvdWdo 17879 -IHB1YmxpY2x5 17880 -YXB0ZXJz 17881 -IFdpbHNvbg== 17882 -X1BSRQ== 17883 -eWFyZA== 17884 -5Lw= 17885 -CWlu 17886 -MzM5 17887 -IHJldmVycw== 17888 -IGJ1bGxldA== 17889 -Y3JpYmVk 17890 -bmVzb3Rh 17891 -ICgkXw== 17892 -YW5ub24= 17893 -Y3Vyc29y 17894 -IGNsb3RoaW5n 17895 -IE11bHRp 17896 -Mjg3 17897 -Oics 17898 -IHZlc3M= 17899 -b3JkaW5hdG9y 17900 -IGVpbmVt 17901 -Q2Fubm90 17902 -IGFybWVk 17903 -CVY= 17904 -5LiK 17905 -LkZsYXQ= 17906 -IFNlcA== 17907 -IFN1YmplY3Q= 17908 -X2ZvbnQ= 17909 -IGNoYXJhY3RlcmlzdGljcw== 17910 -RG9uZQ== 17911 -ZWxu 17912 -IyMjIyMjIyMjIyMj 17913 -UE9T 17914 -IGRlbnNpdHk= 17915 -IFBsYXRmb3Jt 17916 -LWl0ZW1z 17917 -IG92ZXJz 17918 -IHB1c2hpbmc= 17919 -56Q= 17920 -LkNvbm5lY3Rpb24= 17921 -X3Rlcm0= 17922 -IGluaXRpYWxpemF0aW9u 17923 -X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18= 17924 -56w= 17925 -LmRvY3VtZW50 17926 -bGVzaA== 17927 -CWRvY3VtZW50 17928 -IFBpbg== 17929 -w6dh 17930 -IGRlZmluaXRpb25z 17931 -LlBhdGg= 17932 -X1dSSVRF 17933 -IAkK 17934 -Pz4KCg== 17935 -IHRlcnJpYmxl 17936 -YmVhbg== 17937 -aWNrZXRz 17938 -IFNW 17939 -QnV5 17940 -KHRhc2s= 17941 -IHJlZ2ltZQ== 17942 -Z29vZ2xl 17943 -IGNyYWNr 17944 -LnZpc2l0 17945 -TlVN 17946 -ZW5lcmd5 17947 -IHN0cnVjaw== 17948 -X3NhbXBsZQ== 17949 -LnBheWxvYWQ= 17950 -IHJldmlz 17951 -IFNjZW5l 17952 -IHBn 17953 -IGJyZWFrZmFzdA== 17954 -VVJSRU5U 17955 -LmNoYXJBdA== 17956 -X2V4Y2VwdGlvbg== 17957 -IEFudG9u 17958 -IGd1aWRlbGluZXM= 17959 -IGV4aGF1c3Q= 17960 -IEZpbmFuY2lhbA== 17961 -IGluZGVudA== 17962 -IGRlc2t0b3A= 17963 -SGlkZGVu 17964 -RmFpbHVyZQ== 17965 -IHByaW5jaXBsZQ== 17966 -IGl2 17967 -IHNla3M= 17968 -bmV0d29yaw== 17969 -IG51bWJlck9m 17970 -IEFsYmVydA== 17971 -CWxvbmc= 17972 -ODAx 17973 -LC4= 17974 -IHplcm9z 17975 -ZmFkZQ== 17976 -IFR5cA== 17977 -IFRlcm0= 17978 -IEFydHM= 17979 -LkFwcGxpY2F0aW9u 17980 -IGJlaGFsZg== 17981 -5oi3 17982 -IG1lcmU= 17983 -KGAkew== 17984 -IGF3YXJlbmVzcw== 17985 -ZWxwZXJz 17986 -ZmxpeA== 17987 -IHdlaWdo 17988 -IGVzdGltYXRlcw== 17989 -LmNoaWxk 17990 -L08= 17991 -IEJpdG1hcA== 17992 -LmJvdHRvbQ== 17993 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 17994 -RXhwZWN0 17995 -ZW50bw== 17996 -IEZvcnVt 17997 -dmVyYWw= 17998 -IGphaWw= 17999 -IGFiaWxpdGllcw== 18000 -IEhPTEQ= 18001 -IENpdA== 18002 -IGR5bmFt 18003 -IGdyYXk= 18004 -CQkJCQkJCQkJCQkJCQ== 18005 -Lm5leHRJbnQ= 18006 -YW50bHk= 18007 -IEFSSVNJTkc= 18008 -KHByaXZhdGU= 18009 -IHJlamVjdGVk 18010 -IE5pYw== 18011 -IGxlYXRoZXI= 18012 -PXsK 18013 -YWx5dGljcw== 18014 -dGhldGlj 18015 -LlRvcA== 18016 -Mzcz 18017 -LlBhZ2U= 18018 -PXtg 18019 -IDsNCg== 18020 -ZGVwdGg= 18021 -bWFubg== 18022 -V0Q= 18023 -IFNvbQ== 18024 -LlJpZ2h0 18025 -ICl9Cg== 18026 -IHRyYWl0 18027 -w5c= 18028 -aWFj 18029 -IHJ2 18030 -U2FtcGxl 18031 -LlhtbA== 18032 -b3BwZWQ= 18033 -INGE 18034 -bGlzdHM= 18035 -IHRlYXI= 18036 -aXZlcnNhcnk= 18037 -LmNvbGxlY3Rpb24= 18038 -IENvbnN0aXR1dGlvbg== 18039 -IEh0dHBSZXNwb25zZQ== 18040 -IGJyaWxs 18041 -IFByb20= 18042 -aG92ZXI= 18043 -MzY2 18044 -IE1pYW1p 18045 -IGFyZ3Vl 18046 -X2Zsb2F0 18047 -NTA0 18048 -IOOC 18049 -IG5hdA== 18050 -IFRhbA== 18051 -IGludGVncmF0aW9u 18052 -KGN1cg== 18053 -IHJlbW92aW5n 18054 -IGNvZWZm 18055 -IFRob3VnaA== 18056 -IGZvcmVjYXN0 18057 -NDA4 18058 -IFZlZ2Fz 18059 -U2l0ZQ== 18060 -MzQ2 18061 -IHRyYWI= 18062 -IEhlbnJ5 18063 -LWk= 18064 -IGludm9sdmVz 18065 -QlQ= 18066 -IHNsbw== 18067 -SW52b2tl 18068 -IGx1Y2t5 18069 -MDI1 18070 -cmF0 18071 -ID8K 18072 -IGhhbmRsZWQ= 18073 -KGZk 18074 -Y29udGVudHM= 18075 -IE9GRg== 18076 -UkY= 18077 -IHN0eQ== 18078 -IE1vdG9y 18079 -dGVyeQ== 18080 -dGF4 18081 -TUFQ 18082 -IE1ycw== 18083 -IHBob25lcw== 18084 -IFVJVmlldw== 18085 -IikpKTsK 18086 -KGRldg== 18087 -IElyaXNo 18088 -MDE5 18089 -IHdz 18090 -REk= 18091 -X09GRlNFVA== 18092 -IEV2ZW50cw== 18093 -IHN0YWdlcw== 18094 -IH0vLw== 18095 -IGhhYmVu 18096 -U1RBTkNF 18097 -IFNpbg== 18098 -IE1vbmV5 18099 -KHRvcA== 18100 -IGFwcG9pbnRtZW50 18101 -VkVSU0lPTg== 18102 -bWV0YWRhdGE= 18103 -X2NvbW1lbnQ= 18104 -IGNvbGxlYWd1ZXM= 18105 -bWFwcw== 18106 -4pg= 18107 -CgkK 18108 -KGFs 18109 -X3JlcQ== 18110 -IGZ1dA== 18111 -IGFyY2hpdGVjdHVyZQ== 18112 -MzUx 18113 -IFdIRVRIRVI= 18114 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 18115 -X3NjcmVlbg== 18116 -IHN0eWxlVXJscw== 18117 -IG1vbnN0ZXI= 18118 -LnVw 18119 -cGhpYQ== 18120 -IHByb2Nlc3Nvcg== 18121 -IFRlcnI= 18122 -PScs 18123 -IE1hbnVmYWN0 18124 -IE5U 18125 -a2Vs 18126 -aWJlcm4= 18127 -CWZpbGU= 18128 -QWxp 18129 -cmllbnRhdGlvbg== 18130 -IC8vIQ== 18131 -YXBvcmU= 18132 -YW5lb3Vz 18133 -IENyZWF0 18134 -Zm9sZGVy 18135 -NDE1 18136 -IGhheQ== 18137 -U3VwcHJlc3M= 18138 -KGxlZnQ= 18139 -IGV1cm8= 18140 -IGRpc2NsYWltZXI= 18141 -dXN0cnk= 18142 -c2hpcHM= 18143 -X2Zk 18144 -IEZh 18145 -X2luc2VydA== 18146 -IHJvbA== 18147 -aWZ0aW5n 18148 -IENvbW1lbnRz 18149 -X2Jy 18150 -IGxvc3Nlcw== 18151 -IEFkZGVk 18152 -Y2hhcmc= 18153 -INC/0L4= 18154 -X3N5c3RlbQ== 18155 -IFNvbWV0aW1lcw== 18156 -IFNwYWlu 18157 -KGdyb3Vw 18158 -aWFsaXM= 18159 -IGRvbGxhcg== 18160 -IEFyZ3M= 18161 -NDk5 18162 -Mjk3 18163 -cXVpcmVz 18164 -IFRlbg== 18165 -LnNjc3M= 18166 -IHN1cnZpdmU= 18167 -dXNhZ2U= 18168 -IGp1bg== 18169 -aW1pdGVy 18170 -77yBCgo= 18171 -IGZpZnRo 18172 -dG9nZ2xl 18173 -IGRlY2xpbmU= 18174 -KCQi 18175 -KExvbmc= 18176 -aW5nZQ== 18177 -IHBpbG90 18178 -LWxpZ2h0 18179 -LXJhZGl1cw== 18180 -IHBvZGNhc3Q= 18181 -IG5hdHVyYWxseQ== 18182 -UGFnZXM= 18183 -5Li6 18184 -IERlc3BpdGU= 18185 -IGxpZ2h0aW5n 18186 -IGNyYXRl 18187 -IEJpbmFyeQ== 18188 -IHJlZHVjaW5n 18189 -IGVsZWc= 18190 -IE1vdXNl 18191 -IFRlc3RCZWQ= 18192 -IGJlZm9yZUVhY2g= 18193 -X0FSUkFZ 18194 -UmVkaXJlY3Q= 18195 -MzI5 18196 -IGZsb29k 18197 -IHNoaXBz 18198 -MzYz 18199 -IGVsZWN0cmljaXR5 18200 -KSoo 18201 -6rg= 18202 -IFZpZXQ= 18203 -aGVybw== 18204 -IGRpYQ== 18205 -IEtlbnQ= 18206 -aGVhcnQ= 18207 -IHRocmVhdHM= 18208 -X2FjYw== 18209 -IHN5bWJvbHM= 18210 -aXNjaGVu 18211 -X2luc3Q= 18212 -Q3JpdGVyaW9u 18213 -IFRJTQ== 18214 -LkhlaWdodA== 18215 -NTgw 18216 -IOKAmQ== 18217 -KCk7CgoK 18218 -UHJvZHVjdHM= 18219 -X1NQ 18220 -IEN5 18221 -IGRlcGVuZGVudA== 18222 -ZXN0ZQ== 18223 -IGRhdG9z 18224 -ZGl0 18225 -0LDQsg== 18226 -SUdOQUw= 18227 -IGxlc3Nvbg== 18228 -Ij4n 18229 -IENvdmVy 18230 -IEhvcGU= 18231 -IFRpbWVy 18232 -IGRhZA== 18233 -dmlkZXJz 18234 -IFBob3Q= 18235 -Lz8= 18236 -cm9weQ== 18237 -b21pbmc= 18238 -YXNpb24= 18239 -IFwo 18240 -IEVU 18241 -IFJlYWRpbmc= 18242 -IGVwaXNvZGVz 18243 -bG0= 18244 -NDIx 18245 -ZWNoYQ== 18246 -IG5ldXJv 18247 -ODIw 18248 -IGhhcm1vbg== 18249 -IGxpYmVyYWw= 18250 -LWluZA== 18251 -Mzkz 18252 -REFUQQ== 18253 -IGV2ZXJ5ZGF5 18254 -IGRpdmlkZWQ= 18255 -IEFjdGl2ZVJlY29yZA== 18256 -ZmlndXJl 18257 -VUE= 18258 -5Lk= 18259 -cmllbmRseQ== 18260 -dGVjaA== 18261 -NjAx 18262 -LmdhbWVPYmplY3Q= 18263 -0LjRgtGM 18264 -Mzc0 18265 -IG1vb24= 18266 -ZnRpbWU= 18267 -IG5vY2g= 18268 -IFRPUlQ= 18269 -IFZN 18270 -LmluaXRpYWw= 18271 -KGNoaWxk 18272 -IG11c2ljYWw= 18273 -IG9j 18274 -YmFz 18275 -IEhheQ== 18276 -MzYx 18277 -X2xvbmc= 18278 -IG1lbXNldA== 18279 -aWxleQ== 18280 -YWRlbHBoaWE= 18281 -U1Y= 18282 -cm9hdA== 18283 -X3R4 18284 -IGxvbg== 18285 -IG5nT25Jbml0 18286 -YnA= 18287 -IEdvbGRlbg== 18288 -QUNIRQ== 18289 -IHdvcnJpZWQ= 18290 -YXpp 18291 -RWFy 18292 -VGFrZQ== 18293 -KGZw 18294 -YnVyZ2g= 18295 -X0RhdGE= 18296 -Z3Jlcw== 18297 -IE9udA== 18298 -cHVz 18299 -IHRyYW5zcGFyZW50 18300 -IHBvY2tldA== 18301 -IHJhbQ== 18302 -aWdyYXRpb25z 18303 -Lg0KDQo= 18304 -IFso 18305 -IGFkb3B0ZWQ= 18306 -IHJlcG9ydGVkbHk= 18307 -IERyZWFt 18308 -IH0pKTsK 18309 -bG9zaW5n 18310 -IHRlZXRo 18311 -IEJvb2tz 18312 -Iiwm 18313 -ZW5ueQ== 18314 -TEVNRU5U 18315 -IGdlbA== 18316 -IFBsYW50 18317 -NDM3 18318 -IeKAnQ== 18319 -Lmhvc3Q= 18320 -IFJlcGx5 18321 -Mzc2 18322 -cmVuZ3Ro 18323 -IHJlY29nbml0aW9u 18324 -IH19Pgo= 18325 -TEE= 18326 -IG1pcnJvcg== 18327 -IGFzc2lzdGFudA== 18328 -KGRldmljZQ== 18329 -IHNwaXJpdHVhbA== 18330 -YnVpbGRlcg== 18331 -wqc= 18332 -IG91dHI= 18333 -IHR0 18334 -IFBFUg== 18335 -IHJhZGljYWw= 18336 -TWV0aG9kcw== 18337 -IHBhY2U= 18338 -dWR5 18339 -IGd1dA== 18340 -IEdyZWVr 18341 -IG5vbmF0b21pYw== 18342 -IFBhcGVy 18343 -X0dQSU8= 18344 -IG9ic3Q= 18345 -LkFk 18346 -dmlyb25tZW50cw== 18347 -IFNvdg== 18348 -MzU2 18349 -KGNvbg== 18350 -IFRyYW5zYWN0aW9u 18351 -LmFzc2lnbg== 18352 -CWNhdGNo 18353 -ZWx0ZXI= 18354 -IGJpdGNvaW4= 18355 -X0dS 18356 -IDw/PQ== 18357 -X2xhbmc= 18358 -7J2E 18359 -QnJvd3Nlcg== 18360 -IGNvbnNpZGVyYXRpb24= 18361 -IEV4ZWN1dGl2ZQ== 18362 -6Ze0 18363 -O1w= 18364 -IEpTT05PYmplY3Q= 18365 -IEJlbGw= 18366 -IHNwb2tlc21hbg== 18367 -fn5+fn5+fn4= 18368 -b2NrZXk= 18369 -IEdybw== 18370 -IEF3 18371 -Q29uc3RyYWludA== 18372 -IFByYWN0 18373 -IEV2ZXI= 18374 -cHJpbQ== 18375 -OnsK 18376 -X2lt 18377 -UE4= 18378 -TWlsbGlz 18379 -VU1FTlQ= 18380 -IGJhZ3M= 18381 -w6Vy 18382 -QU5ORUw= 18383 -MzU0 18384 -IGlj 18385 -IHRyYW5zcG9ydGF0aW9u 18386 -IFNhdWRp 18387 -aGFuZGxlcg== 18388 -RHJhZw== 18389 -IGhk 18390 -Y29sbGFwc2U= 18391 -X1BI 18392 -IHVi 18393 -QVJN 18394 -IEFQUA== 18395 -IHRvbmlnaHQ= 18396 -IGRpbmluZw== 18397 -UmVjb2du 18398 -IGJj 18399 -aWd0 18400 -KG51bWJlcg== 18401 -Qm9vdA== 18402 -IGVsc2V3aGVyZQ== 18403 -IGFycm93 18404 -YXJnYQ== 18405 -IGRlbGljaW91cw== 18406 -IFNO 18407 -V1I= 18408 -VmFsaWRhdGU= 18409 -IFF1YWxpdHk= 18410 -KGVtYWls 18411 -IGludGVycHJl 18412 -aWdhdGlvbg== 18413 -IGNob2NvbGF0ZQ== 18414 -NTI1 18415 -X2VkZ2U= 18416 -IHN0b3Bz 18417 -OmZ1bmN0aW9u 18418 -KXw= 18419 -IHRoYWk= 18420 -IExvYWRpbmc= 18421 -U3Rvcnk= 18422 -VHJpZ2dlcg== 18423 -YnJhbmNo 18424 -IHRk 18425 -ZW50aWNhdGVk 18426 -IGFkdmVudHVyZQ== 18427 -IGJsb2NrY2hhaW4= 18428 -RXZlbnRIYW5kbGVy 18429 -IHNxcnQ= 18430 -LlBy 18431 -TG5n 18432 -QmVjYXVzZQ== 18433 -IHZpdg== 18434 -IG9jZWFu 18435 -eWx2YW5pYQ== 18436 -0LDRgQ== 18437 -IFV0aWxz 18438 -IGRlc3Blcg== 18439 -IGRlZmVy 18440 -CXJlcXVpcmU= 18441 -aGw= 18442 -UmVxdWlyZQ== 18443 -XVw= 18444 -IGRpcmVjdGlvbnM= 18445 -X3Jlc291cmNl 18446 -IHN1YnNjcmliZQ== 18447 -IMO6 18448 -IEhlYXJ0 18449 -ZXN0cw== 18450 -LXN1Yg== 18451 -IFJo 18452 -Zm9yRWFjaA== 18453 -IGRlbGlnaHQ= 18454 -IHRlcnJpdG9yeQ== 18455 -LmNvbmN1cnJlbnQ= 18456 -ICgr 18457 -anBn 18458 -IHByZXBhcmF0aW9u 18459 -IHJvdW5kZWQ= 18460 -Q29tbQ== 18461 -LkxlZnQ= 18462 -IG9waW5pb25z 18463 -IE5hdmlnYXRpb24= 18464 -KGZpcnN0 18465 -Iiwk 18466 -IGhpcmU= 18467 -IGRldGVjdGlvbg== 18468 -LmdldEVsZW1lbnRz 18469 -IGVwcw== 18470 -IHNrbGVhcm4= 18471 -IGN6 18472 -IC8+DQo= 18473 -bWV0aWM= 18474 -IHRyYW5zZm9ybWF0aW9u 18475 -5Y+3 18476 -IHJnYg== 18477 -aXN0cmlidXRpb25z 18478 -IGltcGxpY2l0 18479 -L2lu 18480 -ZGVzdGluYXRpb24= 18481 -0LDRgtGM 18482 -WmVybw== 18483 -IHVuc2V0 18484 -OTIw 18485 -LndoZXJl 18486 -Lmdv 18487 -IGZvcm1hdGlvbg== 18488 -IGRlY2xhcmF0aW9u 18489 -KCkNCg0K 18490 -IEV4cGw= 18491 -CQkJICA= 18492 -L3Bybw== 18493 -LkpTT04= 18494 -NDQx 18495 -IGRlc2s= 18496 -LnN1YnN0cg== 18497 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 18498 -bHlu 18499 -cHNvbg== 18500 -NDA3 18501 -ZGlzYWJsZQ== 18502 -IEZ1bmM= 18503 -CUFzc2VydA== 18504 -IE1BUks= 18505 -IGRlZmVhdA== 18506 -IGJsaW5k 18507 -IGNvbnN0YW50cw== 18508 -MzYy 18509 -LmhlYWRlcnM= 18510 -VUlMRA== 18511 -IGV4cGVuc2Vz 18512 -UGl4ZWw= 18513 -IGhy 18514 -IGZlbA== 18515 -IEVhc3Rlcm4= 18516 -NDI0 18517 -NDkw 18518 -X2RlbA== 18519 -MzU3 18520 -IEN1Yg== 18521 -IHNx 18522 -CWNvdW50 18523 -IERpcmVjdG9yeQ== 18524 -IGV4Y2x1cw== 18525 -IGhpc3Rvcmlj 18526 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 18527 -IGNvbXBvc2l0aW9u 18528 -IGRhdGFHcmlkVmlldw== 18529 -IEJ1cm4= 18530 -IEJD 18531 -TWFzdGVy 18532 -IHNwYXdu 18533 -IGJlYXJpbmc= 18534 -LlNldEFjdGl2ZQ== 18535 -aWxv 18536 -IGdhbGxlcnk= 18537 -IGZvdW5kZWQ= 18538 -IGF2YWlsYWJpbGl0eQ== 18539 -LnNxcnQ= 18540 -IHBlcw== 18541 -IERPTQ== 18542 -bWF0ZQ== 18543 -T2N0 18544 -IG1hdGNoZWQ= 18545 -aXRpdml0eQ== 18546 -IGFueGlldHk= 18547 -LnByaWNl 18548 -IEluc3RhbnQ= 18549 -7Io= 18550 -IHR1dA== 18551 -SUNvbGxlY3Rpb24= 18552 -LnNoYXJlZA== 18553 -X3NxbA== 18554 -dGJs 18555 -bGlicmFyeQ== 18556 -X2Rlc3Ryb3k= 18557 -ZXJtYWw= 18558 -IE5vdGVz 18559 -IEVpbg== 18560 -IHNvdXRoZXJu 18561 -IE9USEVSV0lTRQ== 18562 -IG1hY3Jv 18563 -Lmxvd2Vy 18564 -Y2xz 18565 -Q29udGVudFZpZXc= 18566 -Lmxpbms= 18567 -Y29uc3RhbnQ= 18568 -IEJlcw== 18569 -IHNvbWVib2R5 18570 -bmI= 18571 -Mzk5 18572 -Ij57 18573 -KGxvY2Fs 18574 -Li4uLi4= 18575 -IE51bGw= 18576 -bXg= 18577 -IMOn 18578 -IHBhdXNl 18579 -LS0tLS0tLS0tLS0= 18580 -X01P 18581 -IENN 18582 -IGZvcktleQ== 18583 -IERWRA== 18584 -IGNsb3Nlc3Q= 18585 -X0RFVklDRQ== 18586 -IFN0ZXBoZW4= 18587 -IEJCQw== 18588 -IFRyYXZlbA== 18589 -UGFpbnQ= 18590 -IFJlc3VsdHM= 18591 -IFJ1bGU= 18592 -IHRw 18593 -IHJhdGluZ3M= 18594 -Y2lu 18595 -Y3N2 18596 -Pi8= 18597 -IEdPUA== 18598 -bGFk 18599 -INGA 18600 -IGluZGV4UGF0aA== 18601 -bWF0cml4 18602 -PWY= 18603 -YXJzZWQ= 18604 -IH0pOw== 18605 -IENvcw== 18606 -IFNjb3Jl 18607 -IHRhaw== 18608 -IEVTUA== 18609 -IElOQw== 18610 -X05VTEw= 18611 -LWZsZXg= 18612 -Il1b 18613 -aW50bw== 18614 -ZWxhbmQ= 18615 -QXV0aG9yaXphdGlvbg== 18616 -X0ZBTFNF 18617 -IGdhdGU= 18618 -IHZpZA== 18619 -aXN0ZW50 18620 -VElNRQ== 18621 -IHJld3JpdGU= 18622 -IHRpZQ== 18623 -IGFyY2hpdmU= 18624 -NTEx 18625 -LmV2ZW50cw== 18626 -LmdldFBhcmFtZXRlcg== 18627 -IFBlcm1pc3Npb24= 18628 -IHByb2dyYW1tZQ== 18629 -IOk= 18630 -anVk 18631 -IGNhbWVyYXM= 18632 -MzM4 18633 -MzQ5 18634 -KHN5cw== 18635 -IFN5cmlhbg== 18636 -IGltcHJvdmVtZW50cw== 18637 -IGhpcA== 18638 -IHN1aWNpZGU= 18639 -IHNjaG9sYXI= 18640 -IGNvbXBhdGlibGU= 18641 -MDIy 18642 -cmVtb3Rl 18643 -LmRvd24= 18644 -RlVOQ1RJT04= 18645 -IG1hbmFnaW5n 18646 -IFVJS2l0 18647 -LnJhdw== 18648 -Pj4+Pg== 18649 -Mzcx 18650 -IGRlbWFuZHM= 18651 -ZWxsaXRl 18652 -IGRlbnQ= 18653 -IE1pY3Jv 18654 -5Y+W 18655 -J11bJA== 18656 -IElF 18657 -aW1lbnNpb24= 18658 -IHRyZW0= 18659 -NjMw 18660 -IGdhaW5lZA== 18661 -LndpdGg= 18662 -Lm9r 18663 -aG91 18664 -IGJvbQ== 18665 -YW1wYWlnbg== 18666 -IGpvaW5pbmc= 18667 -ZmlzaA== 18668 -IGFkZFN1YnZpZXc= 18669 -ODYw 18670 -IG5vcnRoZXJu 18671 -LmNvcg== 18672 -b3JldA== 18673 -RGll 18674 -aW5pc2g= 18675 -X2NvbXA= 18676 -IGF0dGVuZGVk 18677 -IGNvbGxhcHNl 18678 -IFNT 18679 -YWNlbnQ= 18680 -X0VRVUFM 18681 -IERlZXA= 18682 -UkdC 18683 -CXRlc3Q= 18684 -b2x2ZXM= 18685 -dXNldA== 18686 -VW5pdHlFbmdpbmU= 18687 -d3JpdGVy 18688 -UmVzb2x2ZXI= 18689 -LCU= 18690 -aWZmZXJlbmNl 18691 -X3JlbW92ZQ== 18692 -b25kYQ== 18693 -IGZlbW1l 18694 -Mzg1 18695 -ZGVjb2Rl 18696 -QnJhbmNo 18697 -IGZsdXNo 18698 -IGlubm92YXRpdmU= 18699 -VGVzdHM= 18700 -IFsnLi8= 18701 -IGNvdmVyaW5n 18702 -LmFkbWlu 18703 -dWx0aXBhcnQ= 18704 -KGxhbWJkYQ== 18705 -77u/bmFtZXNwYWNl 18706 -IFNwb3J0 18707 -ICEo 18708 -YWNsZXM= 18709 -IGRlcHJlc3Npb24= 18710 -IEtvbmc= 18711 -NTcw 18712 -IHBlcnQ= 18713 -IENvbm4= 18714 -IE90aGVyd2lzZQ== 18715 -L2hvbWU= 18716 -c3VwcG9ydGVk 18717 -IHBpbms= 18718 -IGludml0ZWQ= 18719 -w7Fvcw== 18720 -X2VuYWJsZWQ= 18721 -IC0K 18722 -Rlc= 18723 -ZW5lcnM= 18724 -IE1Z 18725 -IHN1Z2dlc3Rpb25z 18726 -Q2FudmFz 18727 -IGZlcg== 18728 -IE1hcmtldGluZw== 18729 -QFRlc3Q= 18730 -dW50dQ== 18731 -IFZlbg== 18732 -IENvdQ== 18733 -aXZhbHM= 18734 -RG9uYWxk 18735 -bGltaXRlZA== 18736 -CQkJCQkJCg== 18737 -IGFuYWx5c3Q= 18738 -KGVudHJ5 18739 -IHJlcHJlc2VudGF0aXZl 18740 -X2F0dHJpYnV0ZXM= 18741 -IGZ1cg== 18742 -LmhpZGU= 18743 -cmVzcA== 18744 -YWRvcmVz 18745 -cmlkZXM= 18746 -IEpvc2g= 18747 -cm9ib3Q= 18748 -IE5BVA== 18749 -IHNlc3Nv 18750 -IGludGVncmF0ZWQ= 18751 -OnRydWU= 18752 -cGFydHM= 18753 -IHN0dXBpZA== 18754 -OmV2ZW50 18755 -QGVuZHNlY3Rpb24= 18756 -IHB1 18757 -LlRhYmxl 18758 -IFlpaQ== 18759 -YDsKCg== 18760 -IGNsYW5n 18761 -PSIiPg== 18762 -ZW5nYW4= 18763 -X3BhcmFtZXRlcnM= 18764 -LmludGVybmFs 18765 -IE1vZGVybg== 18766 -IG1ldHJpYw== 18767 -IHNlbWk= 18768 -PXt7Cg== 18769 -NzA3 18770 -LmFtYXpvbg== 18771 -IEJC 18772 -YWludHk= 18773 -dmlld3BvcnQ= 18774 -MzY3 18775 -IHN0YXJ0QWN0aXZpdHk= 18776 -ZGlzcGF0Y2g= 18777 -KioqKio= 18778 -IGZsYXY= 18779 -aWZmZXJlbnQ= 18780 -Mzgy 18781 -W3RoaXM= 18782 -IHN0YWtl 18783 -IGFyZ3VlZA== 18784 -dmlvdXNseQ== 18785 -Lndvcms= 18786 -IE9haw== 18787 -T2xk 18788 -KGFzeW5j 18789 -bm90ZXM= 18790 -IGZsaXA= 18791 -IGRpc2Fn 18792 -IFRF 18793 -CWVycm9y 18794 -PCc= 18795 -IMK7Cgo= 18796 -IGZpbHRlcmVk 18797 -IE1hY2g= 18798 -IGh1bmc= 18799 -X2R1bXA= 18800 -X3NhbXBsZXM= 18801 -LWRpc21pc3M= 18802 -IHJheQ== 18803 -SW1wbGVtZW50ZWQ= 18804 -REs= 18805 -IGplZA== 18806 -MDkw 18807 -IGJyZWFrcw== 18808 -IGZpdHM= 18809 -Lmdy 18810 -IFplcm8= 18811 -b3Jv 18812 -IGVxdWFsbHk= 18813 -ICdb 18814 -IGNvbmNlcm5pbmc= 18815 -PG1ldGE= 18816 -cGxheWVycw== 18817 -X1BPUw== 18818 -X3NpbQ== 18819 -SmFu 18820 -IHlvdXJz 18821 -CU4= 18822 -IHNwaXI= 18823 -IGNoYW1waW9u 18824 -IEFuYWx5c2lz 18825 -YXBh 18826 -IE5TTG9n 18827 -X2xpbmVz 18828 -w7Fh 18829 -CQkgICAgICAg 18830 -ODE5 18831 -LlNj 18832 -UmVw 18833 -ZXRyb2l0 18834 -dXJhYmxl 18835 -TUlU 18836 -Y29tcGF0 18837 -b3duZWQ= 18838 -X2luZGljZXM= 18839 -XSwNCg== 18840 -IGRpc2NvdmVyeQ== 18841 -IERpZWdv 18842 -b2Jp 18843 -LkluZGV4 18844 -IHRyZW5kcw== 18845 -UExBWQ== 18846 -Lm5v 18847 -IGxlbnM= 18848 -X2NmZw== 18849 -IGFubm8= 18850 -YWdhbg== 18851 -IHBlcmlvZHM= 18852 -dGVybXM= 18853 -eXo= 18854 -IGF0dGFja2Vk 18855 -aWJyYXRpb24= 18856 -UEVDSUFM 18857 -X2dyYWQ= 18858 -IGFjY29yZGFuY2U= 18859 -LlJlYWRMaW5l 18860 -LmRldmljZQ== 18861 -cml4 18862 -LmNvbnRhaW5lcg== 18863 -bWF5 18864 -ZXJjaXNl 18865 -IEx1 18866 -IHJn 18867 -INGB0YI= 18868 -CQkKCQkK 18869 -KHVu 18870 -VEVSTkFM 18871 -IGxlc3NvbnM= 18872 -IGFsbGVnYXRpb25z 18873 -IHRyYW5zbWlzc2lvbg== 18874 -LlJlZg== 18875 -TW9iaWxl 18876 -IFRvdXJuYW1lbnQ= 18877 -IE51dA== 18878 -IEdh 18879 -IENhcGl0YWw= 18880 -ZGVmaW5pdGlvbg== 18881 -LWV4cA== 18882 -Y2xlYW4= 18883 -IGZhbnRhc3k= 18884 -IGVuaGFuY2U= 18885 -ZW50ZW5jZQ== 18886 -MDMx 18887 -J106Cg== 18888 -YWNrZXRz 18889 -IGNlbGVicmF0ZQ== 18890 -QCIs 18891 -U2VyaWFsaXplRmllbGQ= 18892 -IGFycmF5cw== 18893 -dGI= 18894 -CXN0 18895 -W2Fzc2VtYmx5 18896 -KHJlZw== 18897 -LmNhdGVnb3J5 18898 -IGltcHJvdmluZw== 18899 -IHNhbG9wZQ== 18900 -Qnl0ZUFycmF5 18901 -T3JpZ2luYWw= 18902 -IFt7Cg== 18903 -5Zue 18904 -IENsaW4= 18905 -b2VuaXg= 18906 -IFNhbXN1bmc= 18907 -IG1haW50YWluZWQ= 18908 -IGFnZW5kYQ== 18909 -ZmFpbA== 18910 -IHByZXNlbnRz 18911 -IHRpbWluZw== 18912 -Lm1hcms= 18913 -Jz48 18914 -IHByb21vdA== 18915 -IGluY2w= 18916 -X29ubHk= 18917 -66W8 18918 -IEF0dG9ybmV5 18919 -LWRhdGU= 18920 -IGxhbmRzY2FwZQ== 18921 -IGZ1 18922 -U1k= 18923 -LnByb3A= 18924 -IEFycg== 18925 -cGFn 18926 -UGFyYWxsZWxHcm91cA== 18927 -JzoNCg== 18928 -IGxvZ3M= 18929 -YXVuY2g= 18930 -dW5jaQ== 18931 -bmFtYQ== 18932 -VGFibGVDZWxs 18933 -aXNzdWVz 18934 -Lns= 18935 -ZWN1cml0eQ== 18936 -X2V4ZWM= 18937 -b2xkcw== 18938 -IGhvc3Rz 18939 -IHByb3Rv 18940 -X2ltcG9ydA== 18941 -X3NvcnQ= 18942 -IEJvdw== 18943 -IE5vcm1hbA== 18944 -IEZhcm0= 18945 -LmNyZWF0ZVBhcmFsbGVsR3JvdXA= 18946 -Um90YXRpb24= 18947 -LmVycg== 18948 -IHBsZWFzZWQ= 18949 -aXRhZ2U= 18950 -Lldo 18951 -CQkgICAg 18952 -TVI= 18953 -IE1PUkU= 18954 -IE5hdHVyYWw= 18955 -X3RyYW5zZm9ybQ== 18956 -QkFTRQ== 18957 -ZW5lcmFs 18958 -dXRkb3du 18959 -LmNvbW1vbnM= 18960 -V1Q= 18961 -IGFhbg== 18962 -LlJlc3VsdA== 18963 -ZG9n 18964 -IGNsaWNraW5n 18965 -KSwKCg== 18966 -I2xpbmU= 18967 -T3BlcmF0b3I= 18968 -IGNpdg== 18969 -IG1lcmc= 18970 -b2J1Zg== 18971 -bmd0aGVu 18972 -IFt7 18973 -IGNhbmNlbGw= 18974 -dHJpZ2dlcg== 18975 -Ljo= 18976 -V09SSw== 18977 -ZGVjbGFyZQ== 18978 -IGRlY3JlYXNl 18979 -xZtjaQ== 18980 -bG9vbQ== 18981 -Lk5vbmU= 18982 -IE1J 18983 -IEphc29u 18984 -IGhlYWx0aGNhcmU= 18985 -aWFtb25k 18986 -c3lsdmFuaWE= 18987 -Kng= 18988 -IFJh 18989 -W2I= 18990 -IHByaW50aW5n 18991 -cGhhYmV0 18992 -IExhYm91cg== 18993 -b3BwZXI= 18994 -IHppam4= 18995 -LXRhcmdldA== 18996 -X0ZVTkNUSU9O 18997 -IG9jdA== 18998 -0LXQvdC40Y8= 18999 -5Zyo 19000 -IHdlc3Rlcm4= 19001 -IGNvbXB1dGVycw== 19002 -IFJFVA== 19003 -SGFzaE1hcA== 19004 -W1N0cmluZw== 19005 -Z2V0VmFsdWU= 19006 -X0RBVEU= 19007 -Lk5leHQ= 19008 -IEZpZg== 19009 -w6ls 19010 -aWNrZWQ= 19011 -5o4= 19012 -LU1N 19013 -IHsKCgo= 19014 -IGNvbnRhY3Rz 19015 -IGRpZ2l0cw== 19016 -UHJvZHU= 19017 -IHVudXN1YWw= 19018 -IHJhcGlkbHk= 19019 -dHVyZXM= 19020 -IGFuZ3J5 19021 -Y2FuY2Vs 19022 -eHh4eA== 19023 -X3BhcnNlcg== 19024 -aWRpdHk= 19025 -X1BSRUZJWA== 19026 -NzEw 19027 -IG1laHI= 19028 -IHJhcmVseQ== 19029 -ZXRoZQ== 19030 -b3Blcw== 19031 -ICUu 19032 -d29ya3M= 19033 -IHRoZXRh 19034 -IGNvbnRyaWJ1dGlvbg== 19035 -IFRvbnk= 19036 -IHNxdWFk 19037 -NTM3 19038 -0LDQuQ== 19039 -IMOubg== 19040 -dGhlcmU= 19041 -b3V0ZWQ= 19042 -CXE= 19043 -mYI= 19044 -Z29vZA== 19045 -TEk= 19046 -6aG1 19047 -IExpdmluZw== 19048 -aXphYmV0aA== 19049 -IGt0 19050 -IERhbGxhcw== 19051 -XV0sCg== 19052 -IC8+Cgo= 19053 -IHJhaXNpbmc= 19054 -L3JvdXRlcg== 19055 -X2dhbWU= 19056 -MzY4 19057 -IENVUg== 19058 -emVucw== 19059 -LmVz 19060 -IGZvbnRXZWlnaHQ= 19061 -KGZ1bmM= 19062 -bm90aWZpY2F0aW9u 19063 -ICcuLi8uLi8uLi8= 19064 -IGJsYW1l 19065 -44CCCgoKCg== 19066 -YW5jbw== 19067 -OTgw 19068 -SWRlbnRpdHk= 19069 -Zm9sbG93 19070 -IGFydHM= 19071 -eHM= 19072 -IG9mZmljaWFsbHk= 19073 -IFN0dWRpbw== 19074 -IHJlY29tbWVuZGF0aW9ucw== 19075 -IGxvY2FsZQ== 19076 -IGFtYXRldXI= 19077 -IEVuYWJsZQ== 19078 -IGNhcHM= 19079 -LkVuZA== 19080 -Mzg4 19081 -LWFkZA== 19082 -X2dzaGFyZWQ= 19083 -IENU 19084 -Rm9yY2U= 19085 -CiAgICAgICAgICAgIAo= 19086 -IG9yYW5nZQ== 19087 -IGxw 19088 -IGFuc3dlcmVk 19089 -LkdyaWQ= 19090 -IGR1YWw= 19091 -IHN0cmF0ZWdpYw== 19092 -IG5vYm9keQ== 19093 -IGZhdGFs 19094 -X2VzdA== 19095 -KGVs 19096 -IOyg 19097 -IEJ1ZGQ= 19098 -QUlU 19099 -X2ZhY3Rvcg== 19100 -LW9uZQ== 19101 -IEhBVkU= 19102 -Ig0KDQo= 19103 -NzYw 19104 -UHJvZg== 19105 -IMOkcg== 19106 -c3RyaW5ncw== 19107 -IGRpcnR5 19108 -IEZhY2U= 19109 -IEJlZ2lu 19110 -IEJ1cw== 19111 -IHdpcw== 19112 -5a2X 19113 -IHNwZWFrZXI= 19114 -IGNhcnJpZXI= 19115 -IE9t 19116 -IGhhZG4= 19117 -QWxsb3c= 19118 -OjpfXw== 19119 -IHZlcmI= 19120 -IENvbXBsZXRl 19121 -IEVhc3k= 19122 -IGJpbGxz 19123 -ICAKCg== 19124 -VmVydGljYWw= 19125 -IHByb24= 19126 -IERlZmluZQ== 19127 -IGxvb2t1cA== 19128 -dmFyaWFibGVz 19129 -IHBhbmRhcw== 19130 -dW1lcw== 19131 -IGlubm9j 19132 -IHNldFVw 19133 -IENoYW1waW9uc2hpcA== 19134 -YXJ0aXN0 19135 -IENUeXBl 19136 -Rm91bmRhdGlvbg== 19137 -4LmI 19138 -IFNldHVw 19139 -NDI4 19140 -IHJlY2lwZXM= 19141 -IFVJQ29sb3I= 19142 -IEZpZ2h0 19143 -IGF1dGhvcml6ZWQ= 19144 -X2NsaWNr 19145 -OTkw 19146 -X3N1Y2Nlc3M= 19147 -YW5nYW4= 19148 -IE1vdW50YWlu 19149 -IERvY3Rvcg== 19150 -IGVnZw== 19151 -IE1lZGljaW5l 19152 -Y2xlcw== 19153 -YC4K 19154 -W2ludA== 19155 -ZGFzaGJvYXJk 19156 -IEFwcHJv 19157 -LWRy 19158 -IHByb2R1Y2Vz 19159 -IHJlbnRhbA== 19160 -IHJlbG9hZA== 19161 -Mzgx 19162 -IGFycml2YWw= 19163 -c3BvdA== 19164 -IHVuZGVydA== 19165 -Mzc4 19166 -IGVxdWlwcGVk 19167 -IHByb3ZlZA== 19168 -IGNlbnRlcnM= 19169 -IGRlZmluZXM= 19170 -YWxzbw== 19171 -IG9wYWNpdHk= 19172 -IFVuZm9ydHVuYXRlbHk= 19173 -IElsbGlub2lz 19174 -INC90LU= 19175 -IFRlbXBsZQ== 19176 -IFRyYWls 19177 -IEtlbGx5 19178 -IG1lYXN1cmVtZW50 19179 -IHNlcGFyYXRlZA== 19180 -LWNpcmNsZQ== 19181 -SGV5 19182 -IFJFQUQ= 19183 -aWdpdHM= 19184 -IGli 19185 -IE1PRA== 19186 -YXR0ZXJ5 19187 -0LDQtw== 19188 -IHZlbmQ= 19189 -0LXQvdGC 19190 -IEh0dHBDbGllbnQ= 19191 -MzU5 19192 -c2FmZQ== 19193 -X0FTUw== 19194 -aWNpdA== 19195 -IENvbnN0cnVjdA== 19196 -IENsbw== 19197 -IFNpeA== 19198 -X1RPS0VO 19199 -KGJsb2Nr 19200 -IHdhcm5lZA== 19201 -Lyoh 19202 -ITwv 19203 -YWNhZGVz 19204 -IG1hcmc= 19205 -ZXJhc2U= 19206 -IGRpc3BsYXlz 19207 -aXN0cmF0b3I= 19208 -Z2V0cw== 19209 -IGd0aw== 19210 -X0dFTkVS 19211 -bmVk 19212 -XyU= 19213 -IGZhdm91cml0ZQ== 19214 -IEJydQ== 19215 -IMOh 19216 -c2Vjb25kYXJ5 19217 -IG1hc3Q= 19218 -IHNvcGg= 19219 -IFNhZmV0eQ== 19220 -aGFyZA== 19221 -MDYy 19222 -cmFpc2U= 19223 -IEV4Y2hhbmdl 19224 -IGNvbnRlbXBvcmFyeQ== 19225 -IGRyZWFtcw== 19226 -IHRlbA== 19227 -IG5laWdoYm9ycw== 19228 -IEhvbHk= 19229 -Mzgz 19230 -Lm1lYW4= 19231 -ODEw 19232 -ZW1pdA== 19233 -IE1lc3M= 19234 -Q2FzdA== 19235 -TkVDVA== 19236 -cGx1Z2lucw== 19237 -IHJi 19238 -d3I= 19239 -IGh1Yg== 19240 -IFN0dWRpZXM= 19241 -NTYy 19242 -IHBvc3Nlc3Npb24= 19243 -JCgnLg== 19244 -ZW5zaXRpdmU= 19245 -IGFkZENyaXRlcmlvbg== 19246 -X18u 19247 -IGV4cGVydGlzZQ== 19248 -QXJjaA== 19249 -IGN1Yg== 19250 -ZXJ2ZXJz 19251 -IHBhcnRpY2xlcw== 19252 -dWFy 19253 -IGJvdW5kYXJ5 19254 -KScs 19255 -YWpv 19256 -IHByZWY= 19257 -OmA= 19258 -IGhhcmFzcw== 19259 -aXU= 19260 -IHJlYWNoaW5n 19261 -IG1lZw== 19262 -IHpv 19263 -KElE 19264 -X3JlcXVpcmVk 19265 -IHPDqQ== 19266 -IFF1ZXVl 19267 -QU8= 19268 -IGdlbQ== 19269 -ODEy 19270 -cHRvbg== 19271 -ODgw 19272 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 19273 -NjYw 19274 -aWpr 19275 -KHsNCg== 19276 -IGNvbGxpc2lvbg== 19277 -IFVrcmFpbmU= 19278 -IC0qLQo= 19279 -TlNJbnRlZ2Vy 19280 -X0JMT0NL 19281 -NTY3 19282 -IFRleHR1cmU= 19283 -IGRlY2xpbmVk 19284 -bmFu 19285 -X3dhaXQ= 19286 -IHBvbGl0aWNpYW5z 19287 -NDEz 19288 -IGNvaW5z 19289 -IGRlcml2 19290 -aGVscGVy 19291 -IFBlcmhhcHM= 19292 -LnJlY3Q= 19293 -IFBvbHk= 19294 -YWJsaW5n 19295 -fS8+Cg== 19296 -IGlubm92YXRpb24= 19297 -XyI= 19298 -ICk7DQoNCg== 19299 -IHNwb3Rz 19300 -IGNob29zaW5n 19301 -LmNz 19302 -IGZsZXhpYmxl 19303 -VUludA== 19304 -NDM1 19305 -OTMw 19306 -IHNjcmF0Y2g= 19307 -LWFs 19308 -IGZlc3RpdmFs 19309 -IG91dHN0YW5kaW5n 19310 -PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09 19311 -TWVhbg== 19312 -IE9yZWdvbg== 19313 -c3ltYm9s 19314 -LmFjY291bnQ= 19315 -ZG5leQ== 19316 -Jycn 19317 -ISIs 19318 -OTAx 19319 -IHBhcnRpY2xl 19320 -w4M= 19321 -W01BWA== 19322 -SVZFUg== 19323 -RVJFTkNF 19324 -TlNNdXRhYmxl 19325 -IENvbHVtYmlh 19326 -XwoK 19327 -LmZy 19328 -IGNvZ24= 19329 -VlI= 19330 -IE1ldGhvZHM= 19331 -IE1hZGU= 19332 -IEJS 19333 -IEVsc2U= 19334 -IGVnZ3M= 19335 -IHN3aW5n 19336 -IEludg== 19337 -IGRpc2Vhc2Vz 19338 -IGZpcm1z 19339 -IGxlbW1h 19340 -fWApOwo= 19341 -bGluZ3M= 19342 -IGd5bQ== 19343 -dW1pbnVt 19344 -LlRyaW0= 19345 -TWVt 19346 -IGNyaXRpY2lzbQ== 19347 -aWJlcm5hdGU= 19348 -X1RY 19349 -aW9uaQ== 19350 -IGd1aWRhbmNl 19351 -IHJlcGVhdGVkbHk= 19352 -IHN1cHBsaWVy 19353 -IHBhaW50aW5n 19354 -ODY0 19355 -LkZyYWdtZW50 19356 -ZWRFeGNlcHRpb24= 19357 -IHdpcmluZw== 19358 -IGNvdXJ0cw== 19359 -V0VC 19360 -5pyJ 19361 -XC4= 19362 -aWxsYW5jZQ== 19363 -IGJyb3dz 19364 -IFBhdHRlcm4= 19365 -UExJQ0FUSU9O 19366 -IFN1bW1lcg== 19367 -Q2hhaW4= 19368 -IGN1dGU= 19369 -bWVyY2lhbA== 19370 -IGRpbA== 19371 -IEZyYW5rbGlu 19372 -CWdsb2JhbA== 19373 -SU5DTFVESU5H 19374 -aGlzdG9yeQ== 19375 -IGxzdA== 19376 -UXQ= 19377 -U0RM 19378 -YWxpYQ== 19379 -aWVyZQ== 19380 -KC4uLg== 19381 -CWNpbg== 19382 -aWZmcw== 19383 -dmVsb3Bl 19384 -IFJvb3Q= 19385 -Y2x1c3Rlcg== 19386 -VXNlck5hbWU= 19387 -aWduZQ== 19388 -PFM= 19389 -IGZlc3Q= 19390 -NDE5 19391 -IGluZGljYXRpbmc= 19392 -a2VlcGVy 19393 -IGNhZGE= 19394 -w6ln 19395 -Y29uc2lu 19396 -IEdC 19397 -IGxi 19398 -ZW1vbnk= 19399 -LWljb25z 19400 -X2RvYw== 19401 -QWN0b3I= 19402 -ZWxlbQ== 19403 -LkRlbGV0ZQ== 19404 -IGluZmVjdGlvbg== 19405 -IFByaXZhY3k= 19406 -IGdyZWF0bHk= 19407 -IFBvcw== 19408 -IFRyZWF0 19409 -Rmxvdw== 19410 -IGF0dHJhY3RpdmU= 19411 -IE1hcmM= 19412 -c3Vkbw== 19413 -dGVzeQ== 19414 -LWFu 19415 -OTk4 19416 -YWJhbWE= 19417 -IFdvdWxk 19418 -IHN1Y2s= 19419 -aW5kZXhQYXRo 19420 -IEV0 19421 -VGltZXM= 19422 -Nzgw 19423 -IGNsdWJz 19424 -X2Fzc29j 19425 -IGFjcXVpcmVk 19426 -KCI6 19427 -IGludGVuc2U= 19428 -Lm1hcHM= 19429 -RXhwZWN0ZWQ= 19430 -VG9nZ2xl 19431 -IGF5 19432 -IGxpZmVzdHlsZQ== 19433 -LWNhbGxlZA== 19434 -IFNub3c= 19435 -Vm9sdW1l 19436 -IGNhbm5hYmlz 19437 -IERpcmVjdGlvbg== 19438 -IExpbWl0ZWQ= 19439 -LXNwZWNpZmlj 19440 -IGRvd250b3du 19441 -L2ljb25z 19442 -IHJldmVu 19443 -TGVn 19444 -ODg1 19445 -PW51bGw= 19446 -NDk2 19447 -S2V5Ym9hcmQ= 19448 -JykpLg== 19449 -ICIiOw0K 19450 -IGF0dGl0dWRl 19451 -Lm5hdmlnYXRl 19452 -LWVycm9y 19453 -QU1QTEU= 19454 -IEpheQ== 19455 -dnI= 19456 -Y293 19457 -LmNvbXBpbGU= 19458 -IG1lbW9yaWVz 19459 -X21hcms= 19460 -IE1pbm5lc290YQ== 19461 -IGtvc3Rlbg== 19462 -IHByb2JhYmlsaXR5 19463 -d2FybmluZw== 19464 -IGdlbmV0aWM= 19465 -Rml4dHVyZQ== 19466 -IEhhc2hTZXQ= 19467 -Tm9tYnJl 19468 -X21vbnRo 19469 -xrA= 19470 -LXN0YXJ0 19471 -eHlnZW4= 19472 -CWZ0 19473 -aWFnbm9zdGljcw== 19474 -IE1hdHRoZXc= 19475 -IGNvbmNlcHRz 19476 -IGNvbnN0cg== 19477 -LlN0YXRl 19478 -0LjQvQ== 19479 -Tm92 19480 -zrE= 19481 -IFBhbmVs 19482 -5Liq 19483 -Y29tcGFyZQ== 19484 -PigpCg== 19485 -IGFwcGx5aW5n 19486 -IHByb21pc2Vk 19487 -IG94 19488 -bmNpYQ== 19489 -IFZhbGlkYXRpb24= 19490 -b3J0cw== 19491 -X2N1cg== 19492 -ZWxlY3Q= 19493 -ZXll 19494 -KERhdGE= 19495 -IHJlcG9ydGVy 19496 -IEJ1ZmY= 19497 -Mzk1 19498 -IHNy 19499 -ICI7 19500 -aWNreQ== 19501 -IHRlbXBvcg== 19502 -U04= 19503 -IHJlc2lkZW50 19504 -cGlyZXM= 19505 -eXNpY2Fs 19506 -IGVuZG9yc2U= 19507 -IFNvbmc= 19508 -aXNFbXB0eQ== 19509 -bGVldA== 19510 -X3V0aWw= 19511 -IGRpc3Rpbmd1 19512 -IFRhbGs= 19513 -IE1vdA== 19514 -KGRlZmF1bHQ= 19515 -LkFyZw== 19516 -Z29yaXRobXM= 19517 -X3dvcmRz 19518 -aW1tZXI= 19519 -X3Jlc2V0 19520 -ZmFtaWx5 19521 -V1c= 19522 -IHNhdmluZ3M= 19523 -IOKAnQ== 19524 -X2VuYWJsZQ== 19525 -c2lkZWJhcg== 19526 -UnVubmluZw== 19527 -IGFsaQ== 19528 -IHRlc3RpbQ== 19529 -IHdhcm5pbmdz 19530 -IENoZW0= 19531 -IEV4aXQ= 19532 -IGZvdW5kZXI= 19533 -cGVjdG9y 19534 -IHJt 19535 -X2RhdGFzZXQ= 19536 -IERhcw== 19537 -IGhhbg== 19538 -R2V0dHk= 19539 -w6Fs 19540 -IG55 19541 -IHBvdmVydHk= 19542 -IHJlc3VsdGVk 19543 -LmJ5 19544 -IFZpc2l0 19545 -IG9idGFpbmluZw== 19546 -LycuJA== 19547 -ICAgICAgICAgICAK 19548 -c2hhbGw= 19549 -X0xFRlQ= 19550 -VUlJbWFnZQ== 19551 -X05hbWU= 19552 -aGF2ZQ== 19553 -IE5vYg== 19554 -bHI= 19555 -LWZvb3Rlcg== 19556 -IG5ha2Vk 19557 -IEdhcmRlbg== 19558 -XEZhY2FkZXM= 19559 -IGdyYWR1YXRl 19560 -NDE3 19561 -IGZyYW5jaGlzZQ== 19562 -cGxhbmU= 19563 -IGNvbnRyaWJ1dGlvbnM= 19564 -IHN0cmluZ1dpdGg= 19565 -IGNyeXB0bw== 19566 -IG1vdmVtZW50cw== 19567 -YXRoZXJz 19568 -IGxpZmV0aW1l 19569 -IGNvbW11bmljYXRl 19570 -amFy 19571 -IEZyYWdtZW50 19572 -X0lG 19573 -IE5hdnk= 19574 -IEZpZ3VyZQ== 19575 -IHNpbXVsYXRpb24= 19576 -X3N0b3A= 19577 -IHJlcG9ydGVycw== 19578 -IHZlcnN1cw== 19579 -YWph 19580 -IM6x 19581 -IGdvdmVybm9y 19582 -TGlzdEl0ZW0= 19583 -IHNlYWxlZA== 19584 -LkJhY2tncm91bmQ= 19585 -ZWRp 19586 -YXNoaW5n 19587 -IGxpcA== 19588 -IElo 19589 -bWVyZ2U= 19590 -IG5lYw== 19591 -MDI0 19592 -ZWxvY2l0eQ== 19593 -QVRFRw== 19594 -IHNlZWRz 19595 -IGZsb2F0aW5n 19596 -NzAx 19597 -X0ZB 19598 -d2Fsaw== 19599 -CXVzZXI= 19600 -X2RlcHRo 19601 -IHdhZ2U= 19602 -QGFwcA== 19603 -Tmls 19604 -KFsi 19605 -KHZlY3Rvcg== 19606 -IHNlY3JldGFyeQ== 19607 -NDYx 19608 -IGpQYW5lbA== 19609 -dmV6 19610 -wqDCoMKgwqA= 19611 -ZGlyZWN0aW9u 19612 -IEVQ 19613 -IGh1bnQ= 19614 -Mzk2 19615 -SnNvblByb3BlcnR5 19616 -IFBPUlQ= 19617 -XSIs 19618 -0LDQvw== 19619 -IEZvcmVpZ24= 19620 -cGFuaWM= 19621 -IHRyaWFscw== 19622 -IEFsZQ== 19623 -IHJ1cmFs 19624 -LXZhbHVl 19625 -YXV0aG9yaXplZA== 19626 -IFNjb3RsYW5k 19627 -LmRyb3A= 19628 -IE1U 19629 -57E= 19630 -Mzkx 19631 -cm93dGg= 19632 -NTE1 19633 -RmlsZVBhdGg= 19634 -IHJlY2FsbA== 19635 -aWZsZQ== 19636 -IGNlbA== 19637 -IFNFTEVDVA== 19638 -a24= 19639 -X2Nhc2U= 19640 -IGNyb3A= 19641 -NTQz 19642 -c3VyZQ== 19643 -cG90 19644 -SUNT 19645 -IHN0ZW0= 19646 -IGluZHVzdHJpZXM= 19647 -UHV0 19648 -IGFiZXI= 19649 -cm9hZGNhc3Q= 19650 -SWNvbnM= 19651 -KSIpCg== 19652 -5oiQ5Yqf 19653 -Z3Vp 19654 -IGFzc3VtZWQ= 19655 -IHJ4 19656 -RUE= 19657 -6Kc= 19658 -RUxM 19659 -IGRvc2U= 19660 -IGluZQ== 19661 -IGRlZXBlcg== 19662 -bGlkZXI= 19663 -IG9yZGluYXJ5 19664 -IGdvbGY= 19665 -NjA1 19666 -X0lNQUdF 19667 -IE5BTUU= 19668 -KG1vZHVsZQ== 19669 -IGF0b20= 19670 -IGJlbHQ= 19671 -IG9mZmljZXM= 19672 -NTA2 19673 -YmV0YQ== 19674 -IHBoaWxvc29waHk= 19675 -KEpTT04= 19676 -LWZpZWxk 19677 -IGludHJvZHVjZQ== 19678 -IGNvbnZlbmllbmNl 19679 -b3B0aW0= 19680 -PiIK 19681 -YXRoeQ== 19682 -IGVtcGxveWVy 19683 -cXVhdGU= 19684 -IGVkaXRlZA== 19685 -QXJndW1lbnRz 19686 -IE5hdGlvbnM= 19687 -X18p 19688 -IG5vc2U= 19689 -IFNhbXBsZQ== 19690 -JykKCgo= 19691 -IGNha2U= 19692 -LmdldEF0dHJpYnV0ZQ== 19693 -SEQ= 19694 -Mzky 19695 -TW9kaWZpZWQ= 19696 -NDQ1 19697 -IHByZWRpY3RlZA== 19698 -xYQ= 19699 -YW5pZQ== 19700 -U29ycnk= 19701 -KGRvYw== 19702 -d2luZA== 19703 -aWV2ZQ== 19704 -IHByb3Zpc2lvbnM= 19705 -QVRFUg== 19706 -T1RF 19707 -TVk= 19708 -LkF1dG93aXJlZA== 19709 -IEJhdGg= 19710 -NDIz 19711 -LkJvb2xlYW4= 19712 -IGJhY2tlbmQ= 19713 -Lk1vdXNl 19714 -YXRlcmFs 19715 -cGFwZXI= 19716 -Q29uc3Q= 19717 -IFZS 19718 -X2VudGl0eQ== 19719 -X0NUUkw= 19720 -IFByb3RlY3Rpb24= 19721 -IEdN 19722 -IFN0dWR5 19723 -IHNvdXA= 19724 -b3RpbWU= 19725 -J3VzZQ== 19726 -XSI= 19727 -L3VzZXJz 19728 -YXVn 19729 -IEhvbmc= 19730 -X25vcm0= 19731 -44Go 19732 -IHNlY3Jl 19733 -KEJ1aWxk 19734 -IENvbnRyYWN0 19735 -b2xhcw== 19736 -IHNhdWNl 19737 -IGFnZ3Jlc3NpdmU= 19738 -IHJhY2lhbA== 19739 -Y2hhcmFjdGVy 19740 -QEA= 19741 -IGNvbXBpbGU= 19742 -IFZvaWQ= 19743 -X3JlbQ== 19744 -X21lbW9yeQ== 19745 -MzQ4 19746 -a2s= 19747 -IG1pYw== 19748 -U2FtZQ== 19749 -VXRpbGl0eQ== 19750 -IEh0bWw= 19751 -IFhtbA== 19752 -UmVhZHk= 19753 -IGdhbGw= 19754 -IGFsbGVnZWRseQ== 19755 -CQkJCSAgIA== 19756 -IE1ldGFs 19757 -IFBlcnNvbmFs 19758 -IGJvcmRlclJhZGl1cw== 19759 -cnhqcw== 19760 -b2JqZWN0cw== 19761 -IHdhbnRpbmc= 19762 -IGJvd2w= 19763 -dmVuZG9y 19764 -b2Zmc2V0b2Y= 19765 -IFJz 19766 -IFJhdGluZw== 19767 -IHJhbGx5 19768 -X05PREU= 19769 -NDE4 19770 -IE1peA== 19771 -IGFkdmVydGlz 19772 -NDg1 19773 -NjY3 19774 -IG5hcnJhdGl2ZQ== 19775 -c2Fs 19776 -IG1j 19777 -U0Vycm9y 19778 -IGZpbmdlcnM= 19779 -IGFjY29tcGFueQ== 19780 -IHRpcmVk 19781 -IHN0cmlkZQ== 19782 -IGd1aQ== 19783 -ZWxpc3Q= 19784 -TG9jYWxl 19785 -IHJlbGVhc2Vz 19786 -aWtpbmc= 19787 -IGFuZ2Vy 19788 -KSkpCgo= 19789 -YWxsZXN0 19790 -U3VtbWFyeQ== 19791 -KE8= 19792 -KGZvcg== 19793 -IGJhc2tldGJhbGw= 19794 -IHJvYWRz 19795 -IEluc3RhbGw= 19796 -IEZhYg== 19797 -aXRtYXA= 19798 -NDc1 19799 -ICkpCg== 19800 -IGludGVyc2VjdGlvbg== 19801 -aWdoYm9y 19802 -IEJyeQ== 19803 -IEhFUkU= 19804 -U29mdHdhcmU= 19805 -ZWxmYXJl 19806 -YWNz 19807 -NjIy 19808 -IHRyYWlsZXI= 19809 -LmdldENsYXNz 19810 -Y2hhcnM= 19811 -IHJlZ3VsYXRpb24= 19812 -IHJlZmVycw== 19813 -IGRlc3RydWN0aW9u 19814 -IGNvbnRpbnVvdXM= 19815 -IEF1c3Rpbg== 19816 -6aI= 19817 -YWthbg== 19818 -LndpbmRvdw== 19819 -IFRlbXBsYXRlcw== 19820 -IGFic2VuY2U= 19821 -Om4= 19822 -IGRpc29yZGVy 19823 -Zmxhc2g= 19824 -IGRlbGV0 19825 -Ym9hcmRz 19826 -ICAJ 19827 -Uk9Q 19828 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 19829 -IGFjcXU= 19830 -IGxhd3N1aXQ= 19831 -IFJldmlld3M= 19832 -IGdhcmFnZQ== 19833 -dGltZXI= 19834 -IGVq 19835 -IFJlY3RhbmdsZQ== 19836 -IGZsb3dlcnM= 19837 -Mzk4 19838 -aWxzdA== 19839 -IEluc3RhbmNl 19840 -U3VwZXI= 19841 -ZGV0 19842 -ZGlzcG9zaW5n 19843 -IEVT 19844 -IElD 19845 -dmVyZQ== 19846 -U2s= 19847 -X2NoYW5uZWxz 19848 -cHV0ZWQ= 19849 -L251bGw= 19850 -bm5lbg== 19851 -NDMx 19852 -IEdhbGxlcnk= 19853 -X2dsb2JhbA== 19854 -QXV0aGVudGljYXRpb24= 19855 -IFJhbms= 19856 -IGJsb2NrZWQ= 19857 -IGNhbG0= 19858 -bWFya2V0 19859 -CXZhbA== 19860 -IGF1Zw== 19861 -cGVyaW9k 19862 -IENvbnN0YW50 19863 -ID8+Ij4K 19864 -IGxvYmJ5 19865 -cGFs 19866 -Mzc5 19867 -IHNpbms= 19868 -NTA4 19869 -aWFo 19870 -0KE= 19871 -dXJuYW1l 19872 -IGNvbnZlcg== 19873 -IGludmVzdGlnYXRl 19874 -Q2hyaXN0 19875 -SHVi 19876 -IElORA== 19877 -IFBlZA== 19878 -dXJhcw== 19879 -CXVybA== 19880 -IFRybw== 19881 -IHByZWZlcmVuY2Vz 19882 -IGd1YXJhbnRlZWQ= 19883 -YAoK 19884 -IHBvcnRpb25z 19885 -IGV2YWx1 19886 -Jz48Lw== 19887 -KCl7Cgo= 19888 -ZW5jb2RlZA== 19889 -emlsbGE= 19890 -LkNsYXNz 19891 -ICpf 19892 -Xyc= 19893 -IHZpZXdlZA== 19894 -IFBoaWxhZGVscGhpYQ== 19895 -LnJvd3M= 19896 -QWRkZWQ= 19897 -IFRvdWNo 19898 -ODQw 19899 -LmRlbGVnYXRl 19900 -cXVlZXpl 19901 -c2xpZGU= 19902 -IFNlbmlvcg== 19903 -KHRhZw== 19904 -IGludGVydmlld3M= 19905 -IHN1YQ== 19906 -YXRhcw== 19907 -QAoK 19908 -ZGlzdGFuY2U= 19909 -IHNlaW4= 19910 -bGF0ZXN0 19911 -IFByaW5jZQ== 19912 -IGx1eHVyeQ== 19913 -IHJlZnI= 19914 -IEtpdGNoZW4= 19915 -0YQ= 19916 -KGF0 19917 -RmluYWw= 19918 -w7xjaw== 19919 -X3plcm8= 19920 -IEFCQw== 19921 -IE1hbmNoZXN0ZXI= 19922 -IGNvdw== 19923 -Q09M 19924 -X05VTUJFUg== 19925 -Y2hhbmdlcw== 19926 -Z2VuZXJhdGU= 19927 -LlByaW50Zg== 19928 -MzY5 19929 -c2hhcmU= 19930 -U3RvY2s= 19931 -IFBU 19932 -QW5pbQ== 19933 -YW5nYQ== 19934 -IGln 19935 -dXBsb2Fkcw== 19936 -IHBhY2tlZA== 19937 -IH1dOwo= 19938 -KHNlbmRlcg== 19939 -IFdpcmU= 19940 -aXNvbnM= 19941 -IHBsYXlvZmY= 19942 -XEU= 19943 -NjA4 19944 -L1I= 19945 -IGhlYWRlZA== 19946 -QWxwaGE= 19947 -KG9yZGVy 19948 -IG9wcG9uZW50cw== 19949 -YWNrc29u 19950 -X21lbWJlcg== 19951 -VHVybg== 19952 -IFNvdmlldA== 19953 -7JeQ 19954 -YXVnZQ== 19955 -NDQ4 19956 -IGluY29taW5n 19957 -IGphaw== 19958 -LWdhbWU= 19959 -IE1hbGU= 19960 -IE1vbnRo 19961 -U3RhZ2U= 19962 -LmV4ZQ== 19963 -T3duUHJvcGVydHk= 19964 -LnNldEl0ZW0= 19965 -IGRj 19966 -5L2c 19967 -IGJydXQ= 19968 -IGF0dGVtcHRpbmc= 19969 -Lmxlbg== 19970 -IGp1ZGdtZW50 19971 -IHNhYg== 19972 -IGNhZA== 19973 -IEl0ZW1z 19974 -Y29tZm9ydA== 19975 -ZWxpemU= 19976 -L2xvZw== 19977 -IGVudHJlcHJlbmU= 19978 -IGNvbXBpbGVy 19979 -X3ZhbGlkYXRpb24= 19980 -cmV2aWV3 19981 -IHRleHRCb3g= 19982 -IGZyYWN0aW9u 19983 -IEJhbA== 19984 -PjsKCg== 19985 -LkF1dG9TY2FsZU1vZGU= 19986 -IGNhdHM= 19987 -NDY1 19988 -IHJlZ2lzdHJ5 19989 -dWx1cw== 19990 -Rkk= 19991 -cGF5bG9hZA== 19992 -LXNlYXJjaA== 19993 -IHN0YXlpbmc= 19994 -YWNpb3Vz 19995 -RGVjb3JhdGlvbg== 19996 -UmV2aWV3 19997 -SW5m 19998 -S2VlcA== 19999 -aXRpcw== 20000 -LFN0cmluZw== 20001 -Q29vcmQ= 20002 -IHBlcm8= 20003 -U2V4 20004 -IEF0bGFudGE= 20005 -dWVzdGE= 20006 -QXJnYg== 20007 -Pio= 20008 -fV8= 20009 -Rm9vdGVy 20010 -IGVtcGxveWVk 20011 -X2JvdW5k 20012 -dmlkZQ== 20013 -LmZ1bmM= 20014 -JHNjb3Bl 20015 -IHNwbw== 20016 -IEFuYWw= 20017 -b3VuY2Vk 20018 -YXJvdW5k 20019 -IHJlc3RyaWN0aW9u 20020 -IHNob3Bz 20021 -5YA= 20022 -IExhdGlu 20023 -LWNvbA== 20024 -IGJhcmVseQ== 20025 -IEV1cm8= 20026 -RXI= 20027 -IGZhaXJl 20028 -X2Rpc3RhbmNl 20029 -X3VubG9jaw== 20030 -UXVvdGU= 20031 -SVZBVEU= 20032 -IOWI 20033 -IGFpbWVk 20034 -IFJldHJpZQ== 20035 -Lml0ZXI= 20036 -IHdyYXBwZWQ= 20037 -IGFncmVlbWVudHM= 20038 -c3RydW1lbnQ= 20039 -KHByb2R1Y3Q= 20040 -IHN0dWRpZWQ= 20041 -LnNldFZhbHVl 20042 -IHll 20043 -IENhY2hl 20044 -TUJPTA== 20045 -IHF1YXJ0ZXJiYWNr 20046 -IHN5bnRheA== 20047 -LmdldEVsZW1lbnRzQnk= 20048 -LnZlcnNpb24= 20049 -d2Vic2l0ZQ== 20050 -UnVubmVy 20051 -X3NpbmdsZQ== 20052 -YXRpdg== 20053 -IEFsdGVybg== 20054 -IEJlYXV0aWZ1bA== 20055 -cmlnaHRhcnJvdw== 20056 -IGRpdmVyc2l0eQ== 20057 -cGxhc2g= 20058 -KGNv 20059 -LkZpbGw= 20060 -IHR5cGluZw== 20061 -Mzg3 20062 -MDIz 20063 -IGNsYXI= 20064 -SGl0 20065 -T08= 20066 -YWNjbw== 20067 -NTA3 20068 -d29ydGg= 20069 -IHNjcmlwdHM= 20070 -IE11c2xpbXM= 20071 -IExM 20072 -ZXJ2aW5n 20073 -KGJvb2xlYW4= 20074 -IGJhc2ViYWxs 20075 -IENBTg== 20076 -Mzk0 20077 -MDQ0 20078 -TUFJTA== 20079 -ZGVwZW5k 20080 -IHJlc3BlY3RpdmU= 20081 -IGNvbnN0ZXhwcg== 20082 -Lio7Cgo= 20083 -J10pKQo= 20084 -IHlhcmQ= 20085 -IGlkZW50aWNhbA== 20086 -aWZlY3ljbGU= 20087 -VVNI 20088 -dXBpdGVy 20089 -LnZhbGlkYXRl 20090 -Y2xp 20091 -SVNURVI= 20092 -SW5kaWNhdG9y 20093 -RmFpbA== 20094 -IGRlbW9jcmFjeQ== 20095 -LnZhcg== 20096 -IHNhdGlzZmllZA== 20097 -LS0tLS0tLS0tLS0tLQ== 20098 -ZW5jZXI= 20099 -aG9y 20100 -IHJvdW5kcw== 20101 -REFP 20102 -b2E= 20103 -IGZsYXNr 20104 -PWM= 20105 -W10K 20106 -L2Rpc3Q= 20107 -IHBhcnRl 20108 -IGNvbmZpcm1hdGlvbg== 20109 -ZXJvbg== 20110 -YXdhcmU= 20111 -PD8+ 20112 -IGRlcGVuZGVuY2llcw== 20113 -IFZpZGVvcw== 20114 -LXJvdw== 20115 -ICoqLwo= 20116 -IG5vdQ== 20117 -IGhvdmVy 20118 -5p4= 20119 -IG5pbg== 20120 -IFVTRA== 20121 -TWFj 20122 -X0xvYWQ= 20123 -IG91dGNvbWVz 20124 -X3NvY2tldA== 20125 -IHF1ZXJpZXM= 20126 -d20= 20127 -NTky 20128 -IGhpdHRpbmc= 20129 -aW51eA== 20130 -TWljaA== 20131 -dWRnZQ== 20132 -QVRBQg== 20133 -IHZ1bG5lcmFibGU= 20134 -5L4= 20135 -IHBvcnRmb2xpbw== 20136 -OllFUw== 20137 -CW1hcA== 20138 -Qm91bmQ= 20139 -IGl0ZXJhdGlvbg== 20140 -aW5jZXNz 20141 -IGFjdG9ycw== 20142 -IFF1YWw= 20143 -X2NsZWFu 20144 -44CR44CQ 20145 -TVNH 20146 -R3JlZW4= 20147 -IE9mZmljZXI= 20148 -IHNtb2tpbmc= 20149 -Pics 20150 -IEZsbw== 20151 -Kys7 20152 -NDMz 20153 -b2x5Z29u 20154 -IGJ1bGs= 20155 -IGRyYW1h 20156 -IGV4Y2VwdGlvbnM= 20157 -b3NlZA== 20158 -ICsNCg== 20159 -IGxlZ2FjeQ== 20160 -Q1Y= 20161 -IGNvbnRyaWJ1dGVk 20162 -IFRlcm1z 20163 -IGJ0 20164 -NDM0 20165 -IHVudHVr 20166 -IGFsaWVu 20167 -PT09Cg== 20168 -CVZlY3Rvcg== 20169 -IGxz 20170 -T25saW5l 20171 -LmZhY2Vib29r 20172 -bnVtZXJpYw== 20173 -b2NrZXRz 20174 -QXV0 20175 -YnVyeQ== 20176 -LXJlZHV4 20177 -IFJlZGlzdHJpYnV0aW9ucw== 20178 -R0xPQkFMUw== 20179 -dXJyZW5jaWVz 20180 -IHRvbnM= 20181 -4oCZLA== 20182 -IMOq 20183 -KGNvbA== 20184 -IFN5bWJvbA== 20185 -IHN0YXllZA== 20186 -IE1M 20187 -IG11bmljaXA= 20188 -IHNleG8= 20189 -U2Vu 20190 -bnI= 20191 -IGdhaW5z 20192 -IHNob3J0bHk= 20193 -Lk1lbnU= 20194 -w70= 20195 -S05PV04= 20196 -IG9wZXJhdG9ycw== 20197 -LVY= 20198 -IFBhdHJpY2s= 20199 -L2FkZA== 20200 -X0NP 20201 -aXJhdGlvbg== 20202 -KHBvc3Q= 20203 -UG9zdHM= 20204 -L18= 20205 -IHBsdWc= 20206 -IGludGVsbGVjdHVhbA== 20207 -IG1ldGFi 20208 -IHByZWduYW5jeQ== 20209 -IFByZW1pZXI= 20210 -bm0= 20211 -IHByZWRpY3Rpb24= 20212 -NjA2 20213 -IE1pbmlzdHJ5 20214 -VGhyZWU= 20215 -dmFsdWF0ZQ== 20216 -IE1pbmk= 20217 -YnU= 20218 -0L7Qtw== 20219 -PHVs 20220 -IGRk 20221 -b2x2aW5n 20222 -IEN1dA== 20223 -NjAy 20224 -IHNjaGVt 20225 -LnRyYWlu 20226 -aXRhdGU= 20227 -IHJpY2U= 20228 -IGJpcmRz 20229 -44Gr 20230 -bWlkZGxl 20231 -c3RydWN0aW9ucw== 20232 -IG5lcnY= 20233 -YXF1ZQ== 20234 -NDUz 20235 -IGZsdQ== 20236 -IHN1cnZpdmFs 20237 -IEdhbGF4eQ== 20238 -IEZhbnQ= 20239 -Lk9yZGVy 20240 -QXR0cmli 20241 -aXJ0cw== 20242 -w6lj 20243 -TW92aWU= 20244 -IGNvbmNl 20245 -cXVhcnRlcnM= 20246 -IG1vb2Q= 20247 -LkFkZFJhbmdl 20248 -OTQy 20249 -IHJlc29sdmVk 20250 -44OI 20251 -IGJ1cm5pbmc= 20252 -NzAy 20253 -CQkJCQ0K 20254 -IFdF 20255 -IGhvc3Rpbmc= 20256 -TEFC 20257 -IG1hbmFnZXJz 20258 -IHN0cmVuZ3RoZW4= 20259 -PGNvbnN0 20260 -IEZpcmViYXNl 20261 -b25lZA== 20262 -IEplYW4= 20263 -Jzwv 20264 -IDo9Cg== 20265 -YWxnb3JpdGht 20266 -IEFyYw== 20267 -IGZyb3plbg== 20268 -X2V2ZW50cw== 20269 -IG92ZXJzZQ== 20270 -Z29vZHM= 20271 -IGZhaXQ= 20272 -IHZpYWdyYQ== 20273 -b3Nlcw== 20274 -OTIy 20275 -IGNvbXBpbGVk 20276 -IEF0aA== 20277 -IHN1YnN0YW5jZQ== 20278 -YW5pbWF0ZWQ= 20279 -UEY= 20280 -cHJldmlvdXM= 20281 -IHJvb3Rz 20282 -KGZpbHRlcg== 20283 -b2x1bWVz 20284 -IGludHJv 20285 -KGV2dA== 20286 -IEJhZw== 20287 -IERlZmluaXRpb24= 20288 -IEZlYXR1cmVz 20289 -QW5ub3RhdGlvbg== 20290 -IGF2Zw== 20291 -KHN1bQ== 20292 -UVVJUkU= 20293 -IHJlbmRlcmVy 20294 -IEZpeA== 20295 -LmRhdGV0aW1l 20296 -PWRldmljZQ== 20297 -U3Bl 20298 -Z2V0SW5zdGFuY2U= 20299 -IGV4dGVuc2lvbnM= 20300 -X25ldA== 20301 -IFBhcmxpYW1lbnQ= 20302 -IGNvbWlj 20303 -NDY4 20304 -IFBpY2s= 20305 -YXJtYQ== 20306 -CW1vZGVs 20307 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 20308 -IG1lbmc= 20309 -bWFudWFs 20310 -YWRhcHRlcg== 20311 -fS0= 20312 -ZWRiYWNr 20313 -IGVsZWN0cmljYWw= 20314 -IENvdW50ZXI= 20315 -QXBwbGljYXRpb25Db250ZXh0 20316 -X2J5dGU= 20317 -KGJ5dGU= 20318 -IEF1dG9t 20319 -IHRlcnJvcmlzdA== 20320 -55A= 20321 -dGhyb3VnaA== 20322 -IGZpc2NhbA== 20323 -b25pbmc= 20324 -NDU1 20325 -IHNwZWN0cnVt 20326 -IGJpdG1hcA== 20327 -IHNsZQ== 20328 -cHJvZA== 20329 -IGFnZWQ= 20330 -IGJlbmU= 20331 -IFNwaQ== 20332 -IGJyaWxsaWFudA== 20333 -IHN0YWJpbGl0eQ== 20334 -IGRpYWJldGVz 20335 -IGNvbmZpZ3VyZWQ= 20336 -Ym9uZQ== 20337 -NzQ4 20338 -NDg0 20339 -b3VzZXM= 20340 -Lmdvb2dsZWFwaXM= 20341 -RkFDRQ== 20342 -IGluc3BpcmF0aW9u 20343 -IERldHJvaXQ= 20344 -ZW5jaA== 20345 -0YDRgw== 20346 -dmVoaWNsZQ== 20347 -U3RhdGlvbg== 20348 -IGhvbGVz 20349 -IGR1cmNo 20350 -Lk1lZGlh 20351 -IENOTg== 20352 -aW5uaW5n 20353 -NjA0 20354 -IFBlbm5zeWx2YW5pYQ== 20355 -IGVtb3Rpb24= 20356 -U2VjcmV0 20357 -w6FyaW8= 20358 -IFJhdGU= 20359 -NDUx 20360 -RGVwdGg= 20361 -IG1vZGVz 20362 -NDI2 20363 -KGlkeA== 20364 -IGhlcw== 20365 -IGdyZXk= 20366 -U3RhbmRhcmQ= 20367 -UXVlc3Q= 20368 -YnV5 20369 -c3Vy 20370 -IFRyYWNr 20371 -b21t 20372 -Lmds 20373 -IChc 20374 -dHdv 20375 -X0lP 20376 -b3NleA== 20377 -X3JvbGU= 20378 -56S6 20379 -cm91dGVz 20380 -U2hvcA== 20381 -IEFTQw== 20382 -IG1lbWNweQ== 20383 -ZGlyZWN0 20384 -NDQ2 20385 -ICoKCg== 20386 -IEJN 20387 -IFBvcg== 20388 -X2hpc3Rvcnk= 20389 -IFJlc3BvbnNlRW50aXR5 20390 -LnNldEZvbnQ= 20391 -IGVuZ2FnZW1lbnQ= 20392 -LGg= 20393 -IFdvcmRQcmVzcw== 20394 -ZmVjaGE= 20395 -IGVudHJhbmNl 20396 -RGVzcGl0ZQ== 20397 -SURFTlQ= 20398 -IHNhbml0 20399 -IEdlbmVyYXRl 20400 -KCIiLA== 20401 -X3ZpZGVv 20402 -U3RyYXRlZ3k= 20403 -X29r 20404 -IHRpZXM= 20405 -IGxvZ2ljYWw= 20406 -IEJyb24= 20407 -KEZpbGU= 20408 -IE1vaA== 20409 -LlNwbGl0 20410 -LlRyeQ== 20411 -IEhpbmQ= 20412 -IHNjb3Jpbmc= 20413 -IGFwcHJvYWNoZXM= 20414 -IGZsb3Vy 20415 -VlJU 20416 -ODA0 20417 -VVNUT00= 20418 -NDY3 20419 -c2NyaXB0cw== 20420 -IEVwaXNvZGU= 20421 -Mzg5 20422 -IEFtYg== 20423 -X09S 20424 -IGZyYXVlbg== 20425 -IHVubGlrZQ== 20426 -IHJpZGluZw== 20427 -IHBpdA== 20428 -IHRyYW5zZg== 20429 -YXJ0ZQ== 20430 -4LmJ 20431 -cmFwZQ== 20432 -cmV0dmFs 20433 -X2FmdGVy 20434 -Ijw8 20435 -NzAz 20436 -IEJlcmxpbg== 20437 -IHRpc3N1ZQ== 20438 -LkludGVudA== 20439 -INC00LvRjw== 20440 -IHN0dW5uaW5n 20441 -IEhhbA== 20442 -LkludGVnZXI= 20443 -IHdoZXJlYXM= 20444 -IGRlbGVn 20445 -IHVzZXJOYW1l 20446 -IGZvcm1hdHM= 20447 -IGNvbXBlbnNhdGlvbg== 20448 -IEh1bQ== 20449 -YXJyaW5n 20450 -IHVuc2FmZQ== 20451 -UGlu 20452 -Y2x1Yg== 20453 -a2V5d29yZA== 20454 -X3RoZW1l 20455 -IGNhbGxlcg== 20456 -IGdob3N0 20457 -IGVudGl0bGVk 20458 -IE1hcw== 20459 -NTYx 20460 -IGRlbW9uc3RyYXRl 20461 -IEhvd2FyZA== 20462 -RHJvcA== 20463 -I3VuZGVm 20464 -NDI3 20465 -IGludm9rZQ== 20466 -IEJyaWRnZQ== 20467 -ZW5kZW4= 20468 -aWJsaW5n 20469 -U2xvdA== 20470 -QVRBQkFTRQ== 20471 -IHRlbXBlcmF0dXJlcw== 20472 -c2VyaWVz 20473 -IFJlbWVtYmVy 20474 -Q2FsZW5kYXI= 20475 -QkY= 20476 -PT8= 20477 -MDY0 20478 -IEFG 20479 -KGh0dHA= 20480 -bWFrZXJz 20481 -ZmluaXR5 20482 -cHJlY2F0ZWQ= 20483 -V0g= 20484 -b2xpZGF5cw== 20485 -LXVu 20486 -aWFsZQ== 20487 -XFVzZXI= 20488 -cmVhc29u 20489 -JywKCg== 20490 -T1dFUg== 20491 -IHByZWRpY3Rpb25z 20492 -cHJvYg== 20493 -Lm5u 20494 -ICc7Cg== 20495 -LkZyb21Bcmdi 20496 -X0xPTkc= 20497 -IHRyb3Vi 20498 -IHVuaXR0ZXN0 20499 -ZWxpaG9vZA== 20500 -CWlz 20501 -NDQy 20502 -IGNvbnNlYw== 20503 -TEVBU0U= 20504 -IGNsaWNrZWQ= 20505 -IHRlbXBsYXRlcw== 20506 -Qlk= 20507 -cGVybQ== 20508 -bWF0Y2hlcw== 20509 -bGF3 20510 -KHRm 20511 -X3JhdGlv 20512 -aXRlbXB0eQ== 20513 -IGNyZWF0b3I= 20514 -Qml0cw== 20515 -RW5jb2Rlcg== 20516 -Ki4= 20517 -IFVJVA== 20518 -IE1hc2s= 20519 -Y3VybA== 20520 -LWdv 20521 -IE9jYw== 20522 -Y29ycmVjdA== 20523 -IEdlcg== 20524 -KGxheW91dA== 20525 -dW5jdA== 20526 -LmRpc3BhdGNo 20527 -O2FtcA== 20528 -LmlzUmVxdWlyZWQ= 20529 -CWRv 20530 -bWly 20531 -IHB0aHJlYWQ= 20532 -LWF1dG8= 20533 -IEljZQ== 20534 -IHZpb2xhdGlvbg== 20535 -IGNvbmNsdWRlZA== 20536 -IHZhcnM= 20537 -Y2FudmFz 20538 -IFRlbXA= 20539 -IFBoaWxpcHA= 20540 -iOuLpA== 20541 -Y3JlYXNl 20542 -IGZpc2hpbmc= 20543 -YWJiaXQ= 20544 -IGNvbmNlbnRyYXRpb24= 20545 -aXJ0aGRheQ== 20546 -IGdyb3Nz 20547 -IGtp 20548 -IEhhbmRsZXI= 20549 -IGltbWlncmFudHM= 20550 -6IA= 20551 -VW5k 20552 -cG4= 20553 -cmFj 20554 -NDU0 20555 -IENvbnN1bHQ= 20556 -Zm9sZA== 20557 -IHN0cnVnZ2xpbmc= 20558 -aGVhdA== 20559 -R2VuZXJpYw== 20560 -IHJpZGlj 20561 -IENPVklE 20562 -b21pdGVtcHR5 20563 -X09QVElPTg== 20564 -6rCA 20565 -IGNyZWF0dXJlcw== 20566 -X1BBR0U= 20567 -ZWk= 20568 -KGhvc3Q= 20569 -X0hQUA== 20570 -NTE2 20571 -IFhYWA== 20572 -IGF3aw== 20573 -YXNjYWRl 20574 -IHByZWc= 20575 -cHJvdmlkZXI= 20576 -UGFs 20577 -ZWdlbg== 20578 -Y2xvbmU= 20579 -LlJlZ2lzdGVy 20580 -IGF0dGFjaG1lbnQ= 20581 -YmVpdA== 20582 -dGhlbGVzcw== 20583 -KERhdGU= 20584 -IEZvcmVzdA== 20585 -Q0dSZWN0 20586 -IGNoaWxkaG9vZA== 20587 -YW1pbmU= 20588 -YXhlcw== 20589 -J109 20590 -TmF2aWdhdG9y 20591 -IHJlcGxpZWQ= 20592 -X2ludg== 20593 -LFQ= 20594 -IEZlYXR1cmU= 20595 -NDM4 20596 -ey0= 20597 -TEFORw== 20598 -IGNvbnZleQ== 20599 -55So5oi3 20600 -IFNlcmlm 20601 -IEF1cw== 20602 -bGljaGU= 20603 -IHVudXNlZA== 20604 -IG1vbnQ= 20605 -bm9kZXM= 20606 -IHNldQ== 20607 -LmNsYXNzTmFtZQ== 20608 -bm9ybQ== 20609 -X1NFUlZFUg== 20610 -IHdpbmc= 20611 -aW54 20612 -UmF3 20613 -IEphbQ== 20614 -NTkw 20615 -IGluc2lnaHQ= 20616 -NDcx 20617 -NTM1 20618 -IE5H 20619 -IEludGVyZmFjZQ== 20620 -IHN0bXQ= 20621 -IG5hbg== 20622 -Y3VsYXRvcg== 20623 -LWFwcA== 20624 -KEJ1bmRsZQ== 20625 -TWVzc2FnZUJveA== 20626 -4K4= 20627 -IG1lZXRz 20628 -dWJ5 20629 -T3B0aW9uUGFuZQ== 20630 -aXRhcmlhbg== 20631 -IGNvbGxhYm9yYXRpb24= 20632 -bW92aWU= 20633 -IGFybW9y 20634 -X2JpdHM= 20635 -IEhhdmluZw== 20636 -IG51ZGU= 20637 -IFNldHRpbmc= 20638 -IHN1Y2M= 20639 -RGVsYXk= 20640 -LmNvbXBvbmVudHM= 20641 -YWNodXNldA== 20642 -IEFsZXhhbmRlcg== 20643 -wqk= 20644 -IG1ldGVycw== 20645 -IHByZXBhcmluZw== 20646 -IGluY2VudA== 20647 -5ZM= 20648 -IGvDtm5uZW4= 20649 -IENvbnNlcnY= 20650 -IG51bWVybw== 20651 -YWNodXNldHRz 20652 -LWludA== 20653 -IGVtcGhhcw== 20654 -bGF5b3V0cw== 20655 -RXhjZWw= 20656 -SUJBY3Rpb24= 20657 -IHJlc2lkZW50aWFs 20658 -ZWxpbmc= 20659 -IE5D 20660 -IEFsbGVu 20661 -IGNldHRl 20662 -IG1pbmRz 20663 -LnJlcXVpcmVk 20664 -2LM= 20665 -IEdpcmxz 20666 -IH07 20667 -IHN0cmluZ1dpdGhGb3JtYXQ= 20668 -IGFkZHJlc3NlZA== 20669 -dGhleQ== 20670 -IEJsb29k 20671 -cG9zZXI= 20672 -IGphbQ== 20673 -yJk= 20674 -5pWw5o2u 20675 -IHN0ZG91dA== 20676 -IFVURg== 20677 -Q2xhc3Nlcw== 20678 -PiI7DQo= 20679 -IFNhdg== 20680 -LkJvbGQ= 20681 -IGVuYWJsZXM= 20682 -CXRtcA== 20683 -IG1hbnVhbGx5 20684 -IFNxdQ== 20685 -dXNlcmlk 20686 -LmZ1bmN0aW9u 20687 -LmNhY2hl 20688 -TE9QVA== 20689 -LlNlcnZpY2Vz 20690 -NTg4 20691 -ZGRpdA== 20692 -dGlt 20693 -PGltZw== 20694 -IFRoaW5ncw== 20695 -IEV2ZXJ5dGhpbmc= 20696 -IGFwdA== 20697 -Mzk3 20698 -ZW1hbmQ= 20699 -IHJvbGxpbmc= 20700 -66Y= 20701 -LmxldmVs 20702 -IHN0b20= 20703 -IFdpbnRlcg== 20704 -IHZpZXdpbmc= 20705 -KHZhbHVlcw== 20706 -b2NvbXBsZXRl 20707 -dmlh 20708 -dXBv 20709 -IGFib3J0aW9u 20710 -NTMy 20711 -acOocmU= 20712 -77yR 20713 -X0JVVFRPTg== 20714 -X2RvbWFpbg== 20715 -IGJyYQ== 20716 -IEFzdA== 20717 -aW5hcw== 20718 -IHN0YXRpc3Q= 20719 -Y29k 20720 -TFI= 20721 -IGRyaXZlcw== 20722 -IGZvbGxvd2Vycw== 20723 -IGFsbGllcw== 20724 -CWN1cnJlbnQ= 20725 -ZWNlc3Nhcnk= 20726 -IGRhbWFnZWQ= 20727 -X3B0 20728 -YW5kbGVz 20729 -b3VudHJpZXM= 20730 -IHNpbXVsdA== 20731 -ZXU= 20732 -IGNvbnRyb3ZlcnNpYWw= 20733 -X0dST1VQ 20734 -IHJpYg== 20735 -LkluZm8= 20736 -Om1t 20737 -Lm5vcm1hbA== 20738 -X0FERFJFU1M= 20739 -IO2V 20740 -YWRkbGU= 20741 -IER1cg== 20742 -LkVsZW1lbnQ= 20743 -NjU2 20744 -V2FybmluZ3M= 20745 -IGNyZWRpdHM= 20746 -IGluaGli 20747 -IGVtaXNzaW9ucw== 20748 -NTQ1 20749 -IGhheg== 20750 -LnlvdXR1YmU= 20751 -dWdnZWQ= 20752 -IGJvdGhlcg== 20753 -IEthbnNhcw== 20754 -IEZpeGVk 20755 -IFRlc3Rz 20756 -IEZJWA== 20757 -NTc2 20758 -VW5pZm9ybQ== 20759 -IGtvbnQ= 20760 -Pj4+ 20761 -c3RhdGlvbg== 20762 -bG9yZQ== 20763 -YXR5cGU= 20764 -aXNob3A= 20765 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 20766 -NTIx 20767 -Q29tYm9Cb3g= 20768 -IHZhY2F0aW9u 20769 -IGluaXRpYXRpdmU= 20770 -IGRlZmF1bHRWYWx1ZQ== 20771 -Nzcw 20772 -Y29uY2F0 20773 -IEto 20774 -NjMy 20775 -IFdlbGNvbWU= 20776 -aXplZE5hbWU= 20777 -TWlncmF0aW9u 20778 -IGdyYWRpZW50 20779 -SG90 20780 -IGhhcmRseQ== 20781 -ZWxv 20782 -IFN0dWRlbnRz 20783 -IGxvb3Nl 20784 -NzMw 20785 -YXR6 20786 -LlNlbmQ= 20787 -Jy8= 20788 -IHVuaXZlcnNhbA== 20789 -IGVudGVycHJpc2U= 20790 -IHJlZ2V4 20791 -IHZpc2l0b3I= 20792 -IEZseQ== 20793 -U2Vx 20794 -4LiZ 20795 -IFZpc3VhbA== 20796 -IGxpYnJhcmllcw== 20797 -YXRvZXM= 20798 -UGF5bWVudA== 20799 -NDQ3 20800 -IHBlbnQ= 20801 -IGdhdGhlcmVk 20802 -VlJUWA== 20803 -IERN 20804 -U3BsaXQ= 20805 -IGxldHRpbmc= 20806 -0J0= 20807 -X2Vycm9ycw== 20808 -ZXBvY2g= 20809 -UEFSQU0= 20810 -Y3U= 20811 -0YHRgtCy 20812 -b2x1dGlvbnM= 20813 -RWRpdGluZw== 20814 -Zm9udHM= 20815 -IGFsbG9jYXRlZA== 20816 -IEJhc2Vk 20817 -KFk= 20818 -IEp1ZGdl 20819 -IGJyb3RoZXJz 20820 -RklMRVM= 20821 -w6dv 20822 -NTMx 20823 -d2I= 20824 -X1BJ 20825 -J14= 20826 -IHN3b3Jk 20827 -LnNlcnZpY2Vz 20828 -IG5s 20829 -VGlt 20830 -aWdn 20831 -IE1vb3Jl 20832 -IGNyeXB0b2M= 20833 -5Ye6 20834 -X3Bvc3Rz 20835 -b3RhdGU= 20836 -Pyc= 20837 -Li4uLgoK 20838 -IGts 20839 -PSIk 20840 -IGRlY29yYXRpb24= 20841 -4bqh 20842 -IERJUkVDVA== 20843 -R1VJ 20844 -KT0+ewo= 20845 -IG5ld3NsZXR0ZXI= 20846 -IHByZWNpcw== 20847 -KHBvaW50 20848 -IEVxdWlwbWVudA== 20849 -dXR5 20850 -IERhdmU= 20851 -IHBhcnRpY2lwYXRpb24= 20852 -dWFyaW9z 20853 -eGl0 20854 -LkFz 20855 -RVRFUg== 20856 -b3JvdXM= 20857 -IHNoaWVsZA== 20858 -W10+ 20859 -aWxpdGFyeQ== 20860 -Lm9yaWdpbg== 20861 -IHByb21vdGlvbg== 20862 -VW50 20863 -IGN0 20864 -VFJB 20865 -NTU2 20866 -Vmlld0hvbGRlcg== 20867 -IHNpZ21h 20868 -ZGVsdGE= 20869 -YXJlaG91c2U= 20870 -Y29udHJhY3Q= 20871 -KFZlY3Rvcg== 20872 -NzIx 20873 -IGNvbXBldGU= 20874 -L2Zvcm0= 20875 -L2NvbXBvbmVudHM= 20876 -IG5y 20877 -IEluZG9uZXM= 20878 -INC+0YI= 20879 -IFZvbHVtZQ== 20880 -LmZpbGVz 20881 -KHJlc3A= 20882 -L21vZGVscw== 20883 -IHN1cmY= 20884 -c3RhbmRhcmQ= 20885 -L28= 20886 -IFhDVEFzc2VydA== 20887 -VklDRVM= 20888 -LkNvZGU= 20889 -U0VE 20890 -IGFjdGl2YXRl 20891 -RGVsdGE= 20892 -IGxpbWl0YXRpb24= 20893 -cmlq 20894 -IHByZWduYW50 20895 -Ol4o 20896 -IHNvdXI= 20897 -cGll 20898 -ODAz 20899 -IGV4cGVuc2U= 20900 -aWNhdGlvbg== 20901 -IExhcmdl 20902 -IMKx 20903 -IEJvd2w= 20904 -KG1vZGVscw== 20905 -L04= 20906 -ODU3 20907 -UGE= 20908 -LnJlbG9hZA== 20909 -IHdvbmRlcmluZw== 20910 -NDYy 20911 -RXhlY3V0aW9u 20912 -CSAgICAgIA== 20913 -IEdyYXBoaWNz 20914 -IENvbnRpbg== 20915 -X2pvYg== 20916 -IGdldE5hbWU= 20917 -IE1hZ24= 20918 -IERXT1JE 20919 -bWFk 20920 -IG5o 20921 -ZmVhdHVyZXM= 20922 -fSIpOwo= 20923 -aGVldHM= 20924 -KHRyYWlu 20925 -em4= 20926 -IHJlY3J1aXQ= 20927 -LmNvbm5lY3Rpb24= 20928 -IGJhcnJlbA== 20929 -IHN0ZWFt 20930 -X3NldHRpbmc= 20931 -IGFuZ3VsYXI= 20932 -YW5lb3VzbHk= 20933 -IGJpbA== 20934 -IE5vcm0= 20935 -NTIy 20936 -KCEk 20937 -aWJ0 20938 -JSg= 20939 -IHBvc2l0 20940 -IEZhdGhlcg== 20941 -aW50ZW5kbw== 20942 -NTY1 20943 -TGl2ZQ== 20944 -MDQx 20945 -IHBvcnRz 20946 -IG1lag== 20947 -IGxhbmRpbmc= 20948 -cG9uZGVy 20949 -IGNvZA== 20950 -X0hFQURFUg== 20951 -Lk1hcmdpbg== 20952 -IGJhbGxz 20953 -IGRpc2N1c3Npb25z 20954 -IGJsZW5k 20955 -SGV4 20956 -IGZhcm1lcnM= 20957 -IG1haW50YWluaW5n 20958 -ICAgDQo= 20959 -c3lu 20960 -W1Q= 20961 -cnVz 20962 -NDM5 20963 -dWZmZXJz 20964 -IGNvbnRyaWJ1dG9ycw== 20965 -X3N5cw== 20966 -LkRlYnVn 20967 -IGNvbnN0cnVjdGVk 20968 -b21lcw== 20969 -P2lk 20970 -c2xpZGVy 20971 -IHN1cHBsaWVycw== 20972 -NjEx 20973 -c2NyaWJlcg== 20974 -cGVz 20975 -0J4= 20976 -IjoNCg== 20977 -XENvbnRyb2xsZXI= 20978 -KSkKCgo= 20979 -IGx1YQ== 20980 -TXVsdGk= 20981 -RU5T 20982 -U3Jj 20983 -IHBldGl0aW9u 20984 -IHNsYXZl 20985 -bG9va2luZw== 20986 -VkVSVA== 20987 -CXZlY3Rvcg== 20988 -U3BlY2lhbA== 20989 -aGg= 20990 -YW5uZQ== 20991 -IE5pZ2Vy 20992 -L3ZpZXdz 20993 -emluZw== 20994 -ZW5kYW50 20995 -PEM= 20996 -c3BlZWQ= 20997 -NTE0 20998 -IHt9OwoK 20999 -QmVnaW5Jbml0 21000 -IGZvcGVu 21001 -QFJlcXVlc3RNYXBwaW5n 21002 -RW5kSW5pdA== 21003 -IHB1bmNo 21004 -U2VuZGVy 21005 -NjAz 21006 -6ZQ= 21007 -Z2V0TWVzc2FnZQ== 21008 -L3R5cGVz 21009 -LlBJ 21010 -KCcnKTsK 21011 -b2N1c2Vk 21012 -KGFsbA== 21013 -IGRyb3Bkb3du 21014 -KS5fXw== 21015 -IFZpbg== 21016 -LkZvcmVpZ25LZXk= 21017 -NjEy 21018 -Y2FuZg== 21019 -b3VyZWQ= 21020 -IE9yZ2FuaXphdGlvbg== 21021 -INCw 21022 -IEN1bHR1cmU= 21023 -KGNscw== 21024 -LF8= 21025 -OTAy 21026 -cmdiYQ== 21027 -7J2Y 21028 -LmRhdGFHcmlkVmlldw== 21029 -IGRvemVu 21030 -IEdlcw== 21031 -ODA1 21032 -NDY0 21033 -X3NoYXJlZA== 21034 -bmljaw== 21035 -IGhvc3A= 21036 -b21ldGVy 21037 -NDk1 21038 -IGNsYWltaW5n 21039 -MDMy 21040 -aWJsZXM= 21041 -cmlr 21042 -5piv 21043 -ZW5hcmlv 21044 -IGRlbmdhbg== 21045 -b2Ji 21046 -bW9udA== 21047 -X3Jhbms= 21048 -KCcvJyw= 21049 -IGFwb2xvZw== 21050 -UHM= 21051 -X3Bvd2Vy 21052 -IEdyZWU= 21053 -IGZ1bGZpbGw= 21054 -IGZpcmViYXNl 21055 -OTEw 21056 -IGZhcmU= 21057 -IEhpbQ== 21058 -IGJlYW4= 21059 -4oCmLg== 21060 -IFNQSQ== 21061 -X1JY 21062 -IHBlcmNlcHRpb24= 21063 -cmVsYXRpdmU= 21064 -Y29tcGlsZQ== 21065 -dXVt 21066 -dXRvcw== 21067 -YXVj 21068 -IEFzaw== 21069 -IGluZGljYXRvcg== 21070 -L3Ro 21071 -LnNldFN0cmluZw== 21072 -IFdpc2NvbnNpbg== 21073 -LkRvbWFpbg== 21074 -IGFydGlmaWNpYWw= 21075 -RGV2ZWxvcA== 21076 -IFNhcmFo 21077 -IGx5aW5n 21078 -KHNlYXJjaA== 21079 -IEVtcGlyZQ== 21080 -dXJyaW5n 21081 -5pe26Ze0 21082 -PSIkew== 21083 -IGdldElk 21084 -IFBheW1lbnQ= 21085 -dHJhbnNpdGlvbg== 21086 -IF0u 21087 -aXhpbg== 21088 -VlQ= 21089 -LXNlbGVjdA== 21090 -IGRlbW9uc3RyYXRlZA== 21091 -IGxhc3ROYW1l 21092 -ZW1wbG95bWVudA== 21093 -LmdldFByb3BlcnR5 21094 -IGZvdWdodA== 21095 -ZmlsZU5hbWU= 21096 -IFBlcnM= 21097 -NDUy 21098 -LWNhcmQ= 21099 -YXN0cg== 21100 -YXR0cnM= 21101 -IHByb21pbmVudA== 21102 -RGVzaWdu 21103 -YW5jb3V2ZXI= 21104 -44GX44E= 21105 -YXJkbw== 21106 -c2VjcmV0 21107 -IHJhZw== 21108 -IHBvaXNvbg== 21109 -LW1hbg== 21110 -LG9taXRlbXB0eQ== 21111 -NzQw 21112 -CXVu 21113 -aXR6ZXI= 21114 -IENhc2lubw== 21115 -IFJvc3M= 21116 -LWZvb3Q= 21117 -KHJlc3VsdHM= 21118 -UGxhbg== 21119 -IGxhc2Vy 21120 -6riw 21121 -X0RS 21122 -NTIz 21123 -RmFjZWJvb2s= 21124 -NDQ5 21125 -IGJvYXJkcw== 21126 -c3Rh 21127 -XV0s 21128 -Njc1 21129 -IHRpbGVz 21130 -U0laRQ== 21131 -ID1+ 21132 -OTcw 21133 -IHByZW1pZXI= 21134 -b2NhYg== 21135 -IGVuY29kZWQ= 21136 -IHJlc2VydmU= 21137 -NjA5 21138 -IEFmZ2hhbmlzdGFu 21139 -IExpc3ROb2Rl 21140 -dXJscw== 21141 -IHN1Ym1pc3Npb24= 21142 -IG5ldQ== 21143 -NDc3 21144 -ICMrIw== 21145 -X1BPU1Q= 21146 -IG1vaXN0 21147 -ZWxsaQ== 21148 -ZWxsaWdlbnQ= 21149 -LmFsZXJ0 21150 -w7Nk 21151 -YnJl 21152 -IENvbGxlY3Q= 21153 -IGdyYXBoaWM= 21154 -IGxvbmdpdHVkZQ== 21155 -IFByb3ZpZA== 21156 -IENhbGN1bGF0ZQ== 21157 -eGZmZmY= 21158 -Y3JpdGVyaWE= 21159 -IHdhdGVycw== 21160 -cm9jaw== 21161 -bG9xdWVudA== 21162 -IFRyaWI= 21163 -NTEz 21164 -IGJ1cnN0 21165 -IHN1ZmZpeA== 21166 -LkV4dGVuc2lvbnM= 21167 -aXNoZXM= 21168 -aXZlbA== 21169 -IExJS0U= 21170 -IEdldHR5 21171 -LkFjdGlvbkV2ZW50 21172 -LnNsZg== 21173 -IEhBTA== 21174 -dXBhbA== 21175 -RUFS 21176 -NTI0 21177 -dWRp 21178 -X3RpbWVvdXQ= 21179 -VUY= 21180 -IFNpbmdhcG9yZQ== 21181 -IEFkdmVudA== 21182 -X2ludGVydmFs 21183 -Y2hhZnQ= 21184 -IEVtZXI= 21185 -IHRlbGVwaG9uZQ== 21186 -IFR1cms= 21187 -X2ludGVyZmFjZQ== 21188 -IE93bg== 21189 -IGVuY291cmFnZWQ= 21190 -PE9iamVjdA== 21191 -X1RleHQ= 21192 -IE9udGFyaW8= 21193 -IEFwcGx5 21194 -LmZpcmViYXNl 21195 -IGFudGli 21196 -UHJpb3JpdHk= 21197 -ZW5leg== 21198 -RGF5cw== 21199 -Y2lk 21200 -dXJyZW5jZQ== 21201 -Oy8= 21202 -aW5uZWQ= 21203 -0YHRjw== 21204 -IHZleg== 21205 -Znc= 21206 -Ly8k 21207 -YXR0YWNr 21208 -NDU4 21209 -IHN0YXJ0dXA= 21210 -YWluZXJz 21211 -LmZyYWdtZW50 21212 -b3BhY2l0eQ== 21213 -KGNvbm4= 21214 -aGVpbQ== 21215 -Lm5ldHdvcms= 21216 -KHN0cmVhbQ== 21217 -Njcw 21218 -IE5PTg== 21219 -dG9s 21220 -ODMw 21221 -IFhib3g= 21222 -IERT 21223 -IGNhY2hlZA== 21224 -IHByb3N0aXR1dGFz 21225 -IEJhbHQ= 21226 -KCdb 21227 -NTc1 21228 -IG5vZXhjZXB0 21229 -Iic= 21230 -IHNk 21231 -LnZhbGlk 21232 -X2Fn 21233 -IHJhY2Vz 21234 -NDgx 21235 -IHJvZA== 21236 -aXR1ZGVz 21237 -PD4o 21238 -NTQ0 21239 -LlByb2R1Y3Q= 21240 -Rm9ybXM= 21241 -TkVX 21242 -UGF5 21243 -CWJvb2xlYW4= 21244 -X2NvbnRhY3Q= 21245 -IEVsZWN0cmlj 21246 -c2tpcA== 21247 -IHd1cg== 21248 -IGNocm9uaWM= 21249 -X2RyaXZlcg== 21250 -OTQw 21251 -IFNhYg== 21252 -IFVsdA== 21253 -IFJhZA== 21254 -U1RBVFVT 21255 -IExld2lz 21256 -T0I= 21257 -IGdpZnRz 21258 -LlJlYw== 21259 -VFJVRQ== 21260 -IGludGVuc2l0eQ== 21261 -TWFya2Vy 21262 -LmNvbXBhcmU= 21263 -ZmZpYw== 21264 -Q29va2ll 21265 -IEJhYnk= 21266 -IEJpZ0RlY2ltYWw= 21267 -aWxldA== 21268 -IEhPTERFUlM= 21269 -IExhZHk= 21270 -IGx1bmc= 21271 -IEFsYWJhbWE= 21272 -IGRlc3M= 21273 -YCk7Cg== 21274 -IEJ1aWxkZXI= 21275 -X3JlZ2lvbg== 21276 -IG5ldXRyYWw= 21277 -OTA5 21278 -Qm90aA== 21279 -IGhw 21280 -IGhvcm4= 21281 -IHNlZ21lbnRz 21282 -IEVD 21283 -Ij0+Ig== 21284 -KHJlYw== 21285 -IFBp 21286 -R00= 21287 -IGxhcHRvcA== 21288 -U2NhbGFy 21289 -NDYz 21290 -aXNk 21291 -LWRpYWxvZw== 21292 -IEFuZGVyc29u 21293 -IG1pc3Rha2Vz 21294 -NzA4 21295 -IEhhbg== 21296 -amVz 21297 -ZXN0aW5hdGlvbg== 21298 -NDM2 21299 -IHByb21pc2Vz 21300 -Ymlk 21301 -IFNjaWVudA== 21302 -R0lO 21303 -IFBlcmZvcm1hbmNl 21304 -YmFnZQ== 21305 -LnVzZXJz 21306 -bGVhZGluZw== 21307 -IG9yYWw= 21308 -R3JhcGhpY3M= 21309 -NDg4 21310 -X1BUUg== 21311 -NTE4 21312 -aGFuZw== 21313 -IGluZXY= 21314 -cHJvY2Vzc2luZw== 21315 -RmFjdG9y 21316 -IE5B 21317 -JHN0cmluZw== 21318 -IGdyb3VuZHM= 21319 -LlNhdmVDaGFuZ2Vz 21320 -Y2xvY2s= 21321 -OTQx 21322 -Y3JpcGNpb24= 21323 -IE5ld3Rvbg== 21324 -Z2M= 21325 -LmluY2x1ZGVz 21326 -IGJsYXN0 21327 -ICctJw== 21328 -IHB1ZWRl 21329 -NDY5 21330 -LlNlc3Npb24= 21331 -IGdyZXA= 21332 -X2ZpbmFs 21333 -IEdheQ== 21334 -IEdpdmU= 21335 -aXJp 21336 -LXN0YXI= 21337 -IFVJSW1hZ2U= 21338 -X2Vwb2No 21339 -dWJi 21340 -ZW50aA== 21341 -IGVsaXRl 21342 -IGNhbXBhaWducw== 21343 -IFBvcm5v 21344 -X2Fzc2lnbg== 21345 -UHJvdG9jb2w= 21346 -IEJlaW5n 21347 -IEFpcnBvcnQ= 21348 -IGNvbnZlbnRpb25hbA== 21349 -IFdhdA== 21350 -IENJ 21351 -RVRB 21352 -IEFudGhvbnk= 21353 -IHRhYmxldA== 21354 -KGZvcm1hdA== 21355 -IGNvbnNpc3RlbnRseQ== 21356 -IElvd2E= 21357 -NDc0 21358 -IGF2YXRhcg== 21359 -MDI3 21360 -LmN1cnNvcg== 21361 -IVs= 21362 -IGhhbmdpbmc= 21363 -SGVy 21364 -U3VjaA== 21365 -JzsKCgo= 21366 -b3JnZW91cw== 21367 -KCk9PQ== 21368 -IHZpZXdNb2RlbA== 21369 -IOOD 21370 -IGVscw== 21371 -IEFnZW50 21372 -RmV0Y2g= 21373 -YXBvcg== 21374 -IGN4 21375 -cHJlYWQ= 21376 -IFBpZXI= 21377 -b2VmZg== 21378 -NjE2 21379 -U24= 21380 -ODkw 21381 -IFZpcnR1YWw= 21382 -QXBy 21383 -LldoaXRl 21384 -NjE1 21385 -X01PRA== 21386 -IFBvaW50cw== 21387 -5aSx 21388 -IGdlbmVz 21389 -IHZlbmRvcg== 21390 -IG1haW5zdHJlYW0= 21391 -PHNyYw== 21392 -IEVsaXphYmV0aA== 21393 -RGVjb2Rlcg== 21394 -LXN0YXRl 21395 -IEdsYXNz 21396 -bmN5 21397 -YWRpYW5z 21398 -X21vbg== 21399 -IFJlbW90ZQ== 21400 -IHdpcmVsZXNz 21401 -IE1p 21402 -5Yk= 21403 -NDY2 21404 -6KGo 21405 -c3RhZ2U= 21406 -IFRpbGU= 21407 -bGxpYg== 21408 -VmFyaWFudA== 21409 -PT0K 21410 -IGdvbGRlbg== 21411 -KFFTdHJpbmc= 21412 -LnB1dEV4dHJh 21413 -IERvbQ== 21414 -IEFuaW1hdGlvbg== 21415 -IGludGVyYWN0aXZl 21416 -aWZhY3Q= 21417 -6Zmk 21418 -TEVU 21419 -IGZyZXF1ZW50 21420 -IDw+Cg== 21421 -RmlsZW5hbWU= 21422 -IHNuZQ== 21423 -IEZvb3RiYWxs 21424 -IHJpdmFs 21425 -IGRpc2FzdGVy 21426 -aW9uaWM= 21427 -IERhbWFnZQ== 21428 -LlJlc291cmNl 21429 -LWVu 21430 -IFR5cGVz 21431 -Z2V0U3RyaW5n 21432 -KGJvYXJk 21433 -IGJvbA== 21434 -cGxhaW4= 21435 -enlt 21436 -4Liy 21437 -IHNjYW5uZXI= 21438 -aWxkZXI= 21439 -X21zZ3M= 21440 -5o8= 21441 -KGludGVudA== 21442 -IGRlc3RydWN0 21443 -IGJ1c3Q= 21444 -IEVtcGxveQ== 21445 -b25p 21446 -IFVJVmlld0NvbnRyb2xsZXI= 21447 -IG9kZHM= 21448 -ZWFyZXI= 21449 -R2VvbWV0cnk= 21450 -IHlpaQ== 21451 -X0VYUE9SVA== 21452 -IEF0dGFjaw== 21453 -IG5pZXQ= 21454 -IGltcHJlc3Npb24= 21455 -IEdpbA== 21456 -X3Byb2I= 21457 -NTI4 21458 -IENG 21459 -IEV4cGVyaWVuY2U= 21460 -L3BsdWdpbnM= 21461 -Lk1ldGhvZA== 21462 -IGJlbGllZnM= 21463 -TmF0aXZl 21464 -X2J1aWxk 21465 -IHZpZw== 21466 -IHJhbmtz 21467 -Y292ZXJlZA== 21468 -NzA1 21469 -c3VjaA== 21470 -R3VhcmQ= 21471 -LnBhY2s= 21472 -YWRkZXI= 21473 -ODA5 21474 -aXZpYQ== 21475 -bG5n 21476 -INCy0Ys= 21477 -NTUy 21478 -VGltZXN0YW1w 21479 -X25vdw== 21480 -IHBva2Vy 21481 -IHVuYw== 21482 -IHNoYXBlcw== 21483 -LXR5cGVz 21484 -X3BlcmlvZA== 21485 -cGs= 21486 -IHZldGVyYW4= 21487 -IHNvbm8= 21488 -IGFwcG9pbnRlZA== 21489 -b3ZlcmZsb3c= 21490 -LmRyaXZlcg== 21491 -X2NhdA== 21492 -dXR0 21493 -cGxhbnQ= 21494 -aW1i 21495 -IEFjY2VwdA== 21496 -IGNvbmNlcnQ= 21497 -CW5vZGU= 21498 -CXo= 21499 -Pz4NCg== 21500 -IGJhbm5lZA== 21501 -CSAgICAgICAgICAgICAgIA== 21502 -IHRveGlj 21503 -IGRpc2FwcGU= 21504 -NDcz 21505 -yJs= 21506 -IGdyYWNl 21507 -YXRlZnVs 21508 -UmVwbHk= 21509 -IENydXo= 21510 -NDg2 21511 -IHNjcmFw 21512 -IGtleXdvcmRz 21513 -c2ltcA== 21514 -IG1vcnRnYWdl 21515 -IGN5YmVy 21516 -IEV4ZWN1dGU= 21517 -IGxhdGl0dWRl 21518 -aWZ1 21519 -LkNPTQ== 21520 -ZGJv 21521 -IHNvcnRz 21522 -IEdhcw== 21523 -b21pYWw= 21524 -LkxvY2Fs 21525 -Q2VsbHM= 21526 -LlJlcGxhY2U= 21527 -U3RyaW5ncw== 21528 -LmZpdA== 21529 -IFRoaXJk 21530 -JSIsCg== 21531 -IHt9Ii4= 21532 -IFNvbnk= 21533 -IFs6 21534 -NTg1 21535 -IGZhbGxlbg== 21536 -LicpCg== 21537 -aW5o 21538 -IE1D 21539 -IHJlZGlz 21540 -Q29kZXM= 21541 -IHByb2ZpbGVz 21542 -aG9vaw== 21543 -UmVkdWNlcg== 21544 -X0ZVTkM= 21545 -IG5hdmlnYXRl 21546 -c3RybGVu 21547 -IGhvcm0= 21548 -4Z4= 21549 -IFNS 21550 -LmJvb3Q= 21551 -IGRpZ2VzdA== 21552 -CWhlYWRlcg== 21553 -LmZpbmRPbmU= 21554 -5oE= 21555 -RGJUeXBl 21556 -bmlh 21557 -X21lcmdl 21558 -IGRvbm5l 21559 -L0dldHR5 21560 -X0NIQVI= 21561 -IGJhbmRz 21562 -LlVSTA== 21563 -YXJ0aWFs 21564 -IGZyZXE= 21565 -IHNpc3Q= 21566 -Tmc= 21567 -IHJlbmRlcmluZw== 21568 -XENvcmU= 21569 -V2lkZ2V0cw== 21570 -IFZB 21571 -IGFjdGl2aXN0cw== 21572 -U3Rl 21573 -PV8= 21574 -YWxsYQ== 21575 -U3RhbXA= 21576 -IGxvYWRz 21577 -IHh4 21578 -IExlYXJuaW5n 21579 -Lk12Yw== 21580 -dWly 21581 -KCIk 21582 -IGNvbm5lY3Rpbmc= 21583 -UmVhZE9ubHk= 21584 -dXJ1 21585 -IEVhZw== 21586 -QklU 21587 -X0RFTA== 21588 -5ac= 21589 -YXJyYXNz 21590 -ZXh0ZXJuYWw= 21591 -IFlPVVI= 21592 -IEJyZXc= 21593 -IEZpdmU= 21594 -IHJlc2l6ZQ== 21595 -aWdpZA== 21596 -ZXJhdGlvbg== 21597 -NjUz 21598 -INGN 21599 -NTM2 21600 -5Yqg 21601 -MDM5 21602 -IENhdGNo 21603 -2YE= 21604 -IExlb24= 21605 -YW1pbA== 21606 -LkJvZHk= 21607 -Q2xpcA== 21608 -L2xpc3Q= 21609 -LmJy 21610 -RWRpdFRleHQ= 21611 -CWRi 21612 -LkdhbWU= 21613 -KEJ1aWxkQ29udGV4dA== 21614 -YmFja2VuZA== 21615 -LlJlZA== 21616 -ZmFjZWJvb2s= 21617 -NTI5 21618 -LnVybHM= 21619 -bXI= 21620 -cm9sbGVk 21621 -LS0tLS0tLQ== 21622 -IGludGVydmVudGlvbg== 21623 -IHJldGlyZW1lbnQ= 21624 -IEtpdA== 21625 -IFBSRQ== 21626 -VXBwZXJDYXNl 21627 -IFNvY2tldA== 21628 -IDot 21629 -IHN0dWR5aW5n 21630 -IE1ldHJv 21631 -YXJkZWQ= 21632 -IGNvbnZlcnNhdGlvbnM= 21633 -Q2FsbGVk 21634 -IGV4YW1pbmU= 21635 -ZXJ0aWZpY2F0ZQ== 21636 -Lmd6 21637 -LXJlc3BvbnNpdmU= 21638 -IHJlZnVuZA== 21639 -X25ldHdvcms= 21640 -MDI2 21641 -YWxsb3dlZA== 21642 -ZW1wdA== 21643 -IG1lYWxz 21644 -Q2F0ZWdvcmllcw== 21645 -IHRyYXZlbGluZw== 21646 -IGtn 21647 -IHNoYW1l 21648 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 21649 -IGV4cGxpY2l0bHk= 21650 -IG1hdGhlbWF0aWM= 21651 -IFN1aXRl 21652 -IFJHQg== 21653 -KioqKioqLw== 21654 -IG1peHR1cmU= 21655 -bGVhcm5pbmc= 21656 -LnRlbXBsYXRl 21657 -YXR0cw== 21658 -d3g= 21659 -CWN0eA== 21660 -LnByb3BlcnRpZXM= 21661 -IGRyaW5rcw== 21662 -IEVpdGhlcg== 21663 -c2V0VGV4dA== 21664 -LmdldERhdGE= 21665 -LnppcA== 21666 -IHJldmVhbHM= 21667 -PHRhYmxl 21668 -Lkhhc2hNYXA= 21669 -IEh1cg== 21670 -KSIpOwo= 21671 -LmZyYW1ld29yaw== 21672 -IFNUQVJU 21673 -ZmVlZGJhY2s= 21674 -NDU3 21675 -IHNhZmVseQ== 21676 -Lmljb24= 21677 -Y29uZmlndXJl 21678 -LmxvY2s= 21679 -LmxheWVycw== 21680 -Lz4uCg== 21681 -IHJhbmtlZA== 21682 -X2ltcGw= 21683 -IEhhbmRsZXM= 21684 -IGhvc3RlZA== 21685 -IHVwZGF0aW5n 21686 -YWxidW0= 21687 -6Z0= 21688 -IHNoYWRlcg== 21689 -RWRpdG9ycw== 21690 -LXJvdW5k 21691 -W117 21692 -IHNlcA== 21693 -IEhp 21694 -VEVN 21695 -bG9va3Vw 21696 -Lm1hbg== 21697 -X0lOUFVU 21698 -IHRocmVhdGVuZWQ= 21699 -X0lNUE9SVA== 21700 -IGRyb3Bz 21701 -cnVpdA== 21702 -c2lk 21703 -Ym90aA== 21704 -IEV4Y2Vs 21705 -IGplcg== 21706 -b3JkaW5hcnk= 21707 -0LXQuQ== 21708 -VklFVw== 21709 -cmVwbHk= 21710 -ICk6Cg== 21711 -Y29sb3Jz 21712 -dmVyaWZpZWQ= 21713 -X1Ry 21714 -X3BhcnNl 21715 -IGNvbmdyZXNz 21716 -NjE3 21717 -UHJvbWlzZQ== 21718 -aW50cw== 21719 -IE1vdGhlcg== 21720 -LkFwaQ== 21721 -IER1cmF0aW9u 21722 -IGZpcnN0TmFtZQ== 21723 -aW5oZXJpdGRvYw== 21724 -IE1hcnM= 21725 -IGFwcg== 21726 -T0RZ 21727 -IHZpc2l0cw== 21728 -NjMx 21729 -IGhlYWxpbmc= 21730 -bGV0dGVycw== 21731 -KSkpOw0K 21732 -ZnV0dXJl 21733 -LkZyYW1ld29yaw== 21734 -IGtpc3M= 21735 -IGludm9sdmU= 21736 -IHNpbGVudA== 21737 -YWRvd3M= 21738 -IGFueWJvZHk= 21739 -c2No 21740 -Njkw 21741 -IHNvbGVseQ== 21742 -LWltZw== 21743 -IHByb3ByaQ== 21744 -IGluc3RydWN0 21745 -IGxpY2Vuc2Vz 21746 -IG1ldGg= 21747 -IGNvbmRlbQ== 21748 -IERvbWFpbg== 21749 -IEhhcnJpcw== 21750 -IHPDpQ== 21751 -Q0VQVA== 21752 -QmF0Y2g= 21753 -QGV4dGVuZHM= 21754 -IENPTlRSSUJVVA== 21755 -LkRhdGFGcmFtZQ== 21756 -NDcy 21757 -X3BhY2tldA== 21758 -cmVjaXNpb24= 21759 -IGZvY3VzaW5n 21760 -Lmh0 21761 -X18iOgo= 21762 -OkdldA== 21763 -IEtD 21764 -IHBhc3NhZ2U= 21765 -U2VnbWVudA== 21766 -X2NlbnRlcg== 21767 -LXpB 21768 -X0JM 21769 -IGNvbnZpbg== 21770 -IGNsYXNzaWZpZWQ= 21771 -IE5TTXV0YWJsZQ== 21772 -X2Fw 21773 -dGlsZQ== 21774 -UmVjdGFuZ2xl 21775 -NDky 21776 -KG51bXM= 21777 -dmVucw== 21778 -IFVJQnV0dG9u 21779 -IEZlZGVy 21780 -YW1v 21781 -IG91dGxpbmU= 21782 -IFBhcnNlcg== 21783 -IOKJ 21784 -IFdvcmtz 21785 -LlNjaGVtYQ== 21786 -IGVuZ2luZXM= 21787 -NjM3 21788 -NTYz 21789 -X2NvbW1vbg== 21790 -NTQy 21791 -X29sZA== 21792 -IHNldENvbnRlbnRWaWV3 21793 -IC8vLzw= 21794 -IEJU 21795 -Zm0= 21796 -IGRpdmVycw== 21797 -X3dlaWdodHM= 21798 -ZW1hcms= 21799 -IEFDVA== 21800 -IHByb3BvcnRpb24= 21801 -b3ZlcmxheQ== 21802 -LmRpcm5hbWU= 21803 -IEdpdA== 21804 -X1JFRkVSRU5DRQ== 21805 -PD4= 21806 -bGI= 21807 -X3J1bGU= 21808 -6LSl 21809 -IFB1dGlu 21810 -IHNsZWVwaW5n 21811 -KCk6DQo= 21812 -IHByZXNlcnZl 21813 -IHBhcmxpYW1lbnQ= 21814 -IExvb2tpbmc= 21815 -IHBpY2tpbmc= 21816 -IERpc3BhdGNo 21817 -IHNsaXA= 21818 -65M= 21819 -IEx5bg== 21820 -X3NpZ25hbA== 21821 -Y29uZmlndXJhdGlvbg== 21822 -IFBpdHQ= 21823 -NDkx 21824 -YWRlbg== 21825 -cHJvY2VkdXJl 21826 -IGVudGh1c2k= 21827 -ZmlnaHQ= 21828 -IENvbnNpZGVy 21829 -IHRvcm4= 21830 -Q29ubmVjdGVk 21831 -LmNvcw== 21832 -X2dyb3Vwcw== 21833 -IFRoaW5r 21834 -IGRlbGliZXI= 21835 -IHJlc2lk 21836 -d29ya2luZw== 21837 -LmNvbHVtbnM= 21838 -IENhbGxlZA== 21839 -IGVzbGludA== 21840 -PiIs 21841 -X0RPV04= 21842 -aGlzdA== 21843 -IEFkdmFuY2Vk 21844 -IHJld2FyZHM= 21845 -YWN0b3Jz 21846 -IHNpbGVuY2U= 21847 -NDc5 21848 -IG15dGg= 21849 -IG5ldXI= 21850 -NTE5 21851 -IGF1Y3Rpb24= 21852 -LkdldFN0cmluZw== 21853 -ZWtz 21854 -KHByb2plY3Q= 21855 -NTk4 21856 -CW1zZw== 21857 -CW91dHB1dA== 21858 -IGNvbXBsYWludHM= 21859 -NTUx 21860 -LFM= 21861 -IHRibA== 21862 -ICwKCg== 21863 -cmlvcnM= 21864 -YWhyZW4= 21865 -IGxhd3llcnM= 21866 -cmVkdXg= 21867 -X3N5bWJvbA== 21868 -b2ZmZWU= 21869 -X1JFU1VMVA== 21870 -KE5hbWU= 21871 -VVRD 21872 -LmN1cnJlbnRUaW1l 21873 -IG9yZ2FuaXM= 21874 -LmFyZw== 21875 -NTMz 21876 -IG1pbmlt 21877 -d2ljaw== 21878 -IHJlY2VpdmVz 21879 -QmFsYW5jZQ== 21880 -IHNwZWFrcw== 21881 -IERheXM= 21882 -IEJlbG93 21883 -NDgz 21884 -dGlwbw== 21885 -UHJlc2VudA== 21886 -IHJlc2Vydg== 21887 -aHA= 21888 -IHJpdA== 21889 -X1JJR0hU 21890 -LS0p 21891 -IGNoYWlybWFu 21892 -Nzgx 21893 -RElT 21894 -IEJPT1NU 21895 -IGV4cGVyaW1lbnRz 21896 -Njg3 21897 -X18pOwo= 21898 -IHN0YW1w 21899 -IGZlcnQ= 21900 -IGZvbmQ= 21901 -VGVy 21902 -ZWx2ZQ== 21903 -dXJlbg== 21904 -K2k= 21905 -ZW5kZW5jeQ== 21906 -IHZpcnR1YWxseQ== 21907 -Li4uIg== 21908 -772e 21909 -OTI1 21910 -LWNlbnQ= 21911 -X3VuaXF1ZQ== 21912 -IHByaWNpbmc= 21913 -bWlj 21914 -UkVTSA== 21915 -IDo6Og== 21916 -IGFubm90YXRpb24= 21917 -IENpcmNsZQ== 21918 -b25nb2Ri 21919 -aXRhcw== 21920 -ICUo 21921 -KGNvbXBvbmVudA== 21922 -INC+0LE= 21923 -KHBvcnQ= 21924 -LWhvdXI= 21925 -Lm9iag== 21926 -TEJM 21927 -IGp1cnk= 21928 -R0JU 21929 -IHNweQ== 21930 -IFByb2Zlc3Npb25hbA== 21931 -ICIiOwoK 21932 -IHN0cmlraW5n 21933 -IGRpc2NyaW1pbmF0aW9u 21934 -IHBheXM= 21935 -OTM3 21936 -bGljdA== 21937 -ZW50ZXM= 21938 -IHRocm93aW5n 21939 -IFBsdWdpbg== 21940 -KGRlZg== 21941 -IFJ1bnRpbWVFeGNlcHRpb24= 21942 -IE1pZ3JhdGlvbg== 21943 -NTk5 21944 -IGRpYw== 21945 -YmFn 21946 -b25pYQ== 21947 -IGNvcnJ1cHRpb24= 21948 -NzA0 21949 -KE1hcA== 21950 -IHByeg== 21951 -LmR0bw== 21952 -IGFjcXVpcmU= 21953 -U3RhdGVUb1Byb3Bz 21954 -IGxvdmluZw== 21955 -0L7Qtg== 21956 -X3BhdHRlcm4= 21957 -IGVtb3Rpb25z 21958 -IHB1Ymxpc2hlcg== 21959 -X2Jl 21960 -IGNvdXBsZXM= 21961 -NDk4 21962 -b2o= 21963 -IENoYXJ0 21964 -IHRyb3A= 21965 -LnRvb2w= 21966 -IGVzdGFibGlzaG1lbnQ= 21967 -IGRvbA== 21968 -NjU0 21969 -IHRvd2Vy 21970 -IGxhbmU= 21971 -IFN5ZG5leQ== 21972 -IGZpbGxpbmc= 21973 -Y2xhaW1lZA== 21974 -NjQ0 21975 -IGRpYWxvZ3Vl 21976 -IGNvbnZlbnRpb24= 21977 -Ym9va2luZw== 21978 -cGFyZW5jeQ== 21979 -5rE= 21980 -IEdlbmVyaWM= 21981 -NzE4 21982 -XFNjaGVtYQ== 21983 -NDgy 21984 -NjE4 21985 -IHJhbmdlcw== 21986 -L2No 21987 -IHBhbmVscw== 21988 -IHJ1bGVk 21989 -55Sf 21990 -LnRz 21991 -X3NldHM= 21992 -IGNsZWFudXA= 21993 -UHJldmlvdXM= 21994 -IEFuaW1hbA== 21995 -NjA3 21996 -KCQo 21997 -IEF2ZQ== 21998 -b2xsYXI= 21999 -MDI4 22000 -X2V2YWw= 22001 -CU5hbWU= 22002 -KHRyZWU= 22003 -ICJd 22004 -NTcx 22005 -IGR1dGllcw== 22006 -PScv 22007 -Q2xpY2tlZA== 22008 -IGRpZmZlcmVudGx5 22009 -IENsYXJr 22010 -IGRpdA== 22011 -b2xvZ2lzdHM= 22012 -IHN5bmQ= 22013 -IHNlbmRz 22014 -LWtub3du 22015 -a2I= 22016 -IE1vZGFs 22017 -aXRhdGl2ZQ== 22018 -IHJhY2luZw== 22019 -IGhpZ2hsaWdodHM= 22020 -IFNpbW9u 22021 -IENhcHRhaW4= 22022 -5L+h 22023 -IENC 22024 -Y29udGlu 22025 -YXJhbg== 22026 -IHBoeXNpY3M= 22027 -cmV0dHk= 22028 -ZXRhbA== 22029 -Lm1k 22030 -YXhpb3M= 22031 -IHNwZWFrZXJz 22032 -IHByZXA= 22033 -IGF3YXJkZWQ= 22034 -7KeA 22035 -IENvcm4= 22036 -IE5hdHVyZQ== 22037 -VURJTw== 22038 -NzM3 22039 -IHByb2o= 22040 -LXByZQ== 22041 -W3U= 22042 -RmVhdHVyZXM= 22043 -IGlzRXF1YWw= 22044 -QmluYXJ5 22045 -c2ln 22046 -IGNvbmZ1c2lvbg== 22047 -NTQ2 22048 -NTY4 22049 -IEhhdA== 22050 -IGt0w7M= 22051 -LmNvbmZpZ3VyZQ== 22052 -TU9O 22053 -NDk0 22054 -L2VkaXQ= 22055 -X0FkZA== 22056 -LHRydWU= 22057 -NTQx 22058 -IGNsaQ== 22059 -RXJyb3JNZXNzYWdl 22060 -LWxvYWRlcg== 22061 -RGltZW5zaW9ucw== 22062 -dWx0aXBseQ== 22063 -IHshIQ== 22064 -IFNxbENvbW1hbmQ= 22065 -IHNwb2tlbg== 22066 -IHBpY3M= 22067 -IHRveQ== 22068 -KEtleQ== 22069 -IExvb3A= 22070 -2Kg= 22071 -RUFUVVJF 22072 -aW5jdGlvbg== 22073 -X3NldHVw 22074 -d3JhcHBlcg== 22075 -IHRvbmc= 22076 -Y3VsYXI= 22077 -T3B0 22078 -LlBs 22079 -PSIs 22080 -KGxlbmd0aA== 22081 -dW1u 22082 -IGNocm9t 22083 -IHNldmVudA== 22084 -IElsbGVnYWxBcmd1bWVudEV4Y2VwdGlvbg== 22085 -NDc4 22086 -CXN0YXJ0 22087 -IGJlZ3Vu 22088 -Q0VQVElPTg== 22089 -ZGF0YXNldA== 22090 -ODI1 22091 -IEZhaWxlZA== 22092 -Y29scw== 22093 -NDU5 22094 -IGtuZWU= 22095 -aW1vcmU= 22096 -LnNwbGljZQ== 22097 -c2hlbGw= 22098 -aWdnZXJz 22099 -IHRoZW1lcw== 22100 -OTk1 22101 -IERK 22102 -IEFzc2lzdGFudA== 22103 -LSQ= 22104 -TWF5YmU= 22105 -IG9yZGVyaW5n 22106 -IEludGVsbGlnZW5jZQ== 22107 -IE1hc3NhY2h1c2V0dHM= 22108 -IGZhaWxpbmc= 22109 -ZWxzb24= 22110 -R3JlYXQ= 22111 -PWk= 22112 -LnJlc3Q= 22113 -IGludml0ZQ== 22114 -LWRpc2FibGU= 22115 -Lkdyb3VwQm94 22116 -4oCZZXN0 22117 -IHRhY2tsZQ== 22118 -Z3Y= 22119 -ZXR0ZXI= 22120 -ICksDQo= 22121 -X3J1bGVz 22122 -Lndhcm4= 22123 -ZnVuY3Rpb25z 22124 -IENocmlzdGlhbnM= 22125 -IGJhY2tlZA== 22126 -IHNsaWRlcg== 22127 -IGVuam95aW5n 22128 -bmVzdA== 22129 -IGhpag== 22130 -X21z 22131 -Ly8q 22132 -QW5ub3RhdGlvbnM= 22133 -IFZhcmlhYmxlcw== 22134 -PFY= 22135 -KHNlcnZlcg== 22136 -IE9yYWNsZQ== 22137 -ZWxlbWVudHM= 22138 -IG9yZ2FuaXNhdGlvbg== 22139 -X3BvaW50ZXI= 22140 -IEhlYWRlcnM= 22141 -W2Q= 22142 -IGRlYWRsaW5l 22143 -aXNzYQ== 22144 -IGtuaWZl 22145 -IE5BU0E= 22146 -IEhlaWdodA== 22147 -Nzg0 22148 -IEFzeW5j 22149 -IHZlbnVl 22150 -LmRvbQ== 22151 -Ym91cm5l 22152 -IEhhd2Fp 22153 -IG1lbW8= 22154 -aWN0aW9ucw== 22155 -IHN1cnZlaWxsYW5jZQ== 22156 -b21p 22157 -L2Fzc2V0cw== 22158 -NTg3 22159 -IGVkdQ== 22160 -xJs= 22161 -IHJvc3Rlcg== 22162 -IGhpcmVk 22163 -IFRvaw== 22164 -IHBsYWNlbWVudA== 22165 -dXJhdGlvbnM= 22166 -IHNldFN0YXRl 22167 -IE1hZ2F6aW5l 22168 -IGhvcnJvcg== 22169 -VHJ5 22170 -IGxhZw== 22171 -IEV2ZXJ5b25l 22172 -dGh1cg== 22173 -KSk7DQoNCg== 22174 -LnJldHVybg== 22175 -IHN5bXA= 22176 -4paI4paI 22177 -IG5pZ2h0cw== 22178 -d29ya2Vy 22179 -IGFsZQ== 22180 -ZW5uZXNzZWU= 22181 -LnN0ZXA= 22182 -IHN5bmNocm9uaXplZA== 22183 -NDg3 22184 -b3VyaQ== 22185 -RG9lcw== 22186 -LmNoYW5nZQ== 22187 -Zm9u 22188 -LnNldEJhY2tncm91bmQ= 22189 -aXJjdWxhcg== 22190 -NDc2 22191 -Ky0= 22192 -IENJQQ== 22193 -NzI5 22194 -IEphbmU= 22195 -IFNpbWlsYXI= 22196 -LUk= 22197 -bGV2ZWxhbmQ= 22198 -IHByb3NwZWN0 22199 -X2ZvdW5k 22200 -CWNvbG9y 22201 -LkRpYWdub3N0aWNz 22202 -IGFubm91bmNl 22203 -IGFzc3VtZXM= 22204 -L3Ry 22205 -IGJk 22206 -OTg3 22207 -IENhcmJvbg== 22208 -IGFuYWx5cw== 22209 -NTY0 22210 -LmRlc3Q= 22211 -bmlr 22212 -IExpZQ== 22213 -LWluZGV4 22214 -RHJhd2FibGU= 22215 -IFRBRw== 22216 -IHRyaWFuZ2xl 22217 -X0ZMT0FU 22218 -CQkgICAgIA== 22219 -LmJsYWNr 22220 -dnVl 22221 -Y3VyYWN5 22222 -IGFmZmVjdHM= 22223 -OTA2 22224 -IHN1cmVseQ== 22225 -U2xpZGVy 22226 -dWtp 22227 -Y2VyeQ== 22228 -IHVudGVy 22229 -LnByb2ZpbGU= 22230 -b3Jkb24= 22231 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 22232 -bGVhdmU= 22233 -IHNtYXJ0cGhvbmU= 22234 -Z2ll 22235 -IGNvbnNwaXI= 22236 -IHR1dG9yaWFs 22237 -57G7 22238 -IGNhYg== 22239 -NzY1 22240 -IFN1bW1hcnk= 22241 -KgoK 22242 -w6Ro 22243 -IlRoaXM= 22244 -IHNsaWRlcw== 22245 -Ijwv 22246 -LmRldg== 22247 -Jzw= 22248 -IFJpbmc= 22249 -xYJh 22250 -IGtvdGxpbg== 22251 -LmR1bXBz 22252 -IGJhc3M= 22253 -7Is= 22254 -UE9JTlQ= 22255 -IHV0dGVy 22256 -IMOpcw== 22257 -LmZ1bGw= 22258 -T0xM 22259 -IGNlcmVtb255 22260 -c2xvdA== 22261 -IGFpbXM= 22262 -dG9vbHRpcA== 22263 -LnNjb3Jl 22264 -LWRk 22265 -NjQy 22266 -IHByb3g= 22267 -UmVjb2duaXplcg== 22268 -ZHluYW1pYw== 22269 -w6RuZA== 22270 -L3N0ZA== 22271 -RFU= 22272 -IE5vdEltcGxlbWVudGVk 22273 -KCItLQ== 22274 -UkFX 22275 -NjM1 22276 -IGV0aG5pYw== 22277 -YW5ubw== 22278 -IGNoYW1waW9uc2hpcA== 22279 -LHNlbGY= 22280 -IGFjY2VwdGFibGU= 22281 -IFNwcml0ZQ== 22282 -W3R5cGU= 22283 -w7xo 22284 -IFZL 22285 -KGpQYW5lbA== 22286 -NTQ4 22287 -aXRy 22288 -66A= 22289 -YXVyYQ== 22290 -IGZhY3VsdHk= 22291 -YXZlcnM= 22292 -IFJlY29yZHM= 22293 -LlNlY3VyaXR5 22294 -IGNvbnN0cmFpbnQ= 22295 -LkJs 22296 -VWludA== 22297 -YmFsYW5jZQ== 22298 -IGNvbW1l 22299 -IE5paw== 22300 -U3VwcHJlc3NXYXJuaW5ncw== 22301 -IE9jZWFu 22302 -NTU0 22303 -X0lk 22304 -RGF0YVNldA== 22305 -IGluc2VydGVk 22306 -IjsNCg0K 22307 -4oCz 22308 -aXBwZXQ= 22309 -IGFubml2ZXJzYXJ5 22310 -IHJldGlyZWQ= 22311 -b3JjaA== 22312 -IHBlcnBldA== 22313 -XEZvcm0= 22314 -IGludm9sdmVtZW50 22315 -X3VzZXJuYW1l 22316 -YWxlbQ== 22317 -X1NFUlZJQ0U= 22318 -IEluZGlhbmE= 22319 -IGNpZ2FyZXQ= 22320 -YXJ0eg== 22321 -IFJD 22322 -IG1lYXN1cmVtZW50cw== 22323 -572u 22324 -IGFmZmlsaWF0ZQ== 22325 -YWNpb25hbA== 22326 -LXNlY3Rpb24= 22327 -X2NvbnRyb2xsZXI= 22328 -dmFyZA== 22329 -X2Vs 22330 -IFRveQ== 22331 -PFA= 22332 -TWFjaGluZQ== 22333 -w7ptZXI= 22334 -IFllYWg= 22335 -IllvdQ== 22336 -IG1vbA== 22337 -LkNs 22338 -Y29udHJvbGxlcnM= 22339 -IHN1c3BlbmRlZA== 22340 -Kys7Cgo= 22341 -QVRU 22342 -IHByb2plY3Rpb24= 22343 -UGFkZGluZw== 22344 -NTg2 22345 -Lm1hdGg= 22346 -Njg2 22347 -ZmFjdG9yeQ== 22348 -MDQy 22349 -IGdhbW1h 22350 -KCk+ 22351 -Y3ljbGU= 22352 -IEJ1bGw= 22353 -cGF0aHM= 22354 -IHVucA== 22355 -IHZpZXdEaWRMb2Fk 22356 -X01vZGVs 22357 -IGFzc2VydFRydWU= 22358 -IHJhdGVk 22359 -RGVjbA== 22360 -dmVydGVk 22361 -IERhdA== 22362 -YnJldw== 22363 -IHBvaW50aW5n 22364 -TXM= 22365 -IFBvaW50ZXI= 22366 -KSc= 22367 -X25vbg== 22368 -NTI3 22369 -IFNFQw== 22370 -IHllYWg= 22371 -Z2VuY3k= 22372 -aW5pdGlhbGl6ZQ== 22373 -Zmx5 22374 -NzEx 22375 -W3Bvcw== 22376 -LGc= 22377 -VGVsZQ== 22378 -MDM0 22379 -IGpva2U= 22380 -IGNsYXVzZQ== 22381 -LmZpbmRCeUlk 22382 -ZW5lcw== 22383 -KGluc3RhbmNl 22384 -NjI2 22385 -wqM= 22386 -OTE1 22387 -IHNsaWM= 22388 -X2hvbWU= 22389 -ICovfQo= 22390 -X3BhZ2Vz 22391 -KHNlcnZpY2U= 22392 -OTA1 22393 -UlA= 22394 -IEFtb25n 22395 -LmdldEN1cnJlbnQ= 22396 -ODA2 22397 -44K5 22398 -IHNsZWU= 22399 -PTw/ 22400 -X3Byb3A= 22401 -Zmx1c2g= 22402 -IE1N 22403 -QmVs 22404 -Tm90ZXM= 22405 -ICovCgoK 22406 -MDM1 22407 -IHJo 22408 -VGFibGVz 22409 -IEp1 22410 -IFwNCg== 22411 -bGljaGVu 22412 -IEluc3VyYW5jZQ== 22413 -XQoKCg== 22414 -IGNvb3Blcg== 22415 -4oCUdGhl 22416 -Lm1hdA== 22417 -NDg5 22418 -IGZvaQ== 22419 -KGF1dG8= 22420 -TWFyZ2lu 22421 -NjM2 22422 -IHJlc2lkZW5jZQ== 22423 -NTU5 22424 -IEhpc3Rvcg== 22425 -IH49 22426 -RGk= 22427 -ICcpCg== 22428 -IGV4Y2x1ZGU= 22429 -LkRyb3A= 22430 -JyI7Cg== 22431 -IGNvYw== 22432 -X3VwbG9hZA== 22433 -SGlkZQ== 22434 -IFVua25vd24= 22435 -IG5vcm1hbGl6ZQ== 22436 -X3JldA== 22437 -LicKCg== 22438 -Lm5vZGVz 22439 -ODcw 22440 -LkRhdGFTb3VyY2U= 22441 -YmxlbXM= 22442 -IGdlbnRsZQ== 22443 -OiQ= 22444 -JykpOwoK 22445 -LlJlc291cmNlcw== 22446 -4og= 22447 -IFRhaQ== 22448 -VkVE 22449 -IEd1bg== 22450 -bGVhbnM= 22451 -IERvYw== 22452 -LlZvaWQ= 22453 -IEFtZW5kbWVudA== 22454 -ODY2 22455 -ZXNzZWQ= 22456 -NzA2 22457 -IHJlY2lwaWVudA== 22458 -Lk5vZGU= 22459 -b3Zv 22460 -IGFsaWduSXRlbXM= 22461 -IFVuaXR5 22462 -IFJvbWU= 22463 -YnVybg== 22464 -IHZvbHRhZ2U= 22465 -IFNIQQ== 22466 -NTM0 22467 -NTcy 22468 -IEdPT0Q= 22469 -aGVscGVycw== 22470 -LyoqKi8= 22471 -IGVsaW1pbmF0ZQ== 22472 -d2Fw 22473 -X2FuZ2xl 22474 -IHJlZnVnZWVz 22475 -CWFzc2VydEVxdWFscw== 22476 -IHByb2Jl 22477 -KCcuLi8uLi8= 22478 -eW91cg== 22479 -IG1lcmNo 22480 -VUJMRQ== 22481 -CXJlc3BvbnNl 22482 -X0RFRg== 22483 -IGVudmlyb25tZW50cw== 22484 -b3VzaW5n 22485 -IHJlc3RyaWN0ZWQ= 22486 -IENPTlRSSUJVVE9SUw== 22487 -NjIx 22488 -IGNvbXBhbmlvbg== 22489 -4bqj 22490 -cG93 22491 -dXJ0bGU= 22492 -Ymll 22493 -LlBlcmZvcm0= 22494 -PW4= 22495 -cmVkaXM= 22496 -IGRpdmlkZQ== 22497 -IGNvbGxlY3RpdmU= 22498 -RGlmZg== 22499 -RHluYW1pYw== 22500 -aXNTZWxlY3RlZA== 22501 -YXN0eXBl 22502 -IExvdA== 22503 -IFN0YXRlbWVudA== 22504 -aWNpcGFudA== 22505 -YWto 22506 -NTE3 22507 -IHNlcmlhbGl6ZXI= 22508 -X0NGRw== 22509 -YXZhbA== 22510 -IHZpZXdlcnM= 22511 -IEZP 22512 -T2Nj 22513 -IHJvYnVzdA== 22514 -IE1pdA== 22515 -X0FORA== 22516 -VHJhbnNpdGlvbg== 22517 -dW5hdGU= 22518 -IHByaWRl 22519 -IGRyYW1hdGlj 22520 -IFBhZ2Vz 22521 -X3R1cGxl 22522 -IGNvcGllZA== 22523 -bW4= 22524 -IG91Z2h0 22525 -IGVxdWFsaXR5 22526 -X2hhcw== 22527 -X1dS 22528 -NTcz 22529 -ZW1p 22530 -IHN1cmdl 22531 -aWxsbw== 22532 -KCl9 22533 -MDgx 22534 -IHBlcmY= 22535 -OTIx 22536 -dWxr 22537 -IGludmVzdG1lbnRz 22538 -Nzg1 22539 -IGdlbmVyYXRpb25z 22540 -IHJlc29ydA== 22541 -IHRydXN0ZWQ= 22542 -X2ZyZXE= 22543 -IGZvcm1h 22544 -QVRJT05T 22545 -IEh1 22546 -IEdyYWQ= 22547 -X2NwdQ== 22548 -ICIsCg== 22549 -cmVzc2U= 22550 -KCoq 22551 -IGhlcmVieQ== 22552 -IGxha2U= 22553 -X1NUQUNL 22554 -IEJ1cmVhdQ== 22555 -IHN1c3RhaW5hYmxl 22556 -IFBF 22557 -IGRlaQ== 22558 -IEFuc3dlcg== 22559 -UGx1cw== 22560 -L3dlYg== 22561 -IHN0ZXI= 22562 -IG1vdW50ZWQ= 22563 -X2NsZWFy 22564 -Zm9ubw== 22565 -aWFuY2Vz 22566 -X2ZpbmQ= 22567 -IGNvbmZ1c2Vk 22568 -X2Jpbg== 22569 -REVDTA== 22570 -IGluc3RhbnRseQ== 22571 -VUlU 22572 -X0RP 22573 -U2V0dXA= 22574 -a2Vl 22575 -X3ByaW50Zg== 22576 -X3N0bXQ= 22577 -IFN0ZWFt 22578 -cHJvZg== 22579 -bHY= 22580 -IHNvbHZpbmc= 22581 -bGF0b3I= 22582 -b3R5cGVz 22583 -QW5kcm9pZA== 22584 -X2VzY2FwZQ== 22585 -TGVhdmU= 22586 -LmdldFRpbWU= 22587 -ODEx 22588 -aWZz 22589 -IGNvdg== 22590 -IENsYXNzaWM= 22591 -LWRhcms= 22592 -NTI2 22593 -RGlzcGF0Y2hlcg== 22594 -LWdyYXk= 22595 -IFBhbGVzdGluaWFu 22596 -LmRlZXA= 22597 -IEluamVjdA== 22598 -IHJlZmxlY3Rpb24= 22599 -NTM4 22600 -IGh5cG8= 22601 -Y29uc3RydWN0b3I= 22602 -LmFwcGxpY2F0aW9u 22603 -eXN0ZXI= 22604 -4pU= 22605 -c2Nob29s 22606 -IENvdw== 22607 -NTkz 22608 -IGZvb3RhZ2U= 22609 -LWlucw== 22610 -IC8qKjw= 22611 -YXRvbQ== 22612 -IHByb2ZpdHM= 22613 -OTIz 22614 -IGJvb2tpbmc= 22615 -X3RocmVzaG9sZA== 22616 -IExpdmVy 22617 -IGNpdGl6ZW4= 22618 -Yng= 22619 -IFN0b3Jt 22620 -IENvcnA= 22621 -IHdpZGVy 22622 -Iikpewo= 22623 -X0FDVElPTg== 22624 -aW9ycw== 22625 -YWlzZXM= 22626 -Om5vbmU= 22627 -IGNpdGVk 22628 -ImZtdA== 22629 -QXVn 22630 -Y29tYg== 22631 -IHdoaXRlcw== 22632 -IHNlc3M= 22633 -Xl4= 22634 -aWdodGg= 22635 -IHRhbmc= 22636 -X0NBUA== 22637 -NjE0 22638 -IGludGVyYWN0aW9ucw== 22639 -NDk3 22640 -IGdhcmQ= 22641 -NjQ2 22642 -IHByaXpl 22643 -NjQ3 22644 -YWZrYQ== 22645 -VHJp 22646 -XEVsb3F1ZW50 22647 -IER5bmFtaWM= 22648 -55CG 22649 -Z3A= 22650 -IHJlYWxt 22651 -IE5p 22652 -IEVkd2FyZA== 22653 -IGlkZW50aWZpY2F0aW9u 22654 -IHBoeXNpY2FsbHk= 22655 -5pys 22656 -IHBpY2tz 22657 -LWZyaWVuZGx5 22658 -PGk= 22659 -aWZpY2U= 22660 -X0FQ 22661 -TG9nZ2Vk 22662 -NTUz 22663 -fSIu 22664 -L3V0aWxz 22665 -IC4uLi4= 22666 -RU5USUFM 22667 -KEFjdGlvbg== 22668 -J10pOwoK 22669 -IHByb3Rlc3Rz 22670 -b2xpbmU= 22671 -X1JFVFVSTg== 22672 -IHBvcHVsYXRpb25z 22673 -IFJhaW4= 22674 -ZHVw 22675 -b3JpYWw= 22676 -IEF1dGhvcml0eQ== 22677 -X2V4cHI= 22678 -MDc1 22679 -LnVz 22680 -IGNvcnJ1cHQ= 22681 -CWltcG9ydA== 22682 -PGNoYXI= 22683 -IExFRlQ= 22684 -IGNhYmluZXQ= 22685 -IG5laWdoYm91cg== 22686 -IFNxbFBhcmFtZXRlcg== 22687 -YXR0ZXJlZA== 22688 -ZW1pYQ== 22689 -IHJldmlld2Vk 22690 -IEhlbGxv 22691 -YmxvY2tz 22692 -KHByb2Nlc3M= 22693 -OTk3 22694 -IG9ic2VydmF0aW9u 22695 -cmF0aW5n 22696 -Lmdsb2JhbA== 22697 -IHByZWZlcmVuY2U= 22698 -LnByZXBhcmU= 22699 -IGRvemVucw== 22700 -V29ya2Vy 22701 -IGNhbGN1bGF0aW9u 22702 -IFRvd2Vy 22703 -YWlyeQ== 22704 -IElTTw== 22705 -IGh1bWFuaXR5 22706 -LmFzSW5zdGFuY2VPZg== 22707 -NzEy 22708 -IGR5cw== 22709 -IHBpZXI= 22710 -aWd1ZQ== 22711 -IGFzc29jaWF0ZQ== 22712 -IGludGlt 22713 -bm90aWZ5 22714 -KHt9LA== 22715 -ODI4 22716 -IFJlcHJlc2VudA== 22717 -cGhldA== 22718 -c2V1ZG8= 22719 -64uI64uk 22720 -LlBvc2l0aW9u 22721 -IGNsb3N1cmU= 22722 -KGNsYXNz 22723 -CXRpbWU= 22724 -IE9yYW5nZQ== 22725 -X29wcw== 22726 -IHBvcHVw 22727 -IEltcHJv 22728 -X3NlY3JldA== 22729 -IEV1 22730 -LnNldExheW91dA== 22731 -dWxseQ== 22732 -IHNjcmV3 22733 -IFNpemVk 22734 -IENPTVA= 22735 -IG5vdGlmaWNhdGlvbnM= 22736 -VHJhbnNmZXI= 22737 -RW1pdHRlcg== 22738 -KG9sZA== 22739 -bGV0aWM= 22740 -NDkz 22741 -IC0KCg== 22742 -IHBhbmlj 22743 -NzE1 22744 -IExDRA== 22745 -cnVsZXM= 22746 -IGFmZmFpcnM= 22747 -IEZpbGw= 22748 -X0lSUQ== 22749 -OTEy 22750 -YXR0YWNobWVudA== 22751 -IHZvbQ== 22752 -PGJ1dHRvbg== 22753 -NTk1 22754 -IHRleHRz 22755 -IGFjdGl2YXRlZA== 22756 -LmFjY2Vzcw== 22757 -KHJlYWRlcg== 22758 -VGVt 22759 -IGNvcm9u 22760 -cm9waA== 22761 -RE1JTg== 22762 -IGVtZXJnZWQ= 22763 -IGluZmxhdGVy 22764 -IEluZGVwZW5kZW50 22765 -b3Jpb3Vz 22766 -IERlbGhp 22767 -Njcy 22768 -IGdseXBoaWNvbg== 22769 -IENhcmw= 22770 -U2k= 22771 -IGV4cGVyaW1lbnRhbA== 22772 -LmJhcg== 22773 -SUFO 22774 -IHNxbGl0ZQ== 22775 -Y2Npw7Nu 22776 -OTA0 22777 -X0JBQ0s= 22778 -LG5hbWU= 22779 -aG9ydA== 22780 -IHRlbnM= 22781 -NTQ5 22782 -6rM= 22783 -dXNpdmU= 22784 -IGdlbnVpbmU= 22785 -IGJ1Y2s= 22786 -L2Rpdg== 22787 -LnJvb20= 22788 -X05FVw== 22789 -ZXN0YWRv 22790 -IEFyaw== 22791 -b2NvbHM= 22792 -LmdlbmVyYXRl 22793 -dG91Y2g= 22794 -Zml4ZWQ= 22795 -ICco 22796 -IHJlZmVycmluZw== 22797 -IG92ZXJ3aGVsbWluZw== 22798 -KGxldA== 22799 -IGZ1ZQ== 22800 -NjIz 22801 -X0VOVg== 22802 -d29tYW4= 22803 -RmlndXJl 22804 -YW5pbWF0ZQ== 22805 -IE1vcnQ= 22806 -IGxvbmdlc3Q= 22807 -Y29sbg== 22808 -VE0= 22809 -Ol8= 22810 -cmllbA== 22811 -LE4= 22812 -IFJBTQ== 22813 -IGp1c3RpZnlDb250ZW50 22814 -IGFjdGl2ZWx5 22815 -L3B1YmxpYw== 22816 -IOuw 22817 -R2l2ZW4= 22818 -T1RBTA== 22819 -5aSx6LSl 22820 -U2VxdWVudGlhbA== 22821 -IHN1cHBsZW1lbnQ= 22822 -LmFi 22823 -IGNhdGVnb3I= 22824 -fX0sCg== 22825 -YWhhbg== 22826 -J3Vu 22827 -b3NpdHk= 22828 -IGFjY29tcGxpc2g= 22829 -VXRpbGl0aWVz 22830 -LnZpZXdz 22831 -LmNu 22832 -Y2VpbA== 22833 -IENCRA== 22834 -IFJG 22835 -UEVH 22836 -IEdpZnQ= 22837 -QVlT 22838 -IFdJTg== 22839 -cGFuaWVk 22840 -IMWf 22841 -IG9ic2VydmVy 22842 -IHNtZWxs 22843 -IHs6 22844 -TGlua2Vk 22845 -PlsK 22846 -b2xlcg== 22847 -IGxpYmVydA== 22848 -IGAK 22849 -IHdlbm4= 22850 -bGF0ZWQ= 22851 -IGltbXVuZQ== 22852 -KE5vZGU= 22853 -IFByb2JsZW0= 22854 -IEFicw== 22855 -bG9ncw== 22856 -IC4uLw== 22857 -IEFEQw== 22858 -IH19Ij4K 22859 -PicpOwo= 22860 -PWI= 22861 -IFdpbmQ= 22862 -bGFob21h 22863 -IGFsbG9jYXRl 22864 -b3JpYW4= 22865 -IHByZXNjcmlwdGlvbg== 22866 -LXF1YWxpdHk= 22867 -IE1heW9y 22868 -ODU1 22869 -aW5lbHk= 22870 -ZW5kZm9yZWFjaA== 22871 -IENvbXBsZXg= 22872 -a29t 22873 -NzA5 22874 -VFk= 22875 -Nzkw 22876 -XV0u 22877 -LlN0eWxl 22878 -X21hbnk= 22879 -JywnJA== 22880 -IGJhcnJpZXI= 22881 -IEZldGNo 22882 -IE1hcnZlbA== 22883 -IHJlc2lzdA== 22884 -0L7Qs9C+ 22885 -YmlkZGVu 22886 -IFJ1bm5hYmxl 22887 -OmZhbHNl 22888 -ODk5 22889 -IGJ1aWxkcw== 22890 -IFN0YWdl 22891 -IGR1Yg== 22892 -ZW1wbw== 22893 -LnNpdGU= 22894 -NTU4 22895 -OwoKCgo= 22896 -OTk0 22897 -IERlbnZlcg== 22898 -IHJldmVs 22899 -IHRyaWdnZXJlZA== 22900 -IGRpY2U= 22901 -X2ZhaWw= 22902 -IGdj 22903 -ODMz 22904 -NTg5 22905 -CVg= 22906 -IFRocm93YWJsZQ== 22907 -Nzc1 22908 -LnJvdXRlcg== 22909 -IFJldm9sdXRpb24= 22910 -0YDQsA== 22911 -X05PTg== 22912 -MDU1 22913 -n6U= 22914 -NTc4 22915 -IGVsZGVy 22916 -IGFicm9hZA== 22917 -INC1 22918 -IEFkdWx0 22919 -Ymxy 22920 -Z2x5cGhpY29u 22921 -NjEz 22922 -IHByb21vdGluZw== 22923 -IGl6 22924 -IFNvbGlk 22925 -NjQ1 22926 -X2xvYWRlcg== 22927 -ZWFybHk= 22928 -LmVuYWJsZWQ= 22929 -LWVkaXQ= 22930 -IFVM 22931 -X3BsYXk= 22932 -IEludGVycnVwdA== 22933 -IGFkdmFudGFnZXM= 22934 -dWNsZQ== 22935 -IG1lY2hhbmljYWw= 22936 -LnRhYmxlTGF5b3V0UGFuZWw= 22937 -IFdvcmtpbmc= 22938 -IGFub255bW91cw== 22939 -UmF0aW5n 22940 -aWdpb3Vz 22941 -X3Bob25l 22942 -LmFkZEFjdGlvbkxpc3RlbmVy 22943 -IGZyYW4= 22944 -dW5kZW4= 22945 -ICopJg== 22946 -X2Jvb2w= 22947 -dWxhdGl2ZQ== 22948 -IGNvbmU= 22949 -IE11bHQ= 22950 -IG3Dtg== 22951 -IEZvcndhcmQ= 22952 -XSk6Cg== 22953 -IGNvbnZpbmNlZA== 22954 -YWN0ZWQ= 22955 -NjQz 22956 -44GT 22957 -IENvbmZpZ3VyZQ== 22958 -IGNlaWxpbmc= 22959 -RGVy 22960 -IHBhc3NlbmdlcnM= 22961 -R3JvdXBz 22962 -IHNvY2Nlcg== 22963 -L1c= 22964 -YXZpb3Jz 22965 -c3dpdGg= 22966 -IFpvbmU= 22967 -Lk9wdGlvbnM= 22968 -IE1vbQ== 22969 -aWVkZXI= 22970 -QXJyYXlz 22971 -IHRyZWF0bWVudHM= 22972 -IHByb3RlY3Rpbmc= 22973 -ZmFj 22974 -IHBpY2tsZQ== 22975 -QnV0dG9uSXRlbQ== 22976 -NzEz 22977 -IGJsb2NraW5n 22978 -c3RyYXI= 22979 -w7I= 22980 -IEV4cG9ydA== 22981 -IHRocmV3 22982 -b3R0YQ== 22983 -IEJBU0U= 22984 -Lndz 22985 -LkxFQURJTkc= 22986 -b3JkZXJCeQ== 22987 -X2RlbGF5 22988 -IFB1 22989 -LmRsbA== 22990 -IENob29zZQ== 22991 -OTky 22992 -UG9saWNl 22993 -IEJFR0lO 22994 -Ym94ZXM= 22995 -IGRpYW1vbmQ= 22996 -LGw= 22997 -IAkJCQ== 22998 -IGN1cmlvdXM= 22999 -NjI0 23000 -dHY= 23001 -IGVyb3Rpc2NoZQ== 23002 -YWNrYWdlcw== 23003 -CVNldA== 23004 -VGljaw== 23005 -LmJvcmRlcg== 23006 -c3RhdGljbWV0aG9k 23007 -IGNoZXI= 23008 -aW52b2ljZQ== 23009 -IGNydQ== 23010 -IGRlZmVjdA== 23011 -X21ldGFkYXRh 23012 -cmVsYXRpb24= 23013 -aWthbg== 23014 -W04= 23015 -KFF0 23016 -KEJhc2U= 23017 -5oGv 23018 -YmVhdA== 23019 -IEVtcHR5 23020 -CW8= 23021 -X3NoaWZ0 23022 -IHJlZ3JldA== 23023 -NzIy 23024 -VGhvc2U= 23025 -Q2VudA== 23026 -IFBvcnR1Zw== 23027 -IElzbGFuZHM= 23028 -IFRJTUU= 23029 -TWFuYWdlbWVudA== 23030 -OTk2 23031 -LXNw 23032 -NTM5 23033 -w6ptZQ== 23034 -IG5vdGlvbg== 23035 -dW5pZnU= 23036 -UEs= 23037 -ODI2 23038 -6KGM 23039 -IENVUkxPUFQ= 23040 -XCJc 23041 -VVY= 23042 -57o= 23043 -ZHJh 23044 -Y291 23045 -PWA= 23046 -IERlc3Ryb3k= 23047 -cnA= 23048 -LmNhbmNlbA== 23049 -R0c= 23050 -cnVudGltZQ== 23051 -IFZ1ZQ== 23052 -IHByb2dyZXNzaXZl 23053 -L3NlcnZpY2Vz 23054 -IHJ1bm5lcg== 23055 -X0ZSQU1F 23056 -LlRvb2xTdHJpcE1lbnVJdGVt 23057 -ICcsJw== 23058 -ZGVsYXk= 23059 -PXV0Zg== 23060 -IHNjcmVlbmluZw== 23061 -IHB1bGxpbmc= 23062 -b21hcw== 23063 -IGFudGg= 23064 -LW5ldw== 23065 -L2xvY2Fs 23066 -IGlQYWQ= 23067 -IHR3aXR0ZXI= 23068 -IGR5aW5n 23069 -IGhlYXZlbg== 23070 -IFVJbnQ= 23071 -IFNlbmF0b3I= 23072 -IHByZXN1bQ== 23073 -IFdhbGtlcg== 23074 -IG92ZXJjb21l 23075 -ZXRlY3Rpb24= 23076 -IGVtYmFycmFzcw== 23077 -Q2hpbmE= 23078 -NjM5 23079 -SW5jbHVkZQ== 23080 -Uk9MTA== 23081 -IGRhdGFUeXBl 23082 -RGF2aWQ= 23083 -4Lij 23084 -bG9w 23085 -LW1vbnRo 23086 -IHNjYXI= 23087 -IFNhZmU= 23088 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 23089 -IGFjY2Vzc29yaWVz 23090 -IHJhbXA= 23091 -X1VTRQ== 23092 -IGNvbnRyYWQ= 23093 -KSldCg== 23094 -IHByZXN0 23095 -IEhS 23096 -IFJhcA== 23097 -IHVzaXpl 23098 -IGNhcGFiaWxpdHk= 23099 -IGNvcnQ= 23100 -LW5leHQ= 23101 -MDc3 23102 -NjI3 23103 -IGJ1cmRlbg== 23104 -ODIy 23105 -X3JlYWRlcg== 23106 -IEBA 23107 -cmVndWxhcg== 23108 -IEth 23109 -MDM2 23110 -TUFO 23111 -IGFzdHI= 23112 -ICcnKQo= 23113 -IGZlZA== 23114 -IHBhcnNpbmc= 23115 -IFllYXJz 23116 -IGJyb2tlcg== 23117 -Ijp7Ig== 23118 -IGFrdA== 23119 -SW52ZW50b3J5 23120 -YWJlbGVk 23121 -IGFyZ3BhcnNl 23122 -KioqKioqKgo= 23123 -dmVyc2F0aW9u 23124 -IGNvcmQ= 23125 -IFRp 23126 -IGhvcGVmdWxseQ== 23127 -IGFo 23128 -dmVyYg== 23129 -IHN0b2xlbg== 23130 -LkVudHJ5 23131 -IGV4cGVjdGluZw== 23132 -T3JpZW50YXRpb24= 23133 -IHBvd2VyZWQ= 23134 -IHBlcnNpc3Q= 23135 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 23136 -J10pOw== 23137 -JykpLAo= 23138 -IENhc2g= 23139 -CWl0ZW0= 23140 -ODE4 23141 -Z3JhZGVz 23142 -cm9wb2w= 23143 -YmFzaWM= 23144 -ICIpOw0K 23145 -IGF3YXJkcw== 23146 -KHJhbmdl 23147 -LWFsbA== 23148 -IElCT3V0bGV0 23149 -IEluZGVlZA== 23150 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 23151 -IHN0b21hY2g= 23152 -IGZsb3dlcg== 23153 -IHNldw== 23154 -X3RpbWVz 23155 -YXZpcw== 23156 -UVN0cmluZw== 23157 -IFJvdXRlcw== 23158 -X3Byb3Q= 23159 -IGNvbWVkeQ== 23160 -IGxvZ291dA== 23161 -IHdvb2Rlbg== 23162 -IHBvc3Rlcg== 23163 -cGllY2U= 23164 -LkpvaW4= 23165 -IFBvaw== 23166 -Y2Vsb25h 23167 -bXV0ZXg= 23168 -Ow0KDQoNCg== 23169 -IHN0cmlrZXM= 23170 -Nzg3 23171 -TG9hZGVk 23172 -KWFyZw== 23173 -ZXNh 23174 -VW5pdGVk 23175 -RXA= 23176 -UEVMTA== 23177 -ODA3 23178 -IEF0bGFudGlj 23179 -dWxsZXQ= 23180 -NjUy 23181 -YXBwbGU= 23182 -IHNldHRsZWQ= 23183 -YWNvbg== 23184 -IHByaW50ZXI= 23185 -IEdD 23186 -5a6a 23187 -IHJlbmRlcmVk 23188 -LOKAmQ== 23189 -aGVpdA== 23190 -c29jaWFs 23191 -Lmdl 23192 -NzE0 23193 -IFJpY2s= 23194 -IFV0YWg= 23195 -Z290 23196 -b25pY2Fs 23197 -IFNjcm9sbA== 23198 -IFNjaWVuY2Vz 23199 -IGp1Zw== 23200 -IGFtcGw= 23201 -ZW50aQ== 23202 -TEVGVA== 23203 -IHRhYnM= 23204 -IGVub3Jtb3Vz 23205 -LmdldEtleQ== 23206 -bG9jYXRl 23207 -LkVY 23208 -LnN0b3JhZ2U= 23209 -Lldl 23210 -IHRvYXN0 23211 -IEFkZGl0aW9uYWxseQ== 23212 -ODgy 23213 -IE5PVw== 23214 -NTQ3 23215 -X1VQREFURQ== 23216 -IHRyYW5zZmVycmVk 23217 -dGhh 23218 -LkRpc3BsYXk= 23219 -X3Vp 23220 -SURFTw== 23221 -IG1lYW5pbmdmdWw= 23222 -IE1vc2Nvdw== 23223 -LHRoaXM= 23224 -IFZpY3Rvcmlh 23225 -5pS5 23226 -INCf 23227 -LnN0YWNr 23228 -IEJhcm4= 23229 -cGFyZWRTdGF0ZW1lbnQ= 23230 -OnN0cmluZw== 23231 -IGJpag== 23232 -IFNUQVRF 23233 -IGVtcGxveWVycw== 23234 -CWlucHV0 23235 -KHw= 23236 -IGxleA== 23237 -aW52b2tl 23238 -CW51bQ== 23239 -Kyss 23240 -YXRpYWw= 23241 -b3JzZXM= 23242 -IGZvcms= 23243 -X3R4dA== 23244 -IEFudG9uaW8= 23245 -ICg8 23246 -YXZlcnNl 23247 -IGRldmFzdA== 23248 -44CA 23249 -LkRlYw== 23250 -IEdhcmQ= 23251 -L3Vp 23252 -LiU= 23253 -dHJp 23254 -IHJvbGxlZA== 23255 -VmFsdWVQYWly 23256 -aXR0ZW4= 23257 -IFRoZXI= 23258 -IHZyb3U= 23259 -IEZsb3c= 23260 -IEZpbmFuY2U= 23261 -IENvbWI= 23262 -SEM= 23263 -LnNldFZpc2libGU= 23264 -aXNs 23265 -IHBr 23266 -Nzcz 23267 -IHVwc2V0 23268 -KHJhdw== 23269 -IFZpY2U= 23270 -ZWF0dXJlcw== 23271 -IExhbmc= 23272 -MDI5 23273 -TG9va2luZw== 23274 -NzY3 23275 -IEFTVA== 23276 -IHRyaXBz 23277 -IEp1c3Rpbg== 23278 -YnJvd3Nlcg== 23279 -PSInLiQ= 23280 -LnZlcnRpY2Vz 23281 -ODIx 23282 -LWNv 23283 -fS97 23284 -ID8s 23285 -IERvbWlu 23286 -IEJlbGc= 23287 -Ijw= 23288 -IHN1cHBvc2U= 23289 -YWRkeQ== 23290 -IHdhbGtz 23291 -Njg4 23292 -RVJSVQ== 23293 -X2ZpbHRlcnM= 23294 -UHJlZmVycmVk 23295 -c2NlbmU= 23296 -0LXRgQ== 23297 -IEFmZmFpcnM= 23298 -ICIjew== 23299 -IG9uU3VibWl0 23300 -IHN0b2Nrcw== 23301 -L3ZpZXc= 23302 -Z3JlZQ== 23303 -LWdldA== 23304 -OTAz 23305 -aGl0 23306 -Sm8= 23307 -LmdldEM= 23308 -NzI1 23309 -SW5pdGlhbGl6ZWQ= 23310 -0YLQuA== 23311 -Y3V0cw== 23312 -KFR5cGU= 23313 -IEFncmVlbWVudA== 23314 -IFZpZXRuYW0= 23315 -IC8qIQ== 23316 -IHBpenph 23317 -LXZpZXc= 23318 -X2Vt 23319 -IGxocw== 23320 -IG11eQ== 23321 -IElkZW50 23322 -IEZyaWVuZHM= 23323 -MDYx 23324 -IGFidW5k 23325 -X0FE 23326 -LnRpbWVzdGFtcA== 23327 -LSc= 23328 -IGR1cGxpY2F0ZQ== 23329 -IGh1bnRpbmc= 23330 -IHJlZ3VsYXRvcnk= 23331 -aWFv 23332 -YW1vdXM= 23333 -IEVudGVydGFpbm1lbnQ= 23334 -W0E= 23335 -aWF0cmlj 23336 -X0NMSUVOVA== 23337 -IEtpZHM= 23338 -L3BrZw== 23339 -QnJlYWs= 23340 -KSkpOwoK 23341 -IFNoYXBl 23342 -IHJlbGF0aW5n 23343 -SW50ZXJydXB0 23344 -YWJsZU9wYWNpdHk= 23345 -ZW1icmU= 23346 -IG15c3Rlcnk= 23347 -IGpvdXJuYWxpc3Rz 23348 -cml0YWJsZQ== 23349 -Lkxpbms= 23350 -IHN0b3BwaW5n 23351 -Q1JFVA== 23352 -LkRC 23353 -IHBvcHVsYXJpdHk= 23354 -IGdldw== 23355 -IGltcHI= 23356 -c2V0VmFsdWU= 23357 -RkxBRw== 23358 -CW1heA== 23359 -IGJha2U= 23360 -d3k= 23361 -IEVjb25vbWlj 23362 -IGVuY29udHI= 23363 -IGZuYW1l 23364 -L2Rl 23365 -UmFuaw== 23366 -IGJ1Z3M= 23367 -LnNt 23368 -IG1lZGlhbg== 23369 -RE9XTg== 23370 -IFN1cmU= 23371 -QXRJbmRleA== 23372 -IERpY2s= 23373 -IChfXw== 23374 -LmRlbHRh 23375 -RnI= 23376 -IHN1Z2dlc3Rpbmc= 23377 -IFJlY3ljbGVyVmlldw== 23378 -LGU= 23379 -U1RBUlQ= 23380 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 23381 -eGZvcmQ= 23382 -IHJlY2VpcHQ= 23383 -Q0xBSU0= 23384 -cmVhZG9ubHk= 23385 -OTY4 23386 -IGVuZ2FnaW5n 23387 -NjE5 23388 -Q2E= 23389 -YXNtYQ== 23390 -IGVuc3VyaW5n 23391 -RW5nbGlzaA== 23392 -IFZhbmNvdXZlcg== 23393 -aHl0aA== 23394 -IHB1cmNoYXNpbmc= 23395 -IFBJ 23396 -LndvcmQ= 23397 -KHNw 23398 -LmhvbWU= 23399 -OmRlZg== 23400 -IGdpZw== 23401 -NTc0 23402 -Njcx 23403 -IFZl 23404 -Zm9ydW0= 23405 -IE1pdGNo 23406 -QmF5 23407 -X0ZM 23408 -NjUx 23409 -IHNvbGw= 23410 -NTc3 23411 -X2NvbHVtbnM= 23412 -IG1pbm9yaXR5 23413 -YmlyZA== 23414 -IGhhbmRlZA== 23415 -U1NM 23416 -U1RBVA== 23417 -IG5lcnZvdXM= 23418 -g70= 23419 -IGZpbGVQYXRo 23420 -Q1JFQVRF 23421 -QXc= 23422 -IHBlbnM= 23423 -ODM1 23424 -c2VlZA== 23425 -IENvbXB1dGU= 23426 -b2xr 23427 -NTk0 23428 -IEFzc2V0 23429 -cmVhY2g= 23430 -JyksDQo= 23431 -bmF2aWdhdGlvbg== 23432 -TEY= 23433 -L3V0aWw= 23434 -IFB1Yg== 23435 -IOKU 23436 -Y2lvbg== 23437 -IyMK 23438 -MDcy 23439 -SUlJ 23440 -VGFnTmFtZQ== 23441 -IGFtaWQ= 23442 -cGVybWlzc2lvbg== 23443 -aWZpYWJsZQ== 23444 -eEZGRkZGRkZG 23445 -0L3QuA== 23446 -LkJ1ZmZlcg== 23447 -X2lycQ== 23448 -ZGFyaw== 23449 -IHJldHZhbA== 23450 -LmZpcmU= 23451 -cHJvZHVjdGlvbg== 23452 -Lmxpc3Rlbg== 23453 -IFdlYXRoZXI= 23454 -IGJ1eWVycw== 23455 -Lm5l 23456 -ZXJw 23457 -IFBlbnQ= 23458 -Njk5 23459 -IHdlbGZhcmU= 23460 -IHBhZ2VTaXpl 23461 -IFN0YWRpdW0= 23462 -ZXJ0YQ== 23463 -IGxldg== 23464 -YW1wYQ== 23465 -UGFnZXI= 23466 -NjY1 23467 -IGNoYXJnaW5n 23468 -IE5ldGZsaXg= 23469 -fG51bGw= 23470 -X3JhbmRvbQ== 23471 -LnhwYXRo 23472 -IHN0ZXJl 23473 -IElTSVM= 23474 -cG9uc2Vz 23475 -KGxvYw== 23476 -NTY2 23477 -ZXlvbmQ= 23478 -IE9mZmljaWFs 23479 -NjU3 23480 -IE1hcnlsYW5k 23481 -RGF0YVR5cGU= 23482 -X3Bhcg== 23483 -e30s 23484 -IEVuam95 23485 -NzI3 23486 -X1NISUZU 23487 -IEF3YXJkcw== 23488 -X0VOVFJZ 23489 -IHNlZW1pbmdseQ== 23490 -ZW50aWNhdGU= 23491 -IGhlYXJ0cw== 23492 -NTgz 23493 -XzsKCg== 23494 -IEhJVg== 23495 -IGluZGl2aWQ= 23496 -IEZsYWc= 23497 -X2N0cmw= 23498 -IENhbGxiYWNr 23499 -LHo= 23500 -IEdQVQ== 23501 -CW9iag== 23502 -IFBob2VuaXg= 23503 -IEJVUw== 23504 -OTA3 23505 -IHJ1YmJlcg== 23506 -X0FVVEg= 23507 -IFNvbHV0aW9ucw== 23508 -KGxvY2F0aW9u 23509 -VmFyaWFibGVz 23510 -LnNldEVuYWJsZWQ= 23511 -X2hpZ2g= 23512 -V08= 23513 -R2VzdHVyZQ== 23514 -IHJldHJ5 23515 -IG9iamVjdEZvcktleQ== 23516 -YWxsb3dlZW4= 23517 -IG1vcw== 23518 -IENlbGU= 23519 -IGlra2U= 23520 -KGNlbGw= 23521 -IE1PREU= 23522 -cmVuYQ== 23523 -IGRlc2NyaWJpbmc= 23524 -NjQx 23525 -IHBoaQ== 23526 -IHJk 23527 -IGRlc2VydmU= 23528 -IHdoZWVscw== 23529 -5biC 23530 -IGNyaXRpY3M= 23531 -NzU1 23532 -TmFtZXNwYWNl 23533 -IEZyYQ== 23534 -IAoKCgo= 23535 -IGFsbGE= 23536 -IHJlcXVpcmluZw== 23537 -5pyf 23538 -dXRhdGlvbg== 23539 -IGRlbGF5ZWQ= 23540 -IGFkbWluaXN0cmF0aXZl 23541 -IGJheQ== 23542 -LmhpZGRlbg== 23543 -VGV4 23544 -MDUx 23545 -IGJvdW5kYXJpZXM= 23546 -IF0pOwoK 23547 -IEZvbGxvd2luZw== 23548 -fi8= 23549 -Rmk= 23550 -X2NvbnY= 23551 -X1RJVExF 23552 -IGRlc2Rl 23553 -SUNvbGxlY3Rpb25WaWV3 23554 -QWxpYXM= 23555 -IGJpdGU= 23556 -cGF0aWVudA== 23557 -X0NPTU1BTkQ= 23558 -Q29tcGxldGVk 23559 -CWVsaWY= 23560 -KDw= 23561 -QnVzaW5lc3M= 23562 -IFBvb2w= 23563 -IHB1cnN1ZQ== 23564 -IEJhbg== 23565 -X3N0ZXBz 23566 -X0RFQ0w= 23567 -dW1ibGU= 23568 -IGNvbWJv 23569 -IExheWVy 23570 -Lnhy 23571 -IGR1cA== 23572 -LS0tLS0tLS0t 23573 -NjI4 23574 -IG1vZGlmaWVy 23575 -cm9i 23576 -cmV6 23577 -Njk2 23578 -IGF0aGxldGVz 23579 -VXNlZA== 23580 -d2Vhcg== 23581 -ODE1 23582 -IGxlZ2l0aW1hdGU= 23583 -ICIKCg== 23584 -IGh2 23585 -U3Rk 23586 -MDM3 23587 -IEhvbGQ= 23588 -IHN1cnZpdg== 23589 -IEFsbGlhbmNl 23590 -IEVhcmx5 23591 -Nzc4 23592 -QmVoYXZpb3I= 23593 -KGZvbnQ= 23594 -L2xpYnM= 23595 -IHJlY3RhbmdsZQ== 23596 -IHNpbmdlcg== 23597 -IGFtcA== 23598 -RXF1YWxUbw== 23599 -ICIuIg== 23600 -IGdpcmxmcmllbmQ= 23601 -5bE= 23602 -bGluZWFy 23603 -b2JzZXJ2 23604 -IHBpw7k= 23605 -IGNvbXBsZW1lbnQ= 23606 -V2l0aFZhbHVl 23607 -KHBhc3N3b3Jk 23608 -dGFrZQ== 23609 -Qmxhbms= 23610 -IENvbXBhcg== 23611 -JyIs 23612 -X3BvbGljeQ== 23613 -bW9uZ29vc2U= 23614 -X0ZBSUxFRA== 23615 -LnJlcG9ydA== 23616 -UmF0aW8= 23617 -LlBlcmZvcm1MYXlvdXQ= 23618 -NzQ3 23619 -dXNhYmxl 23620 -bWVycw== 23621 -X3JlbmRlcg== 23622 -UEVFRA== 23623 -Nzcy 23624 -IGxlc2I= 23625 -CUU= 23626 -X3Rvb2w= 23627 -IGxhZGllcw== 23628 -OTA4 23629 -0L7RgQ== 23630 -KSkpKQo= 23631 -Ozs7Ow== 23632 -LmRvdA== 23633 -IG5lc3Q= 23634 -cGVhaw== 23635 -dWtraXQ= 23636 -ZWNh 23637 -X1NX 23638 -ICYo 23639 -IE9rbGFob21h 23640 -IGJhbmtpbmc= 23641 -NTY5 23642 -IE5pbnRlbmRv 23643 -NzUy 23644 -IHJlcHJvZHVjZQ== 23645 -X2VsZW1lbnRz 23646 -X21hYw== 23647 -cHJveHk= 23648 -IHJlbWFya2FibGU= 23649 -fS8kew== 23650 -IG91dHM= 23651 -Lmhhc05leHQ= 23652 -TU9ERQ== 23653 -NjU4 23654 -IGFuaW1l 23655 -LmNvbm4= 23656 -VW5pcXVl 23657 -RG9t 23658 -IGltcG9ydGFudGx5 23659 -aXR0eQ== 23660 -IGp1aWNl 23661 -VHc= 23662 -IFBhcnRuZXJz 23663 -IGF0dGFja2luZw== 23664 -IHBvcnRhYmxl 23665 -YW1pZW50bw== 23666 -LlBpY3R1cmVCb3g= 23667 -Lmdlbg== 23668 -IG9wdGltYWw= 23669 -NTgy 23670 -IHJlY3Jl 23671 -IGpvdXJuYWxpc3Q= 23672 -IEV4dHJhY3Q= 23673 -IE1vcmVvdmVy 23674 -IG1hcmdpblRvcA== 23675 -LkFw 23676 -IGZpcmluZw== 23677 -TmFO 23678 -CXRlbXBsYXRl 23679 -0LDQtA== 23680 -LkVu 23681 -IGRlZmVuY2U= 23682 -IFRlbA== 23683 -aWxlbg== 23684 -amFu 23685 -PWRhdGE= 23686 -IFVybA== 23687 -IFJldXRlcnM= 23688 -KHRvdGFs 23689 -IEZpZnRo 23690 -IGVzc2F5cw== 23691 -IGludGVycHJldGF0aW9u 23692 -IGNoYXJpdHk= 23693 -IFJ1bGVz 23694 -IHN1YnNlY3Rpb24= 23695 -c3R5bGVk 23696 -YXplcg== 23697 -bGFncw== 23698 -TElTVA== 23699 -IHVwbG9hZGVk 23700 -IHRyYXNo 23701 -IHJlZ2lzdHI= 23702 -IHNlbGxlcg== 23703 -Pic7DQo= 23704 -IHN0YXJ0VGltZQ== 23705 -55k= 23706 -c3k= 23707 -KEh0dHBTZXJ2bGV0UmVxdWVzdA== 23708 -IHRyYXA= 23709 -R0M= 23710 -IGVtYmVkZGVk 23711 -IHN1cnJvdW5kZWQ= 23712 -ODE2 23713 -aW1pdHM= 23714 -VFg= 23715 -eWxpbmRlcg== 23716 -Njg1 23717 -IEZhbA== 23718 -IHNlbnRlbmNlcw== 23719 -IEph 23720 -SUZJQ0FUSU9O 23721 -d2VhcG9u 23722 -b3ZhdGlvbg== 23723 -IGNvYXQ= 23724 -IGludGVycG9s 23725 -IGxpcHM= 23726 -IEt5 23727 -IHZlY3RvcnM= 23728 -X2Ft 23729 -IGludGFrZQ== 23730 -Lndvcmxk 23731 -IGluYm94 23732 -IE1BQw== 23733 -X2Fi 23734 -KG5hbWVvZg== 23735 -NjMz 23736 -IGVudGVydA== 23737 -IGdhdGhlcmluZw== 23738 -IFNJTQ== 23739 -Kysu 23740 -bnlh 23741 -J319 23742 -IFVQREFURQ== 23743 -IHBhYw== 23744 -KGh0bWw= 23745 -IFNhbnQ= 23746 -aWF0aW5n 23747 -IElkZWFz 23748 -IHNwcmF5 23749 -IEhhcnQ= 23750 -IHZlcmlmaWNhdGlvbg== 23751 -YWRlc2g= 23752 -L21vZHVsZXM= 23753 -IE1pbmQ= 23754 -IFNpemVkQm94 23755 -IHNoZWx0ZXI= 23756 -IGhlcm9lcw== 23757 -YXR0eQ== 23758 -IGNlcnRpZmllZA== 23759 -c2o= 23760 -IMOqdHJl 23761 -xYJv 23762 -IHB1Ymxpc2hpbmc= 23763 -IE1hbGF5cw== 23764 -LmdldFVzZXI= 23765 -IFByb3ZpZGVy 23766 -IExpbmtlZExpc3Q= 23767 -IEJvcg== 23768 -Uk9VTkQ= 23769 -ZGlk 23770 -dGFpbg== 23771 -cGlyZQ== 23772 -IEplbm4= 23773 -dGVs 23774 -YW5kZQ== 23775 -NzU3 23776 -X2Zyb250 23777 -IE1jRw== 23778 -VGVzdE1ldGhvZA== 23779 -4Lit 23780 -IG9jY2FzaW9uYWxseQ== 23781 -IFdhbGVz 23782 -IGV4ZXJjaXNlcw== 23783 -INCS 23784 -MDQ1 23785 -LXBsdXM= 23786 -IHZhbGlkYXRvcg== 23787 -IHByYXllcg== 23788 -TEFURUQ= 23789 -X2F1dGhvcg== 23790 -IGxhYm91cg== 23791 -KysK 23792 -LWVxdWl2 23793 -IEdQTA== 23794 -IGZhY2Vib29r 23795 -c2ltcGxl 23796 -Z2x5 23797 -UHJvY2Vzc29y 23798 -aXB5 23799 -NzQ0 23800 -ICo+ 23801 -NjQ4 23802 -IGNsZWFyZWQ= 23803 -IFB1c2g= 23804 -ODU4 23805 -IHBlbmlz 23806 -U3RydWN0dXJl 23807 -bGlq 23808 -IE1vcmdhbg== 23809 -IGhhbmRmdWw= 23810 -Ii4K 23811 -OTg0 23812 -fFw= 23813 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 23814 -IEFxdQ== 23815 -NTg0 23816 -X0lD 23817 -LmxvYWRz 23818 -IG1ldGVy 23819 -IE1hcmluZQ== 23820 -Ojp7 23821 -IFRT 23822 -Nzc2 23823 -IEFycmF5cw== 23824 -LlRpdGxl 23825 -R1JBTQ== 23826 -dGVybWlu 23827 -IGNvaW5j 23828 -RWxzZQ== 23829 -X3N0YXRlcw== 23830 -LXJ1bg== 23831 -bWVtYmVycw== 23832 -Nzgy 23833 -YXN0cm8= 23834 -MDY2 23835 -IG9uUHJlc3M= 23836 -IGJlaW5ncw== 23837 -IGFiYW5kb25lZA== 23838 -IHRheHA= 23839 -b3duZXJz 23840 -Lm1vZGU= 23841 -IGRpYWdub3Npcw== 23842 -IF8K 23843 -IEtuaWdodA== 23844 -CUE= 23845 -IG9ic2VydmU= 23846 -KSwn 23847 -ODIz 23848 -ISIpCg== 23849 -IFBhcmE= 23850 -IHZhcmlhdGlvbg== 23851 -KEZhbHNl 23852 -IEFudGk= 23853 -IGdyaQ== 23854 -IGhvbWVsZXNz 23855 -P3Y= 23856 -IGJleg== 23857 -LlNlcnZlcg== 23858 -cmVsZWFzZQ== 23859 -IFBhdHJp 23860 -IGNoYXJz 23861 -IHJhbmtpbmc= 23862 -YWN0aXZhdGlvbg== 23863 -NTgx 23864 -IHdpZGVz 23865 -cXI= 23866 -LlNxbA== 23867 -YWN1bGFy 23868 -IEJvdA== 23869 -X3N5bmM= 23870 -IGhhcHBpbmVzcw== 23871 -IHZvbHVudGVlcnM= 23872 -ODc3 23873 -IHNpdHM= 23874 -Lzw= 23875 -W2U= 23876 -KGZpbGVOYW1l 23877 -IGNhcGFj 23878 -ODMy 23879 -IE1hcmlh 23880 -ZmF0aGVy 23881 -IGdyYW0= 23882 -Kmk= 23883 -IGNhc28= 23884 -X2RyYXc= 23885 -IFJhdw== 23886 -IEl0ZXJhdG9y 23887 -NjY0 23888 -IFBhZGRpbmc= 23889 -OTI0 23890 -UEQ= 23891 -Qk9Y 23892 -IFNQRUNJQUw= 23893 -IGZlY2hh 23894 -IHZpZGU= 23895 -IExlYWRlcg== 23896 -5Lul 23897 -JCgiLg== 23898 -IGRpYW1ldGVy 23899 -IG1pbGQ= 23900 -NzQ1 23901 -IHJvY2tz 23902 -YXBwaW5ncw== 23903 -MDQ4 23904 -ZGlyZWN0b3J5 23905 -NTU3 23906 -LmZsdXNo 23907 -IEplc3M= 23908 -VU5JVA== 23909 -IFBlYXI= 23910 -IG1hbmRhdG9yeQ== 23911 -U3Vy 23912 -cXQ= 23913 -IHN0cmVhbXM= 23914 -IGNvb3BlcmF0aW9u 23915 -IFNhYw== 23916 -IGNoZWFwZXI= 23917 -CWNo 23918 -YW5pbWF0aW9u 23919 -ZmFyZQ== 23920 -KGhlaWdodA== 23921 -KFRydWU= 23922 -Tlk= 23923 -IHdyZXN0 23924 -IHBvbGxz 23925 -IGVuY291bnRlcmVk 23926 -IE1hcmtldGFibGU= 23927 -X1BBU1NXT1JE 23928 -NzE2 23929 -X1NFTEVDVA== 23930 -IEFyYWJpYQ== 23931 -X2Nsb2Nr 23932 -IHZveQ== 23933 -INC40Lc= 23934 -IHN0aXI= 23935 -aXNpYmxl 23936 -LWVmZmVjdA== 23937 -LmNyZWF0ZWQ= 23938 -IHRveXM= 23939 -IFRyYWRhYmxl 23940 -IHJ1c3Q= 23941 -IHN0cmNweQ== 23942 -X3RpbWVzdGFtcA== 23943 -IHRhbGVudGVk 23944 -LG51bGw= 23945 -IEpvYnM= 23946 -IFBvcnRsYW5k 23947 -IHdlYWtuZXNz 23948 -VGhyb3c= 23949 -IEFuZ2Vs 23950 -5L+u 23951 -NzU0 23952 -IHVuY2VydA== 23953 -77yJCg== 23954 -IOydtA== 23955 -V2hpY2g= 23956 -IFstXTo= 23957 -U29tZXRoaW5n 23958 -IGNvbnZpY3RlZA== 23959 -a2xl 23960 -ZWRpdW0= 23961 -IGJyYW5jaGVz 23962 -IGJhc2Vz 23963 -564= 23964 -IGNvbXBsZXhpdHk= 23965 -IEZpZw== 23966 -LnJlc2hhcGU= 23967 -JGRi 23968 -NzM2 23969 -X0NPTlNU 23970 -IFRlcw== 23971 -LnJ1bnRpbWU= 23972 -IGRlbnk= 23973 -IEJTRA== 23974 -IGty 23975 -aGF0dA== 23976 -IFN0YXRpYw== 23977 -IHVuaXZlcnNpdGllcw== 23978 -UmVwbGFjZQ== 23979 -IGRyb3Zl 23980 -IGFkb2xlcw== 23981 -X3BsdWdpbg== 23982 -IExHQlQ= 23983 -IHRleA== 23984 -ZHVjdGlvbg== 23985 -NzUx 23986 -Nzk5 23987 -RURJ 23988 -IFRlZA== 23989 -X1VSSQ== 23990 -IHJlY2VwdGlvbg== 23991 -YXJ0ZW4= 23992 -LlNpbmdsZQ== 23993 -cmljZQ== 23994 -c2Npb3Vz 23995 -ODQz 23996 -X2Jn 23997 -IHdhZ2Vz 23998 -IFNlcnZsZXQ= 23999 -VUlMYXlvdXQ= 24000 -IGZvcm1hdHRlZA== 24001 -Lk1vZA== 24002 -PGNsYXNz 24003 -aXNlbg== 24004 -IHJlcHJlc2VudGF0aXZlcw== 24005 -Il09 24006 -IHBvcnRhbA== 24007 -IEh1bnRlcg== 24008 -IGhpcmluZw== 24009 -X18pCg== 24010 -cmljdWx1bQ== 24011 -dW8= 24012 -bGllc3Q= 24013 -IHRlYXJz 24014 -TGF0 24015 -IGxpdGVyYWw= 24016 -Lkluc2VydA== 24017 -IGN1cnM= 24018 -IENvbXB1dA== 24019 -IHRlcnJvcmlzbQ== 24020 -IHN3ZWVw 24021 -IFtdDQo= 24022 -IHBhc3Nlbmdlcg== 24023 -IGVhc3Rlcm4= 24024 -IHR3ZWV0cw== 24025 -IG9wZXJhdGVk 24026 -d25k 24027 -IFN5bg== 24028 -LnRvb2xz 24029 -IFdN 24030 -dWxhdGVz 24031 -IGJhY3Rlcmlh 24032 -KGJ5dGVz 24033 -LnNldERhdGE= 24034 -IHZpc2liaWxpdHk= 24035 -Ly89PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09 24036 -ZWxt 24037 -IGdlbmVyYXRpbmc= 24038 -IG12 24039 -IGto 24040 -amVu 24041 -L3NlYXJjaA== 24042 -IGFjY291bnRpbmc= 24043 -c2VnbWVudA== 24044 -YWN0aWM= 24045 -Lmlw 24046 -IGRlcGxveW1lbnQ= 24047 -IGZvb3Rlcg== 24048 -PicsCg== 24049 -IGV4cGFuZGluZw== 24050 -IEhhbWlsdG9u 24051 -IENvbnRyaWI= 24052 -LlRhYmxlcw== 24053 -NzI4 24054 -QWN0aXY= 24055 -SEg= 24056 -b2NvbW1lcmNl 24057 -Xzs= 24058 -IGFtb25nc3Q= 24059 -b3dpbmc= 24060 -ODU5 24061 -IENvbGQ= 24062 -QVBI 24063 -IHBzeWNob2xvZ2ljYWw= 24064 -X3RlbnNvcg== 24065 -IHBhY2thZ2luZw== 24066 -IFN3ZWRlbg== 24067 -IHBhcmU= 24068 -IGFnZ3JlZ2F0ZQ== 24069 -IG1vZGVyYXRl 24070 -ODYy 24071 -X2hhbmQ= 24072 -IGRlc2lnbmF0ZWQ= 24073 -IGRydW0= 24074 -IGdldFVzZXI= 24075 -IENyZWVr 24076 -X3Njb3Bl 24077 -IFRyYW5zZmVy 24078 -IE1hcmc= 24079 -IGZpZ2h0ZXJz 24080 -V25k 24081 -IFNlbA== 24082 -IExhdW5jaA== 24083 -IGVtZXJnaW5n 24084 -aWZyYW1l 24085 -IEFkZGl0aW9uYWw= 24086 -IGZlYXJz 24087 -IHNhdGVsbGl0ZQ== 24088 -Xzo= 24089 -IGRpc3Bvc2luZw== 24090 -R2V0VmFsdWU= 24091 -SHR0cFBvc3Q= 24092 -QVRJVkU= 24093 -dWxhcnk= 24094 -Vmlld3M= 24095 -IGF0dGVuZGluZw== 24096 -IFRlbm5lc3NlZQ== 24097 -IE1pc3Npb24= 24098 -IG1lZGljYXRpb24= 24099 -IFd5 24100 -IEFubmE= 24101 -2Lk= 24102 -IFZlcnRleA== 24103 -LnR5cGVz 24104 -T3JnYW4= 24105 -LkRhdGFHcmlkVmlld1RleHRCb3hDb2x1bW4= 24106 -IFJT 24107 -IHRlbXBv 24108 -KEFwcA== 24109 -ODky 24110 -VmVyc2lvblVJRA== 24111 -LnBvaW50 24112 -IER1dGNo 24113 -SG91cnM= 24114 -TFU= 24115 -IHF1b3RlZA== 24116 -LmJ1aWxkZXI= 24117 -IFBlcmZlY3Q= 24118 -IEFsd2F5cw== 24119 -X3R3bw== 24120 -IGV4Y2x1c2l2ZWx5 24121 -IENyYQ== 24122 -aWZpY2Fy 24123 -IEFXUw== 24124 -aW5naGFt 24125 -Y29tcGxleA== 24126 -a2VybmVs 24127 -IGdyYXZpdHk= 24128 -IHdp 24129 -MDUy 24130 -IG92ZXJ2aWV3 24131 -NjYx 24132 -IFdhbnQ= 24133 -IFdQ 24134 -KHNo 24135 -LnJvdGF0aW9u 24136 -U3RhdGVz 24137 -IFRlZW4= 24138 -X2NvbXBvbmVudHM= 24139 -7IiY 24140 -UmVjZWl2ZWQ= 24141 -IGx5cmljcw== 24142 -cml0ZXM= 24143 -CQkJCQkg 24144 -LUFtZXJpY2Fu 24145 -W251bQ== 24146 -L3B5dGhvbg== 24147 -IFVBUlQ= 24148 -IGFwcGxl 24149 -IEpvbmF0aGFu 24150 -IG1vbWVudHVt 24151 -4Lix 24152 -grk= 24153 -IG1pY2g= 24154 -YW5kcmE= 24155 -IGJpb2xvZ2ljYWw= 24156 -IE1lbnM= 24157 -ICUl 24158 -ZWxzZWE= 24159 -IE1leGljYW4= 24160 -LnJhbmRpbnQ= 24161 -IHRhbGU= 24162 -IFZhbGlkYXRl 24163 -IGRlZmVhdGVk 24164 -Lmh0bQ== 24165 -IGNvcHBlcg== 24166 -PS8= 24167 -Y29zeXN0ZW0= 24168 -IHJpcA== 24169 -ZGVjaW1hbA== 24170 -LlZJU0lCTEU= 24171 -IFRh 24172 -CQkJCQkJCQkJCQkJCQk= 24173 -IGRvd25sb2FkZWQ= 24174 -ZW52aXJvbm1lbnQ= 24175 -IG5vbWluZQ== 24176 -YnVpbGRpbmc= 24177 -IFNwb3Q= 24178 -aXBoZXJhbA== 24179 -IGFsdG8= 24180 -cXVldA== 24181 -IEZU 24182 -L2dldA== 24183 -L21hc3Rlcg== 24184 -V0lO 24185 -5YWD 24186 -Njc2 24187 -V2VzdA== 24188 -YXJnYw== 24189 -IHByb2R1Y2Vycw== 24190 -IE11Y2g= 24191 -X3N0b3JhZ2U= 24192 -Y3JlZGl0 24193 -Q09OVA== 24194 -IHZldA== 24195 -IHZvaWNlcw== 24196 -KCcnLA== 24197 -IGluc3RydW1lbnRz 24198 -NjYy 24199 -IE1TRw== 24200 -ZXNzZQ== 24201 -cmVwb3NpdG9yeQ== 24202 -b21pY3M= 24203 -IGRlYWxlcg== 24204 -U3RpbGw= 24205 -IGJhbm5lcg== 24206 -YXNjaWk= 24207 -IHJlbWFya3M= 24208 -W2pz 24209 -IHNob3J0ZXI= 24210 -Z3VscA== 24211 -IG15c3Rlcg== 24212 -IGt1bg== 24213 -IEJpcmQ= 24214 -IHRpZW5l 24215 -Nzg4 24216 -bnV0 24217 -IFVt 24218 -IHdpc2U= 24219 -WWVhaA== 24220 -SU5FU1M= 24221 -MDQ2 24222 -X2JlZ2lu 24223 -LWhlYWRpbmc= 24224 -Q291cnNl 24225 -IA0KDQo= 24226 -b21iaWU= 24227 -Z3JhZGVk 24228 -IEdQUw== 24229 -IMW8ZQ== 24230 -Rml0 24231 -Y2FwdGlvbg== 24232 -w7Zu 24233 -L2ltYWdl 24234 -bGlh 24235 -KG1vZA== 24236 -IGxlYWs= 24237 -ZW56YQ== 24238 -NjI5 24239 -L0g= 24240 -IEhhcHB5 24241 -OTkz 24242 -RGlzdA== 24243 -bng= 24244 -IEdvdmVybm9y 24245 -KGxhc3Q= 24246 -dGVhY2hlcg== 24247 -IFNlbnQ= 24248 -c3VwcG9ydA== 24249 -ODM4 24250 -amVjdG9yeQ== 24251 -INmF 24252 -UmVnaXN0cmF0aW9u 24253 -MDYz 24254 -IEdyYXk= 24255 -LGZhbHNl 24256 -IGFkanVzdGVk 24257 -KHNldHRpbmdz 24258 -PFI= 24259 -IE1hZ2U= 24260 -IHBsYWludA== 24261 -XykK 24262 -CWl0 24263 -b21ldHJpYw== 24264 -LmJvb3RzdHJhcA== 24265 -IGNhcnJpZXM= 24266 -SXA= 24267 -ICEk 24268 -IHN3aW1taW5n 24269 -IE1hcmlv 24270 -IFF1ZXN0aW9ucw== 24271 -UEFDRQ== 24272 -5pa5 24273 -ZW9y 24274 -fX0i 24275 -IG92ZW4= 24276 -IEtvbg== 24277 -IHdpc2RvbQ== 24278 -IGFjcXVpc2l0aW9u 24279 -ZXNzbWVudA== 24280 -YWdpbmU= 24281 -IGV4cHJlc3Npb25z 24282 -U2VxdWVudGlhbEdyb3Vw 24283 -RnJvbnQ= 24284 -dWxwdA== 24285 -YXdr 24286 -J10pCgo= 24287 -ODEz 24288 -NzMy 24289 -X0FS 24290 -IGFuYWxvZw== 24291 -dWxpbg== 24292 -X1BSSU5U 24293 -IExH 24294 -IGJsb2I= 24295 -IEZ1cnRoZXJtb3Jl 24296 -X2NvbXBvbmVudA== 24297 -IENvbGU= 24298 -TEFO 24299 -U0NSSVBUSU9O 24300 -IGxhcA== 24301 -aWNlbnNpbmc= 24302 -X1RJTUVPVVQ= 24303 -IEZybw== 24304 -IGxpYWJpbGl0eQ== 24305 -IGNvbXBvc2Vk 24306 -NjM0 24307 -LmNyZWF0ZVNlcXVlbnRpYWxHcm91cA== 24308 -X3BlcnNvbg== 24309 -IGJlYW0= 24310 -CSAgICAgICAg 24311 -IE5vdEZvdW5k 24312 -Njg0 24313 -LicK 24314 -w61z 24315 -LlRleHRWaWV3 24316 -UERG 24317 -IGthcg== 24318 -X18oJw== 24319 -ICI6Ig== 24320 -X21lc3NhZ2Vz 24321 -IGhhcnZlc3Q= 24322 -Lmhpc3Rvcnk= 24323 -PicK 24324 -LWZvbGQ= 24325 -5oo= 24326 -IEJldHRlcg== 24327 -ICJcPA== 24328 -c3BhY2luZw== 24329 -IGZ1cm5pc2hlZA== 24330 -OTEz 24331 -b3Nlcg== 24332 -XX0K 24333 -ICQi 24334 -cHVsbA== 24335 -LlBvc3Q= 24336 -OTE5 24337 -KGlw 24338 -l48= 24339 -LmZyb250 24340 -bnRl 24341 -IEZN 24342 -Z3VpZA== 24343 -ODQ0 24344 -IG5lZ290aWF0aW9ucw== 24345 -YWdvbmFs 24346 -OTM0 24347 -IHRyZW1lbmQ= 24348 -dW5nZW9u 24349 -QWR2 24350 -Y2Fyb3VzZWw= 24351 -w59l 24352 -X0RFU0M= 24353 -IGhhbW1lcg== 24354 -4bqt 24355 -ICAgICAgICAKCg== 24356 -LWNvcmU= 24357 -LXNlcnZpY2U= 24358 -IGNvcm5lcnM= 24359 -IFNG 24360 -cHJlZA== 24361 -PkE= 24362 -IEpMYWJlbA== 24363 -IHJvbWFudGlj 24364 -IHRlc3RpbW9ueQ== 24365 -b3Nj 24366 -IEdlbmVyYXRpb24= 24367 -YXN1cmVz 24368 -X2ludGVybmFs 24369 -IHByaW50cw== 24370 -IF0pCg== 24371 -IENsZXZlbGFuZA== 24372 -cmVwbw== 24373 -RGlzYw== 24374 -Njc3 24375 -NzYy 24376 -ICI+Cg== 24377 -77+977+977+977+9 24378 -IG5lYXJlc3Q= 24379 -NTkx 24380 -X3Ri 24381 -KHJlcXVpcmU= 24382 -RU9G 24383 -LWNoaWxk 24384 -IGJ1ZGQ= 24385 -Llh0cmFFZGl0b3Jz 24386 -YWx0aWVz 24387 -NzIz 24388 -XCI6XCI= 24389 -V29yZHM= 24390 -OTE3 24391 -IGxvY2FsbHk= 24392 -IHB1cmNoYXNlcw== 24393 -Njk1 24394 -RHJhd2Vy 24395 -ZXh0cmFjdA== 24396 -IGV4ZWN1dA== 24397 -fScu 24398 -dXNlcmRhdGE= 24399 -IGZvY3VzZXM= 24400 -LW1pbnV0ZQ== 24401 -NzY0 24402 -IFB1Ymxpc2g= 24403 -b2dv 24404 -IG1vdW50YWlucw== 24405 -Qm90 24406 -fT57 24407 -IHRlbnNpb24= 24408 -cm9k 24409 -bWVzaA== 24410 -IHRyYW5zZm9ybWVk 24411 -LFI= 24412 -KCl9Cg== 24413 -Lmxvbmc= 24414 -IGdvcmdlb3Vz 24415 -IFNjaGVkdWxl 24416 -IG9sZGVzdA== 24417 -IHN1YnByb2Nlc3M= 24418 -KElO 24419 -eWVjdA== 24420 -IENvb3Blcg== 24421 -YXJuZXNz 24422 -IE1vbml0b3I= 24423 -LnBhcnQ= 24424 -OTcy 24425 -IE5CQw== 24426 -NjY4 24427 -IGNvdHRvbg== 24428 -IGhvbA== 24429 -NzI2 24430 -IHJnYmE= 24431 -IEJpbw== 24432 -Q29udGludWU= 24433 -UG9k 24434 -IHBhcnRpY2lwYXRpbmc= 24435 -Y2x1c2lvbnM= 24436 -KEJ5VmFs 24437 -NzM0 24438 -w6w= 24439 -IEhPVw== 24440 -X3NldG9wdA== 24441 -IGFjY29tcGFueWluZw== 24442 -MDkx 24443 -YXRvbg== 24444 -IC9c 24445 -IEF1dGhlbnRpY2F0aW9u 24446 -acOpbg== 24447 -IEJhcmFjaw== 24448 -Lyou 24449 -IGVhZ2Vy 24450 -IENhbmNlbA== 24451 -PGxlbW1h 24452 -ZXBo 24453 -CXdpbmRvdw== 24454 -IGluY2lkZW50cw== 24455 -NzU2 24456 -KSwo 24457 -LkRlcw== 24458 -aWJl 24459 -IEZ1bmN0aW9ucw== 24460 -IGhvc3BpdGFscw== 24461 -MDM4 24462 -IG94eWdlbg== 24463 -cm9vdFNjb3Bl 24464 -IGRyZXc= 24465 -CXJlcXVlc3Q= 24466 -bm90aWNl 24467 -YWt1 24468 -YW1lbnRz 24469 -ZmFy 24470 -OTcz 24471 -Nzc0 24472 -IHByZWNpc2U= 24473 -X3dyYXBwZXI= 24474 -IGxpc3RlbmVycw== 24475 -QVo= 24476 -LmJvdW5kcw== 24477 -IEF2ZXJhZ2U= 24478 -ZmllbGRzZXQ= 24479 -X2F4aXM= 24480 -IGV4YW1pbmF0aW9u 24481 -Jy4K 24482 -bW9ucw== 24483 -Kyspew0K 24484 -IEZvcm1z 24485 -7ZWc 24486 -OTE2 24487 -Q3BwTWV0aG9k 24488 -X3RyYWNl 24489 -IGVuZ2luZWVy 24490 -NjYz 24491 -IEZsYXQ= 24492 -IHJldmlzaW9u 24493 -IGhlYXRpbmc= 24494 -NjM4 24495 -L3Byb2ZpbGU= 24496 -LnJ1 24497 -cHJpb3JpdHk= 24498 -IGluZmVy 24499 -X1NUUkVBTQ== 24500 -ICopKA== 24501 -PiQ= 24502 -T0xFQU4= 24503 -T0tJRQ== 24504 -SUJJTElUWQ== 24505 -VUFHRQ== 24506 -IFN1cnZleQ== 24507 -MDcx 24508 -IHJlc2lnbg== 24509 -d2luZw== 24510 -IHNlY3JldHM= 24511 -IGNoaXBz 24512 -SlNPTk9iamVjdA== 24513 -RGVza3RvcA== 24514 -NTk2 24515 -X1NZTUJPTA== 24516 -KHJlc291cmNl 24517 -IDwvPgo= 24518 -IG5ld2VzdA== 24519 -dWxp 24520 -IGRlc2VydA== 24521 -IGRpcA== 24522 -IFBvdw== 24523 -IGVxdWF0aW9u 24524 -IHBvc3NpYmlsaXRpZXM= 24525 -IEZlZA== 24526 -b3NwaA== 24527 -IFsl 24528 -IGJ1YmJsZQ== 24529 -ZXRoZXJsYW5kcw== 24530 -Nzkz 24531 -IGNlbWVudA== 24532 -LmF1dG8= 24533 -X0FO 24534 -4oCZLg== 24535 -c2VsZWN0aW9u 24536 -IEJvbmQ= 24537 -OTg4 24538 -RGVu 24539 -LU8= 24540 -LmdldFR5cGU= 24541 -ODk2 24542 -LldpbmRvdw== 24543 -cHJlcw== 24544 -IHN3aW5nZXI= 24545 -In0pCg== 24546 -IHBpcA== 24547 -IG1pY2U= 24548 -IGNvbXBvdW5k 24549 -LXBsdWdpbg== 24550 -aWtv 24551 -IGNlbnR1cmllcw== 24552 -aWN1bGFy 24553 -LWlubGluZQ== 24554 -CWtleQ== 24555 -Plw8 24556 -RU5TSU9O 24557 -IFsNCg== 24558 -IHByZWNpc2VseQ== 24559 -IMOpdMOp 24560 -IFBhc3Q= 24561 -IENhbWJyaWRnZQ== 24562 -LWZ1bGw= 24563 -IGFuYWx5emU= 24564 -IFN0ZXZlbg== 24565 -IG5lbQ== 24566 -ZHVl 24567 -b3Jlbg== 24568 -IG11c2NsZXM= 24569 -aWppbmc= 24570 -ODUy 24571 -Ly0= 24572 -IEtlbm5lZHk= 24573 -NTk3 24574 -Uk0= 24575 -b3NzaWJsZQ== 24576 -IGFjdHJlc3M= 24577 -IGRvbG9y 24578 -OTE0 24579 -5b2V 24580 -TmVlZA== 24581 -LnRvZ2dsZQ== 24582 -IFJhY2U= 24583 -d2Vycw== 24584 -Lm1hdGVyaWFs 24585 -IER1ZQ== 24586 -IFBlbA== 24587 -I3ByaW50 24588 -IGluZGVwZW5kZW5jZQ== 24589 -ZXh1cw== 24590 -U2hhZG93 24591 -IGVuY29kZXI= 24592 -KGxldmVs 24593 -IFN3aWZ0 24594 -LmRvYw== 24595 -X3NlbGVjdGlvbg== 24596 -OTUy 24597 -IHNlcmlhbFZlcnNpb25VSUQ= 24598 -OTQ1 24599 -TGFiZWxz 24600 -IHBlcmZvcm1hbmNlcw== 24601 -LlRhZw== 24602 -IE5ITA== 24603 -aXplbg== 24604 -L1VJS2l0 24605 -OTkx 24606 -X0NPTlRST0w= 24607 -IGVhcm5pbmdz 24608 -OTc1 24609 -IEFsdA== 24610 -X0hBTkRMRQ== 24611 -Q3R4 24612 -IHBlcnN1 24613 -IHRyYW4= 24614 -56g= 24615 -X0NIQU5ORUw= 24616 -IHNhdGlzZmFjdGlvbg== 24617 -IEdQ 24618 -NzY5 24619 -aW94 24620 -bWl0dA== 24621 -bGFuZG8= 24622 -IHBpZw== 24623 -aW5hbHM= 24624 -w6puY2lh 24625 -NzMx 24626 -U3VyZmFjZQ== 24627 -IFVVSUQ= 24628 -IGJlbmVmaWNpYWw= 24629 -IHNlcXVlbmNlcw== 24630 -CW1lbXNldA== 24631 -IG1hZ2ljYWw= 24632 -wqs= 24633 -IHdvcm4= 24634 -QVND 24635 -cG9wdXA= 24636 -Q09NUA== 24637 -X2JlZm9yZQ== 24638 -ZW5lc3M= 24639 -VWk= 24640 -TGVz 24641 -LnJlcXVpcmU= 24642 -LlNlcmlhbGl6YWJsZQ== 24643 -YWRkR2Fw 24644 -IGF1dGhvcml6YXRpb24= 24645 -MDg1 24646 -LnB5cGxvdA== 24647 -dXJyYXk= 24648 -bGF0aXR1ZGU= 24649 -ODQ1 24650 -ZnJhbWVz 24651 -YWpz 24652 -IGNvbXBhc3M= 24653 -IG9ic2VydmF0aW9ucw== 24654 -X3N1cA== 24655 -LmVudmlyb24= 24656 -IHRyaXBsZQ== 24657 -IFJ1Ynk= 24658 -IGRyYWlu 24659 -X0ZJTFRFUg== 24660 -U2Fu 24661 -VU1Q 24662 -TnVsbEV4Y2VwdGlvbg== 24663 -IEdhYg== 24664 -b3dl 24665 -IFR1cmtpc2g= 24666 -X3NlcXVlbmNl 24667 -IEdyYW50 24668 -dWVsYQ== 24669 -IHdv 24670 -IGN1YmU= 24671 -aXE= 24672 -IGRpc29yZGVycw== 24673 -IGV4dHJhb3JkaW5hcnk= 24674 -IGN0cmw= 24675 -IFNlcQ== 24676 -ZW50cg== 24677 -ODY1 24678 -IHNhbmN0aW9ucw== 24679 -OTQ5 24680 -dXRzY2g= 24681 -UmVwb3J0cw== 24682 -IGluaGVyaXQ= 24683 -UGVyaW9k 24684 -IHBob3RvZ3JhcGh5 24685 -IEZyYW1ld29yaw== 24686 -IHNwZWNpYWxpc3Q= 24687 -ID8KCg== 24688 -X3NlbGVjdGVk 24689 -LlBsYXllcg== 24690 -IGFsbG9jYXRpb24= 24691 -KGFjY291bnQ= 24692 -IHN0cnVjdHVyYWw= 24693 -dmFibGU= 24694 -LW9mZnNldA== 24695 -LkFwcENvbXBhdEFjdGl2aXR5 24696 -0LDQvA== 24697 -LkFkZFdpdGhWYWx1ZQ== 24698 -IGljb25z 24699 -IHNodXRkb3du 24700 -X2xvdw== 24701 -IENvbXBhcmU= 24702 -IENl 24703 -PWhlYWQ= 24704 -bGFt 24705 -LnByZWRpY3Q= 24706 -X0RFQw== 24707 -IFNsZWVw 24708 -IEdyYXRpcw== 24709 -IHN1Z2dlc3Rpb24= 24710 -IERFTA== 24711 -Y2FmZg== 24712 -YXZpcnVz 24713 -Tm90aGluZw== 24714 -nos= 24715 -IHdpZGVzcHJlYWQ= 24716 -IG1lY2hhbmlzbXM= 24717 -IHRleHRBbGlnbg== 24718 -b2NjdXA= 24719 -IFJhaWw= 24720 -Ok5T 24721 -IGZpYmVy 24722 -IG1r 24723 -IHZpbnRhZ2U= 24724 -LWxvbmc= 24725 -LnJlZHVjZQ== 24726 -LkVudGl0aWVz 24727 -KHJlY29yZA== 24728 -IHBsZWFzYW50 24729 -RlJJTkc= 24730 -LkNlbGxz 24731 -T1RU 24732 -CWVsc2VpZg== 24733 -NjQ5 24734 -NzI0 24735 -X2NvbmZpcm0= 24736 -IFZpZXdHcm91cA== 24737 -c3lt 24738 -IHByYXk= 24739 -IHN1c3BlY3RlZA== 24740 -Q29udGFpbnM= 24741 -OTgz 24742 -IGJvcmRlcnM= 24743 -IGNvbXBvbmVudERpZA== 24744 -QVNTRVJU 24745 -IGluZmluaXRl 24746 -LW9yZGVy 24747 -IGhlbGxv 24748 -IEdyYWRl 24749 -LmN1cnJlbnRUaW1lTWlsbGlz 24750 -YXBvbGlz 24751 -emg= 24752 -CU9iamVjdA== 24753 -Olxc 24754 -SE8= 24755 -dmFsdWF0aW9u 24756 -IHZvY2Fi 24757 -NzE5 24758 -IGNvdXBvbg== 24759 -YXRhYmFzZXM= 24760 -LkdldFR5cGU= 24761 -TGVhcm4= 24762 -Nzky 24763 -XT0i 24764 -IEdhcnk= 24765 -b3RpdmU= 24766 -IGFzaA== 24767 -IGJpYg== 24768 -WFhYWA== 24769 -IGJhbGFuY2Vk 24770 -VkFMVUU= 24771 -IE5hdA== 24772 -X0Fk 24773 -PEU= 24774 -5Yy6 24775 -IE1ldGhvZEluZm8= 24776 -ODk3 24777 -TElC 24778 -IGNvbnNpZGVyYWJsZQ== 24779 -IEluZHVzdHJ5 24780 -dGVzdHM= 24781 -LnNldFRpdGxl 24782 -IEJsdWV0b290aA== 24783 -IG1hcHBlZA== 24784 -IEJydWNl 24785 -IE1haW5XaW5kb3c= 24786 -CXN0YXR1cw== 24787 -IHJheg== 24788 -IE1hbmQ= 24789 -IGNsYXNzaWZpY2F0aW9u 24790 -UGVybWlzc2lvbnM= 24791 -OTY5 24792 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 24793 -IGNvbnRhaW5lcnM= 24794 -OnNldA== 24795 -X3htbA== 24796 -IHdoaWxzdA== 24797 -VGhyb3VnaA== 24798 -IHZhbGlnbg== 24799 -IHdvcmxkcw== 24800 -Q09SRA== 24801 -RURJQQ== 24802 -0YDQvtCy 24803 -IHNwYXJl 24804 -IEhhZA== 24805 -IERFRg== 24806 -KHB0cg== 24807 -IHdhcm1pbmc= 24808 -ODk4 24809 -4KS+ 24810 -IGNvbnNlbnN1cw== 24811 -YWduZQ== 24812 -Q1RM 24813 -IOyV 24814 -Lk1haW4= 24815 -d2ViRWxlbWVudA== 24816 -IHBpc3Q= 24817 -Rmxhc2g= 24818 -QXBwZW5k 24819 -LnR3aW1n 24820 -VGFw 24821 -IHZlZ2V0YWJsZXM= 24822 -YWxn 24823 -MDU4 24824 -LnNhbXBsZQ== 24825 -IGNvYWNoaW5n 24826 -KGluZA== 24827 -Q2VsbFZhbHVl 24828 -Q2hlY2tCb3g= 24829 -IEhlbGw= 24830 -Uk9PVA== 24831 -Nzk2 24832 -IHN0YWRpdW0= 24833 -IGludmVzdGlnYXRpbmc= 24834 -KSU= 24835 -c3RlZA== 24836 -OTY1 24837 -IFdyaXRpbmc= 24838 -IOqy 24839 -IHVubw== 24840 -IHt7LS0= 24841 -IGNvb3Jkcw== 24842 -IHVuc2Vy 24843 -b3JnYW5pemF0aW9u 24844 -IENyaW1l 24845 -IERlbW9jcmF0 24846 -NTc5 24847 -IHZpbg== 24848 -L2ZpbGU= 24849 -MDc4 24850 -LWFwaQ== 24851 -IEF5 24852 -IGZ1bmRlZA== 24853 -IEJyZXhpdA== 24854 -IEdo 24855 -ZW50aW5h 24856 -Y2FzZXM= 24857 -IGRhc2g= 24858 -ICEhfQo= 24859 -SEk= 24860 -T2ZmaWNl 24861 -IGNhcHRhaW4= 24862 -IHdvcnNoaXA= 24863 -XEM= 24864 -NzMz 24865 -ODUx 24866 -IGdsb2Jl 24867 -X2JvYXJk 24868 -IGJhYmllcw== 24869 -ODc2 24870 -IGNvbnNlY3V0aXZl 24871 -IGVuaGFuY2Vk 24872 -ZXJldW0= 24873 -IEFkdmlz 24874 -IGdyYWlu 24875 -Nzcx 24876 -IGNyYXc= 24877 -YW5jZWxsYXRpb25Ub2tlbg== 24878 -LmFscGhh 24879 -X1dJVEg= 24880 -IE90dA== 24881 -IENvb2w= 24882 -LmJhdGNo 24883 -IHZlcmlmaWVk 24884 -KGNhbGxiYWNr 24885 -IHJlZ2FyZHM= 24886 -Njgz 24887 -IEludFB0cg== 24888 -b3VjaGVy 24889 -IGtpbg== 24890 -IHRvdWNoZWQ= 24891 -aXTDoA== 24892 -YXRob24= 24893 -IGFkamFjZW50 24894 -IGFjY29tcGFuaWVk 24895 -TEVBUg== 24896 -IGltcGxpZXM= 24897 -IGhpbGw= 24898 -IEJhbHRpbW9yZQ== 24899 -PSIt 24900 -RmluYWxseQ== 24901 -ODgz 24902 -U2Ft 24903 -aWNvcHQ= 24904 -IHNvZA== 24905 -IG1hag== 24906 -IFNoaXBwaW5n 24907 -IGdldEFsbA== 24908 -IGNvYWNoZXM= 24909 -IGRvbmF0aW9ucw== 24910 -aWxvdA== 24911 -IFRhcg== 24912 -Y2Vycg== 24913 -IGJhZGdl 24914 -IG1hcmtlcnM= 24915 -IFJhbmQ= 24916 -YWlzZWQ= 24917 -aXNzYW5jZQ== 24918 -IGV4cGxvcmluZw== 24919 -ODI3 24920 -dWNlZA== 24921 -IEluZG9uZXNpYQ== 24922 -IGJlbmVhdGg= 24923 -IG1hZ25ldGlj 24924 -IG11c2V1bQ== 24925 -bWF0Y2hDb25kaXRpb24= 24926 -IGRpc3J1cHQ= 24927 -IHJlbWluZA== 24928 -IFRN 24929 -IC8+PA== 24930 -IGZvb2w= 24931 -IGVzaw== 24932 -Lk51bGw= 24933 -IERpZXM= 24934 -X09VVFBVVA== 24935 -X1RZUEVE 24936 -IHBhaW50ZWQ= 24937 -Njcz 24938 -NzM1 24939 -IHNvcGhpc3RpYw== 24940 -IEJlYXI= 24941 -Km4= 24942 -X1BBQ0s= 24943 -IGRlbGl2ZXJpbmc= 24944 -IENPVU5U 24945 -5Y2V 24946 -IGplZw== 24947 -LWNhcg== 24948 -Zm5hbWU= 24949 -IHJhbmdpbmc= 24950 -ODQ4 24951 -IE5lZw== 24952 -LyoqKioqKi8= 24953 -IENIQVI= 24954 -IHVsdHJh 24955 -R3JhZA== 24956 -PXQ= 24957 -IGp1ZGdlcw== 24958 -IERpc2U= 24959 -YW5uZXJz 24960 -OTg1 24961 -ODkx 24962 -ODYx 24963 -IHNjYWw= 24964 -X2NhbA== 24965 -IENPTk5FQ1RJT04= 24966 -X2VtYmVk 24967 -KGZu 24968 -IENyYWZ0 24969 -MDQ3 24970 -IFBhcw== 24971 -IiktPg== 24972 -LmNvbnZlcnQ= 24973 -LnJlc291cmNl 24974 -IFNUQVRVUw== 24975 -w7RuZw== 24976 -IFRpdA== 24977 -IGNsYXNzcm9vbQ== 24978 -IEFyY2hpdGVjdA== 24979 -IEtpbmdz 24980 -IHN0ZWFkeQ== 24981 -LyohCg== 24982 -IEdlbmU= 24983 -KSI7Cg== 24984 -aWNpYQ== 24985 -c3Rhbg== 24986 -IENvbnN0cnVjdGlvbg== 24987 -dW1wZXI= 24988 -OTUx 24989 -d2M= 24990 -IENCUw== 24991 -aW5naW5n 24992 -LXBhcnR5 24993 -KGRyaXZlcg== 24994 -TUFSSw== 24995 -MDgy 24996 -IG5lc3RlZA== 24997 -ZXdhcmQ= 24998 -IGRlcGVuZGVuY3k= 24999 -IG1hbGVz 25000 -OTI4 25001 -IE9ORQ== 25002 -IFByb2R1Y3Rpb24= 25003 -XVsk 25004 -44O844M= 25005 -X0xPQUQ= 25006 -IEJvbA== 25007 -ZWxyeQ== 25008 -ODMx 25009 -oOmZpA== 25010 -IFJlcXVpcmU= 25011 -IHBsYWNpbmc= 25012 -eHh4 25013 -Q0FMRQ== 25014 -IHRodW1i 25015 -ODI0 25016 -Q2hvb3Nl 25017 -IHByb3RvdHlwZQ== 25018 -Vk9JRA== 25019 -IGxlc2JpYW4= 25020 -NzQx 25021 -IHRyYWl0cw== 25022 -U2hhcnA= 25023 -IGNvbnN1bWU= 25024 -VHJ1dGg= 25025 -IGFjdGlvblBlcmZvcm1lZA== 25026 -IEVudmlyb25tZW50YWw= 25027 -IERlYW4= 25028 -IGVzdGFkbw== 25029 -c2FtZQ== 25030 -IG51bWVyaWM= 25031 -IHRyYW5zaXQ= 25032 -LkVtYWls 25033 -LXNpZGU= 25034 -X1JVTg== 25035 -IFZpbGxhZ2U= 25036 -X09QRU4= 25037 -6KY= 25038 -LnJlbQ== 25039 -LXdhcm5pbmc= 25040 -YW55YQ== 25041 -UHJvcGVydHlDaGFuZ2Vk 25042 -ICghXw== 25043 -KGNoZWNr 25044 -aWxpYQ== 25045 -IFNvZnQ= 25046 -c3RlcHM= 25047 -IE1hZHJpZA== 25048 -TWVtb3J5V2FybmluZw== 25049 -IGhhbmRsZXJz 25050 -IGV4cGVyaWVuY2luZw== 25051 -IGluc3BlY3Q= 25052 -YnV0dG9ucw== 25053 -UmVjZWl2ZU1lbW9yeVdhcm5pbmc= 25054 -Y2hlbXk= 25055 -TGlua3M= 25056 -IHVybGxpYg== 25057 -LlN5c3RlbUNvbG9ycw== 25058 -IEVpZ2Vu 25059 -IHB1bmlzaG1lbnQ= 25060 -OlVJQ29udHJvbA== 25061 -YmFyYQ== 25062 -LXNldA== 25063 -IH0NCg0KDQo= 25064 -IHRvbGVyYW5jZQ== 25065 -IGludGVyZmFjZXM= 25066 -LnJlZGlyZWN0 25067 -aWdoYm9ycw== 25068 -Y3NyZg== 25069 -X2JhY2tncm91bmQ= 25070 -LlV0aWxz 25071 -X0hU 25072 -Njky 25073 -IEludGVyZXN0 25074 -aW1vcw== 25075 -IGdyYW50cw== 25076 -MDgz 25077 -IGV4YW1pbmVk 25078 -0JQ= 25079 -IGNm 25080 -Zm9yZ2U= 25081 -YmFja3M= 25082 -IE9iamVjdHM= 25083 -X3NlbnQ= 25084 -LmVudHJ5 25085 -IFRIRU4= 25086 -ZWxsaWRv 25087 -Y2lh 25088 -LHJlcw== 25089 -NjU5 25090 -Njgx 25091 -L3N0ZGM= 25092 -Lm5k 25093 -KEludA== 25094 -IEF1dGhvcnM= 25095 -IEFwcENvbXBhdEFjdGl2aXR5 25096 -J3s= 25097 -IG1lZGk= 25098 -TXVzaWM= 25099 -aWdt 25100 -Y2VpcHQ= 25101 -IGF1c3M= 25102 -IHRhcmdldGluZw== 25103 -IEtleXM= 25104 -aG4= 25105 -Ol0K 25106 -IG1pbmVyYWw= 25107 -w64= 25108 -LmNh 25109 -NzYx 25110 -b21lZA== 25111 -IHNoZWV0cw== 25112 -IGNhbWI= 25113 -IGRlYWRseQ== 25114 -LmluamVjdA== 25115 -KHVuaXQ= 25116 -IFNlbGVjdGlvbg== 25117 -Lmdtcw== 25118 -KGNvbm5lY3Rpb24= 25119 -ICQoIg== 25120 -w6ltb24= 25121 -IEN1cnJlbnRseQ== 25122 -cHRl 25123 -X3BhdGhz 25124 -ODQ3 25125 -bGVhZg== 25126 -IGltcGxpY2F0aW9ucw== 25127 -cG9zYWw= 25128 -5L2N 25129 -Wy8= 25130 -YW5jaWE= 25131 -6Zs= 25132 -bXVs 25133 -Y2ll 25134 -IGdlaWxl 25135 -Njc5 25136 -aW1hbHM= 25137 -VUlWaWV3 25138 -IHN1cnJl 25139 -c2VyaWFsaXpl 25140 -SVNP 25141 -IGFyYml0cmFyeQ== 25142 -IHNvY2thZGRy 25143 -LmZu 25144 -IE1lcmM= 25145 -IGNhc3Rpbmc= 25146 -S2V5RG93bg== 25147 -IG5ld1ZhbHVl 25148 -b3BlbnM= 25149 -NzE3 25150 -VG9kbw== 25151 -IGZsZXhpYmlsaXR5 25152 -CQkJCSAg 25153 -VmVsb2NpdHk= 25154 -w7pu 25155 -cm93aW5n 25156 -IGNvbXB1dGVk 25157 -YCkK 25158 -c3RhdGVtZW50 25159 -IHJp 25160 -X2NhcnQ= 25161 -TG93 25162 -dHJhbnNmZXI= 25163 -Lm5hdg== 25164 -IGdyYXZl 25165 -IERvb3I= 25166 -CWFsZXJ0 25167 -Njkx 25168 -Njk4 25169 -LnN1YnNjcmliZQ== 25170 -LXByb2ZpbGU= 25171 -CWJhc2U= 25172 -IOKIkg== 25173 -X18KCg== 25174 -IGVuZ2luZWVycw== 25175 -IGV4cGxvc2lvbg== 25176 -IGRhcmk= 25177 -Njgy 25178 -CUxvZw== 25179 -b25hbA== 25180 -IGlzb2xhdGVk 25181 -e2k= 25182 -IE1zZw== 25183 -RnV0dXJl 25184 -IHJhY2lzdA== 25185 -LXdyYXA= 25186 -IFZlcnM= 25187 -Ym9yZw== 25188 -SVNJT04= 25189 -INGA0LDQ 25190 -IFlhbg== 25191 -ODM2 25192 -aW5pdFdpdGg= 25193 -IG5vbWlu 25194 -KGVtcHR5 25195 -w61u 25196 -44Kk 25197 -CXdpZHRo 25198 -IGNoYW1iZXI= 25199 -L2FqYXg= 25200 -RU1Q 25201 -MDkz 25202 -IG5lY2Vz 25203 -aXZvcw== 25204 -bG9naWM= 25205 -Kikm 25206 -Y3JpcHRz 25207 -OTc2 25208 -Um93QXQ= 25209 -MDUz 25210 -aWJsaW5ncw== 25211 -IGVhcnM= 25212 -IGNvbXB1dGluZw== 25213 -IG1ha2Vy 25214 -IE5laXRoZXI= 25215 -YnJlYWRjcnVtYg== 25216 -IHNlcmlhbGl6ZQ== 25217 -IFdpdGhpbg== 25218 -IGRlbGw= 25219 -X1RSQUNF 25220 -MDky 25221 -PWE= 25222 -IHdpc2hlcw== 25223 -LWluY2g= 25224 -IERvcg== 25225 -IGlubm9jZW50 25226 -IERvbA== 25227 -IGludGVucw== 25228 -Zm9yY2Vk 25229 -MDU0 25230 -IEJJVA== 25231 -IHBob3RvZ3JhcGhz 25232 -IGNhc2E= 25233 -IExlbg== 25234 -XEZyYW1ld29yaw== 25235 -LlNpbXBsZQ== 25236 -IGRlYXI= 25237 -ODk1 25238 -KS8o 25239 -aXBwaQ== 25240 -IG93bnM= 25241 -UGxheWVycw== 25242 -IHByb3Bvc2Fscw== 25243 -LnBp 25244 -dXNhbGVt 25245 -RGFtYWdl 25246 -IGNhbG9yaWVz 25247 -IENyZWF0aXZl 25248 -IFsk 25249 -IC8vDQo= 25250 -Nzg2 25251 -QW5kVmlldw== 25252 -w6htZQ== 25253 -LmN1c3RvbQ== 25254 -X2ZhY3Rvcnk= 25255 -Y29tbWFuZHM= 25256 -X2xvb2s= 25257 -IHN0cmNtcA== 25258 -WU4= 25259 -YWlyZWQ= 25260 -IGF1ZGl0 25261 -0L7RgdGC 25262 -IFJldmVyc2U= 25263 -cm9wcmlhdGU= 25264 -ZXRpY3M= 25265 -PHZlY3Rvcg== 25266 -LnNlbGVuaXVt 25267 -Lm9y 25268 -IHByZWRpY2F0ZQ== 25269 -IGZpbmlzaGluZw== 25270 -IGtsZQ== 25271 -IFJlcG9z 25272 -IEtoYW4= 25273 -IE1ha2luZw== 25274 -IEZT 25275 -IHB1dGU= 25276 -CXN0YXRl 25277 -X1NVUFBPUlQ= 25278 -Jy0= 25279 -b3JpZW50YXRpb24= 25280 -IGV4aXN0ZWQ= 25281 -YXR1cmE= 25282 -IGV4cGVjdHM= 25283 -IFNoYWRvdw== 25284 -OTY2 25285 -IG9yZ2FuaXo= 25286 -5Z6L 25287 -IHN1c3BlbnNpb24= 25288 -NjY5 25289 -IHVpdA== 25290 -IHNpbXVsdGFuZW91c2x5 25291 -IEFmZmVybw== 25292 -OiIpOwo= 25293 -IHJvY2tldA== 25294 -Y2Fz 25295 -ZXRlcm1pbmU= 25296 -YWNldXQ= 25297 -Njkz 25298 -eGw= 25299 -IEFNRA== 25300 -KGdyYXBo 25301 -NzU4 25302 -ODcy 25303 -YXNzb2Np 25304 -X0NS 25305 -LmFyYW5nZQ== 25306 -MDQ5 25307 -KGpMYWJlbA== 25308 -IGJlZWY= 25309 -UXVpY2s= 25310 -LmNhcmQ= 25311 -XSk6 25312 -LWdy 25313 -Nzk3 25314 -LkdPTkU= 25315 -X0NMT1NF 25316 -IE5ldg== 25317 -w61hcw== 25318 -IHN0ZXBwZWQ= 25319 -IEZyZWVkb20= 25320 -IFdS 25321 -TlNBcnJheQ== 25322 -X3J4 25323 -X2RpYWxvZw== 25324 -IGhvdGVscw== 25325 -OTUz 25326 -IChcPA== 25327 -IERpYW1vbmQ= 25328 -IGFzc3VtcHRpb24= 25329 -dW1p 25330 -KGl0ZW1z 25331 -DQ0NCg== 25332 -5rOV 25333 -IG5lbA== 25334 -Qm9va3M= 25335 -5Y6/ 25336 -dXNi 25337 -IEZJTg== 25338 -ODgx 25339 -5qw= 25340 -IGNvcnBvcmF0aW9ucw== 25341 -VVNB 25342 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 25343 -OTI5 25344 -LnByb3BlcnR5 25345 -ZXdpc2U= 25346 -X3Bsb3Q= 25347 -Ij4nOwo= 25348 -IHBlcHBlcg== 25349 -OTg5 25350 -IHNoZWQ= 25351 -IE1lZGl1bQ== 25352 -IENvb2tpZQ== 25353 -ODg5 25354 -IG92ZXJzZWFz 25355 -ZWRvcg== 25356 -YXN1cmVtZW50 25357 -NzY2 25358 -5a2Y 25359 -ICcuJw== 25360 -IHBocA== 25361 -IFBST0M= 25362 -IGV4Y2VwdGlvbmFs 25363 -KHRo 25364 -IEpldA== 25365 -IG9jY3VwaWVk 25366 -LnNldEltYWdl 25367 -IFJlbGF0ZWQ= 25368 -dWNrZXI= 25369 -TWVtYmVycw== 25370 -UFJJTlQ= 25371 -IEdsbw== 25372 -X1ZJRVc= 25373 -fSIsCg== 25374 -IGFkb3B0aW9u 25375 -W10pCg== 25376 -ODQy 25377 -IE1pc3NvdXJp 25378 -IExpbmNvbG4= 25379 -ZXJhbGQ= 25380 -UG9wdXA= 25381 -IGZhdGU= 25382 -LWJvb3RzdHJhcA== 25383 -ZmVjdGlvbnM= 25384 -IFBvbGw= 25385 -X0FSR1M= 25386 -aW5hbmNl 25387 -Njk3 25388 -LWhvbWU= 25389 -Liks 25390 -X2RvbmU= 25391 -Njk0 25392 -OgoKCg== 25393 -IGRpc2N1c3Npbmc= 25394 -IFNRTEV4Y2VwdGlvbg== 25395 -IGVsZWN0cm8= 25396 -CXJlcQ== 25397 -IHp3 25398 -ODg2 25399 -IGx1aQ== 25400 -OTMy 25401 -IG92ZXJuaWdodA== 25402 -JHVzZXI= 25403 -IFdBWQ== 25404 -IGFsbGVyZw== 25405 -IGRpc2FwcG9pbnRlZA== 25406 -IHJhZGlhdGlvbg== 25407 -IGltcHJlc3NlZA== 25408 -aWZpY2F0ZXM= 25409 -IHRvYg== 25410 -Q0xBU1M= 25411 -IGN1ZGE= 25412 -X2RldA== 25413 -LXBvc3Q= 25414 -dWx1 25415 -VHJhbnNsYXRpb24= 25416 -LWhhbmQ= 25417 -LnllYXI= 25418 -IE1vbmdv 25419 -IHVuY2xlYXI= 25420 -LmVuZ2luZQ== 25421 -V0VCUEFDSw== 25422 -cmljZXM= 25423 -X0FDQ0VTUw== 25424 -IGhvbGlkYXlz 25425 -cGVyY2VudA== 25426 -LklkZW50aXR5 25427 -IEdvdg== 25428 -IHBhc3Npb25hdGU= 25429 -ISEu 25430 -IEdyZWVjZQ== 25431 -cGx1c3BsdXM= 25432 -JykpOw== 25433 -R1A= 25434 -IGV4Y2l0 25435 -LnRhYlBhZ2U= 25436 -X2NvbmQ= 25437 -IHNwb25zb3I= 25438 -TU9EVUxF 25439 -X3Byb2M= 25440 -ICQK 25441 -IHJhdGlvbmFs 25442 -LlRvb2w= 25443 -IGlocg== 25444 -Y2Nh 25445 -5ZOB 25446 -IEVzdGF0ZQ== 25447 -SUJVVEU= 25448 -QWN0aW9uUGVyZm9ybWVk 25449 -IFNvbGFy 25450 -poI= 25451 -IGVxdWl0eQ== 25452 -dGlk 25453 -OTM4 25454 -IHJlY2lw 25455 -LnNpbXBsZQ== 25456 -bWs= 25457 -Njg5 25458 -IEx1a2U= 25459 -IEd1YXJkaWFu 25460 -IGVuY3J5cHRlZA== 25461 -IGRvbWluYW50 25462 -LnBsYWNl 25463 -IE5W 25464 -ODM5 25465 -IHRvbmd1ZQ== 25466 -KEdldA== 25467 -IHN0YWlubGVzcw== 25468 -LlBsYXk= 25469 -IGVi 25470 -YWNp 25471 -LmJ1ZmZlcg== 25472 -cmVhZGNydW1icw== 25473 -IHZhY2NpbmU= 25474 -cHJvbQ== 25475 -OTc5 25476 -IHVzZXJJbmZv 25477 -IHNsdWc= 25478 -U2VyaWFsaXplZE5hbWU= 25479 -LXdpZGU= 25480 -IHJlYWN0aW9ucw== 25481 -IFlhbmc= 25482 -IEFkZHM= 25483 -KHVzZXJJZA== 25484 -IHBsYXRlcw== 25485 -IE1FTQ== 25486 -IGJhaWw= 25487 -SW5zaWRl 25488 -ZXRlZA== 25489 -IGVsc2lm 25490 -IHNha2U= 25491 -IGN5Y2xlcw== 25492 -IOyX 25493 -CUk= 25494 -LWNvbGxhcHNl 25495 -ODQx 25496 -IEdNVA== 25497 -ODE0 25498 -RGVjbGFyYXRpb24= 25499 -IGdyb3M= 25500 -IHJlYWNoZXM= 25501 -IGN1c3RvZHk= 25502 -VW50aWw= 25503 -NzUz 25504 -ODU2 25505 -dHU= 25506 -IENoZW4= 25507 -IG54 25508 -KGFkZHI= 25509 -IE9mZmVy 25510 -IGNvbGxlZw== 25511 -YXNzYWRvcg== 25512 -Njc0 25513 -IG1hcHBlcg== 25514 -ODU0 25515 -IFNJR05BTA== 25516 -IEJsb29t 25517 -IEhvbGw= 25518 -IEltcGVy 25519 -LWRlcw== 25520 -X3NpdGU= 25521 -UHJvYw== 25522 -RXF1 25523 -IGF0b21pYw== 25524 -IFdvbWFu 25525 -c2VudA== 25526 -NzM4 25527 -ODE3 25528 -c2Nhcg== 25529 -IGludGVsbGlnZW50 25530 -IEdldHRpbmc= 25531 -IFJlZ2lzdHJhdGlvbg== 25532 -IFBoaWxs 25533 -IGtpbGxlcg== 25534 -dW5pY29kZQ== 25535 -CgkJCg== 25536 -IEphY29i 25537 -IENvbnN0 25538 -IGxvY2F0ZQ== 25539 -IGNhdXM= 25540 -NzQ5 25541 -IFNjaG9sYXI= 25542 -IGNvbnN0aXR1dGlvbmFs 25543 -IGluZmxhdGlvbg== 25544 -IEdvdA== 25545 -PWFycmF5 25546 -ZW5kdW0= 25547 -IHRyYW5zbGF0ZWQ= 25548 -IGRpdm9yY2U= 25549 -RW50cmllcw== 25550 -IHNvcg== 25551 -IFF1b3Rl 25552 -aXJsaW5lcw== 25553 -VUs= 25554 -IGV4Y2Vs 25555 -KG9wdA== 25556 -IEFEVg== 25557 -LDos 25558 -IGNvbnRhY3RlZA== 25559 -NzQy 25560 -IERB 25561 -IHJpbmdz 25562 -IEluZHVzdHJpYWw= 25563 -LmdldENvbnRleHQ= 25564 -IGZvcmdvdHRlbg== 25565 -IFRhbg== 25566 -IHBhbnRz 25567 -IG92 25568 -IGRlY29kZXI= 25569 -IFBhcnRpYWw= 25570 -IHZj 25571 -IGJhdHRsZXM= 25572 -QXJpYWw= 25573 -RlJJTkdFTUVOVA== 25574 -aXJhdGVz 25575 -LHc= 25576 -YWludGVuYW5jZQ== 25577 -IE9k 25578 -IFRlY2hub2xvZ2llcw== 25579 -5YmN 25580 -IENhcnRlcg== 25581 -LmZpbmRBbGw= 25582 -Tm9tZQ== 25583 -QmVu 25584 -IFVzYWdl 25585 -IFBpY3R1cmU= 25586 -IGJhZGx5 25587 -X3BhbmVs 25588 -IHBhdGVudA== 25589 -IFByb3RvY29s 25590 -bG90dGU= 25591 -CXBsYXllcg== 25592 -amVjdGlvbnM= 25593 -NzQ2 25594 -IGRvdQ== 25595 -X3JlbGVhc2U= 25596 -dXJuaXR1cmU= 25597 -X3RheA== 25598 -IEZpZWxkcw== 25599 -LmRhdGFzZXQ= 25600 -X21hc3Rlcg== 25601 -Q0xVREU= 25602 -IFBoYXJt 25603 -YnN0 25604 -IG9wZXJhdGlvbmFs 25605 -LmNlbGw= 25606 -IGlkZW50aWZ5aW5n 25607 -IGp3dA== 25608 -dHVwbGU= 25609 -IFRD 25610 -IENybw== 25611 -OTM2 25612 -aXhtYXA= 25613 -LWNvbXBvbmVudHM= 25614 -Z2VuZXJhbA== 25615 -IG96 25616 -X0Rl 25617 -X2RvdWJsZQ== 25618 -IFRvbw== 25619 -MDg4 25620 -LlZpZXdHcm91cA== 25621 -ODc5 25622 -Z2F0ZQ== 25623 -ZGluZ3M= 25624 -cGhvdG9z 25625 -IGdyYW5kZQ== 25626 -b2xsZWN0 25627 -X2xpbg== 25628 -IGF3ZnVs 25629 -ZmlsdGVycw== 25630 -IGFsdGVybmF0ZQ== 25631 -ZXNw 25632 -IGNvbXByZXNz 25633 -ZW8= 25634 -IFNjYWxl 25635 -IGluZGlyZWN0 25636 -IGludm9pY2U= 25637 -CgoKCgoKCgoKCgoKCgoKCg== 25638 -U3RhcnRpbmc= 25639 -IFBsYXllcnM= 25640 -aWVsZQ== 25641 -LnRoZW4= 25642 -OTgx 25643 -T3Jk 25644 -IFR1cGxl 25645 -IGJvdXQ= 25646 -IFN0YXRpc3RpY3M= 25647 -UHJldmlldw== 25648 -IHB1enpsZQ== 25649 -IFdpZHRo 25650 -U1RBVEU= 25651 -IG92ZXJsYXk= 25652 -CW9u 25653 -IGluZnI= 25654 -IHNtYWxsZXN0 25655 -bG9ja2Vk 25656 -0YLQvg== 25657 -c3Ns 25658 -Nzc5 25659 -IGRlZW1lZA== 25660 -IHNjbw== 25661 -cmVjaw== 25662 -IGpCdXR0b24= 25663 -IG1pc3Npb25z 25664 -ODcx 25665 -56ew 25666 -LlNlbGVjdGVkSW5kZXg= 25667 -VEFCTEU= 25668 -U2VwdA== 25669 -IGFja25vd2xlZGdl 25670 -IHN0cnRvdGltZQ== 25671 -IFRlbGw= 25672 -IERhaw== 25673 -IGFsdW1pbnVt 25674 -IGZlbmNl 25675 -IFN0YXJz 25676 -Q09ORklH 25677 -IHJldHJvZml0 25678 -IGVtcGhhc2lz 25679 -L2hlYWRlcg== 25680 -IFNvbWV0aGluZw== 25681 -aW5pc2hlZA== 25682 -PSciLiQ= 25683 -IFZhbGlkYXRvcnM= 25684 -IHBvbGFy 25685 -c2VjdGlvbnM= 25686 -OTQ0 25687 -LmFzcHg= 25688 -IGFzcGly 25689 -Lk1vY2s= 25690 -Q29kZUdlbg== 25691 -IHBldXQ= 25692 -OTcx 25693 -IGFjY2VwdGluZw== 25694 -IGJhY2tpbmc= 25695 -UGljdHVyZQ== 25696 -L2Fw 25697 -0LXQsw== 25698 -X1NFQw== 25699 -LXVzZQ== 25700 -YW5ub3RhdGlvbg== 25701 -IGNvZ25pdGl2ZQ== 25702 -IGdyaXA= 25703 -aG91cg== 25704 -IExlZ2Fs 25705 -IGVwaWM= 25706 -LnRvb2xTdHJpcA== 25707 -Lm5vdGlmeQ== 25708 -Lkxhc3Q= 25709 -T1JJWg== 25710 -TWlkZGxld2FyZQ== 25711 -Y3JpcHRpb25z 25712 -bGFzaA== 25713 -X0ZPVU5E 25714 -IExpdmVycG9vbA== 25715 -IHt9Iiw= 25716 -OTMx 25717 -SW5zdGFsbA== 25718 -IG5pdA== 25719 -IGZpZ3VyZWQ= 25720 -W2xlbg== 25721 -Lldpbg== 25722 -LnBsYXRmb3Jt 25723 -ODUz 25724 -IGdhbWJsaW5n 25725 -KGR0 25726 -YXZlcnk= 25727 -CWluY2x1ZGU= 25728 -V2hldGhlcg== 25729 -Um91dGluZw== 25730 -IHRoZXJhcA== 25731 -UmVtb3Rl 25732 -IExvc3M= 25733 -eWxs 25734 -IGFwcHJvYWNoZWQ= 25735 -IFZlaGljbGU= 25736 -IEFscGhh 25737 -IHZvY8Oq 25738 -YW5zd2Vycw== 25739 -TlNEaWN0aW9uYXJ5 25740 -OTU0 25741 -Y29uc2lkZXI= 25742 -dW51c2Vk 25743 -IEZhbg== 25744 -b3JhYmxl 25745 -ZnJl 25746 -ODcz 25747 -IERJU0NMQUlN 25748 -IEFjdG9y 25749 -Ll0= 25750 -dG9IYXZl 25751 -LnVzZXJJZA== 25752 -IHNwZWVkcw== 25753 -ZXdheQ== 25754 -IHJlY3Vycw== 25755 -INCz 25756 -X3ByaXY= 25757 -IeKAnQoK 25758 -Q2hvaWNl 25759 -IHNldHRsZQ== 25760 -IHBsYW5lcw== 25761 -J30s 25762 -VG9t 25763 -SVRFUg== 25764 -ISIK 25765 -5bs= 25766 -YWNoZWxvcg== 25767 -IHNlcGFyYXRpb24= 25768 -IGRhbA== 25769 -YWRq 25770 -IHJlZ2lzdGVycw== 25771 -cml6 25772 -IE5vdGljZQ== 25773 -IGx1 25774 -IGNvdXJhZ2U= 25775 -IGF4ZXM= 25776 -Y2VsbGVudA== 25777 -LmFzeW5j 25778 -MDcz 25779 -IGNvbXBhdGliaWxpdHk= 25780 -56s= 25781 -ICEKCg== 25782 -CXRpdGxl 25783 -WUxF 25784 -CW1lc3NhZ2U= 25785 -VVVJRA== 25786 -T0xERVI= 25787 -IEhI 25788 -IFN0eWxlU2hlZXQ= 25789 -IGFjY2Vzc2Vk 25790 -LnZhbGlkYXRpb24= 25791 -dGFza3M= 25792 -IHBvbGx1dGlvbg== 25793 -LmNhbnZhcw== 25794 -IGluZ3JlZGllbnQ= 25795 -IENhYmlu 25796 -QWg= 25797 -b2xkb3du 25798 -IE5PSQ== 25799 -IMOX 25800 -W2Y= 25801 -ZWR1Yw== 25802 -eWFsdHk= 25803 -KG5vdA== 25804 -X1N0YXRl 25805 -OTMz 25806 -YW1lbg== 25807 -Nzk1 25808 -NzM5 25809 -IGRhbw== 25810 -dWRhZA== 25811 -ZWxsZXJz 25812 -fSY= 25813 -bGljaXR5 25814 -X1dJTkRPVw== 25815 -IHRhdHRv 25816 -dmFsb3I= 25817 -LlJhbmdl 25818 -IHJlZmVyZW5jZWQ= 25819 -IFJlc2VydmU= 25820 -TW9uZXk= 25821 -ODc0 25822 -U0NSSVBU 25823 -L3Byb2R1Y3Q= 25824 -Y2hvaWNlcw== 25825 -IHRpbg== 25826 -44KT 25827 -OTE4 25828 -IHNlcGFyYXRvcg== 25829 -IHBrZw== 25830 -YW1tZWQ= 25831 -IE1BVA== 25832 -ISEKCg== 25833 -IHJhaWQ= 25834 -IG1vdGl2YXRpb24= 25835 -IFhQ 25836 -IEJhY2tncm91bmQ= 25837 -IFF1YXRlcm5pb24= 25838 -LmRlZmluZVByb3BlcnR5 25839 -aWtlcg== 25840 -CXBhcmVudA== 25841 -IE9yaWdpbmFsbHk= 25842 -YW50YWdl 25843 -IEhhbnM= 25844 -IHRpbWVsaW5l 25845 -LmN1cg== 25846 -b3BpYw== 25847 -IFNlcXU= 25848 -bXVzdA== 25849 -IENvYWw= 25850 -IGZvcm1hdHRlcg== 25851 -X1JHQg== 25852 -IF8oIg== 25853 -J30pLAo= 25854 -ID09PT09PT09PT09PT09PT09 25855 -IEZVTkNUSU9O 25856 -IGxuZw== 25857 -aWNhdGVz 25858 -bGl2ZQ== 25859 -X2VuZ2luZQ== 25860 -IHRvd25z 25861 -ODY4 25862 -JykpCgo= 25863 -IFBL 25864 -KGFwaQ== 25865 -CXNjYW5m 25866 -MDg5 25867 -cGFja2V0 25868 -LnBob25l 25869 -4YA= 25870 -IEFuZHk= 25871 -X05BTUVT 25872 -OTgy 25873 -UExZ 25874 -OTU1 25875 -IG1pbnM= 25876 -aW1p 25877 -IGJyaWNr 25878 -IGJsYWRl 25879 -LnN0ZG91dA== 25880 -fWA7Cg== 25881 -U2hpZnQ= 25882 -CXNi 25883 -IENoZWNrcw== 25884 -IHBoZW5vbWVub24= 25885 -QXZhdGFy 25886 -IG1pbmlzdHJ5 25887 -cm9zZQ== 25888 -CUZpbGU= 25889 -ODc4 25890 -IHRpdGxlZA== 25891 -KExPRw== 25892 -IGdhbg== 25893 -ZGVzaWdu 25894 -KCksDQo= 25895 -IGJvbmVz 25896 -c3Rt 25897 -xZvEhw== 25898 -IElucHV0U3RyZWFt 25899 -IHZvbHVudA== 25900 -IFNlcmlhbGl6YWJsZQ== 25901 -IGZpZ2h0ZXI= 25902 -IERyYWc= 25903 -VHdpdHRlcg== 25904 -IHN1YnNpZA== 25905 -57w= 25906 -IGZvcnVtcw== 25907 -LmxvYWRpbmc= 25908 -bG9nZ2Vk 25909 -X3RoaXM= 25910 -IHRlcnJhaW4= 25911 -IGlycmU= 25912 -IEluZw== 25913 -IENO 25914 -X29iamVjdHM= 25915 -LnVpZA== 25916 -IGNvbnNjaW91c25lc3M= 25917 -VElOR1M= 25918 -IEdhbGw= 25919 -IHBvcnRyYXk= 25920 -MDU2 25921 -IERldmVsb3Blcg== 25922 -IHBhcnRpY2lwYW50 25923 -ICI7DQo= 25924 -L21vZGVs 25925 -Nzk0 25926 -IE9wZXJhdGlvbnM= 25927 -Xlw= 25928 -IExhdGVy 25929 -IHJhaXNlcw== 25930 -LW5vbmU= 25931 -Lm1ldGE= 25932 -PScuJA== 25933 -RmluaXNoZWQ= 25934 -IHJlcGxhY2luZw== 25935 -IHNhbXBsaW5n 25936 -IEplbg== 25937 -IlRoZXJl 25938 -UkVBTA== 25939 -QUxF 25940 -7Iqk 25941 -T3JkZXJz 25942 -X3BhcmFtZXRlcg== 25943 -IE9seW1waWM= 25944 -IHRyw6hz 25945 -IGFyZW5h 25946 -aW9s 25947 -Oz8+ 25948 -IGltcGFjdHM= 25949 -IFdT 25950 -OmdldA== 25951 -IGZsaWdodHM= 25952 -IFJ1c3NlbGw= 25953 -Y2FtZXJh 25954 -Rm4= 25955 -c2lnbWE= 25956 -IGZvcmNpbmc= 25957 -IGxvY2Fscw== 25958 -IGRlcGFydHVyZQ== 25959 -IGNlbGVicmF0aW9u 25960 -IFNheQ== 25961 -ODg0 25962 -77yS 25963 -IEhpbGxz 25964 -Lmhhc093blByb3BlcnR5 25965 -IHR5cGluZ3M= 25966 -LkFQSQ== 25967 -IGRvbmF0aW9u 25968 -T3BlcmF0aW9uRXhjZXB0aW9u 25969 -LkFjdGl2aXR5 25970 -Y3BsdXNwbHVz 25971 -IENoYXJsaWU= 25972 -IGltcG9ydGVk 25973 -IGRhbm4= 25974 -IG9jY2FzaW9ucw== 25975 -IGltcGxlbWVudGluZw== 25976 -IHB1cnBsZQ== 25977 -LmRpYWxvZw== 25978 -U1FMRXhjZXB0aW9u 25979 -ZXJubw== 25980 -IHdhcnM= 25981 -IHBhc3Rl 25982 -IGRlY3JlYXNlZA== 25983 -IGhhcnNo 25984 -IGVsYWJvcg== 25985 -aW5wdXRz 25986 -IFZpZXdz 25987 -IGVycm9yTWVzc2FnZQ== 25988 -X211bA== 25989 -CXdyaXRl 25990 -IENvcA== 25991 -IEFubnVhbA== 25992 -KGJ1dHRvbg== 25993 -IHZpZGE= 25994 -YmFycw== 25995 -IEhhcnZhcmQ= 25996 -CWV4cGVjdA== 25997 -IGluZGV4ZXM= 25998 -IGRvY3VtZW50YXJ5 25999 -IGZsZXNo 26000 -T1JMRA== 26001 -IERlbHRh 26002 -TUFORA== 26003 -QnJ1c2g= 26004 -LWNvbHVtbg== 26005 -IGRldmVsb3BtZW50cw== 26006 -OTc0 26007 -Nzgz 26008 -bWV0aG9kVmlzaXRvcg== 26009 -c2xpY2U= 26010 -IFBETw== 26011 -IGludmVzdGluZw== 26012 -ODY3 26013 -aXJhYmxl 26014 -IHhtbG5z 26015 -77yb 26016 -YXJ0YQ== 26017 -IHRoZW9yaWVz 26018 -X2NpdHk= 26019 -ICRfXw== 26020 -Q3JlYXRpbmc= 26021 -KHBy 26022 -RHJvcGRvd24= 26023 -aXNtYXRjaA== 26024 -IE5FVA== 26025 -OTI2 26026 -J10pKXsK 26027 -IFZhbHVlcw== 26028 -IFNFTw== 26029 -IFNUQVQ= 26030 -IGVjb3N5c3RlbQ== 26031 -IHRlbXB0 26032 -IFxc 26033 -IC8vewo= 26034 -IENocmlzdG9waGVy 26035 -IEtlbnR1Y2t5 26036 -IEh0dHBTZXJ2bGV0UmVzcG9uc2U= 26037 -IGh5YnJpZA== 26038 -eW9u 26039 -IGZlZWRpbmc= 26040 -IEV4dHJh 26041 -Tm9ybQ== 26042 -SVRDSA== 26043 -IFNlYW4= 26044 -IFVwbG9hZA== 26045 -bXVu 26046 -cHVy 26047 -IHBlcnNpc3RlbnQ= 26048 -IElEQw== 26049 -IFBlcmZvcm0= 26050 -ODYz 26051 -Lm1lcmdl 26052 -X3Jvb20= 26053 -TWVhbndoaWxl 26054 -IT0n 26055 -IFdlbA== 26056 -QXJnc0NvbnN0cnVjdG9y 26057 -ODg3 26058 -LkRhdGFiYXNl 26059 -IGNvdW50aW5n 26060 -KCkq 26061 -lOWbng== 26062 -IFRPUA== 26063 -bWlsbA== 26064 -IERU 26065 -SUdORUQ= 26066 -OTU2 26067 -IEtC 26068 -IGNvbXBseQ== 26069 -U291dGg= 26070 -X2NvbGxlY3Rpb24= 26071 -Q2hhcHRlcg== 26072 -IGV4cGxhaW5pbmc= 26073 -X0FN 26074 -X3Rz 26075 -Y2FyZHM= 26076 -IHF1ZWw= 26077 -IHBvbGU= 26078 -IHRvdWNoZG93bg== 26079 -IE90aGVycw== 26080 -IHBlZXJz 26081 -IFR5cGVFcnJvcg== 26082 -NzYz 26083 -IHNpeHRo 26084 -IGNoZWVy 26085 -IGRpc3B1dGU= 26086 -OTYz 26087 -ODkz 26088 -dXNj 26089 -KV0s 26090 -dGh1bWI= 26091 -IGhpZGluZw== 26092 -IFNJRw== 26093 -bGlrZXM= 26094 -IFBBR0U= 26095 -LlJlZmxlY3Rpb24= 26096 -IGhlYWRxdWFydGVycw== 26097 -VElORw== 26098 -IEdob3N0 26099 -TUxF 26100 -JAo= 26101 -IGNvbnRyYXJ5 26102 -ZXh0ZW5k 26103 -J10pLg== 26104 -RkZFQ1Q= 26105 -IFBpbnRlcmVzdA== 26106 -w7ptZXJv 26107 -cmljYW5l 26108 -CXNlc3Npb24= 26109 -IGNyeXN0YWw= 26110 -LUNvbnRyb2w= 26111 -b3Zlcm5tZW50 26112 -b2dyYWY= 26113 -OTYx 26114 -LWFjdGlvbg== 26115 -dm9sdW1l 26116 -ZnRlbg== 26117 -IHVuY29u 26118 -IGFuaW1hdGU= 26119 -IGxlYXNl 26120 -c2Ny 26121 -IHJlZnVzZQ== 26122 -44CL 26123 -ZnRw 26124 -aW5mb3JtYXRpb24= 26125 -IGV2YWx1YXRlZA== 26126 -IGluamVjdGlvbg== 26127 -IGphY2s= 26128 -IHdvcmtzaG9w 26129 -5rOo 26130 -UFRI 26131 -IFRz 26132 -b2ZmZXI= 26133 -CW9z 26134 -IGtpbmdkb20= 26135 -TWlzc2luZw== 26136 -IGxhd21ha2Vycw== 26137 -ZXh0RmllbGQ= 26138 -IHNpbmdpbmc= 26139 -YWJp 26140 -L2NsaWVudA== 26141 -Lm1lZGlh 26142 -QVRFR09SWQ== 26143 -U2lnbmF0dXJl 26144 -JScsCg== 26145 -IEZ1Y2s= 26146 -XVs6 26147 -IHNlbnNvcnM= 26148 -L2NvbQ== 26149 -IFByaW1hcnk= 26150 -LlNRTA== 26151 -X3Byb2dyYW0= 26152 -IHBpbGxz 26153 -IGludGVncmFs 26154 -IGZsZWV0 26155 -IGRyb3BwaW5n 26156 -LnNs 26157 -QmVlbg== 26158 -IHBldHM= 26159 -IGFkdmlzZWQ= 26160 -IGRyYWdvbg== 26161 -X0VESVQ= 26162 -KGlt 26163 -OTM5 26164 -RkVS 26165 -IERydWc= 26166 -KHJhbmRvbQ== 26167 -IGNvbXByZXNzaW9u 26168 -b3VzdA== 26169 -WyU= 26170 -IGJ1eWVy 26171 -aG9w 26172 -Um9sZXM= 26173 -bWFuYWdl 26174 -IHBhaW5mdWw= 26175 -IEJyYW5jaA== 26176 -LW1vZGFs 26177 -ZW5hbnQ= 26178 -IE1lc2g= 26179 -L2ZvbnQ= 26180 -IEdyYWhhbQ== 26181 -IOKY 26182 -IG5j 26183 -IEZyYW5jaXM= 26184 -IHNwZWNpZmljYXRpb24= 26185 -IGRhbWFnZXM= 26186 -LWNvbmZpZw== 26187 -IHRoZW9yZXQ= 26188 -c2VjdXJl 26189 -X211bHRp 26190 -YWNldXRpY2Fs 26191 -IGRlbWFuZGluZw== 26192 -ZW5uZQ== 26193 -SVNUUw== 26194 -MDk0 26195 -KCkpKTsKCg== 26196 -UmVhc29u 26197 -UmVjZW50 26198 -cGhhc2U= 26199 -IHBzeQ== 26200 -X01BTg== 26201 -IHZvbHVudGVlcg== 26202 -5b8= 26203 -aXN0cmlidXRlZA== 26204 -bGlv 26205 -IHByb2R1Y3Rpdml0eQ== 26206 -X2NvbW0= 26207 -U3ByaW5n 26208 -bmlz 26209 -LndlaWdodA== 26210 -IENhbmNlcg== 26211 -QWxsb2M= 26212 -IFR3ZWV0 26213 -IHNlcGFyYXRlbHk= 26214 -CWNoZWNr 26215 -X3Byb3BlcnRpZXM= 26216 -LlVuaXQ= 26217 -ODI5 26218 -X0NMSw== 26219 -IGd0 26220 -ICgpOwoK 26221 -IGhhbmR5 26222 -ODM0 26223 -IFRob21wc29u 26224 -IHVubmVjZXNzYXJ5 26225 -IFJlYWRlcg== 26226 -ODk0 26227 -R04= 26228 -PXJlcXVlc3Q= 26229 -IFV0aWxpdHk= 26230 -LlJlcG9zaXRvcnk= 26231 -IEF4 26232 -aHlkcg== 26233 -Nzkx 26234 -aWV1 26235 -IHRoeQ== 26236 -IGx0 26237 -X21haWw= 26238 -5L+u5pS5 26239 -YWlsYW5k 26240 -IFBoaWxpcA== 26241 -IGJpdHRlcg== 26242 -IGJldHRpbmc= 26243 -ODM3 26244 -IHRpbWVk 26245 -b2Nrcw== 26246 -MDc2 26247 -J2E= 26248 -IGFsZ29yaXRobXM= 26249 -IHJlaW50ZXJwcmV0 26250 -IHRvc3M= 26251 -cm9nZW4= 26252 -IGhvcGVk 26253 -KHNlbGVjdGVk 26254 -IHZlbnR1cmU= 26255 -VEVY 26256 -IExlYXZl 26257 -LlN1YnN0cmluZw== 26258 -IGdyYXRlZnVs 26259 -NzQz 26260 -dWth 26261 -IENvbnN1bWVy 26262 -IGFnZ3JlZw== 26263 -Q2lyY2xl 26264 -4LiB 26265 -X2Jsb2Nrcw== 26266 -IGxlZ2FsbHk= 26267 -ICJ8 26268 -44OD 26269 -LmJvYXJk 26270 -LkFi 26271 -RnVuY3Rpb25z 26272 -cmVjaXBl 26273 -6Ic= 26274 -IE94Zm9yZA== 26275 -IHdob2xlcw== 26276 -LkJ1aWxk 26277 -X2NoYW5nZWQ= 26278 -aGFp 26279 -IGRlcGFydG1lbnRz 26280 -OTY0 26281 -SW1w 26282 -IGNvYWxpdGlvbg== 26283 -SU5GUklOR0VNRU5U 26284 -IGVtcG93ZXI= 26285 -aXRjaGVz 26286 -Tm9ydGg= 26287 -IGluZmxhbW0= 26288 -T05TRQ== 26289 -IG1pc3NpbGU= 26290 -IFJhag== 26291 -IElzc3Vl 26292 -IGF0b2k= 26293 -Y2FsZWQ= 26294 -LkNvbnRyb2xsZXJz 26295 -IFdvbGY= 26296 -IGNydXNoZXJz 26297 -4buH 26298 -LkF1dGg= 26299 -LmFkZEF0dHJpYnV0ZQ== 26300 -aGlz 26301 -IGJvb3Rz 26302 -LmNsZWFu 26303 -Y2FtcA== 26304 -IHRlbmFudA== 26305 -IHR1bmU= 26306 -IHt9Jy4= 26307 -IHdvcmtvdXQ= 26308 -UmVwbw== 26309 -IHBhcnRpYWxseQ== 26310 -TUlTU0lPTg== 26311 -amFtaW4= 26312 -IFNC 26313 -IGRldGVybWluYXRpb24= 26314 -ICcnKTsK 26315 -IEJlbmc= 26316 -IHZvcw== 26317 -IGluaGFi 26318 -L2xhbmc= 26319 -c2J1cmdo 26320 -RXhlY3V0b3I= 26321 -aG9uZQ== 26322 -IENoYWxsZW5nZQ== 26323 -X2xpbmtz 26324 -LkxldmVs 26325 -IHVuZGVyZ3JvdW5k 26326 -LWNvZGU= 26327 -OTU5 26328 -IG9wdGltaXphdGlvbg== 26329 -bG9nZ2luZw== 26330 -X2Rlc3Q= 26331 -IHNuYWtl 26332 -IGNoZW1pY2Fscw== 26333 -X0lNUE9SVEVE 26334 -YWRvb3A= 26335 -IFRIQVQ= 26336 -bWFuYWdlZA== 26337 -IHJlZHVjZXM= 26338 -IFJFQUw= 26339 -IEd1eQ== 26340 -X0dFTkVSSUM= 26341 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 26342 -LmFtb3VudA== 26343 -IGRlcmU= 26344 -Z2V0VGltZQ== 26345 -IHBhbnQ= 26346 -YW5vbnltb3Vz 26347 -IGhhcm1vbnk= 26348 -IEFsYW4= 26349 -IHNjZW5hcmlvcw== 26350 -IGRpcnQ= 26351 -aHRhZ3M= 26352 -TWM= 26353 -U2hlbGw= 26354 -cmlu 26355 -ew0KDQo= 26356 -LnBvdw== 26357 -CWNsaWVudA== 26358 -IGNvbnNwaXJhY3k= 26359 -IGFkbWlzc2lvbg== 26360 -IFJlZ2lvbmFs 26361 -IFZpZXdDb250cm9sbGVy 26362 -IFBoaWxpcHBpbmVz 26363 -IGRlcG9z 26364 -IHBhcA== 26365 -OTYy 26366 -IFBhZA== 26367 -UGF1bA== 26368 -LkNvbWJvQm94 26369 -IHR1dG9y 26370 -IFJlY2lwZQ== 26371 -d3JpdGluZw== 26372 -IGNvbnRyaWJ1dG9y 26373 -T1RI 26374 -U21hbGw= 26375 -Vkk= 26376 -IGhhY2Vy 26377 -ZXF1 26378 -IEV4YW1wbGVz 26379 -aHVtYW4= 26380 -Lm1lc3NhZ2Vz 26381 -CXR5cA== 26382 -ICgNCg== 26383 -IFNTTA== 26384 -TEVO 26385 -IFJvbW5leQ== 26386 -KGdyaWQ= 26387 -CW1pbg== 26388 -ID4KCg== 26389 -IGZydWl0cw== 26390 -IHZvdGVy 26391 -SW5saW5l 26392 -cGFuZQ== 26393 -IENvbGxlY3Rpb25z 26394 -Y2hhcnNldA== 26395 -IHNwYW0= 26396 -emI= 26397 -aXRlbWFw 26398 -IHN1Y2NlZWRlZA== 26399 -X0NPTA== 26400 -IGVsYXBzZWQ= 26401 -aW1ldGVy 26402 -IHJlY292ZXJlZA== 26403 -VGVuc29y 26404 -aGF0dGFu 26405 -LnNldHVw 26406 -aXN0bw== 26407 -KGhlYWQ= 26408 -OTc3 26409 -IFNJWkU= 26410 -IHRhY3RpY3M= 26411 -IGRpc3R1cg== 26412 -IHByZXZhbA== 26413 -aWNpb3M= 26414 -KFZhbHVl 26415 -X2NvbHM= 26416 -IEZhdA== 26417 -IHNlYWw= 26418 -IHNvbnM= 26419 -IGVuc3VyZXM= 26420 -MDk1 26421 -IHByZXNzaW5n 26422 -PSY= 26423 -aWdlbm91cw== 26424 -IGhhcmFzc21lbnQ= 26425 -X0pTT04= 26426 -IGlnbm9y 26427 -eW5vbWlhbA== 26428 -b21lcg== 26429 -X3N0YXRpYw== 26430 -IHNpZ25pZmljYW5jZQ== 26431 -IGNpcmNsZXM= 26432 -X1N5c3RlbQ== 26433 -IGRpc2NpcGxpbmU= 26434 -IGRyZXNzZWQ= 26435 -IHNwaGVyZQ== 26436 -OTI3 26437 -IGNsaW1i 26438 -NzU5 26439 -X2FjdGlvbnM= 26440 -IEJhYg== 26441 -ICc9Jyw= 26442 -X3NjaGVtYQ== 26443 -InVzZQ== 26444 -IHVuZGVycw== 26445 -IGN1cHM= 26446 -LnNjcmVlbg== 26447 -L25ldw== 26448 -IGFwcGVhcmluZw== 26449 -VE9Q 26450 -dmlzZWQ= 26451 -Y2xhbmc= 26452 -IGludmVzdGlnYXRvcnM= 26453 -IG15c3RlcmlvdXM= 26454 -IHByb21pc2luZw== 26455 -IHF1YWxpZnk= 26456 -IGNhdmU= 26457 -IGVxdWlw 26458 -PXg= 26459 -R1Q= 26460 -KGxpbms= 26461 -LnZlbG9jaXR5 26462 -LmVyYXNl 26463 -b3Rlcg== 26464 -KysrKysrKys= 26465 -cHJvZml0 26466 -IHpvbmVz 26467 -X3VpZA== 26468 -LXNlcg== 26469 -IG9iamVjdGl2ZXM= 26470 -IG1pbGY= 26471 -d2Via2l0 26472 -KG1hdGNo 26473 -bmVo 26474 -IEFzc29jaWF0ZWQ= 26475 -IFRvZG8= 26476 -PWQ= 26477 -MDY1 26478 -Q2Ft 26479 -IHZvY2Fs 26480 -IHN1ZG8= 26481 -KEVY 26482 -IHRyb3U= 26483 -QUJD 26484 -LmJlYW4= 26485 -IEdyb3VuZA== 26486 -IFJFU1Q= 26487 -d2VldHM= 26488 -SW5n 26489 -aW1vbg== 26490 -OTQ2 26491 -X2J1cw== 26492 -IENPTE9S 26493 -dW50bw== 26494 -IGZvc3M= 26495 -IExpbmtz 26496 -ODY5 26497 -w6RuZw== 26498 -L2Zvcm1z 26499 -cHJpc2Vz 26500 -IGFjaGlldmVtZW50 26501 -Q0FMTA== 26502 -0LXQu9GM 26503 -IFZlcmlmeQ== 26504 -X1NPVVJDRQ== 26505 -YXB0Y2hh 26506 -SURE 26507 -X3JlZmVyZW5jZQ== 26508 -R29sZA== 26509 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo= 26510 -OTQ3 26511 -UmVjZWl2ZXI= 26512 -MDk5 26513 -IGFq 26514 -X2RpcmVjdGlvbg== 26515 -fV0= 26516 -IENvbXBldA== 26517 -IGJhbmc= 26518 -Nzk4 26519 -IENhc3M= 26520 -LXVybA== 26521 -dGVjaG4= 26522 -IEplcnVzYWxlbQ== 26523 -bG9uZ2l0dWRl 26524 -Jyk7DQoNCg== 26525 -IHdpbm5lcnM= 26526 -VGFza3M= 26527 -IERNQQ== 26528 -IHRvb2x0aXA= 26529 -jrc= 26530 -IEJyYQ== 26531 -X2R1cmF0aW9u 26532 -Y3VyeQ== 26533 -cGFyZW50cw== 26534 -LS0tLTwv 26535 -IHBhc3Nwb3J0 26536 -ODQ5 26537 -V0M= 26538 -INC7 26539 -Y2Vzc2lvbg== 26540 -IFllbGxvdw== 26541 -IGVuY3J5cHRpb24= 26542 -JwoKCg== 26543 -IGxpc3Rpbmdz 26544 -IENvbW11bmljYXRpb25z 26545 -Ll8K 26546 -ICIiIg0K 26547 -IGZi 26548 -IHN0cmljdGx5 26549 -IExpdGVy 26550 -IEVudGVycHJpc2U= 26551 -X2JvdHRvbQ== 26552 -QUtF 26553 -a2V0 26554 -IHRhbQ== 26555 -QmV0d2Vlbg== 26556 -X1RPUA== 26557 -RGlzYWJsZQ== 26558 -IGZpbGluZw== 26559 -IENocm9u 26560 -U0VRVQ== 26561 -ICZfX18= 26562 -ODQ2 26563 -IGZhbA== 26564 -IFNMT1Q= 26565 -RW1iZWQ= 26566 -dXRoZXI= 26567 -IFJlc3RhdXJhbnQ= 26568 -IHJlYWxpc3RpYw== 26569 -IScpOwo= 26570 -IERFQUw= 26571 -IFBlcmlvZA== 26572 -LmdldFg= 26573 -IHNlaHI= 26574 -Il0nKS4= 26575 -OTQz 26576 -ZXNzYQ== 26577 -CW1lbWNweQ== 26578 -IGFja25vd2xlZGdlZA== 26579 -c2VuYWw= 26580 -IFVuaXZlcnNhbA== 26581 -ICcnOwoK 26582 -L3dpa2k= 26583 -aWVubmU= 26584 -IE5TQXJyYXk= 26585 -IGFjY2VwdGFuY2U= 26586 -IGxpdmVy 26587 -IHRvb3Ro 26588 -IGFjY3Vz 26589 -CUxPRw== 26590 -dmFsdQ== 26591 -5YC8 26592 -IHNlY3RvcnM= 26593 -cGVyaW1lbnRhbA== 26594 -L2NsYXNz 26595 -X2dv 26596 -TWljaGFlbA== 26597 -b2xhdGlsZQ== 26598 -IFBST0Y= 26599 -IGNvbXByb20= 26600 -c3BlY2lhbGNoYXJz 26601 -IOKc 26602 -IGlzRXF1YWxUb1N0cmluZw== 26603 -IEh1bmc= 26604 -LmFzTGlzdA== 26605 -L2dv 26606 -Pj4o 26607 -IEtpcg== 26608 -IGludHJvcw== 26609 -IHNrZXRjaA== 26610 -IHNraWxsZWQ= 26611 -IGltbWVy 26612 -IGFkZXF1YXRl 26613 -X3JlcA== 26614 -KGhlYWRlcg== 26615 -X2xpa2U= 26616 -IHBlcmNlaXZlZA== 26617 -c3No 26618 -IGFzc3VtaW5n 26619 -IGZm 26620 -X3V1aWQ= 26621 -dWxhcw== 26622 -IGRlbW9jcmF0aWM= 26623 -LmVudGl0aWVz 26624 -U2VyaWVz 26625 -YXBob3Jl 26626 -IG5ld2Vy 26627 -fSg= 26628 -U0VD 26629 -YWlybw== 26630 -IGNvbW1vZA== 26631 -IHByaXZpbGVnZQ== 26632 -IGRldXg= 26633 -IEhvcA== 26634 -Licv 26635 -Y3RpYw== 26636 -Lic7Cg== 26637 -PD89 26638 -IFVU 26639 -ZXRpZXM= 26640 -X0NPTlRFTlQ= 26641 -LnJlbGVhc2U= 26642 -LmRpc21pc3M= 26643 -IGZj 26644 -b3VuZ2U= 26645 -cHdk 26646 -X3ByZXY= 26647 -TWdy 26648 -IEJ1ZmZlcmVkUmVhZGVy 26649 -d3JpdHRlbg== 26650 -IEVi 26651 -ICkKCgo= 26652 -dWl0bw== 26653 -IGNvbnRyb3ZlcnN5 26654 -IGRpc3Bvc2Vk 26655 -IGZvdG8= 26656 -TGlzdFZpZXc= 26657 -L2NyZWF0ZQ== 26658 -IENPTA== 26659 -Y29tbXVuaWM= 26660 -MDY4 26661 -IGZyZWVseQ== 26662 -dW5hbA== 26663 -b3ZpZA== 26664 -CXRy 26665 -cGFnaW5hdGlvbg== 26666 -IENvbW1vbnM= 26667 -RWxlbQ== 26668 -IFJFTQ== 26669 -IGNvcnJlbGF0aW9u 26670 -KCkrIg== 26671 -IEhpZGU= 26672 -YW5kaW5n 26673 -KHZlYw== 26674 -aXRvcw== 26675 -IEN1bHQ= 26676 -IG51dHJpdGlvbg== 26677 -dmFscw== 26678 -IGRldGVybWluaW5n 26679 -bG9yZA== 26680 -IHNjYW5kYWw= 26681 -IHNoYWxsb3c= 26682 -b2Rhc2g= 26683 -X3NlcmlhbA== 26684 -IFNsbw== 26685 -IGRpc3Bvbg== 26686 -UGxvdA== 26687 -aWNrbGU= 26688 -IGVsbA== 26689 -IHVuZW1wbG95bWVudA== 26690 -Rk0= 26691 -cm9ucw== 26692 -bMSx 26693 -TW8= 26694 -RXhpc3Q= 26695 -SURT 26696 -Q2hv 26697 -IEtleWJvYXJk 26698 -LnBhcnNlcg== 26699 -LkdldE9iamVjdA== 26700 -IHNwZWxscw== 26701 -IGdlc2No 26702 -IG1hZ25pdHVkZQ== 26703 -X1NM 26704 -aXNkaWN0aW9u 26705 -ICcpOwo= 26706 -aWxpYW5z 26707 -IHNoYXI= 26708 -IFByb2I= 26709 -dWlsdGlu 26710 -IHR1bm5lbA== 26711 -PkM= 26712 -IFdhcnJlbg== 26713 -IG9wdGltaXplcg== 26714 -IFNFUlZJQ0VT 26715 -X29wZXI= 26716 -Z2V0QXR0cmlidXRl 26717 -IE1jSw== 26718 -X3NlbGY= 26719 -MDg0 26720 -LnJz 26721 -IikKCgo= 26722 -R2V0Q29tcG9uZW50 26723 -ZXJjZQ== 26724 -IHRvdXM= 26725 -dW5pdHM= 26726 -J10pOw0K 26727 -Wm9vbQ== 26728 -L0U= 26729 -IG9ic2M= 26730 -IGZhc3Rlc3Q= 26731 -b25saW5l 26732 -IHBlYWNlZnVs 26733 -ZmZlbg== 26734 -IGNhcmdv 26735 -CXBy 26736 -IHNlZWtz 26737 -enU= 26738 -MDc0 26739 -VHJpbQ== 26740 -IHdhcmQ= 26741 -IHZlcmQ= 26742 -IGJsb2dz 26743 -LmV4Y2VwdGlvbnM= 26744 -IFByZW1pdW0= 26745 -IE5ldGhlcmxhbmRz 26746 -U2FmZQ== 26747 -RmluaXNo 26748 -IEFsYnVt 26749 -X0FDQw== 26750 -PXRoaXM= 26751 -dmlydHVhbA== 26752 -XT4= 26753 -X0xBQkVM 26754 -IE5pY2g= 26755 -X3dpbg== 26756 -IEFhcm9u 26757 -V1A= 26758 -OyQ= 26759 -YWltcw== 26760 -IEltYWdlVmlldw== 26761 -IGVuZGxlc3M= 26762 -RVJB 26763 -X0RJU0FCTEU= 26764 -IGNhbmNlbGxlZA== 26765 -LXVz 26766 -IGluc3BlY3Rpb24= 26767 -ZW1pbg== 26768 -IEdyZXk= 26769 -LW9wZW4= 26770 -IGl0ZXJhdGlvbnM= 26771 -Lm93bmVy 26772 -IGtlcmFz 26773 -LlBhc3N3b3Jk 26774 -IFJ5 26775 -IElOUw== 26776 -QWly 26777 -IFNldmVyYWw= 26778 -LlRhYlN0b3A= 26779 -SU5HTEU= 26780 -IEhhaXI= 26781 -IENhbnZhcw== 26782 -QUFBQQ== 26783 -IGZsYXc= 26784 -Y2VkZXM= 26785 -LlJlcG9ydA== 26786 -7Yo= 26787 -IFRpcHM= 26788 -Y3JpcHRvcnM= 26789 -LnRyYW5zYWN0aW9u 26790 -LlNwcmluZw== 26791 -IHZpZXdlcg== 26792 -IGluc2lnaHRz 26793 -6L6T 26794 -b3JkaW9u 26795 -VUlOVA== 26796 -c2Vlaw== 26797 -IEF1Zg== 26798 -7J6Q 26799 -IHN0cmFpbg== 26800 -VG9vbHRpcA== 26801 -IGR6 26802 -aWduYWw= 26803 -YWR0 26804 -IHVj 26805 -ZmluaXRl 26806 -IG5t 26807 -LmNtZA== 26808 -IE15U3Fs 26809 -W2RhdGE= 26810 -LmphY2tzb24= 26811 -LnRyZWU= 26812 -UmVxdWVzdFBhcmFt 26813 -X2FnZW50 26814 -IildDQo= 26815 -IGFzc2Fzcw== 26816 -KENvbnN0YW50cw== 26817 -OnNz 26818 -IE1BTg== 26819 -Ky0rLQ== 26820 -IEJvdHRvbQ== 26821 -cHJpbnRz 26822 -IFNhbWU= 26823 -QEF1dG93aXJlZA== 26824 -c3dhcA== 26825 -aWNpw7Nu 26826 -IHByb3Rlc3RlcnM= 26827 -IGhvbmV5 26828 -IFZldGVy 26829 -KENhbGVuZGFy 26830 -LWFk 26831 -IEJyb29rbHlu 26832 -TGlmZQ== 26833 -X1ZBUg== 26834 -emVjaA== 26835 -IENBTEw= 26836 -X0NBU1Q= 26837 -IEVsZWN0aW9u 26838 -IHRoaWNrbmVzcw== 26839 -VmVyeQ== 26840 -X0lOVEVHRVI= 26841 -LWRldg== 26842 -KSkpKQ== 26843 -YXBhdA== 26844 -b29vbw== 26845 -ZGVtbw== 26846 -IHBhcnNlRmxvYXQ= 26847 -IFJhdGhlcg== 26848 -U1RJVA== 26849 -bWFrZXI= 26850 -W2N1cnJlbnQ= 26851 -Y2hyb25v 26852 -IGNocmlzdA== 26853 -44Gq 26854 -IERldGFpbA== 26855 -xrDhuw== 26856 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 26857 -IHN1bA== 26858 -aWRlbmN5 26859 -UXVl 26860 -IGVsZWdhbnQ= 26861 -YXBvbnM= 26862 -IGRpc2hlcw== 26863 -IGludGVnZXJz 26864 -KHJlYWQ= 26865 -MDU3 26866 -ZmluZFZpZXdCeUlk 26867 -IEFtb3VudA== 26868 -IFNraXA= 26869 -IGhhYml0cw== 26870 -Kiko 26871 -IG1vbnN0ZXJz 26872 -TUFD 26873 -OmVuZA== 26874 -IGZyYW5r 26875 -QXNzZW1ibHk= 26876 -IGRmcw== 26877 -IG5ldXQ= 26878 -X1RZUEVT 26879 -ZXF1YWw= 26880 -bG95ZA== 26881 -KHVyaQ== 26882 -IGNoaQ== 26883 -IGRlZmVuZGFudA== 26884 -IGNvbmZsaWN0cw== 26885 -IHZpbA== 26886 -LWpz 26887 -IFBlYWNl 26888 -IG11dGFibGU= 26889 -KXNlbmRlcg== 26890 -IEZvY3Vz 26891 -5bu6 26892 -IGFwcHJlY2lhdGVk 26893 -c2xlZXA= 26894 -IFJFRA== 26895 -Q3VsdHVyZQ== 26896 -IGRlc2lnbmVycw== 26897 -X2dlbmVyYXRvcg== 26898 -Y29kZXM= 26899 -L2V4 26900 -LkdldFZhbHVl 26901 -dW1ibGVk 26902 -LnNjYWxhanM= 26903 -cGVyb3I= 26904 -IHZldGVyYW5z 26905 -IH0pDQo= 26906 -IHVuZm9ydHVuYXRlbHk= 26907 -X0NSRUFURQ== 26908 -TWFzcw== 26909 -IENMQUlN 26910 -IE1lZXQ= 26911 -X3N1cHBvcnQ= 26912 -QmFuaw== 26913 -KCkuCg== 26914 -RGFyaw== 26915 -X0xPVw== 26916 -IE1pbmluZw== 26917 -IE93bmVy 26918 -aWVyYQ== 26919 -Q2xpZW50ZQ== 26920 -IGVuY291cmFnaW5n 26921 -PlM= 26922 -IGJveWZyaWVuZA== 26923 -IEhhbGY= 26924 -IEFDQw== 26925 -QWZm 26926 -X2Fy 26927 -LWxpZmU= 26928 -Y3g= 26929 -LkpCdXR0b24= 26930 -aXphZG8= 26931 -Lnplcm8= 26932 -Lm9wZW5xYQ== 26933 -b3Rvbg== 26934 -LnRleHRDb250ZW50 26935 -IHRvbGw= 26936 -YXRpZQ== 26937 -IGJhbGxvdA== 26938 -LW51bWJlcg== 26939 -LkV4Y2VwdGlvbg== 26940 -CXBhcmFtcw== 26941 -Y2lyY2xl 26942 -LW1hcA== 26943 -IG5hcA== 26944 -IFJvYm90 26945 -IEljaA== 26946 -cmVnaXN0cmF0aW9u 26947 -QW1hem9u 26948 -cm9sbG1lbnQ= 26949 -KGV4cA== 26950 -IHRhbmtz 26951 -IEdvcmRvbg== 26952 -IG1hY2hpbmVyeQ== 26953 -IGJhc2VsaW5l 26954 -5os= 26955 -MDg2 26956 -2Kk= 26957 -IENvbnZlbnRpb24= 26958 -CWNvbmZpZw== 26959 -b29raWVz 26960 -bXVsdA== 26961 -UmVjb3Jkcw== 26962 -IEVTVA== 26963 -IGdhcmJhZ2U= 26964 -IGNvbmZvcm0= 26965 -aWRhbA== 26966 -IGJhcmc= 26967 -IHN1cnZpdmVk 26968 -IGludmVzdGlnYXRpb25z 26969 -OTM1 26970 -LmNvbnRhaW5zS2V5 26971 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0K 26972 -b3J0aW9u 26973 -IGhvcnI= 26974 -X2h0dHA= 26975 -IG1hbnQ= 26976 -XTsNCg0K 26977 -YmluYXJ5 26978 -OTQ4 26979 -ZW1wbA== 26980 -IGlucXVpcnk= 26981 -IE1lYW53aGlsZQ== 26982 -MDk4 26983 -IGNvbGxlY3Rpbmc= 26984 -LkVudGl0eUZyYW1ld29yaw== 26985 -IiwKCg== 26986 -IFBpYw== 26987 -QEluamVjdA== 26988 -aWNrbmVzcw== 26989 -IEJpbmRpbmc= 26990 -IGNvbnRyb2xsaW5n 26991 -cmV2ZXJzZQ== 26992 -IGNoYWlycw== 26993 -c2VtYmxlZA== 26994 -KGFkZA== 26995 -RGlzYWJsZWQ= 26996 -YW5hcw== 26997 -LnRyYW5zbGF0ZQ== 26998 -LS0tLS0tLS0tLS0K 26999 -IHJlZmxlY3RlZA== 27000 -Il0KCg== 27001 -RXh0ZXJuYWw= 27002 -QXJyb3c= 27003 -U2luZ2xldG9u 27004 -JXg= 27005 -IMU= 27006 -IGFuY2VzdA== 27007 -IE9ybGVhbnM= 27008 -CWNtZA== 27009 -IHByb2hpYml0ZWQ= 27010 -aXRobWV0aWM= 27011 -KGNoYW5uZWw= 27012 -X2Nzcw== 27013 -Rm9yd2FyZA== 27014 -LnNvY2tldA== 27015 -IGx1Yw== 27016 -4oY= 27017 -IEZpcmVmb3g= 27018 -IE1vdmllcw== 27019 -KV8= 27020 -LmVuZHM= 27021 -KHNoYXBl 27022 -IGRlYWx0 27023 -IHNhdmVz 27024 -IGdsb3J5 27025 -IG1lam9y 27026 -IGJyZWF0aGluZw== 27027 -IGVsbGVy 27028 -Z2V0RGF0YQ== 27029 -IGFuZ2xlcw== 27030 -IHRvb2xiYXI= 27031 -IHNwYWNpbmc= 27032 -MDU5 27033 -SVBT 27034 -IGZsb29ycw== 27035 -X0FDVElWRQ== 27036 -IHNodWZmbGU= 27037 -L3NoYXJlZA== 27038 -IEVsZQ== 27039 -ZWRpc2g= 27040 -IHdlYmNhbQ== 27041 -LmV4cGVjdA== 27042 -aWxvYw== 27043 -IEluY2x1ZGVz 27044 -IHR3ZWV0ZWQ= 27045 -IDop 27046 -IEVzc2F5 27047 -Rml4 27048 -LWJldHdlZW4= 27049 -X3dlYg== 27050 -LmNvbnY= 27051 -IHJhY2lzbQ== 27052 -IHJlZmxlY3Rz 27053 -dW1t 27054 -0LjRgtC1 27055 -X2Zvb3Rlcg== 27056 -L2RvY3M= 27057 -IFBvdXI= 27058 -TmdNb2R1bGU= 27059 -LmluaXRpYWxpemU= 27060 -cGF0dGVybnM= 27061 -X0lu 27062 -IEFiYg== 27063 -Kg0K 27064 -IHNlbnRpbWVudA== 27065 -YnVmZg== 27066 -X2NvdW50cw== 27067 -IHJldXNl 27068 -Y2h1bms= 27069 -IGltcG9zZWQ= 27070 -UHJpbWFyeUtleQ== 27071 -Rm9yZWdyb3VuZA== 27072 -IGNvbnN1bWVk 27073 -PyE= 27074 -IGRpY2s= 27075 -IGNocm9u 27076 -IEZlcm4= 27077 -IHJlc3BvbnNpdmU= 27078 -OTU4 27079 -IGluc2VjdA== 27080 -aWN1bHR5 27081 -IHJ3 27082 -IGFsaWtl 27083 -IHN1YnNldA== 27084 -IENvb2tpZXM= 27085 -IFBhaXI= 27086 -IHRpZXI= 27087 -SUZP 27088 -YXZvdXI= 27089 -IFFV 27090 -LHNpemVvZg== 27091 -IG1lcmdlZA== 27092 -bXY= 27093 -aXRvbA== 27094 -eWxvbg== 27095 -IGp1bXBlZA== 27096 -LnJvbGU= 27097 -ZW5zYWpl 27098 -UnVsZXM= 27099 -IGJyb3dzZQ== 27100 -QW5pbWF0b3I= 27101 -IHlvZ2E= 27102 -IHZhcmlhbnRz 27103 -IGNvdXJ0ZXN5 27104 -dXJhbg== 27105 -cGJz 27106 -ZWxzZWlm 27107 -QWx0 27108 -IExhbmU= 27109 -Q0xL 27110 -SU1BUlk= 27111 -X1BST1BFUlRZ 27112 -77yQ 27113 -IGNoYW4= 27114 -IGdyYWR1YWxseQ== 27115 -IHNoYWtl 27116 -IGJsb25kZQ== 27117 -Li4uIik7Cg== 27118 -LXNleA== 27119 -IGdhbWVwbGF5 27120 -YWNpZXM= 27121 -LnJlZnJlc2g= 27122 -VVNC 27123 -IFBsb3Q= 27124 -V2Fz 27125 -aXNzaXBwaQ== 27126 -IFRlbnNvcg== 27127 -IGNyeXB0b2N1cnJlbmN5 27128 -IGRpZmZpY3VsdGllcw== 27129 -RGVsZXRlZA== 27130 -V2l0aG91dA== 27131 -X2FwcGVuZA== 27132 -X3Zlcg== 27133 -OTY3 27134 -IikpDQo= 27135 -IGhvbmVzdGx5 27136 -IHBpdm90 27137 -IHRlbXBz 27138 -X3Bz 27139 -IFVubGlrZQ== 27140 -Wzot 27141 -VlM= 27142 -X2luZg== 27143 -IGp1bmlvcg== 27144 -IGFuaW1hdGlvbnM= 27145 -IGZpbGVwYXRo 27146 -Pzwv 27147 -W1w= 27148 -IG9wZXJhdGVz 27149 -X3JlZA== 27150 -IEJvb3RzdHJhcA== 27151 -bGVhZA== 27152 -ZWZmZWN0 27153 -wr0= 27154 -IFN0ZXI= 27155 -IEJ1Y2s= 27156 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 27157 -IGRlcHV0eQ== 27158 -VGhhbg== 27159 -4bq/ 27160 -T05FTlQ= 27161 -IEhlYXQ= 27162 -ZXRoZWxlc3M= 27163 -XSl7Cg== 27164 -IGtvc3Rlbmxvcw== 27165 -KCk7Ly8= 27166 -IGRlcGxveWVk 27167 -Pnt7JA== 27168 -IHVuaWNvZGU= 27169 -cGxhY2Vz 27170 -IENvZmZlZQ== 27171 -LlNF 27172 -IFBBUg== 27173 -KHR4dA== 27174 -Z2VicmE= 27175 -IGZpcmVz 27176 -TWFpbldpbmRvdw== 27177 -bWVkaXVt 27178 -ICjigJw= 27179 -IGxn 27180 -IGNtcA== 27181 -L2Jhc2U= 27182 -X2xheWVycw== 27183 -X2VudHJpZXM= 27184 -IGFkbWluaXN0ZXI= 27185 -IFNVQ0g= 27186 -QlA= 27187 -IFNjb3R0aXNo 27188 -CQ0KCQ0K 27189 -Z3VhcmQ= 27190 -IFN0cm9uZw== 27191 -SW5zbg== 27192 -IENBUA== 27193 -YXN1cnk= 27194 -IFNFRQ== 27195 -Q2xvY2s= 27196 -ZXJpZQ== 27197 -XG1vZGVscw== 27198 -ICQk 27199 -IENhYg== 27200 -IHd1cmRl 27201 -IHNvbGRpZXI= 27202 -IGNsaXBz 27203 -IGFycmFuZ2VtZW50 27204 -IFdvbmRlcg== 27205 -IEhvcm4= 27206 -IHNjYXJlZA== 27207 -IGN1cmU= 27208 -bWtkaXI= 27209 -IGFsaWduZWQ= 27210 -IFBpbms= 27211 -IGxhbmRlZA== 27212 -RGltZW5zaW9u 27213 -U2Nyb2xsUGFuZQ== 27214 -LmNoYXQ= 27215 -LldpdGg= 27216 -IFRyYWlu 27217 -XS4K 27218 -IHRoaXJ0eQ== 27219 -IGR1cmFibGU= 27220 -IGxk 27221 -IGxhdGVpbml0 27222 -IGNoYXJ0cw== 27223 -IGluc3VsdA== 27224 -LkZhdGFs 27225 -X2N0 27226 -IG1hc2tz 27227 -Q0xVREVE 27228 -UHJlc2lkZW50 27229 -IGNvbG91cnM= 27230 -Z21lbnRz 27231 -LmF0dHJpYnV0ZXM= 27232 -IEZsZXg= 27233 -IENsb2Nr 27234 -w61jdWw= 27235 -aW1lbg== 27236 -Sk8= 27237 -IFJlZ2V4 27238 -X0xJTks= 27239 -IGNvdWNo 27240 -IElOUFVU 27241 -IGJlYXRpbmc= 27242 -YnVzaW5lc3M= 27243 -cHJlY2Vk 27244 -LnVuaXQ= 27245 -IEZlbA== 27246 -TmV2ZXI= 27247 -b3NwZWw= 27248 -LnN0YXJ0c3dpdGg= 27249 -IEVQQQ== 27250 -Lm9ubHk= 27251 -IHByZXZlbnRpbmc= 27252 -eWVy 27253 -Q29sdW1uTmFtZQ== 27254 -IGVsZXZhdGlvbg== 27255 -Zmx1 27256 -aWN5Y2xl 27257 -IG9mZmxpbmU= 27258 -VG9vbGJhcg== 27259 -IGNvbXBldGluZw== 27260 -KV0u 27261 -IG1vZw== 27262 -IGlzVmFsaWQ= 27263 -QXNr 27264 -X2F2 27265 -X2xhdA== 27266 -QU5D 27267 -IEpvaA== 27268 -a2Vycw== 27269 -IGd1YXJkcw== 27270 -IGNoYWlucw== 27271 -IFNpbXBsZURhdGVGb3JtYXQ= 27272 -LnN0YXRpYw== 27273 -IHZlc3NlbA== 27274 -IG11ZA== 27275 -IHN0YWJpbA== 27276 -IHN0cmV0 27277 -Z20= 27278 -YW1hdGlvbg== 27279 -55w= 27280 -LXdpdGg= 27281 -IHJvcw== 27282 -X1BB 27283 -IHJlc3VsdGFkbw== 27284 -IGNvbmZpZGVudGlhbA== 27285 -IFRva3lv 27286 -CXVzaW5n 27287 -IE1hdGhm 27288 -b21iaW5l 27289 -IEVTUE4= 27290 -IGRlYWxlcnM= 27291 -IGRpc21pc3NlZA== 27292 -VFJZ 27293 -IHRlZW5z 27294 -cmVjb3Jkcw== 27295 -IHdpbmdz 27296 -Z2FsbGVyeQ== 27297 -YWNjb3VudHM= 27298 -X0xJQg== 27299 -IGphY2tldA== 27300 -IE5TT2JqZWN0 27301 -IHN0b25lcw== 27302 -IERlbGl2ZXJ5 27303 -IERpZXQ= 27304 -L3dhdGNo 27305 -IHRvaWxldA== 27306 -IEd1ZXN0 27307 -LmRheQ== 27308 -MDY3 27309 -IGludHZhbA== 27310 -MDg3 27311 -VmlzaXQ= 27312 -IGludmVzdGlnYXRlZA== 27313 -IHBlbnRydQ== 27314 -IFRoZWF0cmU= 27315 -YW5kaWRhdGVz 27316 -TGFuZw== 27317 -IFNlcnY= 27318 -IGNvbnRyb2xsZXJz 27319 -IHNldFRpdGxl 27320 -TlA= 27321 -YW15 27322 -ZmxhdA== 27323 -KHVp 27324 -MDY5 27325 -X2RvY3VtZW50 27326 -6IO9 27327 -IENvaW4= 27328 -IEFkYW1z 27329 -cHRpYw== 27330 -IHByb2R1Y3RpdmU= 27331 -IGFjY29tcGxpc2hlZA== 27332 -DQoNCg0KDQo= 27333 -IGRlZmVycmVk 27334 -aWVudGVz 27335 -IHNpbmM= 27336 -b2xhcnM= 27337 -UmlnaHRhcnJvdw== 27338 -IHZhcmlhdGlvbnM= 27339 -KG9mZnNldA== 27340 -OTU3 27341 -LkxheW91dEluZmxhdGVy 27342 -IHN1c3BlbmQ= 27343 -IHByZXZlbnRpb24= 27344 -X3ByaXZhdGU= 27345 -X2pz 27346 -4piF 27347 -IHdpZWRlcg== 27348 -YXR1bQ== 27349 -kow= 27350 -IGFwcGVhcmFuY2Vz 27351 -LkRvY3VtZW50 27352 -IHZhbGlkYXRlcw== 27353 -Y2FsZW5kYXI= 27354 -fSI7Cg== 27355 -LmRlbW8= 27356 -Y29udXQ= 27357 -IGNvcnJlY3Rpb24= 27358 -IERlYWw= 27359 -IGJhdHRlcmllcw== 27360 -LmR1cmF0aW9u 27361 -LFw= 27362 -X21hcmtlcg== 27363 -bXVsdGk= 27364 -IGhhbHQ= 27365 -IGNtcw== 27366 -IHNoYXBlZA== 27367 -QnJv 27368 -cmVkdWNl 27369 -ICMjIyM= 27370 -Q1RPUg== 27371 -IEJlbmVm 27372 -IGljb25pYw== 27373 -IHBpYW5v 27374 -IGVmZmVjdGl2ZW5lc3M= 27375 -fC4K 27376 -IGFqYXg= 27377 -IHZvbHVtZXM= 27378 -4Lih 27379 -IGNsanM= 27380 -ICAgICAgICAgICAgICAK 27381 -YXRocw== 27382 -cmFpdHM= 27383 -5aSn 27384 -0ZY= 27385 -X211bHQ= 27386 -IGZhc2NpbmF0aW5n 27387 -QXZlcmFnZQ== 27388 -IHByw6k= 27389 -IENoYWlybWFu 27390 -LmZpbmRFbGVtZW50 27391 -X3Bpbg== 27392 -IGNvbXBhcmluZw== 27393 -IGRhcmtuZXNz 27394 -LUZp 27395 -LXNlcnZlcg== 27396 -IHNlbGVjdGluZw== 27397 -c3RlcmRhbQ== 27398 -IFBhcnRz 27399 -Rk9STUFUSU9O 27400 -IG5vdGluZw== 27401 -IHBpbGU= 27402 -b2dz 27403 -IHBhbGV0dGU= 27404 -X2Rv 27405 -aXRpemU= 27406 -MDc5 27407 -KCko 27408 -IGRlZmluaW5n 27409 -IHJlbWFpbmRlcg== 27410 -VW5pdHM= 27411 -X1RBU0s= 27412 -SHR0cENsaWVudA== 27413 -U29jaWFs 27414 -IGZ1bmRyYQ== 27415 -TlI= 27416 -Y2hlc3Q= 27417 -Q3VycmVuY3k= 27418 -LmFkYXB0ZXI= 27419 -IGRvcA== 27420 -dW50aW5n 27421 -QU5HVUFHRQ== 27422 -Ikhl 27423 -CWluZGV4 27424 -X3BhY2thZ2U= 27425 -Lkljb24= 27426 -IHJlcGV0 27427 -bWFzcw== 27428 -PSIuJA== 27429 -IFN1ZA== 27430 -IGxpZA== 27431 -cHJvdmluY2U= 27432 -7Jw= 27433 -R1BJTw== 27434 -0Jo= 27435 -IE15U1FM 27436 -IGRvY3M= 27437 -IEdB 27438 -IGlwc3Vt 27439 -S2VybmVs 27440 -IGFjY2VwdHM= 27441 -IGZpdHRpbmc= 27442 -IGN1YW5kbw== 27443 -IGR1cGxpYw== 27444 -IEJyb3RoZXI= 27445 -IEtsZQ== 27446 -bnVtcw== 27447 -IG1vcnBo 27448 -ICMjIyMjIyMj 27449 -IENHUG9pbnQ= 27450 -PHVuc2lnbmVk 27451 -5L6L 27452 -IER1a2U= 27453 -LnNldEJvdW5kcw== 27454 -cXM= 27455 -b3JpYw== 27456 -amVy 27457 -IHJlZ2FyZGVk 27458 -SHR0cFJlcXVlc3Q= 27459 -IGJvbmRz 27460 -IHRob3JvdWdobHk= 27461 -ZW5jZW50 27462 -IGhpZ2hsaWdodGVk 27463 -IGFjcmVz 27464 -IHdvcmtwbGFjZQ== 27465 -IEx1eA== 27466 -IHF1b3Q= 27467 -OTg2 27468 -LmluZmxhdGU= 27469 -IGRvY3VtZW50ZWQ= 27470 -IGFkZGljdGlvbg== 27471 -IG11dGF0aW9u 27472 -LmNpdHk= 27473 -IGJvdHRsZXM= 27474 -IFJlcG9zaXRvcnk= 27475 -b25u 27476 -ZXJybm8= 27477 -QVJJQUJMRQ== 27478 -5bqm 27479 -X0JFR0lO 27480 -Z2xhcw== 27481 -J30pCg== 27482 -IE1hc3NhZ2U= 27483 -IFdoaXQ= 27484 -cmVnZXg= 27485 -V0E= 27486 -IG91dGxldA== 27487 -LWhlYWQ= 27488 -IGV4cGlyZWQ= 27489 -IFRoYWk= 27490 -L2luY2x1ZGU= 27491 -Z3JhZGllbnQ= 27492 -c2NhbmY= 27493 -IHNlYW0= 27494 -d2Fs 27495 -CWJ1Zg== 27496 -QmVhcmVy 27497 -IHByZWNpb3Vz 27498 -aWZhY3Rz 27499 -Y29vcmQ= 27500 -IGV4cGxvcmF0aW9u 27501 -LmdldFk= 27502 -KGhhbmRsZQ== 27503 -VG9waWM= 27504 -IFZlbnQ= 27505 -cmhz 27506 -LS0tLS0tCg== 27507 -IEJyaWdodA== 27508 -IGd1aWxk 27509 -bW90aGVy 27510 -c3Rvcm0= 27511 -IG11bmljaXBhbA== 27512 -IGluaw== 27513 -LlRZUEU= 27514 -d2w= 27515 -Li4uPC8= 27516 -X0RFVg== 27517 -PSIuLw== 27518 -X2Jvb2s= 27519 -dGh5 27520 -aXR6ZXJsYW5k 27521 -b3BsZXM= 27522 -dHJhY3Rpb24= 27523 -IENhbWVyb24= 27524 -IEFuZHJl 27525 -LnJlc3VsdHM= 27526 -IGNocm9tZQ== 27527 -IHNlY3VyZWQ= 27528 -IHN1cmZhY2Vz 27529 -KTw= 27530 -IHRvYmFjY28= 27531 -CXNwcmludGY= 27532 -IGVzY2Fs 27533 -IHN0ZGVycg== 27534 -IE1lbGJvdXJuZQ== 27535 -IGRpc3RyaWN0cw== 27536 -IG1hdHQ= 27537 -b2hlbg== 27538 -IGRhdGFHcmlkVmlld0NlbGxTdHlsZQ== 27539 -KE1vZGVs 27540 -IHNlbnNpdGl2aXR5 27541 -S0E= 27542 -dHJhbnNwb3J0 27543 -LmdldERhdGU= 27544 -IHN1YnRsZQ== 27545 -VUdJTg== 27546 -Lm1vdXNl 27547 -IGFsdGVybmF0aXZlcw== 27548 -IGVsbGU= 27549 -Y29yYXRpb24= 27550 -cmVhdGlvbg== 27551 -5ps= 27552 -X05PUk1BTA== 27553 -RGlzcGxheU5hbWU= 27554 -IGZhbmN5 27555 -SVNFRA== 27556 -TU9E 27557 -LlJlYWRPbmx5 27558 -IFVi 27559 -IEN1 27560 -aWNvbA== 27561 -IE5lbHNvbg== 27562 -IENPUg== 27563 -YW56YQ== 27564 -IFNwYXJr 27565 -ICJcXA== 27566 -LS0KCg== 27567 -d29vY29tbWVyY2U= 27568 -IHJlbWVtYmVyZWQ= 27569 -dmVyaXR5 27570 -IEV4dGVuc2lvbg== 27571 -IFBE 27572 -IHNlYXJjaGVz 27573 -LnNv 27574 -IEZvb3Rlcg== 27575 -ID0n 27576 -IFdBUk5JTkc= 27577 -LWxv 27578 -CXRhYmxl 27579 -IGRyYXdlcg== 27580 -cGljdHVyZQ== 27581 -IEZhbnRhc3k= 27582 -c3Rvcnk= 27583 -IG3Dqm1l 27584 -IwoK 27585 -X3NsaWNl 27586 -b2x0YWdl 27587 -SGFy 27588 -L3k= 27589 -IEVS 27590 -ZGll 27591 -IFBPUw== 27592 -LmFjdGlvbnM= 27593 -KE1haW4= 27594 -ZXdhcnQ= 27595 -YXBldXQ= 27596 -IFNURQ== 27597 -aWRkaW5n 27598 -LnJlYWRMaW5l 27599 -IHNlYXJjaGVk 27600 -V2Vk 27601 -LmZpZ3VyZQ== 27602 -dWdodGVycw== 27603 -KCkuX18= 27604 -IG9yYml0 27605 -c2hpcHBpbmc= 27606 -IGZyaWVuZHNoaXA= 27607 -IFNoaWZ0 27608 -LW9y 27609 -cXVv 27610 -V0hFUkU= 27611 -IEVzcA== 27612 -LmZvcndhcmQ= 27613 -b2ZmaWNl 27614 -IGnDpw== 27615 -IENoZWxzZWE= 27616 -SXRlbVNlbGVjdGVk 27617 -YWNoZXJz 27618 -ZGVsZXRlZA== 27619 -cm91cw== 27620 -ICItIg== 27621 -IEdyYW4= 27622 -IPCfmA== 27623 -LXBvd2Vy 27624 -ZXR0YQ== 27625 -IHJlbWluZGVy 27626 -ZW5zb3Jz 27627 -IEFsbG93 27628 -xJlk 27629 -X3RlYW0= 27630 -IGNyb3du 27631 -dGlja2V0 27632 -IGNvbGxlY3Rpb25WaWV3 27633 -bGFjZQ== 27634 -IGZpeGVz 27635 -IEh1Yg== 27636 -Y2F0YWxvZw== 27637 -IElkZW50aXR5 27638 -IGV4Y2Vzc2l2ZQ== 27639 -IE5hdmlnYXRvcg== 27640 -X0JS 27641 -LXBsYXk= 27642 -IENhbXBhaWdu 27643 -ICAgICAgICAgICAgICAgCg== 27644 -YXNpdmU= 27645 -IHdj 27646 -IEJlaWppbmc= 27647 -L3d3dw== 27648 -IG1ha2V1cA== 27649 -IGRpc3RhbmNlcw== 27650 -IHNhdGlzZnk= 27651 -Q09ORA== 27652 -IHdvdW5k 27653 -KCld 27654 -IHZpb2xhdGlvbnM= 27655 -IHN0YXlz 27656 -LyM= 27657 -aWxpbmU= 27658 -XEV4Y2VwdGlvbg== 27659 -IE1vdGlvbg== 27660 -IGhlYWw= 27661 -X3BsYW4= 27662 -cmFzZXM= 27663 -KG1haW4= 27664 -QXBwbGU= 27665 -IGNvbXBsZXRpbmc= 27666 -IGRldGVybWluZXM= 27667 -U2Nhbg== 27668 -IHN0ZWFs 27669 -IFNvYw== 27670 -QW5hbHlzaXM= 27671 -IGZhdm9yaXRlcw== 27672 -IGNhbXBv 27673 -b25lcg== 27674 -IEZsaWdodA== 27675 -Li4uCgoKCg== 27676 -KSkpKSk7Cg== 27677 -LWNvdW50 27678 -IHB3 27679 -QXNTdHJpbmc= 27680 -IHNleHVhbGx5 27681 -Rmlyc3ROYW1l 27682 -IEVzY29ydA== 27683 -Y2FsYw== 27684 -IFdpa2lwZWRpYQ== 27685 -IGRvY2tlcg== 27686 -IFN3ZWV0 27687 -J2lk 27688 -SW50bw== 27689 -IEh1bnQ= 27690 -LmVxdWFsVG8= 27691 -IGxhYm9yYXRvcnk= 27692 -IEJVU0lORVNT 27693 -RmlsZURpYWxvZw== 27694 -VHJlZU5vZGU= 27695 -LkVuYw== 27696 -IE1heGltdW0= 27697 -IG1vdGhlcnM= 27698 -5rU= 27699 -IGZyYWN0 27700 -LnN0YXJ0c1dpdGg= 27701 -IGhhcmRjb3Jl 27702 -Lm9i 27703 -5aeL 27704 -ID48Lw== 27705 -X3Jv 27706 -KCgq 27707 -Pz8/Pw== 27708 -X3ZlcnRleA== 27709 -a2VpdA== 27710 -IEhhbGxvd2Vlbg== 27711 -VEk= 27712 -IFZh 27713 -X2Nhcg== 27714 -PSJ7eyQ= 27715 -IHJhbmRvbWx5 27716 -0LDQvdC40LU= 27717 -IHNob2NrZWQ= 27718 -IFBva8OpbW9u 27719 -c2lnbmFs 27720 -IFNESw== 27721 -bWlkZGxld2FyZQ== 27722 -IHRyZWF0aW5n 27723 -IGJ1cm5lZA== 27724 -RGVwYXJ0bWVudA== 27725 -IFNwZWN0 27726 -IGNsaWVudGU= 27727 -IFJlZGRpdA== 27728 -X2F2Zw== 27729 -IGluc3RhbGxpbmc= 27730 -X2FscGhh 27731 -LGRhdGE= 27732 -IHNldElk 27733 -IExpc3RWaWV3 27734 -KHByb3BlcnR5 27735 -IGNyb3NzaW5n 27736 -IE9iag== 27737 -IFdhcmQ= 27738 -IFJlZGlyZWN0VG8= 27739 -IFByZXNlbnQ= 27740 -IGRyYXdz 27741 -Y2hlZHVsZWQ= 27742 -IGxlZ2lzbGF0aXZl 27743 -IHR3aXN0 27744 -IFN0cmE= 27745 -IEFGUA== 27746 -IENoYXA= 27747 -LXBy 27748 -OkNHUmVjdA== 27749 -IGNlcw== 27750 -Um91dGVz 27751 -bm9m 27752 -IHZpc2E= 27753 -IFRDUA== 27754 -IEVWRU4= 27755 -aXZpYWw= 27756 -IExldHRlcg== 27757 -UkFZ 27758 -IGltcGxvZGU= 27759 -LmVx 27760 -PScr 27761 -IG1vdGl2YXRlZA== 27762 -LnZpc2libGU= 27763 -LnNob3J0 27764 -Pm1hbnVhbA== 27765 -IFRlY2huaWNhbA== 27766 -IGNvcnBvcmF0aW9u 27767 -IEhX 27768 -YW5rYQ== 27769 -VEFJTA== 27770 -aXN0YXM= 27771 -IHBlcmZvcm1z 27772 -IEJlaGF2aW9y 27773 -LkZvcg== 27774 -X09SREVS 27775 -IEtpY2s= 27776 -IGNhbGxiYWNrcw== 27777 -X2Ry 27778 -dWVnbw== 27779 -aHVi 27780 -dWZmaWNpZW50 27781 -c2t5 27782 -IGJw 27783 -aHRhYmxl 27784 -IE9OTFk= 27785 -IEFVVEhPUlM= 27786 -LkFyZ3VtZW50 27787 -In07Cg== 27788 -IFRodW5kZXI= 27789 -IEtvbQ== 27790 -LlNob3VsZA== 27791 -QVVUSA== 27792 -YWh1 27793 -X3BheW1lbnQ= 27794 -IHN0YXJ0ZXI= 27795 -7ISc 27796 -7Jqp 27797 -QmxvZw== 27798 -LnBhdGNo 27799 -IGdvdmVybmVk 27800 -YXNzeQ== 27801 -LWZvdW5k 27802 -IHRoZWF0ZXI= 27803 -IEZvbnRXZWlnaHQ= 27804 -IEJhdG1hbg== 27805 -Iklm 27806 -LlJhbmRvbQ== 27807 -X2RlbHRh 27808 -IENF 27809 -QXV0aGVudGljYXRlZA== 27810 -IGRyb25l 27811 -IGNvdXM= 27812 -cmFkaXVz 27813 -TWVy 27814 -KE5vbmU= 27815 -IE5K 27816 -X2hlYWRlcnM= 27817 -IGFtZXI= 27818 -cHl0ZXN0 27819 -IEFjdGlvbnM= 27820 -CQkJICAgIA== 27821 -IGV0dA== 27822 -IGhvbHk= 27823 -IHVuY29tZm9ydA== 27824 -IE5pbg== 27825 -IERlY2ltYWw= 27826 -IE1lc3NhZ2Vz 27827 -LnNlbmRlcg== 27828 -XV0pCg== 27829 -IGVtYnJhY2U= 27830 -VGhvdWdo 27831 -L3Nw 27832 -IGN1bHR1cmVz 27833 -IGhpZ2h3YXk= 27834 -dGFy 27835 -LmZhaWw= 27836 -X2hpZGRlbg== 27837 -IGNvbXBvbmVudERpZE1vdW50 27838 -IFdyaWdodA== 27839 -IGphZw== 27840 -X2ls 27841 -Li4vLi4vLi4v 27842 -aWd1 27843 -Rm9vZA== 27844 -IGFjZQ== 27845 -IGHDsW9z 27846 -VVNE 27847 -IG11dHVhbA== 27848 -TG9naWM= 27849 -IHRlbXBsZQ== 27850 -IGJyaWVmbHk= 27851 -IFRyaXA= 27852 -Y2xhc3NtZXRob2Q= 27853 -ZGVmYXVsdHM= 27854 -IGNodW5rcw== 27855 -LCwsLA== 27856 -IFJlYXNvbg== 27857 -JGlk 27858 -LXVwcw== 27859 -IGRhbW4= 27860 -IHRydWNrcw== 27861 -IHVubGltaXRlZA== 27862 -IHNjdWxwdA== 27863 -IENhcmRz 27864 -IGF1dG9y 27865 -IFRlc3Rpbmc= 27866 -IGRpZXNl 27867 -c2hvcHM= 27868 -57Q= 27869 -KHBheWxvYWQ= 27870 -IFBBVEg= 27871 -IE1lbW9yaWFs 27872 -IHJpZGljdWxvdXM= 27873 -ZWdyZWU= 27874 -LXdpbm5pbmc= 27875 -IHJlaGFi 27876 -IHNvcGhpc3RpY2F0ZWQ= 27877 -d3BkYg== 27878 -CXBhdGg= 27879 -ISI7Cg== 27880 -X1NZUw== 27881 -LnNwZWVk 27882 -IHNvYXA= 27883 -c3VmZml4 27884 -V3JhcA== 27885 -IGVuaGFuY2VtZW50 27886 -w4k= 27887 -w7pi 27888 -IHBsYXlsaXN0 27889 -IG1peGluZw== 27890 -YW50aWRhZA== 27891 -PSIiOwo= 27892 -IFJldmlzaW9u 27893 -IEJlYXQ= 27894 -LmluYw== 27895 -LXdheQ== 27896 -ZW5jaWFz 27897 -dWxlcnM= 27898 -Q2F0 27899 -aWRlbA== 27900 -IFNoaXA= 27901 -LnNldENvbG9y 27902 -IHRocmVhdGVuaW5n 27903 -Lm1vZHVsZXM= 27904 -IGFmdGVyd2FyZHM= 27905 -IERhc2hib2FyZA== 27906 -CiAK 27907 -U2lnbmFs 27908 -IHByaW1lcg== 27909 -b3JuZXlz 27910 -aWNpYXJ5 27911 -IGxpZ25l 27912 -X3ByZWRpY3Q= 27913 -IGFlc3Q= 27914 -X2h0dHBz 27915 -Pjo= 27916 -IExleA== 27917 -IHJlbmNvbnRyZXM= 27918 -ZWdyYWw= 27919 -c2NhbGE= 27920 -X2ZhbWlseQ== 27921 -w59lbg== 27922 -X3N5bQ== 27923 -IHVuY2VydGFpbnR5 27924 -IFZBTFVF 27925 -IH07DQoNCg== 27926 -IGJyb2FkZXI= 27927 -IGhvcnNlcw== 27928 -44Gd 27929 -IEthbA== 27930 -b2Jh 27931 -X0lORVQ= 27932 -IEtpbGw= 27933 -anF1ZXJ5 27934 -YW1pbmF0aW9u 27935 -W0Ai 27936 -IG11ag== 27937 -IyMjCg== 27938 -Rmlyc3RPckRlZmF1bHQ= 27939 -dGhlblJldHVybg== 27940 -Q2hl 27941 -L2Zvb3Rlcg== 27942 -IHBhcmtz 27943 -YXNqZQ== 27944 -IEd1bGY= 27945 -IG1vZGVzdA== 27946 -LkluaXQ= 27947 -77yfCgo= 27948 -IHByb3NwZWN0cw== 27949 -IHN2Zw== 27950 -IOWP 27951 -LkRpYWxvZw== 27952 -X05FVA== 27953 -ICgoJA== 27954 -IGVr 27955 -IFdhcm5pbmc= 27956 -IE1L 27957 -PExN 27958 -ICcNCg== 27959 -aWVt 27960 -aGV0aWM= 27961 -IGl4 27962 -dGhpbms= 27963 -LXNoYWRvdw== 27964 -IEVsZA== 27965 -IE5ldmFkYQ== 27966 -IExlYWY= 27967 -IEdST1VQ 27968 -IHByb21v 27969 -ZW50aW5l 27970 -CU1hcA== 27971 -IE1vZGVscw== 27972 -IEtyaXN0 27973 -X2tlcm5lbA== 27974 -LW1hZGU= 27975 -IGNlcnI= 27976 -QXNzZXRz 27977 -ZWxsYXI= 27978 -IGludm9rZWQ= 27979 -LnZ1ZQ== 27980 -IGN1bHRpdg== 27981 -Q2xvc2Vk 27982 -IGdlbmVyYXRlcw== 27983 -ZmZmZmZm 27984 -dGhlc2l6ZQ== 27985 -c3FydA== 27986 -IENhc3RsZQ== 27987 -LmNhcg== 27988 -IGtlZW4= 27989 -dW5kYQ== 27990 -IENyb3c= 27991 -IFNpbmdo 27992 -eXRob24= 27993 -IGJlYW5z 27994 -bGFyZw== 27995 -5paH5Lu2 27996 -QXdlc29tZQ== 27997 -dW5jYXRl 27998 -UGF0aHM= 27999 -b2pp 28000 -KGN1cnI= 28001 -Q09ORFM= 28002 -IG1pbQ== 28003 -IHNob3VsZGVycw== 28004 -SGFyZA== 28005 -YXN0ZXM= 28006 -0LDQtdGC 28007 -IGNvbnZpbmNl 28008 -ZGVjZXNz 28009 -bWFkZQ== 28010 -IENNRA== 28011 -Lklt 28012 -IGNoYW9z 28013 -ZW5zaXZlbHk= 28014 -IGNvb2xpbmc= 28015 -IGJ1cmllZA== 28016 -KCdA 28017 -X1Nl 28018 -CQkJCQkJCQkJCQkJCQkJCQ== 28019 -LmNvbXBhbnk= 28020 -LnN1Ym1pdA== 28021 -cGhhbnQ= 28022 -IGJvb3RzdHJhcA== 28023 -X2hlbHA= 28024 -4Kc= 28025 -LmR1bXA= 28026 -IGRpZmVy 28027 -X21hcHBpbmc= 28028 -IGNpcmN1bGFy 28029 -IGVzY29ydHM= 28030 -IGJlcmU= 28031 -IGdyYWR1 28032 -IExlZ2VuZA== 28033 -aW1lZGlh 28034 -IEJhcmNlbG9uYQ== 28035 -IGJlZHM= 28036 -5Yiw 28037 -44CK 28038 -X3ZvbHVtZQ== 28039 -IHRyZW1lbmRvdXM= 28040 -IHNjYWxpbmc= 28041 -IHBpbnM= 28042 -ZW5hcw== 28043 -dHlwZXBhcmFt 28044 -RGFzaGJvYXJk 28045 -cmVuZGVyZXI= 28046 -IHNwaQ== 28047 -ICYk 28048 -IFNraW4= 28049 -YWxtYXJ0 28050 -IGhvY2tleQ== 28051 -ICciLiQ= 28052 -IGVycm5v 28053 -IGJldw== 28054 -Rm9sbG93aW5n 28055 -Lk1vZHVsZQ== 28056 -ZXJhYmxl 28057 -IE1pbGl0YXJ5 28058 -IFJpbw== 28059 -X2F2YWlsYWJsZQ== 28060 -IFN1cmZhY2U= 28061 -IHN0YWI= 28062 -SUZJRVI= 28063 -IExJU1Q= 28064 -IGRhc2hib2FyZA== 28065 -IGNsdXN0ZXJz 28066 -LnBsdWdpbg== 28067 -IGpvdQ== 28068 -IERlY29y 28069 -Rm91cg== 28070 -IGRlbGxl 28071 -KioqKioqLwo= 28072 -aWF6 28073 -aW5kZQ== 28074 -Y2hpbmc= 28075 -IGdldEl0ZW0= 28076 -LkFkZHJlc3M= 28077 -bWVudGVk 28078 -QW1lcmlj 28079 -UGxhaW4= 28080 -IHVzYg== 28081 -IFByYWN0aWNl 28082 -X21lbnQ= 28083 -LmJsdWU= 28084 -SGludA== 28085 -0YDQsNCy 28086 -IGNvbm5lY3Rvcg== 28087 -IGluaGVyaXRlZA== 28088 -0LjQsg== 28089 -IGludGVydmFscw== 28090 -IGNlcmU= 28091 -IHVk 28092 -IGluY29u 28093 -LkV4aXN0cw== 28094 -IE1pYw== 28095 -Rks= 28096 -KGNhcmQ= 28097 -LlNldHRpbmdz 28098 -IGV4aGliaXRpb24= 28099 -IG9uUHJlc3NlZA== 28100 -IHJlc3RvcmVk 28101 -ZW5ndQ== 28102 -LmRlZg== 28103 -IHJlY3Y= 28104 -LiIpOw0K 28105 -ZW5jb2Rlcg== 28106 -YXRoZXJpbmU= 28107 -KGRlc3Q= 28108 -YXplZA== 28109 -I2VuZHJlZ2lvbg== 28110 -c2VtYmw= 28111 -LE0= 28112 -b2J5 28113 -INC/0LXRgA== 28114 -LkNhbGw= 28115 -IGF0dGVuZGFuY2U= 28116 -LWJvcmRlcg== 28117 -IGFkZHJlc3Npbmc= 28118 -w6pu 28119 -IExldg== 28120 -IGJhc2g= 28121 -YmVuY2g= 28122 -Q3JlZGVudGlhbHM= 28123 -U3BhY2luZw== 28124 -KG9m 28125 -X1JFU0VU 28126 -aWd1b3Vz 28127 -IGNydWVs 28128 -IGNyb3NzZWQ= 28129 -IGxldXI= 28130 -IEdvbGY= 28131 -b3JyZWN0 28132 -IHBhY2tldHM= 28133 -IERhdGFTZXQ= 28134 -IHBhcnRseQ== 28135 -U0VRVUVOVElBTA== 28136 -IGluZGljYXRpb24= 28137 -IFNhbHQ= 28138 -YWNpYQ== 28139 -ICopOwo= 28140 -CWluZm8= 28141 -IFZpZXdCYWc= 28142 -b256 28143 -IGVkaXRvcmlhbA== 28144 -IEFyZW5h 28145 -IHNpcg== 28146 -X1N0YXRpYw== 28147 -KHNvY2tldA== 28148 -c3U= 28149 -Y2hvb3Nl 28150 -Lm1vbnRo 28151 -Lk15 28152 -MDk2 28153 -w6lyaQ== 28154 -O2ZvbnQ= 28155 -ZG9lcw== 28156 -IGNvbnZlcnRlcg== 28157 -IHNhbHY= 28158 -IGxy 28159 -IGluZmx1ZW5jZWQ= 28160 -KGZlYXR1cmU= 28161 -IFF1ZWVucw== 28162 -bGV0dA== 28163 -X01PTg== 28164 -JmFtcA== 28165 -VG91Y2hhYmxlT3BhY2l0eQ== 28166 -T0ZG 28167 -IG1ldGFib2w= 28168 -KGl0ZXI= 28169 -IHZpdGFtaW4= 28170 -IElORElSRUNU 28171 -YXV0b20= 28172 -X3B1YmxpYw== 28173 -IGFkanVzdG1lbnQ= 28174 -IHNwZWNpYWxpemVk 28175 -d2luZG93cw== 28176 -LmFkZEFsbA== 28177 -IGFjY29yZGluZ2x5 28178 -IEpPcHRpb25QYW5l 28179 -IGNlbGxzcGFjaW5n 28180 -IHF1YWQ= 28181 -IGNyZWVw 28182 -IG91dGxldHM= 28183 -fWApCg== 28184 -IHByaWVzdA== 28185 -X1RIUkVBRA== 28186 -IE1hcng= 28187 -IEJ5VmFs 28188 -IGN1YWw= 28189 -6Z2i 28190 -IHRlbXBvcmFyaWx5 28191 -QW5u 28192 -a2VsZXRvbg== 28193 -5aU= 28194 -IExPQw== 28195 -YXVlcg== 28196 -ZGVyaXZl 28197 -IGJlaGF2aW9ycw== 28198 -YXNlbmFtZQ== 28199 -IENlbnR1cnk= 28200 -IGhvcnJpYmxl 28201 -TUVTUw== 28202 -X0xpc3Q= 28203 -d2Vp 28204 -UGF0 28205 -IENob2ljZQ== 28206 -X0ZST00= 28207 -CWxpbmU= 28208 -Lmludm9rZQ== 28209 -LkJvdHRvbQ== 28210 -IG5vd2hlcmU= 28211 -LiIKCgoK 28212 -X2V4cG9ydA== 28213 -IHN0cnVnZ2xlZA== 28214 -LkFwcGVhcmFuY2U= 28215 -IEpCdXR0b24= 28216 -IEplcmVteQ== 28217 -KFtb 28218 -IGtpY2tlZA== 28219 -bWFyc2hhbA== 28220 -c3RhZmY= 28221 -ZXNpdHk= 28222 -IHF1aXo= 28223 -X2VmZmVjdA== 28224 -IH0pKTsKCg== 28225 -bWVs 28226 -YmFubmVy 28227 -IFBJTg== 28228 -IGludmVudGlvbg== 28229 -IGNvbnNvbGlk 28230 -IG9wcw== 28231 -IEJldHdlZW4= 28232 -amFjaw== 28233 -ZXJuYXRpb25hbA== 28234 -IHNhY3JpZmljZQ== 28235 -YWdhdGlvbg== 28236 -IEpveQ== 28237 -IGFtZW5kbWVudA== 28238 -IFNvbGQ= 28239 -IHByaXNvbmVycw== 28240 -0LDQvdC90Ys= 28241 -RG9jdW1lbnRz 28242 -KV0pCg== 28243 -dXN0ZWQ= 28244 -IExpbmVhckxheW91dA== 28245 -b3Nv 28246 -X0VN 28247 -LnNlbGY= 28248 -Lk1pZGRsZQ== 28249 -KS8v 28250 -IFwn 28251 -IGZ1Y2tlZA== 28252 -IE11cnJheQ== 28253 -IHByb2ZvdW5k 28254 -X0VMRU1FTlQ= 28255 -dWx0YQ== 28256 -aWxlcnM= 28257 -cG9ydGZvbGlv 28258 -SnVuZQ== 28259 -dGNw 28260 -bW9kaWZpZWQ= 28261 -IFRyYWNl 28262 -IEtlbA== 28263 -YWx5emVy 28264 -KT0+ 28265 -IFJlcGFpcg== 28266 -X0JF 28267 -QnJhbmQ= 28268 -dWFydA== 28269 -cHJldmlldw== 28270 -IGluaXRpYXRpdmVz 28271 -cnVubmluZw== 28272 -YmFuZw== 28273 -CXVwZGF0ZQ== 28274 -IENvYWNo 28275 -UmljaA== 28276 -IHlvdXR1YmU= 28277 -IHJpdHVhbA== 28278 -YXBwYQ== 28279 -IFJvYmluc29u 28280 -cHJlY2lzaW9u 28281 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLw== 28282 -PVtdCg== 28283 -IGNlbGVicmF0ZWQ= 28284 -T1RP 28285 -IGluY2x1c2lvbg== 28286 -SlA= 28287 -JzsNCg0K 28288 -IG5vdGFibGU= 28289 -KF8u 28290 -TWFuYWdlZA== 28291 -IGd1aWRlcw== 28292 -Jm5ic3A= 28293 -YXRlZFJvdXRl 28294 -IEFkanVzdA== 28295 -IGNvbG9yZWQ= 28296 -X3Njb3Jlcw== 28297 -IFRlc2xh 28298 -X3Byb2dyZXNz 28299 -Lmluc3Q= 28300 -Wydf 28301 -LmZsYWdz 28302 -IGZjbG9zZQ== 28303 -X09QRVI= 28304 -xbx5 28305 -X25vdGU= 28306 -IHRyYW5zZ2VuZGVy 28307 -5ZU= 28308 -UklQVA== 28309 -IGFic2VudA== 28310 -IGFtZXQ= 28311 -IG9wZXJhbmQ= 28312 -66k= 28313 -IGhvb2Q= 28314 -dG9Mb3dlckNhc2U= 28315 -YXZv 28316 -IENpcmN1aXQ= 28317 -IExpbmQ= 28318 -LS19fQo= 28319 -PW0= 28320 -IHN1cHByZXNz 28321 -IE1BUA== 28322 -aWFuZw== 28323 -LWFkbWlu 28324 -IHNpZGViYXI= 28325 -IEJ1 28326 -IEhleA== 28327 -LEY= 28328 -IFNpZ25hbA== 28329 -IHRyYW5zcGFyZW5jeQ== 28330 -IEZlZGVyYXRpb24= 28331 -L1Y= 28332 -UmVx 28333 -IHB1bHNl 28334 -IHRlbmRz 28335 -TnVtYmVycw== 28336 -JSc= 28337 -IGRlcG9ydA== 28338 -ZGF0YXM= 28339 -X1VJTlQ= 28340 -X3RyYQ== 28341 -b2tv 28342 -ICI/ 28343 -Y29tcGV0 28344 -c29sZXRl 28345 -dW5kcnk= 28346 -IG92ZXJsYXA= 28347 -fWAsCg== 28348 -Lmx5 28349 -X3N1bW1hcnk= 28350 -IExvc3Q= 28351 -LkNlbnRlcg== 28352 -IGRpc2FiaWxpdHk= 28353 -LlNlcmlhbGl6YXRpb24= 28354 -IGdlb20= 28355 -ID86 28356 -IFdv 28357 -IHNoaXBwZWQ= 28358 -guaVsA== 28359 -IHVnbHk= 28360 -IGV4Y2l0ZW1lbnQ= 28361 -IGV4dGVyaW9y 28362 -IGNoZWNrb3V0 28363 -IGt1cg== 28364 -LEQ= 28365 -IEFsYXNrYQ== 28366 -IHN5bnRoZXRpYw== 28367 -IEJ1ZGdldA== 28368 -IFN1YnNjcmliZQ== 28369 -ICYK 28370 -yJlp 28371 -IFl1 28372 -CXF1ZXJ5 28373 -fS4K 28374 -IHRyYWdlZA== 28375 -YXNzZW4= 28376 -IGFjY29tbW9kYXRpb24= 28377 -IHBoeXNpY2lhbg== 28378 -IHJlbmFtZWQ= 28379 -IHRpZGFr 28380 -esSF 28381 -IG1pbnVz 28382 -bnljaA== 28383 -MDk3 28384 -X0VYQ0VQVElPTg== 28385 -dGhyZWFkcw== 28386 -IHRpcmU= 28387 -X2NyZWF0ZWQ= 28388 -ZW5zdXJl 28389 -IHdvcnRoeQ== 28390 -IGV4Y3VzZQ== 28391 -IGNsb3Ro 28392 -LnBhcmVudE5vZGU= 28393 -L3BsYXRmb3Jt 28394 -IFVGQw== 28395 -IEd0aw== 28396 -dW5ueQ== 28397 -IGdpYnQ= 28398 -a2VsZXk= 28399 -aHVt 28400 -KHR4 28401 -CWRldg== 28402 -IG91dGZpdA== 28403 -ZG9vcnM= 28404 -IGZvbg== 28405 -aWN1dA== 28406 -dm9sYXRpbGU= 28407 -IGhvbW9zZXg= 28408 -TWF4aW11bQ== 28409 -IGV4cGVuZA== 28410 -IH0pOwoKCg== 28411 -RXE= 28412 -b25kZXJz 28413 -ZGVwYXJ0bWVudA== 28414 -IFBoeXNpY3M= 28415 -In0pOwo= 28416 -IHBhcmFk 28417 -LlN0cg== 28418 -IHNlbGU= 28419 -SUZJRUQ= 28420 -IGRlbGl2ZXJz 28421 -aXZhbg== 28422 -IHJlc3BvbnNpYmlsaXRpZXM= 28423 -IGFkdm9jYXRlcw== 28424 -6LU= 28425 -IFJJRA== 28426 -LnBhcmFtZXRlcnM= 28427 -TWV0cmljcw== 28428 -cm9uaWNz 28429 -IFVJVGFibGVWaWV3Q2VsbA== 28430 -QWJzb2x1dGU= 28431 -aXBzZQ== 28432 -eWx1bQ== 28433 -TUxFbGVtZW50 28434 -X1ZBTElE 28435 -PHRpdGxl 28436 -RGxn 28437 -cGFjZXM= 28438 -IHN5bmRyb21l 28439 -YmVhbnM= 28440 -X2RhdGFiYXNl 28441 -b3ppbGxh 28442 -IE1lZw== 28443 -REJH 28444 -IGx1Yg== 28445 -QmFnQ29uc3RyYWludHM= 28446 -YWJhZA== 28447 -IHByb2plY3RlZA== 28448 -X0JZVEU= 28449 -LlNpemVG 28450 -c3RyZWV0 28451 -CgoKCgoKCgoKCg== 28452 -IExPU1M= 28453 -IGRpcmVjdG9ycw== 28454 -L25ld3M= 28455 -IG51cnNpbmc= 28456 -IERvbmU= 28457 -LkhUVFA= 28458 -ZGlzY291bnQ= 28459 -IFJvdA== 28460 -VG9NYW55 28461 -IGVuYWJsaW5n 28462 -IGF1c3Np 28463 -b3N0YQ== 28464 -ICAgICAgICAgICAgICAgIA0K 28465 -6L29 28466 -IGhlbGljb3B0 28467 -IEluc2lkZQ== 28468 -5L+h5oGv 28469 -aXNwZXI= 28470 -IEFsbGFo 28471 -QVJDSEFS 28472 -IHJvbGxz 28473 -Q29tcGFyZQ== 28474 -WFA= 28475 -SW5kZXhPZg== 28476 -U1VN 28477 -IGFzc3VyZWQ= 28478 -IFBoeXNpY2Fs 28479 -RW5kcG9pbnQ= 28480 -Lkdsb2JhbA== 28481 -LmRldGFpbA== 28482 -IHRoZWZ0 28483 -Lmp1cGl0ZXI= 28484 -IGh1bW9y 28485 -LlJlbmRlcg== 28486 -QWxleA== 28487 -LmNhcA== 28488 -IGJ1ZmZlcnM= 28489 -IGRpc3Bvc2U= 28490 -dGlvbg== 28491 -LnByZXNlbnQ= 28492 -emVs 28493 -LFA= 28494 -IGRlc3BlcmF0ZQ== 28495 -LmdldENvbHVtbg== 28496 -IHR3aW4= 28497 -7JY= 28498 -LmNhbg== 28499 -IGZsZWU= 28500 -IElyYW5pYW4= 28501 -IHN0aWNreQ== 28502 -IFVUQw== 28503 -TFQ= 28504 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v 28505 -IGxpY2Vuc2luZw== 28506 -X1BPSU5U 28507 -IE1hcHM= 28508 -IGxvbA== 28509 -PW1vZGVscw== 28510 -LXRhYg== 28511 -IE5hc2g= 28512 -X2xvZ2dlcg== 28513 -dG9yY2g= 28514 -IENPTlNFUVVFTlRJQUw= 28515 -Tm90RW1wdHk= 28516 -L3JlYWN0 28517 -IHBm 28518 -IGFzc2VydGlvbg== 28519 -IHN1YnNlcXVlbnRseQ== 28520 -X2Nhbg== 28521 -IHBhbmRlbWlj 28522 -b2d1ZQ== 28523 -IisK 28524 -X2VudA== 28525 -X1BhcmFt 28526 -LgoKCgoKCgoK 28527 -UmVzZWFyY2g= 28528 -Q2FwdHVyZQ== 28529 -IGJlbG92ZWQ= 28530 -ZGVt 28531 -IGV4dHJhY3RlZA== 28532 -IGZpZ2h0cw== 28533 -RVJD 28534 -KGF1dGg= 28535 -cG9zaXRpb25z 28536 -IHJldmVyc2Vk 28537 -KHN0YWNr 28538 -IF8p 28539 -dXRvZmY= 28540 -X2Zsb3c= 28541 -54K5 28542 -KEdhbWU= 28543 -IGV4Y2x1ZGVk 28544 -IENTVg== 28545 -Y2c= 28546 -IFRpdGFu 28547 -cGF1c2U= 28548 -IGNlcmNh 28549 -IGR1bXBzdGVy 28550 -TGVzcw== 28551 -IGtvdGxpbng= 28552 -YXN0ZXJ4bWw= 28553 -IHBvaW50ZXJz 28554 -IGZsb3dz 28555 -IFR1bg== 28556 -IE1haW5BY3Rpdml0eQ== 28557 -IGRpc2NyZXQ= 28558 -IGNvbWJpbmF0aW9ucw== 28559 -dmlzaXQ= 28560 -X2JpbmQ= 28561 -b290aW5n 28562 -ZGF0ZXI= 28563 -X2xvb2t1cA== 28564 -Lm5pbw== 28565 -IHN3ZWF0 28566 -IFJk 28567 -IHNjaWVudGlzdA== 28568 -IFBpeGVs 28569 -QE5nTW9kdWxl 28570 -UGxheWluZw== 28571 -IHVuZm9sZA== 28572 -VHJhbnNsYXRl 28573 -IExhd3JlbmNl 28574 -IEZJWE1F 28575 -QmlsbA== 28576 -IFJJR0hU 28577 -IHdoZXJldmVy 28578 -IG9vaw== 28579 -dmlkZW5jZQ== 28580 -IF1dOw== 28581 -IFNraWxs 28582 -dW5pc3Rk 28583 -IPCfmYI= 28584 -IGZlbWFsZXM= 28585 -LS0pCg== 28586 -jrflj5Y= 28587 -IEZyZWQ= 28588 -T3ZlcmFsbA== 28589 -2YI= 28590 -IGVzc2VuY2U= 28591 -IHRoZXJlYnk= 28592 -IHdvdW5kZWQ= 28593 -IERPV04= 28594 -bGVzc29u 28595 -dGV4dHVyZQ== 28596 -Um91bmQ= 28597 -IGF1dG9tYXRlZA== 28598 -INCh 28599 -IFVwZGF0ZXM= 28600 -IHNoYWRl 28601 -cHVibGlzaA== 28602 -IEdlYXI= 28603 -PWxhbWJkYQ== 28604 -IGxldmVy 28605 -KSsi 28606 -aGlsbA== 28607 -IHJhZGFy 28608 -cnlpbmc= 28609 -ICIpLg== 28610 -ZmlsbGVk 28611 -IGxpbmV1cA== 28612 -IGRs 28613 -IHdvcmtzcGFjZQ== 28614 -Vm8= 28615 -X2R0 28616 -67I= 28617 -X0l0ZW0= 28618 -TlNVUkw= 28619 -LnZlcmlmeQ== 28620 -IEhhd2FpaQ== 28621 -R29k 28622 -TWFyY2g= 28623 -IFvigKZd 28624 -IHBlbG8= 28625 -dXJpb3Vz 28626 -IFBpdHRzYnVyZ2g= 28627 -Lkl0 28628 -Q2xlYW4= 28629 -Plw8Xg== 28630 -IGlvcw== 28631 -c291bmQ= 28632 -Il07 28633 -IGZyZWVk 28634 -cm90dGxl 28635 -IExvd2Vy 28636 -W2NvdW50 28637 -5Z0= 28638 -IHBhbGU= 28639 -IFdheW5l 28640 -ZWFydGg= 28641 -X2NhdGVnb3JpZXM= 28642 -VUNL 28643 -Lm1ldGFkYXRh 28644 -IHN1bW1vbg== 28645 -SE9NRQ== 28646 -0L7Qu9GM0Lc= 28647 -IG1hbnVmYWN0dXJlZA== 28648 -IGRvY2s= 28649 -IGNvbXBldGl0b3Jz 28650 -X01PREVM 28651 -b2tpYQ== 28652 -IEhleQ== 28653 -zr8= 28654 -IGJhY2t3YXJk 28655 -IFBPU1M= 28656 -cm9wYQ== 28657 -IGNyaQ== 28658 -X09CSg== 28659 -VHJhbnNwb3J0 28660 -LWhpZ2g= 28661 -IGVyb3Rpaw== 28662 -X3Nsb3Q= 28663 -IGFydGlj 28664 -X2ZyYW1ld29yaw== 28665 -LXNlcmlm 28666 -IFNxbERiVHlwZQ== 28667 -Jyko 28668 -KyIv 28669 -IHdvcmU= 28670 -U2ls 28671 -IHN0b3Jpbmc= 28672 -IFBoYXNl 28673 -dWFudA== 28674 -IGJ1bXA= 28675 -aW5obw== 28676 -IGRpZ24= 28677 -IGJhY2tz 28678 -cXE= 28679 -KGhhc2g= 28680 -IGdlbw== 28681 -IHRlbmRlcg== 28682 -TG9nbw== 28683 -ISkK 28684 -IE1Y 28685 -IEFydGh1cg== 28686 -ZXNzb2E= 28687 -X0No 28688 -IGJlZHJvb21z 28689 -PSIjIj48 28690 -IHRocm9hdA== 28691 -aW5zaWM= 28692 -LmludGVnZXI= 28693 -IHByaW1pdGl2ZQ== 28694 -VHJ1dGh5 28695 -IGZhY2lsaXRhdGU= 28696 -IGNyZWF0aXZpdHk= 28697 -IEROUw== 28698 -IGdyYQ== 28699 -dWV6 28700 -IGNvdW50bGVzcw== 28701 -IFBvbGFuZA== 28702 -J00= 28703 -IERpc3Q= 28704 -IHZlc3Q= 28705 -IGNlcnRpZmljYXRpb24= 28706 -4buR 28707 -aGVsZA== 28708 -ZXh0ZW5zaW9ucw== 28709 -KHN0YXRpYw== 28710 -IGdyYWRlcw== 28711 -IFViZXI= 28712 -44Gf 28713 -IFtdKQo= 28714 -ZGF0b3M= 28715 -IGdldERhdGE= 28716 -IENoYXJn 28717 -IEJT 28718 -Lm1pY3Jvc29mdA== 28719 -LnZpZGVv 28720 -LmRpcmVjdGlvbg== 28721 -LT57Jw== 28722 -bHVh 28723 -YXBlc3Q= 28724 -IGJvaWxlcg== 28725 -ZXJlaw== 28726 -IGRlY2lkZXM= 28727 -Lmphcg== 28728 -SVND 28729 -IFdvcmRz 28730 -KENPTg== 28731 -RU1QTEFURQ== 28732 -cmVlemU= 28733 -c2hvdHM= 28734 -YXBwcw== 28735 -dW50ZWQ= 28736 -LnNldE5hbWU= 28737 -Ojo8 28738 -LWJvbGQ= 28739 -6rI= 28740 -5a+G 28741 -TG9uZ3JpZ2h0YXJyb3c= 28742 -IHVuZmFpcg== 28743 -IGVhcm5pbmc= 28744 -IHNoZWxm 28745 -VVJFTUVOVA== 28746 -IGlkbGU= 28747 -X01FTlU= 28748 -LkN1c3RvbQ== 28749 -QUdFUg== 28750 -LSI= 28751 -X3N3aXRjaA== 28752 -YmVjYXVzZQ== 28753 -KXZpZXc= 28754 -bWFyZQ== 28755 -X2NvbmRpdGlvbg== 28756 -IFN0YXJ0aW5n 28757 -TXZj 28758 -KHByZQ== 28759 -ZHVtcA== 28760 -X0xPQ0s= 28761 -YXRldGltZQ== 28762 -LmNhbGxiYWNr 28763 -IENlcg== 28764 -b3BvbA== 28765 -aWJyYXJ5 28766 -IHJlc2VydmF0aW9u 28767 -CQkJCQkJCQo= 28768 -bGVjdG9y 28769 -Z3JhZHVhdGU= 28770 -IGdlbmVyb3Vz 28771 -IGlvbg== 28772 -cmljYW8= 28773 -bXE= 28774 -X2NvbXBsZXRl 28775 -KGN1cnNvcg== 28776 -IEZvcm1Db250cm9s 28777 -OmNlbnRlcg== 28778 -IHN1YnN0aXR1dGU= 28779 -IFBsYW5uaW5n 28780 -IHBlbnNpb24= 28781 -IHJlY29tbWVuZGF0aW9u 28782 -IFRhZ3M= 28783 -IGdlZg== 28784 -IGFsYnVtcw== 28785 -IHdhc2hpbmc= 28786 -cm9j 28787 -IHRyYWlucw== 28788 -YXRpbmdz 28789 -IGV4cG9uZW50 28790 -YWNrYmFy 28791 -LWxu 28792 -w6Fn 28793 -LkRhdGFBbm5vdGF0aW9ucw== 28794 -IEVJRg== 28795 -IE1hbGF5c2lh 28796 -CVBPUlQ= 28797 -b251cw== 28798 -IGNsZXZlcg== 28799 -IHBldQ== 28800 -PgoKCgo= 28801 -IEFyZ3VtZW50cw== 28802 -IGRlYnVnZ2luZw== 28803 -KHJpZ2h0 28804 -J0Q= 28805 -Y29tcHV0ZQ== 28806 -IGZpbmVzdA== 28807 -T1JBR0U= 28808 -IHNwZWN0YWN1bGFy 28809 -cGhyYXNl 28810 -IGluZGlh 28811 -IGxlZ2VuZGFyeQ== 28812 -YmlydGg= 28813 -IGNvbXBvc2l0ZQ== 28814 -IGdyb3dz 28815 -IFRE 28816 -IGVwaWQ= 28817 -IGxhdW5jaGluZw== 28818 -XV1b 28819 -TWludXRlcw== 28820 -IENoYQ== 28821 -IGNsZWFuZWQ= 28822 -IHdpdG5lc3Nlcw== 28823 -dWthbg== 28824 -CVR5cGU= 28825 -IGhhYmU= 28826 -cGFyYWdyYXBo 28827 -IEpQYW5lbA== 28828 -IEhhbm4= 28829 -IHZhcmllZA== 28830 -IFBva2Vtb24= 28831 -IE1VU1Q= 28832 -5Yqo 28833 -LnZpc2liaWxpdHk= 28834 -b3B1cA== 28835 -Xls= 28836 -LmV4cGFuZA== 28837 -ICInLA== 28838 -LmZhc3RlcnhtbA== 28839 -X2F1dG8= 28840 -IFNoZWV0 28841 -bWFya2Vy 28842 -UGFyY2Vs 28843 -ZXdz 28844 -IFN0cmF0ZWd5 28845 -LW1ha2luZw== 28846 -IHVudmU= 28847 -IHRyYWlsaW5n 28848 -IGNsaWNrcw== 28849 -IEdldENvbXBvbmVudA== 28850 -CWNvbnRlbnQ= 28851 -SUdFTkNF 28852 -RVJORUw= 28853 -TlNNdXRhYmxlQXJyYXk= 28854 -IGJyZWF0 28855 -IGhhcm1mdWw= 28856 -tog= 28857 -IGJlc2lkZXM= 28858 -IGJvcmluZw== 28859 -IGJydXRhbA== 28860 -dmFuZw== 28861 -KHBhcnNl 28862 -cXVpY2s= 28863 -IHB5dGVzdA== 28864 -IHN3aXRjaGluZw== 28865 -KCldCg== 28866 -IOyE 28867 -TEVS 28868 -CWZvbnQ= 28869 -IG5ldHQ= 28870 -KV0KCg== 28871 -KC9c 28872 -5p6c 28873 -dG9BcnJheQ== 28874 -IGJyZWVk 28875 -IENBUg== 28876 -IFdlYXBvbg== 28877 -QWJz 28878 -dG90 28879 -IHNldE5hbWU= 28880 -YXB0aXZl 28881 -IDos 28882 -IGVzY2FwZWQ= 28883 -b3JkZW4= 28884 -IFByaQ== 28885 -dGh1bWJuYWls 28886 -IGRlc2NyaXB0aW9ucw== 28887 -L3N0eWxlcw== 28888 -IFBDSQ== 28889 -IGFscGhhYmV0 28890 -YXN0aWNzZWFyY2g= 28891 -Tk9URQ== 28892 -IGNpYWxpcw== 28893 -IEdyaWZm 28894 -IHBvcnF1ZQ== 28895 -IHByb3RlaW5z 28896 -cGxheXM= 28897 -IHN0YXRpbmc= 28898 -IGltYWdpbmF0aW9u 28899 -IGZhY2lhbA== 28900 -IE1lY2hhbg== 28901 -IGFycmFuZ2Vk 28902 -X3VzZWQ= 28903 -IGFycmFuZ2VtZW50cw== 28904 -IFBpcGU= 28905 -aG9zdG5hbWU= 28906 -IHByb3ZpbmM= 28907 -VGl0 28908 -LkZsYXRTdHlsZQ== 28909 -IFNwbGl0 28910 -IExvYWRlcg== 28911 -LmNj 28912 -IGNsaW5pYw== 28913 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 28914 -IGJha2luZw== 28915 -IEVOVA== 28916 -bmVhdGg= 28917 -44CBCgo= 28918 -QU5F 28919 -LkVudGl0eUZyYW1ld29ya0NvcmU= 28920 -YXBwZXJz 28921 -Lmlj 28922 -IE5nTW9kdWxl 28923 -IEZPUk0= 28924 -ICc7 28925 -LXByb2ZpdA== 28926 -aHc= 28927 -ZW5lbXk= 28928 -IEV5ZQ== 28929 -IGNhdXRpb24= 28930 -dG93bg== 28931 -IHVyZ2Vk 28932 -IEppbW15 28933 -eW5jaHJvbm91cw== 28934 -LXNpemVk 28935 -bWFraW5n 28936 -LHs= 28937 -XScs 28938 -X09iamVjdA== 28939 -YWhvbWE= 28940 -IGFjdGl2aXN0 28941 -SU5WQUw= 28942 -IENvbW1lcmNpYWw= 28943 -IE9ybGFuZG8= 28944 -KHRhYg== 28945 -INio 28946 -QWxnb3JpdGht 28947 -IGhlcml0YWdl 28948 -R2V0TWFwcGluZw== 28949 -IGZhaWx1cmVz 28950 -cmlvcw== 28951 -YXRpdmE= 28952 -IHRldA== 28953 -IGNhcnBldA== 28954 -KFo= 28955 -dGhyZWU= 28956 -IGRpc2Nsb3N1cmU= 28957 -LkVSUk9S 28958 -X2NhbGxlZA== 28959 -IGRpYWw= 28960 -IG9jY2FzaW9uYWw= 28961 -LkVycg== 28962 -IGZ1bmNpb24= 28963 -Y2FmZm9sZA== 28964 -IHJlbGVhc2luZw== 28965 -77yJCgo= 28966 -X1ZhbHVl 28967 -IFZhcmk= 28968 -eWVsbG93 28969 -IHN0cnVnZ2xlcw== 28970 -LmNhbA== 28971 -IERha290YQ== 28972 -CWNsb3Nl 28973 -IHNhbmR3aWNo 28974 -IGFuYWx5dGljcw== 28975 -ICoqKQ== 28976 -JiM= 28977 -IEpvcw== 28978 -IHBhc3NpdmU= 28979 -QVRUUg== 28980 -VGhyb3dhYmxl 28981 -IE11bg== 28982 -IFVpbnQ= 28983 -KGRpc3Bvc2luZw== 28984 -YXJhaw== 28985 -IExlYWRlcnM= 28986 -IGFmZmVjdGluZw== 28987 -IGl0ZW1WaWV3 28988 -IGVjb25vbWljcw== 28989 -ZnY= 28990 -4LmA 28991 -LnJi 28992 -IE92ZXJhbGw= 28993 -IHdlYWx0aHk= 28994 -IGV2b2x2ZWQ= 28995 -bmRh 28996 -IEh1cw== 28997 -cmVzdHJpY3Q= 28998 -dW1lbg== 28999 -IEFncmljdWx0 29000 -IQoKCg== 29001 -IGV4cGlyZXM= 29002 -IHNwb2tlc3BlcnNvbg== 29003 -aW50ZXJ2YWw= 29004 -IMOi 29005 -IHF1ZWVu 29006 -KG5pbA== 29007 -aW5nbw== 29008 -SGVhcA== 29009 -2Y4= 29010 -IGNvbXBsYWlu 29011 -U3lt 29012 -IENsb25l 29013 -IFJ1 29014 -IFdJTEw= 29015 -IENyeXN0YWw= 29016 -L2NvbnRlbnQ= 29017 -aW5nZW4= 29018 -b2ludG1lbnQ= 29019 -TGFzdE5hbWU= 29020 -YXZpY29u 29021 -IElCTQ== 29022 -IERpbWVuc2lvbg== 29023 -YW5o 29024 -aWNpcGFudHM= 29025 -IEFubmU= 29026 -LnByb2dyZXNz 29027 -IGFsZ28= 29028 -b2JpbA== 29029 -IFZvaWNl 29030 -IEZF 29031 -IGdsaQ== 29032 -IHZlZA== 29033 -IHByZXZlbnRz 29034 -XENvbHVtbg== 29035 -IGZvbGs= 29036 -ZXR0aQ== 29037 -IG1u 29038 -IENMQVNT 29039 -IGRpc3BsYXlpbmc= 29040 -IEts 29041 -IEZlcnI= 29042 -ZHV0bw== 29043 -Lmli 29044 -IGRhZG9z 29045 -J25hbWU= 29046 -LXNwYWNl 29047 -IGl0YWxpYW4= 29048 -IGludmVyc2U= 29049 -IGRlbnNl 29050 -dXRlcg== 29051 -IElFbnVtZXJhdG9y 29052 -LXNpZ24= 29053 -IG5hdGlvbndpZGU= 29054 -IHBlcnNvbmE= 29055 -IHNvbHZlZA== 29056 -IGRyYW1hdGljYWxseQ== 29057 -TG9nb3V0 29058 -IGdyYXY= 29059 -IGFuYWx5c2Vz 29060 -b2xsbw== 29061 -IGxhbXA= 29062 -LnRlYW0= 29063 -IEVyb3Q= 29064 -PVsi 29065 -IGRhbmNpbmc= 29066 -ID8+Lw== 29067 -IGNhdGVy 29068 -ZmZl 29069 -IFNoYQ== 29070 -IEJvcw== 29071 -IFJFUVVJUkU= 29072 -IE1vbnN0ZXI= 29073 -IFJC 29074 -IElERQ== 29075 -IHN1aXRz 29076 -IGZvcm1EYXRh 29077 -KHRoZXRh 29078 -IHNwYXRpYWw= 29079 -PU5VTEw= 29080 -IFNxbENvbm5lY3Rpb24= 29081 -IOA= 29082 -IFZlbmV6 29083 -IE1vcm5pbmc= 29084 -IHB1YmxpY2F0aW9ucw== 29085 -IE5PTklORlJJTkdFTUVOVA== 29086 -Zmlyc3ROYW1l 29087 -dWRz 29088 -V291bGQ= 29089 -X0hFQUQ= 29090 -IGludmVzdGVk 29091 -c3RhYmxl 29092 -ZnJlZA== 29093 -IGNvbW1hbmRlcg== 29094 -U0VT 29095 -4oCUYQ== 29096 -YW5jaGU= 29097 -IE1vdmVtZW50 29098 -67M= 29099 -U3VpdGU= 29100 -IGp1cmlzZGljdGlvbg== 29101 -66as 29102 -IEJldGg= 29103 -alF1ZXJ5 29104 -IElzYQ== 29105 -IGRlbnRhbA== 29106 -LCo= 29107 -IExpbWl0 29108 -aWxpYXRpb24= 29109 -PSJ7 29110 -YmFzdA== 29111 -IHR1cmI= 29112 -aXN5 29113 -T09L 29114 -IGFkdm9jYXRl 29115 -aW1hZw== 29116 -TEVDVElPTg== 29117 -0LvRjA== 29118 -KGNhdGVnb3J5 29119 -LmRlYw== 29120 -IHVuaXF1 29121 -X3Nu 29122 -IGF0dHJhY3RlZA== 29123 -IMOJ 29124 -IFJ1bm5pbmc= 29125 -X2VkZ2Vz 29126 -IERpc2FibGU= 29127 -X0FT 29128 -5Zu+ 29129 -IG5ldHdvcmtpbmc= 29130 -X2JyYW5jaA== 29131 -SGF2aW5n 29132 -dG9CZVRydXRoeQ== 29133 -R0k= 29134 -IGNhbXBz 29135 -c2Vw 29136 -LXBhcnQ= 29137 -ICkKCgoKCgoKCg== 29138 -dXN0cmFsaWE= 29139 -IFJlcG9ydHM= 29140 -cml0bw== 29141 -IHdhaXN0 29142 -X3BsdXM= 29143 -IFdX 29144 -LXBlcnNvbg== 29145 -QXByaWw= 29146 -IHNhcg== 29147 -LnRhcg== 29148 -IGFncmljdWx0dXJhbA== 29149 -dGlj 29150 -IHRjcA== 29151 -IHNldFZhbHVl 29152 -YWdlbnRv 29153 -IEFwcGU= 29154 -cGlsZXI= 29155 -Q0FERQ== 29156 -IGFuY2hl 29157 -YXRjaGVy 29158 -IGNvbWljcw== 29159 -IGxicw== 29160 -X3NlZ21lbnQ= 29161 -J109JA== 29162 -aXR0ZXJz 29163 -aWNoZXI= 29164 -R0lORQ== 29165 -IHV0aWxpemU= 29166 -IEN1cnNvcg== 29167 -X2V4cHJlc3Npb24= 29168 -IGRhZw== 29169 -PGxvbmc= 29170 -IHJoeXRo 29171 -5o+Q 29172 -IGNvbnN1bHRhdGlvbg== 29173 -WWV0 29174 -IikpCgo= 29175 -X01BQw== 29176 -Y291bGQ= 29177 -ICdcXA== 29178 -IFZv 29179 -CWh0dHA= 29180 -IGdz 29181 -cGhlcg== 29182 -LWdyaWQ= 29183 -SmFtZXM= 29184 -SnVs 29185 -IHNjaG9u 29186 -IHRlbnNvcmZsb3c= 29187 -IExPR0dFUg== 29188 -YW1hcw== 29189 -IHNjaXB5 29190 -IGNvbnZpY3Rpb24= 29191 -LmFn 29192 -IGFkbWluaXN0cmF0b3I= 29193 -KSl7DQo= 29194 -IG51bg== 29195 -Imdyb3Vw 29196 -UG9y 29197 -IG51cnNl 29198 -ZXhwcmVzc2lvbg== 29199 -YWt5 29200 -IEhlYXZ5 29201 -Lm9wdA== 29202 -LmdldEFsbA== 29203 -IG92ZXJs 29204 -LyIs 29205 -X2NvdW50cnk= 29206 -544= 29207 -IEdFTkVS 29208 -X3JvdXRl 29209 -IERhbA== 29210 -wrQ= 29211 -b2xvYWQ= 29212 -IHVuY29tZm9ydGFibGU= 29213 -KG1lbnU= 29214 -IGhvc3RuYW1l 29215 -JyIpOwo= 29216 -IGNhbGN1bGF0aW9ucw== 29217 -LWNsaWNr 29218 -IHByb3RlY3RpdmU= 29219 -44Kv 29220 -X0Zvcm0= 29221 -dW5ncw== 29222 -QWN0dWFs 29223 -bWY= 29224 -IFByb2Nlc3Npbmc= 29225 -IEludmVudG9yeQ== 29226 -KG1hdHJpeA== 29227 -YXBwcm9wcmlhdGU= 29228 -d2Vn 29229 -aWph 29230 -IGNocg== 29231 -IHJpZmxl 29232 -LXdzag== 29233 -a2Fy 29234 -IGluZGVwZW5kZW50bHk= 29235 -SU9T 29236 -IGNvbnNpc3RlbmN5 29237 -dm4= 29238 -L3N5c3RlbQ== 29239 -IENoYW5nZXM= 29240 -IGV4cG9zZQ== 29241 -aWNpZW50cw== 29242 -IHJlbGF0ZQ== 29243 -CW5leHQ= 29244 -6Kg= 29245 -dWRlcw== 29246 -IGdsYXNzZXM= 29247 -RlhNTA== 29248 -Li4uLi4u 29249 -IFBkZg== 29250 -IGFwcHJvdmU= 29251 -IHtc 29252 -IGV4aXN0ZQ== 29253 -KSko 29254 -QVJFTlQ= 29255 -0L7Qvw== 29256 -IExhdGVzdA== 29257 -IE5pZ2VyaWE= 29258 -LkludGVyZmFjZXM= 29259 -IHJlbW92ZXM= 29260 -RW5lbXk= 29261 -IGVuZm9yY2U= 29262 -dmVydHM= 29263 -CXBvcw== 29264 -X3RleHR1cmU= 29265 -V0FSRA== 29266 -IElOQ0lERU5U 29267 -KGNvbnRhaW5lcg== 29268 -IGRlZmVuZGluZw== 29269 -IFJY 29270 -IEhvb2s= 29271 -YnJpcw== 29272 -IEZsYXNr 29273 -R3JheQ== 29274 -LikK 29275 -dmlzaWJpbGl0eQ== 29276 -IFJlZGlyZWN0VG9BY3Rpb24= 29277 -ZXJyYWw= 29278 -X2VsZW0= 29279 -IHJlc29u 29280 -ZnJvbnRlbmQ= 29281 -X3ZhcmlhYmxlcw== 29282 -YXRlcmlh 29283 -ICsi 29284 -YXZlbGVk 29285 -UklY 29286 -IGRlZmljaXQ= 29287 -X0NoZWNr 29288 -WVlZWQ== 29289 -VG9PbmU= 29290 -c3B5 29291 -IHVuaXRlZA== 29292 -ZW5kZW50 29293 -IHBvZGU= 29294 -44GM 29295 -Q0FU 29296 -KGZtdA== 29297 -IEJvbnVz 29298 -IHJlY2s= 29299 -wro= 29300 -TW9kdWxlcw== 29301 -IHZhY3V1bQ== 29302 -UmFkaW8= 29303 -IERBTUFHRQ== 29304 -UGVu 29305 -IFBhcmtlcg== 29306 -OzsK 29307 -IFJlYWxseQ== 29308 -X25lZw== 29309 -cGVuZGluZw== 29310 -IG5vbWluZWU= 29311 -IENhdGVnb3JpZXM= 29312 -IFVsdHJh 29313 -V2VhcG9u 29314 -IGRlZmVuZGVy 29315 -SXNz 29316 -IEdlbmRlcg== 29317 -IERyZXNz 29318 -IGltcHJpc29u 29319 -IGJhbmtydXB0 29320 -aW1lbnNpb25hbA== 29321 -UEhB 29322 -IFN0cmF0ZWc= 29323 -IFBST0ZJVFM= 29324 -IHBhdHJp 29325 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8= 29326 -ZGVsZWdhdGU= 29327 -IGZvclN0YXRl 29328 -IGRldm90ZWQ= 29329 -X21ha2U= 29330 -IHRlcnJvcmlzdHM= 29331 -IFNuYXA= 29332 -X25hdg== 29333 -IEFB 29334 -IElhbg== 29335 -CWFwcA== 29336 -UGxhY2VtZW50 29337 -X2hkcg== 29338 -PEs= 29339 -IHNhbmc= 29340 -c3Ryb2tl 29341 -LVE= 29342 -Pjw/PQ== 29343 -LW1vZGVs 29344 -YXZhbmE= 29345 -IFdhbmc= 29346 -ICAgICAgICAgICAgIAo= 29347 -CWluaXQ= 29348 -IGVudHJlcHJlbmV1cg== 29349 -YXRpdm8= 29350 -TG92ZQ== 29351 -LW92ZXI= 29352 -V2F0ZXI= 29353 -IG1vZHM= 29354 -Z2VuY2U= 29355 -VGVjaG4= 29356 -Png= 29357 -LlRhc2s= 29358 -bW9uZXk= 29359 -aWJhYmE= 29360 -J30pOwo= 29361 -IFNwZWNpZmlj 29362 -IExpbmVhcg== 29363 -X09QVA== 29364 -SGFzaENvZGU= 29365 -KFBsYXllcg== 29366 -LkNvbnRhaW5zS2V5 29367 -IGNvbGxhcHNlZA== 29368 -dHJhbnNwYXJlbnQ= 29369 -X1JBTkdF 29370 -Vmlld2Vy 29371 -KGNmZw== 29372 -IHNvcnRpbmc= 29373 -IGluZmVjdGVk 29374 -IE5hY2g= 29375 -IGFjY29tbW9kYXRl 29376 -LmVsZW1lbnRz 29377 -X1BBUlQ= 29378 -IFNleHk= 29379 -PWdldA== 29380 -KHllYXI= 29381 -IHhocg== 29382 -Ol0= 29383 -b3dza2k= 29384 -IHN1bW1hcg== 29385 -IMK/ 29386 -IGludGU= 29387 -IHdvcmtmbG93 29388 -IFRhaXdhbg== 29389 -dmVyc2lvbnM= 29390 -5Y+R 29391 -IHN1cnByaXNpbmdseQ== 29392 -IG9wdGljYWw= 29393 -IHByb2Nlcw== 29394 -IGRpc2FncmVl 29395 -IG51ZXZv 29396 -IENBTQ== 29397 -c29ydGVk 29398 -bGVhc2Vz 29399 -aXN0bGU= 29400 -SWRlbnQ= 29401 -CWV2ZW50 29402 -amVjdGVk 29403 -Q2h1bms= 29404 -VmFycw== 29405 -LnByb3ZpZGVy 29406 -IHByb2NlZWRpbmdz 29407 -IGluY2x1c2l2ZQ== 29408 -IGFydHdvcms= 29409 -ZW5kYW50cw== 29410 -77yaCg== 29411 -c2Vlbg== 29412 -IGxpZw== 29413 -IG1ha2Vycw== 29414 -X2Z1bg== 29415 -IGxlbmd0aHM= 29416 -UGF0aFZhcmlhYmxl 29417 -W2l0ZW0= 29418 -4Li1 29419 -RGVhZA== 29420 -RkZGRkZG 29421 -IFVyYmFu 29422 -dXBsZXM= 29423 -aWNoZW4= 29424 -KG51bGxwdHI= 29425 -LnNwZWM= 29426 -LFN5c3RlbQ== 29427 -VVJBVElPTg== 29428 -KGpvYg== 29429 -5byP 29430 -IHRyYWNrZXI= 29431 -xZk= 29432 -IE1S 29433 -IFNRTGl0ZQ== 29434 -IGR0bw== 29435 -IDs7Cg== 29436 -IG1pbnQ= 29437 -IEludHJvZHVjdGlvbg== 29438 -Y2Fv 29439 -IHF1ZXN0aW9uZWQ= 29440 -IGZpdHRlZA== 29441 -cmV2aXNpb24= 29442 -c3E= 29443 -IG1pZw== 29444 -X3VuaXRz 29445 -X2FzeW5j 29446 -IGZsaWNr 29447 -fSk7CgoK 29448 -IG5vdHJl 29449 -fWAs 29450 -RmlsdGVycw== 29451 -IG11bmRv 29452 -X2RheXM= 29453 -IGZybQ== 29454 -dXRj 29455 -IHZhbHM= 29456 -ZXdpZHRo 29457 -IEdlbmVyYXRvcg== 29458 -IEFydGlzdA== 29459 -IElEcw== 29460 -IEFydGljbGVz 29461 -cmVhdGVy 29462 -IENvbXBvbmVudEZpeHR1cmU= 29463 -Lj0= 29464 -IHJvdQ== 29465 -LW5v 29466 -LmJ1a2tpdA== 29467 -ZWdn 29468 -IERpZmY= 29469 -YXRpY3M= 29470 -0YPRhw== 29471 -4oCUCgo= 29472 -IENoYXJsb3R0ZQ== 29473 -Ynll 29474 -IH0pOw0KDQo= 29475 -IFZpaw== 29476 -IEJyb3c= 29477 -IGx2 29478 -IEdpYg== 29479 -LXdpbmc= 29480 -R0xJR0VOQ0U= 29481 -KEls 29482 -IEVuZ2luZWVy 29483 -LldhaXQ= 29484 -IFBpY3R1cmVz 29485 -IHJoZXQ= 29486 -IHRoZXJtYWw= 29487 -IHByYWlzZQ== 29488 -PD4oKTsKCg== 29489 -IFNwaWRlcg== 29490 -UGF1c2U= 29491 -IEJha2Vy 29492 -IHNsb3dlcg== 29493 -IH1dCg== 29494 -X2VucXVldWU= 29495 -IGRpc2FwcGVhcmVk 29496 -IFRpY2tldA== 29497 -SU5VWA== 29498 -X0xPQ0FM 29499 -0LDRgdGB 29500 -QEluamVjdGFibGU= 29501 -Y29tbXVuaXR5 29502 -R2VzdHVyZVJlY29nbml6ZXI= 29503 -5Zu9 29504 -IHNjYWxlcw== 29505 -IC0o 29506 -Lycr 29507 -IFNpdA== 29508 -IGV4ZWN1dGl2ZXM= 29509 -YXJkaW5n 29510 -IGFkdmVycw== 29511 -IGJhY2t3YXJkcw== 29512 -CWNvbnRleHQ= 29513 -IEhhbXA= 29514 -IFBG 29515 -IERlY2s= 29516 -IENyYWln 29517 -QW1lcmljYW4= 29518 -IGJlbGw= 29519 -IHByb2w= 29520 -dWZlbg== 29521 -IHJuZw== 29522 -YXJzaGFs 29523 -IFNpbXBseQ== 29524 -Zmlyc3RuYW1l 29525 -c2hvcmU= 29526 -SnVseQ== 29527 -IG1vcnRhbGl0eQ== 29528 -IOKGkgoK 29529 -SGVscGVycw== 29530 -IGJlbmNobWFyaw== 29531 -ZW1hZGU= 29532 -IG9yZ2FuaXNhdGlvbnM= 29533 -Lmdzb24= 29534 -IFRleHRGaWVsZA== 29535 -IGNpdmlsaWFucw== 29536 -LkFycmF5cw== 29537 -IE1pc3Npc3NpcHBp 29538 -IGludGVybWVkaWF0ZQ== 29539 -Z2V0VXNlcg== 29540 -X2NsdXN0ZXI= 29541 -UmVsYXRpdmU= 29542 -Zm9yZWlnbg== 29543 -LnF1ZXJ5U2VsZWN0b3JBbGw= 29544 -Rm9yZWlnbktleQ== 29545 -IHJlYXNvbmFibHk= 29546 -LS0tLS0tLS0tCg== 29547 -Q2FyZHM= 29548 -IEthbQ== 29549 -IFRob3I= 29550 -IHJvbGxlcg== 29551 -LWVsZW1lbnQ= 29552 -IEN1cnJlbmN5 29553 -ZGRpZQ== 29554 -QUxMWQ== 29555 -IFJB 29556 -IHBlcm1ldA== 29557 -YWFhYQ== 29558 -IGhvbWV3b3Jr 29559 -IFZpdA== 29560 -IG1vbGQ= 29561 -IEZlcg== 29562 -W3N0YXJ0 29563 -IHN0YXRpc3RpY2Fs 29564 -IHNjYXJ5 29565 -X0hPTUU= 29566 -LkJlZ2lu 29567 -Q29uc3RydWN0 29568 -b2dlbmlj 29569 -IERFQUxJTkdT 29570 -IHRhbWJpw6lu 29571 -aXhvbg== 29572 -LmluZA== 29573 -YWNyZQ== 29574 -IHRyYW5zZm9ybXM= 29575 -IE5hcA== 29576 -LkJsb2Nr 29577 -dXNzaWE= 29578 -cGlyYXRpb24= 29579 -dWxlbnQ= 29580 -IGNlaWw= 29581 -Q2xhdXNl 29582 -bmFpcmU= 29583 -VEVT 29584 -IG5lYXQ= 29585 -U1RE 29586 -IFJlZ0V4cA== 29587 -cGVyZm9ybQ== 29588 -Oik= 29589 -IHVuaW9ucw== 29590 -IHN1YmxpYw== 29591 -IHdpbmRz 29592 -bG9hdGluZw== 29593 -Z2xpY2g= 29594 -IHBhZ2luYXRpb24= 29595 -U2tpbGw= 29596 -QXBwbHk= 29597 -IE9wZXJhdG9y 29598 -aXN0b2dyYW0= 29599 -IHF1YWxpdGllcw== 29600 -Q3Jvc3M= 29601 -IGRlY29t 29602 -XSwi 29603 -IEp1YW4= 29604 -Lm1vZGFs 29605 -LkNoaWxk 29606 -IFJvZ2Vy 29607 -U1RJVFVURQ== 29608 -OkNHUmVjdE1ha2U= 29609 -YWxldHRl 29610 -IHN0YQ== 29611 -YXNpZGU= 29612 -IGJsdXI= 29613 -IFdh 29614 -aWZldGltZQ== 29615 -cmVlZA== 29616 -Y29udHJvbHM= 29617 -IGJpbnM= 29618 -INC/0L7Quw== 29619 -Ki8sCg== 29620 -VUlT 29621 -IFJvdQ== 29622 -IERlbW8= 29623 -LWF3ZXNvbWU= 29624 -IENoYWlu 29625 -IGhhc3Rh 29626 -IEJhcnQ= 29627 -LktFWQ== 29628 -IHZlbmRvcnM= 29629 -bm9mb2xsb3c= 29630 -IERlc3Q= 29631 -X2J1aWxkZXI= 29632 -IGFyZ3Vlcw== 29633 -X2Fuc3dlcg== 29634 -Z290bw== 29635 -IFJFU1VMVA== 29636 -IE1PTg== 29637 -IHBvZGVy 29638 -b29ucw== 29639 -X0NBU0U= 29640 -IHJlcGxpYw== 29641 -IGZpbmFuY2luZw== 29642 -IERBVEU= 29643 -Y2Vybg== 29644 -X3RyYWNr 29645 -dGllcw== 29646 -L2xvZ28= 29647 -IE5FR0xJR0VOQ0U= 29648 -Z2V0VHlwZQ== 29649 -PlQ= 29650 -YmV0 29651 -Z2lybA== 29652 -IElOQ0lERU5UQUw= 29653 -LXNpdGU= 29654 -LnRyaWdnZXI= 29655 -IExpc2E= 29656 -X2lucHV0cw== 29657 -IHJlbGF0aXZlcw== 29658 -TG9nZ2VkSW4= 29659 -Q29uZmlndXJl 29660 -SUs= 29661 -LmFjY2VwdA== 29662 -UmVzdW1l 29663 -IERyYWZ0 29664 -ICo+KA== 29665 -IFdB 29666 -ZWRpYW4= 29667 -ZXJuZXNz 29668 -IExheW91dEluZmxhdGVy 29669 -Ki8NCg0K 29670 -b3RoeQ== 29671 -IG9ibGlnYXRpb24= 29672 -U3Vic2NyaWJl 29673 -IHRodW1ibmFpbA== 29674 -ZXhpc3Q= 29675 -IGluc2lzdGVk 29676 -IFVJQ29sbGVjdGlvblZpZXc= 29677 -IEFuZ3VsYXI= 29678 -IHRhYmxldHM= 29679 -IEltcGFjdA== 29680 -44CNCgo= 29681 -YWhv 29682 -IGNoYXJhY3RlcmlzdGlj 29683 -Z2Q= 29684 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0= 29685 -b3VydA== 29686 -YC4= 29687 -QXBwcm8= 29688 -Q29vcmRpbmF0ZQ== 29689 -UmVtZW1iZXI= 29690 -IG1hcmluZQ== 29691 -XT09Jw== 29692 -IEFkbWluaXN0cmF0b3I= 29693 -LmdldERlZmF1bHQ= 29694 -IGZvcmdvdA== 29695 -IFN0cnVjdHVyZQ== 29696 -VnVl 29697 -YXJzaW5n 29698 -bW9tZW50 29699 -a3c= 29700 -X2N1cnNvcg== 29701 -QXR0YWNr 29702 -IGF0aGxldGlj 29703 -IGRpYWdub3NlZA== 29704 -IGVuZGU= 29705 -5Yig6Zmk 29706 -SG91c2U= 29707 -IFBBUkFN 29708 -IHdpa2k= 29709 -IE9wcA== 29710 -IGNvbnNlcnZhdGlvbg== 29711 -IHNuZA== 29712 -X3RlbQ== 29713 -c3Vic3Ry 29714 -IENhcGU= 29715 -LnNpbQ== 29716 -VVRJT04= 29717 -YW5hbg== 29718 -4oCZdW4= 29719 -IGd5 29720 -LXdvcms= 29721 -IGNvbXBlbGxpbmc= 29722 -PScj 29723 -CXN1Yg== 29724 -IGRpcmVjdG9yaWVz 29725 -7Yq4 29726 -IHRvdWNoZXM= 29727 -b3V0aW5lcw== 29728 -LkNvbGxlY3Rpb24= 29729 -c2NoZWR1bGU= 29730 -LmxhdA== 29731 -IERvY3RyaW5l 29732 -Q0FB 29733 -IFJlZmVy 29734 -IHNoaWZ0cw== 29735 -IGxpa2VsaWhvb2Q= 29736 -cHJldGVy 29737 -IEZlbWFsZQ== 29738 -IGludGVyY2VwdA== 29739 -IGxvdQ== 29740 -55m7 29741 -IHJ1Zw== 29742 -IENyb3du 29743 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 29744 -LXByb2R1Y3Q= 29745 -IHByb21wdGVk 29746 -dW5nbGU= 29747 -ZG9ja2Vy 29748 -IFR1 29749 -IFVuaXF1ZQ== 29750 -X0Vycm9y 29751 -dWxvcw== 29752 -IOKE 29753 -IChg 29754 -R2V0dGluZw== 29755 -X3NjYWw= 29756 -IEVuaA== 29757 -w7x0 29758 -IHN1c3RhaW5lZA== 29759 -IHBhdGNoZXM= 29760 -IHByb3NwZXI= 29761 -IEdhemE= 29762 -X2xpZ2h0 29763 -IGluY29ucw== 29764 -LS0tLS0tLS0K 29765 -CQkgICAgICA= 29766 -U0Y= 29767 -Q04= 29768 -OiI7Cg== 29769 -IENvbGxpbnM= 29770 -KCop 29771 -IGNvbXBpbGF0aW9u 29772 -J10NCg== 29773 -IGNvbnNlcXVlbmNl 29774 -LC4uLg== 29775 -IGRt 29776 -IEJMT0NL 29777 -Q2x1c3Rlcg== 29778 -IHNraQ== 29779 -KGFyZ2M= 29780 -VHVwbGU= 29781 -IGpvaW5z 29782 -IFNoZXJpZmY= 29783 -V2Fy 29784 -aW5kaQ== 29785 -IGNvbW1lbnRlZA== 29786 -SE9TVA== 29787 -IGludml0YXRpb24= 29788 -YXBhbmVzZQ== 29789 -IHBlcm1pdHM= 29790 -cHJlY2VkZW50ZWQ= 29791 -X3pvbmU= 29792 -IEFteQ== 29793 -X1JE 29794 -TWluaW11bQ== 29795 -IGludm9jYXRpb24= 29796 -LmVuYWJsZQ== 29797 -aWNodGVu 29798 -LW93bmVk 29799 -Imlk 29800 -X1BPSU5URVI= 29801 -RmFj 29802 -IHNwZWNpZmljYXRpb25z 29803 -IG5vbWluYXRpb24= 29804 -IGdw 29805 -PCg= 29806 -IHJvYm90cw== 29807 -IEplcnJ5 29808 -IGhvbGRlcnM= 29809 -IHdhbmQ= 29810 -Y21z 29811 -IH0pKQo= 29812 -LlRvYXN0 29813 -IElMaXN0 29814 -QmFzZWQ= 29815 -em9vbQ== 29816 -L3N0eWxl 29817 -IEJlY2s= 29818 -TWVu 29819 -IGNvbnRyaWJ1dGluZw== 29820 -IHVuZG8= 29821 -IE9I 29822 -IGFkZE9iamVjdA== 29823 -IGVpZ2Vu 29824 -c2lnbnVw 29825 -6ZSZ 29826 -IGRpc3RhbnQ= 29827 -UEFSQVRPUg== 29828 -IE1hcmk= 29829 -IG3DoQ== 29830 -RW1w 29831 -w7Nz 29832 -IOyImA== 29833 -ZXZ0 29834 -K2o= 29835 -cGFyaw== 29836 -IFN0YXk= 29837 -IER1bg== 29838 -IHNveQ== 29839 -PiU= 29840 -YXppbmVz 29841 -IHRpZW1wbw== 29842 -KG1l 29843 -cHJlc2VudA== 29844 -LlRoaXM= 29845 -IGVkaXRvcnM= 29846 -RklFTEQ= 29847 -Lldvcms= 29848 -IFVuaXZlcnNl 29849 -IGRydW5r 29850 -LnRpbWVy 29851 -IGFsdGVyZWQ= 29852 -IE5hcg== 29853 -66Cl 29854 -LkFjdGl2ZQ== 29855 -aWRvcg== 29856 -560= 29857 -LmRlbHRhVGltZQ== 29858 -IGF3a3dhcmQ= 29859 -JnF1b3Q= 29860 -IFNhZmFyaQ== 29861 -IHRyaWNrcw== 29862 -TUVOVFM= 29863 -ZGl2aXNpb24= 29864 -IHZhcnlpbmc= 29865 -IEhpZ2h3YXk= 29866 -IHBob3RvZ3JhcGhlcg== 29867 -IFN0ZXdhcnQ= 29868 -IGxhc3Rpbmc= 29869 -LlByZQ== 29870 -LmFtYXpvbmF3cw== 29871 -IEx1Y2s= 29872 -LkRlc2NyaXB0aW9u 29873 -IE5heg== 29874 -bmVn 29875 -IGPDsw== 29876 -PDwiXA== 29877 -IFN1cnY= 29878 -IFVuYw== 29879 -UmVjaXBl 29880 -LkJvcmRlclN0eWxl 29881 -IG1vZGlmaWNhdGlvbnM= 29882 -LWF0 29883 -QVRGT1JN 29884 -aGRy 29885 -YWtv 29886 -IHN1YmxpY2Vuc2U= 29887 -IEp1bXA= 29888 -IGJlaW0= 29889 -IE1hbmhhdHRhbg== 29890 -LmJvb2w= 29891 -X2h3 29892 -0YLRjA== 29893 -Qmlu 29894 -IGdhdGV3YXk= 29895 -IiI6 29896 -IFVJUw== 29897 -OiIr 29898 -LWRlZg== 29899 -IFJlZ3VsYXI= 29900 -L3Rlc3Rpbmc= 29901 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 29902 -c3RyaW5nc3RyZWFt 29903 -IGRpc3Bhcg== 29904 -IG1vYmls 29905 -LXJlYWQ= 29906 -IEFkYXB0ZXI= 29907 -IENoYW1waW9ucw== 29908 -IHNjaGVkdWxlcg== 29909 -IGtpbGxz 29910 -IE11bHRpcGxl 29911 -aXJyb3I= 29912 -IGdvZHM= 29913 -QURP 29914 -YWt0ZQ== 29915 -IFVzdWFyaW8= 29916 -LmNpcmN1bGFy 29917 -IHJlY2VwdA== 29918 -IEV4cHI= 29919 -IGVsZGVybHk= 29920 -IG5pY2VseQ== 29921 -IGJlc3Rl 29922 -V2FudA== 29923 -IGNsYXNzaWNhbA== 29924 -LnNwcml0ZQ== 29925 -b2JqYw== 29926 -IE1hc29u 29927 -IHNpc3RlbWE= 29928 -LkJsYWNr 29929 -ZXNv 29930 -IFplaXQ= 29931 -IGRpdmlk 29932 -IGVudGVycw== 29933 -X3N1YmplY3Q= 29934 -IFBsYW5ldA== 29935 -Lndhcm5pbmc= 29936 -IEdyYW0= 29937 -X3Rva2Vucw== 29938 -IGhvdXNlaG9sZHM= 29939 -X2N1c3RvbWVy 29940 -dXNlck5hbWU= 29941 -Y3Jvc3M= 29942 -IHBpb25l 29943 -IGFzc2lzdHM= 29944 -X1NN 29945 -aWJv 29946 -IGxveWFs 29947 -IHVzZWxlc3M= 29948 -I2VsaWY= 29949 -IFVsdGltYXRl 29950 -Q29tZQ== 29951 -Z2Vs 29952 -IGRpY2g= 29953 -eHl6 29954 -aWtlbA== 29955 -b2JyYQ== 29956 -X3NjYW4= 29957 -IEludGVyaW9y 29958 -IE5pY2U= 29959 -IHBsYWM= 29960 -CXRhcmdldA== 29961 -IHZpcmFs 29962 -YXNzbw== 29963 -KCkv 29964 -dW5kZQ== 29965 -IEFkb2Jl 29966 -T3M= 29967 -dmlzaXRlZA== 29968 -IE9X 29969 -IEZlZWQ= 29970 -IFNlcXVlbmNl 29971 -IG1hbmFnZXM= 29972 -aW5zb24= 29973 -IExvdWlzaWFuYQ== 29974 -e30p 29975 -IEhhYg== 29976 -IExE 29977 -IGJpcA== 29978 -cHJpdGVz 29979 -KGVsZW0= 29980 -LmhpYmVybmF0ZQ== 29981 -w6lsw6k= 29982 -IG9obmU= 29983 -X3RyYW5zYWN0aW9u 29984 -IGFubnVuY2k= 29985 -UHVibGlzaGVk 29986 -IEhvbmRh 29987 -IFRhbQ== 29988 -IFBhY2tldA== 29989 -X3NlbGVjdG9y 29990 -IGNoYWxsZW5nZWQ= 29991 -UHJvY2Vzc2luZw== 29992 -LWhvdmVy 29993 -IHRyYWluZXI= 29994 -X2NhbmNlbA== 29995 -IE5TRGljdGlvbmFyeQ== 29996 -YWJyaWM= 29997 -IE1MUw== 29998 -X3NlbnNvcg== 29999 -IHNocmluaw== 30000 -IEZY 30001 -dGhyZXNob2xk 30002 -CUhY 30003 -LW1hcms= 30004 -YC5g 30005 -U2NoZW1l 30006 -KGZ1bGw= 30007 -X3dyaXRlcg== 30008 -IFN5cw== 30009 -IGZsZWQ= 30010 -IENpbg== 30011 -LXdpZGdldA== 30012 -IFByZXZpb3Vz 30013 -R2VuZGVy 30014 -X3F1ZXN0aW9u 30015 -RmVlZA== 30016 -IHNjcnV0 30017 -KHByZWZpeA== 30018 -44CC44CC 30019 -IGluZmVjdGlvbnM= 30020 -UGFydHM= 30021 -IGhpZXJhcmNoeQ== 30022 -X0RFTEVURQ== 30023 -IFBhdGllbnQ= 30024 -X3BheQ== 30025 -IHByb21vdGVk 30026 -IOyL 30027 -IGNpdmlsaWFu 30028 -IGFncmljdWx0dXJl 30029 -IFBpZWNl 30030 -IHN0YW5jZQ== 30031 -dXRzY2hl 30032 -QXNzaWdu 30033 -LkFDVElPTg== 30034 -Rmln 30035 -X3JhZGl1cw== 30036 -IFN5bmM= 30037 -ZHVjZXI= 30038 -ZmFpbHVyZQ== 30039 -ZW5zZWQ= 30040 -cHRpbWU= 30041 -Qk0= 30042 -X2RhdGV0aW1l 30043 -cXVpdm8= 30044 -UVVFVUU= 30045 -6ICF 30046 -QXBwZWFy 30047 -IHN1bW1pdA== 30048 -OnZvaWQ= 30049 -IHZpbmU= 30050 -6K6k 30051 -b25uZQ== 30052 -X1RSQU5T 30053 -LmdyZWVu 30054 -X2Nj 30055 -IGh1bmdyeQ== 30056 -ICI+ 30057 -KCkpOw0KDQo= 30058 -RXh0cmFjdA== 30059 -aXplbnM= 30060 -IHNvbHZlcg== 30061 -Tm90aWZ5 30062 -IGVuZ2xpc2g= 30063 -IFNob3BwaW5n 30064 -aW50ZXJmYWNlcw== 30065 -UkVR 30066 -IGlsbGVn 30067 -IFVJSW1hZ2VWaWV3 30068 -IGRpc2Nvbm5lY3Q= 30069 -IFVudGls 30070 -IENvbnNlcnZhdGl2ZQ== 30071 -QENvbHVtbg== 30072 -IHNoaWZ0ZWQ= 30073 -IDoNCg== 30074 -IGZpY2g= 30075 -IGRsYQ== 30076 -IHNob2U= 30077 -IiksDQo= 30078 -dWxhcml0eQ== 30079 -X1JFU1A= 30080 -V2VhdGhlcg== 30081 -VUlBcHBsaWNhdGlvbg== 30082 -Lml0ZXJhdG9y 30083 -IGFnaW5n 30084 -LlBhcmVudA== 30085 -b3dpZQ== 30086 -KGVxdWFs 30087 -IENvbnY= 30088 -L2RlZmF1bHQ= 30089 -IG1lYXN1cmluZw== 30090 -LnByZXY= 30091 -LklzVmFsaWQ= 30092 -LkZhdA== 30093 -IHPEgw== 30094 -a2V5d29yZHM= 30095 -d2l0aG91dA== 30096 -IHNvdmVyZQ== 30097 -IGV4Y2hhbmdlcw== 30098 -IG1lbHQ= 30099 -IGlzbGFuZHM= 30100 -IEludGVncg== 30101 -IGp1bXBpbmc= 30102 -IGdsZQ== 30103 -IGpvdXJuYWxpc20= 30104 -IGRhdGVk 30105 -TG9jYWxpemVk 30106 -IFJlZnJlc2g= 30107 -UGFydGljbGU= 30108 -IGFh 30109 -IFNUUklDVA== 30110 -IGJvZA== 30111 -LlByb2Nlc3M= 30112 -X0FVVE8= 30113 -IFB1Ymxpc2hlZA== 30114 -ZXZlcnk= 30115 -IHRlY2hub2xvZ2ljYWw= 30116 -bHN4 30117 -IGlycml0 30118 -QWRkaXRpb25hbA== 30119 -IGRlbGltaXRlcg== 30120 -X2xhbmd1YWdl 30121 -LWFyZWE= 30122 -Ym95cw== 30123 -IFR1YmU= 30124 -IHdhdA== 30125 -IG1lY2hhbmljcw== 30126 -X293bmVy 30127 -U3BlbGw= 30128 -IFN0b3JpZXM= 30129 -LkFwcGVuZExpbmU= 30130 -VGFibGVWaWV3 30131 -aGVt 30132 -c3RpY2s= 30133 -b2xsb3dlcg== 30134 -SUZG 30135 -IFVW 30136 -b2xsaXNpb24= 30137 -U1VC 30138 -IGNvbXBhcmFibGU= 30139 -IGRvbmRl 30140 -c2FsZXM= 30141 -bGx2bQ== 30142 -IH1dLAo= 30143 -T1RUT00= 30144 -IFB1cnBvc2U= 30145 -TGFi 30146 -IGludGVydmlld2Vk 30147 -b2lz 30148 -YXNpbA== 30149 -LnNldElk 30150 -IEluc3RydWN0aW9u 30151 -LS0+ 30152 -IE1vZGlmaWVk 30153 -YXRpb25hbGx5 30154 -IE1lZXRpbmc= 30155 -6K+v 30156 -I3JlZ2lvbg== 30157 -IHJvdXRpbmc= 30158 -LmZvY3Vz 30159 -IFlvdXRo 30160 -PEQ= 30161 -IE5hZw== 30162 -Y29udGFjdHM= 30163 -IGZvcm1pbmc= 30164 -IG1pZQ== 30165 -JyxbJy4uLw== 30166 -IEJQ 30167 -IGFwcGV0 30168 -IFRlYWNoZXI= 30169 -IFRQ 30170 -IGFubnVhbGx5 30171 -b3V0ZWRFdmVudEFyZ3M= 30172 -IFNwZWFrZXI= 30173 -IHJlbmFtZQ== 30174 -Q0ZH 30175 -KCIvLw== 30176 -5o6l 30177 -L3BhZ2Vz 30178 -IHByw6lz 30179 -IFNwZWxs 30180 -LkFsbG93 30181 -IElOVEVSUlU= 30182 -ICgj 30183 -4oCZCgo= 30184 -X0dlbmVyaWM= 30185 -Lmltc2hvdw== 30186 -X3RpbQ== 30187 -LWZhY2U= 30188 -KCYo 30189 -YXRpbnVt 30190 -IHJldm9sdXRpb25hcnk= 30191 -IEhvdXJz 30192 -cmFpbg== 30193 -IGFueXRpbWU= 30194 -IGFiYg== 30195 -LmpzcA== 30196 -U2Nyb2xsVmlldw== 30197 -IFRydXRo 30198 -IGFudGljaXBhdGVk 30199 -IGFjY2VudA== 30200 -LmNoZWNrZWQ= 30201 -IHNwZWNpZmllcw== 30202 -IGNhZg== 30203 -IGNlbGxwYWRkaW5n 30204 -IGNvb2tlZA== 30205 -IEh1Z2g= 30206 -cGVlaw== 30207 -X1JBVEU= 30208 -IGRvcm0= 30209 -Lw0K 30210 -SVZJVFk= 30211 -LkNvbnRyb2xsZXI= 30212 -KHBhcnQ= 30213 -LmNvbnN0cmFpbnQ= 30214 -IGludmFzaW9u 30215 -TU9WRQ== 30216 -IGdsdWM= 30217 -bGVuYW1l 30218 -IGFtZW4= 30219 -ZW5nbGlzaA== 30220 -IFN3aXR6ZXJsYW5k 30221 -IjsKCgo= 30222 -cGVzdA== 30223 -LmNvbGxlY3Q= 30224 -Tmli 30225 -IERpY3Q= 30226 -IEVtYg== 30227 -KHN1YmplY3Q= 30228 -IG91dHJhZ2U= 30229 -IGRlY2lkaW5n 30230 -IHNlbnRlbmNlZA== 30231 -RmVjaGE= 30232 -IkE= 30233 -IHF1ZXI= 30234 -IGZvbnRGYW1pbHk= 30235 -IHF1YWRy 30236 -LVk= 30237 -X0NBQ0hF 30238 -IGFuYWx5emVk 30239 -IGdhaW5pbmc= 30240 -IEFnYWluc3Q= 30241 -IFNvdWw= 30242 -dGF1 30243 -IGxpZ2h0d2VpZ2h0 30244 -IFRG 30245 -IEVmZmVjdHM= 30246 -LlR5cGVz 30247 -LmFkZENsYXNz 30248 -IHZlZ2Fu 30249 -6YE= 30250 -Lici 30251 -IEV4cGxvcmVy 30252 -LmRldGVjdA== 30253 -LnNoaWZ0 30254 -IG9ibGlnYXRpb25z 30255 -bGFzdE5hbWU= 30256 -IGFzc29jaWF0aW9ucw== 30257 -IFRpbWVTcGFu 30258 -dW50ZXI= 30259 -IEZyZXNo 30260 -Q29tcGF0aWJsZQ== 30261 -UHVi 30262 -aWRnZXM= 30263 -Lm9wdGlvbg== 30264 -dmFyaQ== 30265 -Lmhhc2hDb2Rl 30266 -IGdlYg== 30267 -LnNlY3Rpb24= 30268 -LW5vdA== 30269 -IFN1Ym1pdA== 30270 -VE4= 30271 -cmVnaXN0cnk= 30272 -X21lZGlh 30273 -IG5hag== 30274 -ZmZ0 30275 -IG1hdGU= 30276 -LXRoaXJk 30277 -IHBvY2tldHM= 30278 -ZXN0YQ== 30279 -IGJlbnQ= 30280 -IE5vcmQ= 30281 -IHJldGFpbGVycw== 30282 -IE1vcnJpcw== 30283 -LiIiIgoK 30284 -V3Jvbmc= 30285 -IMWb 30286 -UmF5 30287 -LmVj 30288 -IEJpbmQ= 30289 -X0hBTkQ= 30290 -KG5vbg== 30291 -aXNWYWxpZA== 30292 -IHNpbWlsYXJseQ== 30293 -X0xJTUlU 30294 -IGR5bmFtaWNz 30295 -IGRpc3RpbmN0aW9u 30296 -44GG 30297 -PE4= 30298 -IG9ydGg= 30299 -IFRveW90YQ== 30300 -IEthdGU= 30301 -IExT 30302 -b3JpZQ== 30303 -IFNwcmluZ3M= 30304 -IGZyZWFr 30305 -bGFzdG5hbWU= 30306 -X01VTFQ= 30307 -LXN0ZXA= 30308 -Iig= 30309 -QUREUg== 30310 -IGVudGVydGFpbmluZw== 30311 -X0NPTkY= 30312 -IGRlY29kZWQ= 30313 -IHN0cmVhaw== 30314 -IHdhaXRlZA== 30315 -IG5vdGlmaWVk 30316 -cm9kdWNlZA== 30317 -dmlzdWFs 30318 -LkxheW91dFBhcmFtcw== 30319 -5rA= 30320 -ZXNpYW4= 30321 -Zml0cw== 30322 -c3ByaW5n 30323 -IEJlcm5pZQ== 30324 -VXNlckRlZmF1bHRz 30325 -IHBlZGVzdA== 30326 -QXBwZWFyYW5jZQ== 30327 -IFdpa2k= 30328 -IE5PVElDRQ== 30329 -IHNzaA== 30330 -IGR1cmFudGU= 30331 -IFppcA== 30332 -xLFy 30333 -IE5BVE8= 30334 -IHR3ZWx2ZQ== 30335 -IHJveWFs 30336 -77g= 30337 -IG1lcmNoYW50 30338 -IEZ1cm5pdHVyZQ== 30339 -J10pLAo= 30340 -LFg= 30341 -IGZvbGRlcnM= 30342 -IEdhdGU= 30343 -CWZ1bmM= 30344 -cGljaw== 30345 -X3VzdWFyaW8= 30346 -IFZlcm0= 30347 -bWVudGlvbg== 30348 -dXJwb3Nl 30349 -IGFsZXJ0cw== 30350 -eGlvdXM= 30351 -X3NpZw== 30352 -IEZ1 30353 -ICg6 30354 -IGR1bWI= 30355 -5YWz 30356 -IGFjY3VyYXRlbHk= 30357 -6YeN 30358 -UkI= 30359 -LXNjcmVlbg== 30360 -IFZFUg== 30361 -am91cg== 30362 -IHJvbWFuY2U= 30363 -dWNjZWVk 30364 -LmNob2ljZQ== 30365 -IGFkaXA= 30366 -X2RpbXM= 30367 -U2VyaWFsaXphYmxl 30368 -44KL 30369 -LmpvYg== 30370 -IHByb2c= 30371 -dWNoYXI= 30372 -IGdlbnRseQ== 30373 -IFJTUw== 30374 -aWN0dXJlZA== 30375 -X0VOQUJMRUQ= 30376 -CWxhYmVs 30377 -YXdrcw== 30378 -IEVuc3VyZQ== 30379 -cmVtZW1iZXI= 30380 -7KCV 30381 -IHRyYW5zbWl0 30382 -e3sk 30383 -LlRyYW5zYWN0aW9u 30384 -dXJzZQ== 30385 -X3JlbGF0aXZl 30386 -IHNpemVk 30387 -IFhY 30388 -IFByaW5jZXNz 30389 -IExhcnJ5 30390 -IHByw7M= 30391 -INGB0YLRgA== 30392 -IHNpc3RlcnM= 30393 -ZXN0cnVjdA== 30394 -IGNoZWNrcG9pbnQ= 30395 -Omxlbmd0aA== 30396 -IENhcmxvcw== 30397 -L2ljb24= 30398 -X1RBUkdFVA== 30399 -VG9rZW5z 30400 -IHBhdGllbmNl 30401 -IFNlbGVjdGVk 30402 -cXR5 30403 -LnNob3dNZXNzYWdl 30404 -IHdpbGRsaWZl 30405 -IFByb3Bz 30406 -Ym0= 30407 -LWFycm93 30408 -IHBhcmNlbA== 30409 -ZmlyZWJhc2U= 30410 -IEJlbmphbWlu 30411 -Y2Vzc28= 30412 -LnRpbQ== 30413 -IEdhcmM= 30414 -LmFueQ== 30415 -IEhPV0VWRVI= 30416 -IEtv 30417 -IGdyYWJiZWQ= 30418 -X2ZyYW1lcw== 30419 -IG9iamVjdEF0SW5kZXg= 30420 -IEFEVklTRUQ= 30421 -IHN1YnVy 30422 -CUdM 30423 -IH0pfQo= 30424 -LWxlbmd0aA== 30425 -7Iuc 30426 -IFBvdHRlcg== 30427 -X2J1ZmY= 30428 -Lmd1aQ== 30429 -IEVuY29kaW5n 30430 -RWxlY3Q= 30431 -LW1lc3NhZ2U= 30432 -IO+/vQ== 30433 -IMiZaQ== 30434 -IEFyZ3VtZW50TnVsbEV4Y2VwdGlvbg== 30435 -0LDRhtC4 30436 -IG1pbmltaXpl 30437 -IHJlc3BvbmRpbmc= 30438 -JF9bJw== 30439 -IEluZGl2aWR1YWw= 30440 -w6Fj 30441 -IElOVEVS 30442 -IG1hc3R1cmI= 30443 -IEJpbg== 30444 -KCck 30445 -65Oc 30446 -IG9wZW5seQ== 30447 -ID48 30448 -IHVudG8= 30449 -b2xvZ2ljYWxseQ== 30450 -IE11bA== 30451 -VklESUE= 30452 -IHNsaW0= 30453 -IENvbW1pc3Npb25lcg== 30454 -KG9u 30455 -IHVuZGVybmVhdGg= 30456 -L2Ri 30457 -dm90ZQ== 30458 -KE1lc3NhZ2U= 30459 -IFBvcGU= 30460 -RGVmaW5lZA== 30461 -IHN3aWZ0 30462 -dXJm 30463 -IGFkYXB0ZWQ= 30464 -U0VM 30465 -IHJldmVudWVz 30466 -IGRpdmluZQ== 30467 -PXk= 30468 -R3JhZGllbnQ= 30469 -X2FjdA== 30470 -IC8qITw= 30471 -IHBvbHlnb24= 30472 -IEZEQQ== 30473 -IENhcnI= 30474 -YXRhYmxlcw== 30475 -KHN0ZG91dA== 30476 -IHJlZnJpZ2Vy 30477 -IGNvb3JkaW4= 30478 -YXZvcml0ZXM= 30479 -0YjQuA== 30480 -IGNvbXBhc3Npb24= 30481 -IFBPU1NJQklMSVRZ 30482 -LXNlY29uZGFyeQ== 30483 -dXJhY3k= 30484 -IGNvbXByb21pc2U= 30485 -X0FW 30486 -X29z 30487 -IGJlc2lkZQ== 30488 -g50= 30489 -IGxu 30490 -LnBsdWdpbnM= 30491 -Q2FwYWNpdHk= 30492 -YWxhaA== 30493 -LmJpbg== 30494 -IENSQw== 30495 -X2JhbGFuY2U= 30496 -IGZsZXhEaXJlY3Rpb24= 30497 -IGFtYml0 30498 -IG5pY2tuYW1l 30499 -IEZvcmNlcw== 30500 -Q0xF 30501 -IFNoZWxs 30502 -IHNhaWw= 30503 -IFdyaXRlcg== 30504 -IEFsaWNl 30505 -ZHc= 30506 -IEluZGlhbnM= 30507 -IE1hcnNoYWxs 30508 -X1NSQw== 30509 -IG5vcm1hbGl6ZWQ= 30510 -IEphZw== 30511 -44KS 30512 -emVpdA== 30513 -cnBj 30514 -w61j 30515 -LmlubGluZQ== 30516 -IHRyYXZlcnM= 30517 -X251bWVyaWM= 30518 -IHV0aWxpdGllcw== 30519 -IGV2YWM= 30520 -SU5QVVQ= 30521 -CXJlZ2lzdGVy 30522 -TVg= 30523 -IENhbXBiZWxs 30524 -IGRhdGFzZXRz 30525 -IGRlbWFuZGVk 30526 -IGluaXRpYWxTdGF0ZQ== 30527 -Z2Fu 30528 -IGVp 30529 -VW5leHBlY3RlZA== 30530 -LXdlYg== 30531 -dHJhaXQ= 30532 -LFk= 30533 -IFRvZGQ= 30534 -IHNrZWxldG9u 30535 -IG9wdGltaXpl 30536 -56ys 30537 -IFVwb24= 30538 -IFN0T2JqZWN0 30539 -IGFwbGlj 30540 -Lic8Lw== 30541 -QUND 30542 -YWxvdXM= 30543 -IGhhc2hDb2Rl 30544 -IEJpYg== 30545 -SU5BTA== 30546 -IGludmlzaWJsZQ== 30547 -IGhldGVy 30548 -IHNhZmVy 30549 -fS8v 30550 -LnRoZW1l 30551 -Lm5hdmlnYXRpb25Db250cm9sbGVy 30552 -X21lc2g= 30553 -c2tpbGw= 30554 -IFZpb2w= 30555 -wrI= 30556 -IEVPRg== 30557 -IEtp 30558 -eW1tZXRyaWM= 30559 -IG1heGxlbmd0aA== 30560 -xaM= 30561 -ZnJpZW5kcw== 30562 -IEV2YW5z 30563 -IGxlbW9u 30564 -ICgu 30565 -U2xpZGU= 30566 -IFRoYWlsYW5k 30567 -IENhbm4= 30568 -IGFtZW5k 30569 -IGNpcg== 30570 -IHNpbGx5 30571 -ZXNpbWFs 30572 -X3BpYw== 30573 -cHJvY2Vzc29y 30574 -SmF2YVNjcmlwdA== 30575 -IGV2aWRlbnQ= 30576 -X2Rp 30577 -PlA= 30578 -dnJvbg== 30579 -LlVO 30580 -IHBhaW50ZXI= 30581 -aXphcnJl 30582 -IGxhdg== 30583 -IHBvbQ== 30584 -cHJlZw== 30585 -PWZ1bmN0aW9u 30586 -KHNlcmlhbA== 30587 -aWZpY2E= 30588 -dW1pbmc= 30589 -5Zyw 30590 -44GC 30591 -LW9w 30592 -VUNI 30593 -IEhlbmQ= 30594 -LnByb3BUeXBlcw== 30595 -IHlv 30596 -IHJvdXRpbmVz 30597 -IGNhcmluZw== 30598 -U2Vt 30599 -IHJlc2VydmVz 30600 -IHByaW9yaXRpZXM= 30601 -cmVkaXRz 30602 -SVNUUg== 30603 -Q29udGVudFR5cGU= 30604 -IFNjaHc= 30605 -L21lZGlh 30606 -IGVzdHI= 30607 -IGNsaW1iaW5n 30608 -LXdlZWs= 30609 -Y2hlcmNoZQ== 30610 -c2Vuc29y 30611 -VG9BcnJheQ== 30612 -IE1vbnRyZWFs 30613 -IGNsb3Vkcw== 30614 -IEluamVjdGFibGU= 30615 -IFJpY2U= 30616 -IHByb3BhZ2FuZGE= 30617 -X3Byb3ZpZGVy 30618 -IGluZG9vcg== 30619 -IGluYXVn 30620 -IGRpcGxvbQ== 30621 -IG1lc3NhZ2luZw== 30622 -X211dA== 30623 -5aaC 30624 -IGt3 30625 -T05T 30626 -YXJpYW5z 30627 -UlBD 30628 -KV0NCg== 30629 -LXJheQ== 30630 -IFNvcg== 30631 -bWFsbA== 30632 -IG1hcmtldHBsYWNl 30633 -IHZ0aw== 30634 -TWE= 30635 -b2dhbg== 30636 -aWdp 30637 -IHNwb25zb3JlZA== 30638 -IERhbmk= 30639 -LlNFVkVS 30640 -PicuJA== 30641 -bXVsdGlwYXJ0 30642 -IFdvbA== 30643 -IHRhYmxlTmFtZQ== 30644 -IFVzZXJuYW1l 30645 -QmFja2dyb3VuZENvbG9y 30646 -IGZyaWdodA== 30647 -X0VNQUlM 30648 -U2VwdGVtYmVy 30649 -X3ZhbHM= 30650 -b3BpYQ== 30651 -IHNwb3R0ZWQ= 30652 -LUNo 30653 -IGRhdGFTb3VyY2U= 30654 -LyIK 30655 -0LXQutGC 30656 -IFJlcXVlc3RNZXRob2Q= 30657 -IFJlcGxhY2U= 30658 -LWRv 30659 -YWhu 30660 -IFBoRA== 30661 -XS4KCg== 30662 -Tk9O 30663 -Z2VtZW50 30664 -IFRocg== 30665 -IHF1aWV0bHk= 30666 -IHRvcnR1cmU= 30667 -IHRlYXM= 30668 -IENZ 30669 -IGF0cg== 30670 -ZGV2ZWxvcG1lbnQ= 30671 -LWRldGFpbA== 30672 -IGxpZ2h0ZXI= 30673 -IGFyZ3Vpbmc= 30674 -IGRlc2VydmVz 30675 -IGN1cnJpY3VsdW0= 30676 -X0NPTlRFWFQ= 30677 -xYJ5 30678 -SElURQ== 30679 -CUlE 30680 -L3VwbG9hZHM= 30681 -IHRpdHM= 30682 -cmVv 30683 -X2Ryb3A= 30684 -LlVURg== 30685 -IHBpY2t1cA== 30686 -IGdyb2Nlcnk= 30687 -IFB1cmU= 30688 -IGVhc2llc3Q= 30689 -UGhpbA== 30690 -LmZlYXR1cmU= 30691 -KCIq 30692 -IGludmVzdG9y 30693 -dG9r 30694 -IGphcg== 30695 -TG9z 30696 -4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU 30697 -LnF1ZXVl 30698 -LXNwZWVk 30699 -TWFs 30700 -dW1ibHI= 30701 -IENPTlNU 30702 -IEhSRVNVTFQ= 30703 -IERhbmNl 30704 -KGZpbGVQYXRo 30705 -IGF0dHJpYnV0ZWQ= 30706 -4KWN 30707 -IEJ1bmQ= 30708 -Y29pbnM= 30709 -IHPDo28= 30710 -IHBpcg== 30711 -cGVyc29uYWw= 30712 -IHByZWxpbQ== 30713 -IHByb3Bvc2U= 30714 -IFRM 30715 -XV0p 30716 -IFN1YnNjcmlwdGlvbg== 30717 -IEtyZQ== 30718 -LGxlbg== 30719 -LkZpcnN0T3JEZWZhdWx0 30720 -KS0t 30721 -X3Byb2R1Y3Rz 30722 -LkdldEJ5dGVz 30723 -U2hpcA== 30724 -IGVuY3J5cHQ= 30725 -IFNH 30726 -IE15c3Q= 30727 -aGly 30728 -IGl0ZXJhdGU= 30729 -IGludGVuZA== 30730 -Lm1vY2tpdG8= 30731 -IGNoYXB0ZXJz 30732 -KGFuZ2xl 30733 -IFZsYWQ= 30734 -6K6+ 30735 -Jy4KCg== 30736 -UmVzcG9uc2VCb2R5 30737 -IEFiZA== 30738 -ZGVhbA== 30739 -IGJhcnJpZXJz 30740 -LW91dGxpbmU= 30741 -YmlsbA== 30742 -IEZhbGxz 30743 -X3NlY29uZA== 30744 -LmluY2x1ZGU= 30745 -LmNlaWw= 30746 -IG9jY3VwYXRpb24= 30747 -cGhvbnk= 30748 -Lm1vdmVUbw== 30749 -IEplbm5pZmVy 30750 -QVNURVI= 30751 -OyI+PA== 30752 -IEVuYWJsZWQ= 30753 -IHRlcm1pbmF0ZQ== 30754 -IElv 30755 -bGF0aW9ucw== 30756 -IFRIRU9SWQ== 30757 -IGVhcmxpZXN0 30758 -IHJhY2s= 30759 -IFNjYXI= 30760 -c2hha2U= 30761 -Y2hpcA== 30762 -IHV2 30763 -IGFsbGlhbmNl 30764 -0L/QuNGB 30765 -IEdPT0RT 30766 -emlvbmU= 30767 -IFZJ 30768 -IHst 30769 -IGZpbHRlcmluZw== 30770 -IG1pc2Nvbg== 30771 -LkRvY2tTdHlsZQ== 30772 -IGJ1c2g= 30773 -IGp1bms= 30774 -5ow= 30775 -IFFVRQ== 30776 -IGhvb2tz 30777 -IGZpcm13YXJl 30778 -IG1pZGRsZXdhcmU= 30779 -ZGlj 30780 -IE9ha2xhbmQ= 30781 -IGFycml2ZXM= 30782 -UGF5bG9hZA== 30783 -cGl4ZWw= 30784 -XXw= 30785 -IHN0YXJ0RGF0ZQ== 30786 -LlBSTw== 30787 -X2F1ZGlv 30788 -IG1pZGZpZWxk 30789 -aWdpZGJvZHk= 30790 -IFN3aXNz 30791 -IENsaXA= 30792 -IER1bXA= 30793 -IFRleHRCb3g= 30794 -IGdlaA== 30795 -eWllbGQ= 30796 -b2Rz 30797 -IHJlZmVyZW5kdW0= 30798 -QmFja2VuZA== 30799 -IENyZWFt 30800 -IGRvbWluYXRlZA== 30801 -IEFyY2hpdmU= 30802 -IHJpZGVycw== 30803 -LnByZXBhcmVTdGF0ZW1lbnQ= 30804 -IHF1YW5kbw== 30805 -IGNoZWY= 30806 -d2lraQ== 30807 -aW5lbA== 30808 -YW1wbGluZw== 30809 -KCJcXA== 30810 -IHNhZw== 30811 -X3Byb3h5 30812 -44GV 30813 -cGRv 30814 -LmdldEVsZW1lbnRzQnlUYWdOYW1l 30815 -IGRlbW9uc3RyYXRpb24= 30816 -IE5QQw== 30817 -IGFyY2hpdm8= 30818 -ZW5kYW5jZQ== 30819 -IGVmZmljaWVudGx5 30820 -KGFjdHVhbA== 30821 -LnRhYmxlVmlldw== 30822 -IG11c2g= 30823 -IGJlYXJz 30824 -X3RocmVhZHM= 30825 -amFz 30826 -YWh1bg== 30827 -IG5ldXJhbA== 30828 -IGRlc2lnbmluZw== 30829 -IEdEUA== 30830 -IGxpZnRlZA== 30831 -55uu 30832 -IEpvaW50 30833 -IEluY2x1ZGU= 30834 -IEdpYW50cw== 30835 -IHdpdGhkcmF3YWw= 30836 -IFJlbnQ= 30837 -bmF0aXZl 30838 -IFNlZWs= 30839 -Z3Jlc3Npb24= 30840 -X0NQVQ== 30841 -XFM= 30842 -IFNoaWVsZA== 30843 -IHNvbGlj 30844 -IGJvb20= 30845 -eWVjdG8= 30846 -IG1hbnVmYWN0dXJl 30847 -IOKAiw== 30848 -IGJib3g= 30849 -IGVhcnRocXU= 30850 -b2xsZWN0b3Jz 30851 -OkAiJQ== 30852 -IGxvb3Bz 30853 -SmU= 30854 -YWxraW5n 30855 -IFdoYXRz 30856 -IEJveXM= 30857 -LmJvb2s= 30858 -QVJHRQ== 30859 -X3BpeGVs 30860 -IHN1c3BlY3Rz 30861 -zrk= 30862 -dXNw 30863 -IEJNVw== 30864 -aWVjZXM= 30865 -KHBlcnNvbg== 30866 -5byA 30867 -6bs= 30868 -IFBvZGNhc3Q= 30869 -IGJvdQ== 30870 -KEl0ZW0= 30871 -w7s= 30872 -KElucHV0 30873 -SHR0cEdldA== 30874 -IGJ1cmc= 30875 -KV4= 30876 -Qk9BUkQ= 30877 -Ki8s 30878 -IGd1bHA= 30879 -IEJlbm4= 30880 -IGRlY2tz 30881 -LnN0YXR1c0NvZGU= 30882 -IGFjdXRl 30883 -IGh1Zw== 30884 -dWd1 30885 -IHBsZWQ= 30886 -LCIl 30887 -aGFwZQ== 30888 -INC30LDQvw== 30889 -IE1haW5l 30890 -LnJlYWw= 30891 -IGRhbGFt 30892 -IE1pbm9y 30893 -LkZsb2F0 30894 -ZGlzcA== 30895 -IHRs 30896 -IGVuY291bnQ= 30897 -PT4k 30898 -IGZn 30899 -dGVlcw== 30900 -IFJlY29tbQ== 30901 -w6Rs 30902 -IGNoZW1pc3RyeQ== 30903 -QmxvY2tz 30904 -T0lE 30905 -IGZvcmV4 30906 -IEFwcGVuZA== 30907 -IHsq 30908 -IFN1cHBseQ== 30909 -Q0dGbG9hdA== 30910 -KGJs 30911 -IGF0ZQ== 30912 -YWRvcmE= 30913 -IGd1c3Q= 30914 -QXNzb2Np 30915 -Pi4K 30916 -RkVUQ0g= 30917 -LnNlcmlhbA== 30918 -d2lkZ2V0cw== 30919 -YXJkbGVzcw== 30920 -aWVmcw== 30921 -X0ZVTEw= 30922 -ZXJuZXRlcw== 30923 -IFByZWQ= 30924 -2K0= 30925 -5LqL 30926 -dWJlcm5ldGVz 30927 -IExhdXJh 30928 -IGxhYmVsZWQ= 30929 -SGlnaGxpZ2h0 30930 -IGFubm95aW5n 30931 -L3VwZGF0ZQ== 30932 -KGRlc2NyaXB0aW9u 30933 -IGludGltaWQ= 30934 -JGM= 30935 -IikpKQo= 30936 -LkFQ 30937 -IFtdKg== 30938 -IEVYSVQ= 30939 -Lkhvc3Q= 30940 -IE9QRU4= 30941 -LnNlbmRNZXNzYWdl 30942 -X2NhbWVyYQ== 30943 -X3RpbGU= 30944 -IHRoZXJt 30945 -b25vbW91cw== 30946 -IGRpc2Fkdg== 30947 -IG5hYXI= 30948 -aW5kZXhPZg== 30949 -IFBQ 30950 -LnByb3RvY29s 30951 -QUZF 30952 -IHRleHR1cmVz 30953 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMj 30954 -dW1iYWk= 30955 -LnN0YXRz 30956 -IEdF 30957 -IGll 30958 -IFNURA== 30959 -IE1hbm4= 30960 -LnJlZmxlY3Q= 30961 -S0I= 30962 -IGRpdmU= 30963 -Lndhdg== 30964 -LyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 30965 -L3NldHRpbmdz 30966 -LmxpZmVjeWNsZQ== 30967 -IGRhdWdodGVycw== 30968 -b3J1cw== 30969 -dWJlcg== 30970 -TklORw== 30971 -c3RyaQ== 30972 -IFRpcA== 30973 -IHpu 30974 -IHN3aXRjaGVk 30975 -aW5ldA== 30976 -dWZmeQ== 30977 -IFRyYW5zcG9ydGF0aW9u 30978 -KGNvbmY= 30979 -ZnJpY2E= 30980 -IFhM 30981 -IExlYWQ= 30982 -X3BlcmNlbnQ= 30983 -PE1hcA== 30984 -IHRocnVzdA== 30985 -b3Ji 30986 -aWtr 30987 -IHRyYXVtYQ== 30988 -QWNjZXNzb3I= 30989 -IEZpdA== 30990 -IFN0cmluZ0J1ZmZlcg== 30991 -ZXhwbA== 30992 -KHNjcmVlbg== 30993 -IGF1ZGllbmNlcw== 30994 -IE9QVElPTg== 30995 -X3JvdW5k 30996 -W25vZGU= 30997 -YmVo 30998 -LT5fXw== 30999 -cGVybWlzc2lvbnM= 31000 -IERldGVybWluZQ== 31001 -Lk1hbg== 31002 -IGFkdmFuY2Vz 31003 -LklucHV0U3RyZWFt 31004 -IHN0cm9uZ2VzdA== 31005 -IGVCYXk= 31006 -ICMt 31007 -IGRpcm5hbWU= 31008 -IFNNUw== 31009 -IG1lZGljYXRpb25z 31010 -IGFtZW5kZWQ= 31011 -IGNodXJjaGVz 31012 -IEltcGVyaWFs 31013 -JHJvdw== 31014 -IE1hZGlzb24= 31015 -IEluc3A= 31016 -IGFmZmFpcg== 31017 -IHBzeWNob2xvZ3k= 31018 -dmg= 31019 -IHNldmVyaXR5 31020 -4oCQ 31021 -IHN0cmlwcw== 31022 -QUg= 31023 -dmVydGlzaW5n 31024 -IGNvbnNl 31025 -SU1BR0U= 31026 -IFN0YXRz 31027 -CXNj 31028 -LkN1cnNvcg== 31029 -IGZyZWV6ZQ== 31030 -c3Nvbg== 31031 -KHhtbA== 31032 -IFN1c2Fu 31033 -LnRpbGU= 31034 -ZWRlZA== 31035 -ICAgIAkJCQ== 31036 -dWVsbGU= 31037 -IE1pdGNoZWxs 31038 -YmFzZWQ= 31039 -T3BlcmFuZA== 31040 -veaVsA== 31041 -IEZG 31042 -CXN0cmNweQ== 31043 -b3VuY2Vz 31044 -aWxkbw== 31045 -LmV4ZWN1dGVRdWVyeQ== 31046 -IGFwcHJvYWNoaW5n 31047 -IFNldmVu 31048 -IG51dHM= 31049 -IHJpYw== 31050 -YXNzaWdubWVudA== 31051 -IGNhbGN1bGF0b3I= 31052 -IE11cnBoeQ== 31053 -IEJvdQ== 31054 -7YQ= 31055 -IGJ1dHQ= 31056 -IHRpY2tz 31057 -UHJvamVjdHM= 31058 -aWxpYg== 31059 -LnRleHRDb2xvcg== 31060 -bW92 31061 -X2xvZ28= 31062 -KHRlbXBsYXRl 31063 -IElOSVQ= 31064 -IGltYWdlVmlldw== 31065 -c2NyaXB0aW9ucw== 31066 -T1JJVFk= 31067 -Q29uc3VtZXI= 31068 -IHVucHJlY2VkZW50ZWQ= 31069 -IHRvdXJpc3Q= 31070 -IGJyb24= 31071 -IGNvbnRyYWN0b3I= 31072 -IGxpY2VuY2U= 31073 -IE5hbQ== 31074 -5q8= 31075 -KHRyYW5zZm9ybQ== 31076 -X0FUVA== 31077 -UHJlZg== 31078 -IEdhbQ== 31079 -IHZlc3NlbHM= 31080 -IGhhdg== 31081 -TGF0ZXI= 31082 -LlRvTG93ZXI= 31083 -IHVybHM= 31084 -IGJyZWFrZG93bg== 31085 -IHBlbmFsdGllcw== 31086 -IGZvc3Rlcg== 31087 -IFVF 31088 -IGNsdWU= 31089 -Y29tZWQ= 31090 -5ZCN56ew 31091 -LW1haW4= 31092 -IHB0cw== 31093 -IGNvdW50ZWQ= 31094 -aWN0cw== 31095 -L3Bvc3Q= 31096 -IGdldGF0dHI= 31097 -IHBpbmc= 31098 -QU5DRUw= 31099 -IHBlYw== 31100 -0YXQvtC0 31101 -YW50b20= 31102 -IEJsdWVwcmludA== 31103 -IEV2ZW50RW1pdHRlcg== 31104 -IGzDpA== 31105 -5rI= 31106 -IHN0cmF3 31107 -KGNvbXA= 31108 -J3VuZQ== 31109 -Pk4= 31110 -LWNsaWVudA== 31111 -ZXNNb2R1bGU= 31112 -LWJhc2U= 31113 -IHJldHJlYXQ= 31114 -X3NpbXBsZQ== 31115 -CQkJCQkJIA== 31116 -ZmVl 31117 -JykNCg0K 31118 -Q29udHJvbEl0ZW0= 31119 -IHN1YnNjcmliZXJz 31120 -cGxlYXNl 31121 -IEVmZg== 31122 -IHBvdW5k 31123 -IEJ5dGVz 31124 -IFRlYQ== 31125 -X2FjdGl2aXR5 31126 -IG1heGlt 31127 -IG9wY29kZQ== 31128 -QlNE 31129 -LmNvbnN0YW50 31130 -O30= 31131 -b21icmVz 31132 -IGNhcmVlcnM= 31133 -KS4KCgoK 31134 -IHNwcmVhZGluZw== 31135 -LWV4cGFuZGVk 31136 -IE9yZA== 31137 -YW1hcmlu 31138 -IG1vYmlsaXR5 31139 -VW5mb3J0dW5hdGVseQ== 31140 -YWtr 31141 -Tkw= 31142 -X3JlZGlyZWN0 31143 -IFBH 31144 -IFNlbnNvcg== 31145 -Ym9s 31146 -dGFw 31147 -X01FTU9SWQ== 31148 -IFVJQWxlcnQ= 31149 -cGxpdHVkZQ== 31150 -V2Vic2l0ZQ== 31151 -IExvZ28= 31152 -bG92ZQ== 31153 -W2luZA== 31154 -IGFsdG9nZXRoZXI= 31155 -IHdvbmRlcmVk 31156 -IGVzcGVy 31157 -IExpYmVyYWw= 31158 -IG9zcw== 31159 -IGVsaXQ= 31160 -IHN0aWZm 31161 -b2RveA== 31162 -X21lbnRpb25z 31163 -IERvdWdsYXM= 31164 -X3BpZA== 31165 -IENL 31166 -IGluaXRXaXRoRnJhbWU= 31167 -LmJsb2c= 31168 -cGtn 31169 -YW5naGFp 31170 -UVVJUkVE 31171 -dXU= 31172 -IG1rZGly 31173 -QVRBTA== 31174 -IHVuaA== 31175 -aW5jZXM= 31176 -c3Ro 31177 -IGh5cG90aGVzaXM= 31178 -IGNhdGE= 31179 -IFRC 31180 -IENsYXI= 31181 -IHByZWRlY2Vzcw== 31182 -IHNpdHVhdGVk 31183 -LXdvcmxk 31184 -KSkv 31185 -IGhlYWRsaW5lcw== 31186 -LnN0YXQ= 31187 -IG91dGJyZWFr 31188 -c3BhdGg= 31189 -X0ZMQUdT 31190 -IFNlcnZsZXRFeGNlcHRpb24= 31191 -U3Vu 31192 -RlJPTQ== 31193 -IERpcg== 31194 -44O744O744O7 31195 -X2Nvb3Jk 31196 -IE9wdGlt 31197 -TW9uaXRvcg== 31198 -LmJpdA== 31199 -WFhY 31200 -IHRvZGFz 31201 -ZmVsZA== 31202 -0YDQuA== 31203 -aW1pcg== 31204 -IHBvbGl0aWNhbGx5 31205 -IG1vbGVjdWxhcg== 31206 -IHRyYWRlZA== 31207 -IHt7JA== 31208 -IFN3ZWRpc2g= 31209 -ICdALw== 31210 -X1JFQUw= 31211 -IHdhcmVob3VzZQ== 31212 -dG9kYXk= 31213 -LEw= 31214 -b3Jw 31215 -PHNlY3Rpb24= 31216 -LWJy 31217 -eW1l 31218 -IFVzZXJTZXJ2aWNl 31219 -IGxpYmVydHk= 31220 -IG1vbWVudG8= 31221 -KEltYWdl 31222 -PHNpemU= 31223 -U2No 31224 -IGpvZw== 31225 -aW9sb2d5 31226 -YXJlbnRseQ== 31227 -IHF1YW50dW0= 31228 -IEFidQ== 31229 -IHJpbQ== 31230 -IG1hbmE= 31231 -Rm9udFNpemU= 31232 -QnVpbGRpbmc= 31233 -c3RhaXJz 31234 -QUlMQUJMRQ== 31235 -ICYn 31236 -IHNlY3Q= 31237 -IHNpZ2g= 31238 -KGJhdGNo 31239 -LklDb250YWluZXI= 31240 -cG9sbA== 31241 -IENvcnBz 31242 -zrU= 31243 -YXJ1 31244 -IEtheQ== 31245 -LnJhbmdl 31246 -X2NsaWNrZWQ= 31247 -IFJvYmVydHM= 31248 -Lk5ldHdvcms= 31249 -ZmluaXNo 31250 -LU1hbg== 31251 -IGNvbGxlZ2Vz 31252 -IEZpbmU= 31253 -IikpLAo= 31254 -ZmlsbQ== 31255 -IHJlbWluZGVk 31256 -IGdlc3R1cmU= 31257 -b3V0aWw= 31258 -IHRocmVhZGluZw== 31259 -IG9iamV0 31260 -IHRvdXJz 31261 -YWN0aXZhdGVk 31262 -Lm1rZGly 31263 -PXVzZXI= 31264 -IHJlZGU= 31265 -ZsO8 31266 -X1NZU1RFTQ== 31267 -cHY= 31268 -IGNvbmdy 31269 -IG1hc3Nhc2pl 31270 -IHByYWN0aXRpb24= 31271 -VW5pdmVyc2l0eQ== 31272 -IHRhYmluZGV4 31273 -0Jg= 31274 -U2V0cw== 31275 -IGNvdW50aWVz 31276 -Z3Vlc3Q= 31277 -ZmFu 31278 -IHdvcmRlbg== 31279 -LmRp 31280 -0L3QsNGH 31281 -wr8= 31282 -aWdEZWNpbWFs 31283 -IHNob3Jl 31284 -IGfDtg== 31285 -IHJlcGFpcnM= 31286 -IGhlbHBlcnM= 31287 -IGNlbnRlcmVk 31288 -T0xMT1c= 31289 -IG1hcFN0YXRlVG9Qcm9wcw== 31290 -IGNlbnRz 31291 -PEE= 31292 -IGV4cGVjdGF0aW9u 31293 -T2N0b2Jlcg== 31294 -IGJnY29sb3I= 31295 -Y2FsZXM= 31296 -LkNPTg== 31297 -IFZlbA== 31298 -IGNyeWluZw== 31299 -LXNlYXNvbg== 31300 -IGZ1bmN0aW9uaW5n 31301 -X0xPQ0FUSU9O 31302 -w7xzcw== 31303 -YmVyeQ== 31304 -UGFyYQ== 31305 -b21pbmF0b3I= 31306 -LWxl 31307 -IGV0aGljYWw= 31308 -aGFzaHRhZ3M= 31309 -ZW1wbG8= 31310 -IG7Dum1lcm8= 31311 -KGFjdGl2aXR5 31312 -LlN0b3A= 31313 -LnN0cmZ0aW1l 31314 -SUxE 31315 -IHRvZQ== 31316 -CU5vZGU= 31317 -IikNCg0K 31318 -IFB1ZXJ0bw== 31319 -IGV4ZWN1dGluZw== 31320 -IEdVSUQ= 31321 -IG9wcG9zaW5n 31322 -YWxwaA== 31323 -IGV4aGliaXQ= 31324 -X2ZsYXNo 31325 -IG1laWxsZQ== 31326 -IGpzb25PYmplY3Q= 31327 -SGVybw== 31328 -YWludGVk 31329 -X0RPTQ== 31330 -IHdpbA== 31331 -IHNsb3Bl 31332 -IG3DpQ== 31333 -IElyYXFp 31334 -IG9yZ2FuaXpl 31335 -CWpRdWVyeQ== 31336 -SFVE 31337 -c2hpbmU= 31338 -Lndl 31339 -IFNraWxscw== 31340 -cG9uc29y 31341 -IGNvbmNsdXNpb25z 31342 -IHJlZm9ybXM= 31343 -IHJlbHVjdA== 31344 -bmFtZWQ= 31345 -IE9saXZlcg== 31346 -IC8vfQo= 31347 -LWxvb2tpbmc= 31348 -IGZvZw== 31349 -IEhP 31350 -IEZyaWVk 31351 -IGluZXZpdGFibGU= 31352 -IERhdGFHcmlkVmlldw== 31353 -SG91cg== 31354 -aWxsZXM= 31355 -bG9naWNhbA== 31356 -IGNvbm5lY3Rpdml0eQ== 31357 -LnR3aWc= 31358 -IEt5bGU= 31359 -KGRzdA== 31360 -LVNo 31361 -IFN0dWRpb3M= 31362 -KExldmVs 31363 -LmpldA== 31364 -X1BST1RP 31365 -LWRlY29yYXRpb24= 31366 -T1RIRVI= 31367 -IHJlYWRpbHk= 31368 -LlBhcmFtZXRlcg== 31369 -IG11bHRpcGx5 31370 -IExJQg== 31371 -YXJtZWQ= 31372 -IHNvb25lcg== 31373 -5oQ= 31374 -X0VT 31375 -IGZvc3NpbA== 31376 -IEFuYw== 31377 -4oCcVGhpcw== 31378 -bG9kYXNo 31379 -UHl0aG9u 31380 -IGhpc3RvZ3JhbQ== 31381 -d2VzdGVybg== 31382 -IGluZmFudA== 31383 -IGNvb3JkaW5hdG9y 31384 -IG5pYg== 31385 -Om0= 31386 -IHJlc3BlY3RlZA== 31387 -IGRlZmluaXQ= 31388 -JlQ= 31389 -X3BhZA== 31390 -IFRyaWdnZXI= 31391 -dGhhbA== 31392 -IGltYWdlTmFtZWQ= 31393 -IGJlYXRlbg== 31394 -CXJj 31395 -IFBhbGFjZQ== 31396 -IGhhemFyZA== 31397 -IGlzb2xhdGlvbg== 31398 -X3Jj 31399 -Y29udHJl 31400 -T1VUUFVU 31401 -IHJlaWdu 31402 -IFBsYXRl 31403 -QVRFUw== 31404 -IGZsdXg= 31405 -IHBhY2tz 31406 -LmdldFNlbGVjdGVk 31407 -IHBhcnRpY2lwYXRlZA== 31408 -IG5lZWRsZQ== 31409 -LWRlcHRo 31410 -Ojo6Ojo6 31411 -LWxhdw== 31412 -aW5zcGFjZQ== 31413 -b25pdG9y 31414 -PW5v 31415 -IEF0b21pYw== 31416 -IEJyYWlu 31417 -RWRpdGFibGU= 31418 -LXNj 31419 -cmVkZW50aWFs 31420 -IFBlcnJ5 31421 -a2ll 31422 -IC0tLS0tLS0tLS0K 31423 -LnN0cm9rZQ== 31424 -KEludGVudA== 31425 -IHVuaXR5 31426 -dW1sYWg= 31427 -RnVydGhlcg== 31428 -IHByemU= 31429 -IHPDuA== 31430 -44KK 31431 -IFBST0NVUkVNRU5U 31432 -IEhvdXNpbmc= 31433 -IGF0dG9ybmV5cw== 31434 -IGNvbXBvc2U= 31435 -YXR0ZXJpbmc= 31436 -IldoYXQ= 31437 -ZHJhdWw= 31438 -IHN0cmFpZ2h0Zm9yd2FyZA== 31439 -SW5zdGFudA== 31440 -LkpUZXh0RmllbGQ= 31441 -IHRyYWRlcw== 31442 -0LvQsA== 31443 -IHsh 31444 -IGxhdGVseQ== 31445 -SU1H 31446 -IEFsZA== 31447 -IElOTkVS 31448 -IGNhcnRvb24= 31449 -LlNvdXJjZQ== 31450 -RkFMU0U= 31451 -IGRvdWdo 31452 -ZmVu 31453 -KHJlY3Q= 31454 -RGF0YVRhYmxl 31455 -Tmljaw== 31456 -IEJ1dHRlcg== 31457 -cmVhZHM= 31458 -X2NvbW1lbnRz 31459 -RU5W 31460 -IENvbm5lY3RpY3V0 31461 -LUZJUlNU 31462 -CQkJICAgICA= 31463 -YWNoaQ== 31464 -Lk1zZw== 31465 -cmVjdGlvbg== 31466 -IHJlbGF4ZWQ= 31467 -IHNoYWZ0 31468 -IGVm 31469 -IEFkZGluZw== 31470 -IGJyZWFjaA== 31471 -IO+8mg== 31472 -cmFtYQ== 31473 -IGNvbmR1Y3Rpbmc= 31474 -ICg7 31475 -KGds 31476 -IENBVVNFRA== 31477 -YXNoaQ== 31478 -IEZMQUc= 31479 -IENvbW1lcmNl 31480 -IElOVEVHRVI= 31481 -aG91cnM= 31482 -IFNjaG9vbHM= 31483 -IG51Y2xl 31484 -QWdhaW4= 31485 -cHJvag== 31486 -IHNldmVudGg= 31487 -RU1QTEFSWQ== 31488 -KG1vY2s= 31489 -J10sDQo= 31490 -X1NQRUVE 31491 -PmZhbHNl 31492 -IHNwYQ== 31493 -IE5lYXI= 31494 -7JU= 31495 -IGludHJpZw== 31496 -X21lbWJlcnM= 31497 -d2F2ZQ== 31498 -IGFuYWx5c3Rz 31499 -X09T 31500 -ZWRpbg== 31501 -IEZyaQ== 31502 -IHJldHJpZXZlZA== 31503 -UmVndWxhcg== 31504 -X29icw== 31505 -RVhQT1JU 31506 -Jyl9fSI= 31507 -ImNsYXNz 31508 -X18oKA== 31509 -YnVja2V0 31510 -IHN0cm8= 31511 -IFBhdGNo 31512 -eXN0aWNr 31513 -ZnVsbmVzcw== 31514 -YXBvcw== 31515 -RGE= 31516 -CQkJCQkgICA= 31517 -IGVucmljaA== 31518 -dW5vcmRlcmVk 31519 -aG9sZQ== 31520 -Q29uZw== 31521 -PFByb2R1Y3Q= 31522 -IEN1cnQ= 31523 -KHRoZQ== 31524 -X2xvd2Vy 31525 -IGF2b2lkaW5n 31526 -IGJ1eno= 31527 -IHZpYWJsZQ== 31528 -dWJh 31529 -LWlz 31530 -YXJlbA== 31531 -IGFjdGVk 31532 -LWRldGFpbHM= 31533 -4LiH 31534 -IFRoZW9yeQ== 31535 -IFB1bg== 31536 -IEFub255bW91cw== 31537 -Li4uIgo= 31538 -w6hyZXM= 31539 -5Y+v 31540 -IFZpc2lvbg== 31541 -X3NlbQ== 31542 -YXNoYQ== 31543 -IGNlbGVicml0eQ== 31544 -IGVuZERhdGU= 31545 -IHBvcHVsYXRl 31546 -IGN1aXM= 31547 -cXVhbnQ= 31548 -Zmxvb3I= 31549 -IGdsb2JhbGx5 31550 -IGNydWlzZQ== 31551 -IFN0YW5sZXk= 31552 -IGJpa2Vz 31553 -LmdldENvbm5lY3Rpb24= 31554 -IHBvb3JseQ== 31555 -X290aGVy 31556 -YW1waW5n 31557 -LiIpOwoK 31558 -b2Rp 31559 -X0FETUlO 31560 -LmNvbG9ycw== 31561 -IEdhbWluZw== 31562 -Pic7Cgo= 31563 -U1RSVUNU 31564 -UVI= 31565 -SURz 31566 -KGFyZ3VtZW50cw== 31567 -X2F1eA== 31568 -KEV2ZW50 31569 -X1BSSVZBVEU= 31570 -IFRyZWs= 31571 -IGRvd25sb2Fkcw== 31572 -bXV0YWJsZQ== 31573 -X1NUUlVDVA== 31574 -KHd4 31575 -IGRvbWFpbnM= 31576 -anNweA== 31577 -IFZpYWdyYQ== 31578 -Q29tbWFuZHM= 31579 -SnM= 31580 -LmNmZw== 31581 -Q29udGVudFBhbmU= 31582 -IEVkaXRUZXh0 31583 -4KWN4KQ= 31584 -QXR0YWNo 31585 -IEFSTQ== 31586 -cG9zaXRpdmU= 31587 -IEdlbmVyYXRlZA== 31588 -IHNlaXplZA== 31589 -PTo= 31590 -IGVsZWN0cm9uaWNz 31591 -IEFwcENvbXBvbmVudA== 31592 -LycsCg== 31593 -LmVxdWFsc0lnbm9yZUNhc2U= 31594 -RG9jdHJpbmU= 31595 -ZGlzaw== 31596 -IFBvbGl0aWNhbA== 31597 -Q0hP 31598 -PEY= 31599 -CWhlaWdodA== 31600 -IEJ1Zw== 31601 -Lmxl 31602 -aWto 31603 -IG1pbGxpc2Vjb25kcw== 31604 -IGNvbnN0aXR1 31605 -bWFn 31606 -Lm5s 31607 -LXJhbmdl 31608 -YW5nZ2Fs 31609 -Jyxb 31610 -cm9wb2xpdGFu 31611 -IMOc 31612 -IFVD 31613 -LmRlc2M= 31614 -LUxBU1Q= 31615 -ZnN0cmVhbQ== 31616 -aWJpbA== 31617 -IGZpZXI= 31618 -VkVSWQ== 31619 -IOuz 31620 -SVJU 31621 -X1VJ 31622 -KGFicw== 31623 -IGtuZWVz 31624 -IHJvb2tpZQ== 31625 -IFZhYw== 31626 -YXJlbmE= 31627 -Y29tbWVuZA== 31628 -LVw= 31629 -IFNVQlNUSVRVVEU= 31630 -U29mdA== 31631 -IHBhcnRpcg== 31632 -d2VhbHRo 31633 -6KaB 31634 -KGRhdGFzZXQ= 31635 -IENsaW1hdGU= 31636 -LXNob3c= 31637 -IHJlbGlhYmlsaXR5 31638 -X2NodW5r 31639 -5Luj 31640 -X3N0b2Nr 31641 -IEVYRU1QTEFSWQ== 31642 -77iP 31643 -IHbDrQ== 31644 -IHNtaWxlZA== 31645 -IGRyaWxs 31646 -LkZ1bmN0aW9u 31647 -IFNJ 31648 -IHJlZ3Jlc3Npb24= 31649 -LVg= 31650 -IEphcg== 31651 -cHJlZg== 31652 -CXN1Y2Nlc3M= 31653 -IEhpdGxlcg== 31654 -IGluc3RpbmN0 31655 -IGZlbW1lcw== 31656 -IGxvdmVy 31657 -PAo= 31658 -IG11bHRpcGxpZXI= 31659 -cmls 31660 -UmVzaXpl 31661 -IEF1dGhvcml6YXRpb24= 31662 -IEthbg== 31663 -RGlzcGF0Y2hUb1Byb3Bz 31664 -IGNyb3Bz 31665 -dG9rZW5z 31666 -ZWNu 31667 -ZW50aWFsbHk= 31668 -IElOVEVSUlVQVElPTg== 31669 -ZmFrZQ== 31670 -VW5kZWZpbmVk 31671 -IEFL 31672 -IFRlc3RDYXNl 31673 -IHJhYg== 31674 -IHRvcnJlbnQ= 31675 -IE90 31676 -QmFycw== 31677 -IGxlY3R1cmU= 31678 -IGVuam8= 31679 -IHJlc3BvbmRz 31680 -IGluZGV4ZWQ= 31681 -T2ZXb3Jr 31682 -X2NoYWlu 31683 -KSktPg== 31684 -IEJlYXV0eQ== 31685 -IGA8 31686 -IHRvdWNoaW5n 31687 -IHwtLQ== 31688 -CWZsYWc= 31689 -bm9ybWFsaXpl 31690 -IHRyYXBwZWQ= 31691 -IGVzdGFibGlzaGluZw== 31692 -L2J1aWxk 31693 -QUo= 31694 -Znk= 31695 -LXJlYWN0 31696 -YXZu 31697 -UklQVElPTg== 31698 -IGt1dA== 31699 -IEZhc2hpb24= 31700 -IEluZm9ybQ== 31701 -Y3VyaXRpZXM= 31702 -PGJ5dGU= 31703 -IFVrcmFpbg== 31704 -IHN1Zw== 31705 -IGNvbnNpc3Rpbmc= 31706 -b29kbGU= 31707 -LmN0eA== 31708 -LlRvTGlzdA== 31709 -IGNvbW1lbnRhcnk= 31710 -IHRyYW5zZmVycw== 31711 -IG5vc3Q= 31712 -aWhhZA== 31713 -IFVwcGVy 31714 -IGNvbmZ1c2luZw== 31715 -bWlzc2luZw== 31716 -LWNs 31717 -IGJvdW5kaW5n 31718 -IGNvbmdyZXNzaW9uYWw= 31719 -IHJldmVhbGluZw== 31720 -ZGg= 31721 -cnVw 31722 -IHRyZXM= 31723 -cmVwZWF0 31724 -LAoKCgo= 31725 -X3RhYw== 31726 -IGV4cGVk 31727 -R2lybA== 31728 -aG9yaXpvbnRhbA== 31729 -ICIuLi8uLi8uLi8= 31730 -KG9wdGlvbg== 31731 -IHdlaXRlcg== 31732 -CXNxbA== 31733 -ID0+ewo= 31734 -IGdhcmxpYw== 31735 -IHJlcHI= 31736 -IHJlcGxpZXM= 31737 -KHByb3A= 31738 -IHNwaXJpdHM= 31739 -IGluc3BpcmU= 31740 -IGJhc2VtZW50 31741 -LnJlamVjdA== 31742 -IGhpbnRz 31743 -IHBvbGxpbmc= 31744 -CSAK 31745 -X3JhdGluZw== 31746 -IGNhdGg= 31747 -YXZpZXI= 31748 -IGNvbXByZXNzZWQ= 31749 -IFZT 31750 -XSc= 31751 -IGp1ZGljaWFs 31752 -IFRyZW5k 31753 -dHJhaW5pbmc= 31754 -RVNUQU1Q 31755 -b2duaXRpb24= 31756 -xIE= 31757 -U0VOVA== 31758 -dmVudGlvbnM= 31759 -IGNvbnN1bHRhbnQ= 31760 -dW1waA== 31761 -IHVzZXJTZXJ2aWNl 31762 -LE5VTEw= 31763 -a2g= 31764 -RGVhcg== 31765 -X0JBRA== 31766 -aXRhdGlvbnM= 31767 -IG1ldGFwaA== 31768 -J8Op 31769 -YW5kaXNl 31770 -LWZvbnQ= 31771 -LmNoYXJ0 31772 -IHNn 31773 -X0NvbnRyb2xsZXI= 31774 -LmpwZWc= 31775 -IFVMT05H 31776 -CWdhbWU= 31777 -KHNz 31778 -IE1hag== 31779 -CWdv 31780 -IFNhZA== 31781 -IEJlcmc= 31782 -IE1pbmU= 31783 -UGFjaw== 31784 -IHJlc2lzdGFudA== 31785 -IFJPTQ== 31786 -IHBlZw== 31787 -IFN0YW5mb3Jk 31788 -IFlhaG9v 31789 -IHNjYWxlZA== 31790 -IGxhbg== 31791 -PVtd 31792 -Ii8+PC8= 31793 -IHBsb3Rz 31794 -LioK 31795 -IHRyYXZlbGVk 31796 -IE9zY2Fy 31797 -Vkw= 31798 -IGxpbmtpbmc= 31799 -IHRpcmVz 31800 -ICcqJw== 31801 -IEJ1ZmZlcmVk 31802 -ZXJp 31803 -ICoqKio= 31804 -IG92ZXJsb29r 31805 -Lk5vbg== 31806 -IHLDqXM= 31807 -IGVneQ== 31808 -5bCP 31809 -IGF0dGFja2Vy 31810 -CQkJCQkJCQkJCQkJCQkJ 31811 -LnN5bmM= 31812 -QVNDQURF 31813 -R3JvdW5k 31814 -IGRlY2F5 31815 -IFRvbg== 31816 -IGpld2Vscnk= 31817 -IGJ5cGFzcw== 31818 -IG1lbWJy 31819 -Uk5B 31820 -PFN5c3RlbQ== 31821 -IE1lZGljYXJl 31822 -KG5ldA== 31823 -b3Np 31824 -SEI= 31825 -REVD 31826 -e0VJRg== 31827 -X2ZpbGw= 31828 -IHRyYXZlbGxpbmc= 31829 -b2JzZXJ2ZXI= 31830 -IGNvbnN1bHRpbmc= 31831 -UkVBVA== 31832 -UGhhc2U= 31833 -KGlp 31834 -IFNVTQ== 31835 -Pg0NCg== 31836 -IHN1ZA== 31837 -CWJhY2tncm91bmQ= 31838 -IHNjaG9sYXJz 31839 -LW11dGVk 31840 -YXLDoQ== 31841 -ID09PT09 31842 -IF9fX18= 31843 -Q3JlYXQ= 31844 -ZW5ldmVy 31845 -L3dw 31846 -IFZQTg== 31847 -RXJyb3JDb2Rl 31848 -KV0sCg== 31849 -KGJ1aWxkZXI= 31850 -IEVuZW15 31851 -U2Vuc29y 31852 -dXNh 31853 -IHRyaWdnZXJz 31854 -IHBsYXlvZmZz 31855 -X1JFUQ== 31856 -ICh+ 31857 -IEJhcnJ5 31858 -IHBlcm1hbmVudGx5 31859 -IFJVTg== 31860 -IGJ1cmU= 31861 -LkZhdGFsZg== 31862 -IGNoaWNr 31863 -CXBhbmlj 31864 -cHNp 31865 -b2th 31866 -6YCJ 31867 -Pls= 31868 -IHVuZGVyc3RhbmRz 31869 -IEp1bmlvcg== 31870 -IElORk8= 31871 -PW15c3FsaQ== 31872 -dXN0YWlu 31873 -LXNvdXJjZQ== 31874 -c2Vydg== 31875 -IENSRUFURQ== 31876 -LmF1 31877 -IHNlbGxz 31878 -ICAKICAK 31879 -RXVyb3Bl 31880 -enc= 31881 -cHJlaA== 31882 -IE5TQQ== 31883 -IHh5 31884 -4Li0 31885 -IEJleW9uZA== 31886 -SW5zdGVhZA== 31887 -Tm9uUXVlcnk= 31888 -IGFyaXNl 31889 -IGF2b2lkZWQ= 31890 -LmVtcGxhY2U= 31891 -X21vZGVscw== 31892 -fSksCg== 31893 -IGhpZA== 31894 -ICZf 31895 -LnBvaW50cw== 31896 -LmdldFdpZHRo 31897 -LkV4ZWM= 31898 -IC8vLy8= 31899 -IFNlc3Npb25z 31900 -Li4uXA== 31901 -IENvbG9tYg== 31902 -IGFjY2VsZXJhdGlvbg== 31903 -cmVzdG9yZQ== 31904 -IGlsZQ== 31905 -b2JpYw== 31906 -PE5vZGU= 31907 -IERY 31908 -IEJlc2lkZXM= 31909 -LmFnZQ== 31910 -IENvbnRhaW5z 31911 -TmF0aW9uYWw= 31912 -IEltcGxlbWVudGF0aW9u 31913 -IGVmZmlj 31914 -IFJN 31915 -SHk= 31916 -IFdlZGRpbmc= 31917 -b2tpZXM= 31918 -IHJlY3Vyc2l2ZQ== 31919 -IHByb3NlY3V0b3Jz 31920 -LlNlbGVjdGlvbg== 31921 -IEZvcm11bGE= 31922 -QmVlbkNhbGxlZA== 31923 -W2lp 31924 -IEZyYW4= 31925 -IHRyYWdlZHk= 31926 -X0ZFQVRVUkU= 31927 -mag= 31928 -Y29tcGFzcw== 31929 -IEJo 31930 -PwoKCg== 31931 -LndyaXRlcg== 31932 -IEhvdXI= 31933 -RGJDb250ZXh0 31934 -aW92 31935 -YW1vbg== 31936 -cmVwcg== 31937 -6YM= 31938 -CWZp 31939 -J11d 31940 -IERyeQ== 31941 -LnJv 31942 -IE9ic2Vydg== 31943 -5qCH 31944 -Rm9ybWVy 31945 -IEJhbGFuY2U= 31946 -CWpzb24= 31947 -IHByenk= 31948 -SVNT 31949 -KHNvY2s= 31950 -IExJTkU= 31951 -IGRlY2U= 31952 -IGFsbHk= 31953 -IHRlbmRlbmN5 31954 -RnVu 31955 -IHNjaGVtZXM= 31956 -IGludGVydmVu 31957 -5piO 31958 -IGFkdmVyc2U= 31959 -cXVvdGVsZXY= 31960 -IHNhY3JpZmlj 31961 -X3NpZGU= 31962 -IG11dGV4 31963 -QUdJQw== 31964 -IG9jY3VycmluZw== 31965 -IENvbW11bmljYXRpb24= 31966 -dW1hcg== 31967 -57yW 31968 -IFRyZWF0bWVudA== 31969 -LnBlcnNvbg== 31970 -IExD 31971 -IGVjaA== 31972 -KCgi 31973 -IERpc2Vhc2U= 31974 -w6Rk 31975 -IEFa 31976 -LkFjY291bnQ= 31977 -IGNvbnRpbnVvdXNseQ== 31978 -RU5ESU5H 31979 -IFJFVFVSTg== 31980 -LXN0cmluZw== 31981 -LmZpbGVuYW1l 31982 -c3ludGhlc2l6ZQ== 31983 -UmVzcG9uZGVy 31984 -KG9wdHM= 31985 -cmVncw== 31986 -IG51ZXN0 31987 -UGVlcg== 31988 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 31989 -IGdhdWdl 31990 -IEtpbg== 31991 -LnNjaGVtYQ== 31992 -IGFycmFuZ2U= 31993 -IEJsYWtl 31994 -X1R5cGVJbmZv 31995 -Q292ZXI= 31996 -IEhhbXBzaGlyZQ== 31997 -UGFwZXI= 31998 -LWlubmVy 31999 -dXRpbGl0eQ== 32000 -IGNyb3Nzb3JpZ2lu 32001 -Rk9S 32002 -IGlnbm9yaW5n 32003 -IERE 32004 -YXZhbg== 32005 -IHRyYWRpdGlvbnM= 32006 -IGdldFN0cmluZw== 32007 -IGV0aGljcw== 32008 -IE1hdGVyaWFscw== 32009 -REVTQw== 32010 -IGVuenlt 32011 -aW9sZXQ= 32012 -IENoaXA= 32013 -IE1jRG9uYWxk 32014 -IG5lcnZl 32015 -54Q= 32016 -Iild 32017 -5rGC 32018 -IFN1Z2Fy 32019 -X1NJTQ== 32020 -anBlZw== 32021 -IGRpc2NyZXRpb24= 32022 -IFRO 32023 -Ym92ZQ== 32024 -IE1pbmltdW0= 32025 -IEZvcm1Hcm91cA== 32026 -IHdvcmtmb3JjZQ== 32027 -IEV4ZWN1dGlvbg== 32028 -ZXJyZXI= 32029 -CSAgICAJ 32030 -IHByZXNjcmliZWQ= 32031 -LlRleHRBbGlnbg== 32032 -T1BFTg== 32033 -IFBC 32034 -aW1pdHk= 32035 -IEV4dGVybmFs 32036 -wrBD 32037 -IEFwcGxpY2F0aW9uQ29udHJvbGxlcg== 32038 -IGJhcnI= 32039 -aW1wbGljaXQ= 32040 -X2RvdA== 32041 -IENvbG9u 32042 -Q09MT1I= 32043 -LlByb2plY3Q= 32044 -Kjwv 32045 -LXhs 32046 -IG9zYw== 32047 -KHBhdHRlcm4= 32048 -Jyl9Cg== 32049 -c3VjY2Vzc2Z1bA== 32050 -YWxvZw== 32051 -U3R1ZGVudHM= 32052 -XXN0cmluZw== 32053 -YW50b24= 32054 -YXR0aQ== 32055 -Y2hlbWljYWw= 32056 -LmluZg== 32057 -KGRy 32058 -OlVJQ29udHJvbFN0YXRl 32059 -dG9JbnQ= 32060 -XTwv 32061 -0LDQtdC8 32062 -IMW+ 32063 -LkFjdGlvbkxpc3RlbmVy 32064 -LlNFVkVSRQ== 32065 -IFNhbHY= 32066 -X1RSQU4= 32067 -L2ludGVybmFs 32068 -IHdlbGNvbWVk 32069 -LmNvbW1lbnQ= 32070 -bXV0YXRpb24= 32071 -IEZBUQ== 32072 -Lm9uZQ== 32073 -IExBQg== 32074 -In19 32075 -IFJvbA== 32076 -aWV2ZWQ= 32077 -IGFkdmVudHVyZXM= 32078 -IGZ1bmVyYWw= 32079 -IHNwb3VzZQ== 32080 -KG9wZW4= 32081 -IFJlYWR5 32082 -IHRvdXJpc20= 32083 -YWRpbg== 32084 -X2ZhY2U= 32085 -4oKB 32086 -IG1pZ3JhbnRz 32087 -IFB1cmNoYXNl 32088 -Y29yZA== 32089 -IE9VVFBVVA== 32090 -KSkNCg0K 32091 -U2VndWU= 32092 -dGFicw== 32093 -IGRvdHM= 32094 -IG5haWw= 32095 -Ym9ybmU= 32096 -IGRlc2lyZXM= 32097 -IHByZXZlbnRlZA== 32098 -J109PQ== 32099 -IHRpbWVseQ== 32100 -SUNB 32101 -U2Nhbm5lcg== 32102 -IEx1Y2Fz 32103 -IGdpdGh1Yg== 32104 -J11bXQ== 32105 -ZGlh 32106 -Y29ub21pYw== 32107 -IGRpZXNlcg== 32108 -dW5kZXJz 32109 -LkhhbmRsZXI= 32110 -PyIs 32111 -LmRhdGFi 32112 -IGFkdmlzZQ== 32113 -LmFuaW1hdGlvbg== 32114 -IG92ZXJoZWFk 32115 -IG9ic3RhY2xlcw== 32116 -X2pvaW4= 32117 -IG3DqQ== 32118 -RmxhdA== 32119 -LmRpc3Bvc2U= 32120 -IEV4cGVjdGVk 32121 -IGZsZXc= 32122 -IGVtYm9k 32123 -X3NsdWc= 32124 -IG5hbWVseQ== 32125 -IHdpdG5lc3NlZA== 32126 -c29saWQ= 32127 -LmxlZ2VuZA== 32128 -UXVhbA== 32129 -X3N1cmZhY2U= 32130 -44Op 32131 -QW1lcmljYQ== 32132 -IGFmZmlsaWF0ZXM= 32133 -IFByb3M= 32134 -X2V4dGVuc2lvbg== 32135 -YmluZGluZw== 32136 -U1RBTEw= 32137 -LnJlYWR5 32138 -IGNvcHlpbmc= 32139 -IEhlbmNl 32140 -IGRpc2NvcmQ= 32141 -X3NoaXA= 32142 -UHJvcGVydHlOYW1l 32143 -CQkgICAgICAgICAgIA== 32144 -IGFjaGlldmluZw== 32145 -IEJlYw== 32146 -Wmlw 32147 -U29tZXRpbWVz 32148 -44GL 32149 -IGNvbnRyYQ== 32150 -IHB1bmlzaA== 32151 -IGluc3VsaW4= 32152 -IGRpc2FwcGVhcg== 32153 -X2VudW0= 32154 -LmF1dA== 32155 -IGhhc2F0dHI= 32156 -YWZmZWN0ZWQ= 32157 -c2hl 32158 -JHRhYmxl 32159 -a3Np 32160 -IGxhY2tpbmc= 32161 -IGRpc2NvdW50cw== 32162 -U3RtdA== 32163 -IEFyZ2VudGluYQ== 32164 -IHVucGFjaw== 32165 -IFJvdXRlZEV2ZW50QXJncw== 32166 -ICc/ 32167 -aW50ZXJvcA== 32168 -IHNvZmE= 32169 -IGR5bg== 32170 -IEdyYWNl 32171 -IGludGVncmF0ZQ== 32172 -2YM= 32173 -IGRlbGF5cw== 32174 -IEltcGxlbWVudA== 32175 -UHJvb2Y= 32176 -IGFwcGxpY2FudHM= 32177 -IExlYXRoZXI= 32178 -7Ja0 32179 -IGVuam95YWJsZQ== 32180 -U3Bpbm5lcg== 32181 -L3o= 32182 -IGZvYW0= 32183 -IExhYm9yYXRvcnk= 32184 -IHJlc2VhcmNoZXI= 32185 -IENocmlzdGlhbml0eQ== 32186 -IGN1c3RvbWl6ZQ== 32187 -IGNpcGhlcg== 32188 -IGRvZA== 32189 -IHPDsw== 32190 -QEVudGl0eQ== 32191 -T05MWQ== 32192 -aW52ZW50b3J5 32193 -IGNvbmNsdWRl 32194 -IGN1ZW50YQ== 32195 -IENvaGVu 32196 -LWluY29tZQ== 32197 -bWJI 32198 -bWVudGF0aW9u 32199 -IHZlcnc= 32200 -dWRw 32201 -QU1M 32202 -LmNvbWJvQm94 32203 -Zmg= 32204 -am9icw== 32205 -RmlsZVN5bmM= 32206 -IEJhcmJhcmE= 32207 -IFNjYW4= 32208 -Y3JlZW5zaG90 32209 -IE9ydGg= 32210 -LnZpZXdEaWRMb2Fk 32211 -IEFSUkFZ 32212 -LEA= 32213 -L2ludA== 32214 -R2VuZXJhdGU= 32215 -IGRlbW9uc3RyYXRlcw== 32216 -IFplbmQ= 32217 -5YiX 32218 -CXZvbGF0aWxl 32219 -PXI= 32220 -IGZt 32221 -CWJ1ZmZlcg== 32222 -ZW5hdGU= 32223 -LkNvbWJpbmU= 32224 -IG1pc2M= 32225 -Y2hlbWFz 32226 -IHB1cmVseQ== 32227 -IGdsVmVydGV4 32228 -LlJlc3Q= 32229 -IHJlY2FsbGVk 32230 -IGZyZWVs 32231 -IHNxdWU= 32232 -VHJhY2tlcg== 32233 -IFBocA== 32234 -IERpc3RhbmNl 32235 -IGJlYXN0 32236 -Q29tcGxleA== 32237 -IGNvbnNpZGVycw== 32238 -572R 32239 -dHJpYnV0aW9u 32240 -IGNvbXBsaW1lbnQ= 32241 -X2xpbmVubw== 32242 -IE11dGFibGU= 32243 -IHVuZGVm 32244 -IEdlbQ== 32245 -IGNvbXBvdW5kcw== 32246 -LnV1aWQ= 32247 -IGFub255bQ== 32248 -IHN0YWlycw== 32249 -IERiU2V0 32250 -d29ydA== 32251 -IFNlbnM= 32252 -LkJlZm9yZQ== 32253 -IGVuZGZvcmVhY2g= 32254 -IFRvZ2V0aGVy 32255 -YXRpbGl0eQ== 32256 -IG1vaXN0dXJl 32257 -LSR7 32258 -KFRlc3Q= 32259 -VEI= 32260 -bXVzaWM= 32261 -IGluc2lzdA== 32262 -IGhlYWRsaW5l 32263 -LkFuZA== 32264 -UEFUQ0g= 32265 -IFByZXBhcmU= 32266 -IHN3aXRjaGVz 32267 -KnA= 32268 -IFll 32269 -X2Ficw== 32270 -LmhhbmRsZXI= 32271 -IGFzc2lnbm1lbnRz 32272 -UHJlZmVyZW5jZQ== 32273 -RU5USVRZ 32274 -IHBpcGVz 32275 -IEFsZXJ0RGlhbG9n 32276 -b2dyYXBoaWNhbA== 32277 -IHBhdGlv 32278 -IHdlYnBhY2s= 32279 -YnBz 32280 -TmF2TGluaw== 32281 -Lk51bWJlcg== 32282 -IEFybW9y 32283 -IFBldGVycw== 32284 -IERlc2M= 32285 -ZHVpbm8= 32286 -IEljb25z 32287 -LmdldEhlaWdodA== 32288 -IHRleHRWaWV3 32289 -CU5VTEw= 32290 -YWxsb2NhdGU= 32291 -fSR7 32292 -IFByaXpl 32293 -LW51bQ== 32294 -Lk1vdmU= 32295 -6L6T5YWl 32296 -LmNhbWVyYQ== 32297 -UHJvYmxlbQ== 32298 -CXR5cGVkZWY= 32299 -KHN0b3Jl 32300 -IERJU0NMQUlNRUQ= 32301 -IHN1YnN0YW50aWFsbHk= 32302 -RkZG 32303 -IGVwc2lsb24= 32304 -IGluZXF1YWxpdHk= 32305 -X2NoaWxkcmVu 32306 -5LiH 32307 -cmVsdQ== 32308 -UGllY2U= 32309 -YW50cnk= 32310 -YmFiZWw= 32311 -dmV0aWNh 32312 -IHN1cnZleXM= 32313 -IGRldGVjdG9y 32314 -CWFyZ3M= 32315 -LlNlbGVjdGVkVmFsdWU= 32316 -IGludGVyZmVyZW5jZQ== 32317 -Li4uKQo= 32318 -LlNUUklORw== 32319 -IFR5bGVy 32320 -IENhdGFsb2c= 32321 -VmVydGljZXM= 32322 -IFByb2plY3Rz 32323 -IExlYmFu 32324 -LiIpCgo= 32325 -Lmtlcm5lbA== 32326 -IHJpZGVz 32327 -IE11dA== 32328 -YW50aA== 32329 -0L7RgNC8 32330 -ZW5uaWFs 32331 -LnRhc2tz 32332 -LnNldFByb3BlcnR5 32333 -YXRlZ29yaQ== 32334 -5pyA 32335 -L2Nvbg== 32336 -YnJhY2U= 32337 -IE5TRXJyb3I= 32338 -J10pKTsK 32339 -bGlzdGVk 32340 -IFByZXZpZXc= 32341 -QWN0aXZhdGU= 32342 -IGN5Y2w= 32343 -LWFjdGl2ZQ== 32344 -aGFk 32345 -VG9v 32346 -IHJlZ2lzdA== 32347 -bGljYWw= 32348 -IHBvZXRyeQ== 32349 -SW1wb3J0cw== 32350 -77yB77yB 32351 -Ojw= 32352 -IGNoYXJt 32353 -IENvdW4= 32354 -b2xsaWRlcg== 32355 -IGh3 32356 -fWAK 32357 -PWFyZ3M= 32358 -IE5ldXJv 32359 -aXRpY2Fs 32360 -aWVuZW4= 32361 -IERvdA== 32362 -X09OTFk= 32363 -RE4= 32364 -IFBsYXlTdGF0aW9u 32365 -IHN0ZWVw 32366 -IHByYWN0aWNhbGx5 32367 -IGFwcGxpY2FudA== 32368 -IGFyb20= 32369 -YW5pYw== 32370 -CWRpc3BsYXk= 32371 -IHRlcm1pbmF0ZWQ= 32372 -IGNsYXJpdHk= 32373 -IE1lbnVJdGVt 32374 -IEt1cg== 32375 -aWpl 32376 -X3dlZWs= 32377 -KGRpY3Q= 32378 -X3JlY29yZHM= 32379 -IENvc3Rh 32380 -IGtldA== 32381 -RXh0ZW5zaW9ucw== 32382 -IG5ldWtlbg== 32383 -aW5zaQ== 32384 -X2luYw== 32385 -IOaW 32386 -IGVpbmY= 32387 -IFJpc2s= 32388 -IGVsZXZhdGVk 32389 -cGVycw== 32390 -VURB 32391 -IEtO 32392 -IGxpbmVk 32393 -IE1vcm0= 32394 -KTsKCgoK 32395 -Pn0K 32396 -cGxhaW50 32397 -Z2V0VGV4dA== 32398 -IGluZGl2aWR1YWxseQ== 32399 -IGNoZWNrYm94 32400 -VVk= 32401 -IExhbWI= 32402 -IGR5c2Z1bmN0aW9u 32403 -IExhcg== 32404 -4LA= 32405 -IENyZWF0aW5n 32406 -Jyk7CgoK 32407 -IlRoZXk= 32408 -bG9jYXRpb25z 32409 -X0NPUkU= 32410 -SW50ZXJhY3Rpb24= 32411 -dW1ibmFpbHM= 32412 -IFBhcnRuZXI= 32413 -YnJpdA== 32414 -IGxlc3Nlcg== 32415 -IFNsb3Q= 32416 -c2V0QXR0cmlidXRl 32417 -IFdhdmU= 32418 -LnBv 32419 -L3N0b3Jl 32420 -IGJyb3dzaW5n 32421 -X3Bk 32422 -c3VtZQ== 32423 -c2Vk 32424 -Q3VydmU= 32425 -IHBsYXNtYQ== 32426 -IHN1c3BpY2lvdXM= 32427 -7J24 32428 -IEJhaA== 32429 -IEV4cGxpY2l0 32430 -X0ND 32431 -LkNsaWVudFNpemU= 32432 -XFZpZXc= 32433 -IHN1YnN0aXQ= 32434 -bG9vbg== 32435 -IEdBTUU= 32436 -IEJyaWQ= 32437 -m+W7ug== 32438 -X1VzZXI= 32439 -IHNxdWFyZXM= 32440 -Zm9uZQ== 32441 -IHNhY3JlZA== 32442 -dWdocw== 32443 -XWludGVyZmFjZQ== 32444 -IFRocm93 32445 -IEtpcms= 32446 -IGVtcGlyZQ== 32447 -IGFzc2Vzc2Vk 32448 -VGF4 32449 -IEhlYXZlbg== 32450 -LWJ1ZmZlcg== 32451 -X1NUQVRJQw== 32452 -w6luw6k= 32453 -LWJvcmRlcmVk 32454 -IHB1bmN0 32455 -KG1vZGU= 32456 -IGtlaW5l 32457 -U2VudA== 32458 -IENhbGN1bA== 32459 -IEV2ZQ== 32460 -IHN0eWxpc2g= 32461 -IG9pbHM= 32462 -LlRlc3RDYXNl 32463 -IHRyYWRlbWFyaw== 32464 -IGxpdGVyYXJ5 32465 -IGNvbmNlbnRyYXRpb25z 32466 -IFJlbGF0aW9ucw== 32467 -KENsYXNz 32468 -IHN0ZGlu 32469 -IHbDpg== 32470 -YmFja3Vw 32471 -LlZFUlNJT04= 32472 -LkF1dG9TY2FsZURpbWVuc2lvbnM= 32473 -c3RhcnRlcg== 32474 -VHJhbnNhY3Rpb25hbA== 32475 -LXBhbmVs 32476 -U3R1ZGlv 32477 -a2M= 32478 -IENoYW1iZXI= 32479 -IFNwaWVs 32480 -IHJobw== 32481 -2KfZhA== 32482 -ISc= 32483 -LkF0dHJpYnV0ZXM= 32484 -IG11cmRlcmVk 32485 -YXBldXRpYw== 32486 -IGludGltYXRl 32487 -IHRleHRGaWVsZA== 32488 -IEJ1ZmZhbG8= 32489 -ZHVtbXk= 32490 -IiU= 32491 -IExpYmVydHk= 32492 -b2Jhcg== 32493 -IFRhbms= 32494 -IFBvcHVsYXI= 32495 -ZXJ2aXNvcg== 32496 -IEluaXRp 32497 -IE1hbGw= 32498 -IFByaW9y 32499 -Q0FQ 32500 -IENsYXk= 32501 -IENlcnRpZmljYXRl 32502 -LkxvY2s= 32503 -LXN0cmlw 32504 -LWRyaXZlbg== 32505 -L2FsbA== 32506 -IE1lc3NhZ2VCb3hCdXR0b25z 32507 -X1NFQ1JFVA== 32508 -X3Bi 32509 -IHJhdHM= 32510 -4KS+4KQ= 32511 -IG50 32512 -LlJvdXRlcg== 32513 -X3RvcGlj 32514 -IHRlbm5pcw== 32515 -IFBVQkxJQw== 32516 -IEFjdGl2YXRlZFJvdXRl 32517 -ICcsCg== 32518 -IGNvc3R1bWU= 32519 -IGpva2Vz 32520 -LkhhbmRsZQ== 32521 -CWJ5dGU= 32522 -IGZsYXZvcnM= 32523 -KGNj 32524 -IHBlcnNvbmFz 32525 -CWltYWdl 32526 -IE5hemk= 32527 -IGdyYW1tYXI= 32528 -IMO6bHQ= 32529 -IHZhbHZl 32530 -IHZpYw== 32531 -IFJhY2hlbA== 32532 -X2ludmFsaWQ= 32533 -UHJlZnM= 32534 -c3RkaW50 32535 -KHJvdXRl 32536 -IGh0bWxzcGVjaWFsY2hhcnM= 32537 -IHBlb3BsZXM= 32538 -cGxpbmU= 32539 -IG52 32540 -IFF1YW50 32541 -b3BwZXJz 32542 -IGN1cnJlbnRVc2Vy 32543 -IENhdGFs 32544 -IHJlY29uYw== 32545 -IGNvbmp1bmN0aW9u 32546 -bHg= 32547 -YW1idXJn 32548 -IGluZmx1ZW50aWFs 32549 -ZGFuZ2Vy 32550 -aW5kZXJz 32551 -ICVAIiw= 32552 -LmNvbmZpZ3VyYXRpb24= 32553 -b3NvbWU= 32554 -LmlkZW50aXR5 32555 -IHBpY2tlcg== 32556 -bm9zdA== 32557 -IERJWQ== 32558 -QXVndXN0 32559 -YWJsbw== 32560 -TGVhZg== 32561 -IFJlY28= 32562 -Y2tv 32563 -RE9D 32564 -IEhlcm0= 32565 -OmFueQ== 32566 -IEludGVydmlldw== 32567 -IFRleA== 32568 -eGZl 32569 -KHdvcms= 32570 -IGxlYXA= 32571 -SGVhZGluZw== 32572 -IHF1YXJ0ZXJz 32573 -XEJ1bmRsZQ== 32574 -cmVi 32575 -UGVyaGFwcw== 32576 -IEdtYkg= 32577 -QmlydGg= 32578 -CXN1bQ== 32579 -IFdhdHNvbg== 32580 -Lm5pbA== 32581 -56E= 32582 -e30KCg== 32583 -aWNhaWQ= 32584 -R2V0dGVy 32585 -Im5hbWU= 32586 -ICINCg== 32587 -X25vbmU= 32588 -em0= 32589 -YWN1dGU= 32590 -dWVzdG8= 32591 -IHNvdXM= 32592 -IHJlYnVpbGQ= 32593 -IG5ld3NwYXBlcnM= 32594 -IEhheg== 32595 -IGtpdHM= 32596 -aWZv 32597 -Qmx1cg== 32598 -IHN1aXRlZA== 32599 -LUlu 32600 -4K8= 32601 -IEtlaXRo 32602 -IE5vcndheQ== 32603 -SU5JVA== 32604 -aXJlY2Npb24= 32605 -aWV0aWVz 32606 -X3VzYWdl 32607 -IERvdWc= 32608 -cmlzZQ== 32609 -IHRyaWxsaW9u 32610 -aW1pdGVk 32611 -IFJFTA== 32612 -YWxpYw== 32613 -IGNyaXRpY2l6ZWQ= 32614 -dGhlb3JlbQ== 32615 -IGNlYXNl 32616 -IHNpZGV3 32617 -IFRlcnJ5 32618 -IHN1YnNpZGk= 32619 -IGZpcm1seQ== 32620 -IGF3cw== 32621 -IGhvdHQ= 32622 -IGRyZXNzaW5n 32623 -YmFkZ2U= 32624 -IEFwcGxpY2F0aW9ucw== 32625 -6L+U5Zue 32626 -IGxhdWdoZWQ= 32627 -IGhvYmJ5 32628 -IG11c2ljaWFucw== 32629 -ICou 32630 -LnBsYWNlaG9sZGVy 32631 -IGNvdW50ZXJz 32632 -IENhcGl0b2w= 32633 -U0RL 32634 -IGhlbG1ldA== 32635 -YW5kYm94 32636 -cXVpdA== 32637 -IGNyaW1pbmFscw== 32638 -IHRlZW5hZ2Vy 32639 -KHVwZGF0ZQ== 32640 -R2w= 32641 -LnNlbGVjdGlvbg== 32642 -IGRpc2NoYXJnZQ== 32643 -IHByZXNlbnRpbmc= 32644 -dWZhY3R1cmVy 32645 -X1VOS05PV04= 32646 -IHN0cmVzc2Vk 32647 -5Zmo 32648 -UHJvdG8= 32649 -X2NvcnJlY3Q= 32650 -aGF1cw== 32651 -IHJlbm92 32652 -IGZpcmVhcm1z 32653 -IHRlY2huaWNhbGx5 32654 -LWJyb3dzZXI= 32655 -IGNhbmR5 32656 -U3Ryb2tl 32657 -IGV4ZWN1dG9y 32658 -IG9jY3VycmVuY2U= 32659 -IElQdg== 32660 -X0lOVEVSRkFDRQ== 32661 -IFJldHJpZXZl 32662 -LmJhZA== 32663 -RXhjaGFuZ2U= 32664 -TmF2YmFy 32665 -IEtpZA== 32666 -KGdldEFwcGxpY2F0aW9uQ29udGV4dA== 32667 -X1NUT1A= 32668 -IEJvc3M= 32669 -TGlzdGVuZXJz 32670 -IHNob290ZXI= 32671 -IEFsYg== 32672 -w6RjaA== 32673 -IHBpeA== 32674 -LmtleUNvZGU= 32675 -YWxvbmU= 32676 -IGFic3VyZA== 32677 -IEN1bQ== 32678 -IE5ld3RvbnNvZnQ= 32679 -aWt0 32680 -IGxhdWdoaW5n 32681 -IGNhcGl0YWxpc20= 32682 -cmVlTm9kZQ== 32683 -VHg= 32684 -X1FVRVJZ 32685 -LlNsZWVw 32686 -KGxvZ2lu 32687 -V2ViRWxlbWVudA== 32688 -IGNlbGVicmF0aW5n 32689 -IGRlcHJlY2F0ZWQ= 32690 -IG1hYXI= 32691 -IGFydGlzdGlj 32692 -X0FTU09D 32693 -IEJvcmRlclJhZGl1cw== 32694 -CXdw 32695 -IHN1cnZpdm9ycw== 32696 -SW5uZXI= 32697 -LXJlZA== 32698 -IHByb3NlY3V0aW9u 32699 -X3Bw 32700 -KCI8Lw== 32701 -IF49 32702 -IGxhbQ== 32703 -IFRyYWRpbmc= 32704 -ZmxhcmU= 32705 -RGV0ZWN0b3I= 32706 -TUY= 32707 -IEVtZXJnZW5jeQ== 32708 -IEVhZ2xlcw== 32709 -cXVhZA== 32710 -IEluY3Jl 32711 -cGxpYW5jZQ== 32712 -XE1pZ3JhdGlvbg== 32713 -IHVwZ3JhZGVz 32714 -Q1BV 32715 -YWdnaQ== 32716 -ZnByaW50Zg== 32717 -aWdpb24= 32718 -IGJlYXV0aWZ1bGx5 32719 -IGRyaWVk 32720 -X0hJR0g= 32721 -IGdwaW8= 32722 -TVND 32723 -IERlcHV0eQ== 32724 -IERlY2w= 32725 -IHRyZWFzdXJl 32726 -c2dpdmluZw== 32727 -X3NpZGViYXI= 32728 -IGFwYXJ0bWVudHM= 32729 -IFdy 32730 -IGJvYXRz 32731 -IGJvcg== 32732 -Lmxhbmd1YWdl 32733 -IFVp 32734 -bGl0 32735 -ZnJt 32736 -YW5jaWVz 32737 -IG1hc3Nlcw== 32738 -IEFzc2lnbg== 32739 -IFBPTA== 32740 -IG1hcERpc3BhdGNoVG9Qcm9wcw== 32741 -IGJyYWNrZXQ= 32742 -IFBhcA== 32743 -IENp 32744 -IEludG8= 32745 -IHRlYW1tYXRlcw== 32746 -IGZvcmFsbA== 32747 -dWx1aQ== 32748 -IENhcm4= 32749 -X0lOUw== 32750 -YXppb25p 32751 -Y2Vw 32752 -IHRvdXJpc3Rz 32753 -LWJsdWU= 32754 -IExlZA== 32755 -IHBlbmV0 32756 -IEZv 32757 -IGltYWdpbmc= 32758 -cHJh 32759 -IHNsYXZlcw== 32760 -b2xlcmFuY2U= 32761 -IGluY29ycG9yYXRlZA== 32762 -Jiw= 32763 -dWFibHk= 32764 -IEthcA== 32765 -WG1sRWxlbWVudA== 32766 -IE11ZWxsZXI= 32767 -Q2hhbmdlTGlzdGVuZXI= 32768 -IEhvbGlkYXk= 32769 -CSAgICAgICAgIA== 32770 -RmxleA== 32771 -CVVzZXI= 32772 -Il0pKQ== 32773 -X3N1Ym1pdA== 32774 -LmJvbGQ= 32775 -IGxvY2tz 32776 -IEN1YmE= 32777 -dWRzb24= 32778 -SG9vaw== 32779 -IFdhcm5lcg== 32780 -X3N0YXI= 32781 -Ij0+JA== 32782 -IGNvbW1h 32783 -dW5jaGVja2Vk 32784 -Z3JhcGhpY3M= 32785 -cm9ycw== 32786 -R1JPVU5E 32787 -KHB1YmxpYw== 32788 -IGN1c3RvbWl6ZWQ= 32789 -IEFya2Fuc2Fz 32790 -IFJldw== 32791 -IGV4cGlyYXRpb24= 32792 -15U= 32793 -IEN1bA== 32794 -IG5vbnM= 32795 -LkZpbHRlcg== 32796 -IHNlbmF0b3I= 32797 -X2RlZmluaXRpb24= 32798 -YXNoaW5ndG9u 32799 -eW1waA== 32800 -L0o= 32801 -IGZ1c2U= 32802 -cmFtaWQ= 32803 -IFN1cHBsaWVy 32804 -IGF1dG9jb21wbGV0ZQ== 32805 -IH0pLA== 32806 -LiIKCgo= 32807 -X2Z1bmN0aW9ucw== 32808 -CXRv 32809 -LmV2YWw= 32810 -IFRPYmplY3Q= 32811 -UmVmZXJlbmNlcw== 32812 -IGhlYXRlZA== 32813 -SEFM 32814 -ICkpfQo= 32815 -fSQ= 32816 -IEJhcnI= 32817 -X1VOSVQ= 32818 -KyQ= 32819 -IGdldFZhbHVl 32820 -aXBlZA== 32821 -Y2hpZWQ= 32822 -KHZt 32823 -Y3Vl 32824 -X2ludGVnZXI= 32825 -X2NvdXJzZQ== 32826 -dGhpcmQ= 32827 -IHJldmlzZWQ= 32828 -KiovCg== 32829 -X0RJUkVDVA== 32830 -T3V0T2Y= 32831 -KCIo 32832 -IEZlZWw= 32833 -IHJlYXNz 32834 -IHN1YnRpdGxl 32835 -cGVyaQ== 32836 -bmY= 32837 -IGVuam95cw== 32838 -IHRyZWF0cw== 32839 -KXRoaXM= 32840 -LXRhYnM= 32841 -YW5jZXJz 32842 -IGNvbnRpbmVudA== 32843 -IGNhcmRpbw== 32844 -U2Vy 32845 -LnF1ZXN0aW9u 32846 -IHBocmFzZXM= 32847 -VmFsaWRhdG9ycw== 32848 -IHBvcHVs 32849 -IGzDrQ== 32850 -c29uZw== 32851 -X0lOVEVSTkFM 32852 -IGFkdmlzZXI= 32853 -IHB1eno= 32854 -IGFtYml0aW91cw== 32855 -IFRvYg== 32856 -IERQ 32857 -IHByZXNpZGVuY3k= 32858 -IHN1cnJlbmRlcg== 32859 -IHdhdGNoZXM= 32860 -X2JpbmFyeQ== 32861 -IFNvb24= 32862 -IGNhbmFkYQ== 32863 -KCIiKQo= 32864 -XT0n 32865 -IEJyYW5kb24= 32866 -ZXBzaWxvbg== 32867 -cnc= 32868 -LmFkZENoaWxk 32869 -LkNvcHk= 32870 -UHJpbmNpcGFs 32871 -UGhvdG9z 32872 -IG1hcmdpbmFs 32873 -IGJhc2ljcw== 32874 -ZWluZw== 32875 -TXVzdA== 32876 -X1N0cmluZw== 32877 -IG9sZQ== 32878 -TWFnZW50bw== 32879 -LmN1c3RvbWVy 32880 -KHByZXY= 32881 -4Lil 32882 -IGxveWFsdHk= 32883 -Q29n 32884 -IHByb3RvY29scw== 32885 -IENvbXBhbmllcw== 32886 -IHRoZW9yZXRpY2Fs 32887 -IGFjY2Vzc2luZw== 32888 -IFplbg== 32889 -Lm9uZXM= 32890 -YXR0aWNl 32891 -X3dvcmxk 32892 -emVz 32893 -IHRhdHRvbw== 32894 -IG1lbm9z 32895 -IGludGVyc2VjdA== 32896 -Il07Cgo= 32897 -YmVsaWU= 32898 -IGluYWN0aXZl 32899 -LnJlYWRsaW5l 32900 -LWxhYmVsbGVk 32901 -LmRvbmU= 32902 -bGlja3I= 32903 -IFdPUks= 32904 -IGRlcml2YXRpdmU= 32905 -IGRhdGFiYXNlcw== 32906 -4oKC 32907 -IHN4 32908 -LmlzQXJyYXk= 32909 -IHlz 32910 -IHBhZGE= 32911 -IEJ1bGxldA== 32912 -KGAv 32913 -aXNBY3RpdmU= 32914 -IENHU2l6ZQ== 32915 -KGVxdWFsVG8= 32916 -IENvbHVtYnVz 32917 -IG1hcnJ5 32918 -REVW 32919 -X2xpbWl0cw== 32920 -cm9uZXM= 32921 -SUFT 32922 -IHRhdQ== 32923 -bWlubw== 32924 -X1dyaXRl 32925 -IFdpbmU= 32926 -IFtbJw== 32927 -IFB1bGw= 32928 -cml0ZXJz 32929 -cmllbnRz 32930 -IHNoaWZ0aW5n 32931 -dXBw 32932 -X1RJTUVS 32933 -IENvbmRpdGlvbnM= 32934 -4bql 32935 -IE9yZGVycw== 32936 -IFN0cmVuZ3Ro 32937 -5omA 32938 -IHZhbGlkaXR5 32939 -IGZvdA== 32940 -ZXR1cg== 32941 -IGJvbHQ= 32942 -5YaF 32943 -IEFsb25n 32944 -b3NoaQ== 32945 -IGFzc3VtcHRpb25z 32946 -IG1hZ2F6aW5lcw== 32947 -X1NQSQ== 32948 -IHB1bnQ= 32949 -X1BST0RVQ1Q= 32950 -IHJlbGF5 32951 -IEphdmFzY3JpcHQ= 32952 -LnRl 32953 -LWVz 32954 -IHdpZGdldHM= 32955 -KGZz 32956 -PEl0ZW0= 32957 -X2V4dHJh 32958 -IHJlY3J1aXRpbmc= 32959 -RXQ= 32960 -IG5lY2Vzc2l0eQ== 32961 -cHc= 32962 -IG5vdmVscw== 32963 -dXNzZWxz 32964 -Q3JlYXRvcg== 32965 -IE1WUA== 32966 -IE9D 32967 -dGhvb2Q= 32968 -Y2xpZW50cw== 32969 -KSkq 32970 -IGNoYXJhY3Rlcml6ZWQ= 32971 -X1NFTkQ= 32972 -dXRp 32973 -VHk= 32974 -LmZyb21Kc29u 32975 -QFNlcnZpY2U= 32976 -44KC 32977 -Q2hyaXM= 32978 -X0lz 32979 -IEpvaG5ueQ== 32980 -IGNsZWFuZXI= 32981 -IEluaXRpYWxpemVz 32982 -VU5L 32983 -KGF4aXM= 32984 -0LXQtw== 32985 -aWV2YWw= 32986 -IFdhcnJpb3Jz 32987 -fSko 32988 -RE1J 32989 -4pmA 32990 -IFRyZWFzdXJ5 32991 -IGZlYXM= 32992 -IHNsYQ== 32993 -X0VOVU0= 32994 -bGhz 32995 -IEluc3RpdA== 32996 -aXBwZXJz 32997 -TGluZWFy 32998 -UmVhZGluZw== 32999 -cXVpcmllcw== 33000 -LWNlbGw= 33001 -Y2hyb21l 33002 -LlNlYXJjaA== 33003 -SU5B 33004 -57G75Z6L 33005 -IAogCg== 33006 -IFNhbXVlbA== 33007 -IG1pbGxz 33008 -IGRvbmF0ZQ== 33009 -IEdlbw== 33010 -KHJvd3M= 33011 -IHNoZWVw 33012 -IMOpbA== 33013 -5L2T 33014 -IGJlbQ== 33015 -X1VOVVNFRA== 33016 -IFJDQw== 33017 -IGludHJvZHVjaW5n 33018 -YXR0YQ== 33019 -IFByaW9yaXR5 33020 -IEZC 33021 -IFNlcmdl 33022 -PiI7 33023 -YXRjaGluZw== 33024 -IEtub3dsZWRnZQ== 33025 -CVRoZQ== 33026 -O21hcmdpbg== 33027 -bGVzc25lc3M= 33028 -b3BhcmQ= 33029 -dW1hdGlj 33030 -KCkpKTsNCg== 33031 -IGZhbHM= 33032 -KGNhY2hl 33033 -VHlwZUlk 33034 -6YCa 33035 -X2Nob2ljZQ== 33036 -IEdvdGg= 33037 -IFNpdGVz 33038 -TUc= 33039 -X2JvcmRlcg== 33040 -SW5kaWNlcw== 33041 -Q29tcGFyZXI= 33042 -IFJlZGlzdHJpYnV0aW9u 33043 -IGNsb3NldA== 33044 -IHZlcnNhdGlsZQ== 33045 -SW5wdXRz 33046 -KioqKioqKioqKioqKioqKioqKio= 33047 -IG9iZXNpdHk= 33048 -cXVpeg== 33049 -Z3Jh 33050 -KGdsb2JhbA== 33051 -5Yqh 33052 -IGNvbGxlY3Rvcg== 33053 -IGtvcg== 33054 -b3ZhYmxl 33055 -QURD 33056 -IEV2ZW50SGFuZGxlcg== 33057 -Lm5j 33058 -IHBsYXliYWNr 33059 -aWVudG9z 33060 -X3Blcm0= 33061 -X1dBUk5JTkc= 33062 -IE9seW1waWNz 33063 -Lm5vcm0= 33064 -IEJyb2FkY2FzdA== 33065 -X3NtYWxs 33066 -ZHJpdmU= 33067 -Lmlsb2M= 33068 -IHR5cGVk 33069 -TUVN 33070 -X2NvbnM= 33071 -RE1FVEhPRA== 33072 -IGx1bg== 33073 -LmRpc3RhbmNl 33074 -KHBhcg== 33075 -cG9vbg== 33076 -IGJhc3Q= 33077 -YWN0aXZpdGllcw== 33078 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 33079 -Og0KDQo= 33080 -U0VS 33081 -KSYm 33082 -X2xzdA== 33083 -IFBvbGlzaA== 33084 -IGtub2NrZWQ= 33085 -IGZydXN0cmF0aW9u 33086 -YXVrZWU= 33087 -IHBob3NwaA== 33088 -aXF1aWQ= 33089 -X2NvZWZm 33090 -5q2k 33091 -TGF0ZXN0 33092 -IER1c3Q= 33093 -VGlwbw== 33094 -IG1haW50YWlucw== 33095 -IG1hcnNo 33096 -aW5jaW5u 33097 -bGJs 33098 -Q2FyZQ== 33099 -IG5laWdoYm9yaG9vZHM= 33100 -X2dwaW8= 33101 -IEFyc2VuYWw= 33102 -RGVt 33103 -IFdoZQ== 33104 -X2hvb2s= 33105 -IGxkYw== 33106 -IEhhcnBlcg== 33107 -IEJlcmtlbGV5 33108 -IGdyYWR1YXRlZA== 33109 -UGVyY2VudA== 33110 -IGFycml2aW5n 33111 -IEFkdmVudHVyZQ== 33112 -KHNjb3Bl 33113 -KCcq 33114 -cXVhcnRlcg== 33115 -IE1hcmll 33116 -U3BlYWtpbmc= 33117 -X2NvZGVnZW4= 33118 -IGltbXVu 33119 -Y2FzdGVy 33120 -44KM 33121 -5ZWG 33122 -IERpbWVuc2lvbnM= 33123 -LnJlY29yZA== 33124 -IHRleHRv 33125 -IE1pY2hlbGxl 33126 -UGVuZGluZw== 33127 -KGJ5 33128 -X1BBUg== 33129 -dWNodA== 33130 -YmVl 33131 -LlRocmVhZA== 33132 -YW1waXJl 33133 -a25vdw== 33134 -IENsaW5pY2Fs 33135 -IG1hcmdpbkJvdHRvbQ== 33136 -IGRpc3Rpbmd1aXNo 33137 -LkZ1bGw= 33138 -LnVuZGVmaW5lZA== 33139 -IFNlcXVlbGl6ZQ== 33140 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 33141 -IGVkdWNhdGVk 33142 -X09WRVI= 33143 -5bqP 33144 -IMKgIMKg 33145 -X2VhY2g= 33146 -IHVyZ2U= 33147 -ZGVwYXJ0 33148 -IGRvbm9ycw== 33149 -IEF1 33150 -IGJpbGxpb25z 33151 -IGJlbG9uZ2luZw== 33152 -X2FnZQ== 33153 -X0ludA== 33154 -IHN1YnN0YW5jZXM= 33155 -bWFjaGluZQ== 33156 -ISEhCgo= 33157 -IGpzb25pZnk= 33158 -aWJiZWFu 33159 -IENhZA== 33160 -IGVuZFRpbWU= 33161 -IGN5Y2xpbmc= 33162 -IFVJVGV4dEZpZWxk 33163 -IGxldmVyYWdl 33164 -IHZhbmlsbGE= 33165 -ZWF0 33166 -TGF1bmNo 33167 -KHB0 33168 -c3RhdGVz 33169 -IENvbnRyb2xz 33170 -IFJlc3BvbnM= 33171 -IEpha2U= 33172 -IGFzbGVlcA== 33173 -Zm9ydHVuYXRl 33174 -Lm5leHRMaW5l 33175 -U2l6ZU1vZGU= 33176 -7J28 33177 -VGVzdGluZ01vZHVsZQ== 33178 -R2VybWFu 33179 -IEludmVzdGln 33180 -LnJldmVyc2U= 33181 -IEJBQ0s= 33182 -KERhdGVUaW1l 33183 -IG5vbnByb2ZpdA== 33184 -IEV4cGVjdA== 33185 -IHRhbnRv 33186 -J10pLA== 33187 -CXRoZQ== 33188 -TXVsdGlwbGU= 33189 -KGdldEFjdGl2aXR5 33190 -X1dBSVQ= 33191 -IGrDoQ== 33192 -ZGVjb3I= 33193 -bGV2YW5jZQ== 33194 -IEdpdEh1Yg== 33195 -bWluYXRpb24= 33196 -X3F1YW50aXR5 33197 -LlNjYW5uZXI= 33198 -IExpb24= 33199 -6ZSZ6K+v 33200 -IGRyZQ== 33201 -IHRhbnRyYQ== 33202 -IGNvbnRlbnRUeXBl 33203 -IGZpZA== 33204 -X2FsdA== 33205 -TlNJbmRleFBhdGg= 33206 -LXBs 33207 -5YyW 33208 -IGFudGliaW90 33209 -dGFibGVz 33210 -YWNpYWw= 33211 -IFJlZ2lzdHJ5 33212 -IG9saXZl 33213 -aWdlcnM= 33214 -IHN1YnNjcmliZXI= 33215 -X3ByZXM= 33216 -IFN5bnRheA== 33217 -IGxvdmVycw== 33218 -LkJ5dGU= 33219 -b2xkZXJz 33220 -X2ZvcndhcmQ= 33221 -YWx3YXlz 33222 -Q2FwdGlvbg== 33223 -UHJpdg== 33224 -IFRhbXBh 33225 -aXNhdGV1cg== 33226 -LWxhYmVsbGVkYnk= 33227 -IFRvU3RyaW5n 33228 -IOyCrA== 33229 -IGluaXRpYXRlZA== 33230 -V0Y= 33231 -IGluc3RpdHV0aW9uYWw= 33232 -aW5qZWN0 33233 -IFNjcg== 33234 -IGRvY3RyaW5l 33235 -IHNwYWNpb3Vz 33236 -aXN1cmU= 33237 -IEFuYQ== 33238 -InRpbWU= 33239 -ZXNzYWdpbmc= 33240 -IGNpZA== 33241 -IE5hbg== 33242 -IGluY29tcGxldGU= 33243 -VEFH 33244 -LWJ1aWxk 33245 -RGVjZW1iZXI= 33246 -IHJlc2lkdWFs 33247 -KFBETw== 33248 -IExpc3Rlbg== 33249 -IGdseXBo 33250 -IGdhcHM= 33251 -bmVh 33252 -LlJlY3Q= 33253 -IHNhdQ== 33254 -IFBob3RvZ3JhcGg= 33255 -IGV4ZWN1dGFibGU= 33256 -IEV4cGVydA== 33257 -Q29yb3V0aW5l 33258 -X3NpemVz 33259 -IE5M 33260 -LmlzVmFsaWQ= 33261 -KTt9Cg== 33262 -LXJlZw== 33263 -IGNpdGluZw== 33264 -Y3dk 33265 -IE90dGF3YQ== 33266 -IEJhdHQ= 33267 -IHJlbmV3YWJsZQ== 33268 -IHByZWxpbWluYXJ5 33269 -IGFzeWx1bQ== 33270 -IHdyaXN0 33271 -IHV0aWxpeg== 33272 -IGRldGVudGlvbg== 33273 -RmFzdA== 33274 -IGFuZ2U= 33275 -aW5jaW5uYXRp 33276 -IHN0ZWVyaW5n 33277 -IE5hTg== 33278 -aW9zaXR5 33279 -L3BhZ2U= 33280 -IOi/ 33281 -c3Rlcm9s 33282 -IGRpc2c= 33283 -KERC 33284 -IERFU0NSSVBUSU9O 33285 -IF8k 33286 -IG9ic3RhY2xl 33287 -IGJpemFycmU= 33288 -IGV4dHJhY3Rpb24= 33289 -X2V4cGVjdGVk 33290 -IGxvc2Vz 33291 -IENlbGVicg== 33292 -IGh0bWxGb3I= 33293 -IGV4cGxvaXQ= 33294 -0L7Qu9GM0LfQvtCy 33295 -WFla 33296 -IG1hZ25ldA== 33297 -YW1wZWQ= 33298 -IGF0b21z 33299 -U291cmNlcw== 33300 -cGVjdGl2ZXM= 33301 -0YHQu9C4 33302 -ID0NCg== 33303 -IGRhcmU= 33304 -IFdhbHRlcg== 33305 -IGJyaWdodG5lc3M= 33306 -IGFubm90YXRpb25z 33307 -648= 33308 -aXNrZQ== 33309 -U2NoZWR1bGU= 33310 -LmltYWdlcw== 33311 -cm9zc28= 33312 -ICIuLg== 33313 -Z2FtbWE= 33314 -IGluc3RydWN0b3I= 33315 -IG92ZXJ3cml0ZQ== 33316 -LWFt 33317 -IGRldmFzdGF0aW5n 33318 -IFNhaW50cw== 33319 -IGhz 33320 -IGJvbnVzZXM= 33321 -JG91dHB1dA== 33322 -aWpk 33323 -KEFjdGlvbkV2ZW50 33324 -bW9uaXRvcg== 33325 -IG1hdHRyZXNz 33326 -SmFudWFyeQ== 33327 -Lmpw 33328 -IGNhcmFjdGVy 33329 -IGltcG9zZQ== 33330 -X3Jlc3Q= 33331 -IFNpZ25hdHVyZQ== 33332 -IGNvcm9uYXZpcnVz 33333 -44GK 33334 -X2NvbXBhcmU= 33335 -TWVhc3VyZQ== 33336 -aXRhdGVk 33337 -ZWxpams= 33338 -aWdvcw== 33339 -ZXNhcg== 33340 -IHJ1c2hlZA== 33341 -bWV0cnk= 33342 -X1NFUEFSQVRPUg== 33343 -X1dF 33344 -X0FUVFJJQlVURQ== 33345 -IHlhbWw= 33346 -IHNwZWNz 33347 -IFJhaA== 33348 -cGhlcmlj 33349 -IEludmVzdG1lbnQ= 33350 -w6RsbA== 33351 -IGFwcGVhbGluZw== 33352 -IHZpZXdwb3J0 33353 -56k= 33354 -IG1hcmdpbkxlZnQ= 33355 -IHN1YnRyYWN0 33356 -IEVESVQ= 33357 -CUFycmF5TGlzdA== 33358 -Z3JhZGluZw== 33359 -IEZhaWx1cmU= 33360 -YXNwZXI= 33361 -RUVL 33362 -KG5vdw== 33363 -PG9iamVjdA== 33364 -IEFsaWdubWVudA== 33365 -cGxlYWRv 33366 -cXR0 33367 -KEVSUk9S 33368 -IElOVkFMSUQ= 33369 -IHVzZXJpZA== 33370 -cmFpc2Vz 33371 -SURJ 33372 -IHZhcmlhbmNl 33373 -IE5pbA== 33374 -L2RlbGV0ZQ== 33375 -X01BSU4= 33376 -LlRva2Vu 33377 -LkNhdGVnb3J5 33378 -PikK 33379 -Q29sbGlzaW9u 33380 -IEdyZWF0ZXI= 33381 -IFJhY2luZw== 33382 -YWxhbg== 33383 -IG1vbmV0YXJ5 33384 -LG5ldw== 33385 -IFNvcnJ5 33386 -LkVuYWJsZQ== 33387 -IEluc3RhbnRpYXRl 33388 -b2xsZW4= 33389 -66m0 33390 -IENhbGxpbmc= 33391 -X2hvdXI= 33392 -QURB 33393 -IHNoeQ== 33394 -KSoq 33395 -ID09Pg== 33396 -IGVzcGVjaWFs 33397 -IGludGVycHJldGVk 33398 -IT0i 33399 -IHBoYXJtYWN5 33400 -LnNpbmdsZQ== 33401 -IENpYWxpcw== 33402 -IHBhcmFz 33403 -LnRvVXBwZXJDYXNl 33404 -IERlbW9u 33405 -UHJpbWU= 33406 -IHJhbmtpbmdz 33407 -QWRkaW5n 33408 -X0hBU0g= 33409 -IEV4YW0= 33410 -2qk= 33411 -IFZpY3Rvcg== 33412 -T2theQ== 33413 -Il07DQo= 33414 -IGZvcnR1bmU= 33415 -IEZFVENI 33416 -ZXhwYW5k 33417 -LkludGVyb3A= 33418 -IGJhcm4= 33419 -5raI 33420 -dWV2bw== 33421 -IHNwZWN1bGF0aW9u 33422 -4pSA4pSA4pSA4pSA 33423 -IE51 33424 -IEJsdWVz 33425 -KGZuYW1l 33426 -IGluaGFiaXQ= 33427 -IFwiJQ== 33428 -Q0VT 33429 -dWxhcmlv 33430 -X2Ny 33431 -IHZhbGlkYXRlZA== 33432 -IG1pZG5pZ2h0 33433 -YW5raW5n 33434 -IGluY29ycG9yYXRl 33435 -IHB1cnN1aXQ= 33436 -RVhQ 33437 -cHJpbWU= 33438 -UGlk 33439 -LVVT 33440 -IE51cnM= 33441 -IFdoZWVs 33442 -6Zg= 33443 -IGlucA== 33444 -IHN1cHBvcnRpdmU= 33445 -Lm1lbWJlcg== 33446 -IFNob3Q= 33447 -LkNoZWNrQm94 33448 -IGFmZmlybQ== 33449 -VG9y 33450 -RnVsbFllYXI= 33451 -IGNvbnNpZGVyYWJseQ== 33452 -Y3JlZGVudGlhbHM= 33453 -X29wdHM= 33454 -Um9sbA== 33455 -KHJvdW5k 33456 -IGNvbWVudA== 33457 -X1VBUlQ= 33458 -IGV4dGVuZGluZw== 33459 -Ukc= 33460 -cmVzdWx0YWRv 33461 -aXR1 33462 -LmdldFNlc3Npb24= 33463 -IGF0dHJhY3Rpb24= 33464 -JkQ= 33465 -JGh0bWw= 33466 -IEplc3NpY2E= 33467 -IEFzc29jaWF0ZQ== 33468 -YcOx 33469 -X2Vk 33470 -IExhZw== 33471 -IG9yaWdpbnM= 33472 -KCkpLT4= 33473 -YWRkRXZlbnRMaXN0ZW5lcg== 33474 -SUFMT0c= 33475 -5ZCm 33476 -LkNvbXBhcmU= 33477 -QWxidW0= 33478 -IEt1 33479 -PFE= 33480 -YXJnZXN0 33481 -IHByb2xvbmc= 33482 -IGNvbmZpZ3VyYXRpb25z 33483 -IGFjY2lkZW50YWxseQ== 33484 -X3Bob3Rv 33485 -ICcnOw0K 33486 -IHZlcnNl 33487 -Qm9i 33488 -IGZhcm1pbmc= 33489 -ZGVsaXZlcnk= 33490 -IE1hY2s= 33491 -IHVzZVNlbGVjdG9y 33492 -LmJvb3RzdHJhcGNkbg== 33493 -a2VlcGluZw== 33494 -ZW55 33495 -LnVwbG9hZA== 33496 -IE1FVEhPRA== 33497 -Y3JlYXRvcg== 33498 -PF8= 33499 -IEVhc3Rlcg== 33500 -Li0t 33501 -VUlCdXR0b24= 33502 -44KJ 33503 -b21ldGVycw== 33504 -IHNoaW5l 33505 -IGhvZ3k= 33506 -XHM= 33507 -IGhhcm5lc3M= 33508 -LkNlbGw= 33509 -IGxpZnRpbmc= 33510 -IGNvbWJpbmVz 33511 -IE9jY3Vw 33512 -ZXhjbHVkZQ== 33513 -cGF0aWFs 33514 -IHJlc3Bpcg== 33515 -X2ZpdA== 33516 -IGZpZnR5 33517 -IE1vbA== 33518 -IHR1bmVk 33519 -LWRpbWVuc2lvbmFs 33520 -IHFz 33521 -IHRvcHM= 33522 -PiI7Cgo= 33523 -cXVpc2l0ZQ== 33524 -Y2hhbm5lbHM= 33525 -L3Jlcw== 33526 -IEFuYWx5dGljcw== 33527 -LmFwcGNvbXBhdA== 33528 -L3Rv 33529 -IG9uRXJyb3I= 33530 -KGF0dHI= 33531 -SVJN 33532 -IHJhZ2F6 33533 -LWFz 33534 -LlNlY29uZA== 33535 -b3JpZW50ZWQ= 33536 -IGRvbm4= 33537 -IGxpZ2h0bmluZw== 33538 -Zmlk 33539 -IFBsZQ== 33540 -44G+44GZ 33541 -dHJv 33542 -LlRydWU= 33543 -T2JzZXJ2YWJsZQ== 33544 -15k= 33545 -dW1iaW5n 33546 -IHByb3NwZWN0aXZl 33547 -LWZpbHRlcg== 33548 -IHB1cnN1YW50 33549 -KHBvaW50cw== 33550 -LkJpbmQ= 33551 -IHBhbG0= 33552 -Y2xlYXJmaXg= 33553 -w7Zz 33554 -IEdvbno= 33555 -IHdlYWtlbg== 33556 -RHJpdmU= 33557 -ZW5pZG8= 33558 -bGxk 33559 -b2JveA== 33560 -YW5lYW4= 33561 -R290 33562 -5L+d 33563 -UmVnZXg= 33564 -5oM= 33565 -IHNhbGFk 33566 -YXNzaXM= 33567 -Im5ldA== 33568 -aW5oZXJpdERvYw== 33569 -IFJW 33570 -cXVpZXI= 33571 -IGNsYXp6 33572 -xLHFnw== 33573 -b3N0ZXJvbmU= 33574 -IGFpcmxpbmU= 33575 -Lmxpc3RkaXI= 33576 -IGRvd25sb2FkaW5n 33577 -IFBhbG0= 33578 -d2F1a2Vl 33579 -Jmx0 33580 -LkJM 33581 -X0lOTElORQ== 33582 -b2Zmcw== 33583 -PDwo 33584 -X25ld3M= 33585 -IGNoYXNl 33586 -Lz48 33587 -IGV1cm9z 33588 -IEVneXB0aWFu 33589 -IFN0YWlubGVzcw== 33590 -X0JPT0w= 33591 -IEd1aWxk 33592 -IER5bmFt 33593 -W2luZGV4UGF0aA== 33594 -IO8= 33595 -IG1lbW9yYWJsZQ== 33596 -IENoYW1waW9u 33597 -UmVzb3VyY2VNYW5hZ2Vy 33598 -LkxvZ2lu 33599 -IEZvcm1lcg== 33600 -eXBlZA== 33601 -IGxsZWc= 33602 -OyIs 33603 -RFdPUkQ= 33604 -IHRheGk= 33605 -IGJvbWJz 33606 -cmFo 33607 -LnRhZ3M= 33608 -X3Rlc3Rz 33609 -c3RvbmVz 33610 -4oCdKQ== 33611 -W2c= 33612 -cnR5cGU= 33613 -IHZ1 33614 -IGhvc3RpbGU= 33615 -Q2hhcnM= 33616 -IFBhdHJpb3Rz 33617 -L3N0YXR1cw== 33618 -PEI= 33619 -IEluY29tZQ== 33620 -IERhZA== 33621 -IHBhdHJvbA== 33622 -X0NIQU5HRQ== 33623 -IHVwZ3JhZGVk 33624 -IGNoaW5h 33625 -c2V0cQ== 33626 -U3RhcnRlZA== 33627 -LlVuZGVm 33628 -IGNoZWNrc3Vt 33629 -IGZydXN0cmF0ZWQ= 33630 -e28= 33631 -IGVuZg== 33632 -IHdvb2Rz 33633 -IEFueW9uZQ== 33634 -RW5jb2Rl 33635 -IFF0V2lkZ2V0cw== 33636 -YXJlYXM= 33637 -IHNoZWVy 33638 -c2tp 33639 -ZW5kcG9pbnQ= 33640 -X1Rlc3Q= 33641 -U291cA== 33642 -fn5+fn5+fn5+fn5+fn5+fg== 33643 -KGZpbGVz 33644 -CQkJCQkNCg== 33645 -LnNwYXJr 33646 -IHZhbHVlZA== 33647 -ICUK 33648 -LmNvbnRyb2xz 33649 -IFhDVEFzc2VydEVxdWFs 33650 -IGZhbWU= 33651 -IFJpYw== 33652 -RE9U 33653 -IEFsYmVydGE= 33654 -5L2/ 33655 -b3NhbA== 33656 -LldlYkNvbnRyb2xz 33657 -IC0tLS0tLS0tLS0tLQ== 33658 -IE1pcw== 33659 -IFNZUw== 33660 -Tm9ubnVsbA== 33661 -PWl0ZW0= 33662 -IGV4cGlyZQ== 33663 -RGVjb2Rl 33664 -X29wZXJhdGlvbg== 33665 -IFZhbGlkYXRvcg== 33666 -LkNFTlRFUg== 33667 -dWZmcw== 33668 -Km0= 33669 -IGF2YW50 33670 -5qyh 33671 -4oCcWW91 33672 -LnBlcm1pc3Npb24= 33673 -Li4uKQ== 33674 -IExpYw== 33675 -X2Nvb3Jkcw== 33676 -Lm5vbWJyZQ== 33677 -Y2xv 33678 -LkludGVybmFs 33679 -IENobw== 33680 -X3N3 33681 -CUls 33682 -Y2xr 33683 -IGNhc3RsZQ== 33684 -KGxheWVy 33685 -cGl0 33686 -IGd1aWRlZA== 33687 -IOKWiA== 33688 -IHN1cGVyYg== 33689 -IHN1cHBsZW1lbnRz 33690 -X2NlbnQ= 33691 -IHBlZWs= 33692 -SU5BUlk= 33693 -LkNvbnRlbnRBbGlnbm1lbnQ= 33694 -ZmFsbHM= 33695 -IikpOw== 33696 -V2FsbA== 33697 -KS4NCg== 33698 -IERhbm55 33699 -aXJtaW5naGFt 33700 -SUFMSVo= 33701 -KGNyZWF0ZQ== 33702 -Iklu 33703 -U2VydmljZVByb3ZpZGVy 33704 -IHByaWNlZA== 33705 -bWFjcm8= 33706 -YW1hYw== 33707 -LmJveA== 33708 -LS0tLQo= 33709 -44Or 33710 -IFN1aXQ= 33711 -dXJzdA== 33712 -YnJ1 33713 -b3VybmFscw== 33714 -bnVtZXJv 33715 -X18oKQo= 33716 -RGFz 33717 -IE1pdHQ= 33718 -dWRlcg== 33719 -P1w= 33720 -ZnU= 33721 -W0I= 33722 -IDopCgo= 33723 -KGludGVy 33724 -YnJhaW5z 33725 -IGF0dGl0dWRlcw== 33726 -VmVyaWZ5 33727 -IHNpZ25hdHVyZXM= 33728 -YWNrQmFy 33729 -IGdk 33730 -SmFjaw== 33731 -LmNhdA== 33732 -IHp6 33733 -d2FyZg== 33734 -RlRFUg== 33735 -Iik7CgoK 33736 -QWxpdmU= 33737 -SUNMRQ== 33738 -IFdoYXRldmVy 33739 -IG91dGxpbmVk 33740 -c3ByaXRl 33741 -0LXQsg== 33742 -X0FC 33743 -X0RFUFRI 33744 -IGNydXNoZWQ= 33745 -YWFh 33746 -KGV2 33747 -5py6 33748 -QW50aQ== 33749 -SUNP 33750 -aXNFcXVhbFRv 33751 -LnN1bg== 33752 -aWN1bG8= 33753 -c2FsZQ== 33754 -X2hleA== 33755 -IFZr 33756 -YXB0b3I= 33757 -VW5pb24= 33758 -IERpc2NvdW50 33759 -bGlzdGE= 33760 -LlVuZGVmT3I= 33761 -IGF1dG9tYXRpb24= 33762 -Tm9y 33763 -5a+5 33764 -5Y+C5pWw 33765 -IHJlZmxleA== 33766 -IExhdXJl 33767 -LnNob3dNZXNzYWdlRGlhbG9n 33768 -LnRlbXA= 33769 -IGFrYW4= 33770 -IF9fX19fXw== 33771 -LklzVHJ1ZQ== 33772 -QVJFRA== 33773 -YWdsZQ== 33774 -RW5lcmd5 33775 -IHF1YW50aXRpZXM= 33776 -4oCZw6k= 33777 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 33778 -IGNpdGl6ZW5zaGlw 33779 -bW91dGg= 33780 -IGluYXBwcm9wcmlhdGU= 33781 -IE91dGRvb3I= 33782 -V2hpdGVTcGFjZQ== 33783 -QW5vbnltb3Vz 33784 -bG9hZHM= 33785 -d2ViRWxlbWVudFByb3BlcnRpZXM= 33786 -VGVu 33787 -IGFjY2lkZW50cw== 33788 -IGFkdmVydGlzZW1lbnQ= 33789 -IFllbWVu 33790 -KGNhbGw= 33791 -IHNsYXZlcnk= 33792 -0YHQvw== 33793 -IExhbQ== 33794 -X0JJVFM= 33795 -b21lZ2E= 33796 -IE9sZQ== 33797 -IGtpZG4= 33798 -X0Fu 33799 -IFJhaWQ= 33800 -Q3JlYXRpb24= 33801 -c2F2ZWQ= 33802 -IHByb3BvcnQ= 33803 -V0FSTklORw== 33804 -XFA= 33805 -IHB3ZA== 33806 -RGF0YVJlYWRlcg== 33807 -aXNjaGVy 33808 -YWRlb24= 33809 -IFByZWRpY3Q= 33810 -IHJlYXNvbmluZw== 33811 -IGRlc3Ryb3lpbmc= 33812 -SGVs 33813 -KmQ= 33814 -IExlZ2lzbA== 33815 -X1By 33816 -CQkJICAgICAgIA== 33817 -IHN5bXBhdGg= 33818 -IGNoZXNz 33819 -IG1hbQ== 33820 -OmhvdmVy 33821 -IGNvbnZlcnRz 33822 -IHBlbGE= 33823 -IHByb2dyZXNzaW9u 33824 -ICJfIg== 33825 -IEdpbGw= 33826 -CXNob3c= 33827 -IHN1cHBvc2VkbHk= 33828 -YWNjdXJhY3k= 33829 -ZWxpbg== 33830 -IHVuZm9sZGluZw== 33831 -IEh5cGVy 33832 -IHdhbm5h 33833 -IHVwcw== 33834 -KCM= 33835 -IENyaW1pbmFs 33836 -KFBvaW50 33837 -YXRMbmc= 33838 -YWN0bHk= 33839 -IGNvbnRyYWN0b3Jz 33840 -J119 33841 -ZHJhdWxpYw== 33842 -w7NkaWdv 33843 -IFRU 33844 -IFdpZGU= 33845 -IEFSRw== 33846 -X2lj 33847 -RkxBR1M= 33848 -U2Nob29s 33849 -IGNsZWFyaW5n 33850 -LWJlaW5n 33851 -PXtb 33852 -LGNvbnN0 33853 -bWFuZW50 33854 -T3ZlcmxheQ== 33855 -KCci 33856 -6YeP 33857 -IFRpbWVzdGFtcA== 33858 -IG1haWxpbmc= 33859 -IENha2U= 33860 -LlRoYXQ= 33861 -IG1lZGl0YXRpb24= 33862 -cXA= 33863 -IGVtcHJlc2E= 33864 -IExpb25z 33865 -IHdlbGQ= 33866 -IExpbmtlZElu 33867 -IGN1c2g= 33868 -IGdlbm9tZQ== 33869 -LkluZGV4T2Y= 33870 -YWdhaW4= 33871 -IGZhbGxiYWNr 33872 -IGNhbXBpbmc= 33873 -cmVkZA== 33874 -LXN0cmlwZWQ= 33875 -IGR2 33876 -RmVicnVhcnk= 33877 -IFByb3h5 33878 -dXNr 33879 -IGRpZXNlbA== 33880 -V1JJVEU= 33881 -UkVBSw== 33882 -TG9yZW0= 33883 -Lkludm9rZQ== 33884 -LWRpdg== 33885 -SW50ZXJjZXB0b3I= 33886 -IERI 33887 -aWFsZXM= 33888 -IHZpbGxhZ2Vz 33889 -2LQ= 33890 -IEVOVg== 33891 -U3lz 33892 -LlhS 33893 -IHBvZW0= 33894 -w4I= 33895 -Y2FkZQ== 33896 -cGxvdHM= 33897 -IHso 33898 -LmdpdA== 33899 -L3N2Zw== 33900 -bmNtcA== 33901 -IMSN 33902 -YWluZXM= 33903 -5Ye95pWw 33904 -ICgpCgo= 33905 -b3BzaXM= 33906 -IFJlbGF0aW9uc2hpcA== 33907 -X2F1dA== 33908 -IEJvbWI= 33909 -CWNvbQ== 33910 -KnNpemVvZg== 33911 -b2ZmaWNpYWw= 33912 -X3BheWxvYWQ= 33913 -CQkJCQkgIA== 33914 -Lm1hbmFnZXI= 33915 -IEFyb3VuZA== 33916 -CXNlbmQ= 33917 -IEV4ZXJjaXNl 33918 -IEJpbGx5 33919 -aXZp 33920 -IG5lZWRpbmc= 33921 -X3VybHM= 33922 -X3Rhc2tz 33923 -IEhlbQ== 33924 -IHRlYXJEb3du 33925 -ZW5jcnlwdA== 33926 -LnRpZQ== 33927 -IGFzbQ== 33928 -SUNI 33929 -IENHUmVjdE1ha2U= 33930 -7ISx 33931 -dWxvbmc= 33932 -IGl0cg== 33933 -IEdTVA== 33934 -IG9mZmVyaW5ncw== 33935 -cm9iZQ== 33936 -RUVF 33937 -b3BlcmF0b3Jz 33938 -X1BST1A= 33939 -aW5kZW50 33940 -QURF 33941 -b3Jm 33942 -65A= 33943 -IGJsZXNzZWQ= 33944 -dmFzY3VsYXI= 33945 -IGNvbm9j 33946 -SGFwcHk= 33947 -QnJpZGdl 33948 -aWxpdGF0aW9u 33949 -am9pbnQ= 33950 -IEFkbWluaXN0cg== 33951 -LXRyYW5zZm9ybQ== 33952 -IG1lYW50aW1l 33953 -L0s= 33954 -IEJlZHJvb20= 33955 -IHJpZ2lk 33956 -IGJyb3dzZXJz 33957 -RU1QVFk= 33958 -LlNlcmlhbGl6ZQ== 33959 -X0VE 33960 -IHN0aXRjaA== 33961 -IGphbg== 33962 -ZWxsdA== 33963 -IGJyYWNl 33964 -IHRyYWlscw== 33965 -cHVibGlzaGVk 33966 -5a+G56CB 33967 -fScpCg== 33968 -IGFjaWRz 33969 -ICEhIQ== 33970 -X2RpcmVjdA== 33971 -PigpKTsK 33972 -YWrEhQ== 33973 -X09DQw== 33974 -IHBsYW5ldHM= 33975 -5p+l 33976 -IER1Ymxpbg== 33977 -IHNlcmll 33978 -LnByaW50Zg== 33979 -ZGVlcA== 33980 -YCk= 33981 -IFwk 33982 -IM68 33983 -X1ZJREVP 33984 -ZW5kb3Jz 33985 -IENyeXB0bw== 33986 -RmFy 33987 -LlRyYW5zcGFyZW50 33988 -LlRS 33989 -aWFzbQ== 33990 -X3RyYWluaW5n 33991 -IHRlYWNoZXM= 33992 -IEJlbHQ= 33993 -IGxpbWl0aW5n 33994 -IEthdGg= 33995 -IEluZGV4UGF0aA== 33996 -IGFjaGlldmVtZW50cw== 33997 -IHNlcsOh 33998 -aW50ZXJvcFJlcXVpcmU= 33999 -IGRpc3Nl 34000 -Lklm 34001 -YXJtaW5n 34002 -dWxzaW9u 34003 -UG8= 34004 -X0RFVEFJTA== 34005 -UHJvdG90eXBl 34006 -IENBTA== 34007 -IGFncmVlcw== 34008 -LnZv 34009 -LkV4ZWN1dGVOb25RdWVyeQ== 34010 -IFRvcGlj 34011 -ICd7fQ== 34012 -QXJt 34013 -IGVjYw== 34014 -TWFn 34015 -IHNlcmlhbGl6ZWQ= 34016 -CWNvbm4= 34017 -Y2FjaGVk 34018 -PXRm 34019 -IEJ5dGVBcnJheQ== 34020 -cHJvdG9idWY= 34021 -dmFyY2hhcg== 34022 -CUFTU0VSVA== 34023 -IGxpc3Rl 34024 -X3RyaWdnZXI= 34025 -t7g= 34026 -RmVlbA== 34027 -VGFob21h 34028 -IExpaw== 34029 -IHN0cnVjdHVyZWQ= 34030 -ZXJndXM= 34031 -LkluaXRpYWw= 34032 -X2dl 34033 -Y2xqcw== 34034 -LmNvbnRhY3Q= 34035 -IGFuZGVyZQ== 34036 -JHN0bXQ= 34037 -X0NVUlJFTlQ= 34038 -IERpc2NvdmVy 34039 -JHJlcw== 34040 -Zm9ybWF0dGVy 34041 -SGE= 34042 -dmFuZ3N0 34043 -IGVtZXJnZQ== 34044 -44CC4oCd 34045 -IENhYmluZXQ= 34046 -LXNxdWFyZQ== 34047 -6YOo 34048 -IHJhZ2U= 34049 -IEFK 34050 -IFZU 34051 -c2hhZG93 34052 -IEZhaXRo 34053 -ZW5hbWVz 34054 -cHJldHR5 34055 -aGFzaWw= 34056 -cGFydHk= 34057 -IHZhcmNoYXI= 34058 -IGZvdG9z 34059 -IGFsdW0= 34060 -IEJlbGdpdW0= 34061 -LnlsYWJlbA== 34062 -IGRlag== 34063 -X251bWJlcnM= 34064 -IGh1 34065 -LnNldEFkYXB0ZXI= 34066 -IFVzdWFsbHk= 34067 -KHNhbXBsZQ== 34068 -LlNoYXJlZA== 34069 -IGJvb2tlZA== 34070 -ID4+PQ== 34071 -IG1pbmVyYWxz 34072 -Ij48Pz0= 34073 -IGFkanVzdG1lbnRz 34074 -IERM 34075 -IHZpYnJhbnQ= 34076 -IERlcGVuZGVuY3k= 34077 -IHphcA== 34078 -L1g= 34079 -IGZvbnRz 34080 -dHJpcA== 34081 -0LjRhw== 34082 -IHR1YmVz 34083 -Y2xhbWF0aW9u 34084 -IOun 34085 -IHByb3RhZ29u 34086 -b3Vwb24= 34087 -IEJydXNo 34088 -KHByZWQ= 34089 -b3VybmV5 34090 -J10pLT4= 34091 -cHJvZw== 34092 -Ym9v 34093 -X21k 34094 -X3BhY2s= 34095 -KGV4cHJlc3M= 34096 -dXR6 34097 -XEF1dGg= 34098 -LGlk 34099 -IENoaWxl 34100 -YWN0aWNl 34101 -IHJlY3J1aXRtZW50 34102 -IHBvc2Vz 34103 -IHZ1bG5lcmFiaWxpdHk= 34104 -aW5zdGFuYw== 34105 -b3J1bQ== 34106 -ZGVzcw== 34107 -IHhs 34108 -JSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSU= 34109 -KGZpZw== 34110 -IGRlbGV0aW5n 34111 -LmRlbA== 34112 -KScpCg== 34113 -IFdlZWtseQ== 34114 -Pz8/ 34115 -KHN0cmNtcA== 34116 -c21pdGg= 34117 -IHB1cnN1aW5n 34118 -LXNv 34119 -IEFwcHM= 34120 -LycK 34121 -IGRlY2lz 34122 -Rk9SRQ== 34123 -RXZlcnlvbmU= 34124 -IGxhbmVz 34125 -VmlydHVhbA== 34126 -LmF0dGFjaA== 34127 -KExvZw== 34128 -IE1lZGljYWlk 34129 -KFBhdGg= 34130 -IFR1cm5lcg== 34131 -L2FwcGxpY2F0aW9u 34132 -IHBvcnRyYWl0 34133 -IG9wcG9zZQ== 34134 -Y2hlY2tvdXQ= 34135 -IGZpbmlzaGVz 34136 -X01F 34137 -QmFycmllcg== 34138 -U29uZw== 34139 -VkFS 34140 -RWFybGllcg== 34141 -cmVsbGE= 34142 -IGhhc3Q= 34143 -YXphcg== 34144 -IHB1bGxz 34145 -bmd4 34146 -IGluc3BpcmluZw== 34147 -0YPRjg== 34148 -LWRpcmVjdGlvbg== 34149 -IGV4cGxvc2l2ZQ== 34150 -IGNyZWF0ZWRBdA== 34151 -c3Rv 34152 -IHdoZWF0 34153 -IEJ1aWx0 34154 -J2Fp 34155 -IHRyYWNrZWQ= 34156 -aGFtbWFk 34157 -Um93QXRJbmRleFBhdGg= 34158 -X2hlYXA= 34159 -RHVl 34160 -IGNvbm5lY3Rz 34161 -LnB1Ymxpc2g= 34162 -ZW11 34163 -IGJ1bGxldHM= 34164 -QkFS 34165 -b2xhdGU= 34166 -IGludGVybmFsbHk= 34167 -IGNhdGNoaW5n 34168 -LXBhc3N3b3Jk 34169 -b3VjaGVk 34170 -5oCn 34171 -ZW91cw== 34172 -IHhyYW5nZQ== 34173 -UXVhbGl0eQ== 34174 -dnY= 34175 -TWFuYWdl 34176 -KCgk 34177 -YWNlbWVudHM= 34178 -IEJyb3RoZXJz 34179 -IEhFQUQ= 34180 -IFVuc3VwcG9ydGVk 34181 -c2Fu 34182 -ZXNp 34183 -KioqCg== 34184 -IGFkYXB0YXRpb24= 34185 -IFdvcmtlcg== 34186 -J10v 34187 -LnNhdmVmaWc= 34188 -KHRyYW5z 34189 -2Kw= 34190 -bmVl 34191 -Q29ycmVjdA== 34192 -Li4uIikK 34193 -IHN1Ym1pdHRpbmc= 34194 -LXBhdGg= 34195 -CWxhc3Q= 34196 -aXNzYW4= 34197 -LnhsYWJlbA== 34198 -IFNlcGFy 34199 -L25v 34200 -X2Jlc3Q= 34201 -IE1pbGxz 34202 -X3NvY2s= 34203 -KGZsYWc= 34204 -IGRlc3RpbmF0aW9ucw== 34205 -ZW1wdGlvbg== 34206 -IEZBSUw= 34207 -5ZKM 34208 -IHJw 34209 -ZmFjdA== 34210 -CWxlbg== 34211 -REFZ 34212 -IHNlaXo= 34213 -X2RzdA== 34214 -bGlw 34215 -LkxpbmVhcg== 34216 -IEJhc2tldA== 34217 -JHQ= 34218 -JGk= 34219 -LWJyYW5k 34220 -IE5laWw= 34221 -IEVx 34222 -IHRob3U= 34223 -b2dlbmU= 34224 -IHNjaG9sYXJzaGlw 34225 -5pu0 34226 -IHN3bw== 34227 -YWdpbmF0b3I= 34228 -ZW5p 34229 -KGJvb2s= 34230 -IGJsaW5r 34231 -dGh1cw== 34232 -IGNhbmNlbGxhdGlvblRva2Vu 34233 -IFBhbGVzdGluaWFucw== 34234 -IHByb2ZpdGFibGU= 34235 -IGJhY2twYWNr 34236 -ZW5zb24= 34237 -PExvbmc= 34238 -IHBvb2xz 34239 -IHN0aWNrcw== 34240 -IHNwb2tlc3dvbWFu 34241 -QmVpbmc= 34242 -IEhlcml0YWdl 34243 -IE5pa2U= 34244 -U0hB 34245 -IE5vdEltcGxlbWVudGVkRXhjZXB0aW9u 34246 -JGNvcmU= 34247 -IFJpY28= 34248 -L2xhdGVzdA== 34249 -IEN6ZWNo 34250 -bmVyUmFkaXVz 34251 -KGxpbmVz 34252 -IHNlbWVzdGVy 34253 -IHdvdW5kcw== 34254 -UHJvY2VkdXJl 34255 -Lm1haWw= 34256 -KCkpOgo= 34257 -IGNvcnJpZA== 34258 -dGVyZWQ= 34259 -IE5DQUE= 34260 -IGdhbGF4eQ== 34261 -X2tpbmQ= 34262 -aWxr 34263 -IHRyYXM= 34264 -X1BPTA== 34265 -IEhldA== 34266 -IHJlZnVnZWU= 34267 -IHRlZW5hZ2U= 34268 -LmJpbmRpbmc= 34269 -cG9zdGFs 34270 -IGnDp2lu 34271 -IERhdGFUeXBl 34272 -6ZY= 34273 -eWNsZXJ2aWV3 34274 -LHZhbHVl 34275 -X2lkZW50aWZpZXI= 34276 -PGI= 34277 -IG91dGZpbGU= 34278 -DQogICAgDQo= 34279 -IGNyw6k= 34280 -IHJlc3BvbmRlbnRz 34281 -IEJlYXN0 34282 -Y2VsZWQ= 34283 -IGludGVyZg== 34284 -LXRoZW1l 34285 -Z2lm 34286 -IFJhbmdlcnM= 34287 -SVRBTA== 34288 -IGF1dGhlbnRpY2F0ZQ== 34289 -Q29tcGxldGlvbg== 34290 -dXJzb3Jz 34291 -IGNpbmVtYQ== 34292 -IGRpc2NvdXI= 34293 -IEphdw== 34294 -T0NLRVQ= 34295 -IHByYXllcnM= 34296 -IEx1aXM= 34297 -ZnJhZw== 34298 -PVsK 34299 -IGJyYXZl 34300 -X3Bvc2U= 34301 -Q2VydGlmaWNhdGU= 34302 -LWZl 34303 -aWZlcmF5 34304 -IEZsYWdz 34305 -Q29udGFpbmVyR2Fw 34306 -IENyaXQ= 34307 -UmVzdWx0U2V0 34308 -CWN1cg== 34309 -IGNvcnJlc3BvbmRz 34310 -U3RhZmY= 34311 -Lkh0dHBTZXJ2bGV0UmVxdWVzdA== 34312 -IG5ldXJvbnM= 34313 -IE1haW5BeGlzQWxpZ25tZW50 34314 -ZWRhcg== 34315 -IGdhZA== 34316 -X3BhcnRz 34317 -IM6y 34318 -IGZ4 34319 -L2ZpbGVz 34320 -IEJyb3M= 34321 -aGlwcw== 34322 -IGdsdWNvc2U= 34323 -IGZhcm1z 34324 -IG1lbnRhbGx5 34325 -cmVzdGF1cmFudA== 34326 -VGFibGVOYW1l 34327 -IE1lcmNlZGVz 34328 -LlZpc3VhbA== 34329 -IGFuY2g= 34330 -aW5hbGc= 34331 -X3J1bnRpbWU= 34332 -IHByb3ByaWV0YXJ5 34333 -IGludGVudGlvbnM= 34334 -aXpp 34335 -U2xpY2U= 34336 -OyI+PC8= 34337 -X1dPUkQ= 34338 -XE1pZ3JhdGlvbnM= 34339 -IEVOQUJMRQ== 34340 -X1BBUkFNRVRFUg== 34341 -IEJpc2hvcA== 34342 -LnN1YmplY3Q= 34343 -aWxsYXM= 34344 -Lm1hdHJpeA== 34345 -dXJyZW5jZXM= 34346 -Knk= 34347 -IGNvc3RseQ== 34348 -IENodWNr 34349 -IGNsb3Nlcw== 34350 -IE1pZ2h0 34351 -LXN0b3Jl 34352 -IG1hbGw= 34353 -aWV0ZW4= 34354 -LkFicw== 34355 -IGNvdXBsZWQ= 34356 -LmJhc2lj 34357 -IDo6Ojo6Ojo6 34358 -TWFrZXI= 34359 -Y2Fubm90 34360 -IGFjaA== 34361 -IEVsaQ== 34362 -4oiS 34363 -b3JuYQ== 34364 -IGNwcw== 34365 -IHRoZXJlb2Y= 34366 -IEB7 34367 -IE5TTXV0YWJsZUFycmF5 34368 -zr0= 34369 -cHJvZHVjdGl2ZQ== 34370 -U3F1YXJl 34371 -dGVtcHRz 34372 -IGVsaW1pbmF0ZWQ= 34373 -PE0= 34374 -IGNvbnNlcnZhdGl2ZXM= 34375 -IFN1cmc= 34376 -LnBhcg== 34377 -IEJ1Y2g= 34378 -KmI= 34379 -Rm9ydA== 34380 -Q29sb3Vy 34381 -IENoaQ== 34382 -ZWRpYw== 34383 -PnRydWU= 34384 -IE5ZQw== 34385 -IGJvcmVk 34386 -IERldGVjdA== 34387 -IGFwcGFy 34388 -IGplYW5z 34389 -IFRhaw== 34390 -SU9E 34391 -IEhvcnNl 34392 -KEZJTEU= 34393 -KD8= 34394 -cmlxdWU= 34395 -b3B0aW1pemVy 34396 -bmF0 34397 -bG95cw== 34398 -CVRva2Vu 34399 -b3VidGVk 34400 -dWVzcw== 34401 -b2NvYQ== 34402 -RGF0YU1lbWJlcg== 34403 -X1BPV0VS 34404 -Y2xhc3NMaXN0 34405 -UHVzaEJ1dHRvbg== 34406 -IFdpRmk= 34407 -LlN0cmVhbQ== 34408 -Lmd1aWxk 34409 -IG5vZw== 34410 -IFBvcnR1Z2Fs 34411 -IFVudGVy 34412 -UHJpbWl0aXZl 34413 -Ym9zcw== 34414 -IERldXRzY2g= 34415 -IGVyb3RpYw== 34416 -IHN0cmNvbnY= 34417 -LlRyeVBhcnNl 34418 -IGdyYW1z 34419 -LlN1Y2Nlc3M= 34420 -X3Br 34421 -IEhhcnZleQ== 34422 -LW1pbmRlZA== 34423 -LmNvdW50cnk= 34424 -W10i 34425 -IGFuZ2Vs 34426 -IGJlYXRz 34427 -IFZvcg== 34428 -aWxpbw== 34429 -Lm1hc3Rlcg== 34430 -c29tZXRoaW5n 34431 -IFBBQ0s= 34432 -KGlm 34433 -UmVxdWVzdEJvZHk= 34434 -IGFudGVz 34435 -L3dpZGdldA== 34436 -IG1vZG8= 34437 -IEFX 34438 -ZmluZGVy 34439 -IG9wdGltaXplZA== 34440 -IG1pc3NpbGVz 34441 -TkI= 34442 -CWludGVybmFs 34443 -dGV4 34444 -IFNyaQ== 34445 -IGRhbWFnaW5n 34446 -IE1haXM= 34447 -LUFsbG93 34448 -IFpo 34449 -LWFsdA== 34450 -ICkpOwoK 34451 -6Ik= 34452 -IGluZmx1ZW5jZXM= 34453 -IGNhdGFs 34454 -X1JFR0lTVEVS 34455 -IEFQSXM= 34456 -LWNlbnR1cnk= 34457 -IGJpb2xvZ3k= 34458 -IEFjdHVhbA== 34459 -IGhlZWxz 34460 -VFJBQ0U= 34461 -X0RJRw== 34462 -RGF0YXNldA== 34463 -IE1hdHRlcg== 34464 -IGNsYXNzaWZpZXI= 34465 -Lndpa2lwZWRpYQ== 34466 -IFJvZ2Vycw== 34467 -IGRvbmF0ZWQ= 34468 -cmF3bGVy 34469 -ZW5lbg== 34470 -IGNhc2lub3M= 34471 -b3J0YWw= 34472 -IHByaXZl 34473 -c3Bl 34474 -ZHVjZXJz 34475 -LmVw 34476 -IGdyYXNw 34477 -YWNqaQ== 34478 -IGRhaXJ5 34479 -IGJ1c2Vz 34480 -LmNvbW0= 34481 -Lmlucw== 34482 -IElSUw== 34483 -IEJlZXI= 34484 -YWRj 34485 -b2FyZA== 34486 -X01FVA== 34487 -ICcrJw== 34488 -cmFucw== 34489 -IGtpbmRh 34490 -IOKUgg== 34491 -IE1hdXI= 34492 -0LDQsw== 34493 -IGJhbmR3aWR0aA== 34494 -aWJ1cw== 34495 -IERpZmZlcmVudA== 34496 -KG1hdA== 34497 -IFJlc3VtZQ== 34498 -X1VOUw== 34499 -ZXN0YWJsaXNo 34500 -IGZvbmN0aW9u 34501 -U3Vic2NyaXB0aW9u 34502 -X2NvbXBhbnk= 34503 -IGxpZ2h0bHk= 34504 -LmNvbmZpcm0= 34505 -LnlhbWw= 34506 -IEJvb3N0 34507 -Q29tbWVyY2U= 34508 -LXRlbXBsYXRl 34509 -X0RFTEFZ 34510 -IEhJ 34511 -IG5hdmln 34512 -KFNlbmRlcg== 34513 -IEhT 34514 -XyIr 34515 -IFJFUVVFU1Q= 34516 -IHdpZmk= 34517 -PSIiCg== 34518 -XSktPg== 34519 -IHJvcGU= 34520 -IHZpb2xhdGVk 34521 -IGdsYW5jZQ== 34522 -IEt1cmQ= 34523 -IOiu 34524 -ZGVjaw== 34525 -IElTQk4= 34526 -IGluZmVjdA== 34527 -IEZvbw== 34528 -IGdldHRlcg== 34529 -IHRlbmVy 34530 -YXBwZQ== 34531 -Lmho 34532 -X2hvdA== 34533 -PEFN 34534 -cG9seQ== 34535 -ISIsCg== 34536 -IGNvbnZlcnRpbmc= 34537 -IFdXRQ== 34538 -Uk9T 34539 -KCd7 34540 -Q29tbWl0 34541 -KUw= 34542 -IE9yZQ== 34543 -IHNwYXJzZQ== 34544 -IGRpc3Bvc2Fs 34545 -IGNhbmNlbGVk 34546 -5ZCO 34547 -IGFlcg== 34548 -IHZpbnls 34549 -4buD 34550 -cmVjb2du 34551 -YXJraW5n 34552 -IHRyaWNreQ== 34553 -KnM= 34554 -IHByb2NlZWRz 34555 -IGlzbw== 34556 -IGNvY29udXQ= 34557 -IGNyYWZ0ZWQ= 34558 -SUVMRFM= 34559 -IHF1ZXN0bw== 34560 -IGNvbW11bg== 34561 -X0NPTk5FQ1Q= 34562 -IHRyYWZmaWNraW5n 34563 -RGVlcA== 34564 -YcOnw7Vlcw== 34565 -Y29kaWdv 34566 -dmVhdQ== 34567 -IGJldHJheQ== 34568 -aW50YQ== 34569 -VEVE 34570 -w6Zy 34571 -bWFydA== 34572 -X0JVUw== 34573 -L3Nj 34574 -aWFsbHk= 34575 -IGNpZ2FyZXR0ZXM= 34576 -6K+B 34577 -KG5u 34578 -IG1vZGVsaW5n 34579 -L3Byb2R1Y3Rz 34580 -d2Fybg== 34581 -IG1ldHJv 34582 -IEl2 34583 -Jik= 34584 -IENhYmxl 34585 -zrs= 34586 -Q29tcGFyaXNvbg== 34587 -Z2FyeQ== 34588 -IEJB 34589 -UEFSVA== 34590 -IHB2 34591 -X3VwZGF0ZWQ= 34592 -Q3JlZGl0 34593 -b3J0aHk= 34594 -b2JzZXJ2YWJsZQ== 34595 -IHRoZWF0cmU= 34596 -QkxF 34597 -O30KCg== 34598 -bGF1bmNo 34599 -X3N0cmluZ3M= 34600 -dWdv 34601 -IFJQRw== 34602 -LWF1dGg= 34603 -0KA= 34604 -aG9sbQ== 34605 -IFBhbmQ= 34606 -VWlk 34607 -IGltcGx5 34608 -7Jy8 34609 -J109Jw== 34610 -L1VzZXI= 34611 -IHN0cmNhdA== 34612 -0L3Ri9C5 34613 -RGF0YUFkYXB0ZXI= 34614 -IGxhbmRzYw== 34615 -IGRpcGxvbWF0aWM= 34616 -77yT 34617 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 34618 -IENoaWNrZW4= 34619 -IGJjcnlwdA== 34620 -LkluZg== 34621 -W2NvbA== 34622 -IFF1YW50aXR5 34623 -LXBvc2l0aW9u 34624 -IGRpZXRhcnk= 34625 -IGZpbG1t 34626 -SXNyYWVs 34627 -UHJldg== 34628 -IE1pbGxpb24= 34629 -IHJlbWVk 34630 -IGJpbGxpbmc= 34631 -IG91dGRvb3Jz 34632 -LnRt 34633 -IG5hZA== 34634 -Rm9yZw== 34635 -Wlo= 34636 -IHNzbA== 34637 -XSwn 34638 -S1Q= 34639 -ZnJlcQ== 34640 -PWRvY3VtZW50 34641 -Ymx1cg== 34642 -rLg= 34643 -IEplZmZlcnNvbg== 34644 -Q3M= 34645 -KHNhdmU= 34646 -IHN0cmFw 34647 -SW5kaWE= 34648 -IGlkZW9sb2d5 34649 -Qk9TRQ== 34650 -IEZQ 34651 -KGFucw== 34652 -IGZldmVy 34653 -IFlhbQ== 34654 -S2luZw== 34655 -4LI= 34656 -QVRJTkc= 34657 -Ym9oeWRy 34658 -cm9sbGJhY2s= 34659 -IG5ld05vZGU= 34660 -IE5WSURJQQ== 34661 -IGhvbm91cg== 34662 -IENvbmZpcm0= 34663 -eGJk 34664 -IHN1Y2Nlc3Nvcg== 34665 -L3U= 34666 -bGl2 34667 -b3VybmFtZW50cw== 34668 -QXR0YWNobWVudA== 34669 -IGdydXA= 34670 -IHRyaWJl 34671 -IGNhcmVz 34672 -ZWZ0 34673 -X3NhbWU= 34674 -J2xhYmVs 34675 -IOOAkA== 34676 -TW90b3I= 34677 -IGluZXhw 34678 -ICIoIg== 34679 -X1BPU0lUSU9O 34680 -IHZhbGxleQ== 34681 -IFJlc3VsdFNldA== 34682 -IHByZXNlcnZlZA== 34683 -IG11dGF0aW9ucw== 34684 -IHF1ZXN0aW9uaW5n 34685 -bXVuaXRpb24= 34686 -cGFyc2VJbnQ= 34687 -IFNy 34688 -IE1ldGFkYXRh 34689 -4oCd77yM 34690 -dGltZXN0YW1wcw== 34691 -IHRyYW5zaXRpb25z 34692 -7Zk= 34693 -0Yo= 34694 -aW9t 34695 -LkRv 34696 -IHBpbmU= 34697 -IGZ1bmc= 34698 -IHRyYW5zbWl0dGVk 34699 -Y3RpbWU= 34700 -IEZhbQ== 34701 -UmV2aXNpb24= 34702 -QmFz 34703 -VVBFUg== 34704 -RGVzdGluYXRpb24= 34705 -dG9IYXZlQmVlbkNhbGxlZA== 34706 -IHVuZm9ydHVuYXRl 34707 -SU5FUw== 34708 -X3Byb2Y= 34709 -QW1vbmc= 34710 -IEN5YmVy 34711 -IEJhdHRlcnk= 34712 -Z2VucmU= 34713 -IFZpZXdNb2RlbA== 34714 -LT0= 34715 -IHV0aWxpemVk 34716 -cGFpbnQ= 34717 -LkludGVnZXJGaWVsZA== 34718 -ZXJuaXR5 34719 -Y29tcGlsZXI= 34720 -4oCLCgo= 34721 -IE1hc3RlcnM= 34722 -LlRvQXJyYXk= 34723 -IHN0cnRvbA== 34724 -IFVrcmFpbmlhbg== 34725 -fSkpOwo= 34726 -IHNoZW1hbGU= 34727 -IlRoYXQ= 34728 -Zm9yYWxs 34729 -L2Rvd25sb2Fk 34730 -IHJoZXRvcmlj 34731 -LmxhdGl0dWRl 34732 -IFdIRU4= 34733 -IHNob2NraW5n 34734 -SUZJQw== 34735 -Lk5vcm1hbA== 34736 -X0ZPTERFUg== 34737 -IGRyaWZ0 34738 -IG1vdW50aW5n 34739 -LWJvb2s= 34740 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK 34741 -IFdpcmVsZXNz 34742 -PiIuJA== 34743 -IHJlbGllcw== 34744 -KENvbnNvbGU= 34745 -SW50ZXJuYXRpb25hbA== 34746 -LT57JA== 34747 -TWlk 34748 -IGRpc3NlcnQ= 34749 -ZGRz 34750 -IGRlcG9zaXRz 34751 -CWRyaXZlcg== 34752 -I2dh 34753 -cHJpc2luZw== 34754 -cHJpbnRsbg== 34755 -IHByZXNlbnRlcg== 34756 -IG1pbmVz 34757 -Q1NT 34758 -IER1YWw= 34759 -KCEo 34760 -IGthbQ== 34761 -IGlzTG9hZGluZw== 34762 -IFByb3RlY3Q= 34763 -LnVwcGVy 34764 -YXJpdW0= 34765 -XToKCgo= 34766 -WWlp 34767 -LXNoaXJ0 34768 -IElNQUdF 34769 -X2NvbG9ycw== 34770 -IHVyZ2VudA== 34771 -LkNvbnRhaW5lcg== 34772 -ISgK 34773 -U2F0dXJkYXk= 34774 -IHNvY2lldGllcw== 34775 -IFRoYW4= 34776 -IENvZA== 34777 -PUA= 34778 -IGF0dGFjaG1lbnRz 34779 -Lm1vYmlsZQ== 34780 -IHNwaXRl 34781 -IGJvdW5jZQ== 34782 -cmF3bA== 34783 -aW5zdGFuY2V0eXBl 34784 -IFRydWNr 34785 -IG1hbmlwdWxhdGlvbg== 34786 -KENvbmZpZw== 34787 -LWluc3Q= 34788 -IHN0b3I= 34789 -aXR1dGlvbg== 34790 -UHJlZmVycmVkR2Fw 34791 -IG1haW5BeGlzQWxpZ25tZW50 34792 -IGxpc3RlbmVk 34793 -JycnCgo= 34794 -b3R0YWdl 34795 -LXByb2plY3Q= 34796 -LkFQUExJQ0FUSU9O 34797 -CXJvb3Q= 34798 -IHdoaXQ= 34799 -IGJpbGRlcg== 34800 -IGtlcg== 34801 -IGFwcGxpYW5jZXM= 34802 -cm93YXZl 34803 -7J2A 34804 -ZW1hdGljcw== 34805 -IE9yZw== 34806 -b3Bpbmc= 34807 -X1NFQVJDSA== 34808 -IGNoYW0= 34809 -YWRkQ29udGFpbmVyR2Fw 34810 -ICgpLg== 34811 -IEFycm93 34812 -SWxsZWdhbA== 34813 -Q3VycmVudGx5 34814 -IHVzYQ== 34815 -IHBhc3N3b3Jkcw== 34816 -IHJlbm93bg== 34817 -YXZlcm4= 34818 -IEV2aWw= 34819 -IGNvbmNhdA== 34820 -IGR1bw== 34821 -IHZhbGU= 34822 -IEJlYW4= 34823 -IGluZGljYXRvcnM= 34824 -Y21hdGg= 34825 -IFB1bXA= 34826 -Tm92ZW1iZXI= 34827 -aWZpY2FudA== 34828 -X0RPTUFJTg== 34829 -cmVnYXI= 34830 -IFBvcnRhbA== 34831 -IiQ= 34832 -IGZvcm1lcmx5 34833 -Il06Cg== 34834 -IFZpc2liaWxpdHk= 34835 -LmdldEVsZW1lbnRzQnlDbGFzc05hbWU= 34836 -X1JFRA== 34837 -IGNoYW1waW9ucw== 34838 -4LQ= 34839 -VmFsb3I= 34840 -X2Vz 34841 -KmE= 34842 -LXJlcGVhdA== 34843 -QmFuZA== 34844 -LnN0YWdl 34845 -IGJ1cmVhdWM= 34846 -Q250 34847 -ZXRlbg== 34848 -LWZ1bmN0aW9u 34849 -IG11aXRv 34850 -UElE 34851 -X2VkaXRvcg== 34852 -IGNyYXNoZWQ= 34853 -ZGVhZA== 34854 -a2F0 34855 -YWdo 34856 -IEVYVA== 34857 -YXNzZXI= 34858 -LXNtYWxs 34859 -IHJlYWxpeg== 34860 -KEVudGl0eQ== 34861 -w7pz 34862 -IEFjdHVhbGx5 34863 -IEVsaXRl 34864 -IGhlbG0= 34865 -KG5vbmF0b21pYw== 34866 -YXNoZXI= 34867 -Q29tbXVuaXR5 34868 -YWxsZW5n 34869 -aXJ5 34870 -IEdyb3d0aA== 34871 -IHN1ZQ== 34872 -IGZyZXF1ZW5jaWVz 34873 -X2Rlc2NyaXB0b3I= 34874 -LkF0dHJpYnV0ZQ== 34875 -IHJlY2lwaWVudHM= 34876 -X05T 34877 -LyIr 34878 -aWJhbg== 34879 -IGF0aGxldGU= 34880 -IElnbg== 34881 -X0RNQQ== 34882 -KGRz 34883 -IFJlcXVpcmVtZW50cw== 34884 -QURJ 34885 -ZXJleg== 34886 -XEFkbWlu 34887 -YnJhc2th 34888 -IFJ1c3Q= 34889 -UmVsYXRpb24= 34890 -Q09E 34891 -IFZFUlNJT04= 34892 -ZW1tYQ== 34893 -KSl7 34894 -LkR1cmF0aW9u 34895 -IENhbWI= 34896 -LWxvZ28= 34897 -IHJlYWRhYmxl 34898 -IGNyZWF0b3Jz 34899 -KCldOwo= 34900 -VXBEb3du 34901 -LWhhbGY= 34902 -LmdldE1vbnRo 34903 -KHNm 34904 -UGlj 34905 -IGh1bmdlcg== 34906 -LnR4 34907 -IGV4Y2VlZGVk 34908 -X3NlZWQ= 34909 -KF4= 34910 -X3Nr 34911 -LnBlcmZvcm0= 34912 -ID46Og== 34913 -IG1vbmdv 34914 -PWZsb2F0 34915 -YmluZFBhcmFt 34916 -U21hcnQ= 34917 -aWZh 34918 -IHNlY3VyaXRpZXM= 34919 -IHByZWp1ZA== 34920 -ICwi 34921 -IGNvcnBz 34922 -IHZyYQ== 34923 -YW1hY2FyZQ== 34924 -aXRlcnI= 34925 -KE1lZGlh 34926 -dWNoZQ== 34927 -IGNvYg== 34928 -IGxpYmVy 34929 -Lmdlb21ldHJ5 34930 -TG9jYXRvcg== 34931 -IHNsaWRpbmc= 34932 -IHN1cmdpY2Fs 34933 -X0NVUg== 34934 -IGNvbnNlY3Q= 34935 -Wyo= 34936 -IFJlc29ydA== 34937 -U3R1Yg== 34938 -X0RPVUJMRQ== 34939 -IFNvcGg= 34940 -IGVsZWN0b3JhbA== 34941 -X2Rpc2FibGU= 34942 -INGB0L4= 34943 -IExpZ2h0bmluZw== 34944 -IG1lbnRpb25z 34945 -b2N5 34946 -IGxlYWtlZA== 34947 -IHJlbGF4aW5n 34948 -UHJlc2VudGVy 34949 -dnNw 34950 -IGd1aWx0 34951 -PS09LQ== 34952 -LnJlcGx5 34953 -IE1pcnJvcg== 34954 -Q2FtcA== 34955 -ICsjKyMrIys= 34956 -ICsjKyMrIysjKyMr 34957 -LkF1dGhvcg== 34958 -IGRpcmVjdGl2ZQ== 34959 -LWhvb2s= 34960 -7YSw 34961 -fQoKCgoK 34962 -QHB5dGVzdA== 34963 -X3JhbmQ= 34964 -bWlz 34965 -IGNvbG9yZnVs 34966 -dWpl 34967 -bGFzc2Vz 34968 -IENsYXNzZXM= 34969 -LmhhdmU= 34970 -JSks 34971 -6aKY 34972 -IGRpc3R1cmJpbmc= 34973 -c3Vic3RyaW5n 34974 -IEtvaA== 34975 -SW52ZXN0 34976 -cHVyY2hhc2U= 34977 -IHJlY3ljbGluZw== 34978 -IEFSVA== 34979 -aWVyYXJjaHk= 34980 -IGZwcw== 34981 -LmNoZWNrQm94 34982 -7ZW0 34983 -X21hdGVyaWFs 34984 -ZHVjYXRpb24= 34985 -IGZ3 34986 -dWRpdA== 34987 -IHJldmlld2luZw== 34988 -IFNpZA== 34989 -U3ludGF4 34990 -IFdyaXR0ZW4= 34991 -YXJnYXI= 34992 -VU1F 34993 -L3E= 34994 -Q2xhc3NpZmllcg== 34995 -T2ZmaWNpYWw= 34996 -IGpheno= 34997 -IG9tZWdh 34998 -UGh5c2ljcw== 34999 -IGx1Z2Fy 35000 -X2FjY2Vzc29y 35001 -LmNvbW1hbmRz 35002 -QWJpbGl0eQ== 35003 -IEJhdGNo 35004 -UkFN 35005 -IGVuY291bnRlcnM= 35006 -LlF1 35007 -QllURQ== 35008 -IERpc3RyaWJ1dGlvbg== 35009 -IHVzbw== 35010 -IFJlY292ZXJ5 35011 -YXBwcm92ZWQ= 35012 -IGRlbmlhbA== 35013 -L3NoYXJl 35014 -TGlua2VkTGlzdA== 35015 -KQ0KDQoNCg== 35016 -dWRkeQ== 35017 -IGZpbmVz 35018 -IHJ5 35019 -VW5pY29kZQ== 35020 -CXJlbmRlcg== 35021 -IHByZW1pc2Vz 35022 -IHBvbg== 35023 -YWxpYXNlcw== 35024 -L0ZvdW5kYXRpb24= 35025 -Y3VkYQ== 35026 -IENvY2s= 35027 -LDop 35028 -KGZvbGRlcg== 35029 -IG3DqWQ= 35030 -ZHJhZw== 35031 -IHRhbGVudHM= 35032 -ICAgCgo= 35033 -0LXRgdGC0LI= 35034 -bW9i 35035 -LnltbA== 35036 -IGFzdGVy 35037 -IGRpc2NyZQ== 35038 -Z29hbA== 35039 -IEdUWA== 35040 -IFNVQ0NFU1M= 35041 -IExPTkc= 35042 -KGZpbmQ= 35043 -IHNpbmd1bGFy 35044 -X3N6 35045 -IEV0aGVyZXVt 35046 -Li4K 35047 -IGlycmVz 35048 -Jykpewo= 35049 -IG1pbmlzdGVycw== 35050 -U3RlcHM= 35051 -aXZlcnNhbA== 35052 -IE5ldmVydGhlbGVzcw== 35053 -LWxlZA== 35054 -ICglKQ== 35055 -56Gu 35056 -IHRpbWV6b25l 35057 -IHN0cmFuZ2Vy 35058 -KHJlbmRlcg== 35059 -IHNodXRpbA== 35060 -IG1waA== 35061 -IHRyaW8= 35062 -cHB5 35063 -IHByZWRvbWlu 35064 -IGVuZG9ycw== 35065 -IFJ1c3NpYW5z 35066 -CXJvdw== 35067 -IHdpemFyZA== 35068 -LnNlcmlhbGl6ZQ== 35069 -IGNvbXBsYWluZWQ= 35070 -IHNpZG8= 35071 -IGRlbGlnaHRlZA== 35072 -LW1l 35073 -IFJhdg== 35074 -SHVtYW4= 35075 -YWRheXM= 35076 -cmVjdg== 35077 -V29ya2luZw== 35078 -SnVtcA== 35079 -IMOlcg== 35080 -IEF1dG9tYXRpYw== 35081 -X0Jhc2U= 35082 -5qC8 35083 -YXVyYW50cw== 35084 -wq8= 35085 -5rg= 35086 -KENUeXBl 35087 -SUZJ 35088 -KGFtb3VudA== 35089 -IGJlbGlldmluZw== 35090 -PW15c3Fs 35091 -IGZpcg== 35092 -IHJlc3RvcmF0aW9u 35093 -ZXJlY28= 35094 -0KI= 35095 -Xycr 35096 -IGVib29r 35097 -IGRlYnJpcw== 35098 -KGlucHV0cw== 35099 -QVlPVVQ= 35100 -IHNjcmVhbWluZw== 35101 -YXZpYQ== 35102 -bGFuZGVy 35103 -IGRpc3RyZXNz 35104 -IGFzc2VtYmxlZA== 35105 -IEF2b2lk 35106 -KHRocmVhZA== 35107 -IFJQQw== 35108 -X0VYSVQ= 35109 -KHF1ZXVl 35110 -0LjRgdGC 35111 -RGxs 35112 -IHNrdWxs 35113 -X3B1Yg== 35114 -Y2hleg== 35115 -bWluYXRl 35116 -ZW5zZW4= 35117 -IGluc2FuZQ== 35118 -Ym91bmRz 35119 -IFJvc2Vu 35120 -IGNvbmRpdGlvbmluZw== 35121 -cHJvY2Vzc2Vk 35122 -dmlkZW9z 35123 -Zm91cg== 35124 -LkNvbnY= 35125 -fDsK 35126 -UGVyc29uYWw= 35127 -Y2VycHQ= 35128 -OlVJQ29udHJvbFN0YXRlTm9ybWFs 35129 -IGRvc2Vz 35130 -IEthcmw= 35131 -IEZyZXF1 35132 -LkJBU0U= 35133 -IFZvdGU= 35134 -IGNvbmN1cnJlbnQ= 35135 -IE1lc3NhZ2VCb3hJY29u 35136 -IMOW 35137 -IER1YmFp 35138 -IFJldGFpbA== 35139 -Om51bWJlcg== 35140 -IE9ic2VydmVy 35141 -IEJpZ0ludGVnZXI= 35142 -X29yaWdpbg== 35143 -X1dPUks= 35144 -RnJhbWVz 35145 -IG5vdGFibHk= 35146 -LuKAnA== 35147 -IHRyb3BpY2Fs 35148 -IG5pY2hl 35149 -YW1pbmE= 35150 -LnN5cw== 35151 -KHRva2Vucw== 35152 -bW9kaWZ5 35153 -b3NpdA== 35154 -c3Ryb20= 35155 -IENvbWljcw== 35156 -T1BUSU9O 35157 -VGlja2V0 35158 -IGZhY3Rvcmllcw== 35159 -IGRpc3B1dA== 35160 -X0ZpbGU= 35161 -IEZpbm4= 35162 -ZWVl 35163 -IERpc2NvcmQ= 35164 -X21vbmV5 35165 -LnRwbA== 35166 -X3NhZmU= 35167 -TEI= 35168 -IGdsdXQ= 35169 -Sks= 35170 -LmZsb3c= 35171 -LWNvbnQ= 35172 -Z29z 35173 -IGhvcml6b24= 35174 -IFJ1c2g= 35175 -Ojoq 35176 -UGlwZQ== 35177 -dWxsYQ== 35178 -Ym9yb3VnaA== 35179 -aGVpbWVy 35180 -KG1vdmU= 35181 -KFRleHQ= 35182 -fSk7DQoNCg== 35183 -d2VsY29tZQ== 35184 -IENvbXBvbmVudHM= 35185 -IGdvdmVybmFuY2U= 35186 -Y2xvc2Vk 35187 -CW1hcmdpbg== 35188 -IGxhdW5kcnk= 35189 -IFRlcm1pbmFs 35190 -aXphcmRz 35191 -LuKAlA== 35192 -LnJlbW90ZQ== 35193 -LnJhZGl1cw== 35194 -IFF1ZWJlYw== 35195 -IGRo 35196 -VGVjaA== 35197 -IE1pc3Q= 35198 -c2VsbGVy 35199 -X2xpdGVyYWw= 35200 -IGdlbml1cw== 35201 -IGJyYWlucw== 35202 -Z2Vt 35203 -IE1lYXN1cmU= 35204 -IGNhdGFzdA== 35205 -cmFuY2U= 35206 -LlRleHRGaWVsZA== 35207 -IGNvbnN1bWluZw== 35208 -ICdcJyc= 35209 -b3VidGVkbHk= 35210 -IENlcnRhaW4= 35211 -RXY= 35212 -ZXJ0aQ== 35213 -YmVpbmc= 35214 -RXhwZXJpZW5jZQ== 35215 -IC8vWw== 35216 -IEFyYWJpYw== 35217 -IENyaXN0 35218 -IEF6dXJl 35219 -IGhvcmE= 35220 -bGFkZXNo 35221 -XEJsdWVwcmludA== 35222 -ZGFy 35223 -LnJlbA== 35224 -IHN1cHJlbQ== 35225 -IFJlYWdhbg== 35226 -IEF0dHJpYnV0ZXM= 35227 -LXNpZGViYXI= 35228 -IHVzZVN0eWxlcw== 35229 -IEFpcmxpbmVz 35230 -IGhpbGxz 35231 -L3hodG1s 35232 -dmluYw== 35233 -X21vY2s= 35234 -CiAgICAgICAgICAgICAgICAK 35235 -IFBpbGw= 35236 -LkxheW91dFN0eWxl 35237 -IENvbW1hbmRlcg== 35238 -XTw= 35239 -c2lnbmF0dXJl 35240 -IHt9DQo= 35241 -IGhhdHJlZA== 35242 -IOuL 35243 -b2xlc3Rlcm9s 35244 -ICoqKioqKioq 35245 -YW5jZWxsb3I= 35246 -Y3JvcA== 35247 -VElN 35248 -CQkKCg== 35249 -eXNxbGk= 35250 -dWl0aXZl 35251 -CXVuc2V0 35252 -X3NlbA== 35253 -IG1lbnVz 35254 -dGljaw== 35255 -IGNvbnN0aXR1dGU= 35256 -IEVsZW1lbnRz 35257 -IFJlZGlz 35258 -YWdnaW8= 35259 -X2Zw 35260 -X2RlcGVuZA== 35261 -ZW1hcw== 35262 -Q0FTVA== 35263 -b3Jhbmdl 35264 -am9u 35265 -IEVtaWx5 35266 -IHBvdGF0b2Vz 35267 -IHJlY2VwdG9y 35268 -IEVsZWN0cm9uaWM= 35269 -IExpZ2h0cw== 35270 -IGNvbWJpbmluZw== 35271 -IFNvbWVvbmU= 35272 -ICMjIyMjIyMjLg== 35273 -IFRPRA== 35274 -L3Nob3c= 35275 -WGQ= 35276 -LiIn 35277 -YWZ4 35278 -IHRyYWdpYw== 35279 -U3R5bGVk 35280 -IE1hcmNv 35281 -R2FsbGVyeQ== 35282 -ZGFsZQ== 35283 -LuKAnQoKCgo= 35284 -w6lyaWU= 35285 -L3NlcnZpY2U= 35286 -5LqG 35287 -IGFtYmllbnQ= 35288 -X1NFVFRJTkdT 35289 -LkFkYXB0ZXI= 35290 -bGVuZQ== 35291 -IHRyYXZlbHM= 35292 -Tm90aWNl 35293 -IGNsZWFucw== 35294 -IEZlbQ== 35295 -Y2hhaXI= 35296 -0YPQvQ== 35297 -L215 35298 -X2JhZA== 35299 -IEVjb25vbWljcw== 35300 -SVNB 35301 -X0NOVA== 35302 -KE1lbnU= 35303 -5LqO 35304 -IFJpZGdl 35305 -IGxlbmd0aHk= 35306 -RG90 35307 -IGp1bXBz 35308 -IGhleQ== 35309 -JHBkZg== 35310 -IHdvcm0= 35311 -IHN1dA== 35312 -IHNoZXI= 35313 -aWFtbw== 35314 -IENhbGM= 35315 -dHJpZXZl 35316 -IGNvcHM= 35317 -IENocm9t 35318 -IHJlZ3VsYXRlZA== 35319 -cmVhdG1lbnQ= 35320 -IEhpZ2hlcg== 35321 -b2tz 35322 -IGRlemU= 35323 -TE9DQVRJT04= 35324 -b25nc1Rv 35325 -IGZpbml0ZQ== 35326 -IHZhcmllcw== 35327 -IHBvc2l0aW9uZWQ= 35328 -J2ls 35329 -6YeR 35330 -IGhpa2U= 35331 -KGRvbmU= 35332 -cGxheWxpc3Q= 35333 -IGFkYQ== 35334 -IGNvYXN0YWw= 35335 -IE5hbmN5 35336 -LkRhdGVUaW1lRmllbGQ= 35337 -Q3BwQ29kZUdlbg== 35338 -IFNpbWlsYXJseQ== 35339 -cmV1cg== 35340 -IENvbnRy 35341 -IEhpZGRlbg== 35342 -IEJldGE= 35343 -YXRjaGVk 35344 -X2luc3RhbGw= 35345 -Lk91dHB1dA== 35346 -TG9va3Vw 35347 -IFJpY2htb25k 35348 -cXVhcmVk 35349 -IG1hbmdh 35350 -LWNvbnRyb2xz 35351 -IEJlcm5hcmQ= 35352 -TGFyZ2U= 35353 -IHNsaWNlcw== 35354 -IG9mZmVuY2U= 35355 -IE1lZ2E= 35356 -IGVzdGFy 35357 -IGpvaW50cw== 35358 -IHN1bW0= 35359 -X3BsYXRmb3Jt 35360 -QnVmZg== 35361 -LmFkZFN1YnZpZXc= 35362 -IHJldGFpbmVk 35363 -TGV0dGVy 35364 -LmRpbQ== 35365 -IGVzc2VyZQ== 35366 -IFNjYWZmb2xk 35367 -RVhQRUNU 35368 -CVJF 35369 -LmxvbmdpdHVkZQ== 35370 -w7xuZA== 35371 -IHN0YXR1ZQ== 35372 -LmFkZFdpZGdldA== 35373 -IENhcmliYmVhbg== 35374 -YWRkUHJlZmVycmVkR2Fw 35375 -aWxkZQ== 35376 -VUlMYWJlbA== 35377 -IE9wcG9ydA== 35378 -IGltcGVyaWFs 35379 -dXJzaW9u 35380 -IG1hbmRhdGU= 35381 -IHByb21vdGlvbmFs 35382 -IHZr 35383 -aWHFgg== 35384 -IHB5bA== 35385 -IENyZWF0aW9u 35386 -0L7Qt9C0 35387 -IHNpbXBsZXI= 35388 -LndoYXQ= 35389 -IFJlY2VudA== 35390 -U3Rvcm0= 35391 -LnF1YW50aXR5 35392 -IExvdg== 35393 -Ii0= 35394 -dWJibGVz 35395 -X25vdGlmaWNhdGlvbg== 35396 -KHdvcmxk 35397 -dXJnZXI= 35398 -Kigt 35399 -OiIK 35400 -aG0= 35401 -YW5zaGlw 35402 -IEFsbW9zdA== 35403 -IG1vdG9yY3ljbGU= 35404 -X2ZlZQ== 35405 -IGFic29yYg== 35406 -IFZpbmNlbnQ= 35407 -IHNvdW5kZWQ= 35408 -w61zdA== 35409 -IHBoYXJtYWNldXRpY2Fs 35410 -aHRhZw== 35411 -IEtpbmRsZQ== 35412 -aXRhbGl6ZQ== 35413 -IEVtcGVyb3I= 35414 -b3VzdGlj 35415 -IHNwZWNpYWxpc3Rz 35416 -5YWs 35417 -Qm9yZGVyU3R5bGU= 35418 -L1w= 35419 -UkVMQVRFRA== 35420 -KCcsJyw= 35421 -KGV4cHI= 35422 -IGh0 35423 -5Y2I 35424 -X0NyZWF0ZQ== 35425 -IHNwZWNpYWxseQ== 35426 -IFtdOw0K 35427 -IGhlZWw= 35428 -IHNlcHQ= 35429 -X2FyY2g= 35430 -KGluaXRpYWw= 35431 -JS4KCg== 35432 -XCIsXCI= 35433 -IGRpc2N1c3Nlcw== 35434 -IHVwdA== 35435 -IFsm 35436 -IG1hbnVz 35437 -LmhhbmQ= 35438 -IE1BSU4= 35439 -IERlbm1hcms= 35440 -IF0sDQo= 35441 -IGNyeXN0 35442 -IG5hY2s= 35443 -Q29vcmRz 35444 -X2lubmVy 35445 -IG1pZHN0 35446 -IGF3YWtl 35447 -INCe 35448 -LWJyZWFr 35449 -w612ZWw= 35450 -X1BBU1M= 35451 -IFBhcmFtcw== 35452 -IGRldHI= 35453 -IHNwaWRlcg== 35454 -IENvbmNlcHQ= 35455 -IHByZW5k 35456 -Q0hFRA== 35457 -LkV4aXQ= 35458 -IHBvcHVsYXRlZA== 35459 -IHZpcnR1ZQ== 35460 -X1NFU1NJT04= 35461 -IG5vdXZlbA== 35462 -b2F1dGg= 35463 -INC00LDQvdC90Ys= 35464 -cmluaw== 35465 -LkhlYWRlclRleHQ= 35466 -YXR1cmF0ZWQ= 35467 -IGVyc3Q= 35468 -IOWF 35469 -4KWH 35470 -X3Zpc2libGU= 35471 -ZXllcg== 35472 -IGxpYWJsZQ== 35473 -IGRlYmU= 35474 -IGJ3 35475 -ey0j 35476 -X1dJTg== 35477 -ZGZz 35478 -SG92ZXI= 35479 -IFBVVA== 35480 -LWFuZ2xl 35481 -IG5vYmxl 35482 -IHRyYWNlcw== 35483 -ZW5jdg== 35484 -IHVzZXJEYXRh 35485 -X2lucw== 35486 -IFN1eg== 35487 -IG5ld3NsZXR0ZXJz 35488 -IE1vZGk= 35489 -IGVudHJlcHJlbmV1cnM= 35490 -IHRyaWJ1dGU= 35491 -IHJ1bW9ycw== 35492 -IHJy 35493 -IFF1YXJ0ZXI= 35494 -6rOg 35495 -IGZlZWRz 35496 -w7Nn 35497 -IGVudmVsb3Bl 35498 -IGxlYXI= 35499 -IGvDuA== 35500 -ZGV2ZWxvcGVy 35501 -U2ltaWxhcg== 35502 -OiIpCg== 35503 -c3Vic2NyaXB0aW9u 35504 -TW9kaWZpZXI= 35505 -aXRhbGlj 35506 -IG5hc3R5 35507 -IHRlcm1pbmF0aW9u 35508 -IGNoYXJtaW5n 35509 -IOKf 35510 -dG9ucw== 35511 -LnRyYWNl 35512 -aG90cw== 35513 -IFVS 35514 -TW9udA== 35515 -IGp1c3RpZmllZA== 35516 -IEdhbmc= 35517 -aW5lYQ== 35518 -IGJvZw== 35519 -KGFw 35520 -XyQ= 35521 -IGNvbnRhbWlu 35522 -LkRvdA== 35523 -CURlYnVn 35524 -KGV4cG9ydHM= 35525 -IHBhaXJlZA== 35526 -IEFzc2lnbm1lbnQ= 35527 -IGF1dG9tb2JpbGU= 35528 -k40= 35529 -IHBoYXNlcw== 35530 -dnc= 35531 -QFN1cHByZXNzV2FybmluZ3M= 35532 -PVw= 35533 -cmFudA== 35534 -LWVk 35535 -CWF3YWl0 35536 -IGNlcnRpZmljYXRlcw== 35537 -Jz4i 35538 -IGludGFjdA== 35539 -Q1RSTA== 35540 -TWlrZQ== 35541 -Z3JlZ2F0aW9u 35542 -QVRURVJO 35543 -IHJlcHVibGlj 35544 -X3VwcGVy 35545 -aWxpYXJ5 35546 -IGNvbXB1dGF0aW9u 35547 -aGlyZQ== 35548 -IFNoaW4= 35549 -X0FOWQ== 35550 -IE1hbnVmYWN0dXJlcg== 35551 -IENhcm0= 35552 -IGJlYXJpbmdz 35553 -X2NvbWI= 35554 -Y2Fk 35555 -dXJpc3RpYw== 35556 -IHdob2xlc2FsZQ== 35557 -IGRvbm9y 35558 -LmludGVyZmFjZXM= 35559 -cHJlc3Nv 35560 -IEJydW4= 35561 -LWNsb3Nl 35562 -cHJvdmU= 35563 -X1NL 35564 -CWZyYW1l 35565 -ZXRyb3M= 35566 -IFBhaW4= 35567 -X0VYUA== 35568 -IExU 35569 -X2Zz 35570 -LmRhdGFz 35571 -CXNz 35572 -dm9pcg== 35573 -IEF4aXM= 35574 -TWFqb3I= 35575 -PSI8 35576 -W2g= 35577 -IHByb2Zlc3M= 35578 -aWdyYXRl 35579 -KHNjb3Jl 35580 -S2V5d29yZA== 35581 -Im9z 35582 -ICAgIAkK 35583 -YW5hbHlzaXM= 35584 -IHJlcGxheQ== 35585 -LnBhc3M= 35586 -XGQ= 35587 -dGxz 35588 -IHNhbmN0 35589 -LmxpZ2h0 35590 -X21vYmlsZQ== 35591 -0YHRgtGM 35592 -CXRvdGFs 35593 -dWl0eQ== 35594 -IHBhdXNlZA== 35595 -TkFT 35596 -IGVuY29yZQ== 35597 -bG9l 35598 -IC0qLQoK 35599 -LmhpZ2g= 35600 -YW1wbGVy 35601 -IFNlY3VyZQ== 35602 -IGZyYWdtZW50cw== 35603 -X3ZlbA== 35604 -aWxsYXJ5 35605 -IFN0ZWlu 35606 -IERhd24= 35607 -IG1heGltaXpl 35608 -4Lii 35609 -IC9e 35610 -IGNvbnRpbnVhbGx5 35611 -IHNoYWRvd3M= 35612 -CSAgICAgICAgICAgICAgICAgICA= 35613 -IElBY3Rpb25SZXN1bHQ= 35614 -IGluZm9ybWFjacOzbg== 35615 -Q0hFQ0s= 35616 -LlNlbGVjdGVkSXRlbQ== 35617 -YnVuZGxl 35618 -b2xsZXk= 35619 -PEludA== 35620 -QUlORVI= 35621 -IFdpbmc= 35622 -dGl0bGVz 35623 -b3VudGFpbg== 35624 -Q1k= 35625 -IExvY2FsZQ== 35626 -Zm9ybWVy 35627 -PGNvbnRleHQ= 35628 -UmFkaW9CdXR0b24= 35629 -X3NjaGVkdWxl 35630 -IGZhYnVsb3Vz 35631 -Um9iZXJ0 35632 -X1BST0ZJTEU= 35633 -IGdhdGVz 35634 -SU1Q 35635 -IFBlbnRhZ29u 35636 -Z29sZA== 35637 -YmFjaA== 35638 -ZW1wbG95ZWVz 35639 -Um90YXRl 35640 -IGNoYW1w 35641 -IHNlbGJzdA== 35642 -QWx0ZXJu 35643 -IGNvbnZlcnRWaWV3 35644 -Lyw= 35645 -IH4o 35646 -U3RyZWV0 35647 -X3BsYWNl 35648 -IHBlcnNvbmFsaXplZA== 35649 -UHVibGlzaGVy 35650 -IFNPQ0s= 35651 -X05BTUVTUEFDRQ== 35652 -IFN0YW5kYXJkcw== 35653 -c29ldmVy 35654 -X0NFTlRFUg== 35655 -SW50ZXJlc3Q= 35656 -w7R0 35657 -dGVtcGVyYXR1cmU= 35658 -Vmlld3BvcnQ= 35659 -Z2V0UmVzb3VyY2U= 35660 -IGVhdGVu 35661 -IHNlbXByZQ== 35662 -IGFibm9ybWFs 35663 -IGN5bGluZGVy 35664 -IHRyb3VibGVz 35665 -bm9k 35666 -0YvQsg== 35667 -Z2FtZXM= 35668 -X2ds 35669 -UGxhbmU= 35670 -Z3JleQ== 35671 -X3RibA== 35672 -LkNvbXBvbmVudFBsYWNlbWVudA== 35673 -IENoYXNl 35674 -TG9nZ2luZw== 35675 -bWFueQ== 35676 -7IY= 35677 -IGZsYW1l 35678 -PSI8Pz0k 35679 -IEdyb3Vwcw== 35680 -LVU= 35681 -0YDQsNC9 35682 -CgoKCgoKCg== 35683 -IHZhdWx0 35684 -b21vbg== 35685 -cHJvYmxlbQ== 35686 -IHRyYWRlcnM= 35687 -IHBlcmlwaGVyYWw= 35688 -IGhvbWVwYWdl 35689 -KGRlcw== 35690 -IFN1Y2Nlc3NmdWxseQ== 35691 -IHJlYm9vdA== 35692 -IGNlbGx1bGFy 35693 -aWlp 35694 -IFBsYW5z 35695 -bGlzdGluZw== 35696 -CWRpcw== 35697 -IFJlZmxlY3Q= 35698 -CWV4Y2VwdA== 35699 -Iiko 35700 -IHRhbWLDqW0= 35701 -VmVoaWNsZQ== 35702 -YWNjaQ== 35703 -bHVzaA== 35704 -T3JkZXJCeQ== 35705 -IGltYWdpbmVk 35706 -Y29kZWM= 35707 -IGRhdGVUaW1l 35708 -TWljcm8= 35709 -IHJlbWluZHM= 35710 -IGZydXN0cmF0aW5n 35711 -IFZpc3Rh 35712 -VHJhaW4= 35713 -INCy0YE= 35714 -IG1vbGVjdWxlcw== 35715 -YXZpbg== 35716 -IGRvdWJsZWQ= 35717 -IGJyYWtl 35718 -IGNhbGNpdW0= 35719 -RnJpZGF5 35720 -IElkZW50aWZpZXI= 35721 -5Z8= 35722 -0YvQuQ== 35723 -IEphaA== 35724 -UmVu 35725 -IHNjYW0= 35726 -IERlbm5pcw== 35727 -LnNldEludA== 35728 -4p8= 35729 -IGFwcGVhbHM= 35730 -IEF1cg== 35731 -IHNwbGFzaA== 35732 -ZXF1YWxzSWdub3JlQ2FzZQ== 35733 -d2h5 35734 -IHNhcA== 35735 -U3VwcG9ydGVk 35736 -IHNlcmE= 35737 -IDoi 35738 -IFZlcm1vbnQ= 35739 -IHJldW4= 35740 -IE5vdmE= 35741 -ICAgICAgICAgICAgCiAgICAgICAgICAgIAo= 35742 -UmF0ZWQ= 35743 -IGxheWluZw== 35744 -IEthcmVu 35745 -LkRlc2VyaWFsaXpl 35746 -IGNvZGVj 35747 -IHRheHBheWVycw== 35748 -OyIpOwo= 35749 -IGNydWRl 35750 -IG1vbGU= 35751 -IHVzZUNvbnRleHQ= 35752 -CXJlc3A= 35753 -IHBrdA== 35754 -IENhbm5vdA== 35755 -UGlwZWxpbmU= 35756 -5YaG 35757 -dGljYWw= 35758 -QWN0aW9uQmFy 35759 -YWVkYQ== 35760 -IENyaXRpY2Fs 35761 -IE5hZA== 35762 -IGJsZWVkaW5n 35763 -IGxsdm0= 35764 -L2N1c3RvbQ== 35765 -IFNpbXBzb24= 35766 -U3k= 35767 -aXRhYmx5 35768 -IFN1bW1pdA== 35769 -KCkpKS4= 35770 -RUxMT1c= 35771 -JCcs 35772 -TWV0 35773 -SW52b2ljZQ== 35774 -b2xpc3Q= 35775 -IHNwaW5l 35776 -YXV0aWZ1bA== 35777 -cGFpZA== 35778 -IGxvY2tlcg== 35779 -X2FybQ== 35780 -XCI+PA== 35781 -IHRyYWplY3Rvcnk= 35782 -X3Jpbmc= 35783 -IGh5ZHJvZ2Vu 35784 -dHJvbg== 35785 -IHN0YXR1dGU= 35786 -IGNvbmRpdGlvbmFs 35787 -IHRyYXk= 35788 -LXNjaG9vbA== 35789 -KHdpZGdldA== 35790 -JGNvbmZpZw== 35791 -IHJlcXVlc3Rpbmc= 35792 -LnVpbnQ= 35793 -ZXRvbg== 35794 -YnJpdGllcw== 35795 -T2ZUeXBl 35796 -QURNSU4= 35797 -cHJlZGljdA== 35798 -IGdlZ2Vu 35799 -IEhhcHA= 35800 -T0NVTUVOVA== 35801 -IEFwYXJ0 35802 -IC0tLS0t 35803 -cm9l 35804 -dWlkZQ== 35805 -anVzdGlmeQ== 35806 -IFNxdWFk 35807 -IHByb2Zlcw== 35808 -LmJvdA== 35809 -X2N1cnJlbmN5 35810 -aW5uZW4= 35811 -IE11bWJhaQ== 35812 -IE51bWJlcnM= 35813 -YXZhbmF1Z2g= 35814 -YWduaXR1ZGU= 35815 -4oCcVGhlcmU= 35816 -PWh0dHA= 35817 -54mH 35818 -IHZi 35819 -Kyc8Lw== 35820 -IG9yZ2FuaXppbmc= 35821 -YW5pdW0= 35822 -SW5TZWN0aW9u 35823 -LmFuZA== 35824 -IGV0ZXJuYWw= 35825 -IHNvdWxz 35826 -X09ORQ== 35827 -X25z 35828 -X2Jhc2lj 35829 -IHJldFZhbA== 35830 -LXNoYXBlZA== 35831 -aWZkZWY= 35832 -IE1vemlsbGE= 35833 -IGVpZw== 35834 -Y29tcGxldGVk 35835 -Tm90aWZpY2F0aW9ucw== 35836 -VEVDVA== 35837 -cmllbg== 35838 -Y29vcmRpbmF0ZXM= 35839 -IHByZXRlbmQ= 35840 -cG9uc29yZWQ= 35841 -LnN0ZGVycg== 35842 -IGdhbWVycw== 35843 -IGRlZmVuZGVk 35844 -VG9vbFRpcA== 35845 -dWl0YXI= 35846 -IGZyYW5jYQ== 35847 -IFdvb2Rz 35848 -IGlocmU= 35849 -IHBzZXVkbw== 35850 -IGNyb3dkcw== 35851 -IFNZU1RFTQ== 35852 -bGVj 35853 -LmtlcmFz 35854 -IGNpcmN1bGF0aW9u 35855 -ZWVy 35856 -LmNi 35857 -dXp6eQ== 35858 -7Zg= 35859 -LnJlYWRlcg== 35860 -IHNlcXVlbA== 35861 -U2V2ZXJhbA== 35862 -LnBvcnRhbA== 35863 -LS0tLS0K 35864 -aXN0cmFy 35865 -77u/Ly8= 35866 -UGk= 35867 -IFwiIg== 35868 -IGN1c3RvbXM= 35869 -IGRpc3BsYXlOYW1l 35870 -IG5vdGljZXM= 35871 -IGNhcmI= 35872 -Ll8KCg== 35873 -IHByb2R1Y3Rv 35874 -INGB0Ls= 35875 -IG51bWVyaWNhbA== 35876 -IHVuaW50 35877 -IGNvZGlnbw== 35878 -T3JkaW5hbA== 35879 -U3RyaW5nVXRpbHM= 35880 -IGTDqWM= 35881 -IExhbg== 35882 -IHNob3djYXNl 35883 -IGFyaXRobWV0aWM= 35884 -LXNjcm9sbA== 35885 -X1RFTVBMQVRF 35886 -IFJvdXRlck1vZHVsZQ== 35887 -IFNoYWRlcg== 35888 -INCd 35889 -cG9saWN5 35890 -UGVyZm9ybWFuY2U= 35891 -CWJvcmRlcg== 35892 -KGZpbGVwYXRo 35893 -56m6 35894 -X2VuZXJneQ== 35895 -X0NT 35896 -VGhlaXI= 35897 -LnNwYWNpbmc= 35898 -KGRw 35899 -IExBTkdVQUdF 35900 -IGhpc3RvcmljYWxseQ== 35901 -Ij57eyQ= 35902 -IGlub2Rl 35903 -c2ls 35904 -IGhhY2U= 35905 -IHNldmVyZWx5 35906 -IE92ZXJ2aWV3 35907 -IHNwcmF3 35908 -IGJlYWNoZXM= 35909 -OmxlZnQ= 35910 -t7s= 35911 -KCR7 35912 -IEZJUlNU 35913 -IFNwYQ== 35914 -LWFzcw== 35915 -IGJhaXNl 35916 -IE5PREU= 35917 -IFBpenph 35918 -UGV0 35919 -KHNlcQ== 35920 -XCI+Cg== 35921 -Q3BwTWV0aG9kUG9pbnRlcg== 35922 -IHZw 35923 -IGlh 35924 -X3NlY29uZHM= 35925 -ZW1ldA== 35926 -L2Jsb2I= 35927 -X1RIUkVTSA== 35928 -Li4uDQo= 35929 -RGVzdA== 35930 -IE5I 35931 -LmRhdGFTb3VyY2U= 35932 -aXTDqXM= 35933 -IEphaw== 35934 -c2VsbA== 35935 -IHdvcmtzaG9wcw== 35936 -PHU= 35937 -IHJpdmFscw== 35938 -IEVYSVNUUw== 35939 -aG9t 35940 -LXRva2Vu 35941 -Y29tcGF0aWJsZQ== 35942 -LkpQYW5lbA== 35943 -IHBoeXNpY2lhbnM= 35944 -YXJ0aW4= 35945 -IGRlc2lyYWJsZQ== 35946 -IGRpc3RpbmN0aXZl 35947 -LkRlcA== 35948 -Z2lk 35949 -aWxpYXRl 35950 -LG1heA== 35951 -IHByZW1pZXJl 35952 -IHFEZWJ1Zw== 35953 -IGFkdm9jYWN5 35954 -IHdoaXNwZXI= 35955 -UHQ= 35956 -IHVuY2hhbmdlZA== 35957 -X3F0eQ== 35958 -6K+35rGC 35959 -U2Vhc29u 35960 -YXZlbGVuZ3Ro 35961 -IFB1bA== 35962 -IGTDrWE= 35963 -J11dXSwK 35964 -YWxpcw== 35965 -KCIm 35966 -Ym9ybw== 35967 -IGJt 35968 -IFJhZGk= 35969 -d3Jvbmc= 35970 -IEdvaW5n 35971 -aW1lVHlwZQ== 35972 -aWpp 35973 -LWZlZWRiYWNr 35974 -IE5hbWVz 35975 -IEJhcHQ= 35976 -IHByb2JhYmxl 35977 -IEV0aGVy 35978 -IFBvbGl0aWNz 35979 -X3Byb3RvY29s 35980 -bGluaW5n 35981 -U2F0 35982 -IGNvcnJlbA== 35983 -LlByaW1hcnk= 35984 -KG51bGxhYmxl 35985 -UklPUklUWQ== 35986 -IGNvbG9yaW5n 35987 -IHV0aWxpemluZw== 35988 -ZGFz 35989 -IGV4cG9ydGVk 35990 -IGNhcnJpZXJz 35991 -Q29udg== 35992 -LmVkaXRvcg== 35993 -acOz 35994 -KGhhbmRsZXM= 35995 -IGFwcHJlY2lhdGlvbg== 35996 -LmltcG9ydA== 35997 -IEF1c3RyaWE= 35998 -IFN0cmlw 35999 -aWxpZ2h0 36000 -IGFwcHJvcHJpYXRlbHk= 36001 -IFByZXN0 36002 -IFdpcg== 36003 -IFVJQXBwbGljYXRpb24= 36004 -YWxjaGVteQ== 36005 -IE1vYg== 36006 -IERldGVybWlu 36007 -ZXJndXNvbg== 36008 -cmVnaXN0ZXJlZA== 36009 -X2NvbnZlcnQ= 36010 -IFZsYWRpbWly 36011 -LlNob3dEaWFsb2c= 36012 -cmVmbGVjdA== 36013 -IHNob29r 36014 -IGFzc3VyZQ== 36015 -IE9mdGVu 36016 -IGNpdmlsaXphdGlvbg== 36017 -IHZvY2FidWxhcnk= 36018 -Zm9yZWdyb3VuZA== 36019 -IFNjb3Bl 36020 -IHVud2FudGVk 36021 -YWN0aW5n 36022 -IChbXQ== 36023 -IG1hcmtpbmc= 36024 -Lm9yaWdpbmFs 36025 -IE1PVkU= 36026 -IHNwb3J0aW5n 36027 -Y2VwdGlvbnM= 36028 -TlNOdW1iZXI= 36029 -U2l6ZXM= 36030 -IHByb3ZpbmNpYWw= 36031 -X1RyYW5z 36032 -IHByb2JsZW1hdGlj 36033 -ZGlnaXQ= 36034 -IEVtbWE= 36035 -bG9ja3M= 36036 -IENyZXc= 36037 -aWJh 36038 -Jyk6 36039 -aXNoYQ== 36040 -IG1hbW0= 36041 -IG9jY3VyZWQ= 36042 -d2Nz 36043 -KHJ1bGU= 36044 -IG1lcmNoYW5kaXNl 36045 -ZXNwZWNpYWxseQ== 36046 -IFR3aW4= 36047 -IG5hbWluZw== 36048 -IHNsb2c= 36049 -IGltcHJvdmVz 36050 -IGFkaGVy 36051 -OnRleHQ= 36052 -LmhhZG9vcA== 36053 -X0hUVFA= 36054 -LnRvTGlzdA== 36055 -LmRpc2FibGVk 36056 -IGxlbnNlcw== 36057 -LmluaQ== 36058 -IFJhcmU= 36059 -IFVidW50dQ== 36060 -IHNjcmFt 36061 -b2xhdGlvbg== 36062 -dGl0dWxv 36063 -RXZlcnl0aGluZw== 36064 -IG5vZGRlZA== 36065 -aWNodGln 36066 -X2NvbnN0YW50 36067 -emM= 36068 -bGlmdA== 36069 -IE5vdGlmeQ== 36070 -b25kbw== 36071 -IElORg== 36072 -KCIr 36073 -IEtheg== 36074 -IGRyZWFk 36075 -Lm1hcHBlcg== 36076 -bGV1cg== 36077 -IENvbWV5 36078 -IE5C 36079 -aWNlcnM= 36080 -LlB1c2g= 36081 -IEhhY2s= 36082 -IEJyYXppbGlhbg== 36083 -X3Byb2Q= 36084 -IC8vCgo= 36085 -IGJpY3ljbGU= 36086 -IHVuYXZhaWxhYmxl 36087 -IGFkb2xlc2NlbnQ= 36088 -Ymxr 36089 -IG1pdGln 36090 -X2JsdWU= 36091 -7Jg= 36092 -ZmFkZUlu 36093 -IFV0aWxpdGllcw== 36094 -IE1O 36095 -O2s= 36096 -PHN0eWxl 36097 -LXN0YXR1cw== 36098 -aW5kbw== 36099 -IGlubmluZ3M= 36100 -IGdq 36101 -IHx8PQ== 36102 -LmV1 36103 -Ok51bWJlcg== 36104 -IGN1aXNpbmU= 36105 -IFVSTHM= 36106 -aWVr 36107 -IHdpcmVz 36108 -CXBz 36109 -aWVn 36110 -Lm1r 36111 -c29hcA== 36112 -IHNvbWV0aW1l 36113 -IHN0YXA= 36114 -X3Nlcmllcw== 36115 -LlRhcmdldA== 36116 -5ro= 36117 -LmRlc3RpbmF0aW9u 36118 -T1VOVEVS 36119 -UmFpc2Vz 36120 -JkE= 36121 -IHNtYXJ0cGhvbmVz 36122 -TklFbnY= 36123 -LnNkaw== 36124 -IGhlbGljb3B0ZXI= 36125 -IGltcGU= 36126 -IEJpcnRo 36127 -QVU= 36128 -YnJlYWRjcnVtYnM= 36129 -Y29vcmRz 36130 -IGV4cGxvcmVk 36131 -IGxvZA== 36132 -IElw 36133 -Z2FibGU= 36134 -aWFuZQ== 36135 -IGFydGlmYWN0cw== 36136 -Qm94TGF5b3V0 36137 -2KfYsQ== 36138 -bGlzdGVuZXI= 36139 -LmNhcnQ= 36140 -IEh1ZmY= 36141 -IEhpbmR1 36142 -IERhdGFUeXBlcw== 36143 -IERydXBhbA== 36144 -SUdOT1JF 36145 -IG9mZnNldHM= 36146 -IFJUQw== 36147 -LWxvZ2lu 36148 -5q4= 36149 -IFFPYmplY3Q= 36150 -IHByb3NlY3V0b3I= 36151 -Um9jaw== 36152 -X2NoYXQ= 36153 -V2F5 36154 -7LI= 36155 -IG5lZ2xpZw== 36156 -IGR1ZGU= 36157 -Ozw= 36158 -IGRlbGVnYXRlcw== 36159 -X2ZhaWxlZA== 36160 -L2Rldg== 36161 -L3dvcms= 36162 -KE5ldw== 36163 -ZXRhYmxl 36164 -KCki 36165 -KEljb25z 36166 -IHBvcms= 36167 -IE1vZGVsQW5kVmlldw== 36168 -IFZJUA== 36169 -IEtvcg== 36170 -bWl4 36171 -IG94aWQ= 36172 -IFNDUkVFTg== 36173 -IEZvdXJ0aA== 36174 -LyIsCg== 36175 -IHRlZQ== 36176 -IFN0ZXZlbnM= 36177 -dGlja3M= 36178 -IHBsZWRnZQ== 36179 -aWJib24= 36180 -IExvYW4= 36181 -IG5lbw== 36182 -bnVtcHk= 36183 -IFNoYXJlZFByZWZlcmVuY2Vz 36184 -LW9yaWVudGVk 36185 -IExvZ2dlckZhY3Rvcnk= 36186 -IEdyYXBoUUw= 36187 -emVuaWE= 36188 -Il8= 36189 -V29tZW4= 36190 -LmNhc3Q= 36191 -IGRlbGliZXJhdGVseQ== 36192 -K2I= 36193 -IEFybg== 36194 -Zm9udFNpemU= 36195 -IG1hemU= 36196 -IGJsYW1lZA== 36197 -Lm1hcw== 36198 -fSkNCg== 36199 -ZWxlcmlr 36200 -IHNjYW5uaW5n 36201 -IFdvcmtzaG9w 36202 -IGZpbmRlbg== 36203 -IGNhdXQ= 36204 -VUlGb250 36205 -KHJldHVybg== 36206 -YWxpbg== 36207 -Y2FzdGxl 36208 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v 36209 -IGluY2VudGl2ZQ== 36210 -b3BhdGg= 36211 -YmxvYg== 36212 -IGNpZ2FyZXR0ZQ== 36213 -IGZlcnRpbA== 36214 -Ki8KCgo= 36215 -IFNoYXI= 36216 -CiAgICAgIAo= 36217 -IHVuY2VydGFpbg== 36218 -IFN0b24= 36219 -T3BlcmF0aW9ucw== 36220 -IFNwZW5jZXI= 36221 -IGRlZmlu 36222 -IFNvbG8= 36223 -b25lc3Q= 36224 -t7vliqA= 36225 -IHVvbW8= 36226 -R2l2ZQ== 36227 -IGRlbnRybw== 36228 -O3BhZGRpbmc= 36229 -ZW50YWk= 36230 -IENhcnM= 36231 -IGVudGh1c2lhc20= 36232 -IE9wZXJhdGluZw== 36233 -U2tpcA== 36234 -cGFyYXRpb24= 36235 -IHByb3RlY3Rz 36236 -IHJldmVy 36237 -ZGc= 36238 -IENpbmNpbm5hdGk= 36239 -IGNvbnNlY3RldHVy 36240 -IG11c3M= 36241 -ZW1wbG95ZWQ= 36242 -YXVzZXM= 36243 -aW5rbGU= 36244 -LlZhbHVlcw== 36245 -o7w= 36246 -bG92 36247 -X1dBUk4= 36248 -IGJvb2ttYXJr 36249 -IEFwb2xsbw== 36250 -LmF4aXM= 36251 -IG3DqXQ= 36252 -IG9wZW5lcg== 36253 -IHR1bW9y 36254 -ZGFu 36255 -IGVsZW1lbnRhcnk= 36256 -IHNraXBwZWQ= 36257 -IEtlcg== 36258 -YXNpYQ== 36259 -X3Jlc3A= 36260 -IGRlbW9s 36261 -IENhbmFkaWFucw== 36262 -IHRhc3Rlcw== 36263 -VUludGVnZXI= 36264 -ICckew== 36265 -LmF3cw== 36266 -Uk9JRA== 36267 -cmlhbnM= 36268 -TVE= 36269 -b3JkYWJsZQ== 36270 -IGNvdXNpbg== 36271 -UHJvcGFnYXRpb24= 36272 -KFNlc3Npb24= 36273 -cGhhbHQ= 36274 -VUxE 36275 -IFNjYWxhcg== 36276 -IGJsb29keQ== 36277 -IOCm 36278 -Lm1hc2s= 36279 -LHE= 36280 -IFVuaXRz 36281 -IGNlbnRyZXM= 36282 -IFByaW0= 36283 -Ll0KCg== 36284 -IFNoYXc= 36285 -UHJvbQ== 36286 -IFRob3VnaHQ= 36287 -Q2hlY2tlcg== 36288 -X291dHB1dHM= 36289 -KGNoYW4= 36290 -RUlOVkFM 36291 -IGJvYg== 36292 -X2NtcA== 36293 -UGVk 36294 -IG1hdHJpY2Vz 36295 -IHZyb3V3ZW4= 36296 -IGdlbnVpbmVseQ== 36297 -aGlnaGxpZ2h0 36298 -KGRpc3BsYXk= 36299 -KSE9 36300 -IGRlbGljYXRl 36301 -IEx1dGhlcg== 36302 -IE1pbGVz 36303 -IHVzZXJJRA== 36304 -JT0= 36305 -YXRldXJz 36306 -X0JVRg== 36307 -LS0tLS0tLQo= 36308 -aW1pdGl2ZXM= 36309 -IHNoZWx2ZXM= 36310 -c2xvdw== 36311 -X2luZm9ybWF0aW9u 36312 -TEVH 36313 -V3I= 36314 -LmZvcm1z 36315 -Y2VsYW5k 36316 -L3Vu 36317 -OiY= 36318 -LuKAmQoK 36319 -PSIl 36320 -IHByb3N0 36321 -IGZvbnRzaXpl 36322 -dWNpw7Nu 36323 -Z2V0aWM= 36324 -YW10 36325 -PSIu 36326 -RGVjb3I= 36327 -QnJpdA== 36328 -ICIiKS4= 36329 -IGZvdW5kaW5n 36330 -LkZpbGVOYW1l 36331 -IFRpZXI= 36332 -IGRpc2Nsb3Nl 36333 -w6Ft 36334 -LnN5bg== 36335 -LlZpZXdIb2xkZXI= 36336 -bGljYW50 36337 -X3N0YWdl 36338 -TW9uZGF5 36339 -IGRlc2VyaWFsaXpl 36340 -dGFsaw== 36341 -IHRyYWRpdGlvbmFsbHk= 36342 -5oCB 36343 -2K4= 36344 -TEVY 36345 -IGVo 36346 -CVJPTQ== 36347 -IHt9KQo= 36348 -UXVlc3Rpb25z 36349 -bmNweQ== 36350 -IGZpeGluZw== 36351 -0LrRgw== 36352 -X0tleQ== 36353 -Ong= 36354 -IFNUUklORw== 36355 -INGE0LDQuQ== 36356 -CWxlZnQ= 36357 -IEJlbmNo 36358 -ZWxsaWo= 36359 -VVJSRUQ= 36360 -IERpYWdyYW0= 36361 -fWNhdGNo 36362 -L3RpbWU= 36363 -IE1pc3Npbmc= 36364 -ZGJuYW1l 36365 -IHNvcmU= 36366 -IFdhbHQ= 36367 -dWdnaW5n 36368 -cmVwcmVzZW50 36369 -IEdT 36370 -bmV5cw== 36371 -CXBhZ2U= 36372 -IHZvbGNhbg== 36373 -KGJ0bg== 36374 -IGV4Y2VlZHM= 36375 -IGVyZw== 36376 -IHBpbG90cw== 36377 -IFNlZA== 36378 -ZXJzaW9ucw== 36379 -IHBhdHJvbg== 36380 -UlY= 36381 -L3RvcA== 36382 -LmFzc2V0 36383 -X2Nyb3Nz 36384 -LkVkaXRvcg== 36385 -LnRi 36386 -IHdlbGNvbWluZw== 36387 -U0NSRUVO 36388 -KWZpbmRWaWV3QnlJZA== 36389 -Q29kZXI= 36390 -PElBY3Rpb25SZXN1bHQ= 36391 -X1FVRVVF 36392 -4YM= 36393 -IGhlaWdodHM= 36394 -UmVxdWVzdHM= 36395 -IHN5bWJvbGlj 36396 -DQ0KDQ0K 36397 -IGNvdXBvbnM= 36398 -LWZpdmU= 36399 -IERlc2t0b3A= 36400 -IG1pc21hdGNo 36401 -ICdfJw== 36402 -X0RJVg== 36403 -QVNPTg== 36404 -LnRyYW5zcG9zZQ== 36405 -KG1hc2s= 36406 -IENlbHQ= 36407 -LkhhbmQ= 36408 -YXR1 36409 -asSZ 36410 -IHt9KTsK 36411 -TWlzcw== 36412 -IHByaW1h 36413 -bXVuZA== 36414 -b2x2 36415 -IFByZXR0eQ== 36416 -IHJlYmVs 36417 -IEZE 36418 -YXN0aWNhbGx5 36419 -T0xU 36420 -LWF4aXM= 36421 -dXhl 36422 -IGVpbmZhY2g= 36423 -IENoZW1pY2Fs 36424 -X3NlZw== 36425 -bGVldGNvZGU= 36426 -bG9wZQ== 36427 -X29yaWc= 36428 -ICAJCQ== 36429 -KERvdWJsZQ== 36430 -IFBheVBhbA== 36431 -LkJhY2tncm91bmRJbWFnZQ== 36432 -IGhvbWVtYWRl 36433 -Liku 36434 -KHBhcnNlcg== 36435 -YXRybw== 36436 -YWNjb3JkaW9u 36437 -RGVmaW5l 36438 -IOyeiA== 36439 -IEFVVE8= 36440 -LnN1bW1hcnk= 36441 -c2NhbGFy 36442 -IEhvb2Q= 36443 -cXVpbg== 36444 -X2Rlcg== 36445 -IEdlc2No 36446 -LmNvbXB1dGU= 36447 -RmVlZGJhY2s= 36448 -IHBoYXJtYWM= 36449 -IMWfaQ== 36450 -IGdsb3Nz 36451 -IEZJTFRFUg== 36452 -SU5TVEFOQ0U= 36453 -IGthbA== 36454 -LlBM 36455 -X0ZSRUU= 36456 -R3JhZGU= 36457 -IOKZ 36458 -Lm1ldHJpY3M= 36459 -IGNhZ2U= 36460 -Llh0cmFHcmlk 36461 -X2Rz 36462 -emln 36463 -aW50ZXJvcFJlcXVpcmVEZWZhdWx0 36464 -LnJlbW92ZUNsYXNz 36465 -PT09PT09PT09PT09PQ== 36466 -IG1hc3RlcnM= 36467 -U3RhdGVFeGNlcHRpb24= 36468 -aWxsZXJ5 36469 -IEJyYWR5 36470 -IGxpbmluZw== 36471 -X2Nz 36472 -aW5zdWxh 36473 -IH06 36474 -W3Bvc2l0aW9u 36475 -IFJ4 36476 -IEJZVEU= 36477 -IFN0cmlrZQ== 36478 -INCa 36479 -IENsdXN0ZXI= 36480 -LmRvd25sb2Fk 36481 -QWxsb3dlZA== 36482 -IGFtZW5pdGllcw== 36483 -IG9uVGFw 36484 -ZnVsV2lkZ2V0 36485 -IHN0cmVuZ3Rocw== 36486 -dHdlZXQ= 36487 -IGFzY2VuZGluZw== 36488 -IGRpc2Nsb3NlZA== 36489 -Z3Jhdg== 36490 -ZGlzdHJpY3Q= 36491 -KTw8 36492 -KSwi 36493 -KGRlZnVu 36494 -X3w= 36495 -IGdhemU= 36496 -0LDRjw== 36497 -IGZvcnR5 36498 -PT09PT09PT09PT0= 36499 -U2NpZW5jZQ== 36500 -c2VtYmxlcg== 36501 -CWJvZHk= 36502 -X3RyYW5zZmVy 36503 -IGxvbmd0aW1l 36504 -IGNvbXBsaWNhdGlvbnM= 36505 -IGJvb3Ro 36506 -VkVSUg== 36507 -IHlpZWxkcw== 36508 -IG5hdmlnYXRvcg== 36509 -OjpfKCc= 36510 -RUNUT1I= 36511 -X0NvbmZpZw== 36512 -IGxhc3RlZA== 36513 -dXNhbA== 36514 -55m75b2V 36515 -IGdsb3Zlcw== 36516 -IGJlbGx5 36517 -U2FsZXM= 36518 -KE1ldGhvZA== 36519 -KG1lbWJlcg== 36520 -IFJlZWQ= 36521 -cGFzc2Vk 36522 -U2lnbklu 36523 -LG51bQ== 36524 -VUxPTkc= 36525 -IExFRw== 36526 -bmVscw== 36527 -IG1lbnRvcg== 36528 -KHJj 36529 -IE9idmlvdXNseQ== 36530 -Lmlm 36531 -IEZyZWRlcg== 36532 -SEVBRA== 36533 -QGF1dGhvcg== 36534 -Q29uZGl0aW9ucw== 36535 -IGdhcmRlbnM= 36536 -IFJpcA== 36537 -KHVzZXJz 36538 -IE9rYXk= 36539 -IHdyZXN0bGluZw== 36540 -aW1lc3RvbmU= 36541 -IENlcnRpZmllZA== 36542 -IHZlcmRpY3Q= 36543 -YWlkYQ== 36544 -LmlubmVyVGV4dA== 36545 -aWNhc3Q= 36546 -CWF0 36547 -IHByZXN1bWFibHk= 36548 -IEZVTg== 36549 -YWplcw== 36550 -0Jc= 36551 -PiIsCg== 36552 -X1Bpbg== 36553 -dWVzZQ== 36554 -IG92ZXJyaWRlcw== 36555 -X3JlYWR5 36556 -QWR2YW5jZWQ= 36557 -IG9waQ== 36558 -LWNhcnQ= 36559 -KCIvIiw= 36560 -IERlYg== 36561 -Q1JZ 36562 -IFZlcnRpY2Fs 36563 -IE9WRVI= 36564 -IENvcnBvcmF0ZQ== 36565 -ICIiOw== 36566 -IHN0ZXBwaW5n 36567 -ZWo= 36568 -IGFjY3VzYXRpb25z 36569 -IG9yYXo= 36570 -X3RhaWw= 36571 -IGluZHVjZWQ= 36572 -IGVsYXN0aWM= 36573 -IGJsb3du 36574 -LC8v 36575 -IGJhY2tncm91bmRz 36576 -4oCZdW5l 36577 -LXNkaw== 36578 -IHNldEludGVydmFs 36579 -IGluY2VudGl2ZXM= 36580 -IHZlZ2V0YWJsZQ== 36581 -X09u 36582 -ZXhwYW5kZWQ= 36583 -cGl4 36584 -X3NoYWRlcg== 36585 -IFNQRFg= 36586 -QGV4YW1wbGU= 36587 -IFdyYXBwZXI= 36588 -Llplcm8= 36589 -UG9zaXRpdmU= 36590 -IHNwaW5uZXI= 36591 -IGludmVudGVk 36592 -IEdhdGVz 36593 -0L7RgtC+0YA= 36594 -IGNvbXBhcmlzb25z 36595 -6Lc= 36596 -LnByaW1hcnk= 36597 -ZGF0YVByb3ZpZGVy 36598 -YWRkaXRpb25hbA== 36599 -CW9wdGlvbnM= 36600 -c25hcHNob3Q= 36601 -LnNldEhvcml6b250YWw= 36602 -ICJ7fQ== 36603 -IEZpc2hlcg== 36604 -aGFsdGVu 36605 -PFR5cGU= 36606 -IG1heExlbmd0aA== 36607 -IE10 36608 -IOqwgA== 36609 -LmpldGJyYWlucw== 36610 -IGlkZW50aWZpZXM= 36611 -IGZsb3dpbmc= 36612 -IERpc2N1c3Npb24= 36613 -YXRzYnk= 36614 -IHNjaHc= 36615 -dWdodHk= 36616 -IHJpdmVycw== 36617 -LnVuaXF1ZQ== 36618 -X1BIWQ== 36619 -ZWRyYWw= 36620 -KGxs 36621 -IGNzcmY= 36622 -cHBlcnM= 36623 -w7xs 36624 -IEVzcGVjaWFsbHk= 36625 -cG9ydGVk 36626 -IEhhcnJpc29u 36627 -KioqKioqKi8K 36628 -VGV4dENvbG9y 36629 -7Iq1 36630 -d2lyZQ== 36631 -IHN0YXR1c0NvZGU= 36632 -IEZpbmlzaA== 36633 -Y2VuY2U= 36634 -IE1jQ2Fpbg== 36635 -IFdvcg== 36636 -KGF3YWl0 36637 -ICktPg== 36638 -IFJlZ2lzdGVyZWQ= 36639 -SU5FRA== 36640 -a2Fs 36641 -cGFyaXNvbg== 36642 -IG9iamV0bw== 36643 -Vmk= 36644 -bWFuZGE= 36645 -IHJlbmV3ZWQ= 36646 -IFNvZg== 36647 -ZXNzZWw= 36648 -Lm5kYXJyYXk= 36649 -IGNyYXA= 36650 -566h 36651 -LmFic3BhdGg= 36652 -KHVw 36653 -IGNsZWFyYW5jZQ== 36654 -IFRX 36655 -X0NPUFk= 36656 -ICAgICAgICAgICAgCQ== 36657 -IGZvcmVzdHM= 36658 -IGFyZ3VhYmx5 36659 -IEFTUw== 36660 -aGV5 36661 -YW1lbA== 36662 -X2ZvcmU= 36663 -IFNvdXRoZWFzdA== 36664 -IGFidXNlZA== 36665 -IHByYWN0aWNpbmc= 36666 -YWtlZGlycw== 36667 -5Li7 36668 -X3Jlc291cmNlcw== 36669 -IHBvbmQ= 36670 -LkZpeGVk 36671 -TGFzdEVycm9y 36672 -IFBzeWNob2xvZ3k= 36673 -ICIvLw== 36674 -ITo= 36675 -UmV1c2FibGU= 36676 -IG1lbnNhamU= 36677 -IHJvc3B5 36678 -IGJvdXI= 36679 -IHZhcmlldGllcw== 36680 -IGVtcGF0aA== 36681 -KCh7 36682 -X29yZw== 36683 -IE1lcw== 36684 -IE1hZ2VudG8= 36685 -SVNUT1JZ 36686 -VW5sZXNz 36687 -IGhq 36688 -IER1dHk= 36689 -SnVu 36690 -LHNpemU= 36691 -IHBhaW50aW5ncw== 36692 -IGRpc3BlbnM= 36693 -ZGFydA== 36694 -IGJlaGF2aW9yYWw= 36695 -IHJwYw== 36696 -Y2FsY3VsYXRl 36697 -ZnJ1aXQ= 36698 -X21t 36699 -CXB0aHJlYWQ= 36700 -TWF4TGVuZ3Ro 36701 -IGN1cnJlbmNpZXM= 36702 -X2NhcGFjaXR5 36703 -IE96 36704 -IGZpcmVhcm0= 36705 -IGNvZWZmaWNpZW50 36706 -IGJhbmtydXB0Y3k= 36707 -d2FydA== 36708 -IGZhdGlndWU= 36709 -QVZB 36710 -IGVzcGE= 36711 -X3Bj 36712 -IFF1b3Rlcw== 36713 -X0xJR0hU 36714 -IFRpY2tldHM= 36715 -IHJlbGF0ZXM= 36716 -IHB1Ymxpc2hlcnM= 36717 -IHVubG9ja2Vk 36718 -IC8vLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 36719 -IEludGVycnVwdGVkRXhjZXB0aW9u 36720 -IG91dGxvb2s= 36721 -cm4= 36722 -IHJlYmVscw== 36723 -V3JpdHRlbg== 36724 -IGFzaWFu 36725 -b3R0bw== 36726 -IAkJCQk= 36727 -X2dwdQ== 36728 -VHh0 36729 -LkltYWdlVmlldw== 36730 -IHN1aXM= 36731 -X3RhYmxlcw== 36732 -LlJlY3ljbGVyVmlldw== 36733 -IHdoYXRzb2V2ZXI= 36734 -6IE= 36735 -XSsrOwo= 36736 -YXNzZXJ0VHJ1ZQ== 36737 -X3ZlcmlmeQ== 36738 -IFJpdmVycw== 36739 -IF1b 36740 -SmV0 36741 -aWRpYW4= 36742 -U2libGluZw== 36743 -IGdlbnJlcw== 36744 -LkFjY2Vzcw== 36745 -T1BT 36746 -IHRyaXZpYWw= 36747 -4Liq 36748 -YWxlbg== 36749 -0LLQtdC0 36750 -IFN3b3Jk 36751 -IHNjcnV0aW55 36752 -KGNi 36753 -IGNvbW1lcmNl 36754 -IGd1YXJhbnRlZXM= 36755 -X2Fkdg== 36756 -IExFVA== 36757 -cmVjaW8= 36758 -IGhpbGFy 36759 -IGJhY2t5YXJk 36760 -44CP 36761 -IGlsbHVzdHJhdGVk 36762 -L3ZlbmRvcg== 36763 -LlV0aWw= 36764 -IHdvdw== 36765 -TE9Z 36766 -IE1hcnNoYWw= 36767 -Ij4nLiQ= 36768 -IEJhaw== 36769 -IG1vZGlmaWVycw== 36770 -ZGljdGlvbmFyeQ== 36771 -IFN0cmU= 36772 -bXVsdGlwbGU= 36773 -IikpLA== 36774 -IENvcnQ= 36775 -J10iKS4= 36776 -KGFkbWlu 36777 -IENyZWF0b3I= 36778 -SW50ZXJuZXQ= 36779 -KG1z 36780 -bG9neQ== 36781 -REVDTEFSRQ== 36782 -IE1hcmN1cw== 36783 -PDw8PA== 36784 -44Gg 36785 -X215 36786 -KGluc3Q= 36787 -IHNjaWVuY2Vz 36788 -TkRFUg== 36789 -LmVudGVy 36790 -IGl0dQ== 36791 -IGJlaGF2ZQ== 36792 -UGFu 36793 -b21iaWVz 36794 -PSc8 36795 -JykpOw0K 36796 -IE1FTlU= 36797 -IFdvcmtlcnM= 36798 -Lk5vRXJyb3I= 36799 -IGJpbmRpbmdz 36800 -IGRpc2FiaWxpdGllcw== 36801 -e1w= 36802 -IE11bmljaXA= 36803 -IGNvcmVz 36804 -dXJwbGU= 36805 -IE5va2lh 36806 -dXNpb25z 36807 -IEZpdG5lc3M= 36808 -LmhhbmRsZUNoYW5nZQ== 36809 -IGphdmFzY3JpcHQ= 36810 -7JqU 36811 -KGRlYw== 36812 -IHBhY2tpbmc= 36813 -LWRlcGVuZA== 36814 -IHRyYW5zY3JpcHQ= 36815 -emVyb3M= 36816 -X2FsZXJ0 36817 -PyIsCg== 36818 -bGlicw== 36819 -sdC+0YI= 36820 -IHwKCg== 36821 -dHJhaW5lZA== 36822 -IEdlbnQ= 36823 -IFJhYg== 36824 -eHA= 36825 -X2NvbmZpZ3VyYXRpb24= 36826 -5aSp 36827 -X2FjY2VwdA== 36828 -LnJlY3ljbGVydmlldw== 36829 -OnVybA== 36830 -IE11aGFtbWFk 36831 -IHByaXZpbGVnZXM= 36832 -X2Jhbms= 36833 -dWt1 36834 -d2FsbGV0 36835 -IFJPT1Q= 36836 -IGVuY3VlbnQ= 36837 -P2ZhbWlseQ== 36838 -CXBvc2l0aW9u 36839 -IGNn 36840 -IHByZWNpcA== 36841 -bWV0aG9kcw== 36842 -X2Zhc3Q= 36843 -aW5jcmVtZW50 36844 -IFRpZ2Vy 36845 -X09DQ1VSUkVE 36846 -cXVpcA== 36847 -IEhBUw== 36848 -X2RvbQ== 36849 -IHdyZWNr 36850 -Ymo= 36851 -IGRlcm4= 36852 -IG9yZ2Fucw== 36853 -LmVudHJpZXM= 36854 -IF8oJw== 36855 -cmFtZW50bw== 36856 -IEphbWll 36857 -IHB1bms= 36858 -SVBQ 36859 -IHByb2dyYW1h 36860 -IGF0dGFpbg== 36861 -IHByb3Zlcw== 36862 -L3NpZ24= 36863 -IGFuc3dlcmluZw== 36864 -IGxhZGRlcg== 36865 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 36866 -IFdhbG1hcnQ= 36867 -IENPTlRFTlQ= 36868 -ZHVjdG9y 36869 -IHZlcmJhbA== 36870 -IFBJRA== 36871 -Y3J5cHRv 36872 -X0NBTExCQUNL 36873 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ== 36874 -IHBvdGVudA== 36875 -IHNob3J0cw== 36876 -LlVyaQ== 36877 -LnVuaWZvcm0= 36878 -O2JvcmRlcg== 36879 -IFdlcg== 36880 -IGhlcmVpbg== 36881 -bGxh 36882 -IElocg== 36883 -UGl4bWFw 36884 -bGl0ZXJhbA== 36885 -ISkKCg== 36886 -Z2VuZXJpYw== 36887 -cnVzdA== 36888 -X3NjcmlwdHM= 36889 -b3N0bw== 36890 -aXR1cw== 36891 -IENvYWxpdGlvbg== 36892 -IHJlbW90 36893 -ZGVwbG95 36894 -IEVhZ2xl 36895 -44CB44CM 36896 -IGltcG9ydGFudGU= 36897 -CW9iamVjdA== 36898 -IHNlYXNvbmFs 36899 -bmVq 36900 -YWlkdQ== 36901 -QmluZFZpZXc= 36902 -IFNpZXJyYQ== 36903 -LWJn 36904 -IG1ha2VTdHlsZXM= 36905 -W29mZnNldA== 36906 -R2FtZXM= 36907 -IGhvcm1vbmU= 36908 -QVJJTw== 36909 -aGVhZHM= 36910 -KHNlbGVjdA== 36911 -IFN0YXJ0ZWQ= 36912 -QHBhcmFt 36913 -X2RlY2w= 36914 -X2Jsb2c= 36915 -IGHDsW8= 36916 -XEFwaQ== 36917 -IE1pbHdhdWtlZQ== 36918 -UHJvdmlk 36919 -QW5pbWF0ZWQ= 36920 -IGNvb2xlcg== 36921 -IFNlZWQ= 36922 -LkVkaXQ= 36923 -z4Q= 36924 -IFRha2luZw== 36925 -IGJvcmRlckNvbG9y 36926 -LWZvdW5kZXI= 36927 -LkxvZ2dlckZhY3Rvcnk= 36928 -ICIiCgo= 36929 -QUxU 36930 -IExhdGU= 36931 -RURJQVRF 36932 -ICk7CgoK 36933 -YWZh 36934 -IGNhbmNlbGxhdGlvbg== 36935 -QXRvbQ== 36936 -IEJpcm1pbmdoYW0= 36937 -ZW1wcmVzYQ== 36938 -SEVNQQ== 36939 -YXNjYWw= 36940 -IHVwc2lkZQ== 36941 -LlZlcnNpb24= 36942 -IEZvbGRlcg== 36943 -IEVpZ2h0 36944 -IFZpbnRhZ2U= 36945 -IEFwcERlbGVnYXRl 36946 -IFByZXZlbnRpb24= 36947 -LnNlcGFyYXRvcg== 36948 -U1RN 36949 -KHJvb20= 36950 -Z2VuZXJhdG9y 36951 -IGNhdHRsZQ== 36952 -CVo= 36953 -IFBhcnRpY2xl 36954 -J307Cg== 36955 -IG5laWdoYm91cnM= 36956 -IFN0YXRlbGVzcw== 36957 -IGFsdGl0dWRl 36958 -IHNhaW50 36959 -0L7QsdCw0LI= 36960 -IGNvbnZpbmM= 36961 -IENvbnRlbnRz 36962 -IGpldW5l 36963 -KHRz 36964 -U2VyaWFsaXphdGlvbg== 36965 -KGNvbGxlY3Rpb24= 36966 -IEpheno= 36967 -IERvZA== 36968 -IFJvY2g= 36969 -YWNpbw== 36970 -Y29tbWVuZGVk 36971 -REVGSU5F 36972 -Lm9ubG9hZA== 36973 -IHNwZWNpYWx0eQ== 36974 -UExBQ0U= 36975 -X01PVkU= 36976 -IGFjY291bnRhYmxl 36977 -UmV1dGVycw== 36978 -IGZpY2tlbg== 36979 -IGRlcHI= 36980 -V293 36981 -Vm9pZA== 36982 -LnNwYWNl 36983 -4LiX 36984 -IHRx 36985 -IFBldHM= 36986 -PCQ= 36987 -KEN1cnJlbnQ= 36988 -YmVycmllcw== 36989 -cGxhbmF0aW9u 36990 -IGxpc3RPZg== 36991 -IFRodQ== 36992 -IFBSSU5U 36993 -IG1pc21v 36994 -IGRvaQ== 36995 -Y2hr 36996 -IFVuaWNvZGU= 36997 -KHJvbGU= 36998 -IHZpcmdpbg== 36999 -PFBvaW50 37000 -X1JFU1BPTlNF 37001 -LWhvdXNl 37002 -IFZlbmV6dWVsYQ== 37003 -RU1BSUw= 37004 -IHDDumI= 37005 -X2V4aXN0 37006 -QmFsbA== 37007 -LkNM 37008 -cmVmZXJlbmNlcw== 37009 -IEJlYXV0aWZ1bFNvdXA= 37010 -CUV4cGVjdA== 37011 -VEhJUw== 37012 -0YPQtA== 37013 -YmFuZQ== 37014 -IHRlbXBvcmFs 37015 -RVJJQw== 37016 -ZXRhcw== 37017 -IHJlZnJlc2hpbmc= 37018 -IHNlY3VsYXI= 37019 -QHN5bnRoZXNpemU= 37020 -YWNjdXI= 37021 -IG5lbGxh 37022 -IFNPTA== 37023 -LnBpcGU= 37024 -Q2hhbm5lbHM= 37025 -6Ieq 37026 -IGluc2VydGlvbg== 37027 -4buL 37028 -ZWxpYQ== 37029 -IGFkanVzdGFibGU= 37030 -Q2FuYWRh 37031 -IElURU0= 37032 -IGN1cnZlcw== 37033 -IENoZWFw 37034 -bGV0aW5n 37035 -IG9wdGltaXN0aWM= 37036 -YWxsbw== 37037 -IHBvbGl0aWNpYW4= 37038 -X2Rvd25sb2Fk 37039 -PWVkZ2U= 37040 -T1JUSA== 37041 -IG1vZGVsbw== 37042 -YXJ0bw== 37043 -LnJvdGF0ZQ== 37044 -IHNlbGVuaXVt 37045 -5oiR 37046 -X2FsaWFz 37047 -IHJlbm93bmVk 37048 -Licu 37049 -IGN6eQ== 37050 -IGFsbGVz 37051 -LkNvbXBpbGVy 37052 -IEJhc3M= 37053 -Q29ubmVjdG9y 37054 -LlJvbGU= 37055 -TElOSw== 37056 -IGNyaXRlcmlvbg== 37057 -bGVtZXRyeQ== 37058 -U3VjY2Vzc2Z1bGx5 37059 -L3BuZw== 37060 -IGV5ZWI= 37061 -YXNwYmVycnk= 37062 -KGdy 37063 -IGRhbmdlcnM= 37064 -IGNvcnJlY3RlZA== 37065 -IGdsb3c= 37066 -IGVsYWJvcmF0ZQ== 37067 -IEJlYXJz 37068 -YXdhaQ== 37069 -PSInKw== 37070 -IHByb21vdGlvbnM= 37071 -IG1hdGhlbWF0aWNhbA== 37072 -ICJg 37073 -X0dlbmVyaWNDbGFzcw== 37074 -IENoZWY= 37075 -LlNvcnQ= 37076 -dGFibGVOYW1l 37077 -UklD 37078 -IHZvbHVudGFyeQ== 37079 -IEJsYWRl 37080 -LWVsZWN0 37081 -IENvbWJhdA== 37082 -IEFiaWxpdHk= 37083 -IGFiZG9t 37084 -IGR1Y2s= 37085 -VG1w 37086 -5YWo 37087 -IGVyYXNl 37088 -LlBo 37089 -IERlZmF1bHRz 37090 -cGFydG1lbnQ= 37091 -X1VTQg== 37092 -w6p0ZQ== 37093 -Oyc= 37094 -IHBhZHM= 37095 -IE9iYW1hY2FyZQ== 37096 -LlRvdGFs 37097 -IGRpdmVydA== 37098 -IGNyaWNrZXQ= 37099 -IHJlY3JlYXRpb25hbA== 37100 -KHJlZA== 37101 -IENsZQ== 37102 -UlU= 37103 -IG1pc3Rha2Vu 37104 -IE1vbnRhbmE= 37105 -IHN0cml2ZQ== 37106 -X3NsaWRlcg== 37107 -IFBsYXN0aWM= 37108 -IGRlY29yYXRlZA== 37109 -IFZQ 37110 -bGljbw== 37111 -CWZhbHNl 37112 -IHByZWZz 37113 -KFwi 37114 -X2ZhbHNl 37115 -aWVuZG8= 37116 -IEAk 37117 -QnVja2V0 37118 -YWN0aWNhbA== 37119 -IFpoYW5n 37120 -LmNvbHM= 37121 -LkJpbmRpbmc= 37122 -IHdheA== 37123 -X1NUT1JBR0U= 37124 -IGxhd24= 37125 -IHJm 37126 -LlNjZW5l 37127 -IENhbGN1bGF0b3I= 37128 -LmRlc2lnbg== 37129 -IHJlc2ls 37130 -0LvQtdC8 37131 -RW1wbG95 37132 -IFByaWNlcw== 37133 -IFBXTQ== 37134 -YWdp 37135 -LmV2YWx1YXRl 37136 -CXBhcmFt 37137 -IGJyYXNz 37138 -YmJlbg== 37139 -IGluZmxhbW1hdGlvbg== 37140 -dWxsaXZhbg== 37141 -IGFubm90 37142 -IHBI 37143 -aWFtZXRlcg== 37144 -IEJUQw== 37145 -KGJveA== 37146 -U3Rvcnlib2FyZA== 37147 -IGNsYXk= 37148 -LmFzc2VydFJhaXNlcw== 37149 -fHN0cmluZw== 37150 -LkFwcGx5 37151 -IG1hdGNoZXI= 37152 -dW5kZWQ= 37153 -IHNhdGlzZnlpbmc= 37154 -IOyglQ== 37155 -UmVuZGVyaW5n 37156 -X2FwcHJv 37157 -aW5kcm9tZQ== 37158 -QU5FTA== 37159 -X2ZpeA== 37160 -YnJ1c2g= 37161 -Lk1hdGNo 37162 -IHNtaWxpbmc= 37163 -b25hdXQ= 37164 -U3VuZGF5 37165 -IGRlbGV0aW9u 37166 -IGVuY291cmFnZXM= 37167 -UHVsbA== 37168 -IHJldmVuZ2U= 37169 -IHF1YXJyeQ== 37170 -dHJhZGU= 37171 -IGNhYmxlcw== 37172 -KGRlbHRh 37173 -aXRlc3BhY2U= 37174 -IGZo 37175 -LmJ1bmlmdQ== 37176 -IHZpZWw= 37177 -X0lOQ0xVREVE 37178 -IFRhaWw= 37179 -YWRhcg== 37180 -b2Zz 37181 -IG1ldGFscw== 37182 -Z29t 37183 -X21ldGhvZHM= 37184 -IG5q 37185 -LlN0ZA== 37186 -KHdpbg== 37187 -JCgn 37188 -IHR1cnRsZQ== 37189 -dXJvbg== 37190 -IGVucm9sbGVk 37191 -IEh6 37192 -IEJveERlY29yYXRpb24= 37193 -IHBvbnQ= 37194 -cmVsYXRpb25zaGlw 37195 -Qmk= 37196 -s7s= 37197 -IG1hc2N1bA== 37198 -IHNoYWRlcw== 37199 -IHZy 37200 -IExvZ2lj 37201 -IGFpbg== 37202 -IERJU1Q= 37203 -IGNvbGxhcg== 37204 -InByb2ZpbGU= 37205 -R2VuZXJhdGVkVmFsdWU= 37206 -IFBvc3NpYmxl 37207 -IGVpbmVz 37208 -g4E= 37209 -LnRpbWVvdXQ= 37210 -IEVj 37211 -IGplcnNleQ== 37212 -LkRvdWJsZQ== 37213 -IHF1YWxpZnlpbmc= 37214 -dm9y 37215 -Q1JFRU4= 37216 -X0FwcA== 37217 -X3JlY3Y= 37218 -IGFsaWVucw== 37219 -SXRz 37220 -RXNj 37221 -aWF0b3I= 37222 -IEVjbGlwc2U= 37223 -IGdo 37224 -VmljdA== 37225 -CWh0bWw= 37226 -dG9v 37227 -LmNvbnN0 37228 -IGFudGVyaW9y 37229 -IFd1 37230 -KGtleXM= 37231 -IHVsdHI= 37232 -X3BvbHk= 37233 -IFRhcA== 37234 -IEJ1ZA== 37235 -QVdT 37236 -IGNyYXNoZXM= 37237 -X3RvdA== 37238 -Q29udGlu 37239 -LWhhbmRlZA== 37240 -YWx0aG91Z2g= 37241 -4Lia 37242 -aWZpY2VudA== 37243 -IGRldmU= 37244 -dXRvcnk= 37245 -IFdvcnRo 37246 -X01T 37247 -IGZsb29yaW5n 37248 -IHNlbGxlcnM= 37249 -IFRoYW5rc2dpdmluZw== 37250 -IHBuZw== 37251 -IHZhbG9yZXM= 37252 -IHNsZWV2ZQ== 37253 -IGZpbGxl 37254 -0JA= 37255 -IGFwcG9pbnRtZW50cw== 37256 -IHZpbQ== 37257 -VXNlckluZm8= 37258 -Qk9PU1Q= 37259 -IHBvc2Vk 37260 -aW5pdGlhbGl6ZWQ= 37261 -LnByb2R1Y3Rz 37262 -IExlYWRlcnNoaXA= 37263 -bWFudWVs 37264 -JyU= 37265 -ZW1hcmtz 37266 -UGVyY2VudGFnZQ== 37267 -KGRpc3Q= 37268 -LmF2YXRhcg== 37269 -KGhPYmplY3Q= 37270 -5LuK 37271 -X2lmZg== 37272 -aWNvbmU= 37273 -Oyk= 37274 -X25pbA== 37275 -IGFib2w= 37276 -0LXRgdGC 37277 -IHZlbnVlcw== 37278 -LkNvbnZlcnQ= 37279 -IScpCg== 37280 -LkJpdG1hcA== 37281 -c2tpbg== 37282 -X0NPTFVNTg== 37283 -UmV2 37284 -R1JFU1M= 37285 -Z293 37286 -IHdpc2hlZA== 37287 -dHJhY3Rz 37288 -LmFzc2VydEZhbHNl 37289 -IHNjcmVlbnNob3Q= 37290 -IGZvaXM= 37291 -Q29tYg== 37292 -TGluZVdpZHRo 37293 -IEdyYWI= 37294 -IGludGVuc2l2ZQ== 37295 -CXNo 37296 -Kyk= 37297 -LmZpcnN0TmFtZQ== 37298 -X1BST0NFU1M= 37299 -IHRpbHQ= 37300 -aXRvcmVk 37301 -LkxPRw== 37302 -IGJhaw== 37303 -IGludGVudGlvbmFsbHk= 37304 -LnBsYXllcnM= 37305 -KGNhbnZhcw== 37306 -KSkpDQo= 37307 -LlByb3ZpZGVy 37308 -X1BVQkxJQw== 37309 -VGFsaw== 37310 -IExpdg== 37311 -Y2hlZHVsZXJz 37312 -IGxj 37313 -YWRpYw== 37314 -ZmVhdHVyZWQ= 37315 -LnJlc291cmNlcw== 37316 -RnVsbE5hbWU= 37317 -IG1lYW53aGlsZQ== 37318 -QnVmZmVycw== 37319 -IHJlc29sdmVy 37320 -IFNBUA== 37321 -X1RF 37322 -R05V 37323 -IEZvcm1zTW9kdWxl 37324 -X3do 37325 -IFN3ZQ== 37326 -LndpZGdldHM= 37327 -IGNhYmluZXRz 37328 -IHN1c2NlcHQ= 37329 -IEJvdHQ= 37330 -YWN0aXZleA== 37331 -YXZhcg== 37332 -YW50aWNz 37333 -ICI9Ig== 37334 -X2t3YXJncw== 37335 -IGdhbWVPYmplY3Q= 37336 -IEFuZ2xl 37337 -Lkl0ZXI= 37338 -bWFyc2g= 37339 -IEJpcnRoZGF5 37340 -IENNUw== 37341 -cmVxdWVzdHM= 37342 -IFBlYXJs 37343 -X0VPTA== 37344 -IGxpbnV4 37345 -KG9yZw== 37346 -X01vdXNl 37347 -LmNvbnN0cnVjdG9y 37348 -IHpk 37349 -IGtpY2tz 37350 -YXJ0aXNhbg== 37351 -IGVheA== 37352 -S24= 37353 -cG9uZ2U= 37354 -IEZpbmxhbmQ= 37355 -IG1ldHJlcw== 37356 -IEFzc2Vzc21lbnQ= 37357 -cGFydG5lcg== 37358 -L3ByZQ== 37359 -IScsCg== 37360 -W0ludA== 37361 -IG9zbG8= 37362 -ZGF0ZXBpY2tlcg== 37363 -L1N0cmluZw== 37364 -b3BsYXk= 37365 -IEhlYnJldw== 37366 -LGRvdWJsZQ== 37367 -IHRyYWJhbA== 37368 -KyJc 37369 -CUVJRg== 37370 -L3RleHQ= 37371 -X0ZJUlNU 37372 -IFBldGU= 37373 -IGVnbw== 37374 -IGV4dHJhcw== 37375 -UERP 37376 -IHJlZ3VsYXRl 37377 -IFFXaWRnZXQ= 37378 -c3Rz 37379 -IFNob3dz 37380 -IE5IUw== 37381 -LmNvdXJzZQ== 37382 -cHRocmVhZA== 37383 -IEZ1ZWw= 37384 -LnRpbWVz 37385 -IMKw 37386 -IHN0cmlkZXM= 37387 -KCQoJyM= 37388 -KHdvcmRz 37389 -IHJoeXRobQ== 37390 -IHNwb250 37391 -IHNlbnNhdGlvbg== 37392 -IHNwaWtl 37393 -Q2xvc2luZw== 37394 -6aG16Z2i 37395 -TnVtZXJpYw== 37396 -IGJyZWF0aGU= 37397 -IGZpbmFsZQ== 37398 -X0ZBQ1Q= 37399 -aW5pb24= 37400 -IGNoaWxs 37401 -IGZvcm1hbGx5 37402 -QU5HRUQ= 37403 -ICc6Jw== 37404 -INC/0YDQuA== 37405 -YXE= 37406 -IEZhYnJpYw== 37407 -KGxhdA== 37408 -IFByaW5jaXBhbA== 37409 -IGVycm8= 37410 -b2NhbGU= 37411 -Tm9t 37412 -IGZvc3Q= 37413 -X0NVU1RPTQ== 37414 -LmludGVsbGlq 37415 -ZXJ0b29scw== 37416 -IGNsYXNzZQ== 37417 -YWRpZW50cw== 37418 -IGZ1bmRyYWlzaW5n 37419 -RU5F 37420 -X09QVElPTlM= 37421 -X29i 37422 -Ly99Cg== 37423 -IHByb3RlY3Rpb25z 37424 -LnNlZWQ= 37425 -TlY= 37426 -dGVybWluYWw= 37427 -Ozs7 37428 -UHJlZGljYXRl 37429 -IOy2 37430 -IGJvbWJpbmc= 37431 -R0Y= 37432 -IGNoZXc= 37433 -KSkpLg== 37434 -cXVhbGlmaWVk 37435 -XT17 37436 -bGlzdGVu 37437 -Q0VOVA== 37438 -ZGlnZXN0 37439 -RWFzdA== 37440 -IGRpdmVy 37441 -IGVuZHBvaW50cw== 37442 -IGVl 37443 -IGNvbGxlYWd1ZQ== 37444 -IGRpc3NlcnRhdGlvbg== 37445 -X2NvbW1pdA== 37446 -X0RBVA== 37447 -LnJj 37448 -IGJyZWFzdHM= 37449 -IFJ1Zw== 37450 -IFBpbA== 37451 -Q29udHJhY3Rz 37452 -IEJyeWFu 37453 -V2ViVmlldw== 37454 -IGNvbmNlbnRyYXRl 37455 -IElubmVy 37456 -ICd8 37457 -c3Rkb3V0 37458 -X1N1Yg== 37459 -Pi0tPgo= 37460 -Vm9s 37461 -IFNTRA== 37462 -KSkpLA== 37463 -Lk9wdGlvbmFs 37464 -IG51cnNlcw== 37465 -IG9yYg== 37466 -X3Bl 37467 -KTsNCg0KDQo= 37468 -cGxhY2Vk 37469 -ZXNzZXI= 37470 -IHRoZXJhcGV1dGlj 37471 -IHdoaXRlc3BhY2U= 37472 -IGFzdG9u 37473 -U3VjY2Vzc2Z1bA== 37474 -IHByYWlzZWQ= 37475 -IFdlcw== 37476 -IGVpZ2h0aA== 37477 -aXJhbA== 37478 -IHZyb3V3 37479 -IGZhY3Rpb24= 37480 -X2JpYXM= 37481 -IHdpdGNo 37482 -IG5wYw== 37483 -KHNi 37484 -IFJvZHJpZw== 37485 -X2JpZw== 37486 -RGVwZW5kZW5jeQ== 37487 -IEFicmFoYW0= 37488 -YXJkaQ== 37489 -Q0FS 37490 -bm9z 37491 -IGFidW5kYW5jZQ== 37492 -IG51dHJpZW50cw== 37493 -aW5zdGVpbg== 37494 -LlZlcnQ= 37495 -IElTUw== 37496 -PFU= 37497 -IHN1bXM= 37498 -X2hpc3Q= 37499 -IGZhcm1lcg== 37500 -IEFicg== 37501 -U2hvdA== 37502 -IEJhZFJlcXVlc3Q= 37503 -IGhhc3M= 37504 -IFJhaWxz 37505 -IGFmZmlsaWF0ZWQ= 37506 -5p2l 37507 -IGVyZg== 37508 -SU5G 37509 -IFZpZXdIb2xkZXI= 37510 -bWluaQ== 37511 -IFJvdGg= 37512 -IGZhaXRoZnVs 37513 -IFBoaWxsaXBz 37514 -QU5ET00= 37515 -XS5b 37516 -X1BBWQ== 37517 -IEFyY3RpYw== 37518 -ZmFrZXI= 37519 -RGlnaXQ= 37520 -TWFsZQ== 37521 -c3RkZXJy 37522 -c2V5cw== 37523 -IMWh 37524 -X3JlbW90ZQ== 37525 -bGlxdWU= 37526 -IGluZGVm 37527 -IEluZHVzdHJpZXM= 37528 -aXRyYQ== 37529 -X3BhaXJz 37530 -PGlvc3RyZWFt 37531 -IHNhbGFyaWVz 37532 -aWtlbg== 37533 -LkZyYW1l 37534 -UExJQw== 37535 -X1NQRUM= 37536 -IE1lZGl0ZXJy 37537 -IHN5c3RlbWF0aWM= 37538 -IGludGVycm9n 37539 -SWNvbkJ1dHRvbg== 37540 -c2Vh 37541 -aW50cm8= 37542 -IElzc3Vlcw== 37543 -ZW5jcnlwdGVk 37544 -IGludGVybmF0aW9uYWxseQ== 37545 -IHNucHJpbnRm 37546 -IHBhc3Rh 37547 -IEJyYWRsZXk= 37548 -X1N0YXR1cw== 37549 -QUxL 37550 -X1BBRA== 37551 -LmxhdW5jaA== 37552 -PHNlbGVjdA== 37553 -IGhhcmRlc3Q= 37554 -IHBoeQ== 37555 -ICgoKg== 37556 -LXNsaWRl 37557 -IE5vYm9keQ== 37558 -U3U= 37559 -IGFzw60= 37560 -Y2xvc2VzdA== 37561 -X2luaXRpYWxpemVy 37562 -IHN1cHBvcnRlcg== 37563 -LWdlbg== 37564 -IHRhbGVz 37565 -IGNvcnA= 37566 -X2Z1 37567 -c2F0 37568 -bmVpZ2hib3I= 37569 -Lk1pZ3JhdGlvbnM= 37570 -IGFsZ3Vu 37571 -IHNpbm9u 37572 -LlNwZWM= 37573 -PywK 37574 -LkdM 37575 -bWFsZQ== 37576 -IG1vbml0b3Jz 37577 -eWxhbg== 37578 -LUxpY2Vuc2U= 37579 -Lm1hdGNoZXM= 37580 -IEFCUw== 37581 -IE1hc3Q= 37582 -IFdhbGxldA== 37583 -KCQoIiM= 37584 -RGlydHk= 37585 -IGNvcGU= 37586 -IGludGVycG9sYXRpb24= 37587 -b3VzZWQ= 37588 -IEpldHM= 37589 -LkZMQUc= 37590 -LkNhbmNlbA== 37591 -LkV2ZW50cw== 37592 -bmV2ZXI= 37593 -IE1Ieg== 37594 -PkQ= 37595 -IHNlcnZsZXQ= 37596 -YmFzdGlhbg== 37597 -ID4m 37598 -U0lE 37599 -X2Nsaw== 37600 -IGRpdmlzaW9ucw== 37601 -fScsCg== 37602 -IGRpbGRv 37603 -IHBhcmFkZQ== 37604 -bWFqb3I= 37605 -IGFib2FyZA== 37606 -Oysr 37607 -IGZ1c2lvbg== 37608 -In0seyI= 37609 -IERpYWxvZ1Jlc3VsdA== 37610 -CWFycg== 37611 -LWVt 37612 -X25y 37613 -KGhhbmRsZXI= 37614 -Lk5FVA== 37615 -Llh0cmFSZXBvcnRz 37616 -IFNoYWg= 37617 -IEJyaWVm 37618 -LSw= 37619 -IHByZWNpbw== 37620 -CQkJICAgICAg 37621 -IHRhbnQ= 37622 -IEdyYW5kZQ== 37623 -L3htbA== 37624 -X0lDT04= 37625 -IFJldHJv 37626 -dW5xdWU= 37627 -IG5hZw== 37628 -dG9GaXhlZA== 37629 -WEw= 37630 -IGRlY2xhcmluZw== 37631 -IENvbmNyZXRl 37632 -IEFtYXppbmc= 37633 -CXByaW50aw== 37634 -IGRlYmF0ZXM= 37635 -REFURUQ= 37636 -IGFlc3RoZXRpYw== 37637 -ZW1ldGVyeQ== 37638 -Um91dGluZ01vZHVsZQ== 37639 -IE5hc2h2aWxsZQ== 37640 -V0FZUw== 37641 -IHdvbGY= 37642 -IG9ic2VydmVycw== 37643 -T1RB 37644 -YW5zb24= 37645 -IGVh 37646 -IGdyZWVuaG91c2U= 37647 -k43kvZw= 37648 -IHN0YWly 37649 -IGltbWlncmFudA== 37650 -X2FwcGx5 37651 -cGVhcmU= 37652 -IEJsb29tYmVyZw== 37653 -X1BMQVlFUg== 37654 -UmVzcA== 37655 -5q2j 37656 -Q2hvb3Nlcg== 37657 -IElDb2xsZWN0aW9u 37658 -UGV0ZXI= 37659 -RXJybw== 37660 -LmRldGVjdENoYW5nZXM= 37661 -TWFwcw== 37662 -IHNxdWVlemU= 37663 -IEhvbWVz 37664 -d2VnaWFu 37665 -IGZvcm1hdHRpbmc= 37666 -IG5lZ290aWF0ZQ== 37667 -dWxk 37668 -IE5lcA== 37669 -IFFC 37670 -IGVjb25vbWllcw== 37671 -ICovLA== 37672 -IHJlZHVuZA== 37673 -IEFiZXI= 37674 -LklzTnVsbE9yV2hpdGVTcGFjZQ== 37675 -eWNsZWQ= 37676 -ICAgICAgICAgICAgICAgICAgCg== 37677 -X1No 37678 -IHNrZXB0 37679 -IHJlY3JlYXRlZA== 37680 -IGdldFR5cGU= 37681 -IG1hcmdpbnM= 37682 -IGNvbG9uaWFs 37683 -Y2hhcnRz 37684 -Ly9A 37685 -IHByb2Nlc3NvcnM= 37686 -6K+0 37687 -YmF0aXM= 37688 -5oSP 37689 -YXRvcmlv 37690 -bWVudGlvbmVk 37691 -UGF0aWVudA== 37692 -IHByZXk= 37693 -Q2hlY2tib3g= 37694 -X3hwYXRo 37695 -LnNraXA= 37696 -IE1vcm1vbg== 37697 -IE1lbW9yeVN0cmVhbQ== 37698 -Q1JFTUVOVA== 37699 -IGt1 37700 -bWVsZA== 37701 -XERhdGE= 37702 -IEtlcm5lbA== 37703 -aWx0cg== 37704 -6YCB 37705 -KHByb2ZpbGU= 37706 -Q2FyYm9u 37707 -Uk9MRQ== 37708 -KHBs 37709 -XSoo 37710 -Lm1lbW9yeQ== 37711 -IG1lZGFs 37712 -IGFkdmlzb3I= 37713 -aXTDpHQ= 37714 -IGhkcg== 37715 -aWVydW5n 37716 -IFByb3ZpZGVz 37717 -KGFscGhh 37718 -IHRlZW5hZ2Vycw== 37719 -LXBhcnNlcg== 37720 -LkxhdExuZw== 37721 -XSgpCg== 37722 -IGZlbG9ueQ== 37723 -CQkJCgkJCQo= 37724 -Qk9PSw== 37725 -IHNsYXNo 37726 -IGNsZWFyZml4 37727 -IFByb3BoZXQ= 37728 -5a65 37729 -cmlnaHRuZXNz 37730 -LWZp 37731 -LmtpbmQ= 37732 -ZXJ0b24= 37733 -Smlt 37734 -IG1hbmlwdWxhdGU= 37735 -IHdvcmtzaGVldA== 37736 -b2xpbg== 37737 -c3RhcnM= 37738 -IGFydGlmYWN0 37739 -X0VNUFRZ 37740 -CW1haW4= 37741 -LS0tLS0tLS0tLS0tLTwv 37742 -L3N0YXRpYw== 37743 -SVRJRVM= 37744 -IENvdW5zZWw= 37745 -IFdD 37746 -IEJMQUNL 37747 -LXN5c3RlbQ== 37748 -IFRyaXBsZQ== 37749 -LmJ0 37750 -c29mdHdhcmU= 37751 -XScpLg== 37752 -SW5qZWN0aW9u 37753 -X25vdGlmeQ== 37754 -IGZpZnRlZW4= 37755 -IGFtYmFzc2Fkb3I= 37756 -YnJlYWtpbmc= 37757 -VVJJQ29tcG9uZW50 37758 -IFByb3Rlc3Q= 37759 -LlJlc2V0 37760 -IE1Qcw== 37761 -dnJv 37762 -LmdldFN0YXR1cw== 37763 -X21vcmU= 37764 -Y3Vw 37765 -IEtlbnlh 37766 -5bey 37767 -IGFtbXVuaXRpb24= 37768 -15XX 37769 -IERhc2g= 37770 -IHVuZGVyZ28= 37771 -IGJ1ZGR5 37772 -0YLQvtGA 37773 -ZXRpY2FsbHk= 37774 -X091dA== 37775 -IEJyb2Fkd2F5 37776 -qow= 37777 -IEZpdHo= 37778 -IHN0cmlwcGVk 37779 -LWNhY2hl 37780 -IHVtYg== 37781 -IGFub20= 37782 -IHNpYmxpbmdz 37783 -b2N1bWVudGVk 37784 -SW50ZXJydXB0ZWRFeGNlcHRpb24= 37785 -IHBlbmc= 37786 -bHN0 37787 -X0FMSUdO 37788 -LWNhcA== 37789 -UkQ= 37790 -Y2VsbHM= 37791 -IE1vdG9ycw== 37792 -IHRyYW5zbGF0aW9ucw== 37793 -dXN0ZXJpbmc= 37794 -6Zo= 37795 -IGxlYWtz 37796 -ZmlsZVBhdGg= 37797 -IG91dGdvaW5n 37798 -X2VuZHBvaW50 37799 -X0dM 37800 -LmxpZmVyYXk= 37801 -cmljaHQ= 37802 -IE9wZW5HTA== 37803 -LmpwYQ== 37804 -IGFmZmVjdGlvbg== 37805 -Zmx1eA== 37806 -IGdseQ== 37807 -IGJ1ZA== 37808 -Pic7 37809 -IGV4cHJlc3Npbmc= 37810 -IElR 37811 -IEZhY3Q= 37812 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioK 37813 -X21hc3M= 37814 -KSk6 37815 -IGNvbmRvbQ== 37816 -IGNyZWF0ZVN0YXRl 37817 -b21ldG93bg== 37818 -IGlycg== 37819 -ID4o 37820 -PkI= 37821 -aXRlcmF0aW9u 37822 -44Oq 37823 -IHNoaXJ0cw== 37824 -b3VudHk= 37825 -LT4k 37826 -X1NJR04= 37827 -IERhbGU= 37828 -IGpq 37829 -RWFzeQ== 37830 -RnJl 37831 -IE55 37832 -IGNobG9y 37833 -bWF0Y2hlZA== 37834 -IEdlcm0= 37835 -LVVB 37836 -IE5hdGhhbg== 37837 -ZWR1Y2F0aW9u 37838 -LXlhcmQ= 37839 -LWNoZQ== 37840 -aG91c2Vz 37841 -cml0aW9uYWw= 37842 -IHByb3hpbWl0eQ== 37843 -IGRpZXNlbQ== 37844 -4bqtcA== 37845 -IGRyb3VnaHQ= 37846 -LmF1ZGlv 37847 -IExlbw== 37848 -IGZhdm9yYWJsZQ== 37849 -aW5jaA== 37850 -IERhdw== 37851 -cmlibHk= 37852 -X3N0dWRlbnQ= 37853 -aWRhYmxl 37854 -T1ZF 37855 -IGxhY2tz 37856 -b3VuY2luZw== 37857 -LmJ1c2luZXNz 37858 -IHJlb3Blbg== 37859 -bWF5YmU= 37860 -X0dMT0JBTA== 37861 -IGRyZXNzZXM= 37862 -IEVkd2FyZHM= 37863 -ZW5zaWJsZQ== 37864 -IEhhcmR3YXJl 37865 -IEV4Y2VsbGVudA== 37866 -IFRpbWVVbml0 37867 -Q1RJT05T 37868 -IHNjaGVkdWxlcw== 37869 -IHNlZ3Vl 37870 -T3BlbnM= 37871 -YW1tZW4= 37872 -LUlkZW50aWZpZXI= 37873 -IHN0YXJpbmc= 37874 -IGhhcHBpbHk= 37875 -IEhvYg== 37876 -J18= 37877 -ICIpOw== 37878 -YW1lbnRvcw== 37879 -ZXRjaGVk 37880 -IC8+fQo= 37881 -LlVzZXJz 37882 -IGludGVycnVwdGVk 37883 -Q29udGFjdHM= 37884 -IHJlZ2lzdHJv 37885 -aW5idXJnaA== 37886 -Q0hB 37887 -X2ltcA== 37888 -cGhpcw== 37889 -c2F5 37890 -IHJldGFpbGVy 37891 -Lk5PREU= 37892 -L21hcHM= 37893 -X0xBU1Q= 37894 -IENoYXJnZQ== 37895 -X2d1YXJk 37896 -Q29sbGlkZXI= 37897 -IFN0YXRlbGVzc1dpZGdldA== 37898 -IjpbIg== 37899 -KCIuLi8uLi8= 37900 -aW94aWRl 37901 -IFN1bmQ= 37902 -ICcnOw== 37903 -dW5zZXQ= 37904 -YWRkV2lkZ2V0 37905 -0LvRjg== 37906 -ZWxsZXM= 37907 -YWxrZXI= 37908 -QXJj 37909 -IGRlZHVjdA== 37910 -R1VJTGF5b3V0 37911 -IFZpbGxh 37912 -IGZvcmJpZGRlbg== 37913 -X3doZXJl 37914 -IFwv 37915 -IFRpYg== 37916 -X0FY 37917 -XQ0KDQo= 37918 -IEJpcg== 37919 -IGJlbmQ= 37920 -IE1BS0U= 37921 -IE1FVA== 37922 -IGZ1dHVyZXM= 37923 -IHdlaWdodGVk 37924 -IiIiDQo= 37925 -IGF1dGhvcml6ZQ== 37926 -KHByb2dyYW0= 37927 -fSx7Ig== 37928 -IGNvZWZmaWNpZW50cw== 37929 -w6pz 37930 -UGVyUGFnZQ== 37931 -IEJhdGhyb29t 37932 -IFB1Ymxpc2hpbmc= 37933 -R1BM 37934 -IHN1Ym1pc3Npb25z 37935 -IE5VTUJFUg== 37936 -asSF 37937 -IGFkZGl0aW9uYWxseQ== 37938 -ZW1wcmU= 37939 -IFNoZWw= 37940 -b3R5cA== 37941 -U29sdXRpb24= 37942 -IHRodW5kZXI= 37943 -X2Vj 37944 -IAogICAgCg== 37945 -IEZlbGxvdw== 37946 -IGtheQ== 37947 -IG5ld1N0YXRl 37948 -T05UQUw= 37949 -SW1wbGVtZW50YXRpb24= 37950 -Lkxvb2s= 37951 -IGVudHM= 37952 -IGxvcnM= 37953 -IEJJRw== 37954 -ZmFi 37955 -IGF2ZXJhZ2Vk 37956 -IEZlZWRiYWNr 37957 -IFdlbGxz 37958 -IG1hcnRpYWw= 37959 -IGluZHVs 37960 -IENvbW11bmlzdA== 37961 -IEZvcmV4 37962 -IEFncmljdWx0dXJl 37963 -Ils= 37964 -IHF1YXI= 37965 -IEtvbnQ= 37966 -CXZpZXc= 37967 -LkJ5dGVz 37968 -ZGVza3RvcA== 37969 -IE1ha2Vz 37970 -YWtlc3BlYXJl 37971 -Lk51bGxhYmxl 37972 -IHNwb3RsaWdodA== 37973 -VkI= 37974 -b3d5 37975 -KHRvcmNo 37976 -dHJpZGdl 37977 -X2JvdW5kcw== 37978 -IGFwb2xvZ2l6ZQ== 37979 -LmFkZEl0ZW0= 37980 -YW50ZA== 37981 -Kik7Cg== 37982 -LHU= 37983 -KGdlbg== 37984 -57uT 37985 -cmVhdG9y 37986 -IENvcmQ= 37987 -b3VwcGVy 37988 -Lm1ldHJv 37989 -IGV3 37990 -IFdPUkQ= 37991 -LkFmdGVy 37992 -IGRldGFpbmVk 37993 -IEhhbW1lcg== 37994 -ZXhpc3Rpbmc= 37995 -IG9zdA== 37996 -IG1vbnVtZW50 37997 -LWN1c3RvbQ== 37998 -VXNlcklE 37999 -IE5vbQ== 38000 -IHJlamVjdGlvbg== 38001 -KGRpbQ== 38002 -IHNpbmdsZXRvbg== 38003 -CWRpZQ== 38004 -YXJpYW5jZQ== 38005 -cmVwb3J0cw== 38006 -XSE9 38007 -ZWxkYQ== 38008 -IHByZXZhbGVuY2U= 38009 -X3JlZ3M= 38010 -LiIu 38011 -IGZlbWluaXN0 38012 -Q29kZWM= 38013 -ICoqCg== 38014 -KGxhYmVscw== 38015 -X01BUks= 38016 -RkFJTEVE 38017 -IGFkbWluaXN0ZXJlZA== 38018 -V04= 38019 -ICAgICAgICAJCQ== 38020 -IG5vdW4= 38021 -d2ln 38022 -IGdvdHRh 38023 -IHJpZg== 38024 -LWlt 38025 -IFBhdWxv 38026 -IENvbW1hbmRUeXBl 38027 -XSkpCgo= 38028 -LXplcm8= 38029 -VHJhaW5pbmc= 38030 -IGxvcmQ= 38031 -X2FydA== 38032 -cmVkZGl0 38033 -Q2VydA== 38034 -IHBlc28= 38035 -Um90 38036 -IGVuZGFuZ2Vy 38037 -LmRy 38038 -dXNlckluZm8= 38039 -dW50cw== 38040 -bnY= 38041 -IFRyYWlsZXI= 38042 -LWZpcnN0 38043 -KG1ha2U= 38044 -IGJlbmVmaWNp 38045 -LWJsYWNr 38046 -acOf 38047 -IHVuZG91YnRlZGx5 38048 -IG1leA== 38049 -IEFuY2llbnQ= 38050 -KGFz 38051 -IGRlc2NlbnQ= 38052 -UGljaw== 38053 -IHJlcGxpY2E= 38054 -JG9iag== 38055 -w6Rocg== 38056 -IGFycm93cw== 38057 -ZnR5 38058 -IExpYnlh 38059 -dWdh 38060 -Y2hhcmdlZA== 38061 -VHVy 38062 -IGhvbWlj 38063 -aXNzZW4= 38064 -IEZha2U= 38065 -IGJlZXJz 38066 -IHNjYXR0ZXJlZA== 38067 -KFRpbWU= 38068 -VVRJTA== 38069 -IGJ1cmVhdWNy 38070 -L3BsYWlu 38071 -IHN0aWNraW5n 38072 -RkFJTA== 38073 -IENvdmlk 38074 -VGhpcmQ= 38075 -X3ByZXNlbnQ= 38076 -IFBpZXJyZQ== 38077 -IOuq 38078 -IFsuLi5dCgo= 38079 -UHJvYg== 38080 -IFRyYWZmaWM= 38081 -aWNhbw== 38082 -ZG9jdG9y 38083 -ICksCgo= 38084 -VGFicw== 38085 -YWx1 38086 -77ya4oCc 38087 -IGluaGVyZW50 38088 -X05v 38089 -cml0aXM= 38090 -IFByb29m 38091 -LmJhc2VuYW1l 38092 -5Lya 38093 -IGNoaW0= 38094 -IFByb3RlY3RlZA== 38095 -Y3JpdA== 38096 -IHByb25l 38097 -INC60L7QvQ== 38098 -IEhlcm9lcw== 38099 -IGFueGlvdXM= 38100 -IGFub3M= 38101 -IHdlZWtlbmRz 38102 -IHNleHQ= 38103 -IHJlZHVjZXI= 38104 -PVVURg== 38105 -aGFsZg== 38106 -IFNhdw== 38107 -Lm1t 38108 -IG51ZXZh 38109 -LmN1cnJlbnRUYXJnZXQ= 38110 -Lmx1YQ== 38111 -X0VYVEVOU0lPTg== 38112 -CXJlZw== 38113 -IEN0cmw= 38114 -X2FsaWdu 38115 -YWNjZXB0YWJsZQ== 38116 -IHJ1c2hpbmc= 38117 -ZnJhYw== 38118 -IGJvYXN0cw== 38119 -Rml2ZQ== 38120 -wrE= 38121 -IFRlbXBlcmF0dXJl 38122 -Pik6 38123 -IGNoYXJ0ZXI= 38124 -UkVBVEVE 38125 -IHN1YmplY3RlZA== 38126 -IG9wYw== 38127 -aGVhbHRoeQ== 38128 -5L2/55So 38129 -IFNjaWVudGlmaWM= 38130 -IGZyYXU= 38131 -cmlhZ2Vz 38132 -4LiU 38133 -LmludmVudG9yeQ== 38134 -YXRpb25hbGU= 38135 -TWFk 38136 -bWludXRlcw== 38137 -Pj4oKTsK 38138 -IEVudg== 38139 -IHJlY29yZGluZ3M= 38140 -IHN1c3BpY2lvbg== 38141 -c3FsaXRl 38142 -CXJlYWQ= 38143 -44Gm 38144 -IHdvcnJpZXM= 38145 -LnB1dFN0cmluZw== 38146 -IFNoYW5naGFp 38147 -KHVpZA== 38148 -cmVy 38149 -IHbDrWRl 38150 -Iik6 38151 -IG1ldGhvZG9sb2d5 38152 -INC60L7RgtC+0YA= 38153 -Y2Nj 38154 -YXZhZA== 38155 -IGluZHVjdGlvbg== 38156 -CVRocmVhZA== 38157 -LHN0cmluZw== 38158 -4bqhaQ== 38159 -bmVobWVu 38160 -dWl0aW9u 38161 -ICpfXw== 38162 -LmVtZg== 38163 -IOyc 38164 -L3RoZW1lcw== 38165 -IE5pbmU= 38166 -Lk9uZQ== 38167 -IEVtYmVk 38168 -IGZheg== 38169 -dWF0aW9ucw== 38170 -IHByaXZhdGVseQ== 38171 -IGxpbmc= 38172 -W0Y= 38173 -dXNoaQ== 38174 -IGxhdW5jaGVz 38175 -KEtFWQ== 38176 -R01U 38177 -IGFpbWluZw== 38178 -cGF0aWJsZQ== 38179 -IEJpZGVu 38180 -aXc= 38181 -IERlZ3JlZQ== 38182 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 38183 -ICQoJzw= 38184 -w6FyaW9z 38185 -dG9VcHBlckNhc2U= 38186 -7KCc 38187 -IEVVUg== 38188 -IG92ZXJzaWdodA== 38189 -IHRhYmxlc3A= 38190 -VXBkYXRlcw== 38191 -Lm1ha2VkaXJz 38192 -IGh1bWlkaXR5 38193 -L3RlbXBsYXRl 38194 -QWx3YXlz 38195 -KElT 38196 -X2NlcnQ= 38197 -RGln 38198 -IHVuZGVyd2F5 38199 -b3J0b24= 38200 -IEh1cnJpY2FuZQ== 38201 -IHNwZW5kcw== 38202 -IFNlZ21lbnQ= 38203 -IGZsaWVz 38204 -IFRvZ2dsZQ== 38205 -IEx5bmNo 38206 -IHNlbnNlcw== 38207 -IEtvcw== 38208 -c2V0RW5hYmxlZA== 38209 -aXN0aWNhbGx5 38210 -IHRlc3Rlcg== 38211 -IGFkbWluaXN0cmF0b3Jz 38212 -IHRhZ2dlZA== 38213 -0JM= 38214 -IHNob3J0Y3V0 38215 -IFJlc29sdXRpb24= 38216 -IHN1cGVydmlzaW9u 38217 -IEFzaGxleQ== 38218 -VHJhY2tpbmc= 38219 -dWxhdG9yeQ== 38220 -YW5kZWw= 38221 -aXN0ZW4= 38222 -IHVucmU= 38223 -KGRpZmY= 38224 -QU5UUw== 38225 -IHJpZGVy 38226 -IHPEhQ== 38227 -LlNlcmllcw== 38228 -X29yZGVycw== 38229 -T1JJWk9OVEFM 38230 -IHJldGVudGlvbg== 38231 -44CCPC8= 38232 -LlRlc3Rz 38233 -U3lu 38234 -LnBhcnNlRG91Ymxl 38235 -a29kZQ== 38236 -emVudA== 38237 -R2VuZXJhdGlvbg== 38238 -IGFkbWl0cw== 38239 -IExlYWs= 38240 -IGFrYQ== 38241 -Uk9XUw== 38242 -IEFuZ2VsYQ== 38243 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 38244 -IG5vb24= 38245 -IHN0YXJr 38246 -IGRyYWdnZWQ= 38247 -44O844I= 38248 -IHJlY3ljbGVyVmlldw== 38249 -IFNpbGljb24= 38250 -X3N1ZmZpeA== 38251 -Sm9u 38252 -Y29jaw== 38253 -IFByb2JhYmx5 38254 -SW50cm9kdWN0aW9u 38255 -IFRlcnJvcg== 38256 -KFRoaXM= 38257 -IEJhc2ViYWxs 38258 -IGplbnRlcg== 38259 -Y2hlc3RyYQ== 38260 -Lm5hbg== 38261 -PWc= 38262 -IGNsYXJpZnk= 38263 -eWlp 38264 -cm9vdHM= 38265 -IG5vdGVib29r 38266 -IEV4Y2VwdA== 38267 -IHJpc2Vz 38268 -IEJydXNzZWxz 38269 -YXRvcmllcw== 38270 -LlVTRVI= 38271 -cm9zc292ZXI= 38272 -L3VwbG9hZA== 38273 -IEV2ZW50dWFsbHk= 38274 -Q29uc2lkZXI= 38275 -IEJvdW5k 38276 -LmlkZW50aWZpZXI= 38277 -KHVuaXR0ZXN0 38278 -IGluZmVyaW9y 38279 -IGNyYw== 38280 -IGF1dGlzbQ== 38281 -VUlBbGVydA== 38282 -IEthdmFuYXVnaA== 38283 -aW5lbWVudA== 38284 -cXVldWVSZXVzYWJsZQ== 38285 -U2tpbg== 38286 -LmJhY2tlbmQ= 38287 -LmdldFN0YXRl 38288 -dW5kaW5n 38289 -IHN1YmNsYXNz 38290 -IHJlZmluZWQ= 38291 -IGFubm95 38292 -IHJuZA== 38293 -RGlyZWN0b3I= 38294 -IOuC 38295 -YmVjY2E= 38296 -bW9uZ29kYg== 38297 -IENvbW1vbndlYWx0aA== 38298 -QXo= 38299 -IFRoaW5n 38300 -IHJlY29t 38301 -dW5pbmc= 38302 -CWNvbg== 38303 -CSAgICAK 38304 -ZW1pY3M= 38305 -ZWNk 38306 -IGhvcm55 38307 -QVRSSVg= 38308 -IG1pc2xlYWRpbmc= 38309 -IEJldw== 38310 -L25vZGU= 38311 -Y3N0ZGlv 38312 -4Lin 38313 -IGFkZGl0aW9ucw== 38314 -cmly 38315 -X3JlcXVlc3Rz 38316 -IHJlY2hlcmNoZQ== 38317 -c3R1ZGVudHM= 38318 -X3Bvc2l0aW9ucw== 38319 -ZXJ0ZXh0 38320 -IEV2b2x1dGlvbg== 38321 -YW5kZXo= 38322 -IGRpc3R1cmI= 38323 -a2V5dXA= 38324 -IEJ1dGxlcg== 38325 -LnJlYWRsaW5lcw== 38326 -X3N0ZGlv 38327 -IGJlZQ== 38328 -IEFyY2hpdmVz 38329 -IG5ldmVydGhlbGVzcw== 38330 -VVJJVFk= 38331 -IGRyb25lcw== 38332 -dXJpdGllcw== 38333 -IOKYhQ== 38334 -Ij4NCg0K 38335 -IGRpYWdvbmFs 38336 -IENhbmNlbGxhdGlvblRva2Vu 38337 -X0ludGVybmFs 38338 -IHJ1aW4= 38339 -LlF0 38340 -b2NyYXRpYw== 38341 -VGVs 38342 -IEFuc3dlcnM= 38343 -bWF0aWM= 38344 -IHhw 38345 -YXRlbQ== 38346 -X2pvYnM= 38347 -X2FueQ== 38348 -IHNlbmlvcnM= 38349 -IGxhbmRtYXJr 38350 -IFFMaXN0 38351 -IG1hbmV1 38352 -b3RpZnk= 38353 -LyI7Cg== 38354 -L3NlcnZlcg== 38355 -IFBoaWxvc29waA== 38356 -dXRlbmFudA== 38357 -KGlv 38358 -aHo= 38359 -IGF1dGhlbnRpY2F0ZWQ= 38360 -ZHY= 38361 -LUNvbXBhdGlibGU= 38362 -T3JpZ2luYWxseQ== 38363 -LGZ1bmN0aW9u 38364 -44CCDQo= 38365 -IFJlcHJlc2VudGF0aXZl 38366 -YXNpbHk= 38367 -aXJjdWl0 38368 -LmR0 38369 -KG1hdGg= 38370 -Lk1hcnNoYWw= 38371 -Wyw= 38372 -IENpdGllcw== 38373 -X3R1cm4= 38374 -fCkK 38375 -IGNhbnRpZGFk 38376 -YWx0ZXI= 38377 -CXVp 38378 -IE5lYnJhc2th 38379 -IHNraXJ0 38380 -LmJn 38381 -U2hhcmVkUHJlZmVyZW5jZXM= 38382 -KHN0eWxl 38383 -IGdyaWVm 38384 -Z2V3 38385 -IHNhZmVn 38386 -b2xhbmc= 38387 -X2xpc3Rz 38388 -7Js= 38389 -IGdyYW5pdGU= 38390 -IGhvdHRlc3Q= 38391 -LmpkYmM= 38392 -LkN1c3RvbWVy 38393 -IOKJpA== 38394 -IHdhYXI= 38395 -X3NjZW5l 38396 -Kycv 38397 -IEpUZXh0RmllbGQ= 38398 -IHNlYXRpbmc= 38399 -IHdlYXJz 38400 -IGAv 38401 -Q2FzZXM= 38402 -IFlvdXR1YmU= 38403 -xLFt 38404 -IGJhbGNvbg== 38405 -LEc= 38406 -TWV0YURhdGE= 38407 -LXByaWNl 38408 -U0NS 38409 -VW5pdHk= 38410 -IHRydW5r 38411 -PXtgJHs= 38412 -IGVhcnRocXVha2U= 38413 -UGFydGlhbA== 38414 -IHN1YnN0 38415 -IGVsaW1pbg== 38416 -PSInLg== 38417 -Ly8qW0A= 38418 -IHN1cGVydmlzb3I= 38419 -dnJvbGV0 38420 -X2FydGljbGU= 38421 -IHBhbmU= 38422 -Ymlv 38423 -IG1vdG9ycw== 38424 -Tk0= 38425 -RnJhbms= 38426 -IG9uaW9u 38427 -LXdvcmQ= 38428 -SXRlbUNsaWNrTGlzdGVuZXI= 38429 -IGJyaXQ= 38430 -ZW5kZW5jaWVz 38431 -Q29tcHV0ZXI= 38432 -X3J1bm5pbmc= 38433 -KGRheQ== 38434 -LWhl 38435 -KG5hbWVk 38436 -IFNhY2g= 38437 -0L7Rhw== 38438 -Y2FtcGFpZ24= 38439 -LkFic3RyYWN0 38440 -KHdyYXBwZXI= 38441 -LnBheQ== 38442 -IHV3 38443 -R2Vv 38444 -cmFpbHM= 38445 -L3NlbGVjdA== 38446 -aWNodGU= 38447 -c29ucw== 38448 -RVZFTlQ= 38449 -IGFsaW1lbnQ= 38450 -UHJvdmlkZXJz 38451 -QXdhaXQ= 38452 -X0lOVEVSVkFM 38453 -Lm9mZg== 38454 -IGdsdXRlbg== 38455 -X2Nsb3Vk 38456 -IHdlbg== 38457 -LmV4dHJhY3Q= 38458 -CWJ1dHRvbg== 38459 -L01N 38460 -UGFydHk= 38461 -IGRlbW9ncmFwaGlj 38462 -X2Vycm5v 38463 -IGhpa2luZw== 38464 -KCcnKQo= 38465 -IixAIg== 38466 -IHdpdA== 38467 -csOh 38468 -b2xvZ2ll 38469 -IFN0eWxlcw== 38470 -IEJyb3dzZXJNb2R1bGU= 38471 -LlJlcXVlc3RNYXBwaW5n 38472 -aWNhbnM= 38473 -UEFHRQ== 38474 -Y3JlYXRpb24= 38475 -IEZlcmd1c29u 38476 -dWRlZA== 38477 -bnVtYmVycw== 38478 -IEdUSw== 38479 -IHByZXNlbnRhdGlvbnM= 38480 -IEJvYmJ5 38481 -X3NwYW4= 38482 -ZXN0eWxl 38483 -IGlsbGVnYWxseQ== 38484 -YWJlbGE= 38485 -IGJhdHRsZWZpZWxk 38486 -Y2FwYWNpdHk= 38487 -dGVycm9y 38488 -XSIpOwo= 38489 -IHdhcnJpb3I= 38490 -bGVhZGVy 38491 -IERCRw== 38492 -IFJldmVudWU= 38493 -IHZpZ2ls 38494 -IGNvdW50ZXJwYXJ0cw== 38495 -KEVycm9y 38496 -QUNURVI= 38497 -IGhlZWZ0 38498 -IHNlbGVjdGlvbnM= 38499 -emV1Zw== 38500 -dG9t 38501 -LXR3bw== 38502 -LjsK 38503 -X3N0YXRlbWVudA== 38504 -IEFpZA== 38505 -IFZ1bA== 38506 -X3JnYg== 38507 -IHByaXplcw== 38508 -IGVkaXRhYmxl 38509 -CWZvcm0= 38510 -xLFuxLE= 38511 -LmRlY29y 38512 -RGVtbw== 38513 -bGljZXM= 38514 -IGVuY3R5cGU= 38515 -cmF0dWxhdGlvbnM= 38516 -IFJPUw== 38517 -X2NoYXJz 38518 -IEphaHI= 38519 -cGFydGlhbA== 38520 -0YPRgg== 38521 -IFJlY2VpdmU= 38522 -IExhbmRz 38523 -QVBURVI= 38524 -IGNob3BwZWQ= 38525 -Li4i 38526 -IEFuYWx5 38527 -IFVJRA== 38528 -IFJhZGVvbg== 38529 -IEJlZQ== 38530 -IHVubQ== 38531 -Pk0= 38532 -LmZpbmRhbGw= 38533 -VG9rZW5pemVy 38534 -IFdIQVQ= 38535 -IHNq 38536 -RHJhd2luZw== 38537 -RXNz 38538 -T05E 38539 -irY= 38540 -KHBhY2tldA== 38541 -4oCUYnV0 38542 -SW52b2NhdGlvbg== 38543 -IE51Y2xlYXI= 38544 -PzsK 38545 -IGdyYW5kZXM= 38546 -IENyeXB0 38547 -cmVtYXJr 38548 -ICcuLi8uLi8uLi8uLi8= 38549 -IGluYWJpbGl0eQ== 38550 -bWFnaWM= 38551 -Y2F0cw== 38552 -IHNpbXVsYXRl 38553 -OiR7 38554 -aW5mbGF0ZQ== 38555 -IGVuZXI= 38556 -Ok5P 38557 -aXBsZXM= 38558 -IG1lcml0 38559 -IFJhdGVk 38560 -IGdsdWU= 38561 -L2Jsb2c= 38562 -IGdyZW4= 38563 -IHRocmlsbGVk 38564 -LkNI 38565 -dW5jYW4= 38566 -IFBSSU1BUlk= 38567 -IHBlcnNlYw== 38568 -IGZlYXJlZA== 38569 -Lk1JTg== 38570 -IFRoZWF0ZXI= 38571 -6ZI= 38572 -YXRlZ29yaWU= 38573 -5q61 38574 -IGFwcGV0aXRl 38575 -c3F1YXJl 38576 -IEFsZXhhbmQ= 38577 -LlVzZXJJZA== 38578 -X2d0 38579 -X2VudGVy 38580 -IGdyYWR1YXRlcw== 38581 -RnJhZ21lbnRNYW5hZ2Vy 38582 -QXV0aG9yaXpl 38583 -LU5MUw== 38584 -KE15 38585 -IHRyaXVtcGg= 38586 -dXN0aW5n 38587 -X1BBUkFNUw== 38588 -Q2hhcmFjdGVycw== 38589 -KDosOiw= 38590 -X0JVSUxE 38591 -TUh6 38592 -IHdhc2hlZA== 38593 -IHVuY2xl 38594 -U3RldmU= 38595 -YXJkb3du 38596 -PHN0ZGlv 38597 -X3Rlcm1z 38598 -IE1BUg== 38599 -IGhvc2U= 38600 -dWN1cw== 38601 -IENsYWlt 38602 -IFJhbXM= 38603 -IG1vZGVsQnVpbGRlcg== 38604 -IG7DqQ== 38605 -dXNlcklE 38606 -PWpzb24= 38607 -LlJlc3BvbnNlV3JpdGVy 38608 -mOiupA== 38609 -IGdydXBv 38610 -LWl0 38611 -IEtP 38612 -LU1haWw= 38613 -IGNvbmZlcmVuY2Vz 38614 -SUZB 38615 -IEFzc2Fk 38616 -IHByb25vdW5jZWQ= 38617 -IGFuY2VzdG9ycw== 38618 -IFRSQUNF 38619 -IEdlRm9yY2U= 38620 -IHByaXZhdA== 38621 -cGVsbA== 38622 -ZW1vamk= 38623 -INmI 38624 -R2VucmU= 38625 -IGNvbmNlbnRyYXRlZA== 38626 -amFuZw== 38627 -TU9URQ== 38628 -IFpvb20= 38629 -dG9vbGJhcg== 38630 -IHV0dGVybHk= 38631 -IGVuY29tcGFzcw== 38632 -IFNvY2Nlcg== 38633 -IGV1cm9wZQ== 38634 -LWFpcg== 38635 -LmFuaW0= 38636 -X0NUTA== 38637 -aGVyZW50 38638 -cmV4 38639 -aW50ZXJhY3RpdmU= 38640 -44Gn44GZ 38641 -IEthcw== 38642 -IGRlc3BlcmF0ZWx5 38643 -KGFy 38644 -IGJpaw== 38645 -IHRyYXZlcnNl 38646 -ZXVycw== 38647 -UmVjeWNsZXJWaWV3 38648 -IE1hcmdhcmV0 38649 -IGhvcGVmdWw= 38650 -IE1pZw== 38651 -X01FTUJFUg== 38652 -cmVjZWl2ZXI= 38653 -TWF0Y2hlcg== 38654 -ZGVwZW5kZW50 38655 -IGV4Y2VsbGVuY2U= 38656 -0LDQtg== 38657 -TE9T 38658 -QXNwZWN0 38659 -IGFkYWxhaA== 38660 -IEVjb25vbXk= 38661 -dWxvdXNseQ== 38662 -IGV2YWx1YXRpbmc= 38663 -IGRldmlhdGlvbg== 38664 -ZXh0ZXI= 38665 -L2RhdA== 38666 -Q29scw== 38667 -IFBva2Vy 38668 -Ym9hcmRpbmc= 38669 -LkNoaWxkcmVu 38670 -QU5HTEU= 38671 -w68= 38672 -IFlvZ2E= 38673 -IGhhdGVk 38674 -QWRhbQ== 38675 -IEZDQw== 38676 -SU1BTA== 38677 -IGZhaW50 38678 -X0RJU1BMQVk= 38679 -IGV2b2x2ZQ== 38680 -IGZyaWRnZQ== 38681 -IHLDqWc= 38682 -IGVtb3Rpb25hbGx5 38683 -4oCcSWY= 38684 -YXdlaQ== 38685 -ZXJlc2E= 38686 -Jywi 38687 -QkVHSU4= 38688 -IFZBUkNIQVI= 38689 -IHhp 38690 -ZmFjdG9y 38691 -dHo= 38692 -X3BoYXNl 38693 -U0VR 38694 -KHJhbmQ= 38695 -IG1hdGhlbWF0aWNz 38696 -IGNvbnRleHRz 38697 -LWFj 38698 -IEZJRw== 38699 -IENhcHRpb24= 38700 -IFdhaXRGb3I= 38701 -LXdlc3Q= 38702 -IGZpcmVmaWdodA== 38703 -X0xFRA== 38704 -ZWN0aW9ucw== 38705 -CXRocm93cw== 38706 -IFRha2Vz 38707 -b2JyZQ== 38708 -IEF2YXRhcg== 38709 -IElubm92YXRpb24= 38710 -IGNhbGlicmF0aW9u 38711 -OnRoaXM= 38712 -X2VuY29kaW5n 38713 -IGNhbGN1bGF0aW5n 38714 -ICMjIyMjIyMjIyMjIyMjIyM= 38715 -IFByb2dyYW1z 38716 -IEhJR0g= 38717 -LmNvbmZpZ3VyZVRlc3RpbmdNb2R1bGU= 38718 -UG9seWdvbg== 38719 -X0RCRw== 38720 -Il0sDQo= 38721 -0LDQsQ== 38722 -IHNpbWlsYXJpdHk= 38723 -IHByemV6 38724 -IEZpcm0= 38725 -IG1pc3VuZGVy 38726 -IE1vdmluZw== 38727 -IE1PVg== 38728 -IHJlYWN0b3I= 38729 -UmVxdWVzdGVk 38730 -ZXhwZWN0cw== 38731 -IGVyZWN0 38732 -bGljaHQ= 38733 -b3VsZGVy 38734 -SURHRVQ= 38735 -IGRldmls 38736 -IHByb2dyYW1tZXM= 38737 -IENvbW1vbk1vZHVsZQ== 38738 -ICInIg== 38739 -KEF1dGg= 38740 -44CC77yM 38741 -IFN0YXRlZnVsV2lkZ2V0 38742 -6K6h 38743 -L29wZW4= 38744 -aW5hbGx5 38745 -LlJvdW5k 38746 -IFdpc2g= 38747 -IGh1bWFuaXRhcmlhbg== 38748 -QWNjZXNzVG9rZW4= 38749 -IFNPQw== 38750 -IHBva2Vtb24= 38751 -IHZhcG9y 38752 -X2FkZGVk 38753 -CUdldA== 38754 -c3BlbGw= 38755 -IEluaXRpYXRpdmU= 38756 -IEhFTA== 38757 -YWlycm8= 38758 -YmxlZA== 38759 -INCx0Ys= 38760 -IHNlbnNpYmxl 38761 -IEx1YQ== 38762 -fCgK 38763 -IGZpeHR1cmVz 38764 -IG9yZ2FzbQ== 38765 -Q3V0 38766 -dWt0 38767 -Z3Vl 38768 -IGNyZWRpYmlsaXR5 38769 -OmltYWdl 38770 -IENQUA== 38771 -LnNu 38772 -KGRlc2M= 38773 -IFJlaWQ= 38774 -LWRlZ3JlZQ== 38775 -X3NvdW5k 38776 -Q2xvbmU= 38777 -4buZ 38778 -YWtzaQ== 38779 -PiR7 38780 -X2NvbmZpcm1hdGlvbg== 38781 -IHRyb3BoeQ== 38782 -V29ya3M= 38783 -IEVsZWN0cm9uaWNz 38784 -IE1lZGl0ZXJyYW5lYW4= 38785 -X21ldHJpY3M= 38786 -IGFubm91bmNpbmc= 38787 -IERBWQ== 38788 -X3Byb3Rv 38789 -IHBlYXI= 38790 -YmFzZVVybA== 38791 -CQkJCQkJCQkK 38792 -IGNvb3JkaW5hdGlvbg== 38793 -Ok4= 38794 -LmFuaW1hdGU= 38795 -IENvdHRvbg== 38796 -X2hpdA== 38797 -4pw= 38798 -IGpldHp0 38799 -aWZ0ZXI= 38800 -KGZpZWxkcw== 38801 -b3dubG9hZA== 38802 -aWZpY2FjaW9u 38803 -LmN1ZGE= 38804 -IExpdQ== 38805 -PmVxdWFscw== 38806 -IEFjZQ== 38807 -0YDQsNC8 38808 -IFN1cGVybWFu 38809 -IEdhcmNpYQ== 38810 -IGFycmVzdHM= 38811 -YWdhcg== 38812 -IHt9KQ== 38813 -IG1hY3Jvcw== 38814 -cm91cGU= 38815 -w6p0cmU= 38816 -IHR3aXN0ZWQ= 38817 -c3RydW1lbnRz 38818 -Xygi 38819 -X3ZlcnRpY2Vz 38820 -IFRyYW5zaXRpb24= 38821 -0LjQug== 38822 -W21heA== 38823 -bWluZA== 38824 -IGFjY2Vzc1Rva2Vu 38825 -IHVubGU= 38826 -bXVz 38827 -Y29w 38828 -IEZhY3Rvcg== 38829 -IGNvbmNlZA== 38830 -IHJldHI= 38831 -LmxpbmFsZw== 38832 -LXNsaWRlcg== 38833 -b2Js 38834 -X1N0YXRpY0ZpZWxkcw== 38835 -IHpvbWJpZQ== 38836 -c2VsbGluZw== 38837 -IGNoYXA= 38838 -IHNoYWtpbmc= 38839 -IFRyYW5zbGF0ZQ== 38840 -IEFtc3RlcmRhbQ== 38841 -IEVUSA== 38842 -X0VYVEVSTg== 38843 -a2Q= 38844 -X2Rpc2M= 38845 -IHByZWNlZGluZw== 38846 -IHByaXg= 38847 -T2JqZWN0TmFtZQ== 38848 -X21vZGlmaWVk 38849 -YXJkd2FyZQ== 38850 -ID8+Ij4= 38851 -IERX 38852 -YCR7 38853 -ID8+Ij48Pw== 38854 -dXllbg== 38855 -IGRvbm5h 38856 -IHhzaQ== 38857 -ICQiew== 38858 -IERyYXdpbmc= 38859 -LG5pbA== 38860 -IG9uZGVy 38861 -Qkc= 38862 -T2JzZXJ2 38863 -IGNvbnNpZGVyYXRpb25z 38864 -Ym9hdA== 38865 -IEJhbmtz 38866 -IGluZGljdA== 38867 -LEk= 38868 -IEJsdQ== 38869 -KHZlcnNpb24= 38870 -Y2xpZW50ZQ== 38871 -b2xhbg== 38872 -TEVTUw== 38873 -YXNzZXJ0U2FtZQ== 38874 -X3ZvaWQ= 38875 -IFdBUw== 38876 -CWVudW0= 38877 -IG1peGVy 38878 -RVc= 38879 -YWZmZQ== 38880 -IGJsb3dqb2I= 38881 -dGV4dEZpZWxk 38882 -IGltbWVuc2U= 38883 -X3JlcG8= 38884 -IGdsb2JhbHM= 38885 -YW50YWdlcw== 38886 -LnRvZGF5 38887 -VGh1cnNkYXk= 38888 -IEJyaWc= 38889 -e30pCg== 38890 -IEltYWdpbmU= 38891 -KEdQSU8= 38892 -IGVzdG8= 38893 -IFByb3ZpbmNl 38894 -IE1lbnRhbA== 38895 -X2NlbGxz 38896 -IEp1bGlhbg== 38897 -LlNjcmVlbg== 38898 -IGNhbmRsZQ== 38899 -IG1vbmRl 38900 -IHZlcmc= 38901 -aXRlcmFscw== 38902 -LWxheW91dA== 38903 -R3Vlc3Q= 38904 -IHZpbmQ= 38905 -IEVjaG8= 38906 -Jyl9 38907 -IG1hbm4= 38908 -X0JPT0xFQU4= 38909 -aGFw 38910 -IG5pZ2h0bWFyZQ== 38911 -VUdI 38912 -IG5vbmV0aGVsZXNz 38913 -IGF0aGU= 38914 -IEhvbGxhbmQ= 38915 -IEJvcm4= 38916 -XE9STQ== 38917 -YW51dA== 38918 -X2xldmVscw== 38919 -IHBldGl0ZQ== 38920 -LWFydA== 38921 -X1NIT1c= 38922 -bnVtYmVyT2Y= 38923 -X3RodW1ibmFpbA== 38924 -YW1pbnM= 38925 -IERlZmluZXM= 38926 -ICI9 38927 -LlN0YXR1c0NvZGU= 38928 -IGRpZ25pdHk= 38929 -IEJpa2U= 38930 -Lk5ld0xpbmU= 38931 -IEdsYXM= 38932 -KGxvZ2dlcg== 38933 -IGNhdGNoZXM= 38934 -dm90ZXM= 38935 -IGV4YW1pbmluZw== 38936 -L3JlZ2lzdGVy 38937 -IHNwZWNpZnlpbmc= 38938 -X2ZpeGVk 38939 -IGRyYXdpbmdz 38940 -VGhyZXNob2xk 38941 -QXg= 38942 -IEFyY2hpdGVjdHVyZQ== 38943 -KHBpZA== 38944 -V2lyZQ== 38945 -KGNvbnQ= 38946 -bGFuZQ== 38947 -TGlzdHM= 38948 -IHNwcmludA== 38949 -IGdyYW5kZmF0aGVy 38950 -X0FH 38951 -IHNjaGVkdWxpbmc= 38952 -Q0xVUw== 38953 -YXR1cml0eQ== 38954 -IGxvY2tpbmc= 38955 -W3NpemU= 38956 -X3N0eWxlcw== 38957 -IHdi 38958 -LS0+Cgo= 38959 -IHNwaW5uaW5n 38960 -X3BlbmRpbmc= 38961 -TWF0Y2hlcnM= 38962 -LktleXM= 38963 -IFBW 38964 -ZW51cw== 38965 -YW50aXM= 38966 -IGRpc2NhcmQ= 38967 -IGhhdWw= 38968 -IGVtcGly 38969 -IHBhdGh3YXk= 38970 -IG9haw== 38971 -0LzQtdC9 38972 -LWluZHVjZWQ= 38973 -IGltcGFpcg== 38974 -IENhbGdhcnk= 38975 -LmlzSGlkZGVu 38976 -ZHo= 38977 -X2luY2x1ZGU= 38978 -IGdt 38979 -ICcoJw== 38980 -UFk= 38981 -dWdnZXN0aW9ucw== 38982 -IGNvbW1vZGl0eQ== 38983 -Y3Jv 38984 -L3N1Yg== 38985 -IGdldEluc3RhbmNl 38986 -IExlZ2FjeQ== 38987 -IEtpbA== 38988 -QmFs 38989 -KHNob3J0 38990 -SW5mb3Jt 38991 -K3g= 38992 -KnI= 38993 -IEhvcGVmdWxseQ== 38994 -b3JhdGU= 38995 -IG1hY2hlbg== 38996 -IHRyZWF0eQ== 38997 -IE9yaQ== 38998 -LnB1YmxpYw== 38999 -LWhvcml6b250YWw= 39000 -IHRhY3RpYw== 39001 -IGJvcmQ= 39002 -d2FyZXM= 39003 -IGFtbW8= 39004 -IExpc3Rz 39005 -IGVxdWF0aW9ucw== 39006 -L2hlcg== 39007 -IE5TVw== 39008 -Qm91bmRpbmc= 39009 -X0NvbGxlY3Rpb25z 39010 -IGF2YWls 39011 -LkRyb3BEb3du 39012 -6LA= 39013 -IGho 39014 -IGzDoA== 39015 -LnBi 39016 -IG1lbW9yaWFs 39017 -IEFUVFI= 39018 -IGV4aGF1c3RlZA== 39019 -IHRzcA== 39020 -CXJlZGlyZWN0 39021 -IGxpa2V3aXNl 39022 -U1RFUg== 39023 -TGphdmE= 39024 -IGNvbmRlbW5lZA== 39025 -b2NhdXN0 39026 -KHN0cmljdA== 39027 -IGV4ZW1wdA== 39028 -IHNtcw== 39029 -IGV4YWdnZXI= 39030 -U1lT 39031 -IGxvdW5nZQ== 39032 -Ol4= 39033 -IHRvZGQ= 39034 -ZGVi 39035 -YXRvcmlhbA== 39036 -IFBvcnRlcg== 39037 -IHR1aXRpb24= 39038 -IGV4ZW1wbA== 39039 -IHBhcmVu 39040 -LmxpbmVUbw== 39041 -IGtpZG5leQ== 39042 -IMOnYQ== 39043 -IGN1aQ== 39044 -77yM6K+3 39045 -WEM= 39046 -IG1vxbw= 39047 -IG5vbWluYXRlZA== 39048 -bHVuZw== 39049 -SW1HdWk= 39050 -IEJ1eno= 39051 -IHN0ZXJlbw== 39052 -cG9ydGFs 39053 -cmVzYXM= 39054 -IGtsYXNz 39055 -IGRyYWZ0ZWQ= 39056 -IHByb2plY3RpbGU= 39057 -L2dwbA== 39058 -KHBhcmFtZXRlcnM= 39059 -KikK 39060 -IGFzc2lzdGVk 39061 -IE5TSW50ZWdlcg== 39062 -c2l0ZW1hcA== 39063 -Om50aA== 39064 -LlZpZXdz 39065 -LkFyZ3VtZW50UGFyc2Vy 39066 -IG1lZXI= 39067 -emllcg== 39068 -IERpZw== 39069 -PD89JA== 39070 -X3Blcm1pc3Npb24= 39071 -CUFkZA== 39072 -b2xvZ2lh 39073 -IHNjaQ== 39074 -IGZpbmFuY2lhbGx5 39075 -IHNjcm9sbGluZw== 39076 -LmRpc3Q= 39077 -X0hBUw== 39078 -dWJ1bnR1 39079 -LnBhZ2Vz 39080 -SW5jcmU= 39081 -YnVyc2U= 39082 -IEFtYXRldXI= 39083 -5rqQ 39084 -QmxvYg== 39085 -IGNob2xlc3Rlcm9s 39086 -REVT 39087 -bWluaW11bQ== 39088 -IHJlZnVzaW5n 39089 -dW5uZWQ= 39090 -0Jw= 39091 -IFJE 39092 -LlNlcnZsZXQ= 39093 -ICovOwo= 39094 -dWRkZW4= 39095 -IHZpZXdCb3g= 39096 -IG1ldGFib2xpc20= 39097 -IHN0ZWFsaW5n 39098 -IEJldmVy 39099 -YWduZXRpYw== 39100 -VkVSUklERQ== 39101 -X0FVRElP 39102 -0YDRiw== 39103 -IGFyY2hpdmVz 39104 -LmxpbmVhcg== 39105 -PXs8 39106 -dW5jYXRlZA== 39107 -QWNjZXNzRXhjZXB0aW9u 39108 -IHBpY3R1cmVCb3g= 39109 -CXNlbGVjdA== 39110 -TGF0aXR1ZGU= 39111 -dmlzb3I= 39112 -cmVpYg== 39113 -IHBhaw== 39114 -SG9wZQ== 39115 -IEl0ZXJhYmxl 39116 -LnJlc3BvbnNlVGV4dA== 39117 -IFF1YWQ= 39118 -IEJyb29rcw== 39119 -IFRvdA== 39120 -T1BU 39121 -ZWxvbmc= 39122 -IGNvY2FpbmU= 39123 -IGFubw== 39124 -RGFu 39125 -IHBzaQ== 39126 -0LDQu9GM 39127 -LmdldENoaWxk 39128 -IFJFRg== 39129 -LWFi 39130 -IFRyaWFuZ2xl 39131 -PFRleHQ= 39132 -IENvbG9tYmlh 39133 -aW5reQ== 39134 -6Imy 39135 -KX0+Cg== 39136 -IHBsYWc= 39137 -cGluZQ== 39138 -IGJsYW5rZXQ= 39139 -IDo8Lw== 39140 -IFRyYW5zbGF0aW9u 39141 -bm92 39142 -IHBlcmZlY3Rpb24= 39143 -IENvbmZlZGVy 39144 -LnN0dWI= 39145 -LkludGVyb3BTZXJ2aWNlcw== 39146 -LlN0b3Jl 39147 -IGVucm9sbG1lbnQ= 39148 -IGRlZXI= 39149 -TW92ZW1lbnQ= 39150 -LWZyb20= 39151 -aGM= 39152 -IGV2YW5nZWw= 39153 -IElsbHVzdHI= 39154 -IHRydW1w 39155 -X1N0YXJ0 39156 -cGxhbmVz 39157 -IEJpbA== 39158 -SW5mb3M= 39159 -LXRyYW5z 39160 -IHJhbmNo 39161 -IExpbmRh 39162 -X21hcg== 39163 -UkVU 39164 -L25ldA== 39165 -TGF3 39166 -TkY= 39167 -IFByZXZlbnQ= 39168 -IGNyaWVk 39169 -IGVkdWNhdGU= 39170 -YXN0aWNz 39171 -eWk= 39172 -LkxpbmVhckxheW91dA== 39173 -TUVUSE9E 39174 -IEVn 39175 -bWFwcGVy 39176 -5pmC 39177 -LmFzYXJyYXk= 39178 -z4E= 39179 -acOnw6Nv 39180 -UmV1c2U= 39181 -X3Jldg== 39182 -IFBST0RVQ1Q= 39183 -X0NvZGU= 39184 -ICAgICANCg== 39185 -IFNFUlZJQ0U= 39186 -X2NvdmVy 39187 -LiwK 39188 -LkV4ZWN1dGVSZWFkZXI= 39189 -IERpbmluZw== 39190 -LmFyY2g= 39191 -IG90cm8= 39192 -IERpc2NvdmVyeQ== 39193 -IEtleUVycm9y 39194 -IEJlbmVmaXRz 39195 -X1NIQQ== 39196 -LlVubWFyc2hhbA== 39197 -SEVBREVS 39198 -TXV0ZXg= 39199 -QU1B 39200 -IGluaXRpYXRl 39201 -U3RheQ== 39202 -TGl0dGxl 39203 -ICgpLA== 39204 -IGRlY2VudHJhbA== 39205 -UmVzb2x1dGlvbg== 39206 -LmhlYWx0aA== 39207 -CWZjbG9zZQ== 39208 -5Lqk 39209 -IHN0YWtlaG9sZGVycw== 39210 -IGFyY2hhZQ== 39211 -RGlnaXRhbA== 39212 -bGVzY29wZQ== 39213 -X3Blbg== 39214 -IEl0ZW1TdGFjaw== 39215 -IENhbm9u 39216 -IEtlbmQ= 39217 -IMO4 39218 -X2FqYXg= 39219 -aW5ncmVkaWVudHM= 39220 -RGVsaXZlcnk= 39221 -U2VjdGlvbnM= 39222 -IGRpc2FwcG9pbnRpbmc= 39223 -IEdyZW4= 39224 -LHJl 39225 -IGRlY3J5cHQ= 39226 -b2xvZ2lj 39227 -X2ZtdA== 39228 -IFNsaWRlcg== 39229 -bmFo 39230 -V2FzaGluZ3Rvbg== 39231 -enVuZw== 39232 -INGG 39233 -eWN6 39234 -aWV2ZXM= 39235 -LkRFQlVH 39236 -IFRJ 39237 -IGhhY2tpbmc= 39238 -IGNlbnRy 39239 -Zmxvd3M= 39240 -IGRpZFJlY2VpdmVNZW1vcnlXYXJuaW5n 39241 -IGFjY291bnRhYmlsaXR5 39242 -Q09VTlQ= 39243 -0LvQtdC80LXQvdGC 39244 -Ymxv 39245 -L2lk 39246 -IFNsb3c= 39247 -aXp6YXJk 39248 -LnJlbW92ZUV2ZW50TGlzdGVuZXI= 39249 -IOyehQ== 39250 -L0k= 39251 -aXNtYQ== 39252 -IEh1ZHNvbg== 39253 -fX0s 39254 -dW1lZA== 39255 -IHJlYWxpc2U= 39256 -dW5zYWZl 39257 -IHp1cw== 39258 -IHNob3J0YWdl 39259 -b2xpYQ== 39260 -X3ByaW9yaXR5 39261 -IGZsb29kaW5n 39262 -b3BlcmF0aW9ucw== 39263 -UG9seQ== 39264 -YWJhbg== 39265 -W2N1cg== 39266 -IGVza29ydGU= 39267 -X0RFU0NSSVBUSU9O 39268 -X25hdA== 39269 -IG1hbGljaW91cw== 39270 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 39271 -IFBhcmtz 39272 -IHRheHBheWVy 39273 -IEZvc3Rlcg== 39274 -IHNleHVhbGl0eQ== 39275 -57O7 39276 -67A= 39277 -XA0K 39278 -LnNlZWs= 39279 -0LDQvdC40Y8= 39280 -L2FydGljbGU= 39281 -6L+H 39282 -IFVocg== 39283 -IGdyYW5kbW90aGVy 39284 -IEJsZQ== 39285 -ZnVydA== 39286 -YW1iYWg= 39287 -bm90aWZpY2F0aW9ucw== 39288 -ZGVwcmVjYXRlZA== 39289 -IHVpbnRwdHI= 39290 -b2tp 39291 -KEFycmF5 39292 -IGF1dG9ub21vdXM= 39293 -IG9icg== 39294 -wq/Crw== 39295 -IGJhc2VuYW1l 39296 -IHVudmVpbGVk 39297 -c29s 39298 -IE5vdEltcGxlbWVudGVkRXJyb3I= 39299 -IGRlcHJlc3M= 39300 -XycuJA== 39301 -IFVOSVQ= 39302 -JScs 39303 -LXRhZw== 39304 -Z3JlcA== 39305 -IE1haW50ZW5hbmNl 39306 -IHdhcmZhcmU= 39307 -X1JFU09VUkNF 39308 -KHNwZWM= 39309 -KGN2 39310 -IG5hZGE= 39311 -55S1 39312 -IGNyb3dkZWQ= 39313 -QmVsb3c= 39314 -IFphY2g= 39315 -RXN0YWRv 39316 -X3ByaW1l 39317 -IHRyYWJham8= 39318 -IGluZm9ybWF0aXZl 39319 -U2NvdHQ= 39320 -IHNlcmlhbGl6ZXJz 39321 -IE5hcw== 39322 -VGh1bms= 39323 -IG1lcmN5 39324 -LC4uLgoK 39325 -IGFkZGljdA== 39326 -LmNvbnN0YW50cw== 39327 -IGRhdGFmcmFtZQ== 39328 -X3JlYXNvbg== 39329 -Z29tZXJ5 39330 -7Iq164uI64uk 39331 -IG5lZ2xlY3Q= 39332 -IExpbmVz 39333 -IG1lbWI= 39334 -X0VYRUM= 39335 -YXNzYWdl 39336 -IFlhcmQ= 39337 -e30nLg== 39338 -IGxvdHRlcnk= 39339 -dGVpbg== 39340 -X2NhbGM= 39341 -aWt1 39342 -X1JFQ09SRA== 39343 -V2Fybg== 39344 -IGhlYWx0aGllcg== 39345 -dXJlbWVudA== 39346 -IHlhcm4= 39347 -IENvcm5lcg== 39348 -KHppcA== 39349 -KGluaXQ= 39350 -IExpdA== 39351 -SFc= 39352 -c3Vic2V0 39353 -IE1G 39354 -RVRFUlM= 39355 -X3JvdA== 39356 -IGVyZQ== 39357 -IE92ZXJyaWRl 39358 -V2FsbGV0 39359 -X3Jld2FyZA== 39360 -IHNhZ2U= 39361 -c2V0VmlzaWJsZQ== 39362 -IEpzb25SZXNwb25zZQ== 39363 -SUNZ 39364 -6K+i 39365 -VmFyQ2hhcg== 39366 -YWF0 39367 -LWdyZWVu 39368 -IGlycQ== 39369 -YW5pdHk= 39370 -IHdob2V2ZXI= 39371 -X3NoYXJl 39372 -IGZvdXQ= 39373 -cm9sbHM= 39374 -IHdpbGxpbmduZXNz 39375 -LmNvbXBvbmVudEluc3RhbmNl 39376 -IGhvbm9yZWQ= 39377 -dXJ2ZXk= 39378 -QmVy 39379 -IHJ1bm5lcnM= 39380 -IGxpZXU= 39381 -b3Jwb3I= 39382 -X3N0cnVjdHVyZQ== 39383 -QmFyQnV0dG9uSXRlbQ== 39384 -YWR4 39385 -IEJlbm5ldHQ= 39386 -IGRpbGln 39387 -IGZsdWN0 39388 -SURERU4= 39389 -X1NlbGVjdGVk 39390 -KGRpdg== 39391 -IHF1aWNrZXI= 39392 -YWxvbmc= 39393 -Z3JhcGhxbA== 39394 -aW5leg== 39395 -IGNpdGU= 39396 -IEluc3RydWN0aW9ucw== 39397 -IGluc2VydGluZw== 39398 -LmNsb3VkZmxhcmU= 39399 -Y291cG9u 39400 -ZWRMaXN0 39401 -IFN0b3Jlcw== 39402 -X21hbGxvYw== 39403 -56ym 39404 -IEF3ZXNvbWU= 39405 -IGxhbWI= 39406 -UkVTVA== 39407 -IGludGVzdA== 39408 -IE5hdmJhcg== 39409 -LmZlYXR1cmVz 39410 -SW5jcmVtZW50 39411 -IFBvbQ== 39412 -IGluc3VmZmljaWVudA== 39413 -X0xPR0lO 39414 -UExFTUVOVA== 39415 -IE9BdXRo 39416 -LklORk8= 39417 -IGV4b3RpYw== 39418 -IENBU0U= 39419 -CSAgCg== 39420 -IEdhbmQ= 39421 -dGhlc2Vz 39422 -IG5vdm8= 39423 -IERlbGw= 39424 -4oCm4oCm4oCm4oCm 39425 -X3NvZnQ= 39426 -IGFncmVlaW5n 39427 -Y2VudHM= 39428 -bG9hbg== 39429 -JyIsCg== 39430 -IFJhbg== 39431 -REVM 39432 -IG9yZ2FuaXNlZA== 39433 -K24= 39434 -IEhlYWx0aGNhcmU= 39435 -IGRldGVyaW9y 39436 -IGltcGxlbWVudGF0aW9ucw== 39437 -IGNhcm4= 39438 -ICwn 39439 -IExPQUQ= 39440 -IHBsYW50ZWQ= 39441 -5pyq 39442 -Rm9ybUNvbnRyb2w= 39443 -X21hdGNoZXM= 39444 -IHBlcmlvZGlj 39445 -X1Rv 39446 -IEpvZWw= 39447 -IGFua2xl 39448 -IG1pbGl0YW50cw== 39449 -IFdpdGNo 39450 -dW5pZm9ybQ== 39451 -dWVudGE= 39452 -T2ZXZWVr 39453 -IHBlcnBldHI= 39454 -IGludGVydmVudGlvbnM= 39455 -KHdyaXRlcg== 39456 -YW50aW5l 39457 -UHJvZ3Jlc3NCYXI= 39458 -IGxlYWd1ZXM= 39459 -Y29tcHJlc3M= 39460 -aXppb25l 39461 -IEVB 39462 -Il09Ig== 39463 -IFN0ZXBoYW4= 39464 -bWludXM= 39465 -c3N0cmVhbQ== 39466 -X2xlZA== 39467 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0= 39468 -IldoZW4= 39469 -QWxyZWFkeQ== 39470 -IGNvbnRlbXBs 39471 -IGF0YXU= 39472 -IENvbmdyZXNzaW9uYWw= 39473 -IHJhcHBvcnQ= 39474 -IEJvdXI= 39475 -aXNoaQ== 39476 -IHR5bQ== 39477 -IEFybWVu 39478 -INGA0LDQtw== 39479 -LWZvcm1hdA== 39480 -X1JlYWQ= 39481 -KGNvbHVtbnM= 39482 -IG5ldWU= 39483 -X2JveGVz 39484 -IFNhbmR5 39485 -XywK 39486 -IFdpemFyZA== 39487 -IG9yZGVu 39488 -IGZpbGVzeXN0ZW0= 39489 -ZmxpZ2h0 39490 -IHdzeg== 39491 -YW5jZWxlZA== 39492 -IGRhd24= 39493 -IEdzb24= 39494 -X3dhcm5pbmc= 39495 -IEljZWxhbmQ= 39496 -IHNsdXQ= 39497 -IHNldElz 39498 -X2lkZW50 39499 -IG9mZnNob3Jl 39500 -IFNrZXRjaA== 39501 -OyU= 39502 -IHRyaWJlcw== 39503 -X1NQQUNF 39504 -IG90cm9z 39505 -Q29tcGlsZXI= 39506 -CUVuZA== 39507 -IF0pLAo= 39508 -R3Jhdml0eQ== 39509 -IHRlbnNpb25z 39510 -IHNtb290aGx5 39511 -S25vdw== 39512 -b290aGluZw== 39513 -IFN0YXJ0dXA= 39514 -IEh5cA== 39515 -IGFtYXpvbg== 39516 -IFJlY2VpdmVk 39517 -emVuaWU= 39518 -654= 39519 -IENob2NvbGF0ZQ== 39520 -IMSw 39521 -Ik5v 39522 -IEFMUw== 39523 -IFByb2dyYW1taW5n 39524 -IERvZ3M= 39525 -IGdvb2RuZXNz 39526 -KGVycm5v 39527 -L2Vz 39528 -IHJlbW90ZWx5 39529 -IEhvb2tz 39530 -VXVpZA== 39531 -IG92ZXJseQ== 39532 -IOWQ 39533 -IGdwdQ== 39534 -IHN0aW11bHVz 39535 -KHN0ZXA= 39536 -LllvdQ== 39537 -IGJpb20= 39538 -SU5D 39539 -LmJpdHM= 39540 -KG1Db250ZXh0 39541 -IGFtZXJpY2Fu 39542 -IHRlcnJpdG9yaWVz 39543 -IE5E 39544 -XSIK 39545 -IE1hcHBpbmc= 39546 -IHByb2NlZWRpbmc= 39547 -LmF4 39548 -IHN1YnN0cmluZw== 39549 -QlVUVE9O 39550 -IEln 39551 -LXBhbmU= 39552 -IEFucw== 39553 -IGdyYWR1YXRpb24= 39554 -IHBlcnNwZWN0aXZlcw== 39555 -TWl4aW4= 39556 -X21pbnVz 39557 -CQkJCSAgICA= 39558 -IikpKQ== 39559 -bm9ybWFsaXplZA== 39560 -Lmxhc3ROYW1l 39561 -IGNsYW4= 39562 -QXNpYQ== 39563 -KE1vdXNl 39564 -cGFnaW5hdGU= 39565 -IGdpZg== 39566 -ZWxpZw== 39567 -IHBvc3RlcnM= 39568 -bmluZ3M= 39569 -IM+E 39570 -IGFwb3N0 39571 -IElocmU= 39572 -RGxsSW1wb3J0 39573 -IEVxdWFs 39574 -IGRpc3Rpbmd1aXNoZWQ= 39575 -bmVhcG9saXM= 39576 -IGJhY2tkcm9w 39577 -IEFsdGVybmF0aXZlbHk= 39578 -L21vZA== 39579 -IGxlbmQ= 39580 -IFNIT1c= 39581 -X2NvZGVz 39582 -IGF0w6k= 39583 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 39584 -LWNhc2U= 39585 -Y2h0ZQ== 39586 -IGRvbmM= 39587 -OmFkZA== 39588 -TmVnYXRpdmU= 39589 -ZmF2b3JpdGU= 39590 -IGF0dHJhY3Rpb25z 39591 -aW50Q29sb3I= 39592 -IFBpcg== 39593 -Q29ubmVsbA== 39594 -TWFuaWZlc3Q= 39595 -dGVhbXM= 39596 -IH07CgoK 39597 -IHBsdXJhbA== 39598 -IG92ZXJ0aW1l 39599 -IEV1cm9wYQ== 39600 -IEJhbmdsYWRlc2g= 39601 -KGFu 39602 -IGxpbmd1 39603 -aXRpbWU= 39604 -aW5zdG9u 39605 -LnNoYWRvdw== 39606 -56iL 39607 -IFVTUw== 39608 -U2VydmVyRXJyb3I= 39609 -SVZFUlM= 39610 -IEppbg== 39611 -IGh1bWJsZQ== 39612 -YXV0b2xvYWQ= 39613 -YXJleg== 39614 -4oCy 39615 -IEFzdHI= 39616 -aWNvbG9u 39617 -LlZpZXdNb2RlbHM= 39618 -b2Jv 39619 -IHN3aXBl 39620 -IHJlY2Vzc2lvbg== 39621 -6ZU= 39622 -IOyY 39623 -bmVyZw== 39624 -aW5ncmVkaWVudA== 39625 -bWFpbHRv 39626 -IEZhbWU= 39627 -UHJpbnRpbmc= 39628 -UGl4ZWxz 39629 -IEJhc2g= 39630 -cG9zdGE= 39631 -X0pP 39632 -IGluZmFtb3Vz 39633 -IExhbmM= 39634 -KGxvY2FsU3RvcmFnZQ== 39635 -LmJsaXQ= 39636 -IHlvdW5nZXN0 39637 -IGZpZWxkTmFtZQ== 39638 -IGNvbnRpbmc= 39639 -IHdvb2w= 39640 -IEltR3Vp 39641 -IE5TVA== 39642 -LnByZWZpeA== 39643 -VG9JbnQ= 39644 -IFNveA== 39645 -IGhhYml0YXQ= 39646 -KCJ8 39647 -PSciKw== 39648 -SU5HVE9O 39649 -X3dyYXA= 39650 -dWNrZXRz 39651 -IFdSSVRF 39652 -IG1lZGljaW5lcw== 39653 -IG1lbWJyYW5l 39654 -IEpUZXh0 39655 -IHJlcHJvZHVjdGlvbg== 39656 -X3JlY2VpdmU= 39657 -VGFibGVSb3c= 39658 -cXVldWVSZXVzYWJsZUNlbGw= 39659 -aG9va3M= 39660 -IHJlbHlpbmc= 39661 -IGRyaWxsaW5n 39662 -X0ls 39663 -KGV4Y2VwdGlvbg== 39664 -IGR1cmFiaWxpdHk= 39665 -IGhlc2l0YXRl 39666 -IGNvbXBhcnQ= 39667 -SUxJTkc= 39668 -IEVsZGVy 39669 -IGNhZmZl 39670 -IGRldmVsb3Bz 39671 -aXNoZXI= 39672 -IHBseQ== 39673 -IHRvbA== 39674 -X1BMQVk= 39675 -IGZyaWN0aW9u 39676 -KGFsd2F5cw== 39677 -IGluZGlnZW5vdXM= 39678 -IE9wZXJh 39679 -IENhbXB1cw== 39680 -YW5jZW1lbnRz 39681 -IGxpdHRlcg== 39682 -LmxpbWl0 39683 -KFRva2Vu 39684 -ZW5pcw== 39685 -IGhpZ2hsaWdodGluZw== 39686 -IEF1Yg== 39687 -IHZhbGlkYXRvcnM= 39688 -LWhvc3Q= 39689 -d2hlZWw= 39690 -PHs= 39691 -KSkr 39692 -IE5ld3NsZXR0ZXI= 39693 -X2F2ZXJhZ2U= 39694 -IHNvZGl1bQ== 39695 -IEhpbA== 39696 -IE1pbGU= 39697 -IEF1dGhTZXJ2aWNl 39698 -U3RhdGlzdGljcw== 39699 -IE51dHJpdGlvbg== 39700 -IHNwb25zb3Jz 39701 -b3ZlbmFudA== 39702 -PT09PT09PT09PT09PT0= 39703 -LkFic29sdXRl 39704 -IGbDpQ== 39705 -SGFuZGxpbmc= 39706 -IC0tLS0tLS0K 39707 -KGRpcmVjdG9yeQ== 39708 -IikuCg== 39709 -YW5vbA== 39710 -LmJyb3dzZXI= 39711 -IEdyaW5kaW5n 39712 -IGNr 39713 -RnJlcXVlbmN5 39714 -KClbJw== 39715 -QWRqdXN0 39716 -Y3Jldw== 39717 -YWZldHk= 39718 -IGdu 39719 -IHdpdmVz 39720 -b29v 39721 -IHByb3N0aXR1 39722 -IG/DuQ== 39723 -aWZ0eQ== 39724 -IGxpdGlnYXRpb24= 39725 -IEV6 39726 -SmVmZg== 39727 -LnBr 39728 -IFNob2Vz 39729 -Y29ybg== 39730 -eXl2c3A= 39731 -IGFkYXA= 39732 -PXU= 39733 -Q09ORg== 39734 -QU5EQVJE 39735 -IGVsZXZhdG9y 39736 -YmlsbGluZw== 39737 -IGNhbmQ= 39738 -IGNhcnA= 39739 -W2ZpZWxk 39740 -LWxpYg== 39741 -c2VxdWVudGx5 39742 -Pi0= 39743 -IGxjZA== 39744 -LS0tLS0tLS0tLS0tLS0t 39745 -KCIi 39746 -IHRhY3RpY2Fs 39747 -IFJvbmFsZA== 39748 -ZXh0cg== 39749 -IEZlc3Q= 39750 -IGZ1ZXI= 39751 -LW5hdmlnYXRpb24= 39752 -IGti 39753 -Z2hvc3Q= 39754 -IGhhbmRsZUNoYW5nZQ== 39755 -X2Nscw== 39756 -KCkhPQ== 39757 -Q29tcGFyYXRvcg== 39758 -LnZt 39759 -IENveA== 39760 -X3Jldmlldw== 39761 -L0A= 39762 -X2Nvb2tpZQ== 39763 -IHJlY29nbmlzZWQ= 39764 -bGRhcA== 39765 -VGhyZWFkcw== 39766 -IFNleHVhbA== 39767 -IEJlYXJpbmc= 39768 -KFNRTA== 39769 -IHhy 39770 -IHRoaWdo 39771 -VVJMQ29ubmVjdGlvbg== 39772 -IFNVVg== 39773 -IG1Db250ZXh0 39774 -IGluY2lkZW5jZQ== 39775 -IEVzdGU= 39776 -LnN1cA== 39777 -X3Rl 39778 -KEVYSVQ= 39779 -Q01E 39780 -LyI+ 39781 -QWxtb3N0 39782 -IFVuZQ== 39783 -IGFuZGVyZW4= 39784 -IFNpbmdsZXRvbg== 39785 -IGJvcmU= 39786 -VGhpbms= 39787 -IG5hcmM= 39788 -XWluaXRXaXRo 39789 -X3Nob3A= 39790 -KHN0cmF0ZWd5 39791 -IScs 39792 -aGVyaXRz 39793 -IERlc2s= 39794 -X21hY2hpbmU= 39795 -Lm5ldHR5 39796 -xLFuZGE= 39797 -PTw= 39798 -IFFS 39799 -IFNpZGViYXI= 39800 -LnNwbGl0Q29udGFpbmVy 39801 -IG9uU3VjY2Vzcw== 39802 -IG1vbmtleQ== 39803 -RW5qb3k= 39804 -KG5vZGVz 39805 -cGVjdHJ1bQ== 39806 -ICgqKA== 39807 -CVVJTlQ= 39808 -LGhlaWdodA== 39809 -IE5ldHdvcmtz 39810 -LnRhaWw= 39811 -LmxpbnNwYWNl 39812 -ICIuLi4= 39813 -TGlzdGVu 39814 -xqE= 39815 -LkNoYW5uZWw= 39816 -LWRlZmluZWQ= 39817 -UmVwZWF0 39818 -YWRqdXN0 39819 -RVJN 39820 -X2FwcGxpY2F0aW9u 39821 -LmFzc2VydE5vdE51bGw= 39822 -LXN0cmVhbQ== 39823 -IHJhYmJpdA== 39824 -IHBvc2l0aW9uaW5n 39825 -IHdva2U= 39826 -IGZpbmc= 39827 -IG11bHRpcGxheWVy 39828 -IHJlZ2lzdGVyaW5n 39829 -dW50aWw= 39830 -w6Vu 39831 -KDo6 39832 -dXNzaW9ucw== 39833 -IHBvdGF0bw== 39834 -IEVxdWFscw== 39835 -LlN1cA== 39836 -L2FwYWNoZQ== 39837 -ICg9 39838 -LiIp 39839 -LnB0cg== 39840 -IFNwZWVjaA== 39841 -LmNsaXA= 39842 -IEdhYnJpZWw= 39843 -IG11c2ljaWFu 39844 -L2lzc3Vlcw== 39845 -LnNob3A= 39846 -IEhpZXI= 39847 -X1JFVA== 39848 -X2J1Y2tldA== 39849 -44Oh 39850 -YXZz 39851 -IHJveg== 39852 -Zmxvd2Vy 39853 -V3JpdGVCYXJyaWVy 39854 -IE1pbGFu 39855 -IGxlZ2lzbGF0dXJl 39856 -IERvbGw= 39857 -IHByb3Zpbmc= 39858 -LmNvbmNhdGVuYXRl 39859 -4pWQ 39860 -IGdjaGFy 39861 -Y2RuanM= 39862 -Ymxlcw== 39863 -IExpc3Rpbmc= 39864 -0LvQvg== 39865 -LnhyTGFiZWw= 39866 -IFNhaw== 39867 -anVzdGljZQ== 39868 -IFZhbGVudGluZQ== 39869 -dW5sZXNz 39870 -IHBpZ2Vy 39871 -KHJ1bg== 39872 -IHRlc3RpZmllZA== 39873 -QU5B 39874 -IFJlbW92ZXM= 39875 -KSkpKTsK 39876 -cmVjYXRlZA== 39877 -IFJ1bnRpbWVNZXRob2Q= 39878 -IGNvbnF1 39879 -44Ki 39880 -IHRpc3N1ZXM= 39881 -YWlsZXI= 39882 -w6l0w6k= 39883 -LVN0YXI= 39884 -IGZsYW1lcw== 39885 -LnNldEljb24= 39886 -IHN1cGVybg== 39887 -IHZhZ2luYQ== 39888 -LXZhcmlhYmxl 39889 -IHdlbGxuZXNz 39890 -Q1VS 39891 -IGJlbGxl 39892 -LmdldFJlcXVlc3Q= 39893 -IHBvY28= 39894 -YmVuaA== 39895 -YWdlbnM= 39896 -IHNwaWxs 39897 -IEp1cg== 39898 -IGRpc3BhdGNoZXI= 39899 -0L3QvtCz0L4= 39900 -ZW1vbmlj 39901 -KGRpcm5hbWU= 39902 -INCU 39903 -IHBhc3Nl 39904 -IGdhbno= 39905 -cmljaW5n 39906 -RVU= 39907 -IG11amVyZXM= 39908 -ZXNzZW4= 39909 -LmF0dHJpYnV0ZQ== 39910 -amo= 39911 -CQkgCg== 39912 -W14= 39913 -IHN0cnRvbG93ZXI= 39914 -bGV4ZXI= 39915 -ZWN0YXI= 39916 -aG90ZWw= 39917 -LnNxdWFyZQ== 39918 -IHJhbGw= 39919 -IGxvd2VyZWQ= 39920 -aGFuZGxlZA== 39921 -TWFya2V0 39922 -IFVzZXM= 39923 -aXZhcw== 39924 -LkJ1c2luZXNz 39925 -44GX44Gm 39926 -RElW 39927 -IHdhc3RlZA== 39928 -IGF2b2ly 39929 -w6pt 39930 -X0FDQ09VTlQ= 39931 -LmV0 39932 -CVNETA== 39933 -a2Fw 39934 -IGZveA== 39935 -dXBwZXQ= 39936 -e30sCg== 39937 -Iiwn 39938 -RmF2b3JpdGU= 39939 -UEVORA== 39940 -IEFFUw== 39941 -fSks 39942 -IGRlZHVjdGlvbg== 39943 -IHBvbMOtdA== 39944 -IGNvbXBvbmVudFdpbGw= 39945 -IFRlbGVyaWs= 39946 -X1NFTEY= 39947 -IG11c2U= 39948 -Q3JhZnQ= 39949 -IGRlbnM= 39950 -4KS/ 39951 -KHRw 39952 -IHRhc3R5 39953 -IGJhbGFuY2Vz 39954 -IGRlZGljYXRpb24= 39955 -IFdhbGxhY2U= 39956 -IHVubGF3 39957 -XCI+XA== 39958 -IG11bQ== 39959 -LXVwZGF0ZQ== 39960 -ZW1lbnRl 39961 -IHNvZGE= 39962 -UmVwdWJsaWM= 39963 -YXNtaW5l 39964 -w6lyaWM= 39965 -KFN0YXR1cw== 39966 -IEpzb25Db252ZXJ0 39967 -IERpc2s= 39968 -LlJlZGlyZWN0 39969 -IGZpbG1pbmc= 39970 -L21vbA== 39971 -Um8= 39972 -IHZpbGxl 39973 -IHRyYWJhag== 39974 -IHN5bnRoZXNpcw== 39975 -cmVnYQ== 39976 -IHJs 39977 -U2NoZWR1bGVy 39978 -SVNIRUQ= 39979 -Y3VycmVudFVzZXI= 39980 -KGVycm9ycw== 39981 -J2g= 39982 -X2JvdA== 39983 -eGltbw== 39984 -IFVTQVJU 39985 -X3N1cGVy 39986 -X0RFQ1JFRg== 39987 -0L3QvtC5 39988 -X1JPVw== 39989 -IHByb21vdGVz 39990 -IFRB 39991 -IGhvcmFz 39992 -IFJlcHJlc2VudHM= 39993 -IG5hbWVvZg== 39994 -IEV4Yw== 39995 -IEdhcmFnZQ== 39996 -IHNlaW5l 39997 -LCM= 39998 -IGhlcmI= 39999 -L3Jlc291cmNlcw== 40000 -IHBsZWFkZWQ= 40001 -LnJhZGlvQnV0dG9u 40002 -IOaY 40003 -T3Bz 40004 -IE5lc3Q= 40005 -Y3N0cmluZw== 40006 -IERlZmVuY2U= 40007 -IHJlZmVyZQ== 40008 -X2xlYWY= 40009 -IHJldmVsYXRpb24= 40010 -66c= 40011 -LmV4ZWN1dGVVcGRhdGU= 40012 -X1dPUkxE 40013 -IGV4cGFucw== 40014 -KCJcIg== 40015 -amFi 40016 -IGRvdWJ0cw== 40017 -IEdlb21ldHJ5 40018 -IGludHJvZHVjZXM= 40019 -IHNlbmF0b3Jz 40020 -IGNhbmFs 40021 -LmhlbHBlcg== 40022 -IEJpb2xvZ3k= 40023 -X1NFTlM= 40024 -LnByZXZpb3Vz 40025 -LXRvdWNo 40026 -YWJpdA== 40027 -IGltcGFjdGVk 40028 -IGJyYWNrZXRz 40029 -LmRpcmVjdA== 40030 -YWNjdW0= 40031 -IHRlc3Rvc3Rlcm9uZQ== 40032 -CWFjdGlvbg== 40033 -IENoYW5jZQ== 40034 -IHBlYWtz 40035 -Q3BwQ29kZUdlbldyaXRlQmFycmllcg== 40036 -IHVuYmVsaWU= 40037 -X3ByZXNz 40038 -LlJlbA== 40039 -YW5nbGVk 40040 -L3RlbXBsYXRlcw== 40041 -LS0+DQo= 40042 -bGltZQ== 40043 -IHN1ZmZpY2llbnRseQ== 40044 -X250 40045 -RXhwYW5k 40046 -LmlzZmlsZQ== 40047 -IGlzRW1wdHk= 40048 -IHF0 40049 -IG11bGhlcg== 40050 -YWNvYg== 40051 -R2Vvcmdl 40052 -5bi4 40053 -IGFzc2lt 40054 -YXNv 40055 -IGNvbXByaXNlZA== 40056 -T1Y= 40057 -KENPTkZJRw== 40058 -CXdyaXRlcg== 40059 -IGRlc3A= 40060 -IHRlbnVyZQ== 40061 -KGNy 40062 -LnBvb2w= 40063 -IEJyZW5k 40064 -IGNlbnNvcg== 40065 -KHRpbWVvdXQ= 40066 -IHBsZWE= 40067 -LldyYXA= 40068 -IHRpZ2h0bHk= 40069 -IFdlcmU= 40070 -IElnbm9yZQ== 40071 -YWJlaQ== 40072 -IGJyaWRnZXM= 40073 -IGNvbmRlbW4= 40074 -IHNpbXBsaWNpdHk= 40075 -IHJvdXRpbmVseQ== 40076 -IGJsYWNrcw== 40077 -amI= 40078 -IFBpdA== 40079 -VXRm 40080 -IC8K 40081 -cmVsb2Fk 40082 -IHNldE9iamVjdA== 40083 -L2dsb2JhbA== 40084 -IGZhdHR5 40085 -IHNvY2tz 40086 -Q291bGRu 40087 -IGVyb3Rpc2s= 40088 -5p2h 40089 -IFByZXNzdXJl 40090 -IE1heg== 40091 -bnBvcw== 40092 -dG9sb3dlcg== 40093 -IEVR 40094 -dXRldXI= 40095 -IE1vbWVudA== 40096 -IGV0YQ== 40097 -e3stLQ== 40098 -IGdyYXBocw== 40099 -IEd1YXI= 40100 -cmluZQ== 40101 -KC0t 40102 -IEh0dHBTdGF0dXM= 40103 -KHN0dWRlbnQ= 40104 -Km5w 40105 -IHJhaWx3YXk= 40106 -IGFzeW5jaHJvbm91cw== 40107 -X3Zt 40108 -J10sJw== 40109 -LHRleHQ= 40110 -bWVyY2hhbnQ= 40111 -KEd1aWQ= 40112 -IEdyYQ== 40113 -aXhlcg== 40114 -ZmV0Y2hBbGw= 40115 -LmFkZExpc3RlbmVy 40116 -ZmxpcA== 40117 -KiQ= 40118 -PigpLA== 40119 -IHN1bmxpZ2h0 40120 -YXNzaWduZWQ= 40121 -IGFiYw== 40122 -IENPTFVNTg== 40123 -IPCfmYIKCg== 40124 -KS4uLg== 40125 -IGVuc2VtYmxl 40126 -IG5ld2xpbmU= 40127 -X1NJTkdMRQ== 40128 -aWVkYWQ= 40129 -IGRhcmtlcg== 40130 -b3JtYXA= 40131 -IGxpb24= 40132 -cGxpdHM= 40133 -IGlsbHVzdHJhdGlvbg== 40134 -IElFRUU= 40135 -IHZpc3Rh 40136 -b3VzYW5kcw== 40137 -KioqKioqKg== 40138 -IFRvbW15 40139 -IGh1ZQ== 40140 -U2Vs 40141 -IGF1cmE= 40142 -IFRoZXJhcHk= 40143 -IGFuaW1hdG9y 40144 -LmNvbnN0cmFpbnRz 40145 -IHZhZ3Vl 40146 -KCIiKQ== 40147 -IHZpbGxhaW4= 40148 -IGJsZXNzaW5n 40149 -IHN0cmluZ0J1aWxkZXI= 40150 -IE1pc2M= 40151 -IERJUg== 40152 -ZmF4 40153 -LW5vZGU= 40154 -IFdhbGtpbmc= 40155 -IEFV 40156 -c2Vzcw== 40157 -IGdyaWxs 40158 -VkVSVElTRQ== 40159 -IEZvb2Rz 40160 -IHRvdXJuYW1lbnRz 40161 -w5M= 40162 -IE1hcnNo 40163 -IHdvbmRlcnM= 40164 -TG9uZ2l0dWRl 40165 -LkNvbW1hbmRUZXh0 40166 -PWlucHV0 40167 -X2VuY29kZXI= 40168 -cGFnZVNpemU= 40169 -IGdldFN0YXRl 40170 -Pj4K 40171 -LmdyZXk= 40172 -cG9k 40173 -IHJlYWRpbmdz 40174 -IHJlY29uc2lkZXI= 40175 -U3RhcnR1cA== 40176 -IGV4Y2Vy 40177 -LmJhbGFuY2U= 40178 -X2N5Y2xl 40179 -X1RpbWU= 40180 -TE9DQUw= 40181 -IEVGSQ== 40182 -IFJleW4= 40183 -LnNldEZvcmVncm91bmQ= 40184 -Ynlu 40185 -IGRpc2Nvbm5lY3RlZA== 40186 -QUNUSVZF 40187 -IGVtYmVkZGluZw== 40188 -aWNrZXJz 40189 -IHN1cnJvdW5kaW5ncw== 40190 -KmM= 40191 -IGdhcmFudA== 40192 -IGJm 40193 -IHdpcGU= 40194 -IOS4iw== 40195 -X1RSQQ== 40196 -YWRveA== 40197 -55U= 40198 -IHN1Y2tz 40199 -IFNvbmdz 40200 -IEFzc29jaWF0ZXM= 40201 -IEJhbGQ= 40202 -IEJyZXR0 40203 -dmVuaWxl 40204 -IHZ0 40205 -IGluYWRl 40206 -IHJlc2lnbmVk 40207 -IEdsZW5u 40208 -LnBhdHRlcm4= 40209 -LkRhdGFCaW5k 40210 -0YPQvA== 40211 -TGF5b3V0SW5mbGF0ZXI= 40212 -Y2hldA== 40213 -IFRlc3RhbWVudA== 40214 -Lm1z 40215 -IHBhdg== 40216 -IFJlYWN0RE9N 40217 -dXJkeQ== 40218 -QURBVEE= 40219 -TXU= 40220 -L2FjdGlvbnM= 40221 -IEpz 40222 -X2V4dHJhY3Q= 40223 -IEJyaW5n 40224 -Omlk 40225 -c3RydA== 40226 -aXZhdGlvbg== 40227 -IG91dHJpZ2h0 40228 -YXp1 40229 -bG95bWVudA== 40230 -0LjRjw== 40231 -YWxkbw== 40232 -IFB1Ymxpc2hlcg== 40233 -RWR1Y2F0aW9u 40234 -UGFsZXR0ZQ== 40235 -X2Rydg== 40236 -ICgkKA== 40237 -IEFuZGE= 40238 -IHJlbWVkeQ== 40239 -IGluY29uc2lzdGVudA== 40240 -dGVjdGlvbg== 40241 -IHJlZ3VsYXRvcnM= 40242 -IHNob3J0ZXN0 40243 -KHBhaXI= 40244 -IEluc3RhbGxhdGlvbg== 40245 -IGRlZmVuZGFudHM= 40246 -ICgpOw== 40247 -LWxhcmdl 40248 -TWVs 40249 -IHRocmVhdGVu 40250 -0L3Rjw== 40251 -IGZldGlzaA== 40252 -b3RpbmU= 40253 -X2RpYw== 40254 -IDwk 40255 -IHN0YWdnZXI= 40256 -c3Bp 40257 -JHJlc3BvbnNl 40258 -U2Vydg== 40259 -LWJvcm4= 40260 -am9z 40261 -CWltZw== 40262 -CVdIRVJF 40263 -X2x0 40264 -5b2T 40265 -LmNvc3Q= 40266 -IFR1ZQ== 40267 -LmxhYmVscw== 40268 -IExW 40269 -d2Nzc3RvcmU= 40270 -IEplc3Nl 40271 -4Lir 40272 -VHJhZGU= 40273 -IHByZWRlY2Vzc29y 40274 -64I= 40275 -ZmluYWxseQ== 40276 -X2dlbmVyYWw= 40277 -b2dnbGVy 40278 -X1JFR0lPTg== 40279 -bmVtZW50 40280 -IGJsb2dnZXI= 40281 -IEhhcmJvcg== 40282 -IERhdGFzZXQ= 40283 -W3c= 40284 -IGF0dGVuZGVlcw== 40285 -Lmljbw== 40286 -bWF4aW11bQ== 40287 -LlVubG9jaw== 40288 -X1NZTkM= 40289 -w6FnaW5h 40290 -IGRvd25z 40291 -IFdpaQ== 40292 -XSkv 40293 -IGtpY2tpbmc= 40294 -dW5pY2F0aW9u 40295 -IERBQw== 40296 -IElEUw== 40297 -IFJlbnRhbA== 40298 -IGN1cnJlbnRUaW1l 40299 -IHZhY2NpbmVz 40300 -IERldmls 40301 -IG5vcnM= 40302 -X21vdXNl 40303 -dXJyZWN0aW9u 40304 -KG5v 40305 -ID4NCg== 40306 -IGFnZ3Jlc3Npb24= 40307 -IGJyZWVkaW5n 40308 -LnN5bWJvbA== 40309 -aW1hbg== 40310 -QWJzb2x1dGVQYXRo 40311 -IFdITw== 40312 -X2ZsdXNo 40313 -LXJvb3Q= 40314 -YXJuYQ== 40315 -Jk0= 40316 -IGZhdGhlcnM= 40317 -IFJvY2tldA== 40318 -aXZlYXU= 40319 -IHdhbmRlcg== 40320 -IGNvbXBvcw== 40321 -IFdhcnJpb3I= 40322 -IFNlYXQ= 40323 -IENsaW5pYw== 40324 -X2ludm9pY2U= 40325 -KGRpc3BhdGNo 40326 -UHJvZHVjdG8= 40327 -YXR1cmluZw== 40328 -b3NzaWVy 40329 -IE1BWQ== 40330 -IGRhZ2dlcg== 40331 -IHNhbml0aXplZA== 40332 -IFJGQw== 40333 -IHByb3Bo 40334 -IHVyaW5l 40335 -IGdyaW5k 40336 -IEV4cGFuZGVk 40337 -ZGVzY3JpcGNpb24= 40338 -LWZ3 40339 -IEtlcnJ5 40340 -PW5hbWU= 40341 -IGNoaw== 40342 -IG5hdGlvbmFsbHk= 40343 -IHRoZWU= 40344 -SW5j 40345 -ID8+Pg== 40346 -LlJhZGlvQnV0dG9u 40347 -Lkh0dHBTZXJ2bGV0UmVzcG9uc2U= 40348 -L1k= 40349 -CWZpZWxk 40350 -IGhvbW1l 40351 -eXBlcg== 40352 -UGh5c2ljYWw= 40353 -PXY= 40354 -IGRyaXY= 40355 -IEVycm9ycw== 40356 -IGPEgw== 40357 -RGVhdGg= 40358 -IFdJTkRPVw== 40359 -IHBvZXQ= 40360 -IFNoYXJw 40361 -IEltbXV0YWJsZQ== 40362 -CWNyZWF0ZQ== 40363 -IGdlaHQ= 40364 -IFJlZm9ybQ== 40365 -YWlzZXI= 40366 -IEluaXRpYWxpemF0aW9u 40367 -IGltbXVuaXR5 40368 -LmNvbXBvc2U= 40369 -IGxhdGVuY3k= 40370 -IExlYmFub24= 40371 -IFBhcmFk 40372 -IGZ1ZWxz 40373 -IEV4aGli 40374 -Y29o 40375 -JSI+Cg== 40376 -IENMSQ== 40377 -KWluaXRXaXRo 40378 -LVph 40379 -X0NMRUFS 40380 -cmVnbg== 40381 -IGZpbmFuY2Vz 40382 -LnN0YW5kYXJk 40383 -X0NBVEVHT1JZ 40384 -LmxpYnJhcnk= 40385 -IHRyYXZlbGVycw== 40386 -X3dw 40387 -IEV2YWx1YXRpb24= 40388 -c3RhcnRpbmc= 40389 -ICkpLAo= 40390 -ZXBpc29kZQ== 40391 -IFZhcmlhbnQ= 40392 -IGRhZW1vbg== 40393 -IEp1bGlh 40394 -IE5S 40395 -IGRvdWJsZXM= 40396 -PHY= 40397 -L3J1bnRpbWU= 40398 -IGludGVycHJldGVy 40399 -IElOREVY 40400 -IEhvbG1lcw== 40401 -X0RJTQ== 40402 -IHBhZGRsZQ== 40403 -X2V4YW1wbGU= 40404 -IGZvcmVncm91bmQ= 40405 -LnJvdXRlcw== 40406 -IHNvd2ll 40407 -U1VDQ0VTUw== 40408 -IENEQw== 40409 -IEJE 40410 -Xy0= 40411 -YXN1cmVk 40412 -V3JpdGluZw== 40413 -IGN1cnJlbnRQYWdl 40414 -KGFuc3dlcg== 40415 -IEFTQ0lJ 40416 -4Kg= 40417 -IHNvY2lhbGx5 40418 -eXl5 40419 -IFNwZWNpYWxpc3Q= 40420 -KGN1c3RvbWVy 40421 -aXN0YW5p 40422 -a2VzdA== 40423 -IE1haw== 40424 -IHRobw== 40425 -LnB0 40426 -KGNvbW1lbnQ= 40427 -IENvbnZlcnRlcg== 40428 -Z2Ft 40429 -Ymlucw== 40430 -LnRlbGU= 40431 -IFZldGVyYW5z 40432 -X0FMTE9D 40433 -0L7Qu9GM0LfQvtCy0LDRgg== 40434 -aW5uYW1vbg== 40435 -O3dpZHRo 40436 -b2hs 40437 -IGZhbnRhcw== 40438 -IHN1bmc= 40439 -CUs= 40440 -KEpzb24= 40441 -IG5laWdoYm91cmhvb2Q= 40442 -IHZvdw== 40443 -IHNpbnM= 40444 -b25hY2Np 40445 -IGVwb2Nocw== 40446 -aW1hZ2Vu 40447 -LkNoYW5nZQ== 40448 -Lm15YmF0aXM= 40449 -U2Vlaw== 40450 -V0VS 40451 -566h55CG 40452 -IGludGVyZXNz 40453 -X0V2ZW50 40454 -ZWRlcmxhbmQ= 40455 -IHRlcnJpdG9y 40456 -IGNpdWRhZA== 40457 -dWNrZWQ= 40458 -IHNuYWNr 40459 -IHRyYW5zcG9ydGVk 40460 -IE1hbmlmZXN0 40461 -IERBVA== 40462 -X3RoZXRh 40463 -IHdvbnQ= 40464 -LgoKCgoKCgoKCgo= 40465 -irbmgIE= 40466 -IEVwaWM= 40467 -RGVjaw== 40468 -bHRyYQ== 40469 -X1pFUk8= 40470 -IFtdOw== 40471 -L3NjcmlwdHM= 40472 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 40473 -5oOF 40474 -IHdlZWQ= 40475 -TkJD 40476 -IHJhcGVk 40477 -IEdhdGV3YXk= 40478 -W00= 40479 -IFRpbWVvdXQ= 40480 -ZW5jaG1hcms= 40481 -LlZpZXdNb2RlbA== 40482 -IHBvcm5vcw== 40483 -IFlh 40484 -dGhyaXRpcw== 40485 -IEZseW5u 40486 -IG1lZ2E= 40487 -YWNpbg== 40488 -IHRyaWJhbA== 40489 -LmFwcGxl 40490 -IEJsbw== 40491 -w6Ju 40492 -aWJp 40493 -cm92 40494 -IExpdmVz 40495 -Xi4= 40496 -Z2V0UmVxdWVzdA== 40497 -IEVzdGFibGlzaA== 40498 -Y29udGFpbmVycw== 40499 -IHN0YXJyaW5n 40500 -IGNlbGVicml0aWVz 40501 -IFJlbGF0aXZl 40502 -IEhlaWdodHM= 40503 -IHRxZG0= 40504 -IE5vcnRod2VzdA== 40505 -aXZpYw== 40506 -CWNs 40507 -IGF1dG9tb3RpdmU= 40508 -ZW50cmlj 40509 -IGZvcnR1bmF0ZQ== 40510 -IGZpcmVwbGFjZQ== 40511 -c2V1ZA== 40512 -bmlja25hbWU= 40513 -O3M= 40514 -X0NBTA== 40515 -aGFsdA== 40516 -KG5z 40517 -X2RlbGV0ZWQ= 40518 -RGV2ZWxvcG1lbnQ= 40519 -bW92aWVz 40520 -IGlkZW50aXRpZXM= 40521 -IHByb21wdGx5 40522 -2KfZhg== 40523 -IGFudGU= 40524 -ICInLCc= 40525 -5Y+j 40526 -aW1wc2U= 40527 -IHlhcA== 40528 -VHlwZU5hbWU= 40529 -IGJpdGNo 40530 -IGFzc29jaWF0ZXM= 40531 -SEVNRQ== 40532 -LWVtcHR5 40533 -INiq 40534 -b2x2ZXJz 40535 -IHBpc3RvbA== 40536 -U2NvcGVk 40537 -YWduZXI= 40538 -J109PSc= 40539 -IElNUA== 40540 -ZXhj 40541 -IG9taXR0ZWQ= 40542 -IG1pbmRzZXQ= 40543 -IFtdKA== 40544 -IG9ybg== 40545 -X0NBTQ== 40546 -QXZn 40547 -TG9jYWxpemVkU3RyaW5n 40548 -IE5hdHVy 40549 -IGNvbXBvc2Vy 40550 -IFBsYXlpbmc= 40551 -IG92ZXJk 40552 -X3V0Zg== 40553 -LnNr 40554 -IEZvbA== 40555 -JHBhZ2U= 40556 -LE9iamVjdA== 40557 -IGJlZXM= 40558 -YWxhcnk= 40559 -YnVsbGV0 40560 -X2xpYnJhcnk= 40561 -T2ZmZXI= 40562 -bG9jYXRlZA== 40563 -IChfLA== 40564 -4oCcSGU= 40565 -IE93bmVycw== 40566 -KSkuCg== 40567 -IGJyaQ== 40568 -LkFkbWlu 40569 -a3Rpb24= 40570 -0LvRjtGH 40571 -IGVyb3RpY2k= 40572 -Q2FuY2VsbGVk 40573 -IGFncg== 40574 -cmV2aWV3cw== 40575 -X2RtYQ== 40576 -UklDVA== 40577 -IGdmeA== 40578 -bXBp 40579 -cHBv 40580 -IC8vQA== 40581 -IHVwcGVyY2FzZQ== 40582 -IGNvbW1pdHRpbmc= 40583 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 40584 -VXNlckRhdGE= 40585 -IHZhaQ== 40586 -CXNvcnQ= 40587 -IGNvbmdyYXQ= 40588 -IGRpb3hpZGU= 40589 -0LTQsA== 40590 -LmFyZWE= 40591 -IEpvc2h1YQ== 40592 -IEtvY2g= 40593 -X2JyZWFr 40594 -YXp1cmU= 40595 -aXN0aWNhbA== 40596 -X0FMUEhB 40597 -X3ZpZXdz 40598 -IGVsaW1pbmF0aW5n 40599 -T01C 40600 -ZW51bWVy 40601 -IEh5ZHJv 40602 -KCoo 40603 -RVJUSUNBTA== 40604 -IGluZXZpdGFibHk= 40605 -IHN0b2xl 40606 -LWVhc3Q= 40607 -aWVyb24= 40608 -IGxpbmdlcg== 40609 -L2RvYw== 40610 -xbo= 40611 -IEFscmVhZHk= 40612 -YXNpbw== 40613 -IC0tCg== 40614 -IGFiYnJldg== 40615 -IEF0b20= 40616 -aGlt 40617 -IElOU0VSVA== 40618 -c3Vu 40619 -4pmq 40620 -Q09OTkVDVA== 40621 -ZXJhdG9y 40622 -IE1hbm5pbmc= 40623 -IDoo 40624 -Z2Fz 40625 -PT4n 40626 -IHF1ZXJ5c2V0 40627 -O30NCg== 40628 -IFBvcHVsYXRpb24= 40629 -dXRlZFN0cmluZw== 40630 -cmVzaWRlbnQ= 40631 -X0ZPTlQ= 40632 -IFJlc3BvbmQ= 40633 -IG9ic2N1cmU= 40634 -IG9ic2VydmFibGU= 40635 -IENvbnRyaWJ1dG9ycw== 40636 -a29u 40637 -IE11c2s= 40638 -ZXhhbw== 40639 -IFR1Yg== 40640 -Qm9vdEFwcGxpY2F0aW9u 40641 -U09S 40642 -Lkhvcml6b250YWw= 40643 -LmZpbmRCeQ== 40644 -LnBvd2Vy 40645 -IHBvc2l0aXZlbHk= 40646 -dmVuaWVuY2U= 40647 -IEpvbmc= 40648 -IHdoaXN0bGU= 40649 -INC30L3QsNGH 40650 -IGxlbmRpbmc= 40651 -IGRlc3RydWN0aXZl 40652 -IG9uRGVsZXRl 40653 -YXV0aG9yaXphdGlvbg== 40654 -KCk7Pz4= 40655 -X29yaWdpbmFs 40656 -c2NpZW5jZQ== 40657 -YXRyYQ== 40658 -Pyw/LA== 40659 -IEFzYw== 40660 -IGNvbnZpbmNpbmc= 40661 -JGE= 40662 -b3JnZW4= 40663 -X0RhdGU= 40664 -IFByb3ZpZGU= 40665 -IGxvbmVseQ== 40666 -KScK 40667 -ZXhjaGFuZ2U= 40668 -Oz8+Cg== 40669 -LmZhc3Q= 40670 -U2FtcGxlcw== 40671 -TG9uZG9u 40672 -J10pDQo= 40673 -IElvbmlj 40674 -IHBlc3Nv 40675 -IEtuaWdodHM= 40676 -IFJhZg== 40677 -X2F0dHJz 40678 -IHJlcGVhbA== 40679 -Pk1haW4= 40680 -IE9yZGVyZWQ= 40681 -X05ldw== 40682 -PSIiPjwv 40683 -dXJscGF0dGVybnM= 40684 -QVRJT05BTA== 40685 -cGVlY2g= 40686 -IElkYWhv 40687 -IHByaW5jZXNz 40688 -IEN1c3RvbWVycw== 40689 -YXdheXM= 40690 -YWRi 40691 -IEJyeWFudA== 40692 -bm9uY2U= 40693 -IGFkdWw= 40694 -IGBgKA== 40695 -IGFmdGVybWF0aA== 40696 -PWRpY3Q= 40697 -dGV4dEJveA== 40698 -IHNwZXJt 40699 -IGNvdWdo 40700 -SG9y 40701 -4oCZUw== 40702 -LkNvbXBvbmVudFJlc291cmNlTWFuYWdlcg== 40703 -IHJlZ3VsYXRvcg== 40704 -IHBhcnRuZXJzaGlwcw== 40705 -L3Byb2plY3Rz 40706 -dHJ5cw== 40707 -IExhc2Vy 40708 -4p+p 40709 -IEZ1bms= 40710 -IHVuY29uc2Npb3Vz 40711 -IGNydXN0 40712 -IFRlYW1z 40713 -IEJhbm5lcg== 40714 -IEhvbmV5 40715 -bGVtcw== 40716 -IG1heFdpZHRo 40717 -UG9pbnRlckV4Y2VwdGlvbg== 40718 -ZmFkZU91dA== 40719 -LVN0 40720 -IHN0cmFuZ2Vycw== 40721 -X0dP 40722 -V3JpdGFibGU= 40723 -X0luZm8= 40724 -Lk5vbk51bGw= 40725 -YW5ub3RhdGlvbnM= 40726 -IEdE 40727 -IGVuZG9yc2Vk 40728 -CVRva2VuTmFtZQ== 40729 -IERlcGVuZGluZw== 40730 -WU5BTQ== 40731 -IE1ldGVvcg== 40732 -IEluY3JlYXNl 40733 -Lk1hbnk= 40734 -PT0o 40735 -LlVVSUQ= 40736 -X0tFUk5FTA== 40737 -IHZpZMOp 40738 -IHBx 40739 -IFF0R3Vp 40740 -IFZhcmlvdXM= 40741 -IGpvaG4= 40742 -X3BhdGNo 40743 -IHRvdXRlcw== 40744 -IEZhaWw= 40745 -IHN1cnZpdmluZw== 40746 -KCIkew== 40747 -ICAgICAgIA0K 40748 -IGltYWdlVXJs 40749 -LndvcmRwcmVzcw== 40750 -c291cmNlcw== 40751 -CWdsVmVydGV4 40752 -4oCZYQ== 40753 -IGVzY29s 40754 -UkFSWQ== 40755 -IFNuYWtl 40756 -IHF1aW50 40757 -IGxhc3Rz 40758 -IEhhcm1vbg== 40759 -IGNvaWw= 40760 -IGV4cGxvaXRhdGlvbg== 40761 -bGVlbg== 40762 -Jz4iOwo= 40763 -IFNFUlZFUg== 40764 -IEhFQURFUg== 40765 -X3ZlbG9jaXR5 40766 -IEludm9rZQ== 40767 -LnRpbWVzdGFtcHM= 40768 -IHN1bGY= 40769 -SVFVRQ== 40770 -IGluaGFiaXRhbnRz 40771 -cGhpbnM= 40772 -YXp6bw== 40773 -IG1vbm8= 40774 -TGVnZW5k 40775 -IG5vbmNl 40776 -SUZF 40777 -OyI7Cg== 40778 -LWNyZWF0ZQ== 40779 -IiIsCg== 40780 -cGVybWl0 40781 -IEltbWlncmF0aW9u 40782 -IHBhdGhuYW1l 40783 -ZmZlY3RpdmU= 40784 -4pmA4pmA 40785 -IGV4YW1z 40786 -LWV2ZW50 40787 -IFRpbGw= 40788 -W21pZA== 40789 -RklY 40790 -O2NvbG9y 40791 -KE9yZGVy 40792 -X3RyYWl0cw== 40793 -IG9yZGVyQnk= 40794 -IHN1bnQ= 40795 -IE5pY2hvbGFz 40796 -2LI= 40797 -IHN1bm55 40798 -aW5lcnM= 40799 -IGFjY2Vzc2liaWxpdHk= 40800 -IEhC 40801 -LmNvbXA= 40802 -CW9w 40803 -IG1pbm9yaXRpZXM= 40804 -ZXRoZXVz 40805 -IGNvbGxhYm9yYXRpdmU= 40806 -cHJpdA== 40807 -SElS 40808 -IHdyYXBz 40809 -CWRyYXc= 40810 -Z29k 40811 -IElY 40812 -LmFwcHM= 40813 -IE5N 40814 -IGlycmVsZXZhbnQ= 40815 -IFRpZ2Vycw== 40816 -IGRpYWc= 40817 -R1Y= 40818 -IEFjY2Vzc29yaWVz 40819 -a29udA== 40820 -IHNpbXBsaWZ5 40821 -IEZhdm9yaXRl 40822 -X3Rvb2xz 40823 -KFtdKTsK 40824 -IHRvd2Vycw== 40825 -QmVz 40826 -IGh1bnRlcg== 40827 -IHNhbG9u 40828 -KGJ1ZmY= 40829 -CWRlYnVn 40830 -IG1hbHdhcmU= 40831 -TW92aW5n 40832 -LW9wdGlvbnM= 40833 -KSsn 40834 -IExPVkU= 40835 -X1NPQ0tFVA== 40836 -X2Zpbg== 40837 -IERlbGF3YXJl 40838 -IHNoZXJpZmY= 40839 -LWludmFsaWQ= 40840 -IEZVTEw= 40841 -INC/0L7QtA== 40842 -ZWxhcw== 40843 -InN0cmluZ3M= 40844 -IFJlcHJlc2VudGF0aXZlcw== 40845 -c3VyZmFjZQ== 40846 -cmVzb2x2ZWQ= 40847 -aHRkb2Nz 40848 -KSk6DQo= 40849 -IHByZXNzdXJlcw== 40850 -IG5vcm1z 40851 -IHBsYQ== 40852 -IHN1cm5hbWU= 40853 -IHBvc3RhbA== 40854 -IERlcGFydA== 40855 -IHNsYXVnaHRlcg== 40856 -b3JpZGE= 40857 -IGhlYmJlbg== 40858 -IGRlc2Fy 40859 -Y29tcGFjdA== 40860 -X0xBTkc= 40861 -5ZCI 40862 -b3BvbHk= 40863 -X3JhZA== 40864 -IFNURE1FVEhPRA== 40865 -TGF6eQ== 40866 -ICAgCQ== 40867 -Li4uLA== 40868 -KHdlYg== 40869 -IFBvbnQ= 40870 -IGV0d2Fz 40871 -IHVwd2FyZA== 40872 -X2hhdA== 40873 -IF0sCgo= 40874 -IGJhc2VVcmw= 40875 -IHdvcnJ5aW5n 40876 -LWFkZG9u 40877 -KGdldENsYXNz 40878 -U1BJ 40879 -IGNhcHR1cmluZw== 40880 -KX0sCg== 40881 -RWZmZWN0cw== 40882 -IGNvbXBldGVudA== 40883 -IGZvdWw= 40884 -IHN1YnNjcmliaW5n 40885 -IE9CSkVDVA== 40886 -SVhFTA== 40887 -YnVja3M= 40888 -KGVkZ2U= 40889 -KHBhc3M= 40890 -IFBldGVyc29u 40891 -IGJvb2Jz 40892 -IERlbGF5 40893 -X3NxdWFyZQ== 40894 -ZWxpbQ== 40895 -b3RlcnM= 40896 -X1BD 40897 -JUU= 40898 -b25jbGljaw== 40899 -IFNWRw== 40900 -IHRvcHBlZA== 40901 -IGZpc3Q= 40902 -c21hcnQ= 40903 -IFJhbHBo 40904 -KG93bmVy 40905 -am91cnM= 40906 -IGJyb256ZQ== 40907 -IEFyZ3VtZW50RXhjZXB0aW9u 40908 -KG9yaWdpbmFs 40909 -X1NDQUxF 40910 -X2Nw 40911 -IHJlY29tbWVuZHM= 40912 -LnNldFN0eWxl 40913 -U3VyZQ== 40914 -TEFORA== 40915 -IHJlcGVhdGluZw== 40916 -TWF0dA== 40917 -LlZpc2liaWxpdHk= 40918 -IGVudGVycHJpc2Vz 40919 -LlNldHVw 40920 -KHNjZW5l 40921 -IFJlYWN0aXZl 40922 -dXJnZQ== 40923 -Ync= 40924 -LlB1dA== 40925 -cGVyc2lzdA== 40926 -LmNvb2tpZQ== 40927 -IEF1ZGk= 40928 -YHM= 40929 -c3VwcGxpZXI= 40930 -KEZvcm0= 40931 -wqE= 40932 -X3Nv 40933 -jIA= 40934 -IExlZ2lvbg== 40935 -dHRl 40936 -TmQ= 40937 -TG9zcw== 40938 -KGF0dHJz 40939 -LnNjYXR0ZXI= 40940 -IGdyb29t 40941 -IGdsaW1wc2U= 40942 -IG5haWxz 40943 -IGN1bXVsYXRpdmU= 40944 -IGZhemVy 40945 -X3NlcnZpY2Vz 40946 -Lk51bQ== 40947 -aWJpbGl0 40948 -X3Jlc29sdXRpb24= 40949 -IFR4 40950 -dW1pbml1bQ== 40951 -b3Bh 40952 -LnNjaGVkdWxl 40953 -c210cA== 40954 -4LiV 40955 -dXJyeQ== 40956 -w7xr 40957 -Z29vZw== 40958 -X3NpZ25hdHVyZQ== 40959 -LmludG8= 40960 -IFN0ZXBz 40961 -IGhvbWVvd25lcnM= 40962 -IE5TVVJM 40963 -IFBBQw== 40964 -ICAgICAgICAgICAgCgo= 40965 -PicpCg== 40966 -ZW5o 40967 -IGluY2Fw 40968 -JE1FU1M= 40969 -IG1vaW5z 40970 -IEZp 40971 -IG9mZnNlYXNvbg== 40972 -cHJlc3Npb25z 40973 -Pi48Lw== 40974 -IE1hcmtlcg== 40975 -IG9uQ2xvc2U= 40976 -TEVWRUw= 40977 -IGludGVyZmVyZQ== 40978 -IENvbGlu 40979 -IFJlc2lzdGFuY2U= 40980 -RGlzY291bnQ= 40981 -IFdlYkVsZW1lbnQ= 40982 -IGJhdGhyb29tcw== 40983 -bGVnYWN5 40984 -IENhcHR1cmU= 40985 -IGFyaXNpbmc= 40986 -ICIpOwoK 40987 -0YjQuNCx 40988 -IEluZmluaXR5 40989 -QWR2ZXJ0aXNlbWVudHM= 40990 -IENvbWluZw== 40991 -IFBST0pFQ1Q= 40992 -X1BST1RPQ09M 40993 -IHVzZURpc3BhdGNo 40994 -LmNoYW5uZWxz 40995 -IENpdGl6ZW5z 40996 -ZW50cmU= 40997 -X21w 40998 -LkNvbnN0YW50cw== 40999 -IFNlcmlhbGl6ZQ== 41000 -X0lOQw== 41001 -KGx1YQ== 41002 -IGNsYXNo 41003 -X3dpdGhvdXQ= 41004 -LmtleVNldA== 41005 -IHJlY2VpdmVycw== 41006 -5pa55rOV 41007 -KG1lbQ== 41008 -IEhvcml6b250YWw= 41009 -IGNvY2t0YWls 41010 -IGNob29zZXM= 41011 -LklubmVy 41012 -IHJlbGllZA== 41013 -b3VudGVy 41014 -ICJe 41015 -IHRlbmFudHM= 41016 -ImA= 41017 -X1BN 41018 -ZXJzZWQ= 41019 -IH19Ij48Lw== 41020 -IHByb3ZpbmNlcw== 41021 -X1JBVw== 41022 -XEFwcA== 41023 -IHByb3N0aXR1ZXI= 41024 -X2dhaW4= 41025 -LnRlbmNlbnQ= 41026 -ZmZlY3Rz 41027 -KHBr 41028 -c2t1 41029 -IHVzYWJsZQ== 41030 -RVJWRUQ= 41031 -IGFudGVubmE= 41032 -aGVh 41033 -cGxpc3Q= 41034 -X1BMVUdJTg== 41035 -0YHQuw== 41036 -Lmxvb2t1cA== 41037 -4buB 41038 -IGVubGFyZw== 41039 -IHBpc3M= 41040 -SGFt 41041 -aW1hcA== 41042 -IGludmFsaWRhdGU= 41043 -IHNpbGs= 41044 -PSIjIj4K 41045 -IEdyYXNz 41046 -IEdvYWw= 41047 -X3BkZg== 41048 -SGFuZGxlcnM= 41049 -IHN0YWNrcw== 41050 -LmdldEZ1bGxZZWFy 41051 -PVtdOwo= 41052 -6L2m 41053 -LFY= 41054 -KHNwbGl0 41055 -0YPQvdC6 41056 -IGJha2VjYQ== 41057 -IH4vLg== 41058 -cGV6 41059 -dGFpbHM= 41060 -IEdsZW4= 41061 -IHNldEltYWdl 41062 -IENvbWlj 41063 -QkxPQ0s= 41064 -CVRoaXM= 41065 -b2FkZXI= 41066 -IGNhcGl0YWxpc3Q= 41067 -X1NURVA= 41068 -KEJvb2xlYW4= 41069 -IENvcnJlY3Q= 41070 -cmluYQ== 41071 -IGNvbmNhdGVu 41072 -5a6e 41073 -KCk6Cgo= 41074 -IHVuYW5pbQ== 41075 -bGxp 41076 -YWxhcnM= 41077 -LW5l 41078 -IGRpdm9y 41079 -IEtpY2tzdGFydGVy 41080 -XS5f 41081 -PG51bWJlcg== 41082 -L21lbnU= 41083 -R1JBUEg= 41084 -dmlzaXRvcg== 41085 -IGltcHJvcGVy 41086 -X05FWFQ= 41087 -IGJpc2E= 41088 -YmFja2dyb3VuZENvbG9y 41089 -L2lucHV0 41090 -IG1vaQ== 41091 -R29hbA== 41092 -bGlxdQ== 41093 -IG1pc2NvbmR1Y3Q= 41094 -IGNvbXByaXNlcw== 41095 -YXducw== 41096 -IFBpZQ== 41097 -cmFpcw== 41098 -cm9sZXVt 41099 -IGN1cnNl 41100 -eXU= 41101 -X3BvbGw= 41102 -LmN1cnJlbnRVc2Vy 41103 -RVNI 41104 -XSlb 41105 -IHN0b3J5dA== 41106 -KT87Cg== 41107 -Kj0= 41108 -IEJ1cmc= 41109 -L2xheW91dA== 41110 -X2JhY2tlbmQ= 41111 -Oz8+PC8= 41112 -IFdoYXRzQXBw 41113 -IE1vdW50YWlucw== 41114 -dmlzaW9ucw== 41115 -Zmx1ZW5jZQ== 41116 -LmNyZWF0ZUNvbXBvbmVudA== 41117 -IFBzeQ== 41118 -Zm9yZ2V0 41119 -c3J2 41120 -X0NPTVBPTkVOVA== 41121 -IE5leHVz 41122 -ICl7 41123 -ZW5kaQ== 41124 -SU1VTQ== 41125 -IEdG 41126 -57uE 41127 -4oCUdGhhdA== 41128 -Yms= 41129 -TW96aWxsYQ== 41130 -IGRlZmVuZGVycw== 41131 -LXNldHRpbmdz 41132 -aW1taW5n 41133 -IE9QVA== 41134 -IENX 41135 -IHRoYXRz 41136 -IE9wZW5pbmc= 41137 -UmVsZWFzZWQ= 41138 -bnBt 41139 -IGhycw== 41140 -IGdyb3VwZWQ= 41141 -LyIuJA== 41142 -IEhpc3RvcmljYWw= 41143 -KCQiew== 41144 -b3ZpYw== 41145 -KHNpZ24= 41146 -IFBob3RvZ3JhcGh5 41147 -IHNpZ251cA== 41148 -X0FSQ0g= 41149 -LnRlc3RuZw== 41150 -L2FuZ3VsYXI= 41151 -UmVzdENvbnRyb2xsZXI= 41152 -c2hpdA== 41153 -dWxsZQ== 41154 -LnBhdXNl 41155 -KFtdLA== 41156 -KHF1ZXN0aW9u 41157 -aWxvZ3k= 41158 -IEV1Zw== 41159 -LWxvY2Fs 41160 -IGt2aW4= 41161 -IHJlc2VydmF0aW9ucw== 41162 -b2JpYQ== 41163 -IHN1YnNpZGlhcnk= 41164 -IGFjY3VtdWxhdGVk 41165 -IFFWYXJpYW50 41166 -IEJKUA== 41167 -IE5vcm1hbg== 41168 -IEludGVncmF0aW9u 41169 -LlZhcmlhYmxl 41170 -KFJlc291cmNl 41171 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 41172 -RXhwb3Nl 41173 -ICd9 41174 -LkNPTE9S 41175 -INGH0LjRgQ== 41176 -QWpheA== 41177 -IHRocnU= 41178 -TW92aWVz 41179 -IHByb3Bvc2l0aW9u 41180 -L3RoZW1l 41181 -TW9kZWxQcm9wZXJ0eQ== 41182 -IEF3cw== 41183 -IEFuZHJlYQ== 41184 -IE1lcmdl 41185 -LmZpbmlzaA== 41186 -KHJlcXVpcmVk 41187 -IFByZWw= 41188 -ZWxlZA== 41189 -5pON5L2c 41190 -LlRSQQ== 41191 -TUFT 41192 -IHJlYWxpc2Vk 41193 -cm9pZHM= 41194 -CWZu 41195 -cmg= 41196 -LiI8Lw== 41197 -dmlkaWE= 41198 -IGRlcHVpcw== 41199 -IEJW 41200 -TG4= 41201 -IGx1c3Q= 41202 -QXNj 41203 -CQkJCQkJCSA= 41204 -aXNsZQ== 41205 -LWNhcmU= 41206 -X0lOVg== 41207 -IERyZXc= 41208 -IHdoYXRz 41209 -IENhcGFjaXR5 41210 -UGFybQ== 41211 -X21vbml0b3I= 41212 -LnN0dWRlbnQ= 41213 -IFJOQQ== 41214 -LmVuZHN3aXRo 41215 -Ymlo 41216 -IE1MQg== 41217 -L3Byb2plY3Q= 41218 -IHJlc3Rpbmc= 41219 -c2VwYXJhdG9y 41220 -eWQ= 41221 -ZXJ0aWE= 41222 -IG1vbml0b3JlZA== 41223 -Ij4qPC8= 41224 -LkZD 41225 -IE5FV1M= 41226 -IENhbGxz 41227 -IGFkZXF1 41228 -Q2hlY2tpbmc= 41229 -ZXN0aW1hdGU= 41230 -IHJlY2FsbHM= 41231 -X2ZyZXF1ZW5jeQ== 41232 -IHVzZVJlZg== 41233 -IEdyb3Zl 41234 -IFhpYQ== 41235 -IMOt 41236 -ZXNzZW5nZXI= 41237 -LWNvc3Q= 41238 -LmZj 41239 -IEt1bWFy 41240 -LkZvY3Vz 41241 -ZWxsYW5lb3Vz 41242 -LkFsZXJ0 41243 -ZWF4 41244 -IG9yY2g= 41245 -LnBt 41246 -IGxhbmRsb3Jk 41247 -KHBvcA== 41248 -X2FjdHVhbA== 41249 -IExC 41250 -R3JhbmQ= 41251 -LnJlbmRlcmVy 41252 -IGxvYg== 41253 -Y3VzdG9tZXJz 41254 -IGNhcHR1cmVz 41255 -V0lORE9X 41256 -IGRvY2g= 41257 -IGFwb2xvZ3k= 41258 -IEphbWE= 41259 -QFs= 41260 -LnRha2U= 41261 -bm9vcA== 41262 -IGx1bQ== 41263 -IGRpZmZlcmVudGlhbA== 41264 -IGVmZmljYWN5 41265 -CUlO 41266 -X0JPWA== 41267 -X3Nk 41268 -X3J0 41269 -Y29kZXI= 41270 -b3VuY2VtZW50 41271 -aGFzQ2xhc3M= 41272 -IHJpc2t5 41273 -IEVzdGFkbw== 41274 -LURE 41275 -IENhcnNvbg== 41276 -U3VmZml4 41277 -IHRvZGE= 41278 -IFRyYWNrZXI= 41279 -IERlbGVnYXRl 41280 -YCxg 41281 -IFBhcmtpbmc= 41282 -IG5lcg== 41283 -YXpv 41284 -IEZpbGVJbnB1dFN0cmVhbQ== 41285 -IHJlY291bnQ= 41286 -cWk= 41287 -Y2tlbg== 41288 -IHNvY2lhbGlzdA== 41289 -IEludm9pY2U= 41290 -INC/0YDQvg== 41291 -JSIs 41292 -ZW5uZW4= 41293 -IHZpdm8= 41294 -IG9yZ2FuaXphdGlvbmFs 41295 -IHVuY29tbW9u 41296 -dXRhcg== 41297 -IGh1bGw= 41298 -VHVlc2RheQ== 41299 -IGFzc2Vzc21lbnRz 41300 -KGFwcGxpY2F0aW9u 41301 -IHByZW1pc2U= 41302 -U3RhcnRUaW1l 41303 -IGRr 41304 -IGludGVyZmVy 41305 -IFF1ZWVuc2xhbmQ= 41306 -IGNyZWRlbnRpYWw= 41307 -IGxlaXN1cmU= 41308 -WVo= 41309 -IENtZA== 41310 -QlVT 41311 -dXNhbg== 41312 -CXZlYw== 41313 -aW9sb2dpY2Fs 41314 -IExvdHM= 41315 -IGVubGlnaHQ= 41316 -IGZyZXNobWFu 41317 -IENPTU1BTkQ= 41318 -IEFjdGlvbkxpc3RlbmVy 41319 -dXRt 41320 -YXJpdXM= 41321 -VHdpZw== 41322 -IHN3ZXB0 41323 -LXRvb2w= 41324 -xJA= 41325 -Y2hhcHRlcg== 41326 -LWdyYWRl 41327 -IGN1cmlvc2l0eQ== 41328 -IHN1c3RhaW5hYmlsaXR5 41329 -IE1pbmVjcmFmdA== 41330 -d2VuZA== 41331 -SWZFeGlzdHM= 41332 -IEN1bHR1cmFs 41333 -IFNhY3JhbWVudG8= 41334 -TGF5ZXJz 41335 -U3Vic2NyaWJlcg== 41336 -LkdyYXBo 41337 -IGxt 41338 -ZXN0eQ== 41339 -YWR2ZXJ0 41340 -JHA= 41341 -IEhvY2tleQ== 41342 -IERFVA== 41343 -c2V0VGl0bGU= 41344 -eWFuZw== 41345 -IGJhYmU= 41346 -ZWxzaXVz 41347 -VHJhdmVs 41348 -IG1lc21v 41349 -KG1hcFN0YXRlVG9Qcm9wcw== 41350 -X1NFTA== 41351 -LXBvcA== 41352 -IGVtaXNzaW9u 41353 -4oCZLgoK 41354 -LnN3aXRjaA== 41355 -b3Rpb25z 41356 -LnBob3Rv 41357 -TFY= 41358 -YW1vZGVs 41359 -IHdvcmR0 41360 -SUdHRVI= 41361 -IFRPREFZ 41362 -T0xT 41363 -X0lERU5U 41364 -IGNvbW1lbnRpbmc= 41365 -RGF0b3M= 41366 -IGhpbGFyaW91cw== 41367 -KGFueQ== 41368 -IGRhbXA= 41369 -LWNvbnRyb2xsZWQ= 41370 -ICI8Pw== 41371 -X2JsYWNr 41372 -TmV0QmFy 41373 -LnNldFNlbGVjdGVk 41374 -Q3Nz 41375 -IHF1YXJ0 41376 -IG93bmluZw== 41377 -IEZJRUxE 41378 -LnJlbHU= 41379 -IGxpcw== 41380 -7Jqw 41381 -LlJFTEFURUQ= 41382 -IGxvaw== 41383 -IEZsaXA= 41384 -IHByZXN0aWdpb3Vz 41385 -IGRn 41386 -IElucHV0U3RyZWFtUmVhZGVy 41387 -IHVzdQ== 41388 -IGdpcg== 41389 -IGFuYQ== 41390 -X3B5 41391 -dW5uZWw= 41392 -CXN5c3RlbQ== 41393 -IGNvYXRpbmc= 41394 -IEdlbnJl 41395 -ZXJybw== 41396 -IENMSUVOVA== 41397 -IHN0cmV0Y2hlZA== 41398 -Lkhhc1ZhbHVl 41399 -Ozs7Ozs7Ozs= 41400 -54mI 41401 -IGZpbmFscw== 41402 -LmdldENoaWxkcmVu 41403 -IC0tfX0K 41404 -IENvd2JveXM= 41405 -IEVkaW5idXJnaA== 41406 -IFBsYXph 41407 -YWJlbg== 41408 -QXJ0aXN0 41409 -VVJB 41410 -IEh1Z2hlcw== 41411 -b2JiaWVz 41412 -X25vaXNl 41413 -Lk9iamVjdHM= 41414 -RXhwcmVzc2lvbnM= 41415 -IGFudGhyb3A= 41416 -JykpDQo= 41417 -KS4i 41418 -Y3JpcHRpdmU= 41419 -IHNhbG1vbg== 41420 -IHdhc3Q= 41421 -cmhv 41422 -LnRpY2s= 41423 -IGV4cGxvcmVz 41424 -IEFsZ29yaXRobQ== 41425 -Q2hhckFycmF5 41426 -4LiE 41427 -X1BBQ0tFVA== 41428 -SkU= 41429 -Il1dOwo= 41430 -Lm5vdGU= 41431 -QmFja2luZw== 41432 -IEhvbGRlcg== 41433 -cmVpY2g= 41434 -IFppb24= 41435 -L2dy 41436 -ICAgICAgICAgICAgICAgICAgIAo= 41437 -TW90aW9u 41438 -IFRyaWJ1bmU= 41439 -IGNyaXRpY2FsbHk= 41440 -IENSTQ== 41441 -IGJsb3dpbmc= 41442 -IGNvbW1pc3Npb25lcg== 41443 -Sm9l 41444 -IFRlbGV2aXNpb24= 41445 -CXByZQ== 41446 -IFRSQU4= 41447 -IFZpa2luZ3M= 41448 -IEJFVA== 41449 -d291bGQ= 41450 -LkNhcHRpb24= 41451 -IGJhY29u 41452 -aG1h 41453 -bWVyZ2Vk 41454 -IHN1YnNjcmlwdGlvbnM= 41455 -b2NjdXBpZWQ= 41456 -TGl2ZURhdGE= 41457 -IGFsbG93YW5jZQ== 41458 -cmlnZXNpbWFs 41459 -ZGRk 41460 -LmxvZ291dA== 41461 -IFRhbmc= 41462 -IHdhcm10aA== 41463 -TW9kZWxJbmRleA== 41464 -IFByYQ== 41465 -IHNjZW50 41466 -IGhhY2tlcnM= 41467 -IGlsbHVzdHJhdGU= 41468 -SWNo 41469 -IGRpYXM= 41470 -Q0FTRQ== 41471 -IFNjaQ== 41472 -JHVybA== 41473 -IE1PRFVMRQ== 41474 -dXNob3J0 41475 -bGllcnM= 41476 -IERldmljZXM= 41477 -bWluc3Rlcg== 41478 -dW5hbWU= 41479 -IHVucg== 41480 -RXhhbXBsZXM= 41481 -IHJpc2Vu 41482 -LmFp 41483 -Y2hyb20= 41484 -X3dvcmtlcg== 41485 -IGFsaWFzZXM= 41486 -TW91c2VFdmVudA== 41487 -IHNldHRlcg== 41488 -IFB1cnBsZQ== 41489 -Sm9pbkNvbHVtbg== 41490 -PWU= 41491 -VEhPT0s= 41492 -IFRvdw== 41493 -IENydXNoaW5n 41494 -IEplZGk= 41495 -IEdyaWZmaW4= 41496 -IGtvcw== 41497 -X0ZT 41498 -aW5nZXM= 41499 -c29sZXM= 41500 -KG5hbWVz 41501 -IEJpZA== 41502 -LXBvd2VyZWQ= 41503 -TXVsdA== 41504 -YW1pbGlhcg== 41505 -LmNsZWFuZWQ= 41506 -IFppbW1lcg== 41507 -CWNsZWFy 41508 -IHVuc3VwcG9ydGVk 41509 -Q2FsbGFibGU= 41510 -IHJlcHM= 41511 -YWx0ZXJu 41512 -X1JFUE9SVA== 41513 -LmdldENvbHVtbkluZGV4 41514 -X1NUT1JF 41515 -IHN1Y2h0 41516 -c3VidGl0bGU= 41517 -IHBlcmQ= 41518 -q5g= 41519 -Lk5PVA== 41520 -fT48Lw== 41521 -OmQ= 41522 -bWRp 41523 -YmluZFZhbHVl 41524 -IERlY2lzaW9u 41525 -UmV0dXJuVmFsdWU= 41526 -LGluZGV4 41527 -eGZj 41528 -IHNlcnVt 41529 -Z2V0RmllbGQ= 41530 -Q29ubmVjdGlvblN0cmluZw== 41531 -LW9iamVjdA== 41532 -LnJlY3Y= 41533 -IHVuZGVyZ3JhZHVhdGU= 41534 -LkluZnJhc3RydWN0dXJl 41535 -IEthYg== 41536 -IGFkdmlzb3J5 41537 -LXRyZWU= 41538 -IG11ZQ== 41539 -aW5mb3Jt 41540 -LmVtYmVk 41541 -IGVycm9yQ29kZQ== 41542 -bWljcm8= 41543 -IHNwYXJrZWQ= 41544 -IGltYWdlcnk= 41545 -Y29uYw== 41546 -X21pc3Npbmc= 41547 -IHN1cnBsdXM= 41548 -S1M= 41549 -CVJUSE9PSw== 41550 -VGVsbA== 41551 -cml1bQ== 41552 -IFJhZGl1cw== 41553 -cmlrYQ== 41554 -bG9zaW9u 41555 -IEhlcm4= 41556 -R2FtbWE= 41557 -IEZlZQ== 41558 -IE5hbWVk 41559 -IENhbnlvbg== 41560 -IEpTT05BcnJheQ== 41561 -IHp3ZWk= 41562 -IFNTSA== 41563 -IHNlcnZhbnQ= 41564 -Y29hbA== 41565 -IGRlbnlpbmc= 41566 -IHNwbGl0cw== 41567 -SW5jb3JyZWN0 41568 -IHRveA== 41569 -IEFuYWx5c3Q= 41570 -IGFjY3JlZA== 41571 -dWJsZQ== 41572 -IHd0 41573 -IFRyaWFs 41574 -LmV4dGVuc2lvbg== 41575 -IENhcmVlcg== 41576 -IHNlY3VyaW5n 41577 -IExpbA== 41578 -IHByb2plY3Rpb25z 41579 -IHllYXN0 41580 -TWFkZQ== 41581 -IGZvdW5kYXRpb25z 41582 -YWNpZmlj 41583 -LnZvbHVtZQ== 41584 -IG1pcnJvcnM= 41585 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM= 41586 -IHZpb2xhdGU= 41587 -YXJzZXJz 41588 -IHNvY2lv 41589 -IHRraW50ZXI= 41590 -IExJTks= 41591 -LmdldFNpemU= 41592 -IFdob2xl 41593 -KXZpZXdEaWRMb2Fk 41594 -CWRvbmU= 41595 -dWRlYXU= 41596 -XCI+PC8= 41597 -QW5kcmV3 41598 -ZXJi 41599 -IGbDtg== 41600 -LmNsdXN0ZXI= 41601 -IGRpc2NvdXJzZQ== 41602 -X0RFRklO 41603 -IHB1ZWRlbg== 41604 -IExPVw== 41605 -LmF2 41606 -IHByZWNh 41607 -IHF1bw== 41608 -IHZlbG9j 41609 -LCcn 41610 -IHh5eg== 41611 -CXBhZGRpbmc= 41612 -IHRvbWF0b2Vz 41613 -IEJlbnQ= 41614 -X2N1cnI= 41615 -TlNEYXRl 41616 -IGdldEN1cnJlbnQ= 41617 -IFtg 41618 -V2VkbmVzZGF5 41619 -LkJhcg== 41620 -IFZvdXM= 41621 -aW56 41622 -IFF1aW5u 41623 -ZXhjZWw= 41624 -ZG9z 41625 -IG91dGRhdGVk 41626 -T1VUSA== 41627 -IE1ha2Vy 41628 -ZXBlbmRlbmN5 41629 -IGR1bGw= 41630 -IFdpbm4= 41631 -b2dl 41632 -Y2xhdmU= 41633 -IG5vdmE= 41634 -IGF2YWw= 41635 -Q2FwdA== 41636 -IFNwb3RpZnk= 41637 -IGp1bA== 41638 -KXRhYmxlVmlldw== 41639 -IGZpbGVuYW1lcw== 41640 -IGVza29ydA== 41641 -5ZGo 41642 -IHNrZXc= 41643 -dGVyaW9y 41644 -IGZpbmFuYw== 41645 -IHRhYmxh 41646 -IFVJQg== 41647 -ICgpOg== 41648 -IERvY2tlcg== 41649 -cGVyY2VudGFnZQ== 41650 -TWVldA== 41651 -aWNoaQ== 41652 -IGludGVyaW0= 41653 -ICc9Jw== 41654 -LkpTT05PYmplY3Q= 41655 -KGZpZA== 41656 -IGRvd250 41657 -IHRyYW5zaWVudA== 41658 -IFN0ZXBo 41659 -IGlnbm9yYW5jZQ== 41660 -IENvZGVz 41661 -PScnLA== 41662 -IElDRQ== 41663 -IHRyYW5xdQ== 41664 -IEV4dGVuZGVk 41665 -IG11bmQ= 41666 -IEhPTUU= 41667 -IGtpbG9tZXRlcnM= 41668 -IGltYWdlbg== 41669 -b3V4 41670 -KHN6 41671 -WW91bmc= 41672 -dWZmZWQ= 41673 -IFdha2U= 41674 -IGFpZGU= 41675 -UFJPQw== 41676 -IFJhdA== 41677 -IExpdGg= 41678 -YmFydA== 41679 -IEFycmFuZ2U= 41680 -cHJvbXB0 41681 -0KM= 41682 -KGN0 41683 -IEludGVydmFs 41684 -ZGVwdA== 41685 -RGFuaWVs 41686 -IGZpbGxz 41687 -LnRlbnNvcg== 41688 -KHRyaW0= 41689 -IGplYWxvdXM= 41690 -RmVi 41691 -XENvbW1vbg== 41692 -IGFtZW5kbWVudHM= 41693 -X29wZXJhdG9y 41694 -X2N1c3RvbWl6ZQ== 41695 -IF1d 41696 -IGJu 41697 -IGRpc2FwcG9pbnRtZW50 41698 -IG1pbGxlbm4= 41699 -LndoZW4= 41700 -IG9iZXk= 41701 -IG9mZmVuZGVycw== 41702 -V2lsZA== 41703 -IGNlbGxGb3I= 41704 -IGFwcGFyYXR1cw== 41705 -LmFmdGVy 41706 -IEVQUw== 41707 -IGFkb3JhYmxl 41708 -b3BlcmFuZA== 41709 -KGxpc3RlbmVy 41710 -dmVhbA== 41711 -ICko 41712 -IGNhcmRpb3Zhc2N1bGFy 41713 -dXBsaWNhdGVz 41714 -cmlzdG9s 41715 -IHJlZnVzZXM= 41716 -KFFXaWRnZXQ= 41717 -IGVsZW1lbnRv 41718 -TnVtYmVyT2Y= 41719 -LmRlbGF5 41720 -Lmdyb3Vwcw== 41721 -Ij4nKw== 41722 -5Z2A 41723 -YWNlbmN5 41724 -KFVSTA== 41725 -X2hhbGY= 41726 -PWw= 41727 -IGxpc3RWaWV3 41728 -KHNlY3Rpb24= 41729 -LnRvQXJyYXk= 41730 -Ky8= 41731 -IFJvZHJpZ3Vleg== 41732 -aXN0cmVhbQ== 41733 -IGVsaWdpYmlsaXR5 41734 -Ojot 41735 -Lm5ld0luc3RhbmNl 41736 -UEI= 41737 -IEFzc2V0cw== 41738 -IENvbXBvc2l0ZQ== 41739 -IExhYnM= 41740 -IEhhbWFz 41741 -KyspOwo= 41742 -IGJsaw== 41743 -IE5lbw== 41744 -THVj 41745 -QGxvZ2lu 41746 -IHVuYXdhcmU= 41747 -Lm1ldA== 41748 -X1JFTEVBU0U= 41749 -KFNU 41750 -QU1JTA== 41751 -cmlrZQ== 41752 -ICgpewo= 41753 -KHNwcmludGY= 41754 -IEFjY291bnRz 41755 -IFZJRVc= 41756 -IEFq 41757 -44Kw 41758 -IHdoaXNr 41759 -IGlkaQ== 41760 -IHJvZGU= 41761 -IGlobg== 41762 -IEVsZW1lbnRhcnk= 41763 -UXR5 41764 -IGludHJpZ3Vpbmc= 41765 -IOWk 41766 -Sm9icw== 41767 -CW9mZnNldA== 41768 -IEFobWVk 41769 -IFRhbGliYW4= 41770 -IOiOt+WPlg== 41771 -IGluamVjdGVk 41772 -LkF1dGhlbnRpY2F0aW9u 41773 -X2xpbmVhcg== 41774 -LkRlY2ltYWw= 41775 -IGFwcGxlcw== 41776 -IHNoYXJlaG9sZGVycw== 41777 -IGJha2Vk 41778 -LmRpZmY= 41779 -IEVkZGll 41780 -b2tlcnM= 41781 -IGNvbmZyb250ZWQ= 41782 -dm9pY2Vz 41783 -IHR1cw== 41784 -IFNwaW4= 41785 -Tk9ERQ== 41786 -X1Vu 41787 -Q1RY 41788 -L2dvb2dsZQ== 41789 -VGVtcGVyYXR1cmU= 41790 -ICcnKS4= 41791 -IG1hZ25pZmljZW50 41792 -IHN0YXJ0SW5kZXg= 41793 -c2VtYmxlcw== 41794 -QW55b25l 41795 -ems= 41796 -ZWhlbg== 41797 -IERhbWU= 41798 -LnN0cmljdA== 41799 -IHJlcGxhY2Vz 41800 -IGxpbmViYWNr 41801 -IHB1c2hlcw== 41802 -IGNoZWVr 41803 -IFNoaQ== 41804 -X0JZVEVT 41805 -UkVB 41806 -4bqjbg== 41807 -X0NPTk5FQ1RJT04= 41808 -R2F0ZXdheQ== 41809 -IFRyYXZpcw== 41810 -IEFY 41811 -IEJhc2ljYWxseQ== 41812 -IFVwZ3JhZGU= 41813 -4Ko= 41814 -dGhlbWVz 41815 -ZXJtbw== 41816 -a29y 41817 -RmVtYWxl 41818 -X2F0dGFjaA== 41819 -IOyCrOyaqQ== 41820 -IHBveg== 41821 -PT09PT09PT09PT09PT0K 41822 -KHN5bWJvbA== 41823 -IFNlY3Rvcg== 41824 -X18pCgo= 41825 -X3BhZGRpbmc= 41826 -77yaIg== 41827 -IGZhYnM= 41828 -IHJhbmdlZA== 41829 -c2V0TmFtZQ== 41830 -IHBlcnJvcg== 41831 -4pc= 41832 -IEZpbGVSZWFkZXI= 41833 -IGZ1bGZpbGxlZA== 41834 -X0N1cnJlbnQ= 41835 -IGRvbWluYXRl 41836 -IHNtdWdn 41837 -UG9zdE1hcHBpbmc= 41838 -X2ZvcmNl 41839 -IGJsb2M= 41840 -IEdpYW50 41841 -KHZpZGVv 41842 -IENV 41843 -U3lzdGVtU2VydmljZQ== 41844 -IGVsZg== 41845 -IGtvbnRha3Q= 41846 -66o= 41847 -a2Vlcw== 41848 -Z3Rr 41849 -IHBhcmFtSW50 41850 -IG1hcmt1cA== 41851 -dWFsZXM= 41852 -IGFjY291bnRlZA== 41853 -IGdhbmdiYW5n 41854 -UllQVA== 41855 -IFdyb25n 41856 -IGNyZWRpdGVk 41857 -IE1FU1NBR0U= 41858 -IGZsYXdz 41859 -IGJidw== 41860 -IG1ldGFib2xpYw== 41861 -IE9FTQ== 41862 -L2V2ZW50 41863 -KENvbGxlY3RvcnM= 41864 -bW9udG9u 41865 -YXBwZWFy 41866 -IG9wdGVk 41867 -IGNoZWF0 41868 -IGRhdg== 41869 -IFByb2NlZWQ= 41870 -IOq4 41871 -YW5rZWQ= 41872 -0LjQtw== 41873 -YW5zaw== 41874 -IEhhbmc= 41875 -IENsZXI= 41876 -IGRpc2d1 41877 -IGNtYXA= 41878 -LmNsanM= 41879 -IGF1bWVudA== 41880 -bGV6 41881 -IEpvaW5lZA== 41882 -X3JlY2VpdmVk 41883 -IGFlcmlhbA== 41884 -b3RlbA== 41885 -IGdyZWV0 41886 -InM= 41887 -IEdlbmVzaXM= 41888 -IENhbGlm 41889 -cGFuaW9u 41890 -IHRhaWxvcmVk 41891 -bWFwcGluZw== 41892 -YW5kRXhwZWN0 41893 -LnRyYWNr 41894 -YXRvbXk= 41895 -IE93 41896 -dWxsYWg= 41897 -Llllcw== 41898 -IFNpbXBsZU5hbWU= 41899 -ZGJo 41900 -J2Vu 41901 -IG5vbnNlbnNl 41902 -IHBoaWxvc29waGljYWw= 41903 -KGdldENvbnRleHQ= 41904 -IGlzc28= 41905 -IEFDRQ== 41906 -c3RhcnREYXRl 41907 -IGLEmWQ= 41908 -IEFVVEhPUg== 41909 -IEdsb2Jl 41910 -IGluc2VjdHM= 41911 -X0Fs 41912 -dXNoaW5n 41913 -6K6w 41914 -L0hvbWU= 41915 -IExvY2FsRGF0ZQ== 41916 -bmVlZGVk 41917 -aGVzaXZl 41918 -IGlsbHVzaW9u 41919 -5LqM 41920 -IHRyYXQ= 41921 -eG8= 41922 -L2RldGFpbA== 41923 -X01BVENI 41924 -IGJyb2FkYmFuZA== 41925 -IHdhbA== 41926 -IElsbGVnYWxTdGF0ZUV4Y2VwdGlvbg== 41927 -SVJFQ1RJT04= 41928 -IG5vcnRoZWFzdA== 41929 -ZXNpdW0= 41930 -IENsaWVudGU= 41931 -dWxhbmNl 41932 -bnR5 41933 -IHRlY24= 41934 -RGV2aWNlcw== 41935 -IGdyYWlucw== 41936 -IE9n 41937 -IFNFTA== 41938 -dWRpYW50 41939 -ICsrOwo= 41940 -IGV4cGxhbmF0aW9ucw== 41941 -b2Njbw== 41942 -IGRpZXRz 41943 -IGNvaG9ydA== 41944 -KGNvbnRyb2xsZXI= 41945 -Lkl0ZXJhdG9y 41946 -LXJpY2g= 41947 -cm9jZXNz 41948 -R0Q= 41949 -IGNhcmJvaHlkcg== 41950 -IGZyaWVk 41951 -IEVtcGxveW1lbnQ= 41952 -7J6l 41953 -IExlb25hcmQ= 41954 -XyR7 41955 -cXVhcmVz 41956 -IGNvbXBhbmlvbnM= 41957 -IHBhcmlz 41958 -IHN0aW11bGF0aW9u 41959 -IFpvbw== 41960 -IHJlbGV2YW5jZQ== 41961 -IENvbG91cg== 41962 -IHNwZWFy 41963 -b3Rpb25hbA== 41964 -IExpdGU= 41965 -IEtvc3Rlbg== 41966 -IMOz 41967 -X2F0dGFjaG1lbnQ= 41968 -b3JwaGlj 41969 -IGRhbWl0 41970 -IGRsZw== 41971 -IHRocml2ZQ== 41972 -Q0hBTkdF 41973 -IEFwcGFyZW50bHk= 41974 -IGF0dWFs 41975 -IHJvb3RlZA== 41976 -KGltYWdlcw== 41977 -YXdp 41978 -YXJpYXQ= 41979 -IGNoZXJyeQ== 41980 -U1RBVElD 41981 -bW50 41982 -IFVzZXJJZA== 41983 -aWxsZXQ= 41984 -IEhpc3Bhbmlj 41985 -IG5haw== 41986 -IGNlbnRybw== 41987 -IGRpbXM= 41988 -X2luaXRpYWxpemU= 41989 -xLFr 41990 -IENlbnRlcnM= 41991 -UkVO 41992 -IGV2b2x1dGlvbmFyeQ== 41993 -IFRvcGljcw== 41994 -X2RhbWFnZQ== 41995 -ZW1lcg== 41996 -IHJ1bmQ= 41997 -IHB1bmlzaGVk 41998 -IGN1Ymlj 41999 -ZmFpcg== 42000 -W107Cgo= 42001 -IGluc3RhbnRpYXRl 42002 -IG92ZXJzZWU= 42003 -LWRlbGV0ZQ== 42004 -dW50ZWVy 42005 -c3RhcnRUaW1l 42006 -IFBpcGVsaW5l 42007 -X0dBTUU= 42008 -IENpcg== 42009 -CU51bGw= 42010 -LkZvcm1hdHRpbmc= 42011 -dWN1bWJlcg== 42012 -IFJpZGU= 42013 -IHpvbw== 42014 -IGNoZWNrZXI= 42015 -5ZCM 42016 -PUM= 42017 -IGdyaXQ= 42018 -Iik7Ly8= 42019 -X3h5 42020 -IERlY2xhcmF0aW9u 42021 -IGNhbGxhYmxl 42022 -Rm9v 42023 -IExpc3RJdGVt 42024 -IGluYWNjdXI= 42025 -bWxpbg== 42026 -CURhdGE= 42027 -IGV2b2x2aW5n 42028 -YXdhbg== 42029 -IGNhZmU= 42030 -Zm9saw== 42031 -X0lEWA== 42032 -IEFueXRoaW5n 42033 -IFBhbGVzdGluZQ== 42034 -IEdyaWRWaWV3 42035 -IGNvbG9ueQ== 42036 -IEdlcm1hbnM= 42037 -KCs= 42038 -LnBpZA== 42039 -LmpzeA== 42040 -IFN1cGVyaW9y 42041 -Q2hyaXN0aWFu 42042 -IExlY3Q= 42043 -CUdhbWU= 42044 -IGluc3RydW1lbnRhbA== 42045 -QW5pbWF0aW9ucw== 42046 -0LTQsNC7 42047 -IE1vc2Vz 42048 -CQkNCgkJDQo= 42049 -enM= 42050 -a3Rl 42051 -5Lia 42052 -X0RJU1Q= 42053 -Yml0bWFw 42054 -ZEI= 42055 -IHBlcnNpc3RlbmNl 42056 -0YDQvtGB 42057 -JGw= 42058 -QnJvbg== 42059 -IHt8 42060 -X2NoYXJ0 42061 -IENvbnN1bQ== 42062 -IGhlbXA= 42063 -ICIpKQo= 42064 -IGF0dGFja2Vycw== 42065 -IGtub3dsZWRnZWFibGU= 42066 -IGNldA== 42067 -IHZpcnVzZXM= 42068 -J0k= 42069 -IHBpdGNoZXI= 42070 -IHN3ZWVwaW5n 42071 -PWxpc3Q= 42072 -YXB0b3Bz 42073 -LmRlcHRo 42074 -IGluc3RydWN0ZWQ= 42075 -IFJ1cw== 42076 -YmVuaGF2bg== 42077 -INC40L0= 42078 -U3BvcnRz 42079 -IG9uc2V0 42080 -5p2D 42081 -LlJFRA== 42082 -X3Np 42083 -IFBTVA== 42084 -Lm9uQ2hhbmdl 42085 -PnRhZw== 42086 -IFJvaA== 42087 -X2NoYXJhY3Rlcg== 42088 -IExhd3M= 42089 -IEJhY2hlbG9y 42090 -X3N3YXA= 42091 -LnJlYWN0aXZleA== 42092 -IHJld2FyZGluZw== 42093 -TWVkaXVt 42094 -LVs= 42095 -IFJlY2VudGx5 42096 -Sm9pbnQ= 42097 -cGFydGl0aW9u 42098 -IE1pbnV0ZXM= 42099 -IGluZG8= 42100 -IGFic29yYmVk 42101 -IEdO 42102 -X0lORA== 42103 -IHNhYmVy 42104 -U3Bhd24= 42105 -b3V0cHV0cw== 42106 -IEplZmZyZXk= 42107 -IG1lZGlldmFs 42108 -aGVk 42109 -R3VpZGU= 42110 -IHBzeWNobw== 42111 -IGdsYW0= 42112 -RWxpbQ== 42113 -w6RkY2hlbg== 42114 -X3BsYWlu 42115 -IFNhdQ== 42116 -LWZvdXI= 42117 -IGFuYWx5emluZw== 42118 -UVVFUlk= 42119 -IHRvbWF0bw== 42120 -X2J1dHRvbnM= 42121 -VkVO 42122 -LnNldFN0YXR1cw== 42123 -LlVybA== 42124 -KwoK 42125 -IGNvbXBsYWluaW5n 42126 -ZGVncmVl 42127 -Y29uZmlybWVk 42128 -IHN1YnQ= 42129 -cGFyc2Vk 42130 -IHRvcnF1ZQ== 42131 -IHRyb3VibGVk 42132 -IFRBUkdFVA== 42133 -IHRyYWRlbWFya3M= 42134 -IENvb3JkaW5hdGU= 42135 -IFZpdg== 42136 -IC8vfQoK 42137 -IGFwcsOocw== 42138 -LmdldFBvc2l0aW9u 42139 -KEtleUNvZGU= 42140 -IFNpbHZh 42141 -IG1ldGVvcg== 42142 -IGVuZG9yc2VtZW50 42143 -T3ZlcnZpZXc= 42144 -IFBvc3M= 42145 -LkluamVjdA== 42146 -IGV2ZW5seQ== 42147 -IHZpc3VhbGl6YXRpb24= 42148 -IHdjaGFy 42149 -IEhETUk= 42150 -IGZ1bmN0 42151 -aWNrbmFtZQ== 42152 -JywnJywn 42153 -IGZvcndhcmRz 42154 -TWFuYWdlZE9iamVjdA== 42155 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 42156 -CXNlcnZlcg== 42157 -IE91dGxvb2s= 42158 -IENocm9uaWNsZQ== 42159 -IGR1YmJlZA== 42160 -IGRvaw== 42161 -IFdlYXI= 42162 -LkFM 42163 -cGFyZW4= 42164 -LkludGVyZmFjZQ== 42165 -SW50ZXJmYWNlcw== 42166 -LmNvZA== 42167 -IGRpYg== 42168 -Lkdsb2JhbGl6YXRpb24= 42169 -IEFjYWRlbWlj 42170 -IGFzc21z 42171 -QXV0b20= 42172 -IGx3 42173 -IE5X 42174 -ICYmDQo= 42175 -IHByb2JsZW1h 42176 -IE1hbnVmYWN0dXJpbmc= 42177 -bGltaXRz 42178 -LW1vYmlsZQ== 42179 -IGZpbG1l 42180 -L21hcA== 42181 -IGRvaXQ= 42182 -IEluaw== 42183 -IHN1ZWQ= 42184 -LmFycg== 42185 -IHVuZGVybWlu 42186 -IFByb2M= 42187 -Y3JvbGxWaWV3 42188 -X18k 42189 -IHNpZGV3YWxr 42190 -KHRoYXQ= 42191 -4Li3 42192 -W3E= 42193 -Z3JhbW1hcg== 42194 -IHTDqw== 42195 -cXVpdG8= 42196 -IHNwaXJhbA== 42197 -ZXh0ZW5kZWQ= 42198 -IGZvY2Fs 42199 -IGRpZ2dpbmc= 42200 -cGFz 42201 -IFRhbGw= 42202 -LnByb3h5 42203 -aXR1cmVz 42204 -VFJBQ1Q= 42205 -IFJlYWxt 42206 -IGZlZGVy 42207 -IG9yaWVudGVk 42208 -IEFsdGVybmF0aXZl 42209 -IG93ZQ== 42210 -IHNvdXJjZWQ= 42211 -aW5rZXI= 42212 -LmRldA== 42213 -U2Vw 42214 -IFF1aQ== 42215 -IFBhbG1lcg== 42216 -KF8s 42217 -c2FtcGxlcw== 42218 -b3llcg== 42219 -dWxsYW4= 42220 -cXVleg== 42221 -RWRnZXM= 42222 -IHNob3V0 42223 -IEFjaGll 42224 -IGhhYXI= 42225 -X0NvbnN0cnVjdA== 42226 -IHByZW1hdHVyZQ== 42227 -IHJldmVydA== 42228 -JykuCg== 42229 -IHNjaG4= 42230 -ZmlsdGVyZWQ= 42231 -bnVsbHB0cg== 42232 -U2F2ZWQ= 42233 -aXRlY3R1cmU= 42234 -Q0xB 42235 -IHZs 42236 -c3RlbGw= 42237 -CU1l 42238 -IExpcA== 42239 -bmF0aW9uYWw= 42240 -IHdob2xseQ== 42241 -IHNwcmluZ3M= 42242 -LlRpbWVy 42243 -CXNyYw== 42244 -ZWxzZW4= 42245 -5YW2 42246 -IGNvbW11bmljYXRpbmc= 42247 -IFF1aXo= 42248 -IHRlbmc= 42249 -IGdleg== 42250 -IE91dHNpZGU= 42251 -LlNpZ24= 42252 -KGNz 42253 -IGRpc3B1dGVz 42254 -IFdlaXNz 42255 -YW5uZXM= 42256 -Pk5v 42257 -IEJhY2g= 42258 -LnJlbW92ZUFsbA== 42259 -cmVmZXI= 42260 -L2Rhc2hib2FyZA== 42261 -IEFqYXg= 42262 -SW5kZXhDaGFuZ2Vk 42263 -IFdlYWs= 42264 -JyIK 42265 -IHNpZ2h0cw== 42266 -YWNjZXNzVG9rZW4= 42267 -IEpvaQ== 42268 -KGRvbWFpbg== 42269 -CWN2 42270 -IGNvbnRpbnVhdGlvbg== 42271 -IHBsdW0= 42272 -YWRpcg== 42273 -LnNldE1lc3NhZ2U= 42274 -IO+8jA== 42275 -IHN3YWxsb3c= 42276 -IExhbXA= 42277 -IHF3 42278 -IHV1 42279 -Q29pbg== 42280 -dWJpYw== 42281 -IERlYWxz 42282 -cmFjZQ== 42283 -IGRpY3RhdG9y 42284 -IG1lbWU= 42285 -dHVybmVk 42286 -IEp1bGll 42287 -LmdyaWRDb2x1bW4= 42288 -IHB1cHB5 42289 -IHBhbQ== 42290 -ICl7DQo= 42291 -IGludml0aW5n 42292 -IGZyZW5jaA== 42293 -dmlt 42294 -IHdyYXBwaW5n 42295 -ICMtfQo= 42296 -KFst 42297 -RWFybHk= 42298 -IHNoaW55 42299 -LmZhY2Vz 42300 -IHJlYmVsbA== 42301 -YWJjZGVm 42302 -w6RsdA== 42303 -IGVzdGltYXRpb24= 42304 -cGh5cw== 42305 -bG9zdXJlcw== 42306 -X1JFTA== 42307 -IGV4Y2x1c2lvbg== 42308 -IFNreXBl 42309 -d2Vpc2U= 42310 -LXN0b3A= 42311 -bm90aGluZw== 42312 -IEVnZw== 42313 -aXNvcnM= 42314 -UmljaGFyZA== 42315 -IGNvdW5zZWxpbmc= 42316 -IGNvbW1lbQ== 42317 -IFFNZXNzYWdlQm94 42318 -IFN5bmQ= 42319 -IEZyb3N0 42320 -IENvbXBldGl0aW9u 42321 -IEF3YWtl 42322 -IHRlZA== 42323 -aWNpb25lcw== 42324 -IERldkNvbXBvbmVudHM= 42325 -VkVSVElTRU1FTlQ= 42326 -b3R0aQ== 42327 -LnJ1bm5lcg== 42328 -IHVuaXF1ZWx5 42329 -LmZsYWc= 42330 -CXJz 42331 -X2dlbmVyaWM= 42332 -IGBgYAo= 42333 -QUNISU5F 42334 -IG1laW4= 42335 -KEFwcGxpY2F0aW9u 42336 -KGJy 42337 -IHJhdGlvcw== 42338 -Oiw= 42339 -IFhDVGVzdA== 42340 -dXN0YWluYWJsZQ== 42341 -LXd3dw== 42342 -aXRsZXM= 42343 -X1RFTVA= 42344 -IHN5c3Q= 42345 -dW1lcmljVXBEb3du 42346 -CWFzc2VydFRydWU= 42347 -IHdm 42348 -LnBlZWs= 42349 -IEJ1bGc= 42350 -IHRlcnJpZnlpbmc= 42351 -Lk1PREU= 42352 -IEdX 42353 -w6Fy 42354 -IGZpYw== 42355 -IGNvbW1pdG1lbnRz 42356 -LXRlY2g= 42357 -IExpcXVpZA== 42358 -b3Bleg== 42359 -emhlaW1lcg== 42360 -YcOxYQ== 42361 -LW1lZGlh 42362 -KGFuaW1hdGVk 42363 -X2dvYWw= 42364 -IGd1bQ== 42365 -eXN0b25l 42366 -LlNFVA== 42367 -IFdlbmQ= 42368 -c2V0Q2VsbFZhbHVl 42369 -IG1zZ3M= 42370 -Y2FzaA== 42371 -QUxMT0M= 42372 -L2F3cw== 42373 -IG1pY3Jvd2F2ZQ== 42374 -LlBvaW50ZXI= 42375 -CUNvbnNvbGU= 42376 -X3NvcnRlZA== 42377 -IEZpbGlw 42378 -UHJvZA== 42379 -IC8vITw= 42380 -aW5ncm91cA== 42381 -IGtz 42382 -X1RSSQ== 42383 -IHRlYXNwb29u 42384 -IEFUVA== 42385 -IHJlY292ZXJpbmc= 42386 -IEdMT0JBTA== 42387 -LlBhcg== 42388 -IC8+Owo= 42389 -IG1hcmJsZQ== 42390 -dWxhdG9ycw== 42391 -IEN5Y2xl 42392 -IGhlcmJz 42393 -X21ldHJpYw== 42394 -KSE= 42395 -X0NMT0NL 42396 -X0J1dHRvbg== 42397 -SGFycnk= 42398 -6L+b 42399 -IHN0cmFpbnM= 42400 -IEFwcEJhcg== 42401 -IENoYW4= 42402 -L3ZpZGVv 42403 -IGJhbQ== 42404 -LlByb2dyZXNz 42405 -JGY= 42406 -bGVtZW4= 42407 -IGlycmVndWxhcg== 42408 -IER1bmNhbg== 42409 -IE1pbnQ= 42410 -LXZpZGVv 42411 -4Ka+ 42412 -w7N3bg== 42413 -IEVNUFRZ 42414 -IHN0YWNrZWQ= 42415 -IEhB 42416 -X2N1dA== 42417 -IHdoZXJlaW4= 42418 -IFdheXM= 42419 -KGNvdW50ZXI= 42420 -6K+V 42421 -Rm9ybUdyb3Vw 42422 -IGJsZXc= 42423 -Y291cnNlcw== 42424 -IHByb2R1Y3Rvcw== 42425 -cnlz 42426 -IFJlc3Ry 42427 -IHN0eWxpbmc= 42428 -PnM= 42429 -IHBpdg== 42430 -IGl0ZXJ0b29scw== 42431 -Z2V0UmVwb3NpdG9yeQ== 42432 -IElr 42433 -X2RldmljZXM= 42434 -bGF5dWk= 42435 -IGhhbGZ3YXk= 42436 -IGZyYW7Dpw== 42437 -IHR1bmluZw== 42438 -T0E= 42439 -X05vZGU= 42440 -YXJkZQ== 42441 -IGZpZXJjZQ== 42442 -bGljdGVk 42443 -Iw0K 42444 -IGJyZWFrdGhyb3VnaA== 42445 -IEVyaWs= 42446 -IGJyaWRl 42447 -IC4i 42448 -Y3VsdXM= 42449 -aW5zaWRl 42450 -IEluZGlhbmFwb2xpcw== 42451 -IEVF 42452 -IHlvZw== 42453 -dXJyZXQ= 42454 -LmZz 42455 -LmdyYWQ= 42456 -X2NhcmRz 42457 -X2FjY3VyYWN5 42458 -X2VwaQ== 42459 -cXVlZGE= 42460 -L29yZw== 42461 -6aqM 42462 -IGNvbXB0ZQ== 42463 -KSlb 42464 -T3V0c2lkZQ== 42465 -R3JlYXRlcg== 42466 -IFJlbmRlcmVy 42467 -LmFjdG9y 42468 -QWNjb3VudHM= 42469 -SWRsZQ== 42470 -X2hvdXJz 42471 -ZXJuZXI= 42472 -Sm9pbmVk 42473 -IG1lbmo= 42474 -cmVxdWlyZXM= 42475 -IE9QRVI= 42476 -LnJlbW92ZUNoaWxk 42477 -CXNw 42478 -IGVzc2U= 42479 -cmlmdA== 42480 -eEZF 42481 -IFNoYWtlc3BlYXJl 42482 -X19fX19fX19fX19f 42483 -IGJ1ZGdldHM= 42484 -TW9kZWxTdGF0ZQ== 42485 -ZmlsbGFibGU= 42486 -LWNvbXBvbmVudA== 42487 -b2Nvcw== 42488 -IEJVVFRPTg== 42489 -L2lv 42490 -LG91dA== 42491 -c21z 42492 -VGhvbWFz 42493 -IEFybWVk 42494 -cmVzdW1l 42495 -IHJvdGF0aW5n 42496 -IFZhdWx0 42497 -IHNldXM= 42498 -Ligq 42499 -IGFtaW5v 42500 -IFtdKTsKCg== 42501 -IHByb3ZvYw== 42502 -bm94 42503 -LkdldEVudW1lcmF0b3I= 42504 -PT09PT09PQo= 42505 -5paZ 42506 -X3Njcm9sbA== 42507 -IGZpbG1lZA== 42508 -IFNvY2k= 42509 -Z2Fw 42510 -Z3Jv 42511 -Vm90ZQ== 42512 -IkJ1dA== 42513 -X1JD 42514 -QW5pbWFs 42515 -woA= 42516 -aWJpbGU= 42517 -IGF3YWtlbg== 42518 -b3Jlc3Q= 42519 -aW5qYQ== 42520 -IEl2YW4= 42521 -KENvbW1hbmQ= 42522 -ICoqKioq 42523 -zrc= 42524 -IGt2aW5kZXI= 42525 -L2hlbHBlcnM= 42526 -X2Nhc2Vz 42527 -dGc= 42528 -7IS4 42529 -UmVnaXN0ZXJlZA== 42530 -CXBhc3M= 42531 -X2RpZ2l0cw== 42532 -IGNvbnRvdXI= 42533 -IGluZmFudHM= 42534 -IGp1c3RpZmljYXRpb24= 42535 -IEZvcnR1bmF0ZWx5 42536 -Q29udHI= 42537 -IG9uQ3JlYXRlVmlldw== 42538 -X1NBTVBMRQ== 42539 -IGFsbG93TnVsbA== 42540 -IG51ZA== 42541 -IGZldGNoZWQ= 42542 -X2VxdQ== 42543 -IFVuYWJsZQ== 42544 -PVwiIg== 42545 -PnsK 42546 -IGNvbW1pdHRlZXM= 42547 -aXN0ZW1h 42548 -KyIu 42549 -w61hbg== 42550 -bWFudA== 42551 -IHNvdXRoZWFzdA== 42552 -77yMCg== 42553 -ZGlhbG9ncw== 42554 -UFJPSkVDVA== 42555 -Y2hhcmdlcg== 42556 -LXBvcnQ= 42557 -KHV1aWQ= 42558 -LmV4cG9ydA== 42559 -U2l4 42560 -IFJQ 42561 -UHJlbQ== 42562 -IGNvbnNjaWVuY2U= 42563 -IG1hcmdpblJpZ2h0 42564 -X2Rpc3RyaWJ1dGlvbg== 42565 -eWFtbA== 42566 -cmVzaXppbmc= 42567 -RG9jaw== 42568 -IExvY2F0aW9ucw== 42569 -R1k= 42570 -U2VlZA== 42571 -QlVGRkVS 42572 -b3NzaXA= 42573 -dWxsZW4= 42574 -VGhpbmdz 42575 -LXNlbGY= 42576 -LnBvbGw= 42577 -UExBWUVS 42578 -IOWu 42579 -R1JPVVA= 42580 -IEF3YXk= 42581 -IGdvc3BlbA== 42582 -eGZk 42583 -TWFyeQ== 42584 -IFBvcnRhYmxl 42585 -VFVSRQ== 42586 -IHV0aWxpcw== 42587 -IHNlaXQ= 42588 -IHN0cmFuZA== 42589 -IHRyYW5zYw== 42590 -IChe 42591 -IEFsZnJlZA== 42592 -Lm1lbQ== 42593 -LmNpcmNsZQ== 42594 -IH4v 42595 -Zm9yY2luZw== 42596 -IHJpb3Q= 42597 -cHJveA== 42598 -VEhPTg== 42599 -aXphY2nDs24= 42600 -IE5J 42601 -cm9zdA== 42602 -IGRpc3Bybw== 42603 -X2luc3RhbmNlcw== 42604 -77yM4oCc 42605 -b2dyYXBoZXI= 42606 -ZW5kYXM= 42607 -IElzYWFj 42608 -IFBpbmU= 42609 -L2Rpcw== 42610 -IGNvbG9yV2l0aA== 42611 -aXRlcmF0ZQ== 42612 -X3N0cmlkZQ== 42613 -IHB1bnRv 42614 -LkV2ZW50QXJncw== 42615 -KGNlbnRlcg== 42616 -IG5laWdoYm9yaW5n 42617 -IFByaXNvbg== 42618 -IE1lc3Nlbmdlcg== 42619 -IGVwaWRlbWlj 42620 -ZGFv 42621 -X2NvbXBsZXg= 42622 -IGdyYXZlbA== 42623 -X0RJUA== 42624 -w6ltZW50 42625 -IEFyaQ== 42626 -X2JpdG1hcA== 42627 -LnF1aXQ= 42628 -KHZhbGlk 42629 -IHBlbmQ= 42630 -IHJlc3BpcmF0b3J5 42631 -IHJlYm91bmQ= 42632 -RGVmYXVsdFZhbHVl 42633 -44Ot 42634 -IGNvbW1pdHM= 42635 -LnRlc3Rz 42636 -X2Zy 42637 -aXRldA== 42638 -LnNm 42639 -IHNwYWNlY3JhZnQ= 42640 -Y3JpdGljYWw= 42641 -IGRlcHJlc3NlZA== 42642 -IEFueU9iamVjdA== 42643 -IHVuYg== 42644 -IGRpc2Nlcm4= 42645 -KG15c3Fs 42646 -TGF0aW4= 42647 -IEJvZw== 42648 -IFdpbGRsaWZl 42649 -VG9GaWxl 42650 -aW94aWQ= 42651 -QFJlc3RDb250cm9sbGVy 42652 -ICIkKA== 42653 -IDw8Ig== 42654 -IGRlZmVjdHM= 42655 -IGRhdHVt 42656 -aGlu 42657 -IHJlYWxpemFy 42658 -YW55YWh1 42659 -IFNpZw== 42660 -QERhdGE= 42661 -YWRhcHRpdmU= 42662 -IENhdGhlcmluZQ== 42663 -LmNy 42664 -IENPT0tJRQ== 42665 -IHBpY3R1cmVk 42666 -IEZpZ2h0ZXI= 42667 -UXVlcnlhYmxl 42668 -IEFueXdheQ== 42669 -IEdMRlc= 42670 -X25hbWVzcGFjZQ== 42671 -X2Z0 42672 -IF0p 42673 -T3JnYW5pemF0aW9u 42674 -IGNvbnN0aXR1dGVz 42675 -IHF1YW5k 42676 -KGNodW5r 42677 -Ii8+DQo= 42678 -IExha2Vz 42679 -bWFpbndpbmRvdw== 42680 -Q2FydGh5 42681 -c3Bpbg== 42682 -KGNzdg== 42683 -OnJlZA== 42684 -LWNvbW1lcmNl 42685 -4Li5 42686 -IGRpc2NvdmVyaW5n 42687 -IGVjbw== 42688 -X2ZhYw== 42689 -aW5jZXRvbg== 42690 -IEdyZWVucw== 42691 -and0 42692 -2LU= 42693 -IEJyb25jb3M= 42694 -IEdvb2Rz 42695 -KEdUSw== 42696 -IHJldHVyblZhbHVl 42697 -IHNpZW1wcmU= 42698 -IG5ldXRy 42699 -d2VudA== 42700 -IE5hdGFs 42701 -IGVudGh1c2lhc3RpYw== 42702 -4buN 42703 -Rk4= 42704 -L2RhdGFiYXNl 42705 -Q2F0YWxvZw== 42706 -IGJydW4= 42707 -IEthc2g= 42708 -X1Bs 42709 -aXNjcmlt 42710 -LHdpZHRo 42711 -IGlubWF0ZXM= 42712 -QXNzaWdubWVudA== 42713 -IEhhdmVu 42714 -IHBsYXlncm91bmQ= 42715 -ZXhhbQ== 42716 -QENvbnRyb2xsZXI= 42717 -dWxpYXI= 42718 -LmdldFBhcmVudA== 42719 -ICI7Cgo= 42720 -OnNpemU= 42721 -aXNzb3Jz 42722 -IGZpcw== 42723 -IGFsYw== 42724 -ZW5zYXRpb24= 42725 -IE5peG9u 42726 -IG1pZ2h0eQ== 42727 -LXN0cg== 42728 -X3NwZWNpYWw= 42729 -X0FEQw== 42730 -IFR3aWc= 42731 -dW1ibGluZw== 42732 -LWFkZHJlc3M= 42733 -IGhlcm9pbg== 42734 -WVRF 42735 -ICAgICAgICAgICAgICAgICAK 42736 -RnJpZW5k 42737 -IGF2ZQ== 42738 -IFBORw== 42739 -IEt1cmRpc2g= 42740 -RGF0YVNldENoYW5nZWQ= 42741 -IGJsYWRlcw== 42742 -YnJhbA== 42743 -U3RlYW0= 42744 -IHNpZ3U= 42745 -SVJUVUFM 42746 -YWNvcw== 42747 -VURQ 42748 -KGRhdGFiYXNl 42749 -aGVj 42750 -IFN0cmluZ3M= 42751 -X3NjYWxhcg== 42752 -CWRlc2M= 42753 -IFRMUw== 42754 -OyIK 42755 -IENvcmJ5bg== 42756 -U2ltcGxlTmFtZQ== 42757 -dWVsbA== 42758 -IEVudHJl 42759 -ZWxsaXRlcw== 42760 -LXBsYWNl 42761 -IGZyYW5rbHk= 42762 -IEVyZg== 42763 -Q0VM 42764 -IHBhw61z 42765 -IGhlZGdl 42766 -IGxhdGVudA== 42767 -IElSUQ== 42768 -IEhlcmFsZA== 42769 -IFByZWM= 42770 -67O0 42771 -LlRFWFQ= 42772 -U2FsYXJ5 42773 -IGF1dHVtbg== 42774 -IHRyYXZhaWw= 42775 -LlN1bQ== 42776 -IGNhcmVk 42777 -TW9y 42778 -IGludHVpdGl2ZQ== 42779 -IGpvdXJuYWxz 42780 -X0lU 42781 -IFRyb3U= 42782 -5Lyg 42783 -SGFzQ29sdW1uTmFtZQ== 42784 -Q29tcG9zaXRl 42785 -IHNwaWNl 42786 -X2Rpc2s= 42787 -X0NPREVT 42788 -IEludHJvZHVjZWQ= 42789 -aW9uYQ== 42790 -IG51ZXN0cmE= 42791 -b2N0 42792 -ICAgIAogICAgCiAgICAK 42793 -KHBhcmFtZXRlcg== 42794 -IHN0dWRpb3M= 42795 -IHByb2plY3RJZA== 42796 -IGJkc20= 42797 -LlNxbENsaWVudA== 42798 -aW1pemVy 42799 -IENBUkQ= 42800 -K3Q= 42801 -YWFu 42802 -LnNvbA== 42803 -X0FkanVzdA== 42804 -IHJpZ2h0ZW91cw== 42805 -IExvZ2dpbmc= 42806 -LmZpbHRlcnM= 42807 -X1RBQg== 42808 -CXN5cw== 42809 -cm9waGlj 42810 -b3RoZXJhcHk= 42811 -IEJyb3dzZQ== 42812 -a2V5Ym9hcmQ= 42813 -Uk9O 42814 -K1w= 42815 -cm9wcGVk 42816 -IGV4dGVuc2l2ZWx5 42817 -Zms= 42818 -IGxpbWU= 42819 -eWVhcnM= 42820 -RXhj 42821 -IHNwaA== 42822 -IGNoZWF0aW5n 42823 -YW5kcm8= 42824 -w61v 42825 -IHByaW5jZQ== 42826 -b2lyZQ== 42827 -IERlc3RpbmF0aW9u 42828 -IENvbnZlcnRz 42829 -IHVwc3RyZWFt 42830 -b2xlZA== 42831 -IHNlcnZhbnRz 42832 -IHNlbWFudGlj 42833 -IGNydW5jaA== 42834 -IGV2ZW50dWFs 42835 -cnVubmVy 42836 -L2Vycm9y 42837 -U3Bpbg== 42838 -IHNlY3JldGx5 42839 -IGFzc2VtYmxl 42840 -LlBlcnNvbg== 42841 -ZW5kZXJyb3I= 42842 -Xzw= 42843 -IHBlbmRhbnQ= 42844 -U2xlZXA= 42845 -IENoZW1pc3RyeQ== 42846 -IGJvc3Nlcw== 42847 -bGs= 42848 -KSkpLAo= 42849 -QmxvY2tseQ== 42850 -REVWSUNF 42851 -IHJlZmxlY3Rpbmc= 42852 -IGFtcGxl 42853 -TWlsbGlzZWNvbmRz 42854 -IFByZXNpZGVudGlhbA== 42855 -IHVzdWFyaW9z 42856 -IE5a 42857 -IFNhbGFyeQ== 42858 -IEFtYW5kYQ== 42859 -X25w 42860 -anVyeQ== 42861 -IGvDtm4= 42862 -IHRoZXJhcGlzdA== 42863 -IGhvbW9zZXh1YWw= 42864 -IERyYWtl 42865 -LXdpbmRvdw== 42866 -IExvY2F0ZWQ= 42867 -LkRyaXZlcg== 42868 -IFZJREVP 42869 -IG1lcmNoYW50cw== 42870 -IENoZXN0 42871 -LWxvY2s= 42872 -L3BocA== 42873 -IG1pbGFubw== 42874 -X1NUWUxF 42875 -YXJnZXI= 42876 -aWRlYQ== 42877 -R1VJRA== 42878 -YWR2YW5jZWQ= 42879 -bWVhbA== 42880 -T3B0aW9uc0l0ZW1TZWxlY3RlZA== 42881 -PScl 42882 -IENoYW0= 42883 -OmRhdGE= 42884 -KHN0YXQ= 42885 -V2lsbEFwcGVhcg== 42886 -IGluZm9ybWFs 42887 -YWpp 42888 -IHJlcHJvZHVjdGl2ZQ== 42889 -IENBUw== 42890 -44Gj 42891 -RlVOQw== 42892 -IFJ1dGg= 42893 -KSso 42894 -Q09OU1Q= 42895 -IEZhbnM= 42896 -IGdyb3VwSWQ= 42897 -eGZmZmZmZmZm 42898 -IHNhbXBsZXI= 42899 -IH19Ij4= 42900 -LnRoZQ== 42901 -IGhvbGxvdw== 42902 -V0FZ 42903 -IEZhY3VsdHk= 42904 -QXR0cmlidXRlZFN0cmluZw== 42905 -IExvb2tz 42906 -IFJleA== 42907 -ams= 42908 -IE1JTA== 42909 -IGJhcmQ= 42910 -Lkxvbmc= 42911 -IGxpdmVzdA== 42912 -IHNrYWw= 42913 -aWNpc20= 42914 -TUFJTg== 42915 -IG11Y2hv 42916 -Qk9EWQ== 42917 -IGVzZQ== 42918 -CXVzZQ== 42919 -Rm9vdA== 42920 -LlNRTEV4Y2VwdGlvbg== 42921 -IGluaGVyaXRhbmNl 42922 -cmVjZWl2ZWQ= 42923 -IHB1dGFz 42924 -ZWRpcw== 42925 -YWxzYQ== 42926 -IEVycm9yTWVzc2FnZQ== 42927 -Qm9va2luZw== 42928 -IHRyYWN0 42929 -YWN6 42930 -IENhbnQ= 42931 -X3JlZ2V4 42932 -IGlkZW9sb2dpY2Fs 42933 -IGppaGFk 42934 -aG9z 42935 -L3N5cw== 42936 -Y29sbQ== 42937 -KHBvb2w= 42938 -IGVzdMOhbg== 42939 -IFBlbmRpbmc= 42940 -ZW3DoXM= 42941 -IGt0w7NyeQ== 42942 -KSk7CgoK 42943 -dHJhbnNhY3Rpb25z 42944 -IHdpZWxk 42945 -aXRlcmU= 42946 -ZXJ0dXJl 42947 -X3Nz 42948 -IHN0cmV0Y2hpbmc= 42949 -IHByaXNvbmVy 42950 -LlJlYWRBbGw= 42951 -IGJlc2No 42952 -LS07DQo= 42953 -IGNyaXNw 42954 -X1NDQU4= 42955 -IGFl 42956 -U3RyaWN0 42957 -IE1pbm5lYXBvbGlz 42958 -IEJvZWluZw== 42959 -YXJpcw== 42960 -cmVr 42961 -X3BpcGU= 42962 -IHByaWVzdHM= 42963 -KEVJRg== 42964 -ZWhpY2xlcw== 42965 -IEludGVyYWN0aXZl 42966 -YmV0d2Vlbg== 42967 -CU51bGxDaGVjaw== 42968 -IEJsYWly 42969 -IEx0 42970 -X2lubGluZQ== 42971 -ZXRoeWw= 42972 -wrw= 42973 -X3BhY2thZ2Vz 42974 -IGJhcnJlbHM= 42975 -X2hl 42976 -IHJlZ2V4cA== 42977 -X3B0cw== 42978 -X0hhbmRsZXI= 42979 -aW5ndWxhcg== 42980 -IE5pc3Nhbg== 42981 -IFJhbmNo 42982 -IHBlcmNo 42983 -VW5zdXBwb3J0ZWQ= 42984 -U21pdGg= 42985 -IExlZ2VuZHM= 42986 -TWk= 42987 -IGdm 42988 -c3RlZGVy 42989 -IGFjcXVpcmluZw== 42990 -IHNpbXVsYXRvcg== 42991 -KCksIg== 42992 -cmVjZWl2ZQ== 42993 -IGlucGxhY2U= 42994 -QUNUSU9O 42995 -IFdlYkRyaXZlcg== 42996 -ZmlsZXN5c3RlbQ== 42997 -PE9yZGVy 42998 -bG9wZW4= 42999 -IEhFSUdIVA== 43000 -LnNldEJvcmRlcg== 43001 -jbA= 43002 -X19bIg== 43003 -IGNsYW1w 43004 -U2Vnb2U= 43005 -YmFuZHM= 43006 -dG9MaXN0 43007 -YW1iYQ== 43008 -PicrCg== 43009 -IGNyZWRpYmxl 43010 -YW1hdA== 43011 -cGxheWluZw== 43012 -LnNldEltYWdlUmVzb3VyY2U= 43013 -cXVlbA== 43014 -IHBvZHI= 43015 -Z2VvbQ== 43016 -RWs= 43017 -IFFhdGFy 43018 -IGdlbGQ= 43019 -PycsCg== 43020 -IGN5bA== 43021 -KGF4 43022 -IFdJ 43023 -dXJhbGx5 43024 -IEJyYXNpbA== 43025 -IHNlbnph 43026 -YWxleQ== 43027 -b25lbg== 43028 -IGJhaA== 43029 -IG1vbGVjdWxl 43030 -UmFk 43031 -6L+w 43032 -QU5DSA== 43033 -LWJhY2tncm91bmQ= 43034 -LWFnZW50 43035 -IHByb2xpZmVy 43036 -OmJvb2xlYW4= 43037 -IHRpZGU= 43038 -ZXJpYWxpemVy 43039 -XzsNCg== 43040 -RmVl 43041 -Kiop 43042 -ZXJneQ== 43043 -IEhvbm9y 43044 -LkxvZ2dpbmc= 43045 -aXJpcw== 43046 -IHVuZGVybWluZQ== 43047 -IER5 43048 -IHR5cg== 43049 -IGRlcXVl 43050 -IGRhbWVy 43051 -KFtdKQo= 43052 -LmxheW91dENvbnRyb2xJdGVt 43053 -cGVhdGVk 43054 -Q0FO 43055 -cmFnbWVudHM= 43056 -TGFuZA== 43057 -KV0pOwo= 43058 -IFNhaA== 43059 -IERFQ0w= 43060 -V2l0aGlu 43061 -IE5hbWVzcGFjZQ== 43062 -YW5vdGhlcg== 43063 -c2VtYmxpbmc= 43064 -LmRlc2NyaWJl 43065 -Q29uc3Vt 43066 -IEZlYXI= 43067 -Z2l2ZW4= 43068 -T3Jhbmdl 43069 -PGJvb2xlYW4= 43070 -IHN0ZWFkaWx5 43071 -cGFSZXBvc2l0b3J5 43072 -IHJlc3VsdFNldA== 43073 -X0VOVEVS 43074 -X3JlcGVhdA== 43075 -IHRvbmVz 43076 -IFBST1A= 43077 -bmFs 43078 -cGFydGljbGU= 43079 -IHNpZ25hbGluZw== 43080 -IGFjY2Vzc29yeQ== 43081 -CQkJCQkJICA= 43082 -IHZpZWxl 43083 -IE5vYWg= 43084 -LWFn 43085 -IG11cmRlcnM= 43086 -IGFpcmVk 43087 -IFBMQVk= 43088 -IFN1bGxpdmFu 43089 -X0NvcmU= 43090 -IHVsb25n 43091 -IGJsb2dnaW5n 43092 -PlRoaXM= 43093 -IGRhdGFJbmRleA== 43094 -IHByaW50YWJsZQ== 43095 -IEV5ZXM= 43096 -X3RhcmdldHM= 43097 -KFB5 43098 -Lm92ZXI= 43099 -IGJydQ== 43100 -YW1wdG9u 43101 -IHBsYWludGlmZg== 43102 -PEtleQ== 43103 -YnVsbA== 43104 -IOKfqA== 43105 -SXNzdWU= 43106 -LmNvcm5lclJhZGl1cw== 43107 -Q3JpdGljYWw= 43108 -X3BoaQ== 43109 -LmFuZ2xl 43110 -IGR5bmFtaWNhbGx5 43111 -ISIpOw0K 43112 -Pik7Cg== 43113 -aW52ZXN0 43114 -LioKCg== 43115 -IHTDqWzDqQ== 43116 -IHN1cGVyZg== 43117 -IGNhc2NhZGU= 43118 -RFRE 43119 -IHZpdmlk 43120 -IHN1YnNpZGllcw== 43121 -IEhhc3M= 43122 -IGNvbGxhcHM= 43123 -IGNlcmFtaWM= 43124 -e30iLg== 43125 -IExlYWthZ2U= 43126 -LXRyYXNo 43127 -Y29sbGFwc2Vk 43128 -LXNvY2lhbA== 43129 -IENoYWQ= 43130 -IGluY2xpbmVk 43131 -IHN0bw== 43132 -IHN0b3J5Ym9hcmQ= 43133 -LnBheW1lbnQ= 43134 -c3RhY2tvdmVyZmxvdw== 43135 -IFJhaWRlcnM= 43136 -ICMn 43137 -b2xpY2llcw== 43138 -7Jy866Gc 43139 -ZW1hcA== 43140 -IGtq 43141 -IHF1b3Rh 43142 -IEdhcmRlbnM= 43143 -67KI 43144 -IEFuZ2Vscw== 43145 -IG9mdA== 43146 -IGxvd2VyY2FzZQ== 43147 -IGlQYXJhbQ== 43148 -IGNoZWFwZXN0 43149 -dW50YQ== 43150 -X3BrdA== 43151 -aWNhdG9ycw== 43152 -IGxldXJz 43153 -IGRlY3JlYXNlcw== 43154 -CWRlZmluZQ== 43155 -UFJFQw== 43156 -YW1tZXJz 43157 -IFByZXBhcmVkU3RhdGVtZW50 43158 -KGRpcmVjdGlvbg== 43159 -IGNyZXdz 43160 -YXJrZWQ= 43161 -IE1lbXBoaXM= 43162 -IFNlbGw= 43163 -R1RL 43164 -IG1haWQ= 43165 -OmRpc2FibGU= 43166 -6ZuG 43167 -IFBm 43168 -IGFsYmVpdA== 43169 -b3Blbmg= 43170 -Pz4iPgo= 43171 -LmdldFNvdXJjZQ== 43172 -KHNjYWxl 43173 -RHU= 43174 -IFBJTA== 43175 -X3JlZnJlc2g= 43176 -IGJldHM= 43177 -KGNhcg== 43178 -IFZvbg== 43179 -fC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCg== 43180 -IEdyYXQ= 43181 -TXVjaA== 43182 -KERpYWxvZw== 43183 -LnN0b3BQcm9wYWdhdGlvbg== 43184 -IHRlaw== 43185 -IGV4aXRz 43186 -J10sJA== 43187 -IHBob25lTnVtYmVy 43188 -dWNz 43189 -ZWNpbWFs 43190 -LS0tLS0tLS0tLS0tLS0= 43191 -aW5w 43192 -LnBvam8= 43193 -IGNvcnB1cw== 43194 -IHByYWN0aXRpb25lcnM= 43195 -LnBpYw== 43196 -InRlc3Rpbmc= 43197 -IHN0cmluZ0J5 43198 -Lk5vdE51bGw= 43199 -IHJhbmc= 43200 -LkR5bmFtaWM= 43201 -X1JlbmRlcg== 43202 -0LDRgtCw 43203 -V2FpdGluZw== 43204 -IFdpaw== 43205 -IG92ZXJ3aGVsbWVk 43206 -JSI+ 43207 -IEFF 43208 -fX0+Cg== 43209 -dXc= 43210 -X3R5cA== 43211 -IGJ1Y2tldHM= 43212 -IGdyZWV0aW5n 43213 -IGxhdWdodGVy 43214 -IGFudGFnb24= 43215 -dWdnZXN0aW9u 43216 -LWVtYWls 43217 -CXRvcA== 43218 -IGVyb3M= 43219 -X3RyaQ== 43220 -IGlzc3Vpbmc= 43221 -IGjDoQ== 43222 -IGlzb2xhdGU= 43223 -T3ZlcmZsb3c= 43224 -LEU= 43225 -IG51dHJpdGlvbmFs 43226 -IEFiYm90dA== 43227 -IG5m 43228 -LnRvdWNo 43229 -LmZldGNoYWxs 43230 -X3ppcA== 43231 -Iil9Cg== 43232 -IGFtYXQ= 43233 -IENpc2Nv 43234 -IG7DpQ== 43235 -UExFWA== 43236 -IHNlaQ== 43237 -Zm90bw== 43238 -LnRvSnNvbg== 43239 -5aSa 43240 -IEtsZWlu 43241 -IGxpYmM= 43242 -IG1pbmVycw== 43243 -5aI= 43244 -LXByaW50 43245 -IFByaWRl 43246 -VG9kb3M= 43247 -IG1hc2tlZA== 43248 -IHNldERhdGE= 43249 -IHRlbGVmb24= 43250 -IHVuaGFwcHk= 43251 -IFRhYmxlcw== 43252 -Z2Vi 43253 -KGRlYnVn 43254 -X2FsbG93ZWQ= 43255 -LWFjY2Vzcw== 43256 -IGxvZ2lzdGljcw== 43257 -IGdlbXM= 43258 -IE1hdHVyZQ== 43259 -IHJzcA== 43260 -IEFsbGU= 43261 -LmdldEJ5dGVz 43262 -XHdlYg== 43263 -eW5jaHJvbml6ZWQ= 43264 -UGFyYWdyYXBo 43265 -IHRocm90dGxl 43266 -LnNxbGl0ZQ== 43267 -Y29uc3VsdGE= 43268 -IFNlYWg= 43269 -Q2U= 43270 -IHN1Ym1hcg== 43271 -RVJF 43272 -Vm91cw== 43273 -IHJlZGRpdA== 43274 -IHNxbGFsY2hlbXk= 43275 -LW1pbGU= 43276 -b2NpZGU= 43277 -UG91cg== 43278 -fX0iPgo= 43279 -c3RlYWQ= 43280 -IEAo 43281 -IFtdKQ== 43282 -IEFkcw== 43283 -IG92ZXJsb2Fk 43284 -cmlkZGVu 43285 -IERlc2VydA== 43286 -IFdyYXA= 43287 -IFBvcnR1Z3Vlc2U= 43288 -ZXR6 43289 -CWZpcnN0 43290 -IG1pbGVzdG9uZQ== 43291 -5peg 43292 -0YPRiQ== 43293 -KHN1Y2Nlc3M= 43294 -PFZlY3Rvcg== 43295 -Y29vbA== 43296 -IFtdKTsK 43297 -ZXJ2YWxz 43298 -IGludmVydA== 43299 -Imlv 43300 -Y3Vyc28= 43301 -ZnJhZ21lbnQ= 43302 -IGZlYXNpYmxl 43303 -LnNldFBvc2l0aW9u 43304 -IGVsbQ== 43305 -IGltYWdpbg== 43306 -QFNwcmluZw== 43307 -IGJhdHM= 43308 -cHXDqXM= 43309 -Z2FsZW1lbnQ= 43310 -bnNpYw== 43311 -Z2llbmU= 43312 -ZWxsYXRpb24= 43313 -IEJhaWxleQ== 43314 -U2hhcg== 43315 -IFR1bA== 43316 -IEhL 43317 -IGZyZWV6aW5n 43318 -Z2xt 43319 -Y2VhbnM= 43320 -LWN1dA== 43321 -X2NpcmNsZQ== 43322 -5ZGY 43323 -bmVnYXRpdmU= 43324 -IGluZGlhbg== 43325 -c2FsdA== 43326 -IHRpbmc= 43327 -CW1vZA== 43328 -IHNpbnQ= 43329 -YWtpbg== 43330 -dW1s 43331 -IFRleHRJbnB1dA== 43332 -IHBvcHBlZA== 43333 -VE1Q 43334 -IHBhcmtlZA== 43335 -15nX 43336 -IEZ1c2lvbg== 43337 -IGhlYXRlcg== 43338 -RVRG 43339 -cm96ZW4= 43340 -aGFsbA== 43341 -IE1paw== 43342 -bGV2YXJk 43343 -LWhlYXJ0 43344 -CW9yZGVy 43345 -TWFraW5n 43346 -IHBsZWRnZWQ= 43347 -IGRpcnM= 43348 -JHBvc3Q= 43349 -IEhlcnI= 43350 -c3RhbnRpYXRl 43351 -LCIK 43352 -LmdldENvbG9y 43353 -IFNBVA== 43354 -IHRpbWVkZWx0YQ== 43355 -IE1haQ== 43356 -CW1ldGhvZA== 43357 -IGlkaW90 43358 -IFRyYXY= 43359 -aWRlbnRpZmllZA== 43360 -IERpdmluZQ== 43361 -LmdldFBhdGg= 43362 -RGFzaA== 43363 -IGluZmlsdHI= 43364 -IGhhbmRsZVN1Ym1pdA== 43365 -YnJvb2s= 43366 -LmdlbmVyaWM= 43367 -LnNob3J0Y3V0cw== 43368 -Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLg== 43369 -IGRhdGluZ3M= 43370 -IE1W 43371 -77u/Iw== 43372 -fSIKCg== 43373 -IGltcHJpc29ubWVudA== 43374 -YXNvbmlj 43375 -cm91ZA== 43376 -dWNpb24= 43377 -5oql 43378 -IGRpYWxlY3Q= 43379 -IG9uTW91c2U= 43380 -Y29uc3RleHBy 43381 -LmxhYmVsQ29udHJvbA== 43382 -IHdlYWtlcg== 43383 -IG1hbmtpbmQ= 43384 -IFJFQ0U= 43385 -IGRpeg== 43386 -IGFwcEJhcg== 43387 -IHF1w6k= 43388 -ZnJh 43389 -X2RlZmF1bHRz 43390 -IGFsaXF1 43391 -X2F0b20= 43392 -OmluZGV4UGF0aA== 43393 -IG1pc3Nlcw== 43394 -IHZpc3VhbGx5 43395 -IEhhbmRz 43396 -U1RSVQ== 43397 -aWF0ZXM= 43398 -X2Fzc2V0 43399 -RmluZGVy 43400 -bWlkdA== 43401 -IHNuYWNrcw== 43402 -KF9fKCc= 43403 -LnVyaQ== 43404 -IEluc3RydW1lbnQ= 43405 -dmVuaXI= 43406 -KCRfXw== 43407 -LkRvdE5ldEJhcg== 43408 -IGNvbmZpZ3M= 43409 -IGd1ZXNzZWQ= 43410 -4KS/4KQ= 43411 -IGluaXRpYWxpemVy 43412 -ID8iLA== 43413 -IFZlcml6b24= 43414 -bWFuaWZlc3Q= 43415 -Z2ViZW4= 43416 -LmRldGFpbHM= 43417 -R2F0ZQ== 43418 -cG9uc2libGU= 43419 -IEVsaW0= 43420 -LHN0cg== 43421 -IHdyaXRpbmdz 43422 -IERlcmVr 43423 -IENvb3JkaW5hdG9y 43424 -IHBpbGxvdw== 43425 -IG5vdGljZWFibGU= 43426 -UnM= 43427 -IGR1cGxpY2F0ZXM= 43428 -ZXJuZWxz 43429 -a0o= 43430 -Lnp6 43431 -b2xsYW5k 43432 -IFNFQ1RJT04= 43433 -X2ZuYW1l 43434 -dWZmbGVk 43435 -J10uJzwv 43436 -X0NN 43437 -IHly 43438 -cGxhdA== 43439 -b2JvZHk= 43440 -bmRl 43441 -KEVsZW1lbnQ= 43442 -IEF0bGFz 43443 -IO+8iA== 43444 -IG5pdmVs 43445 -IGluc2lzdHM= 43446 -W1A= 43447 -IGVudGh1c2lhc3Rz 43448 -IOyeheugpQ== 43449 -IGJldmVyYWdl 43450 -e30iLA== 43451 -OnJpZ2h0 43452 -IG5vdXZlYXU= 43453 -IENvbXBsZQ== 43454 -IFBhZw== 43455 -b3ducw== 43456 -IHJlbWVtYmVycw== 43457 -IFByYWRlc2g= 43458 -IGNoYWxr 43459 -IExhdXJlbg== 43460 -XFNlcnZpY2U= 43461 -X0dFTg== 43462 -PiIpCg== 43463 -IERvbGxhcg== 43464 -IGVtb2pp 43465 -Q2Fyb3VzZWw= 43466 -LXBsYXllcg== 43467 -IGFkanVzdGluZw== 43468 -IGp1Z2E= 43469 -YWxsZW5nZXM= 43470 -Z2VuZQ== 43471 -KGJvZHlQYXJzZXI= 43472 -bG9wZWRpYQ== 43473 -IEJlaGluZA== 43474 -IHNsZWV2ZXM= 43475 -IGRyYWdnaW5n 43476 -IENoZXZyb2xldA== 43477 -IGJpeg== 43478 -aXZpdGllcw== 43479 -IEZyZXF1ZW5jeQ== 43480 -LGNoYXI= 43481 -LldISVRF 43482 -X3ByZXZpZXc= 43483 -KSc7Cg== 43484 -X2F4 43485 -SU9OUw== 43486 -LmNwdQ== 43487 -LmlucHV0cw== 43488 -VUJF 43489 -X2ZlZWQ= 43490 -IFN1cHBsZW1lbnQ= 43491 -ISku 43492 -ZXN1cw== 43493 -IFVEUA== 43494 -IG1pY3JvcGhvbmU= 43495 -IGNvbmZpcm1z 43496 -LmlzTm90RW1wdHk= 43497 -IjoiIiwK 43498 -X1NDUkVFTg== 43499 -CWV4cGVjdGVk 43500 -Ky0rLSstKy0= 43501 -IEhhaXQ= 43502 -ZmFzdGNhbGw= 43503 -IGRlcGljdA== 43504 -dmI= 43505 -X3BpY3R1cmU= 43506 -CWRlc2NyaXB0aW9u 43507 -IFdpZmU= 43508 -dWNp 43509 -IHZpY2lvdXM= 43510 -5LuW 43511 -dWViYQ== 43512 -IHNldFVzZXI= 43513 -44Gh 43514 -IGRpdmluZw== 43515 -IG9wZXJh 43516 -dXNlcmNvbnRlbnQ= 43517 -YXJhaA== 43518 -KX0s 43519 -eXVu 43520 -dmVsdA== 43521 -IHVuY292ZXJlZA== 43522 -IGhpcHM= 43523 -IG9zY2lsbA== 43524 -IGFzc2VydGluZw== 43525 -IFhp 43526 -LnJlc3RvcmU= 43527 -a2Vh 43528 -IHNwZWxsaW5n 43529 -IGRlcml2ZQ== 43530 -YWJ3ZQ== 43531 -IERvdw== 43532 -LnNldFR5cGU= 43533 -X3Zz 43534 -IGNvenk= 43535 -LmNhdGVnb3JpZXM= 43536 -T3Jn 43537 -X21ncg== 43538 -IGR1bmdlb24= 43539 -Y29sbGVjdGlvblZpZXc= 43540 -IEJsYW5r 43541 -YWNpYXM= 43542 -w6TDpA== 43543 -X2NsZWFudXA= 43544 -X0FDVElWSVRZ 43545 -IHRyaWFuZ2xlcw== 43546 -Lk1lbnVJdGVt 43547 -IGlwaG9uZQ== 43548 -IFdvbg== 43549 -XV0KCg== 43550 -IENvbXBhcmlzb24= 43551 -LkRvYw== 43552 -IGNhbm9uaWNhbA== 43553 -IFN1ZGFu 43554 -Jyl7 43555 -VXBJbnNpZGU= 43556 -YnVpbHRpbg== 43557 -RU5DWQ== 43558 -eGJl 43559 -IGNodWNr 43560 -IGNvbnRyYWRpY3Q= 43561 -IG51ZXN0cm8= 43562 -IGFyY2hpdGVjdHVyYWw= 43563 -IEZpYg== 43564 -IGNvbXBhcmVz 43565 -Kms= 43566 -Q2Zn 43567 -54Sh 43568 -bnRlbg== 43569 -TWF0Y2hlcw== 43570 -IERPV05MT0FE 43571 -X0hBTkRMRVI= 43572 -bWFuYWdlbWVudA== 43573 -W1M= 43574 -RU5H 43575 -woDC 43576 -ZmFuZw== 43577 -IHNsaXBwZWQ= 43578 -IExhbmth 43579 -ZXNjYXBpbmc= 43580 -IHRhY2tsZXM= 43581 -IFBlZHJv 43582 -LlByb3A= 43583 -Licn 43584 -LkdlbmVyYXRlZA== 43585 -Lk5ld0d1aWQ= 43586 -YXRyaWdlc2ltYWw= 43587 -aWxsb24= 43588 -IHN0YXRpc3RpYw== 43589 -c3BlY2llcw== 43590 -aG9sZGluZw== 43591 -RHJ1cGFs 43592 -IGZ1bmRhbWVudGFsbHk= 43593 -IGJvbmRhZ2U= 43594 -IHJlc29sdXRpb25z 43595 -SW5saW5lRGF0YQ== 43596 -XFR5cGU= 43597 -ZXN0aW9u 43598 -LndyYXA= 43599 -IHdhcnJpb3Jz 43600 -IExPQ0FM 43601 -QXJjaGl2ZQ== 43602 -IGVtYnJhY2Vk 43603 -4bun 43604 -LlZlcg== 43605 -IEFmZm9yZGFibGU= 43606 -b2xlc2FsZQ== 43607 -IEFwcGxpZWQ= 43608 -IENvbnZlcnNpb24= 43609 -bWVnYQ== 43610 -X2NhbQ== 43611 -IGNlcmVtb24= 43612 -YXVydXM= 43613 -IFZvbGs= 43614 -Lm9wZW5z 43615 -L2Fib3V0 43616 -IFN0ZA== 43617 -am91cm5hbA== 43618 -KCkpew0K 43619 -LCJc 43620 -KEFycmF5cw== 43621 -IERlbnNl 43622 -YXNlw7Fh 43623 -w6RubmVy 43624 -L3N0YXQ= 43625 -dXNlckRhdGE= 43626 -IGdlcm1hbg== 43627 -IHR6 43628 -d29ydGh5 43629 -Rm9ybWF0RXhjZXB0aW9u 43630 -cGhlcmQ= 43631 -IHNtaWxlcw== 43632 -IFdoZW5ldmVy 43633 -KGFkYXB0ZXI= 43634 -LmJhZGxvZ2lj 43635 -IGJyaWVmaW5n 43636 -LkdyaWRDb2x1bW4= 43637 -LWNoYXI= 43638 -ZGltZW5zaW9u 43639 -IENvcHBlcg== 43640 -IG5pbnRo 43641 -ICd7ew== 43642 -IHJhdg== 43643 -X1RhYmxl 43644 -IGRlcml2YXRpdmVz 43645 -IFJhaXNl 43646 -IEZ1dA== 43647 -YXJtb3I= 43648 -LXBhZGRpbmc= 43649 -IHJlbWlu 43650 -CXN0eWxl 43651 -IE1lbWJlcnNoaXA= 43652 -IHNwcmVhZHM= 43653 -IGdhbGxlcmllcw== 43654 -IENsYXJrZQ== 43655 -IGNvbmNlcHRpb24= 43656 -bWludXRl 43657 -IGFidXNpdmU= 43658 -X2Fkag== 43659 -IHRlcnJpZmlj 43660 -IG92ZXJ0 43661 -b3VyY2luZw== 43662 -IGVudHJhZGE= 43663 -bGV2ZWxz 43664 -IGNyaXRpcXVl 43665 -IHJlc3BlY3Rz 43666 -IE1NQQ== 43667 -aWVuZQ== 43668 -IGVuY2Fwcw== 43669 -IFJheW1vbmQ= 43670 -RGl2aWRlcg== 43671 -aXZhYmxl 43672 -YmF6 43673 -IEBfOwo= 43674 -IENsYWlyZQ== 43675 -IHVyZ2luZw== 43676 -Q0VF 43677 -IHRyYW5zZm9ybWVy 43678 -ZGlzY29yZA== 43679 -IEpvdXJuZXk= 43680 -dG9z 43681 -IGNvbXBldGl0aW9ucw== 43682 -IE9CSg== 43683 -IEJpcw== 43684 -IHJlbGF4YXRpb24= 43685 -aWR5 43686 -X0lOU1RBTkNF 43687 -IFByZWY= 43688 -ZGFkb3M= 43689 -aWNpZW5jaWVz 43690 -IE1lZGlhUXVlcnk= 43691 -IEN1YmU= 43692 -IFN0cmFuZ2U= 43693 -Z3B1 43694 -KGRheXM= 43695 -X0luaXRTdHJ1Y3Q= 43696 -IGZpbmdlcnByaW50 43697 -ZW1hdA== 43698 -IEdlY2tv 43699 -IHJhaWxz 43700 -IEx1bQ== 43701 -c3RyYWN0aW9u 43702 -aWd1bmc= 43703 -KG1vdmll 43704 -X2RpY3Rpb25hcnk= 43705 -X2ludGVycnVwdA== 43706 -IFFD 43707 -aWtlZA== 43708 -YXBwZW5kQ2hpbGQ= 43709 -cmVjaXBpZW50 43710 -csOp 43711 -VmU= 43712 -IHRvd2Vs 43713 -Lmxhc3RJbmRleE9m 43714 -IHBsYWNlYm8= 43715 -IFdpZQ== 43716 -LmVzcA== 43717 -KERlYnVn 43718 -b3BlcmF0aXZl 43719 -IGRlY2Vhc2Vk 43720 -Jmlk 43721 -CW11dGV4 43722 -ZWxpYw== 43723 -IGJhcHQ= 43724 -CQ0KDQo= 43725 -IGZhcnRoZXI= 43726 -SGFsZg== 43727 -LmRpc2FibGU= 43728 -Lm1lbnVTdHJpcA== 43729 -bGVjY2lvbg== 43730 -IHJlc3VsdENvZGU= 43731 -IGNhbnM= 43732 -LWVsZWN0aW9u 43733 -ZmVtYWxl 43734 -X0ZJWA== 43735 -YXVzaWJsZQ== 43736 -IFBPV0VS 43737 -IHJlY29uc3RydWN0aW9u 43738 -IHNjYW5z 43739 -Llh0cmFCYXJz 43740 -4oCYcw== 43741 -UmVtb3ZlZA== 43742 -IHBhcmFncmFwaHM= 43743 -X21hcmdpbg== 43744 -IGx5bXBo 43745 -IGJvcw== 43746 -bGluZ3Rvbg== 43747 -IEJhcHRpc3Q= 43748 -IGFkdmVydGlzZW1lbnRz 43749 -IE1hbmFnZQ== 43750 -L3l5eXk= 43751 -SU9VUw== 43752 -RU5DRVM= 43753 -IEZpY3Rpb24= 43754 -CW1lbnU= 43755 -IEZpbGVPdXRwdXRTdHJlYW0= 43756 -b3Zhbg== 43757 -IEZlbmc= 43758 -IHNraXBwaW5n 43759 -Z2V0Q2xhc3M= 43760 -YW5uaQ== 43761 -IHJlYm91bmRz 43762 -IHB1YmxpY2l0eQ== 43763 -IGluZ3Jlcw== 43764 -dXNlbWVudA== 43765 -IHRob3VnaHRmdWw= 43766 -LkNoYXJ0 43767 -IGhhdHRl 43768 -cGFzc3BvcnQ= 43769 -IGhvb2tlZA== 43770 -IExlbnM= 43771 -IGZsYWdzaGlw 43772 -IHN0aXA= 43773 -IEdFTg== 43774 -IGNsdWVz 43775 -aXB2 43776 -IFJpc2U= 43777 -IEdldw== 43778 -dGFibGVuYW1l 43779 -IGZvcmVtb3N0 43780 -X3ZhbGlkYXRl 43781 -X2FuYWx5c2lz 43782 -b2xsYQ== 43783 -IHF1YWxpZmljYXRpb25z 43784 -IGRpc3RyaWJ1dGlvbnM= 43785 -IEZsb3dlcg== 43786 -IHRlbnNl 43787 -IHRoYW5rZnVs 43788 -IGNsdXRjaA== 43789 -IHVuaWZpZWQ= 43790 -cm9hZHM= 43791 -IHNpdGk= 43792 -IHN0YWxs 43793 -X1BSSU9SSVRZ 43794 -Y3N0ZGxpYg== 43795 -X1VTRVJOQU1F 43796 -LmJ5dGVz 43797 -P3BhZ2U= 43798 -ZXJtYWxpbms= 43799 -IFZlZ2V0 43800 -L3ZuZA== 43801 -LWF1dGhvcg== 43802 -Lk5PTkU= 43803 -IENvbmN1cnJlbnQ= 43804 -IENyeQ== 43805 -IHN0YXJ0ZXJz 43806 -IEludGVyYWN0aW9u 43807 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 43808 -IExFVkVM 43809 -RWxs 43810 -IGNvbWJvQm94 43811 -IFRoZXJlc2E= 43812 -dGVr 43813 -X0hhbmRsZQ== 43814 -IGFieQ== 43815 -LmdkeA== 43816 -LGVuZA== 43817 -KExvY2Fs 43818 -T2w= 43819 -a25pZmU= 43820 -YXJpYWw= 43821 -IEhvZmY= 43822 -IHByb3N0aXR1ZXJhZGU= 43823 -RG9jdG9y 43824 -SW5zdGFuY2Vz 43825 -LlNldFZhbHVl 43826 -CWZyb20= 43827 -IGx1eHVyaW91cw== 43828 -SW5kZW50 43829 -QWxsb2NhdG9y 43830 -X0RSQVc= 43831 -KCIsIiw= 43832 -IEZyYW5jZXM= 43833 -IGdyb3VwQm94 43834 -KHNjaGVtYQ== 43835 -UHJpbnRm 43836 -T1JJRVM= 43837 -LWdyYWRpZW50 43838 -IHJlcHV0 43839 -YXJpbg== 43840 -X0RPTkU= 43841 -aW5jcmU= 43842 -aWdudHk= 43843 -IGV4ZXJ0 43844 -IC0u 43845 -L0FwcA== 43846 -LXRocm91Z2g= 43847 -IGRlY2xpbmluZw== 43848 -IGRlc3NlcnQ= 43849 -IGluY3VtYg== 43850 -IGRlc2lnbmF0aW9u 43851 -LlBPUlQ= 43852 -LHN0cm9uZw== 43853 -IHNhbmRib3g= 43854 -IHdpbmVz 43855 -IFBhdg== 43856 -JHN0cg== 43857 -YXNrZWxs 43858 -IGjDtg== 43859 -IFBZ 43860 -R2V0SW5zdGFuY2U= 43861 -VGV4dElucHV0 43862 -Z2FtZU9iamVjdA== 43863 -L2V2ZW50cw== 43864 -Y3JlYXRlZEF0 43865 -IGxvY2FsVmFy 43866 -IFdISVRF 43867 -cGVyZWQ= 43868 -aWxlZ2U= 43869 -ZWZmaWNpZW50 43870 -LGNvbG9y 43871 -Y2F0ZQ== 43872 -IENhZmU= 43873 -IHNpbWlsYXJpdGllcw== 43874 -IHB1bXBz 43875 -IEh1bmdhcnk= 43876 -LlVzZXJuYW1l 43877 -IHNrYXRl 43878 -IHRvdWNoZG93bnM= 43879 -IGFjY2VsZXJhdGU= 43880 -IEhlbGVu 43881 -T01FTQ== 43882 -IEt1bg== 43883 -X3ZvbA== 43884 -IGZpbmRBbGw= 43885 -IE1lbnNjaGVu 43886 -YWhlYWQ= 43887 -KTsi 43888 -a29tbWVu 43889 -IHBvc3Nlc3NlZA== 43890 -LmFyZ21heA== 43891 -LnRyYW5zaXRpb24= 43892 -QVJQ 43893 -T0xVTUU= 43894 -KHNjcmlwdA== 43895 -INCY 43896 -IEZpbmRpbmc= 43897 -b25jZXM= 43898 -SW8= 43899 -Qm9sZA== 43900 -IHJlbmV3YWw= 43901 -X0RJQUxPRw== 43902 -IGRpc3JlZw== 43903 -SU5URVJO 43904 -IHRvdXRl 43905 -IGVsZWN0cg== 43906 -IEdyb3Nz 43907 -CXRydWU= 43908 -LkZpZWxkcw== 43909 -IFdJRFRI 43910 -IERlbnQ= 43911 -IMOB 43912 -TlNOb3RpZmljYXRpb24= 43913 -IGFvcw== 43914 -IG1lbGVl 43915 -LlZhbGlkYXRpb24= 43916 -IERFQw== 43917 -LWRlcGVuZGVudA== 43918 -IHN1aWM= 43919 -VHJhaXRz 43920 -JG1lc3NhZ2U= 43921 -IERlYXI= 43922 -CUZJTEU= 43923 -bGFuZ3VhZ2Vz 43924 -LlByb3Q= 43925 -LmFkZHI= 43926 -LWdlbmVyYXRpb24= 43927 -SUNPTg== 43928 -IHRyYW5zcGxhbnQ= 43929 -LWRlc2NyaXB0aW9u 43930 -IGNoYXNpbmc= 43931 -IGNoZWVz 43932 -IH0qLwo= 43933 -VHJhZA== 43934 -cXVlcmllcw== 43935 -L3dpZGdldHM= 43936 -c3VicGFja2FnZQ== 43937 -IGVzcGVj 43938 -IGNyYWNrZWQ= 43939 -IGNvbXBldGl0b3I= 43940 -UHVyY2hhc2U= 43941 -LXRlYW0= 43942 -b2xlY3VsYXI= 43943 -b3JUaHVuaw== 43944 -JlA= 43945 -IHJlbGVudA== 43946 -LyN7 43947 -IHByb2R1Y3RJZA== 43948 -IOi+ 43949 -IExhdg== 43950 -IEFsdGVy 43951 -Lk1vZGU= 43952 -QURJTw== 43953 -Z3Jw 43954 -5re75Yqg 43955 -UXVpdA== 43956 -IGRlcHRocw== 43957 -LWNhdGVnb3J5 43958 -IERBVEFCQVNF 43959 -U1BFTEw= 43960 -IEZhbGNvbg== 43961 -IFFTdHJpbmdMaXN0 43962 -ICcnLg== 43963 -IEluc3RpdHV0aW9u 43964 -ZGFtYWdl 43965 -YXpvcg== 43966 -YmVsb25nc1Rv 43967 -dmVyYWdlcw== 43968 -IE5PTkU= 43969 -aXBwZXRz 43970 -LFwK 43971 -IGZvb3RwcmludA== 43972 -X2FyY2hpdmU= 43973 -bmFr 43974 -LmdldEZpZWxk 43975 -IFJlZmxlY3Rpb24= 43976 -ICdd 43977 -IEhCTw== 43978 -X2Rpc2NvdW50 43979 -IGluY2VzdA== 43980 -IERvZGdl 43981 -IFdhZGU= 43982 -Lk5P 43983 -ImVuY29kaW5n 43984 -IEJsb2NrY2hhaW4= 43985 -IGxhd3N1aXRz 43986 -IE1haW50 43987 -Y2h0ZW4= 43988 -IMOpdGFpdA== 43989 -IGt0w7NyZQ== 43990 -X2N0bA== 43991 -KHRpbWVy 43992 -QmF0dGxl 43993 -aXpv 43994 -YXllZA== 43995 -SU9S 43996 -IEdsYXNnb3c= 43997 -IHN5bnRo 43998 -X2xvZ3M= 43999 -LnBvc2U= 44000 -X0FkanVzdG9yVGh1bms= 44001 -KCgm 44002 -IHVuc3VyZQ== 44003 -eXN0YXRl 44004 -7ZWY64qU 44005 -T1VMRA== 44006 -Lm5n 44007 -IGRlZmF1bHRkaWN0 44008 -d29ya3NwYWNl 44009 -IHNlbGVjdGl2ZQ== 44010 -UGlja2VyQ29udHJvbGxlcg== 44011 -WU5BTUlD 44012 -Lm1ldGhvZHM= 44013 -IHBhdGh3YXlz 44014 -IEZldw== 44015 -S0c= 44016 -Q1JZUFQ= 44017 -Zm9sbG93aW5n 44018 -IERMQw== 44019 -IFNhcmE= 44020 -IHByZXNldA== 44021 -ZXN0cnVjdG9y 44022 -IEt1cnQ= 44023 -IGFpcnBsYW5l 44024 -IG9tcA== 44025 -IFBhcmVudHM= 44026 -IE1hcnRpbmV6 44027 -LmNvbXBsZXRl 44028 -IGJyb2FkbHk= 44029 -IHNjYXJl 44030 -IE3DqQ== 44031 -IGVsaW1pbmF0aW9u 44032 -IHBvdXJlZA== 44033 -L3N3 44034 -IGNvbXVu 44035 -IG1hc2M= 44036 -IE9yZ2FuaWM= 44037 -IFN0cmluZ1V0aWxz 44038 -aWxhdGVyYWw= 44039 -IHJlbHVjdGFudA== 44040 -LWFnZQ== 44041 -IG56 44042 -LiJc 44043 -IHBhc3Rvcg== 44044 -YWxleg== 44045 -IGVmZWN0 44046 -cHJvdg== 44047 -L2luaXQ= 44048 -IHBlbm4= 44049 -dW5kcw== 44050 -IHNzaXpl 44051 -IFByb2o= 44052 -YmFzZW5hbWU= 44053 -IHNoZWxscw== 44054 -IE5lY2s= 44055 -IEVuZm9yY2VtZW50 44056 -dmlkZWQ= 44057 -c3Rvd24= 44058 -U3BoZXJl 44059 -JHI= 44060 -dXNzZW4= 44061 -YWZpbA== 44062 -IFRlbGVncmFt 44063 -IGFuYWx5dGljYWw= 44064 -0L3Ri9C1 44065 -dXN1YWxseQ== 44066 -eG4= 44067 -IGhpc3Rvcmlhbg== 44068 -IEdyZWdvcnk= 44069 -b2xwaA== 44070 -IFVuYQ== 44071 -IGNvbnRyaWJ1dGVz 44072 -JS0= 44073 -YW50aWFnbw== 44074 -0YDQtdC0 44075 -LnJlZ2lvbg== 44076 -IGFicnVwdA== 44077 -IFVuc3VwcG9ydGVkT3BlcmF0aW9uRXhjZXB0aW9u 44078 -IFRBU0s= 44079 -X2ZpbmlzaA== 44080 -IG5vdG9yaW91cw== 44081 -IFZz 44082 -IE1R 44083 -IHN1bnNldA== 44084 -IHVuYWNjZXB0YWJsZQ== 44085 -YXJjZXI= 44086 -IGlsbHVtaW4= 44087 -IE9yYg== 44088 -IGJo 44089 -RXN0ZQ== 44090 -X2Rpc3BhdGNo 44091 -IHJpcHBlZA== 44092 -IHRvdWpvdXJz 44093 -IFBhcmNlbA== 44094 -X2xs 44095 -LnVzZXJOYW1l 44096 -LmNsYXNzZXM= 44097 -U09VUkNF 44098 -KE51bWJlcg== 44099 -0LXQu9GP 44100 -IGhlYWRwaG9uZXM= 44101 -KHNpZGU= 44102 -Y29uc3RpdHV0aW9u 44103 -YW5uYWg= 44104 -DQogICAgICAgIA0K 44105 -IGNsaWZm 44106 -LXJlZg== 44107 -IG1vc3RyYXI= 44108 -IFBvd2VsbA== 44109 -K3k= 44110 -IEJH 44111 -X2ZyYWdtZW50 44112 -LlBvcnQ= 44113 -IHJlYWxpemluZw== 44114 -cGFyYW1yZWY= 44115 -IGhvbWV0b3du 44116 -QFRhYmxl 44117 -KyI8Lw== 44118 -b21pZA== 44119 -IGR1Zw== 44120 -CWJ0bg== 44121 -IHN1YmplY3RpdmU= 44122 -L2Jyb3dzZXI= 44123 -IHVzaG9ydA== 44124 -IE1vbnRnb21lcnk= 44125 -LXJhdGU= 44126 -CXB1dHM= 44127 -bGV0aWNz 44128 -b3Jucw== 44129 -4oCcV2hhdA== 44130 -ZWVwZXI= 44131 -LkludmFyaWFudA== 44132 -IGNvbmNlYWxlZA== 44133 -X251bXB5 44134 -PT09PT09PT09 44135 -KHBz 44136 -TG9jYXRpb25z 44137 -LmFzdHlwZQ== 44138 -IENIQU5HRQ== 44139 -Lk9yZGVyQnk= 44140 -O2hlaWdodA== 44141 -IGdlbnRl 44142 -IGdydW50 44143 -IFBsYW5l 44144 -IHNhZGx5 44145 -IExvZ2Fu 44146 -X3VzZWM= 44147 -LmRndg== 44148 -IHNpbmNlcg== 44149 -IHBu 44150 -CWd0aw== 44151 -IGluc3RhbGxlcg== 44152 -IGRpc3BsYWNlbWVudA== 44153 -IGJ1cm5z 44154 -0YPRgQ== 44155 -aXZlcmVk 44156 -Ol0pCg== 44157 -c2VhdA== 44158 -YW5pbmc= 44159 -fSkKCgo= 44160 -X3JvbGVz 44161 -YXRpY2Fu 44162 -IGdlbmVyYXRvcnM= 44163 -IGh1cnRz 44164 -IHNuaXBwZXQ= 44165 -IGdzb24= 44166 -IHNlZ3JlZw== 44167 -IGRpc3RyaWJ1dG9y 44168 -IGFkdmFuY2luZw== 44169 -cG9zdGdyZXM= 44170 -IHVzcg== 44171 -IExpcw== 44172 -LmFzc2VydElz 44173 -X2Nk 44174 -IGh5ZHJhdWxpYw== 44175 -LmNvdW50ZXI= 44176 -IEluZGVwZW5kZW5jZQ== 44177 -IGRpZmbDqQ== 44178 -VW5saWtl 44179 -IHRvbWI= 44180 -dmlr 44181 -cG9zdGVk 44182 -d2Y= 44183 -IGRlc2NlbmRpbmc= 44184 -ZHlu 44185 -YW1lbnRhbA== 44186 -IEZydWl0 44187 -IFlv 44188 -LmRvdWJsZQ== 44189 -IElB 44190 -aWV2 44191 -aWJyYXRl 44192 -IFJlbGlnaW9u 44193 -TWFueVRvT25l 44194 -LVRh 44195 -IGJhbmFuYQ== 44196 -IEF2ZW5nZXJz 44197 -IEhvbG9jYXVzdA== 44198 -IGdldEM= 44199 -IGNvbmRv 44200 -IEdvdGhpYw== 44201 -IHByb3NwZXJpdHk= 44202 -VFJBTlM= 44203 -IGRvZXNudA== 44204 -IENoYW9z 44205 -SVRU 44206 -IENVUlJFTlQ= 44207 -XGhlbHBlcnM= 44208 -X1NBVkU= 44209 -YXZpdA== 44210 -Y29tcHV0ZXI= 44211 -X3NoZWV0 44212 -IEJyZXdpbmc= 44213 -IHJvYmJlcnk= 44214 -IOqyvQ== 44215 -INC60L7QvA== 44216 -IG7DpA== 44217 -LnJlZ2V4 44218 -IGRpc3J1cHRpb24= 44219 -IFNpbXVsYXRpb24= 44220 -YXBpZA== 44221 -IHN1cHJlbWU= 44222 -zrw= 44223 -IGNvbW1pc3Npb25lZA== 44224 -IGFic29ycHRpb24= 44225 -IE5ld2Nhc3RsZQ== 44226 -CWNvbnN0cnVjdG9y 44227 -VGVybXM= 44228 -IHJpdg== 44229 -IHJlbGlnaW9ucw== 44230 -V2l0aFRhZw== 44231 -Lkh0bWw= 44232 -bGlua2Vk 44233 -Q29tcG91bmQ= 44234 -IE1hbnM= 44235 -IGxha2Vz 44236 -aXp6bGU= 44237 -LnNldFNpemU= 44238 -YWJlcg== 44239 -IE5lZWRz 44240 -cGFja2FnZXM= 44241 -LlRhYlBhZ2U= 44242 -IHJlZnM= 44243 -IGlvdXRpbA== 44244 -IERvaW5n 44245 -ICJcKA== 44246 -IHBoZW5vbWVuYQ== 44247 -LkdldEludA== 44248 -QUxUSA== 44249 -IHBhcmxpYW1lbnRhcnk= 44250 -IHJlZnVzYWw= 44251 -IGluZXhwZW5zaXZl 44252 -IH0KCgoKCg== 44253 -IHNvbGlkYXJpdHk= 44254 -CXB1c2g= 44255 -aGF1bA== 44256 -IEJlcmU= 44257 -U2l6ZXI= 44258 -SW5kaXZpZHVhbA== 44259 -IGFuY2U= 44260 -IGRpbGU= 44261 -IFBlYWs= 44262 -KGhy 44263 -RWRpdGluZ0NvbnRyb2xsZXI= 44264 -SE4= 44265 -X1BFUklPRA== 44266 -RVRT 44267 -QmFubmVy 44268 -ZXJyb3JNZXNzYWdl 44269 -LkNBU0NBREU= 44270 -LWlnbm9yZQ== 44271 -IFNJR04= 44272 -IE9C 44273 -X2Rk 44274 -KERFRkFVTFQ= 44275 -IHNvbw== 44276 -IFZpY3Rvcmlhbg== 44277 -IGN1cnQ= 44278 -IGRpc2NyZXRl 44279 -cnlsaWM= 44280 -aW1iYWJ3ZQ== 44281 -LnRvRml4ZWQ= 44282 -bMOk 44283 -LnN0ZGlu 44284 -IHF0eQ== 44285 -Uk9MTEVS 44286 -bWVkaWF0ZWx5 44287 -IHBsdW1iaW5n 44288 -IFByb3BlcnR5Q2hhbmdlZA== 44289 -YXJyYW50eQ== 44290 -IEJyZWFrZmFzdA== 44291 -LnNldEhlYWRlcg== 44292 -LnB5dGhvbg== 44293 -Y29tbWVyY2U= 44294 -b3BlbmN2 44295 -Pi0tfX0K 44296 -RnJlbmNo 44297 -RW50aXR5TWFuYWdlcg== 44298 -IFBsYWlu 44299 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8= 44300 -wrM= 44301 -KFJF 44302 -Y2FwdA== 44303 -IG9yZ2FuaXNtcw== 44304 -IGpldHM= 44305 -b2xvY2F0aW9u 44306 -IEFwcFJvdXRpbmdNb2R1bGU= 44307 -IGdsb3Jpb3Vz 44308 -5pyN 44309 -IGRpc2NhcmRlZA== 44310 -CQkJCSAgICAg 44311 -IEFybm9sZA== 44312 -bHVn 44313 -IHBhcmw= 44314 -IGhvcm1vbmVz 44315 -IG1haA== 44316 -IFNvbmlj 44317 -IG9yZ2FuaXplcnM= 44318 -X1BMQVRGT1JN 44319 -Lmludg== 44320 -IGNob3Jk 44321 -dmVudGlvbmFs 44322 -CW9m 44323 -RXBpc29kZQ== 44324 -LkVudW0= 44325 -dW5rdA== 44326 -IERo 44327 -IEphcmVk 44328 -IE5haw== 44329 -IGludGVuZHM= 44330 -RW5kaWFu 44331 -IGF1c3RyYWxpYQ== 44332 -X2N2 44333 -KHJlc29sdmU= 44334 -IGNsaW5pY3M= 44335 -bGlrZWQ= 44336 -QVNISU5HVE9O 44337 -aW5oYQ== 44338 -Jyo= 44339 -IE5Q 44340 -X2JlaA== 44341 -IGhm 44342 -IHfDvHI= 44343 -Y2F0ZWdvcmlh 44344 -JGZvcm0= 44345 -IHN1YndheQ== 44346 -IGlzQWN0aXZl 44347 -cG9wdWxhcg== 44348 -Q291cg== 44349 -IGNvb2xkb3du 44350 -IGFpbnNp 44351 -IEdMdWludA== 44352 -ZXJlYWw= 44353 -IGFycmF5T2Y= 44354 -IGhhdGNo 44355 -PT09PT09PT09PQ== 44356 -cmVzc2Vz 44357 -X1BQ 44358 -Ll4= 44359 -X2RlY2F5 44360 -IEJsZXNz 44361 -bWV0cmljcw== 44362 -IENPUFlJTkc= 44363 -IER1bXBzdGVy 44364 -IEpvc8Op 44365 -IERlc2lnbnM= 44366 -PFZvaWQ= 44367 -57q/ 44368 -ID8+PA== 44369 -ICJ9Cg== 44370 -dGltZXpvbmU= 44371 -IGVlcg== 44372 -bWF4Y2Ru 44373 -IEVTQw== 44374 -aWdhcmV0 44375 -X2Nvbm5lY3RlZA== 44376 -X3JldmVyc2U= 44377 -IHF1ZXN0aW9uYWJsZQ== 44378 -IFVTQw== 44379 -IHR1dHRp 44380 -IGRyb3BvdXQ= 44381 -IEFjdGl2aXRpZXM= 44382 -IFdpbmRz 44383 -JykpKTsK 44384 -IGNvbmdlc3Q= 44385 -xJ/EsQ== 44386 -IHByb2xvbmdlZA== 44387 -6L+Z 44388 -IENyb3NzQXhpc0FsaWdubWVudA== 44389 -TEVFUA== 44390 -IFZBTElE 44391 -IEdheg== 44392 -IGRlcGVuZGVuY2U= 44393 -IFByaXg= 44394 -LkNvbXBpbGVyU2VydmljZXM= 44395 -anVtcA== 44396 -IHN0cmF0 44397 -Y2lyYw== 44398 -IENVU1RPTQ== 44399 -eGFh 44400 -IGJtcA== 44401 -IGJ1cmVhdQ== 44402 -IHdhcmVu 44403 -Tlg= 44404 -KFdpbmRvdw== 44405 -IENocmlzdGll 44406 -X0ZF 44407 -IHRu 44408 -IE9tZWdh 44409 -Y29tbXVuaWNhdGlvbnM= 44410 -SG9tZVBhZ2U= 44411 -Y29tcGxldGlvbg== 44412 -IHN1cHBseWluZw== 44413 -WVBFUw== 44414 -w6F2ZWw= 44415 -5Yi2 44416 -KGNsaWNr 44417 -XENvbnRyYWN0cw== 44418 -L3F1ZXN0aW9ucw== 44419 -IGV6 44420 -QU1T 44421 -Lm1lc2g= 44422 -ICc8Pw== 44423 -asOg 44424 -SW5p 44425 -LiM= 44426 -IENhcmRpbmFscw== 44427 -cGNpw7Nu 44428 -Q3ViZQ== 44429 -IFBhdGllbnRz 44430 -X3ByZWY= 44431 -QWN0aW9uQnV0dG9u 44432 -KGJ1aWxk 44433 -IFZpc2E= 44434 -b3ZlbA== 44435 -KEFycmF5TGlzdA== 44436 -SWdu 44437 -IHJlaGFiaWxpdGF0aW9u 44438 -IHBhbGFjZQ== 44439 -IHNwZWVjaGVz 44440 -fScK 44441 -SHR0cFJlc3BvbnNl 44442 -CWNvZGU= 44443 -RHVtbXk= 44444 -IGFjYWRlbXk= 44445 -Lm1vdmll 44446 -IGluY29ycmVjdGx5 44447 -IGN5Yw== 44448 -KFVuaXR5RW5naW5l 44449 -CWNhbGxiYWNr 44450 -IFNhdGFu 44451 -IEZVTkM= 44452 -IGNoYW50 44453 -IEhlYWx0aHk= 44454 -OicsCg== 44455 -U2hpcHBpbmc= 44456 -X21j 44457 -IER5bGFu 44458 -IFByb2R1Y2Vy 44459 -IHJlc3B1ZXN0YQ== 44460 -IHBvbGlzaGVk 44461 -QnJvYWRjYXN0 44462 -IGJhbGFuY2luZw== 44463 -IFNsaWRl 44464 -IENhcHM= 44465 -c3RpbGw= 44466 -IGhhcHBpZXI= 44467 -IEdvc3BlbA== 44468 -dHJhbg== 44469 -LnBhdGhuYW1l 44470 -QWN0aXZlU2hlZXQ= 44471 -IENoYW5n 44472 -PlwK 44473 -Um9ib3Q= 44474 -SnNvbk9iamVjdA== 44475 -IERG 44476 -IFByb2Nlc3Nvcg== 44477 -X3Nob3VsZA== 44478 -LnByb3RvYnVm 44479 -LXVzZXJz 44480 -IGVtYnJ5 44481 -Rk9OVA== 44482 -IHN0YXJ0dXBz 44483 -IERhdGFTb3VyY2U= 44484 -KSM= 44485 -dXJvcw== 44486 -X0NvbG9y 44487 -IHN0YW5kYWxvbmU= 44488 -fVs= 44489 -amQ= 44490 -IGZvcmdpdmU= 44491 -IG5neA== 44492 -IEdlbmVyYWxseQ== 44493 -IGNvbmZpZ3VyYWJsZQ== 44494 -L29yZGVy 44495 -IHZhcw== 44496 -JykiOwo= 44497 -IFJS 44498 -IFRyb3k= 44499 -IGNvbXByb21pc2Vk 44500 -IFN3YW4= 44501 -aW50ZW5kZW50 44502 -Q2VudHJhbA== 44503 -X2tlZXBlcg== 44504 -IGFycXVpdm8= 44505 -IFJlYWRPbmx5 44506 -X2N1cnZl 44507 -a3Y= 44508 -ZW50aW4= 44509 -6LE= 44510 -IEV5 44511 -LmltcmVhZA== 44512 -IFBhbQ== 44513 -aWZmZQ== 44514 -YXRpdml0eQ== 44515 -eGJj 44516 -IGdyaW0= 44517 -LWZpbGxlZA== 44518 -bmFtZXNl 44519 -J106 44520 -IGF1cg== 44521 -IEdpYnNvbg== 44522 -Lk1vdXNlRXZlbnQ= 44523 -IGxhZG8= 44524 -YXZhZG9j 44525 -IGZhbWls 44526 -IE1vZGVy 44527 -ZnBz 44528 -44CA44CA 44529 -LWV4YW1wbGU= 44530 -IEFsemhlaW1lcg== 44531 -IFV0Zg== 44532 -X2FyZ3VtZW50cw== 44533 -Q29uY2x1c2lvbg== 44534 -dGV4dENvbnRlbnQ= 44535 -cmVtYWluaW5n 44536 -IGludGVycnVwdHM= 44537 -IEJhY2t1cA== 44538 -IE1vbmc= 44539 -IHJlY2VwdG9ycw== 44540 -aGlzdG9y 44541 -LmNvcm91dGluZXM= 44542 -IHNob3V0ZWQ= 44543 -QWxhcm0= 44544 -IGNvbWJ1c3Q= 44545 -IGdyb3Rl 44546 -dWx0dXJhbA== 44547 -KGlkcw== 44548 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 44549 -aXBsaW5hcnk= 44550 -T3B0cw== 44551 -IFlhbGU= 44552 -bG9jYWxTdG9yYWdl 44553 -IGVxdWl2YWw= 44554 -IEZsZWV0 44555 -XGI= 44556 -KnBp 44557 -IFFMYWJlbA== 44558 -5qE= 44559 -IHZ4 44560 -IEFDTA== 44561 -IHN1Y2Vzc28= 44562 -IHBlcmM= 44563 -IE5vdHJl 44564 -IGFuYXJjaA== 44565 -UmluZw== 44566 -c3Bi 44567 -IHN0cnBvcw== 44568 -c3RvcmVz 44569 -IE1hcGxl 44570 -KE1haW5BY3Rpdml0eQ== 44571 -KCIiKSk= 44572 -IHZpZXdIb2xkZXI= 44573 -UXVhZA== 44574 -IGlndWFs 44575 -b3JzY2hl 44576 -Lm1hcmdpbg== 44577 -IGluZGll 44578 -IGZyYW5j 44579 -IEZvcm1CdWlsZGVy 44580 -IFBhcnRpY2lw 44581 -LmZsYXNo 44582 -IHN0b3Jtcw== 44583 -VWx0 44584 -IGZlbg== 44585 -W25ldw== 44586 -RXZlcg== 44587 -PSIK 44588 -IGxvY2FsaXplZA== 44589 -X2ZvbGxvdw== 44590 -IG5hdmU= 44591 -IGRvbWluYW5jZQ== 44592 -KHRpbGU= 44593 -Sm91cm5hbA== 44594 -IFZD 44595 -IHBlbmV0cmF0aW9u 44596 -77yV 44597 -IGNvbXBhcnRtZW50 44598 -IGJpZHM= 44599 -Rm9ybWF0dGVk 44600 -KioqKioqLwoK 44601 -KGNpdHk= 44602 -4oCUaXQ= 44603 -W0M= 44604 -IHVzZUNhbGxiYWNr 44605 -YXVi 44606 -KT8u 44607 -IFZBUg== 44608 -IFNlYmFzdGlhbg== 44609 -IE1vc3M= 44610 -IGFidW5kYW50 44611 -R3JlZw== 44612 -0YLQsA== 44613 -X2Np 44614 -IGJpYmxp 44615 -Q1JN 44616 -IEF0dGVtcHQ= 44617 -aXNtZQ== 44618 -ZGFzaA== 44619 -44CO 44620 -X211 44621 -LkZvcm1hdHRpbmdFbmFibGVk 44622 -SW5kZWVk 44623 -LWRpcmVjdA== 44624 -IHN1Y2tpbmc= 44625 -IHBuZQ== 44626 -b2NhYnVsYXJ5 44627 -IFBhY2tlcnM= 44628 -Lk5hdmlnYXRpb24= 44629 -IHBpZWQ= 44630 -Y3JpYmluZw== 44631 -IFN0dWFydA== 44632 -LlRvRG91Ymxl 44633 -IFNlY29uZGFyeQ== 44634 -U2F2aW5n 44635 -IER1dA== 44636 -IE1hZGQ= 44637 -TWFnaWM= 44638 -LEg= 44639 -LmRvY3VtZW50RWxlbWVudA== 44640 -IEJTVA== 44641 -IGRpZmZlcnM= 44642 -IG1vcmVvdmVy 44643 -X25k 44644 -U0VBUkNI 44645 -0L/RgNCw0LI= 44646 -5rQ= 44647 -dG9NYXRjaA== 44648 -IGRlY3JlYXNpbmc= 44649 -LW1lbWJlcg== 44650 -YW1wdXM= 44651 -KGJvb3N0 44652 -RGFpbHk= 44653 -RGF0YUdyaWRWaWV3 44654 -IEh0dHBDb250ZXh0 44655 -IGhpcHA= 44656 -X3dvcmtlcnM= 44657 -LWxhbmd1YWdl 44658 -6ZM= 44659 -IGNvbnNpc3RlZA== 44660 -YXRoaW5n 44661 -IE1lcmN1cnk= 44662 -JGNvbnRlbnQ= 44663 -IHByYWN0aWNlZA== 44664 -IE1vZHVsZXM= 44665 -X0RBWQ== 44666 -IHdlYWtuZXNzZXM= 44667 -IExvZGdl 44668 -IG5hcg== 44669 -IE1hdGU= 44670 -IGpw 44671 -IEh0dHBIZWFkZXJz 44672 -IHNtbw== 44673 -IFRPS0VO 44674 -XSko 44675 -IGFxdWk= 44676 -c3dhZ2Vu 44677 -IHNydg== 44678 -CWFucw== 44679 -QXJvdW5k 44680 -IE1hbnVlbA== 44681 -IGZpY3Rpb25hbA== 44682 -IElNRw== 44683 -IC4n 44684 -IEJlcnJ5 44685 -IHdhbGxwYXBlcg== 44686 -c2V4dWFs 44687 -aWVybw== 44688 -IOeahA== 44689 -7IaM 44690 -QmFja2luZ0ZpZWxk 44691 -IEFkcmlhbg== 44692 -QkFTRVBBVEg= 44693 -IHJlcGVhdHM= 44694 -IGJsdWVz 44695 -IHVucHJlZGljdA== 44696 -X2NvbGw= 44697 -c3RhY2xl 44698 -IFR1bWJscg== 44699 -IEVsZg== 44700 -IGFzc3VyYW5jZQ== 44701 -IGNlbnN1cw== 44702 -IElNUE9SVA== 44703 -RU5ERVI= 44704 -YW5vcw== 44705 -ID0o 44706 -IEVsbGlz 44707 -IgoKCgo= 44708 -Lndpbg== 44709 -IEFib3Zl 44710 -YWxvbg== 44711 -X3RpY2s= 44712 -IHJlcHJlc2VudGF0aW9ucw== 44713 -IOaV 44714 -d2lk 44715 -IEFybXM= 44716 -TGlzdGE= 44717 -X2ZhaWx1cmU= 44718 -X2Nt 44719 -LkZsYXRBcHBlYXJhbmNl 44720 -IHRocm9uZQ== 44721 -UGF0Y2g= 44722 -IFZveQ== 44723 -ZW5nbA== 44724 -IG5lZ290aWF0aW5n 44725 -PmA= 44726 -IHNob290cw== 44727 -IEZQUw== 44728 -LlllYXI= 44729 -IEtpc3M= 44730 -ZW5jacOzbg== 44731 -cmVldGluZw== 44732 -RnJvbUZpbGU= 44733 -IHJlc2lnbmF0aW9u 44734 -2Lc= 44735 -IHR3aW5z 44736 -xrDhu6M= 44737 -IGdlYnJ1 44738 -LmdldENvbnRlbnQ= 44739 -LlRyZWU= 44740 -IEVtcGxveWVlcw== 44741 -IEZJRkE= 44742 -IGNlcnRhaW50eQ== 44743 -KENs 44744 -IHRvdGFscw== 44745 -ZWRpdGFibGU= 44746 -4KWA 44747 -LlJlcG9ydGluZw== 44748 -TWFz 44749 -cXVpZXQ= 44750 -LnJ1bGVz 44751 -IFZP 44752 -Y29uZXhpb24= 44753 -LEs= 44754 -IGFsbG9jYXRvcg== 44755 -IFBvd2Rlcg== 44756 -XFJlcG9zaXRvcnk= 44757 -QmVhdA== 44758 -X3RpcG8= 44759 -IFsnJyw= 44760 -X0lOVFI= 44761 -IDw8PA== 44762 -PGhy 44763 -Iik9PQ== 44764 -dWdnYWdl 44765 -IENyYXc= 44766 -IMOpZ2FsZW1lbnQ= 44767 -IGdpbmdlcg== 44768 -IHByaW1lcmE= 44769 -IHByb2R1dG8= 44770 -bHRr 44771 -LlVzZXJOYW1l 44772 -IHN0cmVycm9y 44773 -bWl0aA== 44774 -X25i 44775 -IGRpc2NvbWZvcnQ= 44776 -J107Pz48Lw== 44777 -UVQ= 44778 -IGVydXB0 44779 -IERhbmlzaA== 44780 -XEFjdGl2ZQ== 44781 -X2FkYXB0ZXI= 44782 -IGJ1YmJsZXM= 44783 -cm9sbG8= 44784 -b3Jnb3Q= 44785 -0L3Ri9GF 44786 -VkVDVE9S 44787 -b2NvZGU= 44788 -IEJ1bGxz 44789 -IGJvaWw= 44790 -PiIpOw0K 44791 -ZHJvcElmRXhpc3Rz 44792 -IEJlZw== 44793 -X0hBTA== 44794 -IGNyb3NzQXhpc0FsaWdubWVudA== 44795 -IEV2aWRlbmNl 44796 -IHBlY3VsaWFy 44797 -IGluc3RpdHV0ZQ== 44798 -dmVpcw== 44799 -IGZmdA== 44800 -w4E= 44801 -IHpvZWt0 44802 -YW5hbHk= 44803 -IEhvbWVsYW5k 44804 -IHBlbmV0cg== 44805 -dWRkZW5seQ== 44806 -CWVsZW1lbnQ= 44807 -IEJyZW4= 44808 -IFRydWRlYXU= 44809 -IEN1YmFu 44810 -amFt 44811 -dXNsaW0= 44812 -X2V2 44813 -IHN0ZW1z 44814 -fSU= 44815 -neWniw== 44816 -IGJyYW5kaW5n 44817 -IGNvcnJlc3BvbmRlbmNl 44818 -LmpxdWVyeQ== 44819 -ouWNlQ== 44820 -IFJlYWRz 44821 -KEh0dHBTdGF0dXNDb2Rl 44822 -YXNzaW4= 44823 -KHNsb3Q= 44824 -IEdyYWR1YXRl 44825 -Ly8vPA== 44826 -IGluZm9ybWF0aW9ucw== 44827 -RU5BQkxF 44828 -IHB1aXM= 44829 -IGZpbmRlcg== 44830 -IEJyaXM= 44831 -IG5ldHRzdGVkZXI= 44832 -X21pZA== 44833 -IG9ncw== 44834 -IFN0ZXJsaW5n 44835 -IGFycm9n 44836 -c3RyZnRpbWU= 44837 -fAoK 44838 -IHZveA== 44839 -IFJlZ2FyZGxlc3M= 44840 -IGVzbw== 44841 -IENvbWZvcnQ= 44842 -LkJvb2xlYW5GaWVsZA== 44843 -IHVo 44844 -QUNZ 44845 -IHNxdWVleg== 44846 -IFZpYw== 44847 -Y29udHJv 44848 -Lmxv 44849 -IGlyZQ== 44850 -IENvbWVkeQ== 44851 -67Y= 44852 -IG9yaWdpbmF0ZWQ= 44853 -IHNoaXBtZW50 44854 -fG1heA== 44855 -X2d1aWQ= 44856 -bGV2YXRpb24= 44857 -0L3QsNGP 44858 -KHVuZGVmaW5lZA== 44859 -IEREUg== 44860 -IHNob290aW5ncw== 44861 -IExhdGlubw== 44862 -RU5ET1I= 44863 -IGF2ZXJhZ2luZw== 44864 -IGdyZWV0ZWQ= 44865 -IHRoZWF0ZXJz 44866 -0L7QtQ== 44867 -IGRC 44868 -IGdzdA== 44869 -IGRlZmluaXRl 44870 -LlN0b3JhZ2U= 44871 -Lmhlcg== 44872 -IGFmb3Jl 44873 -IFJlYWxpdHk= 44874 -IEdvZHM= 44875 -dmVyc2Vk 44876 -IGhhbmRzb21l 44877 -IGV4Y2x1ZGluZw== 44878 -KGFk 44879 -UXVvdGVz 44880 -IFNjaGVtZQ== 44881 -P3E= 44882 -IFRhbWls 44883 -VGlja3M= 44884 -IHBlc3Q= 44885 -J24= 44886 -IHBvcm5vZ3JhcGh5 44887 -X21vZGFs 44888 -IC0tLS0tLS0tLS0= 44889 -IGRpc3Bvc2FibGU= 44890 -RlJFRQ== 44891 -IHNoYXJr 44892 -Q0hF 44893 -IGRlcGljdGVk 44894 -IGRlbW9uc3RyYXRpb25z 44895 -IEtpbGxlZA== 44896 -IFJVTEU= 44897 -IG9ic2Vzc2Vk 44898 -IHNpbXBsaWZpZWQ= 44899 -UG9zdGFs 44900 -IGNvbmNlcHR1YWw= 44901 -IHBzdA== 44902 -TGFz 44903 -X1BST0pFQ1Q= 44904 -dWNjZWVkZWQ= 44905 -b2x1 44906 -xJ9p 44907 -IHBlcnNvbmFsaXRpZXM= 44908 -IHJlc2hhcGU= 44909 -IGVuY2xvc2Vk 44910 -CXB0cg== 44911 -IHR1dG9yaWFscw== 44912 -IGV4cGxvZGVk 44913 -X0RJUkVDVE9SWQ== 44914 -5YaF5a65 44915 -IGNhbm9u 44916 -IHJlY29nbmlzZQ== 44917 -UEFE 44918 -IEFwcHJveA== 44919 -IFJlc3RvcmU= 44920 -IEltcG9ydGFudA== 44921 -IGhlYXZpZXI= 44922 -LlNlcXVlbnRpYWw= 44923 -RWFydGg= 44924 -IE1pbGs= 44925 -LnNldFJlcXVlc3Q= 44926 -LnRlbQ== 44927 -IHJlY29uc3RydWN0 44928 -IHNrZXB0aWNhbA== 44929 -X1ByaXZhdGU= 44930 -QlVG 44931 -cXVh 44932 -OmE= 44933 -IHNlaw== 44934 -IGR3ZWxs 44935 -b3NzYQ== 44936 -IHJld2FyZGVk 44937 -0LjQuQ== 44938 -KHRvcGlj 44939 -X3BhcnRpdGlvbg== 44940 -IF9fX19fX19fX19fX19fX19fXw== 44941 -S2V5d29yZHM= 44942 -IEZyYW5jbw== 44943 -TGl0ZQ== 44944 -IG5ha2Vu 44945 -INC30LA= 44946 -T0JKRUNU 44947 -IGNyYWZ0cw== 44948 -IFN3YXA= 44949 -LlhuYQ== 44950 -LkNvbm5lY3Q= 44951 -IGJhbGNvbnk= 44952 -KHJlYWw= 44953 -IEJhcm5lcw== 44954 -Ymly 44955 -IFR3ZW50eQ== 44956 -YXlhbg== 44957 -YXRhcnM= 44958 -IFByb3BlbA== 44959 -IElobmVu 44960 -VXBncmFkZQ== 44961 -IGN1cmI= 44962 -LXNlY29uZA== 44963 -IG5lcGg= 44964 -LnByZXM= 44965 -7J6F 44966 -LnNlcQ== 44967 -IHBhZGRlZA== 44968 -Ij8= 44969 -amw= 44970 -44Os 44971 -Jyk8Lw== 44972 -IGNpdmlj 44973 -Z29ucw== 44974 -PmE= 44975 -Q29vcmRpbmF0ZXM= 44976 -IGVuYWN0ZWQ= 44977 -RU5UUw== 44978 -IGxhYw== 44979 -LmZpbmFs 44980 -IFBocFN0b3Jt 44981 -Y2FsbGVk 44982 -IGlucXVpcmllcw== 44983 -Lm1pZGRsZXdhcmU= 44984 -IERvd250b3du 44985 -Lyc7Cg== 44986 -IGtpbG9tZXQ= 44987 -YWNjZWw= 44988 -IHF1aWVu 44989 -d3N0cmluZw== 44990 -c2V0RGF0YQ== 44991 -IG1hbmVyYQ== 44992 -IG1vZHVsYXI= 44993 -cmltcA== 44994 -IHRhcmlmZnM= 44995 -4oCZaWw= 44996 -X1RIUk9X 44997 -L2NvbG9y 44998 -IEhUTUxFbGVtZW50 44999 -IGNhcnJv 45000 -IHByZXJl 45001 -IHBsb3R0aW5n 45002 -IFBvc2l0aXZl 45003 -IE1hY2hpbmVz 45004 -T1RFUw== 45005 -4bub 45006 -cGxlYXNhbnQ= 45007 -IGFsdGU= 45008 -IGFpbmRh 45009 -dGhlc2U= 45010 -IGNvcnM= 45011 -aXBheQ== 45012 -IEFkdmlzb3J5 45013 -IFJ1Ymlv 45014 -anE= 45015 -IGxpbWVzdG9uZQ== 45016 -IGRldGFjaGVk 45017 -6K6+572u 45018 -dGVuYW50 45019 -IERlcHRo 45020 -YWxvcmU= 45021 -INGB0YLRgNC+0Lo= 45022 -IEZPUkU= 45023 -IExheQ== 45024 -cHJlc2VudGF0aW9u 45025 -KScpOwo= 45026 -LnN1YnBsb3Rz 45027 -z4M= 45028 -Tk9X 45029 -R2Fy 45030 -aGFuZGxlcw== 45031 -YWJyYQ== 45032 -cHV0aWVz 45033 -IEVsZWN0cmljYWw= 45034 -TWlkZGxl 45035 -cm9waWM= 45036 -IEpE 45037 -IER5bg== 45038 -IEJyaXN0b2w= 45039 -IE1jQ2FydGh5 45040 -IHN0cmlrZXI= 45041 -IGVudW1lcmFibGU= 45042 -IEV2YW4= 45043 -LmRlZmF1bHRz 45044 -cXVlbmNlcw== 45045 -KXx8 45046 -CXRva2Vu 45047 -4peP 45048 -LWRyb3Bkb3du 45049 -U1RPUkU= 45050 -IEdyYXBoaWM= 45051 -KHBw 45052 -RXhwbA== 45053 -IHVwd2FyZHM= 45054 -IERpc3RyaWJ1dGVk 45055 -IFdFQg== 45056 -SmVy 45057 -aXNOYU4= 45058 -55Sf5oiQ 45059 -PlI= 45060 -w7xzc2Vu 45061 -ZWZz 45062 -IHVuY292ZXI= 45063 -IGx1ZA== 45064 -LmNhbGN1bGF0ZQ== 45065 -IGludHB0cg== 45066 -IG1pZGZpZWxkZXI= 45067 -LkhlYWRlcnM= 45068 -IG1m 45069 -ZXJlZg== 45070 -Lk1ldHJv 45071 -IFNwZWFraW5n 45072 -OmI= 45073 -IGNyeXB0b2N1cnJlbmNpZXM= 45074 -IGRlbW9ucw== 45075 -CUVYUEVDVA== 45076 -IHdpY2tlZA== 45077 -eW91dHViZQ== 45078 -OkludA== 45079 -IEhpbmRp 45080 -IENBVA== 45081 -INi5 45082 -cmFy 45083 -b21vcmU= 45084 -L3Blcg== 45085 -L2xpY2Vuc2U= 45086 -IHJlaW0= 45087 -IGF3YWl0aW5n 45088 -IGxldGhhbA== 45089 -IEVG 45090 -cm91bmRlZA== 45091 -IFBsYXRpbnVt 45092 -INCy0YHQtQ== 45093 -LmNvb3Jkcw== 45094 -LkRldmljZQ== 45095 -L2l0ZW0= 45096 -IFdlbm4= 45097 -Y29tcGlsZUNvbXBvbmVudHM= 45098 -IEtpbmRlcg== 45099 -LnJlbW92ZUl0ZW0= 45100 -IGFuZGE= 45101 -Ym5i 45102 -IHByYQ== 45103 -KHRyYW5zYWN0aW9u 45104 -IGVtYmFycmFzc2luZw== 45105 -CUJPT0w= 45106 -LmNvbnRlbnRWaWV3 45107 -IGV2ZW50ZGF0YQ== 45108 -YXRvcmU= 45109 -IHByb3ZpZGVkSW4= 45110 -aXJtYQ== 45111 -IHpvbmE= 45112 -X0hX 45113 -5pk= 45114 -IHN0b3Zl 45115 -IGNvdW50ZXJwYXJ0 45116 -X1Byb2R1Y3Q= 45117 -X01BTkFHRVI= 45118 -IGluZnJpbmc= 45119 -IEVSQQ== 45120 -X3BhcnR5 45121 -0ZE= 45122 -IGluaWNp 45123 -X1JlcXVlc3Q= 45124 -IG1pcmFjbGU= 45125 -IGNhbmNlbEJ1dHRvbg== 45126 -U3B5 45127 -YXTDsw== 45128 -IHBvbGlzaA== 45129 -IE5pY29sZQ== 45130 -LmRpc3BsYXlOYW1l 45131 -XFJlcXVlc3Rz 45132 -IHVzZUhpc3Rvcnk= 45133 -Um91dGVyTW9kdWxl 45134 -IHN0YXJlZA== 45135 -SURFUg== 45136 -0YPQvdC60YbQuA== 45137 -IG5vdGE= 45138 -JGFycg== 45139 -cGVjaWZpZWQ= 45140 -IHRvcHA= 45141 -X0RSSVZFUg== 45142 -L25n 45143 -5aA= 45144 -X3Rt 45145 -JXRpbWVvdXQ= 45146 -PHM= 45147 -ICgqKQ== 45148 -IEh0dHBSZXF1ZXN0 45149 -X1RSQUNL 45150 -KG5vdGU= 45151 -IEV4cGxvcmU= 45152 -X3NlcnY= 45153 -IOe7 45154 -QmluZGVy 45155 -KyIs 45156 -LmF0dA== 45157 -IEV0aGk= 45158 -IGPDs2RpZ28= 45159 -PSdc 45160 -LmxpbmVz 45161 -KE9m 45162 -5bCG 45163 -bWlzc2libGU= 45164 -IHbDqQ== 45165 -IGFjb3VzdGlj 45166 -IGNyYWZ0aW5n 45167 -bml0 45168 -LmJh 45169 -IEx1Y3k= 45170 -IGlQb2Q= 45171 -IHB1cGlscw== 45172 -LW1heA== 45173 -X3dy 45174 -KGNw 45175 -IFJFUE9SVA== 45176 -IGRucw== 45177 -IFJlZmVyZW5jZXM= 45178 -IHVuZGVydGFrZW4= 45179 -IGvDuGJlbmhhdm4= 45180 -IGNoYWk= 45181 -IENyb2F0 45182 -X0xvZw== 45183 -cm93bmVk 45184 -X21lZA== 45185 -CWRhdGU= 45186 -I19f 45187 -IGNvc3R1bWVz 45188 -IFJlcXVpcmVz 45189 -YWZmbGU= 45190 -54q25oCB 45191 -LVNlbWl0 45192 -ZWxhaWRl 45193 -0LXRgtC+0LQ= 45194 -IHBlc3RpYw== 45195 -IGRyYQ== 45196 -RE9DVU1FTlQ= 45197 -IC4uLg0K 45198 -fWB9Cg== 45199 -IEF1Y3Rpb24= 45200 -IERvY2s= 45201 -eHh4eHh4eHg= 45202 -KGdldFN0cmluZw== 45203 -hY0= 45204 -IGJvcmRlcldpZHRo 45205 -IE1hY2hpbmVyeQ== 45206 -IHByZWRpY3RhYmxl 45207 -LlNI 45208 -IGFtcGxpdHVkZQ== 45209 -LmZvclJvb3Q= 45210 -SU5hdmlnYXRpb24= 45211 -VGFibGVNb2RlbA== 45212 -YXR0cmli 45213 -IG1hbmV1dmVy 45214 -IGV4Y2F2 45215 -QkVSUw== 45216 -IGRhcGF0 45217 -IGluc3RhbGxhdGlvbnM= 45218 -LkFzeW5j 45219 -IHJheXM= 45220 -PeKAnQ== 45221 -Ow0NCg== 45222 -LmNyeXB0bw== 45223 -X2RiZw== 45224 -IEVudW1lcmFibGU= 45225 -T2ZTaXpl 45226 -X2Vwb2Nocw== 45227 -bXc= 45228 -TUVOVQ== 45229 -b3V0bGluZQ== 45230 -IFBhcGVycw== 45231 -PT09PT09PT09PT09Cg== 45232 -IHVuaWZvcm1z 45233 -IEdpZw== 45234 -LXBhY2thZ2U= 45235 -IEplbmtpbnM= 45236 -IEhvbWVQYWdl 45237 -LmlzU2VsZWN0ZWQ= 45238 -IG1lY2hhbmlj 45239 -TUs= 45240 -IFNvdW5kcw== 45241 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo= 45242 -IHJlc2VhcmNoaW5n 45243 -IGluZm9z 45244 -b2dyYXBoaWNz 45245 -ZXJzZXQ= 45246 -KFsnLw== 45247 -IFRpbWJlcg== 45248 -LmFnZW50 45249 -LnRvSlNPTg== 45250 -X2NvbW1hbmRz 45251 -cGFyaW5n 45252 -X2FkanVzdA== 45253 -Lm5vbWU= 45254 -KGdsbQ== 45255 -U3RhdHVzQmFy 45256 -ZmlsZXBhdGg= 45257 -P+KAmQ== 45258 -IGRldGVjdGl2ZQ== 45259 -IHVuc2VyZXI= 45260 -IFRpYmV0 45261 -RU5ERUQ= 45262 -KHNlZWQ= 45263 -IHNuZWFr 45264 -IGFtb3I= 45265 -PSIvLw== 45266 -IFBhbnRoZXJz 45267 -YWxsYXg= 45268 -IExJVkU= 45269 -CURXT1JE 45270 -XT0t 45271 -IHRvcm5hZG8= 45272 -L21pbg== 45273 -IGx1bmdz 45274 -LWN1cnJlbnQ= 45275 -IEJvb2tpbmc= 45276 -5YiX6KGo 45277 -IGVuam95bWVudA== 45278 -4KSw 45279 -SkE= 45280 -dHlwZWQ= 45281 -LkJ0bg== 45282 -ZmF0 45283 -dWdhbA== 45284 -IFNoYXJlcw== 45285 -IGRpc2dy 45286 -IEJBUg== 45287 -IEZPWA== 45288 -T3Bjb2Rl 45289 -IFN6 45290 -a2V5ZG93bg== 45291 -aWN0aW9uYXJpZXM= 45292 -IGRldGFpbGluZw== 45293 -fSkpCg== 45294 -IHBvaw== 45295 -IGRlbW9uc3RyYXRpbmc= 45296 -IG5vdGF0aW9u 45297 -bGF5ZXJz 45298 -QGlm 45299 -IE5QUg== 45300 -LnN0cmljdEVxdWFs 45301 -IFJlY2lwZXM= 45302 -LlRlbnNvcg== 45303 -IGxpcXVvcg== 45304 -IGRlYnRz 45305 -LmVuZHNXaXRo 45306 -V2hlZWw= 45307 -LlBvcw== 45308 -Q1NW 45309 -JGFyaXR5 45310 -IHVuc3RhYmxl 45311 -KGxvc3M= 45312 -RU5TT1I= 45313 -IGVsZXZlbg== 45314 -IExvcGV6 45315 -IEhvcGtpbnM= 45316 -Y29ub20= 45317 -IFNldGg= 45318 -IHBvZW1z 45319 -UXVhbnQ= 45320 -IGdzbA== 45321 -IHN5cnVw 45322 -IHNpYmxpbmc= 45323 -IGNhc3M= 45324 -LXZvdXM= 45325 -w7Z0 45326 -X1BBVFRFUk4= 45327 -X1NFQ1RJT04= 45328 -ZXN0aW1hdGVk 45329 -dXBncmFkZQ== 45330 -Lm1vbmdvZGI= 45331 -IEJvYXQ= 45332 -X0NUWA== 45333 -IGZldGNoaW5n 45334 -dXN0aW4= 45335 -cGllbA== 45336 -TWFyZw== 45337 -UmVmbGVjdGlvbg== 45338 -IGR1Y3Q= 45339 -IE11bmljaXBhbA== 45340 -IGJ4 45341 -LkdldEN1cnJlbnQ= 45342 -bWxpbms= 45343 -IEFjY291bnRpbmc= 45344 -IEdlbmV2YQ== 45345 -X1Bvcw== 45346 -IHBhc3Nlcg== 45347 -IGhlYXJpbmdz 45348 -Y29tcGFu 45349 -IGZyYWdpbGU= 45350 -SW5pdGlhbGl6ZXI= 45351 -d2Fsa2Vy 45352 -Lk1hdGVyaWFs 45353 -IEh1bnRpbmc= 45354 -dHJ5c2lkZQ== 45355 -IGthdA== 45356 -IGNsZXJr 45357 -4Z8= 45358 -ZG9pbmc= 45359 -CWdyb3Vw 45360 -IHNhbmN0aW9u 45361 -Lmxi 45362 -IExhenk= 45363 -IENvbnN0cmFpbnQ= 45364 -UGFnaW5hdGlvbg== 45365 -IHBvdXZleg== 45366 -IEluZGljYXRlcw== 45367 -TUVS 45368 -IGNvdXJz 45369 -IHllYXJseQ== 45370 -IGdyb3NzZQ== 45371 -YWJicmV2 45372 -IERPTg== 45373 -IHByb2NlZWRlZA== 45374 -ZW50bGljaA== 45375 -IHByb3BlcnR5TmFtZQ== 45376 -IFRlYWNoaW5n 45377 -c3RhZHQ= 45378 -IGN1dG9mZg== 45379 -b3JuZXJz 45380 -IGFmcmljYQ== 45381 -IHJlbmRlcnM= 45382 -IFlhbmtlZXM= 45383 -IFRvb2xiYXI= 45384 -c3BhY2Vz 45385 -LmZpbGxTdHlsZQ== 45386 -IHNlZ3VuZG8= 45387 -X3N0cmxlbg== 45388 -LkZpcmViYXNl 45389 -5aSE 45390 -IG1lbnRpb25pbmc= 45391 -XCg= 45392 -IFZhbHZl 45393 -U2V0dGVy 45394 -IHNwYW5z 45395 -IEFsY29ob2w= 45396 -IExldHRlcnM= 45397 -XHhl 45398 -IFRL 45399 -X0JMRQ== 45400 -LmdldFJlc3VsdA== 45401 -PFBsYXllcg== 45402 -IFBhdHQ= 45403 -IGVhc2luZw== 45404 -IHR1cmtleQ== 45405 -IEZlbg== 45406 -Jyki 45407 -IGNvbmZpbmVk 45408 -IGluY2x1cw== 45409 -U3VwZXJ2aWV3 45410 -KHdpdGhJZGVudGlmaWVy 45411 -ZW5jaWFs 45412 -IHN0dWZmZWQ= 45413 -VGhldGE= 45414 -IGVjb25vbWlzdHM= 45415 -fSkpOwoK 45416 -Y29va2llcw== 45417 -IFJvb3Nl 45418 -IENoZWVzZQ== 45419 -IGZpY2hpZXI= 45420 -IGVuZm9yY2Vk 45421 -QUJC 45422 -bm/Fm2Np 45423 -X0FMTE9X 45424 -IHJlY3J1aXRlZA== 45425 -IGV4cGVuZGl0dXJl 45426 -LW5pZ2h0 45427 -IGFzc2VydE5vdE51bGw= 45428 -X2V4ZWN1dGU= 45429 -INiv 45430 -SU5ERVg= 45431 -X0ZNVA== 45432 -IHJlc2N1ZWQ= 45433 -IE1vbnRobHk= 45434 -IENvbnNlcnZhdGlvbg== 45435 -IEdlYg== 45436 -T2JhbWE= 45437 -RXBvY2g= 45438 -aWNpZXM= 45439 -IE9ydA== 45440 -IHNvaXQ= 45441 -KGljb24= 45442 -RnJpZW5kcw== 45443 -bW9s 45444 -IGdyb3VuZGVk 45445 -IENhdXNl 45446 -YWRlbmE= 45447 -V0VFTg== 45448 -IEx1bg== 45449 -SVRJVkU= 45450 -Lmxvb3A= 45451 -X3VudGls 45452 -IGNvcnI= 45453 -LmVkZ2Vz 45454 -IGh5cG90aA== 45455 -Y2hlZHVsaW5n 45456 -dHJhbnNsYXRvcg== 45457 -INCc 45458 -Um9t 45459 -44CRCgo= 45460 -IFhhbWFyaW4= 45461 -IHZpb2xhdGluZw== 45462 -LmFuY2hvcg== 45463 -LS0tCgo= 45464 -IHRyYWRlcg== 45465 -QURWRVJUSVNFTUVOVA== 45466 -IHVuc2VyZQ== 45467 -IERBTw== 45468 -IGJsb25k 45469 -IFBBVA== 45470 -Lmdsb2I= 45471 -IOi+kw== 45472 -IHNwbGl0dGluZw== 45473 -IHVuc3Vic2NyaWJl 45474 -IGF0bW9zcGhlcmlj 45475 -IFRyaW0= 45476 -IGNpdGF0aW9u 45477 -IGluZmVyZW5jZQ== 45478 -IEZ0 45479 -IERhcndpbg== 45480 -ZmluZE9uZQ== 45481 -IEdlbA== 45482 -KENvbnZlcnQ= 45483 -IGFjY2Vzc29y 45484 -O3RleHQ= 45485 -KHNvcnRlZA== 45486 -IGp1ZGdlZA== 45487 -KTtc 45488 -OnA= 45489 -IG1laW5l 45490 -IFNsaW0= 45491 -LkNvbW1hbmRz 45492 -IHBlcmNlaXZl 45493 -Y29ob2xpYw== 45494 -PERhdGE= 45495 -LmVudHJ5U2V0 45496 -IGFzc2VydEZhbHNl 45497 -IFBhdHJvbA== 45498 -ZW5zZW0= 45499 -xYLEhQ== 45500 -qKE= 45501 -V0lEVEg= 45502 -IFJlc2N1ZQ== 45503 -IFVJRg== 45504 -X1RIUkVTSE9MRA== 45505 -IE1pY2hlbA== 45506 -QVRFUklBTA== 45507 -b3BlbnNvdXJjZQ== 45508 -IERpYW5h 45509 -IGludml0ZXM= 45510 -X0JPRFk= 45511 -IHJlc2Vydm9pcg== 45512 -IHJvaQ== 45513 -Y3VzdA== 45514 -KHRj 45515 -77yBIik7Cg== 45516 -IGZlc3RpdmFscw== 45517 -IHBlcmZvcm1lcnM= 45518 -IGNsaW1iZWQ= 45519 -IGp1bmdsZQ== 45520 -U3RyaW5nTGVuZ3Ro 45521 -IHVubGF3ZnVs 45522 -aWVycmU= 45523 -dmVydGlzZW1lbnQ= 45524 -IHN0YWtlcw== 45525 -IGhhdHM= 45526 -TW9kaWZ5 45527 -IExFVFRFUg== 45528 -LkhpZGU= 45529 -IHN0YXR1dG9yeQ== 45530 -X3doaXRl 45531 -IFBlcmw= 45532 -dXRlbmJlcmc= 45533 -ZW1wbGU= 45534 -Lldvcmxk 45535 -IG92ZXJsb29rZWQ= 45536 -IGNvbmNsdWRlcw== 45537 -Lyo9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09 45538 -LXdpc2U= 45539 -CXN0cmVhbQ== 45540 -cG9wdWxhdGlvbg== 45541 -IGV2ZW50bw== 45542 -IGlsbHVzdHJhdGlvbnM= 45543 -ZnRz 45544 -IGF1dG9m 45545 -IFByb2NlZHVyZQ== 45546 -IGRlc2VydmVk 45547 -LXRpbWVz 45548 -IGdvbA== 45549 -TlNFcnJvcg== 45550 -Y3Jlc3Q= 45551 -IFBha2lzdGFuaQ== 45552 -YW55Y2g= 45553 -Z2V0Q3VycmVudA== 45554 -IGxhcg== 45555 -bnRs 45556 -IFJlYmVjY2E= 45557 -IG1hdGVyaWE= 45558 -IGZpbmRCeQ== 45559 -L2Fk 45560 -Q2FsbGJhY2tz 45561 -IEFscw== 45562 -IEthdGll 45563 -IE9ic2VydmFibGVDb2xsZWN0aW9u 45564 -IERvY3VtZW50YXRpb24= 45565 -VHlwZWQ= 45566 -IEN1bHR1cmVJbmZv 45567 -IFRpbW90aHk= 45568 -IGxhdGVyYWw= 45569 -InR5cGU= 45570 -IHVuYXV0aG9yaXplZA== 45571 -IHRlYWNoaW5ncw== 45572 -IGRlYnVnZ2Vy 45573 -W3ZhbHVl 45574 -IGFsb3Jz 45575 -IHV6 45576 -IHNjYXR0ZXI= 45577 -IGRvd253YXJk 45578 -IG1pZ2xp 45579 -c3RhdHVzQ29kZQ== 45580 -ICgpKQ== 45581 -IE1X 45582 -INC80L7Qtg== 45583 -Uk9TUw== 45584 -LmJ1Zg== 45585 -IGZhaXJ5 45586 -IEluZnJhc3RydWN0dXJl 45587 -PT4i 45588 -dGxlbWVudA== 45589 -JCgi 45590 -RnJvbVN0cmluZw== 45591 -IEJpbGQ= 45592 -IGNvbnZlbnRpb25z 45593 -X25hdGl2ZQ== 45594 -IEluc3BlY3Rvcg== 45595 -IFBpc3Q= 45596 -dWJhcg== 45597 -IHJlZ3M= 45598 -IFBpbG90 45599 -VGh1cw== 45600 -Picr 45601 -IGNlbGE= 45602 -Lm5ld3M= 45603 -KFByb2R1Y3Q= 45604 -TGl2aW5n 45605 -UnVzc2lh 45606 -IGZhY2V0 45607 -ZXRpY2Fs 45608 -IFsnJA== 45609 -L1s= 45610 -IERpcmU= 45611 -IGdhc2Vz 45612 -IElORk9STUFUSU9O 45613 -IEVhdA== 45614 -IEZvcnVtcw== 45615 -IENoYXJhY3RlcnM= 45616 -X21ldA== 45617 -IOyLnA== 45618 -IGtpbmdz 45619 -YWNoaWU= 45620 -IExhbWJkYQ== 45621 -IHRpbWVycw== 45622 -IExpZ2h0aW5n 45623 -IENhc2V5 45624 -YWRkaXI= 45625 -YW5kZXg= 45626 -LmFuc3dlcg== 45627 -IEhpcA== 45628 -IFByaW5jaXA= 45629 -U3RhcnREYXRl 45630 -IOOAjA== 45631 -dHJlcw== 45632 -ICYj 45633 -Lk1heFZhbHVl 45634 -IFByb2JsZW1z 45635 -IGxhdGV4 45636 -T2ZDbGFzcw== 45637 -IEx5bm4= 45638 -Ly8n 45639 -IHZveWFnZQ== 45640 -IHNodXR0bGU= 45641 -IFJvbGxlcg== 45642 -IFJ1bnRpbWVFcnJvcg== 45643 -dXlh 45644 -RGlj 45645 -CWJ1aWxkZXI= 45646 -IGJ1bGx5aW5n 45647 -IHNpbXBsZXN0 45648 -LmNhbGxlZA== 45649 -IExS 45650 -IG1vcmFsaXR5 45651 -IHN0dXJkeQ== 45652 -dHJhY2tpbmc= 45653 -LnN3YWdnZXI= 45654 -X0JJTkQ= 45655 -SVRPUg== 45656 -LXVybGVuY29kZWQ= 45657 -INGF 45658 -IFRyaW5pdHk= 45659 -IHRyYXBz 45660 -IHwt 45661 -IHNldFRleHQ= 45662 -IGJhcmdhaW4= 45663 -IGJyYWtlcw== 45664 -LmdldENvZGU= 45665 -IG1pZ3JhdGU= 45666 -IHJpYmJvbg== 45667 -KXJldHVybg== 45668 -IGNoYXJnZXI= 45669 -YWNvbQ== 45670 -QURJVVM= 45671 -IEFtYmFzc2Fkb3I= 45672 -LWFmdGVy 45673 -IGFubmk= 45674 -CXNwaW4= 45675 -Q29uY2VwdA== 45676 -IEhlbmRlcnNvbg== 45677 -IEhPU1Q= 45678 -LnJhbms= 45679 -IE5vcnRoZWFzdA== 45680 -IGJlcmxpbg== 45681 -IHJlcXVpcw== 45682 -LmZlZWQ= 45683 -IHNvdXJjZU1hcHBpbmc= 45684 -IFJlbmNvbnRyZQ== 45685 -LmFqYXg= 45686 -bmVzdGpz 45687 -IHRyZWs= 45688 -IE5hY2lvbmFs 45689 -ICZb 45690 -IHBheWFibGU= 45691 -b3J0ZXg= 45692 -IGRlcHQ= 45693 -ZmllbGROYW1l 45694 -IGNvbXBsZXRlcw== 45695 -IFJWQQ== 45696 -IG9uaW9ucw== 45697 -YWxpZ25tZW50 45698 -Rm9ybWF0cw== 45699 -ICd7JA== 45700 -SGFzaFNldA== 45701 -IEJvZA== 45702 -LkludmFyaWFudEN1bHR1cmU= 45703 -IHNldHRsZW1lbnRz 45704 -IGh5ZHI= 45705 -LnVwZGF0ZWQ= 45706 -dmVudGg= 45707 -KHNlY29uZHM= 45708 -PSIvIg== 45709 -IHdlYnBhZ2U= 45710 -KAoK 45711 -IHRpcg== 45712 -IHRvZXM= 45713 -IEJyaWNr 45714 -IGFtYml0aW9u 45715 -UG90 45716 -PW1heA== 45717 -RVRJTUU= 45718 -IGRlcG90 45719 -Y2FsbHM= 45720 -IE5vcndlZ2lhbg== 45721 -YDo= 45722 -IGJ1cmdlcg== 45723 -IHByb2Zlc3NvcnM= 45724 -IEFsbG9jYXRl 45725 -LXRoaXJkcw== 45726 -LWNoYXJ0 45727 -IGZvcmQ= 45728 -Kk4= 45729 -LmtvdGxpbg== 45730 -IHBhcGVyd29yaw== 45731 -IERFVklDRQ== 45732 -JUAiLA== 45733 -cmVzcGVjdA== 45734 -KG1w 45735 -6auY 45736 -LWlm 45737 -IGN1c2hpb24= 45738 -b2JvdA== 45739 -IHBhcmM= 45740 -U1BBQ0U= 45741 -IE5ldGFueWFodQ== 45742 -IHNlbGZpc2g= 45743 -ZmVhdA== 45744 -IGNsaWVudGVz 45745 -LXRvb2xz 45746 -IHBvcmNo 45747 -IGpx 45748 -LnZlcmJvc2U= 45749 -IGxpYmVyYWxz 45750 -XSkKCgo= 45751 -cGllcw== 45752 -Tm90Qmxhbms= 45753 -KHRlcm0= 45754 -yJtp 45755 -X1BhcmFtcw== 45756 -Lm5vcm1hbGl6ZQ== 45757 -QnVsbGV0 45758 -QVNJQw== 45759 -KGhleA== 45760 -X2NsaWVudGU= 45761 -Kyw= 45762 -X0RJ 45763 -IGZvcnRoY29taW5n 45764 -fSIpXQo= 45765 -c2Vv 45766 -VW0= 45767 -Pk5hbWU= 45768 -IGNvbWZvcnRhYmx5 45769 -aXJlY3Rpb25hbA== 45770 -V0lUSA== 45771 -L3By 45772 -IFBvb3I= 45773 -IFZpdGFtaW4= 45774 -dmlj 45775 -R0g= 45776 -IHByaW9yaXQ= 45777 -IE5O 45778 -IENsb3NlZA== 45779 -pO0= 45780 -IGlzT3Blbg== 45781 -XENvbnNvbGU= 45782 -QW5kRmVlbA== 45783 -LlNVQ0NFU1M= 45784 -X09QRVJBVElPTg== 45785 -cG9sYXRpb24= 45786 -IFRhcw== 45787 -cHN6 45788 -Picu 45789 -Q1VSUkVOVA== 45790 -VmVuZG9y 45791 -aG9zdHM= 45792 -IEVyZA== 45793 -PnRhZ2dlcg== 45794 -IHNvdXJjZU1hcHBpbmdVUkw= 45795 -IG1hcmF0aG9u 45796 -X2Nsb3NlZA== 45797 -IGV4ZW1wdGlvbg== 45798 -IHJlY29nbml6ZXM= 45799 -aWRlc2hvdw== 45800 -JyQ= 45801 -KCcvJyk7Cg== 45802 -bWl0cw== 45803 -d2Fyeg== 45804 -IENoZXJyeQ== 45805 -taw= 45806 -bm9y 45807 -cG9ydGU= 45808 -IHds 45809 -X2JhY2t1cA== 45810 -LmdldEJvb2xlYW4= 45811 -LmdldFJlc291cmNl 45812 -IGRlZmluaXRpdmU= 45813 -LkVkaXRUZXh0 45814 -IHPDrQ== 45815 -LkNPTlQ= 45816 -IFBMQVlFUg== 45817 -LmNhcmRz 45818 -IFNob3Jl 45819 -KCcvJykK 45820 -Y2x1aXI= 45821 -V2ViRHJpdmVy 45822 -KG1vbnRo 45823 -LXJlbGVhc2U= 45824 -IGluc3BlY3Rvcg== 45825 -5aM= 45826 -IE5G 45827 -X2NsaXA= 45828 -5a2Q 45829 -IGludGVyYWN0aW5n 45830 -LnRtcA== 45831 -ICcnJwoK 45832 -IGRlZQ== 45833 -IGZyb3N0 45834 -Il0pKQo= 45835 -IFBsYWNlcw== 45836 -VGhyb3dz 45837 -Zm9yaw== 45838 -L2RheQ== 45839 -aVBob25l 45840 -IE1JQw== 45841 -IGZvbGRpbmc= 45842 -IGNyb3Jl 45843 -IENoaWVmcw== 45844 -cGhlcmljYWw= 45845 -KHByaWNl 45846 -LldyaXRlU3RyaW5n 45847 -IGV4aXRpbmc= 45848 -XScsCg== 45849 -aWdodGluZw== 45850 -SW5ncmVkaWVudA== 45851 -KHZlcnRleA== 45852 -IHNjcm9sbFZpZXc= 45853 -aGY= 45854 -Om5ldw== 45855 -U0VO 45856 -c2VjdG9y 45857 -IHNwaW5z 45858 -IFNjaGVkdWxlcg== 45859 -b3RlY2hu 45860 -c2VtaWNvbG9u 45861 -Rm9udE9mU2l6ZQ== 45862 -IFNwZWNpZmljYWxseQ== 45863 -ZmxhbW0= 45864 -Lk9iamVjdElk 45865 -IGNvbnRh 45866 -X3Blcm1pc3Npb25z 45867 -CUZST00= 45868 -SUNPREU= 45869 -L2tn 45870 -IEhvdGVscw== 45871 -LW1lZA== 45872 -IERpbg== 45873 -IG5hdnk= 45874 -Z2V0UGFyYW0= 45875 -IG1lbmQ= 45876 -IHBvcnRyYXllZA== 45877 -IE1ldHJvcG9saXRhbg== 45878 -UGFpbnRlcg== 45879 -IHJlZmVycmFs 45880 -X2dvb2Q= 45881 -IG1hcnZlbA== 45882 -b3NhaWM= 45883 -Pigm 45884 -LnVy 45885 -IGVzdG9z 45886 -V2lsbGlhbQ== 45887 -IHRpbWJlcg== 45888 -IHF1ZWxxdWVz 45889 -IERvY3VtZW50cw== 45890 -LlhhbWw= 45891 -IGJhdGNoZXM= 45892 -6YGT 45893 -IFJlbGVhc2Vk 45894 -VGFpbA== 45895 -Q09PS0lF 45896 -aGVpZA== 45897 -X3N0YXRpb24= 45898 -IFZpYQ== 45899 -U2FsZQ== 45900 -IFJlcGVhdA== 45901 -IHByb21pbg== 45902 -IFpv 45903 -LWZvcndhcmQ= 45904 -IElvbg== 45905 -aXRhcnk= 45906 -IGp1cw== 45907 -LXJlcXVlc3Q= 45908 -IHByb3VkbHk= 45909 -IFN0cmVhbWluZw== 45910 -KE1vdXNlRXZlbnQ= 45911 -IFNwcmludA== 45912 -X3JvdGF0aW9u 45913 -UmVwb3NpdG9yaWVz 45914 -IHRhcnQ= 45915 -INGB0LI= 45916 -IG1hcHBpbmdz 45917 -6Ko= 45918 -Q3U= 45919 -Q3ljbGU= 45920 -IGJ1bg== 45921 -CWx1YQ== 45922 -44OJ 45923 -ICgoIQ== 45924 -IGNvbGxlY3RpdmVseQ== 45925 -IENvbmQ= 45926 -IHdzenlzdA== 45927 -KGxpYg== 45928 -b3BlbmhhZ2Vu 45929 -X3NraXA= 45930 -LkNvbHVtbkhlYWRlcg== 45931 -6YI= 45932 -cGVyaWVuY2Vk 45933 -j+i/sA== 45934 -X3Byb3Bz 45935 -IGNvbnRyYWNl 45936 -IG1hdGNodXA= 45937 -YWJldGlj 45938 -Lm1lbWJlcnM= 45939 -UkVDVA== 45940 -KGRhdA== 45941 -IHNvZw== 45942 -cmVub20= 45943 -X01ldGhvZA== 45944 -Q3VzdG9tZXJz 45945 -ZnVsbG5hbWU= 45946 -Wk4= 45947 -cmV0cnk= 45948 -IGthcA== 45949 -IE5ldQ== 45950 -6Io= 45951 -YWRkQ2hpbGQ= 45952 -d2lsbFJldHVybg== 45953 -X3Blcm1hbGluaw== 45954 -IGVuZXJnZXRpYw== 45955 -IFdldA== 45956 -IE1vcnI= 45957 -IGdjZA== 45958 -Y291bnRz 45959 -LHR5cGU= 45960 -ZGln 45961 -KExvZ2lu 45962 -IGNyYWNrcw== 45963 -IGJhY3RlcmlhbA== 45964 -IE1lYXQ= 45965 -IEFybXN0cm9uZw== 45966 -IEJyb256ZQ== 45967 -IGFwcHJveGltYXRl 45968 -X2RpcnM= 45969 -bGlnYQ== 45970 -xYJhZA== 45971 -IGtpbmRuZXNz 45972 -IGNvbnRyZQ== 45973 -IEVWRVJZ 45974 -TUVU 45975 -IGFubm91bmNlbWVudHM= 45976 -Z3Bpbw== 45977 -IFdhaXRGb3JTZWNvbmRz 45978 -IFBob3Rvc2hvcA== 45979 -IGRpc2NvbnRpbg== 45980 -L2Rk 45981 -IHRvcG9sb2d5 45982 -YW5pY2Fs 45983 -LmludGVyZmFjZQ== 45984 -YXVjb3Vw 45985 -Lkhhc2hTZXQ= 45986 -QVJJQU5U 45987 -KHJvdXRlcw== 45988 -IFRlaA== 45989 -IGh5cGU= 45990 -XSIpLg== 45991 -IHNsYW0= 45992 -IGJyb3Ro 45993 -LWludGVy 45994 -IFJpZA== 45995 -LW1hbmFnZXI= 45996 -Q2FuY2VsYXI= 45997 -IFBhZ2luYXRpb24= 45998 -IHNvdW5kdHJhY2s= 45999 -IHBvc3Rlcmlvcg== 46000 -IHNjcnVi 46001 -Y3JlYXRpbmc= 46002 -LSo= 46003 -aXJ0ZWVu 46004 -LmR5 46005 -LnN5bW1ldHJpYw== 46006 -ICIiLg== 46007 -PT09PT09PT09PT09PT09 46008 -IGNoYXNzaXM= 46009 -IG51bWJlck9mUm93cw== 46010 -RGV2ZWxvcGVy 46011 -X2JpbnM= 46012 -IE9VUg== 46013 -cmllYg== 46014 -UHJvcw== 46015 -IHdpxJk= 46016 -ImQ= 46017 -IGFzeW5jaW8= 46018 -emVpZ2Vu 46019 -X3NwaQ== 46020 -LkFMTA== 46021 -IHNjcmV3cw== 46022 -Q2hpbmVzZQ== 46023 -IGFwaUtleQ== 46024 -IHVuc3VjY2Vzc2Z1bA== 46025 -IFNlYWhhd2tz 46026 -T1JH 46027 -56ug 46028 -IHByb2Zlc3Npb25hbGx5 46029 -IENvdXBvbg== 46030 -5a2X5q61 46031 -Q29udmVudGlvbg== 46032 -IHBvbHlt 46033 -5omL 46034 -IHNhbHZhdGlvbg== 46035 -IGVuZ2luZWVyZWQ= 46036 -IFdyZXN0 46037 -IEdDQw== 46038 -IHdhcm1lcg== 46039 -TGF5b3V0Q29uc3RyYWludA== 46040 -IGFnZ3Jhdg== 46041 -U2NyaXB0cw== 46042 -dmVudHVyZQ== 46043 -IHJlZnJpZ2VyYXRvcg== 46044 -IGlubm92YXRpb25z 46045 -IFJ1bm5lcg== 46046 -TklD 46047 -IFJvbGxpbmc= 46048 -Q29udHJvbEV2ZW50cw== 46049 -IGxvb3M= 46050 -cGFj 46051 -CXBhbmVs 46052 -ZWZl 46053 -IEJ1ZGRoYQ== 46054 -LS0tLS0tLS0tLS0tLS0K 46055 -5bqT 46056 -KGZvcktleQ== 46057 -IGx1bWlu 46058 -ICg/ 46059 -IEFJRFM= 46060 -LHVzZXI= 46061 -aW1pZW50b3M= 46062 -Y29udGVudFR5cGU= 46063 -YW50bHI= 46064 -6aY= 46065 -IFdlbHQ= 46066 -UHJvZHVjdGlvbg== 46067 -bWlnaHQ= 46068 -IFZJSQ== 46069 -Iiwo 46070 -IG9ic2VydmluZw== 46071 -IGRlbGliZXJhdGU= 46072 -KGNvbnRyb2w= 46073 -IHdpdGhk 46074 -IHNlbWFuYQ== 46075 -U1RBQ0s= 46076 -dWNoZW4= 46077 -TmljZQ== 46078 -IERldXRzY2hsYW5k 46079 -IFNwZWNpZmllcw== 46080 -ZG1h 46081 -aXppbw== 46082 -IEZhY3Rz 46083 -X3BvcHVw 46084 -IERpcmVjdG9ycw== 46085 -ezo= 46086 -W1I= 46087 -INGN0LvQtdC80LXQvdGC 46088 -IHBsYXQ= 46089 -IGRpcmVjdGluZw== 46090 -5LiJ 46091 -IEdpbGJlcnQ= 46092 -4oCmLgoK 46093 -LnFtbA== 46094 -IHRoZXJlYWZ0ZXI= 46095 -IGRpc3Bvc2l0aW9u 46096 -ZHJhZnQ= 46097 -IHN1cmdlb24= 46098 -IEluc2lkZXI= 46099 -QmxlbmQ= 46100 -IFRyZXY= 46101 -dHJpbnNpYw== 46102 -VG9waWNz 46103 -cmlldmU= 46104 -X0ZJTEVOQU1F 46105 -IGF1dHJlcw== 46106 -Sm9zZQ== 46107 -UHJvZHVjZXI= 46108 -ZXJ1cw== 46109 -IHBldGl0 46110 -IE5FWFQ= 46111 -IEZpbHRlcnM= 46112 -IHJlcGxpY2F0ZQ== 46113 -Il0pLg== 46114 -IGxlbmRlcnM= 46115 -XSIsCg== 46116 -O2NoYXJzZXQ= 46117 -Q3BwT2JqZWN0 46118 -IGZsb3JhbA== 46119 -IFRpcG8= 46120 -IGNpcmN1aXRz 46121 -ZWFzeQ== 46122 -KCYk 46123 -aXR0YQ== 46124 -ZXJ5bA== 46125 -X0NPTU1PTg== 46126 -J319Pgo= 46127 -LWJhY2tlZA== 46128 -KHZhcmlhYmxl 46129 -KEluZGV4 46130 -IHZvaXI= 46131 -X2xvY2F0aW9ucw== 46132 -Kyspew== 46133 -IExvdWlzdmlsbGU= 46134 -IGdyYXRpdHVkZQ== 46135 -Lk1vY2tpdG8= 46136 -IFBvd2Vycw== 46137 -aWV1cnM= 46138 -IGdlb2dyYXBoaWM= 46139 -cmFsZQ== 46140 -IGNyYQ== 46141 -IFNwdXJz 46142 -aXBoZXJ0ZXh0 46143 -QUNJT04= 46144 -LWNvbW1vbg== 46145 -IHZpY3Rvcmllcw== 46146 -IEZpbmFscw== 46147 -LnNodWZmbGU= 46148 -LW1pbGxpb24= 46149 -X1BST0M= 46150 -YXNzdW1l 46151 -IGlscw== 46152 -REJD 46153 -Qm9vdFRlc3Q= 46154 -IGxhdm9y 46155 -LnRlc3Rpbmc= 46156 -LmFzdA== 46157 -Il0v 46158 -bW9pZA== 46159 -IHF1YWxpZmljYXRpb24= 46160 -Z2VzY2g= 46161 -CXB1dA== 46162 -IGFpcnBvcnRz 46163 -Skk= 46164 -VGVhY2hlcg== 46165 -X3VuaWZvcm0= 46166 -IG5hbWE= 46167 -IEJhc3Q= 46168 -ZXJ0eXBl 46169 -Y2FwdHVyZQ== 46170 -Z2V0QWxs 46171 -IFJleW5vbGRz 46172 -b29sZWQ= 46173 -LmNvbW1lbnRz 46174 -IGNoaW4= 46175 -KS4q 46176 -INC40LvQuA== 46177 -dGds 46178 -dWRvcw== 46179 -IGTDrWFz 46180 -Y2hhaQ== 46181 -LnByb2dyYW0= 46182 -IHBzeg== 46183 -CWljb24= 46184 -cGhpbA== 46185 -ZW50cmFs 46186 -X1dSQVA= 46187 -b3Zp 46188 -IG5vc3RhbGc= 46189 -SW5maW5pdHk= 46190 -CXlpZWxk 46191 -IHZpdGFtaW5z 46192 -UXVhdGVybmlvbg== 46193 -U2luaw== 46194 -X2dvb2Rz 46195 -IC4uLi4uLi4u 46196 -IFdpbmdz 46197 -dXJpZGFk 46198 -LXN0b3J5 46199 -Il0pCgo= 46200 -aWRlbGl0eQ== 46201 -VHlwZURlZg== 46202 -R3Rr 46203 -IO2M 46204 -X01haW4= 46205 -IGNoZXo= 46206 -IFJhdmVu 46207 -IHBheXJvbGw= 46208 -IGZyZWVsYW5jZQ== 46209 -TExV 46210 -IE1lbmQ= 46211 -ZWRheQ== 46212 -QXBpTW9kZWxQcm9wZXJ0eQ== 46213 -LkZvcm1Cb3JkZXJTdHlsZQ== 46214 -IGVjb25vbWlzdA== 46215 -c3RhbmJ1bA== 46216 -IGZyZWlnaHQ= 46217 -LUFnZW50 46218 -KG1ldGE= 46219 -IHN5bW1ldHJ5 46220 -ICcuLg== 46221 -LkNhbGVuZGFy 46222 -LWF1dA== 46223 -Z2Y= 46224 -cGVudA== 46225 -eWNsb3BlZGlh 46226 -IHdpc2hpbmc= 46227 -CgoKCgoKCgoKCgoK 46228 -IGdlbnRsZW1hbg== 46229 -IOqz 46230 -PSM= 46231 -IGxlY3R1cmVz 46232 -4oCcSW4= 46233 -ICFf 46234 -IGhi 46235 -IFZlbmRvcg== 46236 -UmVjZW50bHk= 46237 -X25vdGVz 46238 -5o+Q56S6 46239 -Ik15 46240 -SGVhZGVyc0hlaWdodA== 46241 -X1NP 46242 -IHVud2lsbGluZw== 46243 -IHN1cGVyaGVybw== 46244 -Z2lv 46245 -cHN5 46246 -IFBlZXI= 46247 -amF2YXg= 46248 -JmFwb3M= 46249 -IENyaXNpcw== 46250 -b3JkaW5hbA== 46251 -TWVtY3B5 46252 -KysrKysrKysrKysrKysrKw== 46253 -LXZhbA== 46254 -IHdvcmtib29r 46255 -LWFw 46256 -PWs= 46257 -IG1ldGFsbGlj 46258 -X3BlZXI= 46259 -QnlQcmltYXJ5S2V5 46260 -X1NE 46261 -dWF0b3I= 46262 -X1NIQURFUg== 46263 -KU1hdGg= 46264 -LlRyYW5zZm9ybQ== 46265 -IGNvd3M= 46266 -UGhp 46267 -IENsZW0= 46268 -KF8oIg== 46269 -IEx1ZA== 46270 -LWRlbGF5 46271 -IFNlY3VyaXRpZXM= 46272 -IE9ydGhvZG94 46273 -U3ltZm9ueQ== 46274 -KHJlcG9ydA== 46275 -IGVudGVydGFpbg== 46276 -RVBT 46277 -aXpvcGg= 46278 -ZXh1YWw= 46279 -SVJE 46280 -5LuO 46281 -IGxpdGg= 46282 -IHNhbml0aXpl 46283 -IGZlbWluaW5l 46284 -SVNCTg== 46285 -LmF1dGhlbnRpY2F0aW9u 46286 -X3BpcGVsaW5l 46287 -L2NvbnN0YW50cw== 46288 -IENPTkY= 46289 -IGx1Y3I= 46290 -cmljaWE= 46291 -LnR0Zg== 46292 -LnNldENvbnRlbnQ= 46293 -IHN0YW4= 46294 -b3JlYW4= 46295 -IExsb3lk 46296 -LnJhd1ZhbHVl 46297 -IGdvcg== 46298 -IEJyb3ducw== 46299 -UmVncmVzc2lvbg== 46300 -IGxvd2VyaW5n 46301 -bmFpc3NhbmNl 46302 -IGJsb3dz 46303 -IGFtYXplZA== 46304 -IHVucmVsYXRlZA== 46305 -UmV2aWV3cw== 46306 -IHJ1Ynk= 46307 -IE1vZGlmaWVy 46308 -IGdpYW50cw== 46309 -LnRocmVhZA== 46310 -IGNvbnRhaW5tZW50 46311 -IFN0YXJ0Q29yb3V0aW5l 46312 -dW1hdA== 46313 -b3JlbGVhc2U= 46314 -IFJhbmR5 46315 -QGVuZGlm 46316 -RGlnZXN0 46317 -IHN1YnVyYmFu 46318 -PSIpOwo= 46319 -IGFubm9uY2U= 46320 -LnZhcmlhYmxl 46321 -XEZvdW5kYXRpb24= 46322 -IGFjcmU= 46323 -VmFu 46324 -IHR1cGxlcw== 46325 -ZG5z 46326 -IFN0YW5kaW5n 46327 -X2xhcmdl 46328 -IGJveGluZw== 46329 -U3VwcG9ydEFjdGlvbkJhcg== 46330 -IEZvcnR1bmU= 46331 -IFJ1bQ== 46332 -X211bHRpcGxl 46333 -YXJjaGljYWw= 46334 -IGZ3cml0ZQ== 46335 -X3F1b3Rl 46336 -IGZvb2xpc2g= 46337 -IGNvbXByaXNpbmc= 46338 -INC+0L8= 46339 -LXNlbGVjdGVk 46340 -dmY= 46341 -bWFpZA== 46342 -TmFtYQ== 46343 -KGRhdGV0aW1l 46344 -IGluZGlyZWN0bHk= 46345 -Z2FydA== 46346 -Zml4dHVyZXM= 46347 -Y2hvcw== 46348 -IEhhbG8= 46349 -IHJlY3VycmluZw== 46350 -LW5ld3M= 46351 -dmls 46352 -IE51cnNpbmc= 46353 -LXByb2R1 46354 -IEhR 46355 -XEh0dHBGb3VuZGF0aW9u 46356 -ZW5jaQ== 46357 -YXVlbg== 46358 -IHZ5 46359 -b2NyYWN5 46360 -IGRlbGVnYXRpb24= 46361 -IGFzcGhhbHQ= 46362 -IHNldFNlbGVjdGVk 46363 -a29r 46364 -L3Jlc3Q= 46365 -bWV0aWNz 46366 -IE5TRGF0ZQ== 46367 -IHRyYXZlbGxlZA== 46368 -IHJlY2li 46369 -IG1pbWU= 46370 -Q0xJRU5U 46371 -IEdV 46372 -IEhBTkRMRQ== 46373 -L1E= 46374 -W3o= 46375 -IGJvdGhlcmVk 46376 -IEJCUQ== 46377 -w6dhcw== 46378 -X2V4YW1wbGVz 46379 -X0ZJTg== 46380 -IHdoaXRlQ29sb3I= 46381 -IGFzdHJvbm9t 46382 -LWRpcg== 46383 -IHNvdmVyZWlnbg== 46384 -IGJyZWV6ZQ== 46385 -IGlubmluZw== 46386 -IEVkbW9udG9u 46387 -Z2xp 46388 -LmJsb2dzcG90 46389 -anN4 46390 -IHZlcnNh 46391 -IE1vaGFtbWVk 46392 -LkpvYg== 46393 -LXRvZ2dsZXI= 46394 -INC/0L7Qu9GM0LfQvtCy0LDRgg== 46395 -YXJkb24= 46396 -IG5ld2Jvcm4= 46397 -IG5hdmFs 46398 -bm90ZXE= 46399 -IHR1bWJscg== 46400 -IGhlbnRhaQ== 46401 -IFR5cGljYWxseQ== 46402 -IGxvb3Q= 46403 -LlNwcml0ZQ== 46404 -RmxpZ2h0 46405 -IHdhdmVsZW5ndGg= 46406 -LXNr 46407 -IEVsbGU= 46408 -X2V4cG9ydHM= 46409 -INGP 46410 -IElI 46411 -aXpvcGhyZW4= 46412 -IO2B 46413 -X3ByaW1hcnk= 46414 -IG1vaXM= 46415 -IEJO 46416 -IHN5c3RlbWlj 46417 -IGRpZmVyZW50ZXM= 46418 -SU5DVA== 46419 -ICcnCgo= 46420 -JHE= 46421 -V2lkZ2V0SXRlbQ== 46422 -Y2xpZGU= 46423 -JGZpbGU= 46424 -TGVtbWE= 46425 -L3RhYmxl 46426 -YWdyaWQ= 46427 -IE1vbmdvREI= 46428 -aW50ZQ== 46429 -IGFwcHJlbnQ= 46430 -wq1pbmc= 46431 -LkRi 46432 -IMOC 46433 -aGFtbWVy 46434 -PScnOwo= 46435 -IGJyb2tlcnM= 46436 -aXRsZW1lbnQ= 46437 -c2VtYmxpZXM= 46438 -RWxl 46439 -e3g= 46440 -IGxhc3RuYW1l 46441 -PC0= 46442 -IGZsYXR0ZW4= 46443 -X2JhbmQ= 46444 -LlJvb3Q= 46445 -LnJlYWRGaWxlU3luYw== 46446 -PT09PT09 46447 -LnJ4 46448 -Pw0K 46449 -IG1ldGFwaG9y 46450 -VGk= 46451 -Y29udGU= 46452 -IGRlYml0 46453 -IGNvbnRlbXB0 46454 -Q3BwVHlwZQ== 46455 -5pSv 46456 -Rm9ybUZpZWxk 46457 -cmF0aW8= 46458 -b3NvcGhlcg== 46459 -IGltcGxhbnQ= 46460 -UFVSRQ== 46461 -IGFsdGE= 46462 -X21hbmFnZW1lbnQ= 46463 -IHJlZmluZQ== 46464 -IENoZWNrQm94 46465 -IENoYXJs 46466 -LXZlcnNpb24= 46467 -Y29uZGl0aW9uYWw= 46468 -dmVudWVz 46469 -IHJpZmxlcw== 46470 -IG9mZnNwcmluZw== 46471 -IG1pbGxpbmc= 46472 -IHNoYXJwbHk= 46473 -IHVuZGVyd2F0ZXI= 46474 -KG9yaWdpbg== 46475 -X0NvbnRyb2w= 46476 -IC4k 46477 -UGx1Z2lucw== 46478 -IGRyeWluZw== 46479 -IGlsbHVzdHJhdGVz 46480 -LXU= 46481 -IHZlZ2V0YXJpYW4= 46482 -bnBj 46483 -SGVhcnQ= 46484 -OycsCg== 46485 -Y29tbWE= 46486 -dGVlbnRo 46487 -YXNhbg== 46488 -L3NwZWM= 46489 -X21vdmVz 46490 -LW1hcmdpbg== 46491 -IGluZ2Vu 46492 -wqDCoMKg 46493 -IHByb2pldA== 46494 -IG90cmE= 46495 -IGJyYXM= 46496 -LnV0Yw== 46497 -IHNsZXB0 46498 -PXN1Yg== 46499 -YWJpbGl0 46500 -cG9zdGVy 46501 -IHNkaw== 46502 -b3VuY2lsbA== 46503 -IHdk 46504 -UHJlcGFyZWRTdGF0ZW1lbnQ= 46505 -IERydW0= 46506 -KGF0dHJpYnV0ZQ== 46507 -IEV0aGVybmV0 46508 -CURC 46509 -Q2FsaWZvcm5pYQ== 46510 -Y3ViZQ== 46511 -W0k= 46512 -LkNyZWF0ZWQ= 46513 -IEhN 46514 -IHRyYWNpbmc= 46515 -Rm9ybXNNb2R1bGU= 46516 -LXlvdQ== 46517 -LmN1cnJlbmN5 46518 -ZmVlZGluZw== 46519 -IHRib2R5 46520 -TGk= 46521 -YWNjaW9u 46522 -bmFz 46523 -IHRyb3V2ZXI= 46524 -Tk9ORQ== 46525 -In0sDQo= 46526 -IGZ0cA== 46527 -V2l0aElkZW50aWZpZXI= 46528 -cG9sYXRl 46529 -RmlsZUluZm8= 46530 -IHB1cnN1ZWQ= 46531 -ICAgIA0KICAgIA0K 46532 -REVTQ1JJUFRJT04= 46533 -fSovCg== 46534 -RnJvbU5pYg== 46535 -IGRlY29yYXRpdmU= 46536 -X1NTTA== 46537 -KGNoYXQ= 46538 -VExT 46539 -IHN1cnByaXNlcw== 46540 -YWxjdWxhdGU= 46541 -IFNwbGFzaA== 46542 -KENvbmZpZ3VyYXRpb24= 46543 -IFNFTQ== 46544 -aW1zb24= 46545 -L2xpYnJhcnk= 46546 -PERvdWJsZQ== 46547 -LnJvYm90 46548 -wqDCoMKgwqDCoMKgwqDCoA== 46549 -IENQRg== 46550 -IFVuZGVyc3RhbmRpbmc= 46551 -IGNvc21ldGlj 46552 -IFh0 46553 -dGlwcw== 46554 -K2s= 46555 -KCIn 46556 -IFBEVA== 46557 -V0FS 46558 -LmdldE9iamVjdA== 46559 -IFRyYWRpdGlvbmFs 46560 -LnNsdWc= 46561 -IERpcGw= 46562 -PSIiLA== 46563 -IEZpbG1z 46564 -IEFuaW0= 46565 -LmhlbHA= 46566 -IGVtYmFzc3k= 46567 -IEJvb3Rz 46568 -IGJ1bms= 46569 -LXJpc2s= 46570 -IHBjaQ== 46571 -IC9cLg== 46572 -IElQVA== 46573 -IGNyYXNoaW5n 46574 -IGlwdg== 46575 -X2tl 46576 -IFJFU1A= 46577 -LkxvZ0Vycm9y 46578 -IGluYWRlcXVhdGU= 46579 -SW9u 46580 -IEbDvHI= 46581 -cmljdWxh 46582 -IHNob3VsZEJl 46583 -YWxyZWFkeQ== 46584 -J10uIjwv 46585 -IFN0dWZm 46586 -RGlnaXRl 46587 -IHRyYW5zbGF0b3I= 46588 -X3Nwcml0ZQ== 46589 -bGV0YWw= 46590 -IG1haW9y 46591 -IFNleGU= 46592 -dGhhbmtz 46593 -IENvbXBsZXRlZA== 46594 -IGdhc29saW5l 46595 -LmF0dHJz 46596 -YmFnYWk= 46597 -IE9yaWc= 46598 -Ol0s 46599 -LmxvY2FsZQ== 46600 -IFJvbWE= 46601 -w61m 46602 -IGZhdm9yZWQ= 46603 -IHZhaW4= 46604 -IHNwb29u 46605 -IEphaHJlbg== 46606 -IG5pbmc= 46607 -V1dX 46608 -LGZsb2F0 46609 -X0RBVEFCQVNF 46610 -Qm9vdHN0cmFw 46611 -IENCQw== 46612 -IENodW5r 46613 -X2ludG8= 46614 -IEtvbA== 46615 -IGRlZmVuc2Vz 46616 -b3JlZFByb2NlZHVyZQ== 46617 -YmFsbHM= 46618 -VGV4dENoYW5nZWQ= 46619 -IHNoYXBpbmc= 46620 -IH19Pg== 46621 -R0VE 46622 -ZmFx 46623 -IG9wdGlvbmFsbHk= 46624 -X0Rpcw== 46625 -IFN1Y2Nlc3NmdWw= 46626 -IENlbnN1cw== 46627 -IGluY2FyY2Vy 46628 -X0NBUkQ= 46629 -IGF2aWF0aW9u 46630 -IEd5bQ== 46631 -QXV0aG9yaXR5 46632 -LkJlYW4= 46633 -c2hhZGVy 46634 -Tm90RXhpc3Q= 46635 -X1RleHRDaGFuZ2Vk 46636 -IFNUT1A= 46637 -KHRlYW0= 46638 -Ikg= 46639 -d2c= 46640 -IGdyaW5kZXI= 46641 -IHN0cmlwZQ== 46642 -IHByZXNlcnZhdGlvbg== 46643 -Q2xhaW0= 46644 -YXZlcnNhbA== 46645 -d2FyZWhvdXNl 46646 -dGFyZ2V0cw== 46647 -VHJ1c3Q= 46648 -IGFsbGV2 46649 -LHd3dw== 46650 -b3Vzc2U= 46651 -X2NoYW4= 46652 -X1NpemU= 46653 -c3lzdGVtcw== 46654 -IG9iamVjdGlvbg== 46655 -IEthbmU= 46656 -IGNvcnJvcw== 46657 -IERTTA== 46658 -IHVh 46659 -IE1I 46660 -IFN0cmF0ZWdpYw== 46661 -X3RjcA== 46662 -IOqwkg== 46663 -IGJvcnJvd2Vk 46664 -IEFjaA== 46665 -CWNvbW1hbmQ= 46666 -IGdwcw== 46667 -bGVzdG9u 46668 -aWNoZXZlcg== 46669 -IFVB 46670 -IGFzc2F1bHRlZA== 46671 -IHNwZWNpYWxpemVz 46672 -CXNlYXJjaA== 46673 -SG90ZWw= 46674 -ICAgICAgICAgICAgICAgICAgICANCg== 46675 -IFBpdGNo 46676 -INmB 46677 -UkVBRFk= 46678 -IHBhcmVudGFs 46679 -IGfDqW7DqQ== 46680 -IGRvbm7DqWVz 46681 -IGRldGFpbg== 46682 -VEFSR0VU 46683 -IHByb3RhZ29uaXN0 46684 -IGNsZWFySW50ZXJ2YWw= 46685 -IEljb25CdXR0b24= 46686 -IEdldEFsbA== 46687 -VHlwZUluZm8= 46688 -RUg= 46689 -4oCcVGhleQ== 46690 -IHtb 46691 -IGdhZw== 46692 -INqp 46693 -IERyb3Bkb3du 46694 -LmZyZWU= 46695 -Z29uZQ== 46696 -aW1lbnM= 46697 -IGluc3RhbA== 46698 -CWN1cmw= 46699 -X0NBTg== 46700 -IEJvbmU= 46701 -77yU 46702 -b255bXM= 46703 -LWdvdmVybm1lbnQ= 46704 -LmJpbmRpbmdOYXZpZ2F0b3I= 46705 -IERhbnM= 46706 -IE1jTA== 46707 -KGVu 46708 -Pihf 46709 -0JLRiw== 46710 -Lio7DQo= 46711 -PWo= 46712 -LWNvcg== 46713 -U29u 46714 -LlRvb2xTdHJpcEl0ZW0= 46715 -LWFyb3VuZA== 46716 -X1hNTA== 46717 -ZW5kRGF0ZQ== 46718 -IHNsYWNr 46719 -IHJvdGF0ZWQ= 46720 -IG5vcWE= 46721 -IGNvdHRhZ2U= 46722 -IGVuY29udHJhcg== 46723 -X3NraWxs 46724 -aG91ZXR0ZQ== 46725 -IQ0K 46726 -LndlYXRoZXI= 46727 -IGVtcGhhc2l6ZWQ= 46728 -5a62 46729 -INGB0L/QuNGB 46730 -IENvbXBpbGVy 46731 -KGFuZHJvaWQ= 46732 -IOKAug== 46733 -LnR1cm4= 46734 -IHN1cHByZXNzaW9u 46735 -X2NhbGxz 46736 -ICpA 46737 -KHN0cmxlbg== 46738 -LmhleA== 46739 -IEJpbGxz 46740 -IFJTQQ== 46741 -z4I= 46742 -IEVzY2FwZQ== 46743 -ZW1lbnRpYQ== 46744 -IGZyb250ZW5k 46745 -IHBpbnQ= 46746 -X2V4Yw== 46747 -enpv 46748 -W10sCg== 46749 -ICInLCci 46750 -LkVudmlyb25tZW50 46751 -IGFmb3JlbWVudGlvbmVk 46752 -IGVuZHVyZQ== 46753 -cHJvdG90eXBl 46754 -dGhlcmFweQ== 46755 -c3Np 46756 -RGVn 46757 -X3BsdWdpbnM= 46758 -LnVzZXJJbmZv 46759 -UHJpbnRlcg== 46760 -IFBST0dSQU0= 46761 -IHJ1aW5z 46762 -IGVtcGlyaWNhbA== 46763 -IGNyYXds 46764 -IEJvaWxlcg== 46765 -LWNvbW1lbnQ= 46766 -LnN1YnBsb3Q= 46767 -X2V0 46768 -ICcuJyw= 46769 -bWlub3I= 46770 -IEN1c3RvbXM= 46771 -IHlhdw== 46772 -dW5kZXJsaW5l 46773 -IENvbW8= 46774 -KCgn 46775 -KG1lYW4= 46776 -IGNoYXF1ZQ== 46777 -IEJsb2Nrcw== 46778 -LnJhZA== 46779 -aWxpYnJpdW0= 46780 -IHdlYmRyaXZlcg== 46781 -IG1lbGhvcg== 46782 -ZGFuYQ== 46783 -IEFidXNl 46784 -IFNvdXRod2VzdA== 46785 -IFBhcmVu 46786 -UEVSVElFUw== 46787 -CUlM 46788 -IHNjcmVhbQ== 46789 -dnU= 46790 -IGluY29tZXM= 46791 -IG5pbQ== 46792 -IGxhY2U= 46793 -IGNvbXBlbnNhdGU= 46794 -UmV2ZXJzZQ== 46795 -RGF0 46796 -X2F0dGFjaw== 46797 -IG5vdXI= 46798 -YWNoZW4= 46799 -Y2Vr 46800 -PEZ1bmM= 46801 -d2ll 46802 -Y29tcHJlc3NlZA== 46803 -LW1hdGNo 46804 -KCIiKV0K 46805 -aW1pemVk 46806 -Lm9yaWVudGF0aW9u 46807 -LmNvbXBhcmVUbw== 46808 -IG1hc3NhZ2dp 46809 -IOychA== 46810 -IGVsYm93 46811 -IGFudGlveGlk 46812 -dW5kcmVkcw== 46813 -L3Rvb2xz 46814 -IFJPVw== 46815 -YW5tYXI= 46816 -IFdvdw== 46817 -X3RpY2tldA== 46818 -UHJvZ3JhbW1pbmc= 46819 -IHRoZW9y 46820 -LXJldmlldw== 46821 -KCkpKSk7Cg== 46822 -IFJpY2hhcmRzb24= 46823 -IFBvY2tldA== 46824 -XVtd 46825 -YW1wcA== 46826 -X2hlYWx0aA== 46827 -IFBPUA== 46828 -IE5hdmFs 46829 -R3Vlc3M= 46830 -IGFuY2VzdG9y 46831 -LkdldEFsbA== 46832 -LmxvY2FsU2NhbGU= 46833 -IE1hcHBlcg== 46834 -IGFjY3VtdWxhdGlvbg== 46835 -IHNpbXVsYXRlZA== 46836 -IERyaXZlcnM= 46837 -IGTDqXM= 46838 -Y3VycmluZw== 46839 -IGVsZXBoYW50 46840 -IGFkdmVydGlzZWQ= 46841 -IG1haWxib3g= 46842 -U0hJRlQ= 46843 -IE1vbmljYQ== 46844 -IGFuYw== 46845 -IHdhcmRyb2Jl 46846 -SW5ncmVkaWVudHM= 46847 -IHx8DQo= 46848 -aXBweQ== 46849 -IGFudGliaW90aWNz 46850 -YXZpbmdz 46851 -KGN4 46852 -IEZlcnJhcmk= 46853 -IEFuaW1hdG9y 46854 -LmR0eXBl 46855 -cmVtb3ZlZA== 46856 -b3JkZXJieQ== 46857 -IGNyZXM= 46858 -b2PDqg== 46859 -IHB5bQ== 46860 -IENpcmN1bGFy 46861 -QGluZGV4 46862 -IFdhcm0= 46863 -U2F5 46864 -IEFzc2lzdGFuY2U= 46865 -IGN1cnRhaW4= 46866 -IE1vbnRl 46867 -SUxFUg== 46868 -IENWRQ== 46869 -IER1Y2s= 46870 -IEFsbG93cw== 46871 -X2ZpcmU= 46872 -IERlcmJ5 46873 -IHJlcG9z 46874 -IGh0dHBDbGllbnQ= 46875 -IHBzeWNoaWF0 46876 -IG5vd2FkYXlz 46877 -IGNhdXRpb3Vz 46878 -IENvbXB1dGluZw== 46879 -IGNvbXBsZXRpb25IYW5kbGVy 46880 -IFdlbHNo 46881 -IEJFU1Q= 46882 -IHN0cmVzc2Z1bA== 46883 -X1BF 46884 -5pel5pyf 46885 -IERhdGFGcmFtZQ== 46886 -CUludGVnZXI= 46887 -X1ByaW50 46888 -TW92ZXM= 46889 -IHRyYW5zZm9ybWluZw== 46890 -LkJhdGNo 46891 -eWFob28= 46892 -UG9zaXRpb25z 46893 -emVq 46894 -IG5vb2Q= 46895 -aW9yZXM= 46896 -Xyo= 46897 -IGNsaw== 46898 -IEZsb3lk 46899 -IGhhcA== 46900 -Zm9udHNpemU= 46901 -IG5heg== 46902 -Lm5vdGlmaWNhdGlvbg== 46903 -IERlcHJlc3Npb24= 46904 -IGFjbmU= 46905 -KioqCgo= 46906 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCg== 46907 -LmNvbnRlbnRz 46908 -eW50aA== 46909 -IFN0cmFpZ2h0 46910 -Jyl9fSI+PC8= 46911 -IGJ1bGI= 46912 -Ulg= 46913 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0K 46914 -IGNvbXVuaWM= 46915 -IFJO 46916 -LW1lZGl1bQ== 46917 -TEVBTg== 46918 -PWxlbg== 46919 -UGhvbmVOdW1iZXI= 46920 -ZXJ2YXRpb25z 46921 -QWNjdXJhY3k= 46922 -IEFubm90YXRpb24= 46923 -X2tleXdvcmQ= 46924 -X2hpbnQ= 46925 -IEF0aGVucw== 46926 -IGFzc2lzdGluZw== 46927 -IEhD 46928 -LkluaXRpYWxpemU= 46929 -JykpKQo= 46930 -dXBh 46931 -IHN1aXY= 46932 -IElQQw== 46933 -PFRFbnRpdHk= 46934 -IGJyYW5kZWQ= 46935 -b29tbGE= 46936 -bGFyxLE= 46937 -IFhNTEh0dHBSZXF1ZXN0 46938 -IGTDqWrDoA== 46939 -IHRyYW5zY3JpcHRpb24= 46940 -IHByZXZhbGVudA== 46941 -LnBsYW4= 46942 -IHN0YXJl 46943 -IHdvcmtvdXRz 46944 -IEVkdWNhdGlvbmFs 46945 -IG1lc3N5 46946 -IE1PVA== 46947 -LkNvbW1hbmRUeXBl 46948 -UWVk 46949 -KGdjYQ== 46950 -IExpbmVhckxheW91dE1hbmFnZXI= 46951 -IEJsb3c= 46952 -IEFsdW1pbnVt 46953 -IHN3aW5nZXJjbHVi 46954 -IFRyYW5zaXQ= 46955 -IGV4cG9z 46956 -dmly 46957 -KHNlY29uZA== 46958 -IGJlbG9uZ2Vk 46959 -U3RvbmU= 46960 -6ZW/ 46961 -IFN1bA== 46962 -IGdpZA== 46963 -IGFsbG95 46964 -ZXJ2YQ== 46965 -aXNlY29uZA== 46966 -X1JFTkRFUg== 46967 -IGFuZ2Vscw== 46968 -IFBoaWxvc29waHk= 46969 -b3B1cw== 46970 -IG1vbw== 46971 -ZW5ndWlu 46972 -X1ZBUklBQkxF 46973 -X0RFU1Q= 46974 -KGF1eA== 46975 -IGhvZQ== 46976 -IGRvYg== 46977 -YXR0YWNobWVudHM= 46978 -IGNvcnJpZG9y 46979 -IGRpdmlkZW5k 46980 -nbw= 46981 -IFRocm91Z2hvdXQ= 46982 -Lm9wdGlt 46983 -JG5ldw== 46984 -IGJlcmc= 46985 -IHNwcmVhZHNoZWV0 46986 -LlRyeUdldFZhbHVl 46987 -IHBheW91dA== 46988 -IE9uRGVzdHJveQ== 46989 -YXV0aGVudGljYXRpb24= 46990 -IE1pZ3VlbA== 46991 -cnRj 46992 -IENocmlzdGluZQ== 46993 -IEFJUg== 46994 -IGp1cmlz 46995 -IGRlc3BhaXI= 46996 -IHBhdGVudHM= 46997 -LWhhcw== 46998 -JV4= 46999 -5LuY 47000 -X3N0cmR1cA== 47001 -IFJlYXI= 47002 -ZXR0ZXM= 47003 -KHByb3BlcnRpZXM= 47004 -IHdyaXRhYmxl 47005 -LmlzTnVsbA== 47006 -b2xpY3M= 47007 -X2Jsb2I= 47008 -IGN1YWxxdWllcg== 47009 -YWZp 47010 -b3d5Y2g= 47011 -6I635Y+W 47012 -w4c= 47013 -IENhcmRpbmFs 47014 -IHRlbWE= 47015 -IkFuZA== 47016 -UGFnZVNpemU= 47017 -56eS 47018 -LlNpbXBsZURhdGVGb3JtYXQ= 47019 -IFdpbm5lcg== 47020 -IGNvcnJlbw== 47021 -X3dl 47022 -LmFkZE9iamVjdA== 47023 -KGNvdXJzZQ== 47024 -IGhvZw== 47025 -b3Bybw== 47026 -IHByb2JhdGlvbg== 47027 -dW5hYmxl 47028 -KGFjdGl2ZQ== 47029 -5Zu+54mH 47030 -IHBlcnRhaW5pbmc= 47031 -IGVtcGhhc2l6ZQ== 47032 -IFByaW50ZXI= 47033 -PS4= 47034 -IHVwZ3JhZGluZw== 47035 -L2NvbnRhY3Q= 47036 -PVtb 47037 -LXNhbg== 47038 -CXZhbHVlcw== 47039 -IGRvc2FnZQ== 47040 -U29saWQ= 47041 -IFJvb3NldmVsdA== 47042 -5ZWG5ZOB 47043 -IHJlY3JlYXRpb24= 47044 -IFRlcm1pbg== 47045 -LkJhZA== 47046 -IEJvbHQ= 47047 -U2t5 47048 -X0ltYWdl 47049 -IHNxdWly 47050 -IENvYg== 47051 -T1JO 47052 -IGF1Yw== 47053 -LkxFRlQ= 47054 -J0I= 47055 -LXJlc2lzdGFudA== 47056 -PiIr 47057 -IHRva2VuaXplcg== 47058 -IHNvdmVyZWlnbnR5 47059 -IFBlbmNl 47060 -KCkiKTsK 47061 -IHBlc3NvYXM= 47062 -Lkdl 47063 -IEluY2x1ZGVk 47064 -IHBhZ2luYQ== 47065 -IGV4cG9zaW5n 47066 -0LXRiA== 47067 -X1NDUklQVA== 47068 -LyQnLA== 47069 -VGh1bWJuYWls 47070 -15Q= 47071 -d2ViRWxlbWVudFg= 47072 -d2ViRWxlbWVudFhwYXRocw== 47073 -cHJlc3N1cmU= 47074 -IEN1cnJ5 47075 -X0NQ 47076 -T0xVVElPTg== 47077 -SUxFUw== 47078 -cHJvdGVjdA== 47079 -b29sYQ== 47080 -V29ya3NwYWNl 47081 -e307Cg== 47082 -IFVOUw== 47083 -IHN5bXBhdGh5 47084 -cm9rZXI= 47085 -IHJlbW9kZWw= 47086 -CWNlbGw= 47087 -IGF0b3A= 47088 -LkZ1bGxOYW1l 47089 -IGZhdXQ= 47090 -IEVhc2lseQ== 47091 -X2R5bmFtaWM= 47092 -IGZyYW1lZA== 47093 -IG1vdGl2ZQ== 47094 -6Lev 47095 -c2Ft 47096 -IG1hcmNh 47097 -IFRleHRFZGl0aW5nQ29udHJvbGxlcg== 47098 -IGRlc3RydWN0b3I= 47099 -Y3JlYW0= 47100 -IHJ1ZGU= 47101 -IEJvbGQ= 47102 -IEluZGlnZW5vdXM= 47103 -IGdlbnM= 47104 -IHJlbGFjaW9u 47105 -KHN5c3RlbQ== 47106 -IFVJRm9udA== 47107 -X2NoYXJnZQ== 47108 -VVNURVI= 47109 -RVY= 47110 -Lk5hbWVzcGFjZQ== 47111 -IG1lcmdlcg== 47112 -IGNhbGxvYw== 47113 -Z2FuZw== 47114 -QmFkUmVxdWVzdA== 47115 -IHNwZXI= 47116 -LWRlc2lnbg== 47117 -IOKH 47118 -Q2hhbg== 47119 -IG9yZ2FuaXNt 47120 -LCk= 47121 -PWlk 47122 -X3BsYW5l 47123 -IENhc2Vz 47124 -ZWxmYXN0 47125 -IExlZ2lzbGF0dXJl 47126 -IEZha2Vy 47127 -IGludm9raW5n 47128 -LXV0aWxz 47129 -KCkuJw== 47130 -LmZhY2U= 47131 -IGd1YXJkaWFu 47132 -bXlNb2RhbA== 47133 -IGNsaXBib2FyZA== 47134 -IEFUTQ== 47135 -IHBlYXM= 47136 -IFN5bHY= 47137 -LmNhbGM= 47138 -IENvbnRhY3Rz 47139 -aW50VmFsdWU= 47140 -IG1vZGlmeWluZw== 47141 -IEJhcmI= 47142 -Lmxvc3M= 47143 -X3BlcmNlbnRhZ2U= 47144 -QXNrZWQ= 47145 -KGxzdA== 47146 -YXRlZ29yaWNhbA== 47147 -LWZpbGVz 47148 -IFJvbWFuaWE= 47149 -LkFj 47150 -IGhhaQ== 47151 -IEZseWluZw== 47152 -IMW8 47153 -anA= 47154 -IFRyYWluZXI= 47155 -LmFyYw== 47156 -X2RlZw== 47157 -IHRyYWNlYmFjaw== 47158 -T3JGYWls 47159 -RkxPVw== 47160 -Lm9sZA== 47161 -b3lh 47162 -Z210 47163 -aXNlbXB0eQ== 47164 -IHZhY2NpbmF0aW9u 47165 -IG9ic29sZXRl 47166 -cmVjb2duaXplZA== 47167 -IHJ1aW5lZA== 47168 -IFJlaW4= 47169 -IFRyYWNraW5n 47170 -eGZi 47171 -2KfbjA== 47172 -IHbDpnJl 47173 -IGJyeXN0ZXI= 47174 -IElUUw== 47175 -IGRlc3Rpbnk= 47176 -IHN3ZWFy 47177 -IHJlZGVz 47178 -IGNsZg== 47179 -IGZsaXBwZWQ= 47180 -CWhlYWQ= 47181 -Qmx1ZXRvb3Ro 47182 -IE92ZXJyaWRlcw== 47183 -OkJvb2xlYW4= 47184 -Xz0= 47185 -X2xy 47186 -c3Bhd24= 47187 -OmluZGV4 47188 -VkFMVUVT 47189 -aXNrZXk= 47190 -PyIpOwo= 47191 -LnN5bnRoZXRpYw== 47192 -IENoZWNraW5n 47193 -c3RydWN0dXJlcw== 47194 -aXBpbmc= 47195 -IHZvY2Fscw== 47196 -LVVw 47197 -IE1hbnVmYWN0dXJlcnM= 47198 -IE1hcnJpYWdl 47199 -5Luj56CB 47200 -IGdhcm5lcg== 47201 -X0NsaWVudA== 47202 -cGFyYWxsZWw= 47203 -UklFTkQ= 47204 -IHZpbmVnYXI= 47205 -c2VndWU= 47206 -SkI= 47207 -IGNvbnRhY3Rpbmc= 47208 -IENhcnJvbGw= 47209 -IG91dHJlYWNo 47210 -dGVuc29y 47211 -X3ZhcmlhbnQ= 47212 -IHRoZWF0 47213 -bGljYWJsZQ== 47214 -e3w= 47215 -dGlueQ== 47216 -X2xldHRlcg== 47217 -IHBlbmNpbA== 47218 -SGVhZGVyc0hlaWdodFNpemVNb2Rl 47219 -aWx0cm8= 47220 -LmF1dG9jb25maWd1cmU= 47221 -LmRyYWc= 47222 -LnVzZVN0YXRl 47223 -IEJNSQ== 47224 -aGludA== 47225 -Q29tcGlsZQ== 47226 -Klw= 47227 -ZW5hcnk= 47228 -IGx2bA== 47229 -LkNhY2hl 47230 -Kz0i 47231 -X3R2 47232 -cnVpdG1lbnQ= 47233 -IGZyZWFk 47234 -QXJ0aWNsZXM= 47235 -ZmlsYQ== 47236 -IHBhY2thZ2Vk 47237 -4piG 47238 -QVRIRVI= 47239 -IFBsYW5uZWQ= 47240 -c2NoZW1l 47241 -IGRpYXJ5 47242 -IG9mZmVuc2Vz 47243 -Lzw/ 47244 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 47245 -UHJvZ3Jlc3NIVUQ= 47246 -IEdvcg== 47247 -LmdldFRpdGxl 47248 -IG1vY2tlZA== 47249 -IFRvcnk= 47250 -ICIpIjsK 47251 -I2c= 47252 -IGxpZWQ= 47253 -IHN2Yw== 47254 -X2d1aQ== 47255 -RU5UUlk= 47256 -IHNlcnZpY2lv 47257 -bW91c2VvdmVy 47258 -U0FDVElPTg== 47259 -44Kz 47260 -IHJlaWZl 47261 -bGVjdHJpYw== 47262 -X2NyZWF0aW9u 47263 -UmVhbGl0eQ== 47264 -KCcr 47265 -cHJvZHVjdElk 47266 -U3VwcGxpZXI= 47267 -LUxl 47268 -LnJlcG8= 47269 -dWNraW5n 47270 -X1N0cg== 47271 -IFJlbGF5 47272 -0LjQuA== 47273 -IHBlcnY= 47274 -Q2hpY2Fnbw== 47275 -IG1haXNvbg== 47276 -IHN0aWNrZXI= 47277 -X3ByZXNzZWQ= 47278 -U3dhcA== 47279 -IElH 47280 -IHN1c2NlcHRpYmxl 47281 -b2NhZG8= 47282 -IGdpbg== 47283 -ZXhl 47284 -aWdoYm9yaG9vZA== 47285 -KWA= 47286 -IGRpYWdyYW1z 47287 -IGluZmxhbW1hdG9yeQ== 47288 -IHTDqQ== 47289 -IFBvcHVw 47290 -IGFwcHJlaA== 47291 -IFBvcnRmb2xpbw== 47292 -IHdvcnM= 47293 -LmVudW1z 47294 -0LXQs9C+ 47295 -L0J1dHRvbg== 47296 -IFBoYW50b20= 47297 -ICM6 47298 -IGRpaw== 47299 -cGFnZXI= 47300 -ZnRhcg== 47301 -IG9yZ2FuaXplcg== 47302 -KGNoaWxkcmVu 47303 -IE11bmljaA== 47304 -IHN0cmFuZw== 47305 -IFJX 47306 -44K/ 47307 -TWFo 47308 -cHRpZGU= 47309 -IGxlYXJucw== 47310 -IHJlZHVjdGlvbnM= 47311 -IFJlcGxhY2VtZW50 47312 -T1RT 47313 -YWxjb24= 47314 -KHBhcnRz 47315 -YmFzaA== 47316 -IENpdGl6ZW4= 47317 -jbDsnbQ= 47318 -IEh0dHBTZXJ2bGV0 47319 -X1NDSEVNQQ== 47320 -bWVhbnM= 47321 -IGhvcnJpZmlj 47322 -VkVSSUZZ 47323 -IERDSEVDSw== 47324 -ICgv 47325 -LmJlZm9yZQ== 47326 -LnRleHR1cmU= 47327 -Z2V0TW9jaw== 47328 -IFNlbnNl 47329 -SW5zcGVjdG9y 47330 -VGV4dE5vZGU= 47331 -KEFM 47332 -LmdldE5vZGU= 47333 -IGJveWM= 47334 -IEJyaXNiYW5l 47335 -IGJhdHRsaW5n 47336 -CXR4 47337 -IGxvYmJ5aW5n 47338 -YnVpbHQ= 47339 -IFNFRUs= 47340 -IHJhbmRvbWl6ZWQ= 47341 -Z25p 47342 -X2NsdXN0ZXJz 47343 -X2lkZW50aXR5 47344 -IGNhcmRpYWM= 47345 -IG5ld1VzZXI= 47346 -LlZpZGVv 47347 -ZHVpdA== 47348 -XWluaXQ= 47349 -QXRs 47350 -KXZhbHVl 47351 -VGV4dFV0aWxz 47352 -INC10YHQu9C4 47353 -Q29tcHV0ZQ== 47354 -PSgn 47355 -CQkgICAgICAgICAgICAgICA= 47356 -IGFydGVy 47357 -IFRXTw== 47358 -JykpLA== 47359 -IERJVg== 47360 -IHByaXZpbGVnZWQ= 47361 -IFBhcnRuZXJzaGlw 47362 -IEhlYXRoZXI= 47363 -YmF5 47364 -YXRpc2ZpZWQ= 47365 -aW5zdGFncmFt 47366 -X1NlbmQ= 47367 -IEFTRg== 47368 -JG5hbWU= 47369 -IGJvbw== 47370 -IGTDqWY= 47371 -X0ZpZWxk 47372 -IEVkdQ== 47373 -Y2FuZGlkYXRl 47374 -cnVieQ== 47375 -IGFjY3VtdWxhdGU= 47376 -KEludFB0cg== 47377 -IGJ1c2luZXNzbWFu 47378 -IGVjb25vbWljYWxseQ== 47379 -IFJpbmdz 47380 -IElucHV0cw== 47381 -uYQ= 47382 -YWNpZQ== 47383 -IEFsYXJt 47384 -IExvZ291dA== 47385 -LnNlcXVlbmNl 47386 -IFZpZW5uYQ== 47387 -b3By 47388 -IGRydW1z 47389 -PWNvbmZpZw== 47390 -cXVp 47391 -IGRhdG8= 47392 -IHBvbHltZXI= 47393 -IENoYW5nZWQ= 47394 -V2ViUmVxdWVzdA== 47395 -IEFkdmFuY2U= 47396 -IHVuZGVyZ29pbmc= 47397 -LkNvbnNvbGU= 47398 -IGN1cnJlbnROb2Rl 47399 -IFdvb2w= 47400 -IHDDoWdpbmE= 47401 -UkVHSVNURVI= 47402 -IHNhZ2E= 47403 -IFlPUks= 47404 -YW1hbmhv 47405 -5a6M 47406 -IEJ1bmRlcw== 47407 -IERpYWxvZ0ludGVyZmFjZQ== 47408 -Z2VvaXM= 47409 -dW5jaWF0aW9u 47410 -PyQ= 47411 -LkFzc2VydGlvbnM= 47412 -IHNlYXRlZA== 47413 -IFNweQ== 47414 -UG9zZQ== 47415 -IkM= 47416 -IGFob3Jh 47417 -INGE0LDQudC7 47418 -IOuzgA== 47419 -IHdhcnA= 47420 -UHJvamVjdGlvbg== 47421 -IFNpbmdsZXM= 47422 -IEFkdmVydGlzaW5n 47423 -TGludXg= 47424 -dXN0eQ== 47425 -IHBlbmFs 47426 -VVNJQw== 47427 -b2RpYQ== 47428 -Lm5ldGJlYW5z 47429 -IFVn 47430 -IEJyZW50 47431 -LWxvZw== 47432 -L2NhdGVnb3J5 47433 -IEN1c3RvbWl6ZQ== 47434 -aXJlbg== 47435 -77yaPC8= 47436 -aW5hcnM= 47437 -ICgrKw== 47438 -R29pbmc= 47439 -RVhFQw== 47440 -KG1lc2g= 47441 -IHBlcmltZXRlcg== 47442 -Q2xz 47443 -Y2VpdmluZw== 47444 -bWVuc2FqZQ== 47445 -KCkpKXsK 47446 -IHByb3N0YXRl 47447 -X2J1eQ== 47448 -IFJvb2Y= 47449 -LlJldHVybg== 47450 -IG1hcnJpYWdlcw== 47451 -X3RodW1i 47452 -574= 47453 -4K+N 47454 -VGV4dHVyZXM= 47455 -KFRFWFQ= 47456 -c2hvcnRjdXQ= 47457 -VHJhbnNmb3JtZXI= 47458 -QVRJQw== 47459 -IFNub3dkZW4= 47460 -c2NyaWJlcnM= 47461 -bWFya2Vk 47462 -IOKGkQ== 47463 -aG9yYQ== 47464 -T1BFUg== 47465 -IEZZ 47466 -IEF1dGhlbnRpYw== 47467 -IGF1ZGk= 47468 -cmFtZXI= 47469 -IExpdGVyYXR1cmU= 47470 -IGl0ZW1JZA== 47471 -LkF0dA== 47472 -KGNudA== 47473 -IEtT 47474 -LWxpbnV4 47475 -IFBhcnRpY2lwYW50 47476 -IENydWlzZQ== 47477 -aXR1bG8= 47478 -dXN0cmlhbA== 47479 -IGNsYXNl 47480 -ID0k 47481 -X2RhdGVz 47482 -Y3VycmVudFBhZ2U= 47483 -aXhh 47484 -ZXhhY3Q= 47485 -IHRzbA== 47486 -LlNv 47487 -L2RvY3VtZW50 47488 -aGFydA== 47489 -X0lETEU= 47490 -e30u 47491 -eWV0 47492 -SXJvbg== 47493 -IFRocm9uZXM= 47494 -c25k 47495 -XHhh 47496 -IGJldmVyYWdlcw== 47497 -X3RyYW5zcG9ydA== 47498 -IGZvaWw= 47499 -IHRhc3Rpbmc= 47500 -IGdvZWQ= 47501 -TWVtbw== 47502 -IG5pdHJvZ2Vu 47503 -Lk1lbWJlcg== 47504 -LmZsYXQ= 47505 -IGlsbHVt 47506 -bWluZW50 47507 -Lnpvb20= 47508 -IFB0cg== 47509 -b2Npbw== 47510 -IENvbnN1bHRpbmc= 47511 -IENvbmU= 47512 -CWl0ZW1z 47513 -IExN 47514 -IG9hdXRo 47515 -IFByb2dyYW1tZQ== 47516 -b2Nob25k 47517 -KHNlbGVjdG9y 47518 -IHdhdGVycHJvb2Y= 47519 -IE1lcmtlbA== 47520 -IHN1ZmZlcnM= 47521 -IG5wbQ== 47522 -6LGh 47523 -IExhbmRpbmc= 47524 -IExBTg== 47525 -CQkJCQkJDQo= 47526 -L2lz 47527 -IHPDqXJpZQ== 47528 -IEdVSUxheW91dA== 47529 -Z2l2ZQ== 47530 -X0NZ 47531 -QnJvd3Nl 47532 -Lm11bHRpcGx5 47533 -PSIkKA== 47534 -dXNv 47535 -LXBhcmVudA== 47536 -Lk1hdGg= 47537 -Lm51bWJlck9m 47538 -IHRpZW5lbg== 47539 -IHJlc2VudA== 47540 -IHBpdGNoaW5n 47541 -Il0pLAo= 47542 -LlV0aWxpdGllcw== 47543 -IG11bHRpcGxpY2F0aW9u 47544 -OnR5cGU= 47545 -IHBwcmludA== 47546 -aWFuaQ== 47547 -5YiZ 47548 -IGxhdW5jaGVy 47549 -IHJ1Z2J5 47550 -546w 47551 -CgkJCQo= 47552 -aGlk 47553 -QW5nbGVz 47554 -IGdvb2RieWU= 47555 -IGlucHV0U3RyZWFt 47556 -LndhdGNo 47557 -R29vZHM= 47558 -IFNheXM= 47559 -PkY= 47560 -IFN0aWNr 47561 -IGNlcmM= 47562 -IFNsZWU= 47563 -CQkgICAgICAgIA== 47564 -PEltYWdl 47565 -IOiuvg== 47566 -LWVkaXRvcg== 47567 -cGllY2Vz 47568 -IERyYW1h 47569 -IC8vLy8vLy8vLy8vLy8vLy8vLw== 47570 -IFRhc2tz 47571 -QVJD 47572 -Z2F0ZXdheQ== 47573 -LmdldGN3ZA== 47574 -Lk1ldGFkYXRh 47575 -IGd1ZXNzaW5n 47576 -5Zyw5Z2A 47577 -IHNtYXJ0ZXI= 47578 -IEdldEVudW1lcmF0b3I= 47579 -IGVmdGVy 47580 -L29wZXJhdG9ycw== 47581 -IEdMZmxvYXQ= 47582 -IGbDuHI= 47583 -IG9wYXF1ZQ== 47584 -5L+d5a2Y 47585 -U3ByZWFk 47586 -U1lTVEVN 47587 -IGludmVyc2lvbg== 47588 -IEJhc2tldGJhbGw= 47589 -IHNpbXVsYXRpb25z 47590 -IGRlbmllcw== 47591 -IGF2ZXo= 47592 -X2xpc3RlbmVy 47593 -IGVuaGFuY2luZw== 47594 -IE15dGg= 47595 -IExha2Vycw== 47596 -X01E 47597 -TmRFeA== 47598 -REFUQUJBU0U= 47599 -IHThuw== 47600 -YXJ0aA== 47601 -W2xlZnQ= 47602 -IGNvbnRlc3Rz 47603 -c3RpbGU= 47604 -KEtFUk4= 47605 -X2Zj 47606 -X3Bt 47607 -IHByZXNpZGVudHM= 47608 -IGhvc3BpdGFsaXR5 47609 -IGZhZGVJbg== 47610 -Uk9QRVJUWQ== 47611 -X21hcHM= 47612 -IERlZmluaXRpb25z 47613 -IGFzc2Vzc2luZw== 47614 -IHVzYXI= 47615 -IHF1YW50aXRhdGl2ZQ== 47616 -bW96 47617 -QmVhdXRpZnVs 47618 -Wygo 47619 -Ym9ucw== 47620 -ZnJlcXVlbmN5 47621 -Q29udGFpbg== 47622 -IHB1enpsZXM= 47623 -IENhc3Rybw== 47624 -IHZpbGxh 47625 -IGtpbmRseQ== 47626 -Rm9udEF3ZXNvbWU= 47627 -ZXJuYQ== 47628 -ZXBvY2hz 47629 -X2RhdGFz 47630 -CWlw 47631 -LnBhZGRpbmc= 47632 -IENvbnRlc3Q= 47633 -IGVkaXRpb25z 47634 -IGRpc3Byb3BvcnRpb24= 47635 -IElDTw== 47636 -IGNvbWViYWNr 47637 -PXZhbHVl 47638 -cmlhZA== 47639 -LXNvcnQ= 47640 -U3VibWl0dGVk 47641 -KG5ldHdvcms= 47642 -IENlbA== 47643 -IGluc3RhbGxtZW50 47644 -bGFzaGVz 47645 -Lkxpc3RWaWV3 47646 -IFZhdGljYW4= 47647 -KE1lZGlhVHlwZQ== 47648 -SVZFRA== 47649 -cmVhY2hhYmxl 47650 -Oklz 47651 -IENJVFk= 47652 -5Lqs 47653 -IEhlbHBmdWw= 47654 -IGJhxZ8= 47655 -JQ0K 47656 -IHBzeWNoaWF0cmlj 47657 -IHJlY3ljbGVk 47658 -Rk9STUFU 47659 -IEdyb3c= 47660 -YmluZQ== 47661 -R2l0 47662 -LnNz 47663 -IFdlYXBvbnM= 47664 -IFN0eQ== 47665 -X2Fycm93 47666 -KnNlbGY= 47667 -aXJlbWVudA== 47668 -IGRlZ2xp 47669 -QXBwRGVsZWdhdGU= 47670 -X2Jhbm5lcg== 47671 -IGNvb3JkaW5hdGVk 47672 -IFdlYmNhbQ== 47673 -IGNlbGVicmF0aW9ucw== 47674 -LmFjdA== 47675 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 47676 -KHNob3c= 47677 -IHdlZWtkYXk= 47678 -IGNvbmNlcnRz 47679 -0L7Qu9C9 47680 -Y2xpbg== 47681 -IGNyb24= 47682 -IE5pbQ== 47683 -LnNldFZlcnRpY2Fs 47684 -IEVsbGVu 47685 -2LPYqg== 47686 -IFNBTQ== 47687 -RWZm 47688 -Z3o= 47689 -c3RlYW0= 47690 -IGFudGlxdWU= 47691 -cGh5c2ljYWw= 47692 -IEZvcm1EYXRh 47693 -LnNldHRlcg== 47694 -IFBPSU5U 47695 -Qm9u 47696 -IGZsYXZvdXI= 47697 -ZXJ2ZW50aW9u 47698 -X0VOVElUWQ== 47699 -CSAgICAgICAgICAgIA== 47700 -IGludHJpbnNpYw== 47701 -IOaO 47702 -YXBwZW5kVG8= 47703 -YXJhbWVs 47704 -KV0p 47705 -IFJlY29tbWVuZA== 47706 -KW0= 47707 -T3V0T2ZSYW5nZQ== 47708 -IGtuaWdodA== 47709 -IHNhdGVsbGl0ZXM= 47710 -IFRpdGFucw== 47711 -IHdlaWdoZWQ= 47712 -IERhbmE= 47713 -ZWFzZQ== 47714 -IHNpcA== 47715 -U0lN 47716 -IERldmVsb3BlcnM= 47717 -bWFsaW5r 47718 -L2NoZWNr 47719 -X1BMTA== 47720 -bnVuZw== 47721 -IGRyeWVy 47722 -PUE= 47723 -LmR3 47724 -X1NRTA== 47725 -IHN1YnBsb3Q= 47726 -RFJPUA== 47727 -IHByb3RvdHlwZXM= 47728 -IGhvdXJseQ== 47729 -ZGlzcGxheU5hbWU= 47730 -IGFzaQ== 47731 -IFZpb2xlbmNl 47732 -IGFzdHJvbmF1dA== 47733 -IGRhdGF0eXBl 47734 -IGluZm9ybWF0aW9uYWw= 47735 -IGludmVzdGlnYXRpdmU= 47736 -ZXRlcm1pbmVk 47737 -cmVuYWw= 47738 -Oyc+ 47739 -CWNvbA== 47740 -Vkc= 47741 -X2Jvb2xlYW4= 47742 -cmVjZW50 47743 -ICopCgo= 47744 -IFJhaW5ib3c= 47745 -b21tZW4= 47746 -IGx1cg== 47747 -IG9wcHJlc3Npb24= 47748 -KCIsIik7Cg== 47749 -IEZhY2lsaXR5 47750 -REVGSU5FRA== 47751 -IG5lb24= 47752 -IG9mZmVuZGVy 47753 -QUZQ 47754 -IENsZWFuaW5n 47755 -W10pOg== 47756 -IHVuZG9jdW1lbnRlZA== 47757 -LlJlcG9zaXRvcmllcw== 47758 -IEd1aXRhcg== 47759 -0LDRgdGB0LjQsg== 47760 -U2tpbGxz 47761 -IHRlc3RpbW9u 47762 -cnlwdG9ncmFwaHk= 47763 -IEFtYmVy 47764 -IFN0YWxpbg== 47765 -IGxvbmU= 47766 -IGFwZW5hcw== 47767 -IGRpZXNlcw== 47768 -IEFyZHVpbm8= 47769 -6L2s 47770 -PT0t 47771 -X0FjdA== 47772 -IGNvZGVk 47773 -4pag 47774 -YW1idXJnZXI= 47775 -LWxpbmtz 47776 -IGFybW91cg== 47777 -LkhpZ2g= 47778 -Z2V0Q29udGVudA== 47779 -c3RhZw== 47780 -IGhlY2s= 47781 -IOyXhg== 47782 -IE1jQ29ubmVsbA== 47783 -IENvbmNlcnQ= 47784 -IEFsbG9j 47785 -w6RyZQ== 47786 -LnJlcGxhY2VBbGw= 47787 -IHBhcnRpdGlvbnM= 47788 -cm90dA== 47789 -IEZsZQ== 47790 -X1RSRUU= 47791 -cmVhc29uYWJsZQ== 47792 -IFJlcG9ydGluZw== 47793 -IGJpbGxpb25haXJl 47794 -c2NvcmVz 47795 -bWlucw== 47796 -LWV5ZQ== 47797 -TU9SRQ== 47798 -YWJvcnQ= 47799 -IFNXVA== 47800 -IGludmVydGVk 47801 -IFRlYWNoZXJz 47802 -O24= 47803 -IGFzdHJv 47804 -0L3QvtCy 47805 -0LDQvdC40YY= 47806 -cHJvZHVjdG8= 47807 -Y291bnRyaWVz 47808 -IE93ZW4= 47809 -IGNvbnRhbWluYXRpb24= 47810 -IHZpYmU= 47811 -IEVsbGk= 47812 -LnNjcmlwdA== 47813 -IE9saXZl 47814 -RE1B 47815 -dmllcg== 47816 -OnNlbWljb2xvbg== 47817 -LW1vZHVsZQ== 47818 -Z3Jlc3NpdmU= 47819 -YWd1 47820 -X3BsYXllcnM= 47821 -IHJlc3VsdGFkb3M= 47822 -c3RhcnRlZA== 47823 -c2Nyb2xsVG9w 47824 -PT09PT0= 47825 -IHdlaWdoaW5n 47826 -IFtbWw== 47827 -emFobA== 47828 -KE5T 47829 -IEFzc2VydGlvbg== 47830 -bGVhZ3Vl 47831 -LnNldFRleHRDb2xvcg== 47832 -CU1lc3NhZ2U= 47833 -IG1vbXM= 47834 -X0FG 47835 -Lndo 47836 -QUxT 47837 -IGF1dHJl 47838 -XQoKCgo= 47839 -Lm9wYWNpdHk= 47840 -IEJ1ZGRoaXN0 47841 -IGRlYWY= 47842 -IE9yZ2FuaXNhdGlvbg== 47843 -KEdsb2JhbA== 47844 -ZW5zY2g= 47845 -IGhlYWRhY2hl 47846 -IEFsaWVu 47847 -X2lub2Rl 47848 -IFN0YXJr 47849 -IOaJ 47850 -LWxuZA== 47851 -b3JlZg== 47852 -X2ZlYXQ= 47853 -IHBlZGVzdHJpYW4= 47854 -IG5vbWluYWw= 47855 -IGJhbGxvb24= 47856 -IHNwcml0ZXM= 47857 -UHJvdG90eXBlT2Y= 47858 -IEFwb3N0 47859 -IEZFQVRVUkU= 47860 -T0g= 47861 -IHJlY2Vzcw== 47862 -IERvbm5h 47863 -Y29uc3VtZXI= 47864 -JEdMT0JBTFM= 47865 -IEdJRg== 47866 -LWZyYW1l 47867 -SW5pY2lv 47868 -IHBhc3NhZ2Vz 47869 -RGF0ZVN0cmluZw== 47870 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 47871 -LmJ5dGU= 47872 -QnVn 47873 -aW5pdGlhbGl6ZXI= 47874 -cGt0 47875 -b2RpdW0= 47876 -IERFUg== 47877 -Lm9wcw== 47878 -bGVyaQ== 47879 -IGdpZnRlZA== 47880 -IGRldGFjaA== 47881 -dGVycmFpbg== 47882 -ZWx0ZXJz 47883 -44GP 47884 -LmxvYWRlcg== 47885 -IE5HTw== 47886 -c3RybmNtcA== 47887 -S2g= 47888 -KGZvbnRTaXpl 47889 -cm9ja2V0 47890 -IHByZWNlZGVudA== 47891 -IEF1cm9yYQ== 47892 -IEV4cGVyaW1lbnQ= 47893 -aXNwaGVyZQ== 47894 -RW5jb2RlZA== 47895 -IOKAkwoK 47896 -IHB5cmFtaWQ= 47897 -IEFubml2ZXJzYXJ5 47898 -b2ZpbA== 47899 -658= 47900 -KHBsdWdpbg== 47901 -Q29lZmY= 47902 -IGNvb3BlcmF0ZQ== 47903 -IHByZWRvbWluYW50bHk= 47904 -SVNN 47905 -UGhyYXNl 47906 -X0RFRklORQ== 47907 -RmxpcA== 47908 -QU1JTFk= 47909 -IE1hcmtldHM= 47910 -IFN0cmVhbVJlYWRlcg== 47911 -IENvbWJpbmU= 47912 -IG1hbnVzY3JpcHQ= 47913 -enph 47914 -LHRw 47915 -V2hhdGV2ZXI= 47916 -SVRJQ0FM 47917 -aWdoYm91cg== 47918 -RGF0YVByb3ZpZGVy 47919 -LlRleHR1cmU= 47920 -cHJpdmFjeQ== 47921 -LlNESw== 47922 -IHJlY2hhcmdl 47923 -IGNwcA== 47924 -IENGRw== 47925 -KGhvbGRlcg== 47926 -KHB5 47927 -bW90 47928 -IHNhdm9pcg== 47929 -IFJvc2E= 47930 -IFBDcw== 47931 -IO2Z 47932 -Lmhlcm9rdQ== 47933 -IGZyZW4= 47934 -IFJpbGV5 47935 -YWdhdGU= 47936 -IHNvbmQ= 47937 -Lnhsc3g= 47938 -IGhhY2tlZA== 47939 -c3RhZA== 47940 -R2k= 47941 -IHNhbml0eQ== 47942 -IFNxbERhdGFBZGFwdGVy 47943 -Li4uIiw= 47944 -IFB1c3N5 47945 -ICoqKioqKioqKioqKioqKio= 47946 -IGhhc3NsZQ== 47947 -X1BBUkVOVA== 47948 -IFVBRQ== 47949 -IGJlZ2lubmVycw== 47950 -KENsaWVudA== 47951 -IHN0YXRpc3RpY2FsbHk= 47952 -LmhvdXI= 47953 -ZWRlbHRh 47954 -IHRyYWN0aW9u 47955 -dWVsdmU= 47956 -YXJhdA== 47957 -IHNhdW5h 47958 -SU5WQUxJRA== 47959 -IGluZGljdG1lbnQ= 47960 -QUxMRQ== 47961 -IGRpc3NlbnQ= 47962 -IFR5cG9ncmFwaHk= 47963 -IGludGVudGlvbmFs 47964 -c2l0 47965 -IEFuaW1hbHM= 47966 -IGNvdW50cnlzaWRl 47967 -IHVhcnQ= 47968 -fVwi 47969 -IHNlYW1sZXNz 47970 -vuekug== 47971 -IGF1dG9z 47972 -ICInIjsK 47973 -Rmx1c2g= 47974 -QU5OT1Q= 47975 -IGFsZ2VicmE= 47976 -YXNzb2M= 47977 -IFdhdGVycw== 47978 -IHByZXBhcmF0aW9ucw== 47979 -cm9ueW0= 47980 -Wyxd 47981 -U2Fucw== 47982 -IGFybWllcw== 47983 -aXBlZw== 47984 -IGNyZWFteQ== 47985 -LmFydA== 47986 -ZXRyZQ== 47987 -IEFuaW1hdGVk 47988 -IHVucGxlYXNhbnQ= 47989 -ZW1lYW4= 47990 -Z3JlYXQ= 47991 -acSF 47992 -IEVhcmxpZXI= 47993 -IGNoaWM= 47994 -IHByZXNlcnZpbmc= 47995 -KGV4ZWM= 47996 -IEludmVzdGlnYXRpb24= 47997 -CUdQSU8= 47998 -IHJpZ29yb3Vz 47999 -aWpv 48000 -PW51bQ== 48001 -IHRvb2xTdHJpcA== 48002 -KXNldA== 48003 -KyIm 48004 -IEFjY2VsZXI= 48005 -IGRldmVsb3BtZW50YWw= 48006 -aXNwb3NhYmxl 48007 -IGZsYXdlZA== 48008 -cmVuZQ== 48009 -VXBkYXRpbmc= 48010 -IHdhdGNoZG9n 48011 -IGRlbm9taW5hdG9y 48012 -IHN1YnVyYnM= 48013 -IC4uLik= 48014 -IGNvbnZpY3Rpb25z 48015 -Y2xvc3VyZQ== 48016 -LklQ 48017 -IHRyYW5zbGF0ZXM= 48018 -LnN3dA== 48019 -LlRyYWNl 48020 -IG1ldHRyZQ== 48021 -LmlzRW5hYmxlZA== 48022 -IEVmZmVjdGl2ZQ== 48023 -LnRvSW50 48024 -IGVuY2hhbnQ= 48025 -IHN0dW5uZWQ= 48026 -IHBvaQ== 48027 -L2NvZGU= 48028 -YWRt 48029 -LmRhdGFiaW5kaW5n 48030 -IExvcmVt 48031 -X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXw== 48032 -IGxlZGdlcg== 48033 -IGNhcmE= 48034 -IEdpcg== 48035 -IHdhaXRz 48036 -VW5v 48037 -IGN3ZA== 48038 -6L6R 48039 -IFRSZXN1bHQ= 48040 -IHJlam8= 48041 -IGVtaXR0ZWQ= 48042 -IFdlc3RtaW5zdGVy 48043 -5LiA5Liq 48044 -bmVr 48045 -X1Rpcw== 48046 -IGVuYWN0 48047 -CXdpdGg= 48048 -b3JnaWE= 48049 -IGp1ZQ== 48050 -UGVyZm9ybQ== 48051 -U1BBVEg= 48052 -LnRvcGlj 48053 -IERhdGVu 48054 -4bqn 48055 -IHNpdGlv 48056 -X01N 48057 -IlNv 48058 -YmlhbA== 48059 -IHNjb3BlZA== 48060 -UmVxdWlyZXM= 48061 -IFRPVEFM 48062 -IENoYW5jZWxsb3I= 48063 -KGNvbnRlbnRz 48064 -IHN0ZWFsdGg= 48065 -ZGV2aWNlcw== 48066 -LXBhc3M= 48067 -aWxpaA== 48068 -IE1hbGNvbG0= 48069 -IERlcG90 48070 -IGNvbmZpZ3Vy 48071 -YXVzc2lhbg== 48072 -X2NvbnN0cmFpbnQ= 48073 -0LLQtdGC 48074 -R1JB 48075 -IFJhdGVz 48076 -LmRhdGFHcmlkVmlld1RleHRCb3hDb2x1bW4= 48077 -IE5vYmVs 48078 -aXRpY3M= 48079 -IGlnbm9yYW50 48080 -IFJlcG9ydGVy 48081 -IEVib2xh 48082 -IFNob2Nr 48083 -X3JlbGF0aW9u 48084 -IE5pbmph 48085 -KWM= 48086 -IHRpY2tlcg== 48087 -LmlzQ2hlY2tlZA== 48088 -IFN1cHBsaWVycw== 48089 -IFJhcGlk 48090 -TGV2ZWxz 48091 -4oKs4oSi 48092 -CXF1ZXVl 48093 -IGNob3A= 48094 -IFVuaXg= 48095 -cmVqZWN0 48096 -LWNhbGVuZGFy 48097 -KHNvcnQ= 48098 -w6huZQ== 48099 -ZXJjaWNpbw== 48100 -IGhlY3Q= 48101 -Q0FMTFRZUEU= 48102 -cm91cG9u 48103 -IHJlbnRhbHM= 48104 -YXV0aG9ycw== 48105 -e25hbWU= 48106 -IEZJRk8= 48107 -IGxhc3Nlbg== 48108 -IE5vdXM= 48109 -IHNuYXBwZWQ= 48110 -IGZlcnRpbGl0eQ== 48111 -ImxvZw== 48112 -Y2xpY2tlZA== 48113 -IHBsYW50aW5n 48114 -IGdi 48115 -L291dHB1dA== 48116 -UEVBVA== 48117 -IGNhdGVnb3JpYQ== 48118 -IGJhY2g= 48119 -UHJvZmVzc29y 48120 -aW50aA== 48121 -Il0NCg== 48122 -UmVjb3JkZXI= 48123 -c2VyZGU= 48124 -IFRyYW5zbWlzc2lvbg== 48125 -dHJhZA== 48126 -IHR1cmJv 48127 -X1ZFUlRFWA== 48128 -XEV2ZW50 48129 -aWx2ZXI= 48130 -IGJvZGlseQ== 48131 -IFNvdXJjZXM= 48132 -IGtpbGxpbmdz 48133 -LnhyVGFibGVDZWxs 48134 -IGZvbGRlZA== 48135 -L2xlZ2Fs 48136 -dW5lcg== 48137 -IFJpZmxl 48138 -IE1JREk= 48139 -X1NlbGVjdGVkSW5kZXhDaGFuZ2Vk 48140 -LlNpemVUeXBl 48141 -IFdlYlNvY2tldA== 48142 -IHNlbGVjY2lvbg== 48143 -U2FuZA== 48144 -b3Ryb3M= 48145 -IGVudmlzaW9u 48146 -L2V0Yw== 48147 -IE1lbGlzc2E= 48148 -U3BvdA== 48149 -0L3QvtC1 48150 -X0FSTQ== 48151 -QXR0ZW1wdA== 48152 -IEJJ 48153 -44GU 48154 -IERV 48155 -IGJhY2tsYXNo 48156 -c3RyaWRl 48157 -L2NsYXNzZXM= 48158 -IHRleHRDb2xvcg== 48159 -X3N0YWZm 48160 -b2JsaW4= 48161 -YWdlbnRh 48162 -LmNvbGxlY3Rpb25z 48163 -aWxsYWdl 48164 -Jw0KDQo= 48165 -ZmxhdHRlbg== 48166 -X3NhbGVz 48167 -X01BU1RFUg== 48168 -VFc= 48169 -X2Rh 48170 -UGl0Y2g= 48171 -cGhpZXM= 48172 -IHpvbWJpZXM= 48173 -IFZFUlk= 48174 -IFBoYXJtYWN5 48175 -IHByb2dyZXNzQmFy 48176 -IGhhc2h0YWc= 48177 -U2lkZWJhcg== 48178 -QHN0b3A= 48179 -KHBj 48180 -0L7Qu9C2 48181 -TUFLRQ== 48182 -IENvcm9u 48183 -IGt2aW5uZXI= 48184 -IE1haWQ= 48185 -Ym9i 48186 -LnRpdGxlTGFiZWw= 48187 -IHN1Y2Nlc3Nlcw== 48188 -IERlbW9jcmFjeQ== 48189 -IFN1cmdlcnk= 48190 -IGNvdWdhcg== 48191 -IGN1cnNv 48192 -IGxvcm8= 48193 -aXN0ZW5jeQ== 48194 -U2VuaW9y 48195 -w6Zr 48196 -IEFBQQ== 48197 -IEJPT0s= 48198 -0LrQvg== 48199 -V1NUUg== 48200 -ICovLAo= 48201 -b3lhbA== 48202 -LnZlY3Rvcg== 48203 -IFNQRUM= 48204 -U1NG 48205 -IGNvbXB1bHM= 48206 -IEFwcGVhbHM= 48207 -IFdpbnN0b24= 48208 -IE1vY2tpdG8= 48209 -Y29udHJpYg== 48210 -LmF2YWlsYWJsZQ== 48211 -ZW50aXR5TWFuYWdlcg== 48212 -YXJpYXM= 48213 -X3NhbGU= 48214 -X3Jz 48215 -IGRlY29kaW5n 48216 -IGxvY2F0b3I= 48217 -b2xpdGg= 48218 -IGtvbA== 48219 -IGFzY2lp 48220 -IFJ1dA== 48221 -L2ludGVyZmFjZQ== 48222 -CQkJCQkJICAg 48223 -IE51bWVy 48224 -LmZsaXA= 48225 -LWRlbA== 48226 -IGJvbHN0ZXI= 48227 -b25vbWlj 48228 -IHpt 48229 -TEc= 48230 -RmluZEJ5 48231 -IGFkYXB0aXZl 48232 -bG9v 48233 -IHZ1ZQ== 48234 -KHJldmVyc2U= 48235 -X2NhbnZhcw== 48236 -LnJvbGVz 48237 -aWZpY2Fkbw== 48238 -dmVuaWVudA== 48239 -IkFz 48240 -IEVudHI= 48241 -YWxpZ25lZA== 48242 -IGJlcmVpdHM= 48243 -Ly8vCgo= 48244 -Lmd3dA== 48245 -LmVtcGxveWVl 48246 -X2NsaQ== 48247 -IGFudGljaXBhdGU= 48248 -6ZmQ 48249 -IHBpaw== 48250 -IG11c2hyb29tcw== 48251 -KHR0 48252 -IG9tYQ== 48253 -IFNhbmNoZXo= 48254 -X2dvb2dsZQ== 48255 -LlZhbGlk 48256 -IEZpbGVOYW1l 48257 -aXZhdGl2ZQ== 48258 -a2Vk 48259 -LXdhcg== 48260 -IG1hdHVyaXR5 48261 -0LjQtA== 48262 -IG1pbmVy 48263 -UmVkdWNlcnM= 48264 -IExhdExuZw== 48265 -X1NURA== 48266 -RGlnaXRz 48267 -Q2FsYw== 48268 -LXVwbG9hZA== 48269 -IGhhbmRpYw== 48270 -4Li14LmI 48271 -ZWdyYXRlZA== 48272 -IFNUTQ== 48273 -Q2xpZW50cw== 48274 -IFR1cmJv 48275 -U1lOQw== 48276 -IHBob3RvZ3JhcGhlcnM= 48277 -Lk91dA== 48278 -LmNoYXJhY3Rlcg== 48279 -QlVJTEQ= 48280 -LnVubG9jaw== 48281 -IGFyaXNlcw== 48282 -IENvbW1hbmRz 48283 -KCIiKTsNCg== 48284 -X0ZPUkU= 48285 -Oycs 48286 -KyIn 48287 -LkltYWdlcw== 48288 -Iil7 48289 -IE1leWVy 48290 -IG5lZ2F0aXZlbHk= 48291 -IERMTA== 48292 -IGV4ZQ== 48293 -IGRlZmljaWVuY3k= 48294 -IHdpbGRseQ== 48295 -LXN3aXRjaA== 48296 -Y29uc3RydWN0aW9u 48297 -IGV4Y2VwdGlvbmFsbHk= 48298 -IExpeg== 48299 -L2phdmE= 48300 -IHRoZWlycw== 48301 -IENvbnRlbXBvcmFyeQ== 48302 -bGlz 48303 -LmZpbGxSZWN0 48304 -IE5GQw== 48305 -IHJlaGU= 48306 -KG51bWJlcnM= 48307 -IHJhc3Rlcg== 48308 -IGZpZ3VyaW5n 48309 -IHNob3dj 48310 -IEppbGw= 48311 -IGFyY2FkZQ== 48312 -IENvbnN0cnVjdHM= 48313 -bWRs 48314 -KCd8 48315 -IGlkZW50aWZpZXJz 48316 -IHN0ZWxsYXI= 48317 -KENvbm5lY3Rpb24= 48318 -ICJ7ew== 48319 -eW9y 48320 -KG15c3FsaQ== 48321 -IGRvdmU= 48322 -T2ZCaXJ0aA== 48323 -LmRpc2Nvbm5lY3Q= 48324 -X2hp 48325 -IHp3aXNjaGVu 48326 -IEdydW5k 48327 -aXJvcw== 48328 -X0FycmF5 48329 -Lm9uY2xpY2s= 48330 -YW5zb20= 48331 -QW5zd2Vycw== 48332 -CXJlbW92ZQ== 48333 -RmE= 48334 -IGh1cnJ5 48335 -LWluZg== 48336 -IGdldENsYXNz 48337 -IFJlZ3VsYXRpb24= 48338 -IEZMQUdT 48339 -bWlzYw== 48340 -S2Vu 48341 -X2hlYWRpbmc= 48342 -R0h6 48343 -LWVudHJ5 48344 -IGJpb2dyYXBoeQ== 48345 -U2ln 48346 -LW1m 48347 -V2F0Y2hlcg== 48348 -4oCcQQ== 48349 -fXB4 48350 -IHNwaWN5 48351 -X3Nx 48352 -TG9zdA== 48353 -KHRyYWNr 48354 -0LDQu9C4 48355 -RGVzY2VuZGluZw== 48356 -PGJpdHM= 48357 -cXVpbmU= 48358 -IEFkdm9j 48359 -X1NO 48360 -IEhhbm5haA== 48361 -UE9Q 48362 -IGVtaXR0ZXI= 48363 -IGN5bg== 48364 -IENBRA== 48365 -Pyku 48366 -L3NldA== 48367 -IFNpc3Rlcg== 48368 -IEVuZHBvaW50 48369 -IG1lbm9y 48370 -IGludGVycA== 48371 -cms= 48372 -aWRsZQ== 48373 -IG91dGZpdHM= 48374 -LnZlcnRleA== 48375 -IGNsaWM= 48376 -QVJFTg== 48377 -IHBvc3R1cmU= 48378 -IE9wcG9ydHVuaXR5 48379 -dng= 48380 -IEZvcmJlcw== 48381 -LkRpcmVjdGlvbg== 48382 -IHJlc2lkZQ== 48383 -IHJlbWVtYmVyaW5n 48384 -bmVzdHk= 48385 -QXV0b3Jlc2l6aW5n 48386 -cHJvdmlkZXJz 48387 -IEFI 48388 -IGh1cnRpbmc= 48389 -IExpbHk= 48390 -ZXZhbHVhdGU= 48391 -bGlqaw== 48392 -cGFwZXJz 48393 -IFNtYXNo 48394 -IExBU1Q= 48395 -IHdlbGxz 48396 -d2FzaGVy 48397 -X1JPTEU= 48398 -IERhbmdlcg== 48399 -Kigo 48400 -X3JlcG9zaXRvcnk= 48401 -IFJlc29sdmU= 48402 -IFJvb21z 48403 -X1JH 48404 -IFFU 48405 -b29w 48406 -IEhlYXA= 48407 -IHNsb3dpbmc= 48408 -IGdyYXR1aXRl 48409 -X2NhdGFsb2c= 48410 -IHBvbHlub21pYWw= 48411 -THk= 48412 -cGNz 48413 -Rm94 48414 -IEN5cg== 48415 -IGRpbWlu 48416 -L21vbnRo 48417 -U2FsdA== 48418 -IGhpbmQ= 48419 -LlBFUg== 48420 -Rm9ydW0= 48421 -Y2Vu 48422 -X3BvbA== 48423 -7Zi4 48424 -IGluc2Vy 48425 -KH4= 48426 -QHRlc3Q= 48427 -IEdvbGRtYW4= 48428 -IHVwbG9hZGluZw== 48429 -RmM= 48430 -IGtvbW1lcg== 48431 -IG1pdHQ= 48432 -X2xvZ2dlZA== 48433 -IGJ1Y2tz 48434 -LWxheWVy 48435 -KX07Cg== 48436 -IE9N 48437 -IHZlZw== 48438 -Y29sb3Vy 48439 -INC+0LHRig== 48440 -U3RkU3RyaW5n 48441 -X3F1ZQ== 48442 -IFRpYW4= 48443 -IHNwZWNpYWxpemU= 48444 -0LjQvw== 48445 -INC60Ls= 48446 -dHJpYWw= 48447 -LWVkZ2U= 48448 -IG1hcnM= 48449 -T0dMRQ== 48450 -IGVtcGF0aHk= 48451 -IEJvbQ== 48452 -IGNvbGxpc2lvbnM= 48453 -IGNhcnRl 48454 -IFRlaWw= 48455 -IE1QTA== 48456 -IHBvcm7DtA== 48457 -IGFpcmxpbmVz 48458 -QXdz 48459 -TnM= 48460 -IFNwYXdu 48461 -KHVzZQ== 48462 -6buY6K6k 48463 -IHlhY2M= 48464 -c3Rvcg== 48465 -IGNvbmZlc3M= 48466 -IHBlcXVl 48467 -cmFnZQ== 48468 -PyIK 48469 -L2RhdGF0YWJsZXM= 48470 -IFNob3dlcg== 48471 -X18v 48472 -IGNyeXN0YWxz 48473 -IGJ1c2Nhcg== 48474 -IEhhdXM= 48475 -aXphw6fDo28= 48476 -X2VudGl0aWVz 48477 -lYw= 48478 -mow= 48479 -eGNj 48480 -dmlydA== 48481 -LWNoZXZyb24= 48482 -KFJlc3VsdA== 48483 -Y2FrZQ== 48484 -Q09NRQ== 48485 -IHByb2hpYml0 48486 -IENoZXNz 48487 -IGJlYXVjb3Vw 48488 -INGH0YLQvg== 48489 -UlVO 48490 -IElL 48491 -w7PFgg== 48492 -X1VwZGF0ZQ== 48493 -IHNsZWVr 48494 -IFNwZWNpZnk= 48495 -X2NyZWRlbnRpYWxz 48496 -xZ90 48497 -IFVzZXJOYW1l 48498 -CVZhbHVl 48499 -IGFycmF5TGlzdA== 48500 -IGV4Y2hhbmdlZA== 48501 -aXBzaXM= 48502 -LnJlbGF0ZWQ= 48503 -IFNlaXRl 48504 -X0JBUg== 48505 -IExlbQ== 48506 -IFdBVENI 48507 -IENsaWVudHM= 48508 -IC4q 48509 -IEVhcmw= 48510 -LXJlcG9ydA== 48511 -IGZvcmVpZ25lcnM= 48512 -IHN0cmVuZ3RoZW5pbmc= 48513 -CURlc2NyaXB0aW9u 48514 -KGdv 48515 -LnRvb2xiYXI= 48516 -IGNhbGN1bGF0ZXM= 48517 -CXNvdXJjZQ== 48518 -IGN6YXM= 48519 -IHJlY2w= 48520 -YWJv 48521 -IGxvY2FsaG9zdA== 48522 -IF57Cg== 48523 -LlBvcA== 48524 -IERlc2lnbmVk 48525 -XEFic3RyYWN0 48526 -SG9sZA== 48527 -IEd1aWRlbGluZXM= 48528 -aXBsaW5l 48529 -IGNhY2hpbmc= 48530 -LlJlYWRlcg== 48531 -X2V4dGVybmFs 48532 -LnN0cnB0aW1l 48533 -IFdlZWtlbmQ= 48534 -LU1hcg== 48535 -IEJlaQ== 48536 -IHsqfQ== 48537 -IFJ1ZA== 48538 -IGV4cGxvcg== 48539 -IEJvdWxldmFyZA== 48540 -Q2FzaA== 48541 -IHByZXBhcmVz 48542 -IHNlcmlhbGl6YXRpb24= 48543 -ZXdhdGVy 48544 -IGFkYw== 48545 -OgoKCgoKCg== 48546 -UmVmZXI= 48547 -IHNjYW5uZWQ= 48548 -fX0KCg== 48549 -IEZ1bA== 48550 -IHRvdXJpbmc= 48551 -44OD44Kv 48552 -Pigo 48553 -c3VydmV5 48554 -IO2Y 48555 -Li4uJykK 48556 -IERpdmlkZXI= 48557 -b3Ns 48558 -X0NBTkNFTA== 48559 -X3ByZXBhcmU= 48560 -c3Rpbg== 48561 -IEhlYXRo 48562 -LlByaW1hcnlLZXk= 48563 -IOKGkA== 48564 -IExvY2FsRGF0ZVRpbWU= 48565 -IGNvb3BlcmF0aXZl 48566 -TGVhcm5pbmc= 48567 -LmVucXVldWU= 48568 -IGdvb2c= 48569 -IFJlZ3Jlc3Npb24= 48570 -aW1hdGVz 48571 -IHZveWV1cg== 48572 -IERyaW5r 48573 -cGx1Zw== 48574 -IGxlbmRlcg== 48575 -bWFuYQ== 48576 -IHBlcnNvbm5lcw== 48577 -eXBzZQ== 48578 -IHVubGluaw== 48579 -IFJhdmVucw== 48580 -IGh1cmQ= 48581 -IHBlcmlvZGljYWxseQ== 48582 -QVJHUw== 48583 -IEdI 48584 -Y2hhcmFjdGVycw== 48585 -Li4uIgoK 48586 -LWVzdGFibGlzaA== 48587 -IGRu 48588 -KGNvbmRpdGlvbg== 48589 -IEdyYXZpdHk= 48590 -IGVzdGFz 48591 -X2ZvY3Vz 48592 -Q3JlYXR1cmU= 48593 -KHNpdGU= 48594 -IGNhcnI= 48595 -IFJM 48596 -IFJJ 48597 -IE1vdG8= 48598 -QVNG 48599 -IEx1Y2tpbHk= 48600 -CVJvdXRl 48601 -IGVudHJvcHk= 48602 -KCIsIg== 48603 -Q29sbGVjdA== 48604 -KGNvbnRhY3Q= 48605 -IEZsb3JlbmNl 48606 -IHByZW1pdW1z 48607 -IGxpZmVjeWNsZQ== 48608 -IGJhbnM= 48609 -eGVm 48610 -V2ViS2l0 48611 -IEZsb2F0aW5n 48612 -IGNvc2E= 48613 -U3BlY2lmaWM= 48614 -IExvYW5z 48615 -YnJlYWQ= 48616 -IGRlc2NyaXB0b3Jz 48617 -IHs6Lg== 48618 -VEhSRUFE 48619 -IFRyZW50 48620 -IHNjb3A= 48621 -UUE= 48622 -IEFudGFy 48623 -cGVs 48624 -X2RpZmZlcmVuY2U= 48625 -X2NoYW5nZXM= 48626 -KC4uLik= 48627 -IFJvdGF0aW9u 48628 -IExHUEw= 48629 -IEpVU1Q= 48630 -KFRhc2s= 48631 -X3N1YnNldA== 48632 -IFRSQU5T 48633 -5Yqb 48634 -IFNjb3V0 48635 -LXBvcHVw 48636 -IHNtb2tlZA== 48637 -X0NsYXNz 48638 -IHR1cm5vdmVy 48639 -YnJha2s= 48640 -IFJvY2t5 48641 -dGFz 48642 -LlJlZ3VsYXJFeHByZXNzaW9ucw== 48643 -IEVsbGlvdHQ= 48644 -IFNwaW5uZXI= 48645 -RFVDVElPTg== 48646 -IGxpYnJl 48647 -IG1vbHRv 48648 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 48649 -IEZUUA== 48650 -bXBlZw== 48651 -KGZlYXR1cmVz 48652 -IGJhbGQ= 48653 -IFZpZA== 48654 -IHNob3V0aW5n 48655 -TGludA== 48656 -IHNvY2tldHM= 48657 -IHByb3c= 48658 -IG5vdXZlbGxl 48659 -aXNjYXJk 48660 -IFNwb25zb3I= 48661 -IGNvbnN1bHRh 48662 -KSkpOw== 48663 -SW5kaWFu 48664 -IFJhc3BiZXJyeQ== 48665 -IHRlYW1tYXRl 48666 -IEpXVA== 48667 -IEdoYW5h 48668 -IGNha2Vz 48669 -cHJpbWVy 48670 -Zm9ybWE= 48671 -ZXJnYXJ0ZW4= 48672 -X01hbmFnZXI= 48673 -IHByZXNlYXNvbg== 48674 -R0FNRQ== 48675 -fCI= 48676 -IEJyb2Nr 48677 -IG9jY3VweQ== 48678 -IGRlY29yYXRpb25z 48679 -w6FuZA== 48680 -IGNvdA== 48681 -IHBhcmFu 48682 -RGlzaw== 48683 -cmVtYWlu 48684 -Pj8= 48685 -U3Ryb25n 48686 -IGZyYW5jZQ== 48687 -IEVyYQ== 48688 -LWNy 48689 -LkJ1ZmZlcmVkUmVhZGVy 48690 -IFBhcmFkaXNl 48691 -IFZBVA== 48692 -IEFuZGVycw== 48693 -IGxpbWI= 48694 -YW1wb28= 48695 -IGltcGVyYXRpdmU= 48696 -VVRJTElUWQ== 48697 -IFJlY29nbml0aW9u 48698 -IHJhZ2F6emU= 48699 -IHBvcHM= 48700 -eXByZXNz 48701 -IGVtYmFyZ28= 48702 -Ly97Cg== 48703 -IHN5bGw= 48704 -UFRS 48705 -5a2Y5Zyo 48706 -IGRpZG50 48707 -TWFpbGVy 48708 -IGFjYWRlbWljcw== 48709 -IEZyYXVlbg== 48710 -bmVpZGVy 48711 -LXJlbA== 48712 -IHJhaW5ib3c= 48713 -KElu 48714 -IHNsaWNlZA== 48715 -PT09PT09PT09PT09PQo= 48716 -KHNlbmQ= 48717 -TlNNdXRhYmxlRGljdGlvbmFyeQ== 48718 -dm9z 48719 -KHBhY2thZ2U= 48720 -IG9yZGluYW5jZQ== 48721 -dmlld2Vy 48722 -IFNhbnRvcw== 48723 -LXNlbGxpbmc= 48724 -IGdvdg== 48725 -ZXR0bGU= 48726 -IGZvdW5kZXJz 48727 -IHdha2luZw== 48728 -c2xhc2hlcw== 48729 -LXBvdW5k 48730 -cmVjaHQ= 48731 -2KfYqg== 48732 -Lm9uQ2xpY2s= 48733 -IG5vcmQ= 48734 -c3TDpG5k 48735 -X3doZW4= 48736 -VVRFUlM= 48737 -aWNj 48738 -IGNhcHN1bGU= 48739 -IFdpZA== 48740 -TWFyYw== 48741 -4Li4 48742 -cm9yZWQ= 48743 -VUdF 48744 -TE9VRA== 48745 -IEF1ZGl0 48746 -aXBpZW50cw== 48747 -b3BpYW4= 48748 -IFN1ZQ== 48749 -IHd1cmRlbg== 48750 -LkhlbHBlcnM= 48751 -IGZhY3Rpb25z 48752 -W25w 48753 -LXRoYW4= 48754 -IHJlY28= 48755 -IGthcw== 48756 -IGNtZHM= 48757 -L25ldHdvcms= 48758 -eGJm 48759 -Z2V0Q29sb3I= 48760 -IGJpYXNlZA== 48761 -IExhaw== 48762 -RGF0YXM= 48763 -dmVudHM= 48764 -IOuy 48765 -X1BT 48766 -LlZhbGlkYXRl 48767 -SW52b2tlcg== 48768 -IG5ldWVu 48769 -IGp1dmVuaWxl 48770 -VklTSU9O 48771 -IGRldm90ZQ== 48772 -IGxpbmhh 48773 -IGRpc2NvdW50ZWQ= 48774 -XENvbmZpZw== 48775 -IHdvcnRod2hpbGU= 48776 -IHNraW5ueQ== 48777 -IENvdXJzZXM= 48778 -bGV5cw== 48779 -IE1vcnRnYWdl 48780 -S2V2aW4= 48781 -IGFubm91bmNlcw== 48782 -XSkq 48783 -cmVzZXJ2YXRpb24= 48784 -IOaVsA== 48785 -IHByZWp1ZGljZQ== 48786 -IFN0cmluZ0NvbXBhcmlzb24= 48787 -IGJlYXJk 48788 -LXdpbg== 48789 -IFPDo28= 48790 -CW1z 48791 -amFs 48792 -IEVhcm4= 48793 -X3BvcnRz 48794 -IE5vbWJyZQ== 48795 -X0NPUg== 48796 -IEJVSUxE 48797 -LnNvdW5k 48798 -WWVsbG93 48799 -IGxpbmViYWNrZXI= 48800 -IGNoYXJpdGFibGU= 48801 -anVn 48802 -X05PTk5VTEw= 48803 -IERlbnRhbA== 48804 -Ij4kew== 48805 -CW1hdGNo 48806 -UnVzc2lhbg== 48807 -IHZlcnNjaA== 48808 -IHBpbm5lZA== 48809 -IGFkb3B0aW5n 48810 -T3B0aW9uc01lbnU= 48811 -UGFn 48812 -IHBhaXJpbmc= 48813 -IHRyZWFk 48814 -ZXJjaXNlcw== 48815 -IFNwcmVhZA== 48816 -KWk= 48817 -IEJBRA== 48818 -X3Rm 48819 -VUlJbWFnZVZpZXc= 48820 -cG9wdWxhdGU= 48821 -YmFi 48822 -IM+D 48823 -Wysr 48824 -IG9waW9pZA== 48825 -ICMjCg== 48826 -ZHR5cGU= 48827 -IFN0YXJ0cw== 48828 -KCcvJyk= 48829 -IHBlcnNvbmFscw== 48830 -LW1hcmtldA== 48831 -IHJlZHVuZGFudA== 48832 -IEVzc2VudGlhbA== 48833 -IHNjcmFweQ== 48834 -INC40Lw= 48835 -YWNs 48836 -IGNyZWFy 48837 -IEJlbmQ= 48838 -IHJlbGlldmU= 48839 -LXJvb20= 48840 -d2lmZQ== 48841 -IHbDoA== 48842 -IFFQb2ludA== 48843 -IHF1YXNp 48844 -IG1ldGhvZE5hbWU= 48845 -XHhj 48846 -IFBlcnU= 48847 -L1RoZQ== 48848 -Lm9ybQ== 48849 -IHZpeg== 48850 -L3BkZg== 48851 -TG9jYXRlZA== 48852 -IGNvbmZyb250YXRpb24= 48853 -IENoYW1waW9uc2hpcHM= 48854 -IGh5cGVydA== 48855 -IGRq 48856 -IFVzZXJJbmZv 48857 -IOWIm+W7ug== 48858 -XHhi 48859 -KHNpbQ== 48860 -ID09Cg== 48861 -IHN0YWdpbmc= 48862 -IGRyYXN0aWNhbGx5 48863 -5a2m 48864 -bG9yZHM= 48865 -Lmxlc3M= 48866 -0LLQtdC00LjRgtC1 48867 -IEJ1Y2tldA== 48868 -IE1hbQ== 48869 -LnRlcm0= 48870 -X3Bp 48871 -Y3p5 48872 -LnB1Yg== 48873 -cHJlY2lv 48874 -IFZpcnQ= 48875 -IHJvbWFu 48876 -aXRhdA== 48877 -TGV4 48878 -X2luZm9z 48879 -xLA= 48880 -Lm90aGVy 48881 -VkVMTw== 48882 -IHBvbmRlcg== 48883 -IGhhbm5v 48884 -KFBhZ2U= 48885 -ZG9p 48886 -IHBvbGl0ZQ== 48887 -IHByb2dyYW1tZXI= 48888 -RGllcw== 48889 -JGQ= 48890 -IHJlcGxpY2F0aW9u 48891 -YWRkQ29sdW1u 48892 -ZnJpY2Fu 48893 -IGxlbmc= 48894 -YmVlcg== 48895 -b2l0 48896 -IHdhc3Rpbmc= 48897 -eWxpbQ== 48898 -bWVhc3VyZQ== 48899 -TmVn 48900 -IHBhcnRpZQ== 48901 -LmNvbnNvbGU= 48902 -IEd1aW5lYQ== 48903 -VEVM 48904 -X2ZhY3Q= 48905 -LmNodW5r 48906 -IGxlbnQ= 48907 -IGFsbGVy 48908 -IOCklQ== 48909 -X2lkbGU= 48910 -IGFkbWlzc2lvbnM= 48911 -SlNPTkFycmF5 48912 -IHZpYnJhdGlvbg== 48913 -LmhlbHBlcnM= 48914 -5aSW 48915 -IGhlbg== 48916 -am9obg== 48917 -IOyDnQ== 48918 -IGp1ZGdlbWVudA== 48919 -IGdlZW4= 48920 -dGVycmE= 48921 -Xns= 48922 -IEl6 48923 -IGPDog== 48924 -aW5zdGFuY2Vz 48925 -IHRocmVhdGVucw== 48926 -IG3DvHNzZW4= 48927 -S2luZE9mQ2xhc3M= 48928 -IHN0b3J5dGVsbGluZw== 48929 -X2RlbW8= 48930 -cmlhcw== 48931 -UHJpdmFjeQ== 48932 -aGlmdA== 48933 -IFlp 48934 -ZXNvcg== 48935 -7ZWg 48936 -ZW5zaXRpdml0eQ== 48937 -LldyaXRlcg== 48938 -4LiC 48939 -RGlzdHJpY3Q= 48940 -LmdldEpTT05PYmplY3Q= 48941 -SW1wcm8= 48942 -KGdldFJlc291cmNlcw== 48943 -IFNQRUxM 48944 -cm9kdWNl 48945 -IHNsb3dlZA== 48946 -IGxpbmV3aWR0aA== 48947 -IGhvbmVzdHk= 48948 -IENvb3Jk 48949 -IEZvcms= 48950 -IERpc3BhdGNoUXVldWU= 48951 -IENsaWZm 48952 -IFdpcmluZw== 48953 -X1RJTUVTVEFNUA== 48954 -b2xsYWg= 48955 -YXZvaWQ= 48956 -KytdOwo= 48957 -c2VtYW50aWM= 48958 -LWNzcw== 48959 -IHZldG8= 48960 -IE1lcnI= 48961 -IGxlZ2lzbGF0b3Jz 48962 -Q0VFREVE 48963 -IHF1ZXN0aW9ubmFpcmU= 48964 -IFBpbGxz 48965 -Q2FsY3VsYXRl 48966 -KGNvcmU= 48967 -J2U= 48968 -IGRpc2xpa2U= 48969 -IFByZWZlcmVuY2Vz 48970 -X0VYVEVSTkFM 48971 -6LCD 48972 -IGRvZGdl 48973 -5pyN5Yqh 48974 -Lm5hbWVz 48975 -LmRyYXdJbWFnZQ== 48976 -X3Byb20= 48977 -dWNrbGFuZA== 48978 -IDwkPg== 48979 -xLF6 48980 -L3NpdGU= 48981 -6aG5 48982 -cm9waGU= 48983 -IGNvbXBlbGxlZA== 48984 -IGxhcHRvcHM= 48985 -IHVuaQ== 48986 -Q0xPU0U= 48987 -IGNhc3VhbHRpZXM= 48988 -IFVuaWZvcm0= 48989 -VGVybWluYWw= 48990 -LiIsIg== 48991 -REFU 48992 -KFRyZWVOb2Rl 48993 -IEdhbmRoaQ== 48994 -KHN0bXQ= 48995 -QVhC 48996 -Kk0= 48997 -IHVtYnJlbGxh 48998 -YW5pbWFs 48999 -IGdycGM= 49000 -IHdoZXJlYnk= 49001 -IGZsb2F0cw== 49002 -CWFyZw== 49003 -IGRiZw== 49004 -IGV4Y2VlZGluZw== 49005 -RXZlbnRUeXBl 49006 -LlNhdmVDaGFuZ2VzQXN5bmM= 49007 -IHt7ew== 49008 -IG93ZWQ= 49009 -YWhyZW5oZWl0 49010 -IOyn 49011 -IGVxdWlwbw== 49012 -dXJhaQ== 49013 -IGlkb2w= 49014 -XSIpCg== 49015 -X21ham9y 49016 -IGVudGlyZXR5 49017 -aW5nZXJwcmludA== 49018 -w6dvcw== 49019 -L2FjY291bnQ= 49020 -CXJpZ2h0 49021 -dXJzb3M= 49022 -IEVEVA== 49023 -X0lOU0VSVA== 49024 -IHNoaW5pbmc= 49025 -IDw6 49026 -RWRnZUluc2V0cw== 49027 -IGNvbG9uaWVz 49028 -LklN 49029 -CSAJ 49030 -Uk9BRA== 49031 -Q0NDQw== 49032 -cGxhY2luZw== 49033 -IGdldEFjdGl2aXR5 49034 -ZW1hY3M= 49035 -JyUo 49036 -LmNsaWNrZWQ= 49037 -IFRoZW0= 49038 -aXNpYQ== 49039 -QnVzY2Fy 49040 -LnJlbmFtZQ== 49041 -IG9hdGg= 49042 -IGFmdGVyd2FyZA== 49043 -IFVGTw== 49044 -QVBT 49045 -IEphY2tzb252aWxsZQ== 49046 -LnNvbWU= 49047 -Q29uZmlybWVk 49048 -LnNjYW4= 49049 -aWdJbnRlZ2Vy 49050 -RGVjb3JhdG9y 49051 -c2hpZWxk 49052 -cmVzc2l2ZQ== 49053 -LmRpZA== 49054 -6K+36L6T5YWl 49055 -IHNodXR0ZXI= 49056 -RGFt 49057 -IHBhcmVudGluZw== 49058 -ZXllZA== 49059 -JGl0ZW0= 49060 -LWRldmVsb3A= 49061 -IGV4dHJhY3Rz 49062 -IGRlY2VudHJhbGl6ZWQ= 49063 -IEVsc2E= 49064 -X3NwaW4= 49065 -XSkr 49066 -LWluaXRpYWw= 49067 -IG11bHRpdHVkZQ== 49068 -IHNlbnNvcnk= 49069 -IE1PREVM 49070 -IHNhZmVndWFyZA== 49071 -7Lk= 49072 -IGh1bnRlcnM= 49073 -IFRpbnk= 49074 -SU5P 49075 -ZGVjb3JhdGU= 49076 -IE5vU3VjaA== 49077 -SG8= 49078 -KFJlc3BvbnNl 49079 -IHJ1bGVy 49080 -CXNob3J0 49081 -IGNhc3Rlcg== 49082 -IGNsaWVudElk 49083 -IHBkYg== 49084 -64+E 49085 -aXRpYw== 49086 -IEdhbWVTdGF0ZQ== 49087 -IG5ld0l0ZW0= 49088 -KQoKCgoKCg== 49089 -b3Vpcw== 49090 -bm9j 49091 -LkJMQUNL 49092 -X1ZFQ1RPUg== 49093 -LS0tLS0tLS0tLTwv 49094 -IGV4YW1pbmVz 49095 -CWJsb2Nr 49096 -IGFkZG9u 49097 -IHN1cnZleWVk 49098 -IExpc3RlbmVy 49099 -IGZyb250aWVy 49100 -IGxhY2tlZA== 49101 -SlVTVA== 49102 -INGN0YI= 49103 -IHRpbnQ= 49104 -IE15c3Rlcnk= 49105 -ZGF0ZVRpbWU= 49106 -IFR1dG9yaWFs 49107 -IGZ1bGxOYW1l 49108 -IERyYWdvbnM= 49109 -X0ZJTEVT 49110 -IFByaW50V3JpdGVy 49111 -IGJlZXQ= 49112 -IExhZGllcw== 49113 -X3RpcA== 49114 -IEphaHJl 49115 -b3JhbWE= 49116 -IGluc3VsYXRpb24= 49117 -KEVudmlyb25tZW50 49118 -X2FzdA== 49119 -YmVyZ2Vy 49120 -bGVuYQ== 49121 -b2dlbmVvdXM= 49122 -X01PTlRI 49123 -LXByZXNlbnQ= 49124 -IGZyYW1ld29ya3M= 49125 -UVE= 49126 -UEhQRXhjZWw= 49127 -IGNvdW50ZG93bg== 49128 -IEZX 49129 -KGNsdXN0ZXI= 49130 -OmM= 49131 -IG9raHR0cA== 49132 -b2JzZXJ2ZQ== 49133 -W3BsYXllcg== 49134 -Lmhl 49135 -IFBhbmFtYQ== 49136 -QXVzdHJhbGlh 49137 -IG91bmNlcw== 49138 -IGFnZ3Jlc3NpdmVseQ== 49139 -IHdhcm5z 49140 -IGN1c3RvbWl6YXRpb24= 49141 -X1F1ZXJ5 49142 -d2lz 49143 -IGludmFs 49144 -QUZG 49145 -KGNhbWVyYQ== 49146 -V2ly 49147 -IG5lZ290aWF0aW9u 49148 -CU8= 49149 -IHJlc3BlY3RmdWw= 49150 -IGRpYW1vbmRz 49151 -J2F2 49152 -YXBwcm94 49153 -L2Ry 49154 -IGdyYWJz 49155 -IGFjY29tcGFuaWVz 49156 -Y29uc3RyYWludA== 49157 -IHJleg== 49158 -KHJlZ2lvbg== 49159 -IGJhaXQ= 49160 -dGVybWluYXRl 49161 -IEJlbGdpYW4= 49162 -YXNzaXVt 49163 -IF0NCg== 49164 -U3lzdGVtcw== 49165 -b3VzZWRvd24= 49166 -LmJ1cw== 49167 -U2V0VmFsdWU= 49168 -IFByZXA= 49169 -IGNvbnZlbmllbnRseQ== 49170 -Lm1pZA== 49171 -Y2FzZWNtcA== 49172 -TnVtZXJv 49173 -ZGFpbHk= 49174 -IENvZGluZw== 49175 -KGRlc3RpbmF0aW9u 49176 -IyQ= 49177 -dWrEhQ== 49178 -IGVtZXJnZW5jZQ== 49179 -X3BhcmE= 49180 -X0lOQ0xVREU= 49181 -Izo= 49182 -IHJlY29nbml6aW5n 49183 -IGZ1Zw== 49184 -In19LAo= 49185 -IGJ1aWxkZXJz 49186 -IFRlcnJpdG9yeQ== 49187 -IGluaGVyZW50bHk= 49188 -IGRlcml2aW5n 49189 -LmV0aA== 49190 -IERpbm5lcg== 49191 -LnNldE9iamVjdE5hbWU= 49192 -IGNlbGVicmF0ZXM= 49193 -IHF1ZXVlcw== 49194 -IE1hcmtz 49195 -QUxURVI= 49196 -IERhcnQ= 49197 -cG9rZQ== 49198 -X0NIQU5HRUQ= 49199 -IHBhYXI= 49200 -bGllcw== 49201 -LnZvbGxleQ== 49202 -IE1lYW5pbmc= 49203 -IE9GRlNFVA== 49204 -ZW5zaW5n 49205 -IGZyw6Vu 49206 -LmxvY2FsU3RvcmFnZQ== 49207 -IOup 49208 -KHt9KTsK 49209 -ZGVjb2Rlcg== 49210 -IHJvdWxldHRl 49211 -IGRpc21hbnQ= 49212 -SXI= 49213 -IGluc3VyZw== 49214 -ICcnOgo= 49215 -LuKAnQo= 49216 -IGJydW5ldHRl 49217 -LmFzc2V0cw== 49218 -X05FVFdPUks= 49219 -4LiK 49220 -bnlt 49221 -X1NvdXJjZQ== 49222 -XFRlc3Rz 49223 -RXNjYXBl 49224 -Y3J5cHQ= 49225 -LlhNTA== 49226 -IHNvdW5kaW5n 49227 -b3Bjb2Rl 49228 -IGNsYXNzaWZ5 49229 -IGVtYmFycmFzc2Vk 49230 -IExPR0lO 49231 -IHJlc2lkdWU= 49232 -IE5FRUQ= 49233 -LmRlZXBFcXVhbA== 49234 -cGVyYw== 49235 -LWNhbA== 49236 -UmVkaXM= 49237 -VHJh 49238 -KF8p 49239 -YXNrZXRz 49240 -Z3JhZGF0aW9u 49241 -IGVuenltZQ== 49242 -IFN0ZXBoYW5pZQ== 49243 -LkludmFsaWQ= 49244 -J10/Pjwv 49245 -IGRpc3BsYWNlZA== 49246 -IGVsZW1lbnRvcw== 49247 -KGR1cmF0aW9u 49248 -cm93Q291bnQ= 49249 -IEZTdGFy 49250 -bGV0YQ== 49251 -L3BvcHBlcg== 49252 -IHN0YXRv 49253 -IHBlcmZvcm1lcg== 49254 -IGRpc2NpcGxpbmVz 49255 -IEZ1bGx5 49256 -aWN1bGFybHk= 49257 -IGVyc3Rlbg== 49258 -IFBvbHlnb24= 49259 -IGRpc2NpcGxlcw== 49260 -LmlzZGly 49261 -IHRlc3RpZnk= 49262 -X1NS 49263 -cHJpc2luZ2x5 49264 -IEdMaW50 49265 -IHdpcGVk 49266 -IGNhcnZlZA== 49267 -IERpc2g= 49268 -Lmhlcm9rdWFwcA== 49269 -c3RpdGlhbA== 49270 -IE1BVENI 49271 -Y2xhaXI= 49272 -IERheXRvbg== 49273 -LycpCg== 49274 -SURETEU= 49275 -IGluZnJh 49276 -IGxpdmVseQ== 49277 -IGRlcHM= 49278 -IFsuLi5d 49279 -CQkJCQkJCQkJCQkJCQkJCQk= 49280 -IExvbg== 49281 -RXh0cmFz 49282 -VHJhbnNpZW50 49283 -0LLQtdGA 49284 -L21vZHVsZQ== 49285 -IGVuZHVyYW5jZQ== 49286 -X3RleA== 49287 -ICJ+Lw== 49288 -X3lsYWJlbA== 49289 -IG9iZWQ= 49290 -L2dhbWU= 49291 -b3BzeQ== 49292 -IGZpcnN0bmFtZQ== 49293 -LmZvcmNl 49294 -IG1hcnQ= 49295 -XENsaWVudA== 49296 -IGxlZ2l0aW0= 49297 -LmZsYXR0ZW4= 49298 -Iics 49299 -b3NleHVhbA== 49300 -IGpvdXJz 49301 -TUg= 49302 -ZXhwaXJlcw== 49303 -IHN0eWw= 49304 -LmludGVydmFs 49305 -S25vd24= 49306 -IGZvbGxvd2Vy 49307 -IGRhbGxh 49308 -cGlyeQ== 49309 -X3NzbA== 49310 -aXNobGlzdA== 49311 -IFJleQ== 49312 -IHN1cGVybWFya2V0 49313 -T2J2aW91c2x5 49314 -LWVudGVy 49315 -IHByb2JhYmlsaXRpZXM= 49316 -IEhW 49317 -IENpbmVtYQ== 49318 -IGN0eXBlcw== 49319 -IEJDTQ== 49320 -X1RBQw== 49321 -O2E= 49322 -LmJ1dHRvbnM= 49323 -IHJldHJpZXZpbmc= 49324 -aWxhcml0eQ== 49325 -IHVuZGVydGFraW5n 49326 -CXN0YWNr 49327 -IGtlbA== 49328 -IFhlbg== 49329 -KHBoaQ== 49330 -IHRvdWdoZXI= 49331 -IFNlbGxlcg== 49332 -Y2Fwcw== 49333 -IEVtYmVy 49334 -IENoaW4= 49335 -IGxhdWdocw== 49336 -Q29udmVyc2lvbg== 49337 -Lmxpc3RlbmVy 49338 -JkI= 49339 -IHBhcmFkaWdt 49340 -IGp1bmN0aW9u 49341 -JC8sCg== 49342 -W28= 49343 -IENvbnNlcnZhdGl2ZXM= 49344 -z4A= 49345 -bGF0ZXM= 49346 -X0V4Y2VwdGlvbg== 49347 -IG1laWxsZXVy 49348 -IHN0cmFwcw== 49349 -cXVpc2l0ZXM= 49350 -CXNu 49351 -IG1hc3NhY3Jl 49352 -b3R0ZXM= 49353 -X2dyZWVu 49354 -VGl0bGVz 49355 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 49356 -IFJlZ3VsYXRpb25z 49357 -YXJs 49358 -X3Nob3J0Y29kZQ== 49359 -IERyYXdlcg== 49360 -IHBhcm9sZQ== 49361 -IHdpbGRlcm5lc3M= 49362 -aXNzb24= 49363 -IEFGVEVS 49364 -Q3JlZGVudGlhbA== 49365 -QmxvY2tpbmc= 49366 -IEhUQw== 49367 -U2lu 49368 -KGF1dGhvcg== 49369 -IGNvcnRleA== 49370 -Jyl7DQo= 49371 -77yJ77yM 49372 -IGR1bXBlZA== 49373 -IFNodXQ= 49374 -IEtleUV2ZW50 49375 -CVBsYXllcg== 49376 -LmdldFBsYXllcg== 49377 -IGlnbm9yZXM= 49378 -dG9nZ2xlQ2xhc3M= 49379 -IEV4Y2x1c2l2ZQ== 49380 -PigpOw== 49381 -LmdldFA= 49382 -YW55ZQ== 49383 -IG5ldXJvbg== 49384 -aWZvbGQ= 49385 -IEtub3du 49386 -Qml0Y29pbg== 49387 -QW55d2F5 49388 -YXlldHRl 49389 -ICdbJw== 49390 -w6BuaA== 49391 -bWdy 49392 -IGNvcnJlbGF0ZWQ= 49393 -IG5hdXNl 49394 -IG1lbnRhbGl0eQ== 49395 -aGFzTWFueQ== 49396 -IEZH 49397 -YW1waWU= 49398 -SVRV 49399 -RnM= 49400 -LlNw 49401 -X2JldHdlZW4= 49402 -RGVwZW5kZW5jaWVz 49403 -b3Vn 49404 -UGxhY2Vob2xkZXI= 49405 -PXRleHQ= 49406 -IE1hbmFnaW5n 49407 -b2NhbHlwc2U= 49408 -5YyX 49409 -X21hZw== 49410 -Zmxk 49411 -4pE= 49412 -Q0FN 49413 -IEhlbHBlcnM= 49414 -IGRvc3Q= 49415 -L291dA== 49416 -IGFzc2Fzc2luYXRpb24= 49417 -LmdldEltYWdl 49418 -IEtlbm55 49419 -LicpCgo= 49420 -KXsvLw== 49421 -IFJhbmdlcg== 49422 -IGdlaw== 49423 -IHNpbmNlcmU= 49424 -PFZhbHVl 49425 -IERPVA== 49426 -IFZpY3Rvcnk= 49427 -IGxlZ2VuZHM= 49428 -IHByaXNvbnM= 49429 -KGV4cHJlc3Npb24= 49430 -IFJhYmJpdA== 49431 -X3NlbnRlbmNl 49432 -IGJpdGVz 49433 -IG9uRmFpbHVyZQ== 49434 -IOKIiA== 49435 -S2lt 49436 -LmdlbmRlcg== 49437 -IM67 49438 -IFsu 49439 -Il0pOw== 49440 -bGFuZGluZw== 49441 -LWRpZ2l0 49442 -VEVNUA== 49443 -CWVudHJ5 49444 -IHN0cnRvaw== 49445 -IGRlc2NlbmRhbnRz 49446 -dW1ubw== 49447 -IGxlYW5pbmc= 49448 -IHNwZWNpZmljcw== 49449 -cW4= 49450 -IFNwYXJ0 49451 -IHBvcnI= 49452 -RURJQVRFSw== 49453 -IHNlcGVy 49454 -J2F1dA== 49455 -IFNURVA= 49456 -IEJvcmRlckxheW91dA== 49457 -IHJldHJvcw== 49458 -IFNhbHZhZG9y 49459 -IEVOR0lORQ== 49460 -eGRj 49461 -VHdlZXQ= 49462 -dms= 49463 -IOyy 49464 -XTw8 49465 -aGV0aWNz 49466 -Y29kaW5n 49467 -UmVhY2g= 49468 -LnJlcQ== 49469 -Z3VpZGU= 49470 -LnNjb3Bl 49471 -c2hpcnQ= 49472 -cm9nYXRl 49473 -U0VUVElORw== 49474 -IFByb3RlaW4= 49475 -IGVpbmc= 49476 -LkVNUFRZ 49477 -LmRm 49478 -IGNsZWFyZXI= 49479 -IGNyb3Nzb3Zlcg== 49480 -IFRveXM= 49481 -IGNvYXRlZA== 49482 -Lk1vbnRo 49483 -IEF0dGFjaA== 49484 -L3J1bg== 49485 -LnRhYnM= 49486 -IG9nc8Ol 49487 -QnJvd24= 49488 -LkRBVEU= 49489 -IGZvcw== 49490 -5a2X56ym 49491 -V29vZA== 49492 -LXRocmVl 49493 -aGVyaXRlZA== 49494 -IHJvcA== 49495 -KGFj 49496 -IGVtYm9kaW1lbnQ= 49497 -IEtlbm5ldGg= 49498 -IGNhbm5vbg== 49499 -IGJpZGRpbmc= 49500 -PElFbnVtZXJhYmxl 49501 -CXNldFRpbWVvdXQ= 49502 -X2RpZ2l0 49503 -IGVsaW1pbmFy 49504 -KG5l 49505 -YnVkZ2V0 49506 -Q1NJ 49507 -IOyVhA== 49508 -IEFTUA== 49509 -R3JvdXBJZA== 49510 -X0NPVU5URVI= 49511 -Y29uc3VsdA== 49512 -IGlmcmFtZQ== 49513 -bGVnZW4= 49514 -X0RFQ0xBUkU= 49515 -U2hhcnBlcg== 49516 -IEZyaWVuZGx5 49517 -dWxldA== 49518 -LWNvbW1hbmQ= 49519 -INCg 49520 -Y3ljbGVz 49521 -IFdhc3Rl 49522 -IHRhcHBlZA== 49523 -CUJ1ZmZlcg== 49524 -4oCUaW4= 49525 -IAogIAo= 49526 -IElkZWFs 49527 -IENhbmR5 49528 -X1N5bnRheA== 49529 -w6p0 49530 -7J2M 49531 -YWJvdmU= 49532 -IE5hemlz 49533 -IGZzdA== 49534 -c2Vpbg== 49535 -IGt1bm5lbg== 49536 -d2lr 49537 -IFNhdmluZw== 49538 -LmV4dGVuc2lvbnM= 49539 -IERlc2VyaWFsaXpl 49540 -b3VyZw== 49541 -LmF0dHJpYg== 49542 -77yaCgo= 49543 -IFdpbnM= 49544 -LmVxbA== 49545 -Unlhbg== 49546 -X2Fjaw== 49547 -T1VSQ0VT 49548 -IG9ucw== 49549 -Z3Jlc2U= 49550 -YWZpYQ== 49551 -TW9kZXJu 49552 -IGFkaGVyZQ== 49553 -IGJpb3M= 49554 -KGFjYw== 49555 -a2Jk 49556 -VGhyb3du 49557 -qeuLiOuLpA== 49558 -CUh0dHA= 49559 -CXhtbA== 49560 -RW5kRGF0ZQ== 49561 -KHBhcnNlZA== 49562 -LmdldGVudg== 49563 -cmVnaXN0cg== 49564 -bmVsbA== 49565 -aW9uYXJpbw== 49566 -LmlubmVyV2lkdGg= 49567 -cnRs 49568 -UFY= 49569 -X3BpZWNl 49570 -IERlcG9zaXQ= 49571 -eWVycw== 49572 -IE5TTnVtYmVy 49573 -IGdpbnQ= 49574 -ZW5zZW1ibGU= 49575 -IG5ld2NvbQ== 49576 -IFZpZXRuYW1lc2U= 49577 -X2hw 49578 -IGFjY3VzaW5n 49579 -IHF1aXM= 49580 -IGludmVzdGlnYXRvcg== 49581 -ZXNzZW50aWFs 49582 -IENY 49583 -LmZvck5hbWU= 49584 -ZGVmcw== 49585 -IGFuYWx5c2U= 49586 -X2FuaW1hdGlvbg== 49587 -IHRoYQ== 49588 -dGFib29sYQ== 49589 -IFRIQw== 49590 -w61jdWxv 49591 -IGdsb3dpbmc= 49592 -IGhvbm9ycw== 49593 -YnN0cmFjdA== 49594 -a3A= 49595 -SVRFUw== 49596 -ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM= 49597 -I2dldA== 49598 -L0Rlc2t0b3A= 49599 -CWdsbQ== 49600 -IHppbmM= 49601 -w6F0aWNh 49602 -IDw8Cg== 49603 -Vk1M 49604 -IFVubGltaXRlZA== 49605 -dnJl 49606 -LWJlZA== 49607 -X25vbmNl 49608 -IEdJ 49609 -dHJhdmVs 49610 -IGlzS2luZE9mQ2xhc3M= 49611 -IGFub255bWl0eQ== 49612 -RmlyZXN0b3Jl 49613 -IGVtYWlsZWQ= 49614 -X0ZMQVNI 49615 -IGbDpXI= 49616 -4piF4piF 49617 -IDpd 49618 -SHVt 49619 -LnJlc2VydmU= 49620 -w7xt 49621 -IGtvc3Rlbmxvc2U= 49622 -IFNDUA== 49623 -dXRhbg== 49624 -IEdvcmU= 49625 -IGNoYXRz 49626 -Lz4NCg== 49627 -LmdldFJlc291cmNlcw== 49628 -IGx1bXA= 49629 -X2NvbnN0cw== 49630 -KGV4dA== 49631 -CWRpcg== 49632 -4p0= 49633 -IHBhZGRpbmdUb3A= 49634 -IG9ic2Vzc2lvbg== 49635 -IGJhbm5pbmc= 49636 -IEFwcE1vZHVsZQ== 49637 -IHBhcnRpc2Fu 49638 -IGNhdGFsb2d1ZQ== 49639 -IG1pbm9ycw== 49640 -IHBpdGNoZXM= 49641 -d2VlcA== 49642 -IHVuZGVydGFrZQ== 49643 -IHRoZW1lZA== 49644 -YXVkaXQ= 49645 -LnNjcm9sbFRvcA== 49646 -IHJlcg== 49647 -IHN5bXB0b20= 49648 -IG9wZW5pbmdz 49649 -LmJsb2Nrcw== 49650 -b3Blbmlk 49651 -IGFzc2g= 49652 -LXNhdmU= 49653 -IFBpZw== 49654 -IHJlZ2Fpbg== 49655 -IGluaWNpYWw= 49656 -L2Zhdmljb24= 49657 -CWV4cA== 49658 -IHNwaWNlcw== 49659 -aXNrYQ== 49660 -Y2xhaW1z 49661 -bWFr 49662 -ZGVmaW5pdGlvbnM= 49663 -IGNvcnJlc3BvbmRlbnQ= 49664 -IENhbm5hYmlz 49665 -X18sCg== 49666 -IEx1Y2t5 49667 -IEdhdXNzaWFu 49668 -IE5lYXJseQ== 49669 -Q0FE 49670 -J11dCg== 49671 -IGFkZXF1YXRlbHk= 49672 -IFRJVExF 49673 -Y29uc3RpdHV0aW9uYWw= 49674 -LW1t 49675 -X292ZXJyaWRl 49676 -IGJsYXM= 49677 -LnJlYWR5U3RhdGU= 49678 -IHJlbWluaXM= 49679 -IHJlaW5mb3JjZWQ= 49680 -IENvbGxhYm9y 49681 -IGRlY29yYXRpbmc= 49682 -IGJhY2hlbG9y 49683 -RVJSVVBU 49684 -IHVwcmlnaHQ= 49685 -aXBhdGlvbg== 49686 -IE5vYmxl 49687 -IHZhbHVlRm9yS2V5 49688 -IHNldExvYWRpbmc= 49689 -Lklnbm9yZQ== 49690 -5YE= 49691 -R2xvYmFscw== 49692 -IE1lbnQ= 49693 -QVNTRVM= 49694 -IGxpbWJz 49695 -IEhVRA== 49696 -aW5jaQ== 49697 -Lml2 49698 -IFFNb2RlbEluZGV4 49699 -RnVzZQ== 49700 -IHBlZGFs 49701 -X0ZSRVE= 49702 -KHZlcmJvc2U= 49703 -IGxvbmdpdHVk 49704 -IENoYXJ0ZXI= 49705 -6re4 49706 -IGJ1bmRsZXM= 49707 -Lmlnbm9yZQ== 49708 -dW1ibw== 49709 -RU1B 49710 -Li4uLi4uLg== 49711 -c3g= 49712 -LkNhcmQ= 49713 -IGhldXRl 49714 -IHN0ZWVy 49715 -anVtbGFo 49716 -IHtf 49717 -X0NoZWNrZWQ= 49718 -IGZheA== 49719 -IEd1c3Q= 49720 -aXRjaGVucw== 49721 -ICkpCgo= 49722 -IHJlbWFya2FibHk= 49723 -L1hNTA== 49724 -LXJlbW92ZQ== 49725 -X2J0 49726 -IGluY3Vi 49727 -LnBhY2thZ2U= 49728 -LmN1cnJlbnRUaHJlYWQ= 49729 -IEhpZ2hsYW5kZXI= 49730 -LnNpZGU= 49731 -c3BsYXNo 49732 -IGljaQ== 49733 -PUQ= 49734 -IHB1Y2s= 49735 -IGJhbGxvdHM= 49736 -IGh1Z2VseQ== 49737 -Y29lZmY= 49738 -IHBEYXRh 49739 -LkNPTFVNTg== 49740 -IEhlYWxpbmc= 49741 -IG9yZGlu 49742 -ISks 49743 -ICcnLA0K 49744 -KG1k 49745 -IFNhc2s= 49746 -PHN0cm9uZw== 49747 -IHN1cnZpdm9y 49748 -LnNlcmllcw== 49749 -IGNhZmZlaW5l 49750 -IGAo 49751 -LlRSQUlMSU5H 49752 -X0lucHV0 49753 -KCJe 49754 -emQ= 49755 -Jik7Cg== 49756 -IFBpbmc= 49757 -IHZvdWNoZXI= 49758 -LnJhdGluZw== 49759 -LXNoaXJ0cw== 49760 -IFJldHJpZXZlcw== 49761 -LmFsaWJhYmE= 49762 -T3JhY2xl 49763 -X01PVg== 49764 -T2xkRGF0YQ== 49765 -IC8qDQo= 49766 -IGdib29sZWFu 49767 -ID0+DQo= 49768 -IHLDoQ== 49769 -IGJsdW50 49770 -IEltYWdlSWNvbg== 49771 -aWZpaw== 49772 -UlRD 49773 -IGZpYmVycw== 49774 -IHRvaWxl 49775 -LnNlbnQ= 49776 -IFB5UXQ= 49777 -JGFwcA== 49778 -IG1lZGlv 49779 -IGdyYW50aW5n 49780 -IHRzbGludA== 49781 -IE3Dtg== 49782 -KGZpZ3NpemU= 49783 -IGh1cnJpY2FuZQ== 49784 -IGxpZmVz 49785 -IMOE 49786 -cm9jZXNzaW5n 49787 -X3N0YW5kYXJk 49788 -LW9wdGlvbg== 49789 -JykpKQ== 49790 -IHZhY2FudA== 49791 -5bel 49792 -IEhvbGxvdw== 49793 -aGFuZGxlQ2hhbmdl 49794 -IGRpdmlkZXI= 49795 -IEVuZ2luZWVycw== 49796 -IHN2ZW5z 49797 -IGNvbXBsaWFudA== 49798 -dGFuZ2dhbA== 49799 -IENyZWRpdHM= 49800 -IEVtaXJhdGVz 49801 -UnVsZUNvbnRleHQ= 49802 -IHJlYWxpemF0aW9u 49803 -IGRpc3RyYWN0ZWQ= 49804 -XSs9 49805 -IGF1Z21lbnQ= 49806 -IER3 49807 -b3Rw 49808 -b3JyZW50 49809 -RWRpdGFy 49810 -LnN0b2Nr 49811 -U3R1ZHk= 49812 -cGVjdGlvbnM= 49813 -IEdhbWVNYW5hZ2Vy 49814 -PWN1dA== 49815 -IGZsb2Nr 49816 -IFJvbWFucw== 49817 -dGhlbQ== 49818 -LWhvcA== 49819 -IHNjcmVlbnNob3Rz 49820 -IC8qIQo= 49821 -IGNvbnZlcnNpb25z 49822 -IG5vcm1hbGl6YXRpb24= 49823 -KGNvbmZpZ3VyYXRpb24= 49824 -IGFlcm9z 49825 -X3NlY3VyaXR5 49826 -IScK 49827 -Qm9udXM= 49828 -IERSSVZFUg== 49829 -CURhdGU= 49830 -dGll 49831 -IFd5b21pbmc= 49832 -U3RhbmQ= 49833 -aXRyZQ== 49834 -IHNob3BwZXJz 49835 -IGRpc2FkdmFudGFnZQ== 49836 -IGxpa2luZw== 49837 -56yR 49838 -IHVuZGVyc3RhbmRhYmxl 49839 -U0VF 49840 -IGhveQ== 49841 -IG5pbmV0ZQ== 49842 -IGNvbmZlcg== 49843 -IG5vd3JhcA== 49844 -IFZlcm4= 49845 -LA0KDQo= 49846 -aW1lc3RlcA== 49847 -TGF5b3V0TWFuYWdlcg== 49848 -4Lc= 49849 -CXdhaXQ= 49850 -UExFVEVE 49851 -SmFwYW4= 49852 -IGluZHVjZQ== 49853 -IOWv 49854 -0L7Qt9Cy 49855 -X0VORFBPSU5U 49856 -Lmhvcml6b250YWw= 49857 -IGFjY2VsZXJhdGVk 49858 -cmltb24= 49859 -SVZFUw== 49860 -VHJhbnNhY3Rpb25z 49861 -TGVhbg== 49862 -IFNPVVI= 49863 -d2hldGhlcg== 49864 -eWc= 49865 -IG9pZA== 49866 -IEVudGl0eU1hbmFnZXI= 49867 -T1VOVFJZ 49868 -IGZpbGE= 49869 -T0xVTU5T 49870 -SU5VRQ== 49871 -IEFuY2hvcg== 49872 -VFJBTg== 49873 -d29v 49874 -YmxvY2txdW90ZQ== 49875 -IE51cnNl 49876 -IENhcnA= 49877 -IHJlZGVlbQ== 49878 -LnRyeQ== 49879 -IEpQ 49880 -IHRpbWVzdGFtcHM= 49881 -ID8+Ij48 49882 -IFJFTU9WRQ== 49883 -IFN0YXJidWNrcw== 49884 -UmVhbGx5 49885 -IGZsb29kZWQ= 49886 -LkNhbGxiYWNr 49887 -RHJvcERvd24= 49888 -aXBybw== 49889 -IHRlbmRlZA== 49890 -bHRl 49891 -IHByb3BvcnRpb25z 49892 -LXRl 49893 -IFJlbmE= 49894 -bGljYXRl 49895 -Zm9yY2Vz 49896 -LmV4dHJh 49897 -LmF1dGhlbnRpY2F0ZQ== 49898 -0LLQvtC0 49899 -obA= 49900 -IGZvckNvbnRyb2xFdmVudHM= 49901 -IHNlbmhh 49902 -IGtlaW4= 49903 -IG1pbmlzdA== 49904 -IFByZWZlcmVuY2U= 49905 -IFRlbGVncmFwaA== 49906 -0YPQvw== 49907 -c3RycG9z 49908 -IGlsbG5lc3Nlcw== 49909 -IHBpZ3M= 49910 -IGdldEludGVudA== 49911 -U29s 49912 -IMKh 49913 -KGNwdQ== 49914 -W3Byb3A= 49915 -c2NyZWVucw== 49916 -Jyk7Pz4= 49917 -IEFjdHM= 49918 -IHN0cmR1cA== 49919 -IGF2ZXJhZ2Vz 49920 -YW5hbA== 49921 -IENhc3VhbA== 49922 -R3JvdXBCb3g= 49923 -IEhhbmRib29r 49924 -L2NvbW1lbnRz 49925 -IG51bWJlcmVk 49926 -IGJyb2FkY2FzdGluZw== 49927 -55uR 49928 -Lm5hdGl2ZUVsZW1lbnQ= 49929 -Lm11 49930 -IHVwZGF0ZWRBdA== 49931 -IERvZXNu 49932 -LkFD 49933 -LmNvbGw= 49934 -IHJlY29yZGVy 49935 -X3NoYQ== 49936 -Qmc= 49937 -Ymls 49938 -IGJvbHRz 49939 -IOes 49940 -IGltcG9zaW5n 49941 -IEluZm9ybWF0aW9uZW4= 49942 -X2ZsYXNoZGF0YQ== 49943 -ZWNvbm9taWM= 49944 -UmVtYXJr 49945 -dWNhcw== 49946 -IE9mZmljZXJz 49947 -IFRFUg== 49948 -V2Fsaw== 49949 -IG1lcmNhZG8= 49950 -X2dlbmVyYXRl 49951 -SFk= 49952 -Q2FsbGluZw== 49953 -c25hcA== 49954 -c2NyaXB0SWQ= 49955 -Lm9wZXJhdGlvbg== 49956 -IEZsYW1l 49957 -bGluZXNz 49958 -IHJlbnRlZA== 49959 -X3RvZ2dsZQ== 49960 -LWNoYW5naW5n 49961 -IFRZ 49962 -J3V0aWw= 49963 -RUVQ 49964 -IGdyYXBocWw= 49965 -IFVuaQ== 49966 -IGltcHVsc2U= 49967 -LkJhc2lj 49968 -IGVuZXJnaWVz 49969 -TUFSWQ== 49970 -IE1hcmNlbA== 49971 -IG1vcnRhbA== 49972 -IGZyZXM= 49973 -bWVucw== 49974 -bW90aW9u 49975 -IHNhbXBsZWQ= 49976 -4oCcVGhhdA== 49977 -aWRheQ== 49978 -cXVpcG1lbnQ= 49979 -Z2V0SW50 49980 -IEFic29sdXRl 49981 -LCci 49982 -dW5lZA== 49983 -LnNoYXJl 49984 -IH0pKA== 49985 -bW1t 49986 -IFJpc2luZw== 49987 -5Lu7 49988 -IHVuZW1wbG95ZWQ= 49989 -eGZh 49990 -LmZvbGxvdw== 49991 -CQkJCSAgICAgIA== 49992 -c2x0 49993 -LlBob25l 49994 -IGtuaXZlcw== 49995 -IGV2ZQ== 49996 -b25DbGljaw== 49997 -XSkpDQo= 49998 -IFdpdG5lc3M= 49999 -CU5T 50000 -IEVPUw== 50001 -IFN0ZWZhbg== 50002 -IFByaWVzdA== 50003 -4oCUd2hpY2g= 50004 -R2V0U3RyaW5n 50005 -LkJ5 50006 -IHVwc3RhaXJz 50007 -IGRldHJpbWVudA== 50008 -YnJva2Vu 50009 -ZW1icm8= 50010 -IG5pY290aW5l 50011 -aWxpb24= 50012 -IGFzdG9uaXNoaW5n 50013 -X2FmZg== 50014 -IExlc3Nvbg== 50015 -IGFjY2lkZW50YWw= 50016 -b2Rvcg== 50017 -IGRlY2ly 50018 -IG5ld05hbWU= 50019 -Ky4= 50020 -55u4 50021 -aWdzbGlzdA== 50022 -IEdpdGh1Yg== 50023 -IHN1Y2Nlc3NpdmU= 50024 -cmFjaWFs 50025 -IGVudmlyb24= 50026 -6aqM6K+B 50027 -IHJlZGlyZWN0ZWQ= 50028 -VE9UQUw= 50029 -IGdyYWJiaW5n 50030 -IExhbmNl 50031 -IGZvcmZl 50032 -X0NC 50033 -5b6u 50034 -RWxhcHNlZA== 50035 -X3dheQ== 50036 -KERpYWxvZ0ludGVyZmFjZQ== 50037 -X21lYXN1cmU= 50038 -eGJi 50039 -RG9n 50040 -RGVwYXJ0 50041 -LXNyYw== 50042 -cmVzb2x2ZXI= 50043 -d2l0aHN0YW5kaW5n 50044 -X3NoZWxs 50045 -IExhc3ROYW1l 50046 -IEF2aWF0aW9u 50047 -IGJlZ2lubmVy 50048 -KCIlLg== 50049 -KHRvb2w= 50050 -INC90L7Qsg== 50051 -OmluaXQ= 50052 -KEFQSQ== 50053 -IE1vcnJpc29u 50054 -dnRDb2xvcg== 50055 -IHN0YXBsZQ== 50056 -L0lORk8= 50057 -IHN1cGVybmF0dXJhbA== 50058 -IHN0ZWFr 50059 -dGltZWxpbmU= 50060 -enpsZQ== 50061 -ImAKCg== 50062 -U2Vjb25kYXJ5 50063 -IE5lcGFs 50064 -LlN0cmluZ1V0aWxz 50065 -IGFkYW0= 50066 -ICguLi4= 50067 -IHN1YnN0aXR1dGlvbg== 50068 -IGJvYXJkaW5n 50069 -IEtleXdvcmQ= 50070 -IEFzc2F1bHQ= 50071 -ZGJjVGVtcGxhdGU= 50072 -IG9yZGVySWQ= 50073 -KGVuZ2luZQ== 50074 -LmFzc2VydFRoYXQ= 50075 -IFZlbnVz 50076 -IGhvbWljaWRl 50077 -IEF2YWw= 50078 -IGd1dHRlcg== 50079 -IFN1cHBvcnRlZA== 50080 -L3BhcnQ= 50081 -IGFjY2xhaW1lZA== 50082 -SGlzdG9y 50083 -IG1lc2Vz 50084 -w7xiZXI= 50085 -IFJlbmV3 50086 -IGdyYXM= 50087 -IEVr 50088 -IGluZmlsZQ== 50089 -aW5keQ== 50090 -Lm11c2lj 50091 -LlNjcm9sbA== 50092 -IEFnZXM= 50093 -IE5hcnV0bw== 50094 -IEdhdGhlcg== 50095 -IGNvbmZpcm1pbmc= 50096 -PSgi 50097 -IHBpdGNoZWQ= 50098 -b2xleQ== 50099 -RnJhbmNl 50100 -Kyci 50101 -JHRvdGFs 50102 -IG9uZGU= 50103 -IGRpdGNo 50104 -X3NpZ21h 50105 -IGNvbnRpbnVpdHk= 50106 -cmV3YXJk 50107 -LWxvYWQ= 50108 -IHByb2Nlc28= 50109 -TG9ja2Vk 50110 -c3Rhdw== 50111 -IHNwaW5hbA== 50112 -bGF6eQ== 50113 -IT09 50114 -amVzdA== 50115 -IGR1bg== 50116 -IFJvZGdlcnM= 50117 -CWdyaWQ= 50118 -IGxvZ29z 50119 -IEJlbmdhbA== 50120 -LnN1cGVy 50121 -UHJvdmlkZXM= 50122 -IG51dHJpZW50 50123 -LlRpbWVzdGFtcA== 50124 -SVpBVElPTg== 50125 -5YaM 50126 -IGZhdHM= 50127 -IFh4eA== 50128 -Y3RpY2E= 50129 -VGFyZ2V0cw== 50130 -IGNvbnRvdXJz 50131 -IHJlb3JkZXJlZA== 50132 -OkFycmF5 50133 -IHRvbGVyYXRl 50134 -Vmly 50135 -IHRlcnJpYmx5 50136 -IGJyaWNrcw== 50137 -KCZf 50138 -aGI= 50139 -UG9ydGFs 50140 -IEJyZWFk 50141 -LndoaWNo 50142 -wq10 50143 -YXNJbnN0YW5jZU9m 50144 -IGpvYmplY3Q= 50145 -CWxlbmd0aA== 50146 -X01U 50147 -OyI+DQo= 50148 -X0VYSVNU 50149 -IG1hdGVybmFs 50150 -UkVM 50151 -IOqyveyasA== 50152 -aGVl 50153 -IGxheW91dHM= 50154 -IExhcA== 50155 -YWlzeQ== 50156 -IHN0dW1ibGVk 50157 -IFVJRw== 50158 -IFNjbw== 50159 -IGltcGFpcmVk 50160 -UkVTU0VE 50161 -IGFidXNlcw== 50162 -VkY= 50163 -QVJC 50164 -Lk5BTUU= 50165 -cmNo 50166 -cHJpbWly 50167 -X2NvbXBsZXRlZA== 50168 -IHBlbm55 50169 -Q2hyb21l 50170 -KGJlZ2lu 50171 -ZXJuZW4= 50172 -LWNoZWNrYm94 50173 -UGxhaW5PbGREYXRh 50174 -IExQQw== 50175 -cmFkZQ== 50176 -c3Bpcg== 50177 -IGNvbmNlaXZlZA== 50178 -VGlwcw== 50179 -IElvVA== 50180 -IEdhbg== 50181 -6IGU 50182 -IGJpYXNlcw== 50183 -IGNvbnN1bHRhbnRz 50184 -cGxlZA== 50185 -X2h0 50186 -YXNzb2NpYXRlZA== 50187 -XSwKCg== 50188 -IGRlbGlnaHRmdWw= 50189 -INGC0LXQug== 50190 -SGVsdmV0aWNh 50191 -KGxvYWQ= 50192 -LWV4cGFuZA== 50193 -X1dJREdFVA== 50194 -dG9h 50195 -IEFrdA== 50196 -IG9tbg== 50197 -IGNsYXVzZXM= 50198 -SW50ZWw= 50199 -Ki99Cg== 50200 -X3JlZ2lzdHJhdGlvbg== 50201 -IG9sZFZhbHVl 50202 -IHJlc3RvcmluZw== 50203 -IHVucmVhbA== 50204 -T1ZFUg== 50205 -CQoJCgkK 50206 -QVRT 50207 -X3Byb2Jl 50208 -IGRpdmlzb3I= 50209 -LnVwZGF0ZUR5bmFtaWM= 50210 -5bmz 50211 -UHJvZHVjZXM= 50212 -c3RhbXA= 50213 -Lmpib3Nz 50214 -CXRhc2s= 50215 -ISg6 50216 -IHBzeWNoaWM= 50217 -QGNsYXNz 50218 -TWFydGlu 50219 -IFBhc3NlZA== 50220 -Y2xhcmF0aW9ucw== 50221 -aGVs 50222 -0LDRhw== 50223 -CWNvcHk= 50224 -LWJpbg== 50225 -emFu 50226 -aWdyYW0= 50227 -4Ka+4KY= 50228 -KHNpZw== 50229 -IENhdmFs 50230 -XyMj 50231 -ICU9 50232 -b3V0bGluZWQ= 50233 -IEFjaWQ= 50234 -IHVucHJlZGljdGFibGU= 50235 -LWRhc2hib2FyZA== 50236 -SGV4U3RyaW5n 50237 -K2M= 50238 -LlB1YmxpYw== 50239 -4bqp 50240 -IGNvbnZleW9y 50241 -IEVC 50242 -IHNlbGVjdHM= 50243 -IGtub2NraW5n 50244 -IENlYw== 50245 -SUJVVEVT 50246 -b3dhxIc= 50247 -Z2F0c2J5 50248 -KnY= 50249 -ZW50cm9weQ== 50250 -IGRpc3BhdGNoZWQ= 50251 -IGNhbWVs 50252 -IFNhdHVybg== 50253 -IG92ZXJ3ZWlnaHQ= 50254 -KHBob25l 50255 -cGFyYWJsZQ== 50256 -JUI= 50257 -X3ZlY3RvcnM= 50258 -IGJyZXdpbmc= 50259 -IFRr 50260 -IERvd25sb2Fkcw== 50261 -IFNhdmVk 50262 -LlByaWNl 50263 -IGN1cnZlZA== 50264 -IFBhcmVudGhvb2Q= 50265 -6LY= 50266 -LnBubA== 50267 -cGxldGVseQ== 50268 -LkRheQ== 50269 -IGFkdmVydGlzZXJz 50270 -IGVqZWM= 50271 -IHByemVk 50272 -668= 50273 -ISc7Cg== 50274 -IEt1c2g= 50275 -IFRBQg== 50276 -IHF1ZXN0cw== 50277 -IGNvaW5jaWRlbmNl 50278 -dW1taWVz 50279 -IEthc2htaXI= 50280 -IEV0aGljcw== 50281 -X2dyb3d0aA== 50282 -IGFrdGl2 50283 -IGdyb3VwaW5n 50284 -5aKe 50285 -X3RydXRo 50286 -5ZCs 50287 -dG9kb3M= 50288 -aXNldA== 50289 -VGV4Q29vcmQ= 50290 -w6R0dA== 50291 -IFp1cg== 50292 -cm95cw== 50293 -X01BR0lD 50294 -IGJyZXdlcnk= 50295 -KFN0YXRl 50296 -IFNNQUxM 50297 -IFBsYW50cw== 50298 -aXRiYXJ0 50299 -ZWFjaGVy 50300 -IEFkZWxhaWRl 50301 -THU= 50302 -IGZpY2s= 50303 -dW5kbGVz 50304 -X2xvYWRlZA== 50305 -0LjQtQ== 50306 -UG9sbA== 50307 -cml0aWM= 50308 -RUxZ 50309 -ICsn 50310 -IFByb2Zlc3Npb24= 50311 -IHN0YW1wcw== 50312 -IFNldw== 50313 -c2Nyb2xsVmlldw== 50314 -IGNvbW11bmlzdA== 50315 -L3Byb2JsZW1z 50316 -fQ0KDQoNCg0K 50317 -LG8= 50318 -IHVkcA== 50319 -IG9iZXNl 50320 -YXBwcm92ZQ== 50321 -YW5jZWxsYXRpb24= 50322 -X0dhbWU= 50323 -IEhhc2h0YWJsZQ== 50324 -YWRhcHRpdmVTdHlsZXM= 50325 -IHBvc3Nlc3Nlcw== 50326 -Lm1hdGNoZXI= 50327 -ZnVuY3Rpb25hbA== 50328 -TXJz 50329 -CXNhdmU= 50330 -IERiVHlwZQ== 50331 -IGtlbg== 50332 -Z2V0Q29udGV4dA== 50333 -IG1hbnM= 50334 -KHJlbA== 50335 -IEJyb3RoZXJob29k 50336 -KWAK 50337 -6Kej 50338 -LkluZm9ybWF0aW9u 50339 -T3V0T2ZSYW5nZUV4Y2VwdGlvbg== 50340 -IFNlaw== 50341 -Q2Fz 50342 -IGJsb2dnZXJz 50343 -RWl0aGVy 50344 -KCIiIg== 50345 -IHBpbmNo 50346 -IGNvYXJzZQ== 50347 -KXA= 50348 -IFB1bHNl 50349 -IGxlYXJudA== 50350 -IGRlbnRpc3Q= 50351 -IG9uY2hhbmdl 50352 -IGRpcmVjdGl2ZXM= 50353 -KGFjdGlvbnM= 50354 -bnlkZXI= 50355 -IFNoaXI= 50356 -VHJhaXQ= 50357 -X2RlcA== 50358 -IFBFVA== 50359 -IFJFUA== 50360 -LkFwcFNldHRpbmdz 50361 -Y3VhZG9y 50362 -aWRlbmF2 50363 -IGVudmk= 50364 -IHNsYW1tZWQ= 50365 -IFNob290 50366 -IGRhdGVGb3JtYXQ= 50367 -LmpvZGE= 50368 -dmV5cw== 50369 -ICkuCgo= 50370 -IGNhcmVn 50371 -IFBhcmFsbGVs 50372 -X3RyYW5zbGF0aW9u 50373 -LmZ1bmN0aW9ucw== 50374 -Lm9icw== 50375 -UnVudGltZUV4Y2VwdGlvbg== 50376 -W109 50377 -b3ZlcnZpZXc= 50378 -IFNjaGw= 50379 -IG5vaXN5 50380 -IE9uUHJvcGVydHlDaGFuZ2Vk 50381 -U2VuZGluZw== 50382 -IHVuZmFtaWxpYXI= 50383 -VXBvbg== 50384 -IFByaW50cw== 50385 -LnR5cA== 50386 -IGZsZWVpbmc= 50387 -CW1vdmU= 50388 -KFVu 50389 -IHFy 50390 -15w= 50391 -X2JldGE= 50392 -IHNraWVz 50393 -CW1l 50394 -V05E 50395 -IHN0aWNrZXJz 50396 -Ymxhcw== 50397 -IGluc2VydHM= 50398 -IHZlcnNlcw== 50399 -IERldw== 50400 -IHRhbmdpYmxl 50401 -IGhlY2hv 50402 -UE9M 50403 -IHRlYXJkb3du 50404 -b21uaWE= 50405 -SUJF 50406 -LmNvdmVy 50407 -X3N0cmF0ZWd5 50408 -Xi0= 50409 -c2V0UG9zaXRpb24= 50410 -dWFsZQ== 50411 -U2lnbmVk 50412 -IGlmYWNl 50413 -YXNlbGluZQ== 50414 -LnNldFRpbWU= 50415 -IE1pbmVyYWw= 50416 -IEZpZ2h0aW5n 50417 -c2tpbnM= 50418 -IGRpc2NyaW1pbg== 50419 -IGRhbnNr 50420 -IFByaW5jZXRvbg== 50421 -YWNpc3Q= 50422 -ICgpKTsK 50423 -dHJhY2tz 50424 -aW1vbmlhbA== 50425 -YWRlY2ltYWw= 50426 -RVBST00= 50427 -dWdnbGU= 50428 -Lk5vdGlmaWNhdGlvbg== 50429 -JG1haWw= 50430 -Y2FudGlkYWQ= 50431 -IEp1bmc= 50432 -IHNlZWtlcnM= 50433 -IHBsYXVzaWJsZQ== 50434 -dGllcg== 50435 -0LXQtg== 50436 -IHJhcHBlcg== 50437 -IE1hbmE= 50438 -IEh0dHBTdGF0dXNDb2Rl 50439 -IGJ1cm50 50440 -bG9zZXM= 50441 -IEZvdG8= 50442 -IEpzb25PYmplY3Q= 50443 -SW5zdGFncmFt 50444 -IHN5c2NhbGw= 50445 -IHJlYWxpdGllcw== 50446 -IE1BVExBQg== 50447 -Ol57Cg== 50448 -VEVSTQ== 50449 -IENiZA== 50450 -IFBhcmFncmFwaA== 50451 -IHRyYXbDqXM= 50452 -IGNvbnN0cnVjdGluZw== 50453 -IHN3YWw= 50454 -IHBpZ2U= 50455 -TExMTA== 50456 -LWV4aXN0aW5n 50457 -R2V0cw== 50458 -IG1lbHRlZA== 50459 -IG1pdGlnYXRl 50460 -SGVu 50461 -IGht 50462 -aW1hcw== 50463 -IEFv 50464 -IFBlcmV6 50465 -IERBTA== 50466 -IOuLpA== 50467 -IGRpdmlz 50468 -U3Rvcnlib2FyZFNlZ3Vl 50469 -IE1vZGlmeQ== 50470 -IMOcYmVy 50471 -X09WRVJSSURF 50472 -LnBlbQ== 50473 -dW50b3M= 50474 -IGVzcGHDsQ== 50475 -IHs/ 50476 -IFBBWQ== 50477 -X2lwdg== 50478 -IEZ1cnk= 50479 -X18uX18= 50480 -ZWxvdw== 50481 -LWNlbnRlcmVk 50482 -Y2hlY2tz 50483 -X1JlZw== 50484 -LUphdmFkb2M= 50485 -CWxvYWQ= 50486 -IExpa2V3aXNl 50487 -2KfZhQ== 50488 -VU5F 50489 -LnNlbQ== 50490 -eGNi 50491 -IENhdmU= 50492 -X3NsZWVw 50493 -IHNpbGVudGx5 50494 -IEV4dHJlbWU= 50495 -LlRvVXBwZXI= 50496 -CUNIRUNL 50497 -IGN1ZQ== 50498 -IFFCeXRlQXJyYXk= 50499 -IGNvcnJ1cHRlZA== 50500 -IETDqQ== 50501 -IGltcGVk 50502 -R2V0TmFtZQ== 50503 -IGluYWNjdXJhdGU= 50504 -IHNvYmVy 50505 -0LXQtQ== 50506 -IGJhcmNvZGU= 50507 -LS0pewo= 50508 -aW5raQ== 50509 -IMOpcA== 50510 -IGRyaQ== 50511 -IEFMVA== 50512 -Pj4+Pj4+Pj4= 50513 -b250YQ== 50514 -W0w= 50515 -IGludGVyZXM= 50516 -dmVydGluZw== 50517 -IGRpYWdub3N0aWNz 50518 -cGRldg== 50519 -6Kk= 50520 -IEludGVncmF0ZWQ= 50521 -KS4n 50522 -X2dj 50523 -JHRleHQ= 50524 -LmdhbWVz 50525 -IFRlcnJh 50526 -J1Jl 50527 -LnRyYW5zZmVy 50528 -X0ZJRk8= 50529 -Z2V0TW9kZWw= 50530 -IGJsYW5k 50531 -IENvbGVtYW4= 50532 -IHByaW1lcw== 50533 -IOaI 50534 -IGNyb3NzZXM= 50535 -bms= 50536 -R0lORw== 50537 -ICde 50538 -IEJsb2I= 50539 -IGludGVyY291cnNl 50540 -IEJsdmQ= 50541 -IHdlaWdocw== 50542 -X3JlZ3VsYXI= 50543 -IFBlcnRo 50544 -IHNlcGFyYXRpbmc= 50545 -IGJpbGxlZA== 50546 -LnRhYkNvbnRyb2w= 50547 -IHB1cHBldA== 50548 -IHV0aWxpemF0aW9u 50549 -IOKWoA== 50550 -IHN1Y2Nlcw== 50551 -IGxhbXBz 50552 -X3Byb2o= 50553 -RXJpYw== 50554 -IHJlbm92YXRpb24= 50555 -IEZhbWlsaWVz 50556 -IEJpdHM= 50557 -cGFydGlhbHM= 50558 -LU1lbg== 50559 -c29sdXRpb24= 50560 -IGR3YXJm 50561 -LklOVEVHRVI= 50562 -IExPQ0s= 50563 -LmN0 50564 -IGV4Y2VycHQ= 50565 -IFBpeA== 50566 -IEZpcnN0TmFtZQ== 50567 -QU5URUQ= 50568 -IEFkbWly 50569 -LWhlbHA= 50570 -UHJpb3I= 50571 -IEFsaWdu 50572 -LklOU1RBTkNF 50573 -TGluZUVkaXQ= 50574 -KCcvOg== 50575 -IGluZXQ= 50576 -b2R1cw== 50577 -LnBrbA== 50578 -IEtZ 50579 -dXBlcnQ= 50580 -IG5lcnZlcw== 50581 -X2dyYWRpZW50 50582 -fScsJw== 50583 -X3VucmVm 50584 -IHNhdHVyYXRlZA== 50585 -IENvbm5lY3RlZA== 50586 -IEZO 50587 -RVhJVA== 50588 -IHRlbGVwb3J0 50589 -IGF2YWl0 50590 -UGFnZVJvdXRl 50591 -IGRpdm9yY2Vk 50592 -KGxhbmc= 50593 -ZnN0 50594 -IFR5cg== 50595 -IG1lc3Nlbmdlcg== 50596 -aWZzdHJlYW0= 50597 -WFM= 50598 -IEJhbmtpbmc= 50599 -IGluZmVjdGlvdXM= 50600 -IE1vbnM= 50601 -X0xPT1A= 50602 -IHp1csO8Y2s= 50603 -IG9idGVuZXI= 50604 -L3JlcG9z 50605 -VmVs 50606 -YWNybw== 50607 -IHVzZXJSZXBvc2l0b3J5 50608 -c3R5bGVUeXBl 50609 -IFNSQw== 50610 -Vk1MSU5VWA== 50611 -cmVjdXJzaXZl 50612 -L2Jhcg== 50613 -X2NoaXA= 50614 -b21pbmF0ZWQ= 50615 -IE5pdA== 50616 -4oCUdG8= 50617 -IEJ1ZGRo 50618 -0L7QvNC10YA= 50619 -IE1BRw== 50620 -IENIRQ== 50621 -X2Rlbg== 50622 -LnJhaXNlcw== 50623 -X2RlZ3JlZQ== 50624 -IHB1bXBraW4= 50625 -X3RlbXBsYXRlcw== 50626 -X01FRElB 50627 -IFRpbWVsaW5l 50628 -IGJvdHM= 50629 -T2JqZWN0VHlwZQ== 50630 -IGJ1eXM= 50631 -LnBvc3Rz 50632 -Q0FM 50633 -d2FpdGluZw== 50634 -IERhbmllbHM= 50635 -IGRhYmVp 50636 -IFNpZ21h 50637 -aWxvcg== 50638 -aWdlbA== 50639 -LFc= 50640 -QURT 50641 -KHBhbmVs 50642 -7LK0 50643 -aXRhdGluZw== 50644 -LnBhbGV0dGU= 50645 -IG1vc3F1aXRv 50646 -IHRlZ28= 50647 -KHBhcnNlSW50 50648 -IGRlc3B1w6lz 50649 -cHJvbWlzZQ== 50650 -IHdpag== 50651 -dHlwZXNjcmlwdA== 50652 -IFR2 50653 -X0lERU5USUZJRVI= 50654 -KS4KCgo= 50655 -X2ZsYXQ= 50656 -aXRzdQ== 50657 -VVNS 50658 -ZXhwZXJpZW5jZQ== 50659 -LWZpdA== 50660 -cGhpbng= 50661 -X3RocmVzaA== 50662 -IGlkZWFsbHk= 50663 -IEZyZWVtYW4= 50664 -LERC 50665 -X3J3 50666 -562J 50667 -VWI= 50668 -X3N0YXRpc3RpY3M= 50669 -PSIiPjw= 50670 -IGNob3Jl 50671 -IHlvcms= 50672 -aW5zdGFsbGVk 50673 -QWRkaXRpb25hbGx5 50674 -IHBzdG10 50675 -eWxrbw== 50676 -OjoK 50677 -Rm9yZXN0 50678 -IGhlYWRzZXQ= 50679 -IGdhbGxvbg== 50680 -0YDQtdC8 50681 -IHdpdGhkcmF3bg== 50682 -IENhbmRpZGF0ZQ== 50683 -IG1lbHRpbmc= 50684 -IGZyZWV6ZXI= 50685 -IGhs 50686 -X0hFTFA= 50687 -bWltZQ== 50688 -KC8q 50689 -IHRoaXJzdA== 50690 -JHJldHVybg== 50691 -bWVtYmVyb2Y= 50692 -0LXQsQ== 50693 -IEh0dHBTZXJ2bGV0UmVxdWVzdA== 50694 -KG9i 50695 -X1Jlc3VsdA== 50696 -IGFzc2VydGVk 50697 -IGZ1bGZpbGxpbmc= 50698 -IHN0cmV0Y2hlcw== 50699 -cGFyYXRlZA== 50700 -LWZ1bmRlZA== 50701 -IOWb 50702 -aW5nbGVz 50703 -X2Nh 50704 -LmNvbmRpdGlvbg== 50705 -IERpc3BsYXlz 50706 -IG9yYW5n 50707 -IENSRQ== 50708 -IGdsQmluZA== 50709 -IFNlbGVjdG9y 50710 -L3R5cGU= 50711 -IEFsZXhh 50712 -Y2hlZHVsZXM= 50713 -IFBlbmluc3VsYQ== 50714 -IHBhcml0eQ== 50715 -CWRlc3Q= 50716 -IERvb3Jz 50717 -DQoJDQo= 50718 -X2RpbWVuc2lvbg== 50719 -IGFsb2Fk 50720 -LlN0b3JlZFByb2NlZHVyZQ== 50721 -KHBhcmVu 50722 -IEJ1cmtl 50723 -JyldCg== 50724 -LWVuZ2luZQ== 50725 -IHF1aXI= 50726 -IEh5YnJpZA== 50727 -IERvZQ== 50728 -IG91dGxpbmVz 50729 -IFRyZW5kcw== 50730 -X05W 50731 -cGVyaW1lbnRz 50732 -IEhpbg== 50733 -Pycs 50734 -CVRleHQ= 50735 -RlVM 50736 -IHNtZWxscw== 50737 -IHNsaWNr 50738 -IG1pc2VyYWJsZQ== 50739 -IEFycmF5QWRhcHRlcg== 50740 -IHBhcmFtU3RyaW5n 50741 -SG9t 50742 -X2xpdGVyYWxz 50743 -dXN1YXJpb3M= 50744 -IHByb21wdGluZw== 50745 -X2xhenk= 50746 -IEFjdGl2YXRpb24= 50747 -X29j 50748 -V2Vhaw== 50749 -IGFuZWNk 50750 -IFVDTEE= 50751 -PXJl 50752 -aXNzZW1lbnQ= 50753 -IEVzY29ydHM= 50754 -RXhjZWxsZW50 50755 -IFBhdXNl 50756 -IHJlcG9zaXRvcmllcw== 50757 -VE9S 50758 -YXJpYXRl 50759 -X2lzbw== 50760 -dXBkYXRlcw== 50761 -aGFsYg== 50762 -dWRpYW50ZQ== 50763 -66Gd 50764 -IG5haXZl 50765 -IFBlZw== 50766 -IExvdW5nZQ== 50767 -QVJHSU4= 50768 -KGJpbg== 50769 -T25DbGlja0xpc3RlbmVy 50770 -IEZBSUxFRA== 50771 -IGxpdGU= 50772 -IGR6aWU= 50773 -IExpdGVyYWw= 50774 -aXZvcg== 50775 -ZmNudGw= 50776 -IGVhdHM= 50777 -IHFlZA== 50778 -VW5sb2Nr 50779 -cmlkaW5n 50780 -dW5kYWk= 50781 -PU0= 50782 -QVRURVI= 50783 -Q29uZmlndXJlQXdhaXQ= 50784 -aWNpYXM= 50785 -dXN0b21lZA== 50786 -IHN1Y2Nlc3Npb24= 50787 -ZW5kVGltZQ== 50788 -IEp1cGl0ZXI= 50789 -IGp1ZGdpbmc= 50790 -ZHJhdGlvbg== 50791 -X2RvY3M= 50792 -Lm1v 50793 -IGVkdWNhdG9ycw== 50794 -IFZpbmU= 50795 -Q29uZA== 50796 -W291dA== 50797 -cWI= 50798 -XFZhbGlkYXRvcg== 50799 -IG1lYW5pbmdz 50800 -IHByZXNlbnRseQ== 50801 -IGRpdmlkaW5n 50802 -b3R0ZW5oYW0= 50803 -YXNjdWxhcg== 50804 -IHRyYWlsZXJz 50805 -IENMT1NF 50806 -0LDQvNC4 50807 -4oCZYWk= 50808 -IEdhaW4= 50809 -d29y 50810 -IHBsYW5uZXI= 50811 -IGRpc3RyaWJ1dGluZw== 50812 -dmF0 50813 -bW9udGhz 50814 -eGxhYmVs 50815 -SEY= 50816 -VmlvbA== 50817 -LkJBU0VMSU5F 50818 -0LXRgtGB0Y8= 50819 -IFJvdGF0ZQ== 50820 -IHR4bg== 50821 -OmJvbGQ= 50822 -IGJsb3Nz 50823 -Rm9yZ2VyeQ== 50824 -KGVtYmVk 50825 -IGpha28= 50826 -c3ByaW50Zg== 50827 -dGhlaXI= 50828 -IGV4aGliaXRz 50829 -LXN0YXRpYw== 50830 -aGVjeQ== 50831 -Z2V0QWN0aXZlU2hlZXQ= 50832 -LmNsaWVudHM= 50833 -44GN 50834 -X2hpZGU= 50835 -W3dvcmQ= 50836 -Q2I= 50837 -YWRkSXRlbQ== 50838 -YXhl 50839 -X3JhZGlv 50840 -YWxpb24= 50841 -bW9kaWZpZXI= 50842 -IHNhdHVyYXRpb24= 50843 -IGRlbm9t 50844 -X3BpeGVscw== 50845 -bWVzcw== 50846 -KGZs 50847 -YXRpZg== 50848 -IHNlY3M= 50849 -IHByb3N0aXR1dGlvbg== 50850 -IGdyYW5kY2hpbGRyZW4= 50851 -IHBhcmFkaXNl 50852 -IEZlbGQ= 50853 -X0JJTkFSWQ== 50854 -aXRvdXM= 50855 -4LmE 50856 -IGZsYXNoaW5n 50857 -LXNpZGVk 50858 -IGNvbnRyYWRpY3Rpb24= 50859 -LyoKCg== 50860 -eWxhYmVs 50861 -IFRldA== 50862 -IGFkbWlyZQ== 50863 -cmVzbw== 50864 -IGxldHo= 50865 -IFNFQVJDSA== 50866 -c2xvdHM= 50867 -IFJld2FyZHM= 50868 -IEhvZw== 50869 -IE5TRGF0YQ== 50870 -c3Rhc2g= 50871 -RmFsbA== 50872 -IEFtZXI= 50873 -TGluZWFyTGF5b3V0 50874 -L3Bob3Rvcw== 50875 -IGZlYXRoZXI= 50876 -IHwNCg== 50877 -RG93bmxvYWRz 50878 -LlN0YXJ0c1dpdGg= 50879 -IC8vIw== 50880 -aW5lVHJhbnNmb3Jt 50881 -IGFmZmlk 50882 -VnRibA== 50883 -IFJvZ3Vl 50884 -c2NyaWJlZA== 50885 -IGZhdWM= 50886 -IE1vbnJvZQ== 50887 -IGRlY2xhcmVz 50888 -bW9kZXJu 50889 -cmVvbg== 50890 -YXliZQ== 50891 -UEFTUw== 50892 -ZmVycw== 50893 -X01VTFRJ 50894 -IE1hdGhlbWF0aWNz 50895 -IHN1ZGFo 50896 -X0FUVEFDSA== 50897 -IG51bWJlcldpdGg= 50898 -IFNvbG9tb24= 50899 -amlu 50900 -b2dyYWZpYQ== 50901 -w7Zs 50902 -X2Rlc2lnbg== 50903 -Y3VsYXRlZA== 50904 -IEx1bmE= 50905 -aWVzeg== 50906 -ID0+Jw== 50907 -IHJldmVsYXRpb25z 50908 -QWxvbmc= 50909 -KGVk 50910 -IEZpbGVuYW1l 50911 -IHlsYWJlbA== 50912 -U2VjdXJl 50913 -IGJ1c2Nh 50914 -YWdub3Npcw== 50915 -X1JFQ0U= 50916 -IG92ZXJsYXBwaW5n 50917 -RXh0ZW50 50918 -IGFudGljaXBhdGlvbg== 50919 -Q2hlY2tz 50920 -IEFMU08= 50921 -b3Jj 50922 -aWxpbmd1YWw= 50923 -aXRhdGlvbmFs 50924 -IGFkdmFuY2VtZW50 50925 -b3Vybw== 50926 -IFByZWRpY2F0ZQ== 50927 -5b6X 50928 -ZXJpYQ== 50929 -IFBpZXJjZQ== 50930 -b3Jpbw== 50931 -IG1lcml0cw== 50932 -IHBlYW51dA== 50933 -LlBhY2thZ2U= 50934 -IENvbmR1Y3Q= 50935 -X1NFTlNPUg== 50936 -IGJvaWxpbmc= 50937 -IGludHJh 50938 -IElHTg== 50939 -IEZ1cg== 50940 -LlJlZnJlc2g= 50941 -IFJlYWNo 50942 -X2RlY29kZXI= 50943 -LkV4cA== 50944 -INGC0LDQug== 50945 -cGlsbA== 50946 -LFE= 50947 -IEdyaWxs 50948 -IHBvcHBpbmc= 50949 -LkFn 50950 -IHByb3llY3Rv 50951 -IG1pbGVhZ2U= 50952 -IGVjb2xvZ2ljYWw= 50953 -XV0pOwo= 50954 -IMKt 50955 -c3VicGxvdA== 50956 -YWNhZA== 50957 -IFRyeWluZw== 50958 -cmVjaXBlcw== 50959 -JGNyaXRlcmlh 50960 -IFBlcnNpYW4= 50961 -LWJvdW5k 50962 -TUFTSw== 50963 -IEdlc3R1cmU= 50964 -IGtr 50965 -IFBWQw== 50966 -IHByb2hpYml0aW9u 50967 -IGNvbWFuZG8= 50968 -IExPT0s= 50969 -U2hvcHBpbmc= 50970 -IGRpc3RvcnRpb24= 50971 -PEJvb2xlYW4= 50972 -LkdldExlbmd0aA== 50973 -dW1wdA== 50974 -XFByb2R1Y3Q= 50975 -ZWxsZXJ5 50976 -IGZpcmV3YWxs 50977 -Zm9ybWF0dGVk 50978 -LnJlZGlz 50979 -IGVzYQ== 50980 -IFJob2Rl 50981 -U29t 50982 -Lm5vbg== 50983 -ICcpLg== 50984 -IGdldFZpZXc= 50985 -4bqhbg== 50986 -cHJ1cw== 50987 -TWF0dGhldw== 50988 -IHNpYQ== 50989 -IEZvcnM= 50990 -R1BV 50991 -aWVudHJhcw== 50992 -X0lOU1Q= 50993 -IG9sYXJhaw== 50994 -IGltcG9ydGluZw== 50995 -VENQ 50996 -LyIpOwo= 50997 -ZWl0aGVy 50998 -IGZyZXNobHk= 50999 -Y2FzY2FkZQ== 51000 -KGNoYXJhY3Rlcg== 51001 -IEplZXA= 51002 -b3RpY3M= 51003 -X1VUSUw= 51004 -Llh0cmFQcmludGluZw== 51005 -LmZpcnN0Q2hpbGQ= 51006 -IEV4Y2VsbA== 51007 -IGR2ZA== 51008 -IHRhbGxlcg== 51009 -IHJhcw== 51010 -eXBhc3M= 51011 -IGFzc2lnbnM= 51012 -IGdyaWV2 51013 -LW1vcmU= 51014 -SkQ= 51015 -IEJ1cm5z 51016 -Jz4NCg== 51017 -LkRlcGVuZGVuY3k= 51018 -LlF1ZXJ5U3RyaW5n 51019 -Lk93bmVy 51020 -IGV4cGlyeQ== 51021 -VGh1 51022 -KFZlYw== 51023 -IGhhemFyZG91cw== 51024 -IHJwbQ== 51025 -QVBPTg== 51026 -IGFkZFRhcmdldA== 51027 -c3ZpbGxl 51028 -cE5ldA== 51029 -IEltZw== 51030 -IFRJTUVS 51031 -LkFuaW1hdGlvbg== 51032 -IGJlaw== 51033 -IGFzc29ydA== 51034 -IGxlYmlo 51035 -IGJvZHlQYXJzZXI= 51036 -IHZpYnJhdGluZw== 51037 -SURM 51038 -IGJ1dHRlcmtuaWZl 51039 -aW50ZXJz 51040 -IHBlcnN1YWRl 51041 -IExHQlRR 51042 -6Is= 51043 -LnNvZnQ= 51044 -IGJlYW1z 51045 -X3N1cg== 51046 -LkRlZg== 51047 -IGxhYnM= 51048 -CXBsdA== 51049 -IHNraW5z 51050 -IHRyYW5zZmVycmluZw== 51051 -IGltYWdpbmFyeQ== 51052 -X0VuZA== 51053 -O2JhY2tncm91bmQ= 51054 -IGxhcHM= 51055 -X0NPTU1FTlQ= 51056 -KFNETA== 51057 -b25kcw== 51058 -LlJlY29yZA== 51059 -IEltcGxlbWVudHM= 51060 -X3RpY2tz 51061 -KCkpKQoK 51062 -IGFyb3Nl 51063 -XT8= 51064 -IE1w 51065 -IElDb21tYW5k 51066 -IHNjdWxwdHVyZQ== 51067 -IGNvbnRyYWN0ZWQ= 51068 -PEhUTUw= 51069 -IGNhbGVuZA== 51070 -YXR5 51071 -L1N1Yg== 51072 -IGt2aW5u 51073 -X0lHTk9SRQ== 51074 -IFNoYW5l 51075 -TUxT 51076 -IHN0aW11bGF0ZQ== 51077 -UGFydGl0aW9u 51078 -IG11bg== 51079 -w7Nt 51080 -ZXJhbGE= 51081 -LWFjY291bnQ= 51082 -LkJpbmFyeQ== 51083 -Y8Op 51084 -IHNlaXpl 51085 -Y29ubmVjdGlvbnM= 51086 -IAogICAgICAgIAo= 51087 -IERpYWdub3N0aWM= 51088 -VklTSUJMRQ== 51089 -IFJ1bnM= 51090 -IGltcHJlc3Npb25z 51091 -c3VpdGU= 51092 -b2JsZQ== 51093 -fi0= 51094 -YWt1a2Fu 51095 -PFBlcnNvbg== 51096 -IE5vcw== 51097 -IEd1aQ== 51098 -LndhaXRGb3I= 51099 -UkVTRVQ= 51100 -IHBvc3Rwb24= 51101 -RGlzY292ZXI= 51102 -YXJyaXNvbg== 51103 -c2hhdw== 51104 -Ymxvb2Q= 51105 -QUpPUg== 51106 -5pu05paw 51107 -IE11c2U= 51108 -5pS2 51109 -IHJldGFpbmluZw== 51110 -b3R0ZQ== 51111 -IG1vc3F1ZQ== 51112 -IFNuZQ== 51113 -IHN0YW5kYXJkaXplZA== 51114 -IG1haW5sYW5k 51115 -X3RocmVl 51116 -dW5nZW9ucw== 51117 -Z2V0RG9jdHJpbmU= 51118 -IHdoYWxl 51119 -IGFnZw== 51120 -IFBvcnNjaGU= 51121 -bm93bGVk 51122 -bGF0ZW50 51123 -IFJlbGF0aW9u 51124 -IC8vJw== 51125 -IHNodXR0aW5n 51126 -IFJlbWl4 51127 -X2Nvdg== 51128 -IHNhaWxpbmc= 51129 -IHZvd2Vk 51130 -IHBvdHM= 51131 -b3V0dQ== 51132 -IGhhaXJ5 51133 -Y2FzdHM= 51134 -UmVsb2Fk 51135 -IHJlY29ubmVjdA== 51136 -dGVyYQ== 51137 -LmNoaWxkTm9kZXM= 51138 -IFJhY2s= 51139 -IGN1cnJlbnRJbmRleA== 51140 -IGFsbGVu 51141 -IOeUqOaItw== 51142 -IEN1YnM= 51143 -W1g= 51144 -X1NFUQ== 51145 -X1JFTU9WRQ== 51146 -LmdldEFjdGlvbg== 51147 -KC9e 51148 -ZXJyYXI= 51149 -IGV0aGVy 51150 -Y3VydmU= 51151 -IHNsYXA= 51152 -IHVvbQ== 51153 -T3RoZXJz 51154 -IGVuZ3I= 51155 -RGlzcG9zaXRpb24= 51156 -IHN0YWdlZA== 51157 -RXll 51158 -IEF1eA== 51159 -YXV0aGVudGljYXRl 51160 -ICQ/ 51161 -IEFuZHJlYXM= 51162 -IHNldHc= 51163 -LkFydA== 51164 -IGZvcmVjYXN0cw== 51165 -IGF1bnQ= 51166 -LW1pZGRsZQ== 51167 -IG1pc2Q= 51168 -ZGVzaw== 51169 -IGVzY29ydGU= 51170 -IENhc2E= 51171 -cm9waWNhbA== 51172 -IGV4ZW1wbGU= 51173 -cGxhbmV0 51174 -KFVJTlQ= 51175 -IHdoaXA= 51176 -IFBDQg== 51177 -Y2xpZGVhbg== 51178 -PSJc 51179 -IG94aWRl 51180 -IHN1Y2NlZWRz 51181 -ZGVyaXZlZA== 51182 -IEVjb25vbQ== 51183 -X2Nvb3JkaW5hdGVz 51184 -aXJhcw== 51185 -RHJhZnQ= 51186 -IHZpc3VhbGl6ZQ== 51187 -QnJpYW4= 51188 -X0FTU1VNRQ== 51189 -IE9iamVjdElk 51190 -IHRyYWluZXJz 51191 -X0ZPUkNF 51192 -IGNvbnNvbGVz 51193 -LXByb2Nlc3M= 51194 -bGljaGVy 51195 -IFNpbW1vbnM= 51196 -VGFraW5n 51197 -IENsYWltcw== 51198 -IGRpZmbDqXJlbnQ= 51199 -QWN0aXZpdHlSZXN1bHQ= 51200 -IHNucw== 51201 -6YCJ5os= 51202 -IENydXM= 51203 -IGxsYW0= 51204 -cmFi 51205 -IEpvYW4= 51206 -QUFB 51207 -CWZpbHRlcg== 51208 -aXNob3Bz 51209 -Z2V0dGluZw== 51210 -4LU= 51211 -IHF1YW50bw== 51212 -UGFzdA== 51213 -b3ZpY2g= 51214 -IGluanVzdGljZQ== 51215 -IEZMT0FU 51216 -IGFscmlnaHQ= 51217 -XERC 51218 -KEdhbWVPYmplY3Q= 51219 -dWlzaA== 51220 -KGJvdA== 51221 -IGdhbGxvbnM= 51222 -IFLDqQ== 51223 -IFNhaWQ= 51224 -IFNURE1FVEhPRENBTExUWVBF 51225 -YWlzaW5n 51226 -X3Byb2Nlc3Nvcg== 51227 -ZWxsaWRvcw== 51228 -dGVyZGFt 51229 -IEJlYW0= 51230 -VGV4dEFyZWE= 51231 -IHJldG9ybm8= 51232 -Lk1ha2U= 51233 -ICQoIjw= 51234 -IGxvY2tkb3du 51235 -IHJlbWVkaWVz 51236 -IHZlZWw= 51237 -eGVl 51238 -ZG9jdHlwZQ== 51239 -Rmls 51240 -IEV4cGFuZA== 51241 -IGVtcGxveXM= 51242 -IHNlc3Npb25TdG9yYWdl 51243 -UGhw 51244 -UHVibGlzaA== 51245 -IHJldGFs 51246 -ZmFicw== 51247 -eW5hbWljcw== 51248 -IHRvc3NlZA== 51249 -IG51bWJlck9mUm93c0luU2VjdGlvbg== 51250 -eHBhdGg= 51251 -XG1vZHVsZXM= 51252 -IGRpc2FzdHI= 51253 -IE1VTFQ= 51254 -Lk1lc2g= 51255 -LXN0YWdl 51256 -IHNkZg== 51257 -aXR1bmc= 51258 -dWdlcw== 51259 -ID8+Ij48Lw== 51260 -X2luZGV4ZXM= 51261 -IHZhbHVhdGlvbg== 51262 -IGxpZmVsb25n 51263 -IGV4cGVkaXRpb24= 51264 -KFlpaQ== 51265 -IHBhaW5z 51266 -IFBSSQ== 51267 -IE1peGVk 51268 -IENoYW5naW5n 51269 -R2VybWFueQ== 51270 -Y29tbXVuaWNhdGlvbg== 51271 -Lm9yZ2Fu 51272 -IE1hcmF0aG9u 51273 -Z2V0UGF0aA== 51274 -IEFjY3VyYWN5 51275 -IEdsb2JhbHM= 51276 -Jyl9fTwv 51277 -IE9XTkVS 51278 -4oCm4oCd 51279 -IHN0YWJiZWQ= 51280 -IHNjaGl6b3BocmVu 51281 -IEZu 51282 -IENPUkU= 51283 -IERhdGFSb3c= 51284 -IExURA== 51285 -IG15dGhz 51286 -IGZhbW91c2x5 51287 -fCwK 51288 -IFNlb3Vs 51289 -U2ly 51290 -IEJlcms= 51291 -UmVnRXhw 51292 -LmdldFJvdw== 51293 -IERlY29kZQ== 51294 -Uk4= 51295 -IG1hbmc= 51296 -IGVtcGxveWluZw== 51297 -X25vbWJyZQ== 51298 -PFRhc2s= 51299 -IEd1eXM= 51300 -IEFydGlrZWw= 51301 -QmVycnk= 51302 -enVyZQ== 51303 -IHZhbGV1cg== 51304 -aGl0cw== 51305 -IGx1Y3JhdGl2ZQ== 51306 -IGluZm9ybWF0 51307 -Q2xpbnRvbg== 51308 -IHRlcw== 51309 -IENlcnRpZmljYXRpb24= 51310 -X3dz 51311 -IG9mZmVuY2Vz 51312 -ZWJyYQ== 51313 -IEF4aW9z 51314 -cmVzdGFydA== 51315 -TE4= 51316 -LkVuY29kZQ== 51317 -bWl1bQ== 51318 -IEZlYXR1cmVk 51319 -0YjQuNCx0LrQsA== 51320 -IERlcHQ= 51321 -OyYj 51322 -IE15ZXJz 51323 -CXRyYW5zZm9ybQ== 51324 -VGV4YXM= 51325 -16g= 51326 -IFlvcmtzaGlyZQ== 51327 -bG5hbWU= 51328 -QnJl 51329 -44GT44Gu 51330 -IHNjZW5lcnk= 51331 -IGbDvGg= 51332 -CQkJCSAgICAgICA= 51333 -IERvb20= 51334 -IEFETUlO 51335 -KGVz 51336 -INC80LDRgdGB0LjQsg== 51337 -X2FzY2lp 51338 -L0RhdGE= 51339 -bGVzaG9vdGluZw== 51340 -QmFu 51341 -IG1lbW9pcg== 51342 -INmG 51343 -IEF1c3M= 51344 -KXBhcmVu 51345 -IGd1aWRpbmc= 51346 -IGJheg== 51347 -w7h5 51348 -QURN 51349 -IGRtYQ== 51350 -LlF1ZXVl 51351 -IFN1cHBsaWVz 51352 -IE1jRA== 51353 -IEFnZW50cw== 51354 -X2Ji 51355 -c2xhc2g= 51356 -IGhhc2hlcw== 51357 -IGNyYW5r 51358 -IFJhZw== 51359 -IGF1dG9ub215 51360 -w610dWxv 51361 -IHJlY3Vyc2lvbg== 51362 -IENyYXp5 51363 -X3RyYWNrZXI= 51364 -IE1i 51365 -X3BoeQ== 51366 -Zm9vYmFy 51367 -CXNwZWVk 51368 -IGNhbXBvcw== 51369 -IG1vdWxk 51370 -IGNoYXJpdGllcw== 51371 -SEVJR0hU 51372 -IGVhdXRv 51373 -X3NvbHV0aW9u 51374 -IERH 51375 -bWFydmlu 51376 -WWVzdGVyZGF5 51377 -IEJlY29tZQ== 51378 -PGxs 51379 -b3Jpcw== 51380 -W25leHQ= 51381 -IGluY3VtYmVudA== 51382 -IER1cA== 51383 -CW92ZXJyaWRl 51384 -5a6J 51385 -CWNmZw== 51386 -IHPDtg== 51387 -IGRlc2U= 51388 -LWRp 51389 -IG9udHZhbmdzdA== 51390 -IGRlY2lzaXZl 51391 -5Lu3 51392 -X2tlZXA= 51393 -KERhdGFiYXNl 51394 -Xy8= 51395 -IENMTA== 51396 -LW1ldGhvZA== 51397 -CVBvaW50 51398 -IEJ5dGVCdWZmZXI= 51399 -IHRyYWNlZA== 51400 -YWRkVG8= 51401 -7IS47JqU 51402 -YW55YWs= 51403 -IGVtcHJlc2Fz 51404 -KHJlcG9zaXRvcnk= 51405 -LmNyZWF0ZVN0YXRlbWVudA== 51406 -IGVsYQ== 51407 -Rm9yZ2VyeVRva2Vu 51408 -IGlzZW1wdHk= 51409 -YXNpbg== 51410 -IExvb2t1cA== 51411 -0LXQvdCw 51412 -IHZpb2xhdGVz 51413 -IFNtYXJ0eQ== 51414 -IHphaw== 51415 -KCQu 51416 -U0hPVw== 51417 -INCi 51418 -YXJ1cw== 51419 -KFRFU1Q= 51420 -cGFja2Vk 51421 -IGhpc3Rvcmlh 51422 -IGNhbmNlcnM= 51423 -IEtyZW1saW4= 51424 -UmVkdWNl 51425 -L2hvdw== 51426 -IMSQ 51427 -VElUTEU= 51428 -LmxvY2FsUG9zaXRpb24= 51429 -bGlhYmxl 51430 -IOesrA== 51431 -IGZyYW5jYWlz 51432 -CWhhc2g= 51433 -IGluaWNpbw== 51434 -IENyYXNo 51435 -IHsu 51436 -IGNsb2Nrcw== 51437 -ZHVjdG9yeQ== 51438 -IFB2 51439 -6528 51440 -IGRvaXM= 51441 -XC0= 51442 -IGphYXI= 51443 -IE1heWE= 51444 -bW96aWxsYQ== 51445 -CXJlc291cmNl 51446 -ISEK 51447 -YXlzY2FsZQ== 51448 -ICctJyw= 51449 -5Y+W5raI 51450 -IHN0YWxl 51451 -Q29ybmVy 51452 -w6hsZQ== 51453 -aXRpdmVz 51454 -emFz 51455 -aWNvcm4= 51456 -LkV4cHJlc3Npb24= 51457 -w7N0 51458 -QXBwbGljYXRpb25z 51459 -UmVzdHI= 51460 -X0luZGV4 51461 -jbDsnbTthLA= 51462 -IEpGcmFtZQ== 51463 -c2l4 51464 -X0lNRw== 51465 -6JeP 51466 -IE51bWVyaWM= 51467 -IHdpcms= 51468 -X1NVTQ== 51469 -PERhdGVUaW1l 51470 -IHB5bGludA== 51471 -IGxhbWVudA== 51472 -IFBvc2U= 51473 -X2VudHJvcHk= 51474 -IGVuY291cmFnZW1lbnQ= 51475 -IGxhaW4= 51476 -5Yib5bu6 51477 -LWZy 51478 -IGNvcnJlY3Rpb25z 51479 -cGhhcw== 51480 -dXVy 51481 -YXRlZ29yaWFz 51482 -IGNhdGFseXN0 51483 -LmFsdA== 51484 -IEZlcm5hbmRv 51485 -LkRhdGFHcmlkVmlld0NlbGxTdHlsZQ== 51486 -IGhlcmJhbA== 51487 -IFJH 51488 -U1RFUA== 51489 -SUZu 51490 -IFRvbmc= 51491 -xb5l 51492 -IElOQ0xVREU= 51493 -IGhj 51494 -dHJhY2tlcg== 51495 -CVN0cmluZ0J1aWxkZXI= 51496 -IERlc3Rpbnk= 51497 -IHNvcGhvbW9yZQ== 51498 -IERlZA== 51499 -IFBBUkE= 51500 -aXpvbnRhbGx5 51501 -LWNoYW5nZQ== 51502 -ZW5kaWQ= 51503 -6YCJ5oup 51504 -aWprZQ== 51505 -IEF0aGxldGlj 51506 -YmFp 51507 -Z2V0UG9zaXRpb24= 51508 -Lm5hbWVzcGFjZQ== 51509 -6K6i5Y2V 51510 -UkFDVA== 51511 -IHJlbGlldmVk 51512 -IHBvdXJpbmc= 51513 -IGl5 51514 -cm92ZQ== 51515 -IGFkb2xlc2NlbnRz 51516 -IGF3ZQ== 51517 -cmVhcw== 51518 -QW50aUZvcmdlcnlUb2tlbg== 51519 -cm93bmluZw== 51520 -IFVuY2xl 51521 -LkNvbm4= 51522 -IE1lZGlhVHlwZQ== 51523 -Lm9yYWNsZQ== 51524 -SU5URVJOQUw= 51525 -LGFuZA== 51526 -IGZhdXg= 51527 -aXBtYXA= 51528 -JG1vZGVs 51529 -IEdlb2Zm 51530 -X0FYSVM= 51531 -KCgpKQo= 51532 -IG5lZ2xlY3RlZA== 51533 -IHF1YXJ0ZXJseQ== 51534 -IGRpZXNlbg== 51535 -IGRyYWdvbnM= 51536 -TmlnaHQ= 51537 -L1dlYg== 51538 -PFZlYw== 51539 -CSAgICAgICAgICAgICAgICAgICAgICAg 51540 -IE9icw== 51541 -YmRk 51542 -IGhlaXI= 51543 -LWFuZ3VsYXI= 51544 -TWVudVN0cmlw 51545 -ICciPic= 51546 -a2luc29u 51547 -INC60L7Quw== 51548 -b2duaXRpdmU= 51549 -X2xp 51550 -IGltbWluZW50 51551 -IGFmZmluaXR5 51552 -LnNpZ25hbA== 51553 -IG5vdGNo 51554 -IFN0ZWVsZXJz 51555 -bWF4bGVuZ3Ro 51556 -S0s= 51557 -IEV1Z2VuZQ== 51558 -X1BXTQ== 51559 -cm9p 51560 -IOKXjw== 51561 -IEhhbWJ1cmc= 51562 -Lk11c3Q= 51563 -IGF4ZQ== 51564 -ZW5lZg== 51565 -IGFtYml0aW9ucw== 51566 -IFNwZWNpZXM= 51567 -IFN0cmVzcw== 51568 -IGF3aGlsZQ== 51569 -INCx0YPQtA== 51570 -IHdpdGhzdGFuZA== 51571 -IERlY29kZXI= 51572 -X2ludmVudG9yeQ== 51573 -IHsNDQo= 51574 -IHRndA== 51575 -IHJhaWxyb2Fk 51576 -V0FTSElOR1RPTg== 51577 -IG5lZ290aWF0ZWQ= 51578 -TlNU 51579 -LXBob25l 51580 -LFU= 51581 -IGV4ZXJjaXNpbmc= 51582 -4bul 51583 -X1BJWEVM 51584 -YXZvcnM= 51585 -aXRlcmF0ZWQ= 51586 -IHZhbXBpcmU= 51587 -YWRhbA== 51588 -SW5ncmVzZQ== 51589 -IHVuZw== 51590 -amVjdGl2ZQ== 51591 -LmNlbGxz 51592 -IG5hbm8= 51593 -IG1hcmtkb3du 51594 -X1JVTEU= 51595 -KGV2ZW50cw== 51596 -IGx1Z2dhZ2U= 51597 -TUVTU0FHRQ== 51598 -aWdrZWl0 51599 -JGNvdW50 51600 -QXR0cmlidXRlTmFtZQ== 51601 -SUdJTkFM 51602 -X0VudA== 51603 -IEJG 51604 -IENPTU1FTlQ= 51605 -X2luaQ== 51606 -IEV1cm9wZWFucw== 51607 -IEJlbGxl 51608 -5ZG9 51609 -KVsn 51610 -5bqU 51611 -IFVzZWZ1bA== 51612 -LnJlZmVyZW5jZQ== 51613 -KCkiLA== 51614 -X2dyYWRl 51615 -IEthdw== 51616 -IHNlbnRlbmNpbmc= 51617 -IHNvY2lhbGlzbQ== 51618 -bW9uc3Rlcg== 51619 -X0xBWUVS 51620 -IGRlZXBlc3Q= 51621 -d2s= 51622 -IE5vaXNl 51623 -IyMjCgo= 51624 -IHByw6lj 51625 -b3RsZQ== 51626 -0YLQtQ== 51627 -YXVm 51628 -aWJhbA== 51629 -IGNvbnF1ZXI= 51630 -PkVtYWls 51631 -IGFtYnVsYW5jZQ== 51632 -T0FE 51633 -ICgiJQ== 51634 -IEZJ 51635 -LmZpeHR1cmU= 51636 -IHRlcnNl 51637 -ICAgIAkJCQk= 51638 -IHNhbmN0dWFyeQ== 51639 -dWdp 51640 -IENvbXBhcmF0b3I= 51641 -RGVmaW5pdGlvbnM= 51642 -IGFzdGhtYQ== 51643 -IGxhY3Q= 51644 -IGhhcmR3b29k 51645 -LmNsb2Nr 51646 -IGF0dHJhY3Rpbmc= 51647 -IE1vdXI= 51648 -KGRpc3RhbmNl 51649 -aWNpdHM= 51650 -IGJvbm5l 51651 -IEFDQ0VTUw== 51652 -LkRlc2VyaWFsaXplT2JqZWN0 51653 -IFR5cGVk 51654 -IGpldQ== 51655 -IGFwcElk 51656 -IENsYXJh 51657 -IEhG 51658 -IFJlaWNo 51659 -aXBwbGVz 51660 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 51661 -X2RlbGl2ZXJ5 51662 -ZXJpYWxpemF0aW9u 51663 -IHBsYWludGlmZnM= 51664 -U2NpZW50 51665 -c2hvcHBpbmc= 51666 -IER1bW15 51667 -IFdhbGQ= 51668 -R3JvdXBOYW1l 51669 -IGluc2NyaXB0aW9u 51670 -ZWxvZw== 51671 -Ojo6Ojo6Ojo= 51672 -X2xk 51673 -QmFja1ByZXNzZWQ= 51674 -LlJhdw== 51675 -IE9uVHJpZ2dlcg== 51676 -IG11c2V1bXM= 51677 -IEJlZW4= 51678 -IEFkdmVudHVyZXM= 51679 -IHNsYXRl 51680 -IGxldHQ= 51681 -IHN1bmQ= 51682 -IEdpbg== 51683 -IE1lY2hhbmljYWw= 51684 -LnNoaXA= 51685 -QXBwQ29tcG9uZW50 51686 -IGRlc3RpbmVk 51687 -IGR3ZWxsaW5n 51688 -UHJvZmlsZXI= 51689 -UHJlcGFyZQ== 51690 -emVpY2g= 51691 -IHNpbGljb24= 51692 -KGhhcw== 51693 -ICMl 51694 -VklERU8= 51695 -IGNvbGxhYm9yYXRl 51696 -TGlu 51697 -IHNjb3Blcw== 51698 -KGNsYXNzTmFtZQ== 51699 -KHNk 51700 -YW5kaW4= 51701 -LmhhbQ== 51702 -U2VydmljZUltcGw= 51703 -LWRlc2NyaWJlZA== 51704 -IGlyb255 51705 -c3RpYWw= 51706 -IEh1YXdlaQ== 51707 -KHJlcG8= 51708 -IHVuZXhwZWN0ZWRseQ== 51709 -IEthaQ== 51710 -Lmluc3RhbGw= 51711 -XHhm 51712 -IGV4aGliaXRlZA== 51713 -X1RDUA== 51714 -IE94 51715 -X0NITw== 51716 -IHByb3N0aXR1ZXJ0ZQ== 51717 -IHbDpA== 51718 -IHNpdG8= 51719 -IGNvbnN0aXR1ZW50cw== 51720 -IENvbnRpbnVlZA== 51721 -IFNBVkU= 51722 -cnNz 51723 -L21lc3NhZ2U= 51724 -dWJlcw== 51725 -IG1pc2RlbWVhbg== 51726 -IHRheGF0aW9u 51727 -IHN0b3J5bGluZQ== 51728 -aGFpcg== 51729 -IEZpbmRz 51730 -U0lH 51731 -dmVyaWZpY2F0aW9u 51732 -fj0= 51733 -Lmhw 51734 -SXRlcmFibGU= 51735 -0YvQtQ== 51736 -YXRvcmk= 51737 -IGN0cg== 51738 -Ung= 51739 -Xyk7Cgo= 51740 -ZGFn 51741 -LnBpbg== 51742 -IHBzZXVk 51743 -IGludm8= 51744 -0YHRgtGA 51745 -X3BpeA== 51746 -5Li656m6 51747 -IHN3b3Ju 51748 -4oCUb3I= 51749 -X3JlZ2lzdHJ5 51750 -IGRpc2FzdGVycw== 51751 -IFJPSQ== 51752 -IOKAlQ== 51753 -YWt0dQ== 51754 -Zm9yZXN0 51755 -YmVpdGVu 51756 -4oCUSQ== 51757 -dWV2YQ== 51758 -ZWd0 51759 -IHNwaWtlcw== 51760 -VVJFUw== 51761 -IFJlY29tbWVuZGVk 51762 -IGV4cGxvaXRlZA== 51763 -IEZyZWRlcmljaw== 51764 -X0NPTVBMRVRF 51765 -IERydWdz 51766 -ISEhISEhISE= 51767 -IFJpdg== 51768 -U1RPUA== 51769 -Uk9PTQ== 51770 -IFBBU1NXT1JE 51771 -Q29va2llcw== 51772 -LkVs 51773 -4but 51774 -IEJlcnQ= 51775 -IGhhc2hlZA== 51776 -aWNlc3Rlcg== 51777 -IGRlY29yYXRvcg== 51778 -IHF1ZXJ5U3RyaW5n 51779 -OjsK 51780 -ICJbIg== 51781 -b3RvcGU= 51782 -LUFtZXJpYw== 51783 -IE1hdHRoZXdz 51784 -VVJBTA== 51785 -4oCcLA== 51786 -U3VtbWVy 51787 -Zm9z 51788 -X0NPTlRBSU5FUg== 51789 -X0FDSw== 51790 -IGZpbHRy 51791 -X2Rpc3A= 51792 -X1Jl 51793 -IGZhY2lsZQ== 51794 -0LDRiA== 51795 -IOyVig== 51796 -IGViZW4= 51797 -IHNwcmluaw== 51798 -IFF1aW50 51799 -PlY= 51800 -IGhpc3RvcmlhbnM= 51801 -b3VybWV0 51802 -IE1vbml0b3Jpbmc= 51803 -bGVkZ2Vy 51804 -Y290dA== 51805 -IHdhcmU= 51806 -R0dMRQ== 51807 -Y2Fycw== 51808 -IE1FRElBVEVL 51809 -IHZvbHVwdA== 51810 -X1ZpZXc= 51811 -SEVM 51812 -KGNvcHk= 51813 -KHN0YXRz 51814 -IGNocm9tb3NvbWU= 51815 -IEN1cnRpcw== 51816 -LWNvbmY= 51817 -KGFzc2V0 51818 -IGh2b3I= 51819 -RmlsZVN5c3RlbQ== 51820 -PD4oKTsNCg== 51821 -b2NvZGVy 51822 -IENhbm5vbg== 51823 -KXg= 51824 -IFNtb290aA== 51825 -IFNBUw== 51826 -X2Nl 51827 -CXByZXY= 51828 -X21vdmll 51829 -RWM= 51830 -X3dhbGw= 51831 -PEJ1dHRvbg== 51832 -IEZBU1Q= 51833 -IG9uVmlldw== 51834 -dWxhbg== 51835 -IFNVUFBPUlQ= 51836 -IGdlc2NoaWNodGVu 51837 -IFNvbnM= 51838 -SW1t 51839 -JElGbg== 51840 -IGZhaXJuZXNz 51841 -IGRwaQ== 51842 -YXRzdQ== 51843 -Sm9zaA== 51844 -RXF1YWxpdHk= 51845 -IH0oKQo= 51846 -X2xlc3M= 51847 -IFJhdGlv 51848 -IENhdHM= 51849 -IFN0ZXJu 51850 -TW9uc3Rlcg== 51851 -IG1lcmN1cnk= 51852 -w7xocg== 51853 -IHBsdXNpZXVycw== 51854 -LmRlc2VyaWFsaXpl 51855 -c2NvcHk= 51856 -LkZhbHNl 51857 -KWFuaW1hdGVk 51858 -IEV4cGVydHM= 51859 -ICIiKXsK 51860 -LldoZW4= 51861 -c2VlYWxzbw== 51862 -LnVucGFjaw== 51863 -TEVN 51864 -LnNlbGVjdEFsbA== 51865 -IHBlcmNlcHRpb25z 51866 -dWRpbmc= 51867 -aXJsaW5n 51868 -IFByaW50aW5n 51869 -Z3JhbXM= 51870 -IEZpbGVTdHJlYW0= 51871 -ZXJ2aWxsZQ== 51872 -aWxvZw== 51873 -aWNtcA== 51874 -X0NvdW50 51875 -IGxpdmVzdG9jaw== 51876 -LWNh 51877 -ZG9jdW1lbnRz 51878 -IHBvbGVz 51879 -CXdhbnQ= 51880 -IGZsdW9yZXM= 51881 -IHN0YW5kcG9pbnQ= 51882 -IEh1Z2U= 51883 -IHJhZGlhbnM= 51884 -IFVJQmFy 51885 -RURJVU0= 51886 -IEhpc3Rvcmlj 51887 -X2hvbGRlcg== 51888 -IE1hcmluZXM= 51889 -IHTDpA== 51890 -LkxpZ2h0 51891 -cXVpcmVy 51892 -YXNvbnJ5 51893 -ZGl2aWRlcg== 51894 -IEZsdXR0ZXI= 51895 -X2Zi 51896 -cmVzdHJpY3RlZA== 51897 -IEV2ZXJ5Ym9keQ== 51898 -TsOjbw== 51899 -IGtub3Q= 51900 -IFR3aXRjaA== 51901 -IGhhbGx3YXk= 51902 -KENvbGxpZGVy 51903 -SW5wdXRFbGVtZW50 51904 -PykK 51905 -L29mZg== 51906 -Lyk= 51907 -cGxheWVk 51908 -W09G 51909 -IGJhdHRpbmc= 51910 -X2Rs 51911 -IGNvbWVkaWFu 51912 -IMOpdg== 51913 -IERFTQ== 51914 -IEVkZW4= 51915 -OndoaXRl 51916 -Jycs 51917 -Q29uc3RydWN0aW9u 51918 -YWNlcmI= 51919 -IHRhc2tlZA== 51920 -Lm1hbmFnZQ== 51921 -UmVsYXRpb25zaGlw 51922 -IHBob24= 51923 -bno= 51924 -X0JHUg== 51925 -VmFsaWRhdGVBbnRpRm9yZ2VyeVRva2Vu 51926 -X2Fpcg== 51927 -4oCcV2hlbg== 51928 -IGdsZnc= 51929 -IENvbnZlcnNhdGlvbg== 51930 -X1RPVEFM 51931 -LFo= 51932 -IGdyYXo= 51933 -IGl0ZXJhYmxl 51934 -IFBBU1M= 51935 -IGFkdmVydGlzZQ== 51936 -IG3DtmdsaWNo 51937 -L3RyYWlu 51938 -IFZvbGtzd2FnZW4= 51939 -IGNyZWVweQ== 51940 -ICIpDQo= 51941 -UVVFTkNF 51942 -IGFsdGFy 51943 -IGVkaXRz 51944 -Y29tcGlsZWQ= 51945 -YXduaW5n 51946 -IER1bmdlb24= 51947 -IG9zZw== 51948 -TmF2aWdhdGlvbkJhcg== 51949 -IHRyZW5kaW5n 51950 -IEVjbw== 51951 -b2dnbGVz 51952 -Y2RvdA== 51953 -fC0= 51954 -U2ll 51955 -ZWNyZXQ= 51956 -IE5lZ2F0aXZl 51957 -IExpbmc= 51958 -IERJTQ== 51959 -IENXRQ== 51960 -IENhcnJpZXI= 51961 -IGNhcnRyaWRnZQ== 51962 -X3VzYg== 51963 -PW9z 51964 -IEphY2tpZQ== 51965 -IG90cmFz 51966 -IGNvbW1vZGl0aWVz 51967 -IFByZXNlbnRhdGlvbg== 51968 -KSYmKA== 51969 -IE1hcnRoYQ== 51970 -IENhdGhvbGljcw== 51971 -IE1vbmQ= 51972 -0L7QsdGL 51973 -X2Fic29sdXRl 51974 -IGFzaGFtZWQ= 51975 -cG9uc29ycw== 51976 -dGFs 51977 -IHNhZG5lc3M= 51978 -IHB1w7I= 51979 -RmFkZQ== 51980 -LXByZXZpZXc= 51981 -IFJlcXVlc3Rz 51982 -IENhbHZpbg== 51983 -aG9ybg== 51984 -UmV1c2VJZGVudGlmaWVy 51985 -KHByb3ZpZGVy 51986 -L2FwcHM= 51987 -aW1lbw== 51988 -CUNsYXNz 51989 -U2Ftc3VuZw== 51990 -IFdPUkxE 51991 -IGNpbm5hbW9u 51992 -ZG90ZW52 51993 -IElVc2Vy 51994 -IERFVg== 51995 -X0NoYXI= 51996 -LmliYXRpcw== 51997 -ZXRp 51998 -L21l 51999 -c3N0 52000 -LnN5bQ== 52001 -IFJ1Z2J5 52002 -LW1hc3Rlcg== 52003 -YWphcg== 52004 -IFlFQVI= 52005 -IG9kcA== 52006 -IFJvbGVz 52007 -IGJpcGFydGlzYW4= 52008 -YWlsbGU= 52009 -IGJsb2NrZXI= 52010 -IGdyZWVucw== 52011 -LlNFQ09ORFM= 52012 -IGJlbGlldmVycw== 52013 -IExpa2Vz 52014 -RkxPQVQ= 52015 -IG1haw== 52016 -IGdjYw== 52017 -4pWQ4pWQ 52018 -KCJ+Lw== 52019 -U0NSSVBUT1I= 52020 -IHRvbm5lcw== 52021 -IFNhbmc= 52022 -IHRyYW5zcG9zZQ== 52023 -ZW5uYWk= 52024 -UHJlZA== 52025 -IHNvbGx0ZQ== 52026 -LmdpdGh1YnVzZXJjb250ZW50 52027 -KHByaW50 52028 -IEhvbGU= 52029 -55yL 52030 -YWRnZXQ= 52031 -IHByb21wdHM= 52032 -IGdlbmV0aWNhbGx5 52033 -IEhvZA== 52034 -IHZlcnRpY2FsbHk= 52035 -X2NvbnRyb2xz 52036 -0YHRgtCw0L0= 52037 -Iil7DQo= 52038 -JHRpdGxl 52039 -IH0pLAoK 52040 -IHN0YXRld2lkZQ== 52041 -IENvcnJlc3BvbmQ= 52042 -IEF0dHI= 52043 -aXRhbnQ= 52044 -RWxlbWVudFR5cGU= 52045 -IG91dHdhcmQ= 52046 -IGZhbWlsaWE= 52047 -KGFydGljbGU= 52048 -IGJsYXQ= 52049 -wqAK 52050 -IGdsR2V0 52051 -IFJlY2VpdmVy 52052 -ICUt 52053 -YWRhbQ== 52054 -V2lubmVy 52055 -IHRhaWxvcg== 52056 -X3B3ZA== 52057 -ZXJ0ZW4= 52058 -U3Rhbg== 52059 -CWFsbA== 52060 -YWxpdmU= 52061 -c3RydG90aW1l 52062 -77+9cw== 52063 -c2Vzc2lvbnM= 52064 -JGNvbm4= 52065 -YXNzaXN0 52066 -IGNoYXR0aW5n 52067 -IE1hbnQ= 52068 -ICVA 52069 -ICIiKTsKCg== 52070 -IGRndg== 52071 -IO2VqA== 52072 -LnJlcGVhdA== 52073 -X01lc3NhZ2U= 52074 -IGFkdmlzZXJz 52075 -L3BhdGg= 52076 -IGtlcw== 52077 -KX08Lw== 52078 -TWlzYw== 52079 -IGJzb24= 52080 -IHRyaW1tZWQ= 52081 -IEFjaw== 52082 -VmVydGV4QXR0cmli 52083 -57Si 52084 -dWF0ZXM= 52085 -Lm15c3Fs 52086 -IGRlc3Rpbg== 52087 -IHByb2Js 52088 -KENvbnN0YW50 52089 -YXNzZXM= 52090 -LWltYWdlcw== 52091 -X0FSRUE= 52092 -X18qLw== 52093 -W10o 52094 -IHNpZ25Jbg== 52095 -xJE= 52096 -eHI= 52097 -YWhpcg== 52098 -LmZpcmVzdG9yZQ== 52099 -IHNlcXVlbnRpYWw= 52100 -IElkZWE= 52101 -LWJhc2lj 52102 -X3BhZw== 52103 -IGluc3RhZ3JhbQ== 52104 -b3Ryb24= 52105 -X2FsaWdubWVudA== 52106 -XFxcXA== 52107 -LkZhY3Rvcnk= 52108 -LnJ1bGU= 52109 -LmNoZGly 52110 -IGxpYnJv 52111 -KGdhbWVPYmplY3Q= 52112 -LlRvb2xTdHJpcEJ1dHRvbg== 52113 -IGRpc2NvdmVycw== 52114 -LkFyZ3M= 52115 -ZG9i 52116 -IHZu 52117 -4oaS 52118 -IGTDvA== 52119 -IFhN 52120 -IGFsdW1uaQ== 52121 -IGhvbmU= 52122 -IHNlY3VyZWx5 52123 -X2Ryb3Bkb3du 52124 -RGlzY2xhaW1lcg== 52125 -IGR6aQ== 52126 -KHRpbWVzdGFtcA== 52127 -Jyld 52128 -IGN1bHRpdmF0aW9u 52129 -Li4uCgoK 52130 -IFRyZWF0eQ== 52131 -IERpc3M= 52132 -IGNvbmZsaWN0aW5n 52133 -LmdldFNlbGVjdGlvbg== 52134 -IHBsYXlhYmxl 52135 -IFNpbGs= 52136 -IEVxdWFsaXR5 52137 -IG1veQ== 52138 -IGZsYXR0 52139 -IG1vdGl2ZXM= 52140 -UGVyZmVjdA== 52141 -LmV4aXN0 52142 -IHR3ZWFr 52143 -IG9taXQ= 52144 -IFR3aWxpZ2h0 52145 -IGtpc3Npbmc= 52146 -IGNocmlzdGlhbg== 52147 -KFNF 52148 -X2RlZmluZQ== 52149 -IFBlbmc= 52150 -U29ydGVk 52151 -J2lu 52152 -TG9ncw== 52153 -4buHbg== 52154 -IG55bG9u 52155 -RHVtcA== 52156 -SW1hZ2luZQ== 52157 -cmVuYW1l 52158 -IGJlZm9yZWhhbmQ= 52159 -cHlnYW1l 52160 -IGJweQ== 52161 -IERq 52162 -IHRpdHVsbw== 52163 -IG5sdGs= 52164 -IFNjaG1pZHQ= 52165 -IENhdg== 52166 -KG9uZQ== 52167 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 52168 -LmdldE1vZGVs 52169 -IFB0 52170 -YXRvaQ== 52171 -LmxvY2Fscw== 52172 -YnVyc2VtZW50 52173 -UHJvdmluY2U= 52174 -IEFwcHJvdmVk 52175 -KCk8PA== 52176 -w7NyaWE= 52177 -dXNjaA== 52178 -IEplbm55 52179 -YXJyYW50cw== 52180 -IExpYmVydA== 52181 -TG9yZA== 52182 -IFJlbW92ZWQ= 52183 -X2NvZGVj 52184 -LmJ1bmRsZQ== 52185 -IEdvbnphbGV6 52186 -b3BlcnM= 52187 -neWni+WMlg== 52188 -ZXR0aW5n 52189 -IGdvZGRlc3M= 52190 -cmlwZQ== 52191 -IG11c2N1bGFy 52192 -CQkJCQkJCQkg 52193 -IEh1Z28= 52194 -IG1lam9yZXM= 52195 -bG9pZA== 52196 -cml0ZWxu 52197 -Z2lz 52198 -YWRkb24= 52199 -ICgoKCg= 52200 -YXBwb2ludG1lbnQ= 52201 -cmVzZXJ2ZWQ= 52202 -CWZyaWVuZA== 52203 -X2F2YXRhcg== 52204 -Qk9PTEU= 52205 -YWhp 52206 -LUVORA== 52207 -IGlmZg== 52208 -w7Ni 52209 -IEJydW5v 52210 -cm93c2FibGU= 52211 -IFBvaXNvbg== 52212 -KGZsYWdz 52213 -dXJ0bGVz 52214 -IEFuaW1l 52215 -IG1pZ3JhbnQ= 52216 -CXN0cmNhdA== 52217 -KHJlcGx5 52218 -IFJlZnVnZQ== 52219 -IEJX 52220 -ZWZ1bA== 52221 -JHZhbHVl 52222 -ZmVk 52223 -ICAgICAgICAgICAgICAgICAgICAgICAK 52224 -6LWE 52225 -KGNt 52226 -IHZ1bG5lcmFiaWxpdGllcw== 52227 -IFsoJw== 52228 -IHVuYmVsaWV2YWJsZQ== 52229 -c3RyaWN0aW9u 52230 -ZW50aWV0aA== 52231 -IHByYXlpbmc= 52232 -Q2xhaW1z 52233 -IGthdWZlbg== 52234 -bsOp 52235 -IHBvaXNvbmluZw== 52236 -Y29sbGVjdGlvbnM= 52237 -IGluaXRTdGF0ZQ== 52238 -IFNldmVyaXR5 52239 -IGNvbnRlbnRpb24= 52240 -IAoJCg== 52241 -LmNvbnRyb2xsZXJz 52242 -c3RydWN0dXJlZA== 52243 -aWN0aW0= 52244 -IE9iZXI= 52245 -IC8qI19f 52246 -X09U 52247 -IEFtZXJpY2Fz 52248 -IEFkYQ== 52249 -UHJvZHV0bw== 52250 -Lm11bHRp 52251 -IGdyYXBl 52252 -YmVn 52253 -5p+l6K+i 52254 -IHF1YXJ0eg== 52255 -IFJvbWFuY2U= 52256 -IE1pZHdlc3Q= 52257 -IGhvdXNlZA== 52258 -IGZ1cm5pc2g= 52259 -aWNvbnQ= 52260 -LnVuc2hpZnQ= 52261 -b3RyZQ== 52262 -IMO6bg== 52263 -aXBwbGU= 52264 -IHN1YnVyYg== 52265 -dWFsaQ== 52266 -Vm9pY2U= 52267 -LklzQW55 52268 -LGNvbHVtbg== 52269 -IFByb3NlYw== 52270 -SURB 52271 -CXBvc3Q= 52272 -cHRvbXM= 52273 -dsOp 52274 -IEluZ3JlZGllbnRz 52275 -w7ZmZg== 52276 -Lm9wZXJhdG9y 52277 -IDw8PQ== 52278 -bGFzdGlj 52279 -IHJlc2VtYmxl 52280 -VW5hdXRob3JpemVk 52281 -IHR1dHRv 52282 -X1NXSVRDSA== 52283 -X1JFQURZ 52284 -fT0= 52285 -bm93bGVkZ2U= 52286 -IGFwcGVuZGVk 52287 -dW5nYW4= 52288 -4oCZZW4= 52289 -IExvcmVu 52290 -cHVibGlzaGVy 52291 -IE1H 52292 -fSwi 52293 -IFdhbHNo 52294 -VGVtcGxhdGVz 52295 -X3NvY2lhbA== 52296 -IHBhcmlzaA== 52297 -IFNwbA== 52298 -bWluYXRlZA== 52299 -KEZBTFNF 52300 -IGZvcmVmcm9udA== 52301 -bW9kaXR5 52302 -IGJpbGF0ZXJhbA== 52303 -IGNvbXBldGl0 52304 -IGNhbmRsZXM= 52305 -LmRw 52306 -IGNvbGxlY3Rz 52307 -dGVsZWZvbm8= 52308 -IGF0dGVudA== 52309 -IExlbW9u 52310 -aXphZGE= 52311 -IHRoZXJhcGllcw== 52312 -IHBhcmFkb3g= 52313 -IHRhcw== 52314 -LXN1Ym1pdA== 52315 -ZWtlcg== 52316 -SU5hdmlnYXRpb25Db250cm9sbGVy 52317 -IG1ldGF2YXI= 52318 -IHNld2luZw== 52319 -IFppbWJhYndl 52320 -IGxhd2Z1bA== 52321 -IGxvcmU= 52322 -IExvYWRz 52323 -INGB0L7Qt9C0 52324 -LnByb21pc2U= 52325 -IEZhY2Vz 52326 -LlBsYXRmb3Jt 52327 -LmdldExvY2F0aW9u 52328 -IHRyb3VibGluZw== 52329 -IHbDrWRlbw== 52330 -IEZlYXR1cmluZw== 52331 -5Lqn 52332 -cWVk 52333 -IG9uQmluZA== 52334 -IHRvZGRsZXI= 52335 -Q2xv 52336 -RGl2aXNpb24= 52337 -LWdhbGxlcnk= 52338 -IEdlbGQ= 52339 -c3BlY2lmaWM= 52340 -RmllbGROYW1l 52341 -X2V4Y2Vs 52342 -XGh0ZG9jcw== 52343 -IERW 52344 -ICY6 52345 -IHR3aWc= 52346 -IENvbmNlcm4= 52347 -IHNob3RndW4= 52348 -IG5pY2tlbA== 52349 -IEx1eHVyeQ== 52350 -X0tFWVM= 52351 -Lm5weQ== 52352 -xa8= 52353 -IGZvcmVoZWFk 52354 -zrI= 52355 -IGVuZGFuZ2VyZWQ= 52356 -L3RoZQ== 52357 -cGlwZWxpbmU= 52358 -xbE= 52359 -bmVv 52360 -RXhwbG9yZQ== 52361 -U3BlY1dhcm4= 52362 -IGludGVyY2hhbmdl 52363 -KHBp 52364 -YmlydGhkYXk= 52365 -RGF0YVJvdw== 52366 -IFNQUg== 52367 -IG9zdGU= 52368 -ICJ+ 52369 -YXRpc2ZhY3Rpb24= 52370 -Tkg= 52371 -b3Jkbw== 52372 -LWZvY3VzZWQ= 52373 -J0E= 52374 -lok= 52375 -LmJlc3Q= 52376 -IFNwZWNpZmljYXRpb24= 52377 -Lz4uCgo= 52378 -b2dlbmVzaXM= 52379 -IE9QVElPTlM= 52380 -dXB0b29scw== 52381 -IG1pbGl0YW50 52382 -IGV4aXRlZA== 52383 -aWdhcg== 52384 -IENPTU0= 52385 -IERpc3Bvc2FibGU= 52386 -YXljYXN0 52387 -IHJvd3NwYW4= 52388 -IHN5bnRoZXM= 52389 -IHNvbmRlcm4= 52390 -IDwhLS08 52391 -IEVuZGU= 52392 -LnZhcmlhYmxlcw== 52393 -IGNvbnNlcXVlbnRseQ== 52394 -c2Rr 52395 -U3VwcGx5 52396 -cmVzcG9uc2l2ZQ== 52397 -T3BlbmluZw== 52398 -cGhvdA== 52399 -IH1c 52400 -IGJ1bGxzaGl0 52401 -IGJlYWNvbg== 52402 -X3NhdA== 52403 -IHNuYXBz 52404 -IEdIeg== 52405 -TE9ORw== 52406 -PHBhaXI= 52407 -IFsKCg== 52408 -IFZlcmc= 52409 -IEVpbmU= 52410 -L3Bvc3Rz 52411 -IGFyYWI= 52412 -IHN1bWE= 52413 -44Oz44OI 52414 -IHNjYXJj 52415 -IG9sZWg= 52416 -ID8/Pw== 52417 -IE9mZmVycw== 52418 -eGVk 52419 -IGZ1bGxXaWR0aA== 52420 -LWFjdGlvbnM= 52421 -T3V0ZXI= 52422 -IEV4cG8= 52423 -w6lyZXI= 52424 -Lkhl 52425 -REg= 52426 -IGhpbA== 52427 -IE1pbGxlbm4= 52428 -0LXQvdGM 52429 -SWNl 52430 -X2dyYXk= 52431 -INC/0L7Qu9GD0Yc= 52432 -IFB1bms= 52433 -IHRpbWV2YWw= 52434 -IGlzYQ== 52435 -IENIdG1s 52436 -LkRhdGFQcm9wZXJ0eU5hbWU= 52437 -IGRpeQ== 52438 -dG91cg== 52439 -IGpUZXh0RmllbGQ= 52440 -IGplbGx5 52441 -IGFra2E= 52442 -LWVyYQ== 52443 -RGVwcmVjYXRlZA== 52444 -X0lNUEw= 52445 -IE1vbnRocw== 52446 -X0lURVI= 52447 -IGFydGU= 52448 -IEhlYWRpbmc= 52449 -IEJvaA== 52450 -IHByYWc= 52451 -IGRvd25zdHJlYW0= 52452 -IEJPQVJE 52453 -X2tleXdvcmRz 52454 -IE1ldHJvRnJhbWV3b3Jr 52455 -KS0o 52456 -PEV2ZW50 52457 -4bqldA== 52458 -IFByZWNpc2lvbg== 52459 -IE1SSQ== 52460 -aGVyZW5jZQ== 52461 -aXhv 52462 -KSkpewo= 52463 -KCk/Pg== 52464 -IHNhYXQ= 52465 -IFdhcmVob3VzZQ== 52466 -X2F0b21pYw== 52467 -IHZvaWNlZA== 52468 -SXRlbUNsaWNr 52469 -ICAgICAgCQ== 52470 -LlJlc3VsdFNldA== 52471 -L3BsdWdpbg== 52472 -IGhhbGxz 52473 -PWZvcm0= 52474 -IFdhZ25lcg== 52475 -ZW1haWxz 52476 -JSUK 52477 -VU5LTk9XTg== 52478 -IFJpbQ== 52479 -dWludHB0cg== 52480 -IExpYmVyYWxz 52481 -IHRlcnJpdG9yaWFs 52482 -IE11cmRlcg== 52483 -IExhZGVu 52484 -IHByZXNpZGVudGU= 52485 -KGNhcA== 52486 -IH0sewo= 52487 -YXZvdXJpdGU= 52488 -ZmluZEFsbA== 52489 -IGFwcGxhdWQ= 52490 -IOuplA== 52491 -L3Bob3Rv 52492 -X3N5bg== 52493 -LndhbGs= 52494 -IHN1bnNoaW5l 52495 -IHN0dWJib3Ju 52496 -IGRvd25zaWRl 52497 -IExURQ== 52498 -LWJ1aWxkaW5n 52499 -UXVlcnlCdWlsZGVy 52500 -X2Rpc2FibGVk 52501 -VGVycg== 52502 -YWtyYQ== 52503 -UmVmcmVzaGluZw== 52504 -X3Byb2Jz 52505 -IGZvbGw= 52506 -PmI= 52507 -IGNvbGxhdGVyYWw= 52508 -JGVycm9y 52509 -IGFjb21wYW4= 52510 -X2l2 52511 -K2Q= 52512 -YWp1 52513 -IOKd 52514 -c3VybmFtZQ== 52515 -LmFydGljbGU= 52516 -IGJpY3k= 52517 -IjoKCg== 52518 -Pjw/PSQ= 52519 -0LrQu9GO0Yc= 52520 -ZWNvbWU= 52521 -RmluZGluZw== 52522 -KHBk 52523 -IHJlY3Rhbmd1bGFy 52524 -ZXN0bw== 52525 -aWhpbA== 52526 -PScnKQo= 52527 -IG1hbnNpb24= 52528 -X2ZpbHRlcmVk 52529 -YW5lZA== 52530 -UFJPRFVDVA== 52531 -TE9HWQ== 52532 -X2ly 52533 -LlJlbW90ZQ== 52534 -IGV4ZWN1dGVz 52535 -b3RlY2hub2xvZ3k= 52536 -IFBST0NFU1M= 52537 -IHJvd0luZGV4 52538 -Z2V0WA== 52539 -TXV0 52540 -aW5za3k= 52541 -KHN0cmluZ3M= 52542 -IE1veg== 52543 -Rmxvb3I= 52544 -LlN0cnVjdA== 52545 -X3ByZWRpY3Rpb24= 52546 -IGNhcnJpYWdl 52547 -IGNvbGxlY3RvcnM= 52548 -IFdoZWVscw== 52549 -IGJ1bmRsZWQ= 52550 -YXhlZA== 52551 -a29s 52552 -X2Nyb3A= 52553 -IGJsb29t 52554 -QmVzaWRlcw== 52555 -IG92ZXJyaWRkZW4= 52556 -IHN1Ym5ldA== 52557 -aWVuaWE= 52558 -Kj46Og== 52559 -IFByaW1pdGl2ZQ== 52560 -IOag 52561 -LkNoYXJhY3Rlcg== 52562 -6KGo56S6 52563 -IEFESEQ= 52564 -Uk9Z 52565 -SmFwYW5lc2U= 52566 -T1VT 52567 -OlVJQ29udHJvbEV2ZW50 52568 -IFBBTA== 52569 -aXphY2lvbg== 52570 -IGNoZXJjaGU= 52571 -b3J0aW5n 52572 -IG9yZ2Fz 52573 -LlV0Yw== 52574 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 52575 -XERvbWFpbg== 52576 -T1JB 52577 -IHRlcnJhY2U= 52578 -IHByaXM= 52579 -CQkJCQkJCQkJCg== 52580 -IHJhaWRz 52581 -X2luY3JlbWVudA== 52582 -IHVuanVzdA== 52583 -JG9wdGlvbnM= 52584 -b25DaGFuZ2U= 52585 -Qmxvb2Q= 52586 -RmlsbQ== 52587 -IGhhbmRpbmc= 52588 -IG11Zw== 52589 -U09MRQ== 52590 -44OV 52591 -aWNvbmR1Y3Rvcg== 52592 -IElzbGFtaXN0 52593 -ICIiKTsNCg== 52594 -LW92ZXJsYXk= 52595 -LGNvbA== 52596 -6Zw= 52597 -YXJyaW5ncw== 52598 -X2NvbnRyYWN0 52599 -CWxs 52600 -cGlw 52601 -X2VtYmVkZGluZw== 52602 -IHBlcm1pdGU= 52603 -IG1vZGVt 52604 -IHRyaWdnZXJpbmc= 52605 -KGh3bmQ= 52606 -LiIpXQo= 52607 -IHNhbnQ= 52608 -IGV4dGluY3Rpb24= 52609 -IGNsYXNoZXM= 52610 -LkF1ZGlv 52611 -IHN1bw== 52612 -Lm11bHQ= 52613 -IHNlYXNvbmVk 52614 -LlZhckNoYXI= 52615 -cG93ZXJlZA== 52616 -ImNvbnRleHQ= 52617 -IG1lbmM= 52618 -KEdyYXBoaWNz 52619 -JHdoZXJl 52620 -IHJlY3VwZXI= 52621 -YWNrbGU= 52622 -IG5ld0RhdGE= 52623 -IEJyZWFraW5n 52624 -ZXJnZWQ= 52625 -IENQUFVOSVQ= 52626 -IE11bGw= 52627 -IGtvbW10 52628 -IExlZWRz 52629 -JywnPQ== 52630 -Lm5leHRUb2tlbg== 52631 -IFJpZw== 52632 -UkVUVVJO 52633 -CXRpbWVy 52634 -fV97 52635 -IE1hcmluYQ== 52636 -IHNsb2dhbg== 52637 -SVpFRA== 52638 -T3BlbkdM 52639 -X1BhZ2U= 52640 -YXRpdmFz 52641 -IGhhemFyZHM= 52642 -J3ZhbHVl 52643 -IGNvcnBzZQ== 52644 -IEZsb3dlcnM= 52645 -X29ubGluZQ== 52646 -ZGFs 52647 -IENvbGxpc2lvbg== 52648 -w6BuZw== 52649 -IGZlcnJ5 52650 -IHBva2U= 52651 -IFRvdXJpc20= 52652 -aW5lcmFyeQ== 52653 -L1NldA== 52654 -LkVtcGxveWVl 52655 -PkA= 52656 -LHZhbA== 52657 -IE1pbGY= 52658 -YXZleg== 52659 -UmV0cnk= 52660 -LiIv 52661 -IHJvdW5kaW5n 52662 -LXBsYWNlbWVudA== 52663 -IGNlcnY= 52664 -TWV4 52665 -IE1zZ0JveA== 52666 -X3Npbms= 52667 -bWFuaWE= 52668 -X2NyZWRpdA== 52669 -R3VhcmRhcg== 52670 -IHZhbml0eQ== 52671 -IGltbXV0YWJsZQ== 52672 -IGNvbnRhbWluYXRlZA== 52673 -0LrQsNC3 52674 -5Liy 52675 -YWNoYQ== 52676 -IGhhdGg= 52677 -IGVudW1lcmF0aW9u 52678 -LmdldEJ5 52679 -4bq/dA== 52680 -IERhbw== 52681 -b2JpZXJubw== 52682 -IEd1dA== 52683 -X1BJUEU= 52684 -LmFkdg== 52685 -IEd1dGVuYmVyZw== 52686 -YWRo 52687 -66y4 52688 -ZnVzYw== 52689 -LlZL 52690 -cHRh 52691 -IEVNUA== 52692 -LkZpcnN0TmFtZQ== 52693 -IHJlYWxpemVz 52694 -LmNn 52695 -IHVuaXRl 52696 -UExJVA== 52697 -IEFiZHVs 52698 -IE1FRA== 52699 -UkFJTlQ= 52700 -IHF1ZXN0YQ== 52701 -c3RkaW4= 52702 -IGNhbG9yaWU= 52703 -CWdsQmluZA== 52704 -IGFybWE= 52705 -eWxsYW5k 52706 -T01Q 52707 -LXE= 52708 -IEtoYWw= 52709 -c2FsYXJ5 52710 -CUFORA== 52711 -c2dp 52712 -X3RoYW4= 52713 -LWJ1aWx0 52714 -ICsvLQ== 52715 -IG5hcmdz 52716 -X2xhdW5jaA== 52717 -IFNR 52718 -em9u 52719 -IEJlbmVk 52720 -X3VuaW9u 52721 -PigpOw0KDQo= 52722 -IFNpbXM= 52723 -IERhdGVz 52724 -CUNvbm5lY3Rpb24= 52725 -IFBlcmM= 52726 -Z3JhbnQ= 52727 -YW1waWw= 52728 -IGFnZ3JlZ2F0aW9u 52729 -ZXNlbGVjdA== 52730 -X1NVUA== 52731 -KHsKCg== 52732 -Lm9t 52733 -IHdt 52734 -LmNvbnRyYWN0 52735 -LU9yaWdpbg== 52736 -IGdlbWU= 52737 -ZnJlZXpl 52738 -TlVNQkVS 52739 -LmN1cnI= 52740 -IEdsYWQ= 52741 -c2xh 52742 -IFJlYg== 52743 -0LXRgdGC0LLQvg== 52744 -YXJib24= 52745 -L2NvbnRyb2xsZXJz 52746 -U2xvdHM= 52747 -LmRlZXBjb3B5 52748 -RlVMTA== 52749 -dWlyZQ== 52750 -QHN0dWRlbnQ= 52751 -4LmJ4Lit 52752 -VHJhbnNsYXRvcg== 52753 -IHByZWZlcmFibHk= 52754 -Y2hlbWlzdHJ5 52755 -IEphY29icw== 52756 -bmFy 52757 -ICgiXA== 52758 -bmVhcg== 52759 -aWZpcXVl 52760 -CWNvbHVtbg== 52761 -IG1pbnV0b3M= 52762 -aWdlcw== 52763 -IGVzdGFibGU= 52764 -LWRpc2M= 52765 -KENoYXI= 52766 -a292 52767 -ZXhhbXBsZXM= 52768 -X18oIg== 52769 -INC60LDQug== 52770 -IEJvcmlz 52771 -KGR4 52772 -c3By 52773 -IG92ZXJoYXVs 52774 -YXRvb24= 52775 -IEhhcmxleQ== 52776 -aWNhbWVudGU= 52777 -4paI4paI4paI4paI 52778 -ZXZpdHk= 52779 -dXNoZXI= 52780 -LlZpc3VhbFN0dWRpbw== 52781 -V2F2ZQ== 52782 -IE5vcm1hbGx5 52783 -c3Rvb2Q= 52784 -b3JuaW5ncw== 52785 -IGhhbmRtYWRl 52786 -KGxvZ2dpbmc= 52787 -IGNhcmNpbg== 52788 -YWNqYQ== 52789 -IHN1cGVycw== 52790 -IHNpZWdl 52791 -CUlm 52792 -IElMb2dnZXI= 52793 -VUFSVA== 52794 -QW5pbWF0aW9uRnJhbWU= 52795 -IHRhcGVz 52796 -IGFpZHM= 52797 -IENvbG9uZWw= 52798 -dmVlZG9y 52799 -IG1kbA== 52800 -cGhvbg== 52801 -RGlzbWlzcw== 52802 -QXZhaWxhYmlsaXR5 52803 -VW5pZm9ybUxvY2F0aW9u 52804 -IGlkZWFscw== 52805 -cXVldHRl 52806 -a2VpdGVu 52807 -IEVNQUlM 52808 -IE5lYg== 52809 -IHN1bW1vbmVk 52810 -IGdvdmVybm1lbnRhbA== 52811 -IEhvcnJvcg== 52812 -Y2hhbmdpbmc= 52813 -IEFjdGl2YXRl 52814 -SWxs 52815 -PHRib2R5 52816 -Y3JlYXRpdmU= 52817 -IEJMRQ== 52818 -IG1hZG5lc3M= 52819 -T3JOaWw= 52820 -IGhpbg== 52821 -xZM= 52822 -LkdldEtleQ== 52823 -X2NvbnNvbGU= 52824 -Ik91cg== 52825 -IGd1aW50 52826 -IGFtaQ== 52827 -IHJlZmxlY3RpdmU= 52828 -IGNyYWNraW5n 52829 -IFJp 52830 -UkFM 52831 -dXJzZWQ= 52832 -cHVyZQ== 52833 -IHJlcGFpcmVk 52834 -IHRpZ2Vy 52835 -IE5pY29sYXM= 52836 -VnM= 52837 -bnRo 52838 -LmV4cHJlc3Npb24= 52839 -IHNlYXM= 52840 -X0FDQ0VQVA== 52841 -IGZvcmM= 52842 -IEZyYXU= 52843 -IHRocmVzaA== 52844 -IM+A 52845 -KEJBU0U= 52846 -X09wZW4= 52847 -V3VudXNlZA== 52848 -IERvbWVzdGlj 52849 -KHByaXY= 52850 -Z3Vlc3M= 52851 -Ly8hCg== 52852 -Z2V0SXRlbQ== 52853 -KCkpCgoK 52854 -bXV0YXRpb25z 52855 -IHN0cw== 52856 -IGRlbWVudGlh 52857 -c3Bva2Vu 52858 -JHBhcmFtcw== 52859 -IHBhdHJvbnM= 52860 -IHJ1bndheQ== 52861 -IEJVWQ== 52862 -Lldhcm5pbmc= 52863 -IG5ldXRyYWxpdHk= 52864 -emhvdQ== 52865 -0YDQsNGJ 52866 -YWt0ZXI= 52867 -IENvbnN0cnVjdG9ycw== 52868 -w5NO 52869 -IFByb2dyZXNzaXZl 52870 -IEJ1cmdlcg== 52871 -IGluY3VycmVk 52872 -IGltcGxpY2l0bHk= 52873 -X2Vudmlyb25tZW50 52874 -IGV4YWNlcmI= 52875 -IGVuZHVyaW5n 52876 -c2lj 52877 -IFBhcnRpY2lwYW50cw== 52878 -X0Jsb2Nr 52879 -IGVucm9sbA== 52880 -X2VtcGxveWVl 52881 -IFBlcHBlcg== 52882 -bGF1Z2h0ZXI= 52883 -44OW 52884 -J107Pz4= 52885 -PScu 52886 -KHJlbmFtZQ== 52887 -IHNoZWx0ZXJz 52888 -IEFNQQ== 52889 -X2dhcA== 52890 -IFJFVVRFUlM= 52891 -eGFtcHA= 52892 -T01JQw== 52893 -IHBlZGlkbw== 52894 -IGTDqXZlbG9w 52895 -X18oLyoh 52896 -X29k 52897 -d2VyZQ== 52898 -X051bWJlcg== 52899 -X211bHRpcGxpZXI= 52900 -S0VFUA== 52901 -IHNob3dlcnM= 52902 -IG1hZ2U= 52903 -IHNpbm8= 52904 -Y3Jvdw== 52905 -LmlkeA== 52906 -X25vdGljZQ== 52907 -dWVpbA== 52908 -IG15cmlhZA== 52909 -IEF2YWlsYWJpbGl0eQ== 52910 -Y2VudHJhbA== 52911 -IEFCT1VU 52912 -IGluY29ycG9yYXRpbmc= 52913 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCg== 52914 -X3dpZGdldHM= 52915 -IHN5c3RlbUZvbnRPZlNpemU= 52916 -w7ZydA== 52917 -L2pwZWc= 52918 -IFNNVFA= 52919 -KGJyb3dzZXI= 52920 -Z3Vucw== 52921 -c2V0dw== 52922 -X0FWQUlMQUJMRQ== 52923 -IGluY29ycG9yYXRlcw== 52924 -L2FuZHJvaWQ= 52925 -eXg= 52926 -5biD 52927 -X2xhYg== 52928 -IGxlYWtpbmc= 52929 -IEhpbnQ= 52930 -w7xuY2hlbg== 52931 -LlNjYWxl 52932 -IGZpcmV3b3Jrcw== 52933 -IGxQYXJhbQ== 52934 -YnNk 52935 -YXhvbg== 52936 -KHByZWRpY3Q= 52937 -Q29uZ3JhdHVsYXRpb25z 52938 -IFNwZWN0cnVt 52939 -SVJD 52940 -IEFkbWluaXN0cmF0aXZl 52941 -IGltcHJpc29uZWQ= 52942 -UlNwZWM= 52943 -IHJldGFpbnM= 52944 -IHNldHRsaW5n 52945 -IGNpdGF0aW9ucw== 52946 -IFdvcmxkcw== 52947 -c3RyY29udg== 52948 -b3VzYW5k 52949 -IEJlZ2lubmluZw== 52950 -IEFuZHJld3M= 52951 -IFNoYXJvbg== 52952 -RXhlY3V0aW5n 52953 -Z3JvdXBJZA== 52954 -YWRkRmllbGQ= 52955 -IGV4cGFuZHM= 52956 -IGtpbG9tZXRyZXM= 52957 -bGlua3k= 52958 -IGdycA== 52959 -SU5BVElPTg== 52960 -QnJpdGlzaA== 52961 -IGNvbXBvcnQ= 52962 -LkRhdGFHcmlkVmlld0NvbHVtbg== 52963 -IFByb2R1Y3Rpb25z 52964 -aWxkZW4= 52965 -IHVuaXg= 52966 -X2dhbGxlcnk= 52967 -X1BST1ZJRA== 52968 -b3JkZXJpbmc= 52969 -X2Fubg== 52970 -Ymg= 52971 -LkRlc2lnbg== 52972 -IHRyZWZmZW4= 52973 -IHVuZGVybGluZQ== 52974 -X251bXM= 52975 -7ZWc64uk 52976 -KXY= 52977 -dXNpemU= 52978 -IGRpc2FwcGVhcmFuY2U= 52979 -VG9Cb3VuZHM= 52980 -IHBjbA== 52981 -IFdpbm5pcGVn 52982 -IFNoZXJtYW4= 52983 -X2xhbWJkYQ== 52984 -bmFudA== 52985 -IHJvb3RWaWV3 52986 -LkZsYWdz 52987 -IGNlbnNvcnNoaXA= 52988 -c2VudGVuY2U= 52989 -LnJlYWRJbnQ= 52990 -X2Fzc2lnbm1lbnQ= 52991 -IHZlcnNjaGllZA== 52992 -IEZyYWN0aW9u 52993 -IG5hdGlvbmFsaXN0 52994 -IGp1ZWdv 52995 -IERlYWxlcg== 52996 -IHByZWRpY3Rpbmc= 52997 -YXVwdA== 52998 -aGVsbQ== 52999 -X1BSSUNF 53000 -X0RT 53001 -KCIjew== 53002 -bGlmdGluZw== 53003 -IHBvc2luZw== 53004 -IE5TTXV0YWJsZURpY3Rpb25hcnk= 53005 -IHNtYXNo 53006 -IGFraW4= 53007 -IGNhbXB1c2Vz 53008 -IE91dGxpbmU= 53009 -IEVsYXN0aWM= 53010 -X0NoZWNrZWRDaGFuZ2Vk 53011 -KElFbnVtZXJhYmxl 53012 -c3F1ZWV6ZQ== 53013 -cHR1bmU= 53014 -X0ZST05U 53015 -bWg= 53016 -IOyDneyEsQ== 53017 -UnVuV2l0aA== 53018 -IHR1cm5vdXQ= 53019 -c2libGluZ3M= 53020 -KWU= 53021 -X0FSR1VNRU5U 53022 -IEdyaWRCYWdDb25zdHJhaW50cw== 53023 -X1BPT0w= 53024 -LlJJR0hU 53025 -aWdnaW5z 53026 -dGVsZXBob25l 53027 -XEV4dGVuc2lvbg== 53028 -IEFyaXN0 53029 -aXR1cg== 53030 -IGZyaWVz 53031 -X2R1cA== 53032 -RXhwYW5kZWQ= 53033 -LXJv 53034 -IFdvcmxkd2lkZQ== 53035 -IENvcms= 53036 -w7Ns 53037 -TGlt 53038 -IGRlbm4= 53039 -UHJldHR5 53040 -IGZ5 53041 -VHJpYW5nbGU= 53042 -RmVhdHVyZWQ= 53043 -KENvbW1vbg== 53044 -X2VmZg== 53045 -ICIiDQo= 53046 -4bubaQ== 53047 -X0xJTkVBUg== 53048 -IFJpY2E= 53049 -IGNhZsOp 53050 -IGFwcGVsbA== 53051 -IG5pdmVhdQ== 53052 -ICYs 53053 -IGZhYnJpY3M= 53054 -X1BsYXllcg== 53055 -IGh5Z2llbmU= 53056 -IGRpc2FzdHJvdXM= 53057 -IHNoYXJlZEluc3RhbmNl 53058 -X3BpdGNo 53059 -cno= 53060 -ZW5tZW50 53061 -TmVhcg== 53062 -X1NUQVRT 53063 -IHN0YWlu 53064 -IEROQw== 53065 -IGlzc3U= 53066 -Xks= 53067 -CXRyZWU= 53068 -X2Jsaw== 53069 -c2V6 53070 -bGFpbg== 53071 -YW11 53072 -X293bmVk 53073 -VVNBUlQ= 53074 -Lmhhc0NsYXNz 53075 -SVNPTg== 53076 -IGZvZQ== 53077 -dXNoZWQ= 53078 -X1VOU0lHTkVE 53079 -IGluZGV4aW5n 53080 -IEZpcmViYXNlQXV0aA== 53081 -IGxpdGVyYWN5 53082 -IFNVUg== 53083 -IENvbHRz 53084 -YmVjdWU= 53085 -IEludHJv 53086 -IGNoYW90aWM= 53087 -IGFuaQ== 53088 -IEFubmll 53089 -xrDhu50= 53090 -LmR4 53091 -ZGlzY29ubmVjdA== 53092 -IGFyY2hpdmVk 53093 -W0xpc3Q= 53094 -PU4= 53095 -LnByZXNlbnRhdGlvbg== 53096 -UmVzdGF1cmFudA== 53097 -IHJvY2tldHM= 53098 -PWh0dHBz 53099 -L29w 53100 -IHB1cnNl 53101 -IEtyaXM= 53102 -IGNvcmFs 53103 -c2V0UGFyYW1ldGVy 53104 -IGlycmln 53105 -UXVlZW4= 53106 -TlNEYXRh 53107 -IHZhc3RseQ== 53108 -LkZpbGVz 53109 -IGZlbWluaXNt 53110 -KFN0cmVhbQ== 53111 -IGF0cmli 53112 -IGxpcXVpZGl0eQ== 53113 -PEZpbGU= 53114 -dHJhZw== 53115 -W2NvbnRhaW5z 53116 -IGhpbmRp 53117 -CWNw 53118 -aG9tZXBhZ2U= 53119 -IHN1cnBhc3M= 53120 -IGRheWxpZ2h0 53121 -YXV0aG9yaXpl 53122 -IENvbnNlcXVlbnRseQ== 53123 -QXN5bmNSZXN1bHQ= 53124 -IERpYXJ5 53125 -LlBhdHRlcm4= 53126 -LiovCg== 53127 -ZW5zY2hhZnQ= 53128 -IEp1ZGljaWFyeQ== 53129 -QWR1bHQ= 53130 -KCY6 53131 -IGplb3BhcmQ= 53132 -IEJsaXp6YXJk 53133 -IGdn 53134 -IjsvLw== 53135 -WEhS 53136 -IHBhc3N3ZA== 53137 -Pn0= 53138 -JyksJw== 53139 -IGNvbXBhcmF0b3I= 53140 -LmNoYWlu 53141 -IGluc3VyZWQ= 53142 -X0VER0U= 53143 -IHR5bGtv 53144 -X01BSk9S 53145 -d2F2 53146 -XEZpbGU= 53147 -RW50cg== 53148 -J2FwcA== 53149 -IGZvcmdpdmVuZXNz 53150 -CWRzdA== 53151 -Ijot 53152 -Lm1vbg== 53153 -ICgKCg== 53154 -IGNhcGl0YQ== 53155 -IGluaXRDb21wb25lbnRz 53156 -IHN3b3Jkcw== 53157 -IE91dHB1dFN0cmVhbQ== 53158 -IGhlYXJz 53159 -IFNQQUNF 53160 -LWluc3BpcmVk 53161 -X2Jvb3Q= 53162 -Lm5vbmU= 53163 -LmdldElucHV0U3RyZWFt 53164 -IGRldmlzZQ== 53165 -IHBlZGlhdHJpYw== 53166 -YW5zaQ== 53167 -X3BhcnRpYWw= 53168 -IHNoYXJk 53169 -IGZ1cmlvdXM= 53170 -IGRyYXdhYmxl 53171 -JSku 53172 -KGVt 53173 -IEJha2U= 53174 -CXBlcnJvcg== 53175 -IFJlbGlnaW91cw== 53176 -LSIr 53177 -CQkJICAgICAgICAgICA= 53178 -IFNlY3JldHM= 53179 -KG5vcm1hbA== 53180 -QUNFUw== 53181 -IFN0b2NraG9sbQ== 53182 -LW5vcm1hbA== 53183 -IGFjY3VzdG9tZWQ= 53184 -IGJvdXRpcXVl 53185 -IFN3aW5n 53186 -IGZpbQ== 53187 -IFBV 53188 -LlNvY2tldA== 53189 -ICciJw== 53190 -YW5q 53191 -TWFudWFs 53192 -IG11amVy 53193 -IHBoeXNpb2xvZ2ljYWw= 53194 -Y29udGFpbg== 53195 -TWVyZ2U= 53196 -IHN1YXM= 53197 -ICd7Ig== 53198 -bmVnbw== 53199 -IHN1YnNjcmliZWQ= 53200 -dG9hc3Q= 53201 -X1ZFUkJPU0U= 53202 -IGtuaXQ= 53203 -IEFydGlzdHM= 53204 -IGhlYXJ0YmVhdA== 53205 -IGZpcmVmaWdodGVycw== 53206 -c3Nh 53207 -W3s= 53208 -IHVuZGVyc2NvcmU= 53209 -IGhpc3Rvcmllcw== 53210 -aWdtb2lk 53211 -RmllbGRWYWx1ZQ== 53212 -VG9BZGQ= 53213 -LkNv 53214 -IEhhcm9sZA== 53215 -QXZvaWQ= 53216 -aWdoYm91cnM= 53217 -b3JkZQ== 53218 -IHRydXRocw== 53219 -L2Fs 53220 -IHdpcmVk 53221 -IEl0YWxpYQ== 53222 -IHNlcnZpY2lvcw== 53223 -IEFVRElP 53224 -ICciKw== 53225 -IHB1bXBpbmc= 53226 -IENsZW1lbnQ= 53227 -w4NP 53228 -5Y6f 53229 -Pm4= 53230 -IHN0clNxbA== 53231 -amRiYw== 53232 -4oE= 53233 -CVNFVA== 53234 -IEJVRkZFUg== 53235 -Oi8vIg== 53236 -IGNpcmN1bXN0YW5jZQ== 53237 -VUlUYWJsZVZpZXdDZWxs 53238 -LnZlcnRpY2Fs 53239 -IEpvaG5z 53240 -dG9saXN0 53241 -IGRyaXZld2F5 53242 -IGxlYXJuZXJz 53243 -dG9iZXI= 53244 -d2lubmVy 53245 -LXlvdXI= 53246 -LnN0YXRlcw== 53247 -SE0= 53248 -IGdyYWRpZW50cw== 53249 -IHNlaXp1cmU= 53250 -IG1hdGVy 53251 -IGRldGFs 53252 -IFJlZHVjZQ== 53253 -KG1vdXNl 53254 -IFJlU2hhcnBlcg== 53255 -LXJvdXRpbmc= 53256 -INi0 53257 -IGpvaW50bHk= 53258 -IEZhbWls 53259 -PE1lc3NhZ2U= 53260 -ZXhwaXJl 53261 -X3RyYWRl 53262 -4oCmLi4= 53263 -IEZVTkNUSU9OUw== 53264 -IHhlbg== 53265 -IHt9Ow== 53266 -RmFi 53267 -IGZlYXN0 53268 -KERi 53269 -Rmlyc3RSZXNwb25kZXI= 53270 -xLFsxLE= 53271 -IG1heFZhbHVl 53272 -IC06 53273 -YXB0aWM= 53274 -Lkdzb24= 53275 -IFJvdmVy 53276 -X2Nu 53277 -bG91ZA== 53278 -IGNoYW1iZXJz 53279 -INC30LDQtA== 53280 -LmZvcmVhY2g= 53281 -LmdldEVtYWls 53282 -55+l 53283 -Lk5vZGVz 53284 -IFZX 53285 -IFdhaXRpbmc= 53286 -KFF0Q29yZQ== 53287 -IHPDs2xv 53288 -cnE= 53289 -YW5ndWFyZA== 53290 -IHJlc2VtYmxlcw== 53291 -Oltb 53292 -IGdlZA== 53293 -X0VQ 53294 -KEFjdGl2aXR5 53295 -IElzbg== 53296 -IENydXNoZXJz 53297 -X1JVTlRJTUU= 53298 -CW9wZW4= 53299 -IEhpZ2hsaWdodHM= 53300 -w6lyYXRpb24= 53301 -IHllbGxpbmc= 53302 -IExJR0hU 53303 -UGhvdA== 53304 -dmVuZ2U= 53305 -IFN1c3A= 53306 -IENocg== 53307 -LkRpc3RhbmNl 53308 -YXJzaW1w 53309 -bGljYXM= 53310 -Lk1vbg== 53311 -IHN1Y2tlZA== 53312 -cHJpbnRlZA== 53313 -bXV0ZQ== 53314 -IHNldEVycm9y 53315 -Lk9wdGlvbg== 53316 -IGltcGFpcm1lbnQ= 53317 -bm9pc2U= 53318 -IHBhcnRuZXJlZA== 53319 -w40= 53320 -ZGVucw== 53321 -aWN6 53322 -IHdhaXRGb3I= 53323 -IG92ZXJsb29raW5n 53324 -IEZPUk1BVA== 53325 -IFRTdHJpbmc= 53326 -IHJlbnRpbmc= 53327 -CWNvbXBvbmVudA== 53328 -LkZyZWU= 53329 -IExhdW5jaGVy 53330 -PWRhdGU= 53331 -IFBvZHM= 53332 -QUdNRU5U 53333 -Q29kaWdv 53334 -Qml0RmllbGRz 53335 -IHViaXF1 53336 -LWNhcm91c2Vs 53337 -IFNpbXVsYXRvcg== 53338 -aW5vZGU= 53339 -J10pewo= 53340 -IEJhZ2hk 53341 -IG5vcnRod2VzdA== 53342 -aHRha2luZw== 53343 -PCY= 53344 -IHRyYW0= 53345 -IGZvcndhcmRlZA== 53346 -IGVycm9yTXNn 53347 -X0FTU0lHTg== 53348 -IEVudGl0aWVz 53349 -LlBhcnQ= 53350 -cmVhdHVyZQ== 53351 -KFVyaQ== 53352 -IERyaXZpbmc= 53353 -IGludmFzaXZl 53354 -aWdyYXRpb25CdWlsZGVy 53355 -b3NhdXJz 53356 -CXBvcnQ= 53357 -IGJyYW4= 53358 -aXR0aW5ncw== 53359 -RG9vcg== 53360 -IHsl 53361 -KGxpbWl0 53362 -IHNxdWFyZWQ= 53363 -IERJU1BMQVk= 53364 -LkFjY2VwdA== 53365 -LmJhc2VVcmw= 53366 -LkVudGVy 53367 -IC4uLikK 53368 -IG93bA== 53369 -IHNsYXRlZA== 53370 -LmZlY2hh 53371 -X1NFRw== 53372 -PXsk 53373 -IE9OTElORQ== 53374 -T05Z 53375 -INC00LDQvdC90YvRhQ== 53376 -b250ZQ== 53377 -X0NMSUNL 53378 -U2E= 53379 -SW1wb3J0YW50 53380 -IGNhcm91c2Vs 53381 -IGFwcGVhbGVk 53382 -IE5pZQ== 53383 -L2Jvb2s= 53384 -W10+KA== 53385 -IHhtYXg= 53386 -IGxhbmdl 53387 -LlN1cHByZXNz 53388 -IFRoaW5raW5n 53389 -QWRkcmVzc2Vz 53390 -IFNhbGx5 53391 -LVRW 53392 -IENoYXJsZXN0b24= 53393 -KSIKCg== 53394 -IHRhbGx5 53395 -IHVsbA== 53396 -IGxvY2FsZXM= 53397 -ZXdhbg== 53398 -IGluY3JlbWVudGFs 53399 -65Cc 53400 -IGNhcmV0 53401 -anVyZQ== 53402 -IGRvcg== 53403 -IGxvY2FsaXphdGlvbg== 53404 -IHNlYWZvb2Q= 53405 -IFJ1YmJlcg== 53406 -LlRoZXJl 53407 -IEZpc2hpbmc= 53408 -WVlZ 53409 -bWFnZQ== 53410 -IEZsZXhpYmxl 53411 -IEdFTkVSQUw= 53412 -ZWth 53413 -IHRocml2aW5n 53414 -IHNpcw== 53415 -IGJvdXJnZW9pcw== 53416 -RmFrZQ== 53417 -LFwi 53418 -INC+0LQ= 53419 -Q09S 53420 -LWVmZmVjdGl2ZQ== 53421 -IHNrdQ== 53422 -ZWRseQ== 53423 -IyMKCg== 53424 -IEhvbGx5 53425 -IEZMQVNI 53426 -L1RS 53427 -Lm5z 53428 -cHJvYmU= 53429 -Z2lmdA== 53430 -b3dpdHo= 53431 -LW5hdmJhcg== 53432 -IHNhY2s= 53433 -57qn 53434 -IFRocmVhdA== 53435 -WkE= 53436 -WE0= 53437 -JyksCgo= 53438 -IExMVk0= 53439 -YXN6 53440 -RWRpdGVk 53441 -V2l0aFN0cmluZw== 53442 -U2lsdmVy 53443 -eW5h 53444 -X3JlbmRlcmVy 53445 -CURFQlVH 53446 -KG9wZXJhdGlvbg== 53447 -IFNsb3Rz 53448 -IEF1YnVybg== 53449 -eGVj 53450 -IGhvbW9zZXh1YWxpdHk= 53451 -LlJlc3RDb250cm9sbGVy 53452 -ZXJzaXZl 53453 -IHByb2ZpbA== 53454 -IE15YW5tYXI= 53455 -cm9zc2U= 53456 -X0lSUW4= 53457 -IHNlbmRNZXNzYWdl 53458 -IHRlY2huaWNpYW5z 53459 -IG1hbmU= 53460 -Y29tbW9ucw== 53461 -IHNocmVkZA== 53462 -Qm9vc3Q= 53463 -IHN5bXBhdGhldGlj 53464 -LWVmZg== 53465 -IENlcnRhaW5seQ== 53466 -IHfDpGg= 53467 -IFJvY2hlc3Rlcg== 53468 -dWNjaQ== 53469 -dXJt 53470 -ZW1wb3I= 53471 -ICIiOgo= 53472 -LXNwYWNpbmc= 53473 -IHNpeHR5 53474 -IOKckw== 53475 -X3JlcG9ydGluZw== 53476 -V2ls 53477 -b3lv 53478 -IGRpZFNlbGVjdA== 53479 -LmdldExvbmc= 53480 -LnNldEVycm9y 53481 -X25j 53482 -IERvbmc= 53483 -CWFzeW5j 53484 -IEhpZ2hseQ== 53485 -XToNCg== 53486 -TGVha3M= 53487 -LC4uLgo= 53488 -dmFsdWF0b3I= 53489 -ZGljdGlvbnM= 53490 -b3hlbA== 53491 -IGdlc3R1cmVz 53492 -PSI/ 53493 -YmFncw== 53494 -IFJlbGllZg== 53495 -c3Vic2V0ZXE= 53496 -KG5hbWVzcGFjZQ== 53497 -fXw= 53498 -IG1pY3JvYmk= 53499 -IHB1cml0eQ== 53500 -Y2hpbw== 53501 -fT8= 53502 -X01VVA== 53503 -X2FjdGl2YXRpb24= 53504 -IFBpcmF0ZXM= 53505 -ICUj 53506 -aWZpY2FjacOzbg== 53507 -5Ys= 53508 -IE5SQQ== 53509 -w6dvbg== 53510 -fSkoKTsK 53511 -IENoZXN0ZXI= 53512 -4oCT4oCT 53513 -Z2V0Q29ubmVjdGlvbg== 53514 -LmFyZ3VtZW50cw== 53515 -RmV0Y2hpbmc= 53516 -IEZyeQ== 53517 -IERpdA== 53518 -IHppY2g= 53519 -cGFzdA== 53520 -LWxpYnJhcnk= 53521 -IEhheWVz 53522 -IGJvdW50eQ== 53523 -IFNwcmluZ2ZpZWxk 53524 -UE9S 53525 -IEFQUg== 53526 -IEVtYmFzc3k= 53527 -UVVFU1RJT04= 53528 -IFNvbGRpZXI= 53529 -ZXJ0YXM= 53530 -IE5PUk1BTA== 53531 -IGR1cw== 53532 -Ym9sdA== 53533 -IGRvcnQ= 53534 -IExpZnQ= 53535 -IGdldFJhbmRvbQ== 53536 -LlJ1bldpdGg= 53537 -LCksCg== 53538 -IHZhcmFyZ2lu 53539 -IGhhbmRsZUNsaWNr 53540 -XEh0bWw= 53541 -IGhvbW1lcw== 53542 -Y2lkYWRl 53543 -KGVw 53544 -SmE= 53545 -L2RpYWxvZw== 53546 -LnJhdGU= 53547 -IFdlaQ== 53548 -ZnVsbHNjcmVlbg== 53549 -IE5Vbml0 53550 -Lm1lYXN1cmU= 53551 -VmFscw== 53552 -IFNpZ25lZA== 53553 -IHJ1cw== 53554 -IHJhZnQ= 53555 -IEJsb25kZQ== 53556 -IG5ldHM= 53557 -IE1ldHJpYw== 53558 -aWNoVGV4dEJveA== 53559 -IHVyZQ== 53560 -IGludGVycmFjaWFs 53561 -ICd9Cg== 53562 -KHN0b3JhZ2U= 53563 -SW50ZWdyYXRpb24= 53564 -IGJhbmNv 53565 -QVNZ 53566 -IGppbnQ= 53567 -IGRlZ3JhZGF0aW9u 53568 -IEhBTkQ= 53569 -dWVyZG8= 53570 -PScn 53571 -IHN0cm9rZXM= 53572 -cmV3cml0ZQ== 53573 -KFNldA== 53574 -IE1hdERpYWxvZw== 53575 -IGRvc3NpZXI= 53576 -CWFuZA== 53577 -QURESU5H 53578 -IG11dHVhbGx5 53579 -IHByZWNlZGVk 53580 -fX07Cg== 53581 -IHN1YnR5cGU= 53582 -IHJlc29sdmluZw== 53583 -IGdlb21ldHJpYw== 53584 -W2NvbHVtbg== 53585 -IENUUkw= 53586 -IEhM 53587 -IGRhaA== 53588 -ICg7Ow== 53589 -UmFpbHM= 53590 -w5w= 53591 -IEdlbmVyYXRlcw== 53592 -LUxlbmd0aA== 53593 -cGVkbw== 53594 -b2dlbm91cw== 53595 -IFJvYmVydHNvbg== 53596 -LkJvb2w= 53597 -b2RlcnM= 53598 -X0FHRU5U 53599 -cGFzc3dk 53600 -IE5vZGVz 53601 -LmJp 53602 -IFdC 53603 -IHByb3BoZXQ= 53604 -c2xhdmU= 53605 -IOW8 53606 -IHdlaWw= 53607 -JTwv 53608 -IGNhcmJz 53609 -5rC0 53610 -IGV4cHJlc3NseQ== 53611 -XHhk 53612 -LWV5ZWQ= 53613 -IENyZWF0dXJl 53614 -Y29udGFpbmVk 53615 -KFNJRw== 53616 -IEVuaGFuY2VtZW50 53617 -IENvcnM= 53618 -R2Fs 53619 -X1NJR05BTA== 53620 -cmVpbnRlcnByZXQ= 53621 -IFFQdXNoQnV0dG9u 53622 -X05vbmU= 53623 -IGdlbm9jaWRl 53624 -IFNlYWw= 53625 -5LiK5Lyg 53626 -KHBlcg== 53627 -0LvRjNGC 53628 -IMOgcw== 53629 -LlRlbXBsYXRl 53630 -ICkNCg0K 53631 -LnNpbmdsZXRvbg== 53632 -CXNsZWVw 53633 -IHNwYXduZWQ= 53634 -IHBvc3Nlc3Npb25z 53635 -Z2V0Q29uZmln 53636 -IHRhaQ== 53637 -bHVkZQ== 53638 -IE1ldGVy 53639 -IGJpYmxpY2Fs 53640 -bWFyc2hhbGxlcg== 53641 -LlRvb2xraXQ= 53642 -IExlc2JpYW4= 53643 -LnNtYXJ0 53644 -IGJveWNvdHQ= 53645 -IGZyeQ== 53646 -LWRlc2M= 53647 -X1NlcnZpY2U= 53648 -IG1hY2h0 53649 -IENhaXJv 53650 -w6Bp 53651 -X3ByZXZpb3Vz 53652 -LnRyYW5zcG9ydA== 53653 -TWVkaWNhbA== 53654 -Q0dQb2ludA== 53655 -UVVBUkU= 53656 -IGJyaWdodGVy 53657 -IGNoZWNrQm94 53658 -IEZPVU5E 53659 -LmJyYW5jaA== 53660 -IGJsYWg= 53661 -IFByZWx1ZGU= 53662 -T2ZmbGluZQ== 53663 -TGlzdGluZw== 53664 -LyoqLyou 53665 -IEpS 53666 -cGhhbnRz 53667 -Z2V0WQ== 53668 -LkZpbmRDb250cm9s 53669 -Ii4uLg== 53670 -0LrQtQ== 53671 -SFJFU1VMVA== 53672 -IGNoZWNrbGlzdA== 53673 -KGFzdA== 53674 -IGJvcnJvd2luZw== 53675 -4oCmYW5k 53676 -INCX 53677 -IHByb2N1cmVtZW50 53678 -LXRhc2s= 53679 -X2hhbA== 53680 -UGxheWxpc3Q= 53681 -LnN0YXI= 53682 -X1NVUFBPUlRFRA== 53683 -QVNN 53684 -JUE= 53685 -cmVzdHJpYWw= 53686 -INC40YHQvw== 53687 -IHBhZ2Vy 53688 -IERpYWJldGVz 53689 -IE1haGFy 53690 -dGFu 53691 -QWN0dWFsbHk= 53692 -Pi8v 53693 -IFhW 53694 -4KeN 53695 -IHNlamE= 53696 -LnZpc3VhbA== 53697 -a2tlcg== 53698 -XTsKCgo= 53699 -IHR5cGVOYW1l 53700 -LkJ1dA== 53701 -Q2xpZW50UmVjdA== 53702 -aWNhbHM= 53703 -IERqYW5nbw== 53704 -IFJhcGU= 53705 -IHBheWRheQ== 53706 -KHJlc291cmNlcw== 53707 -LmJpeg== 53708 -dG9p 53709 -KFJ1bnRpbWU= 53710 -IER5bmFtaWNz 53711 -IEludmFsaWRPcGVyYXRpb25FeGNlcHRpb24= 53712 -KHR5cGVz 53713 -IFRhYnM= 53714 -Lk1pZGRsZUxlZnQ= 53715 -eGFi 53716 -IF8o 53717 -IERyZWFtcw== 53718 -X0dyb3Vw 53719 -KGNvcg== 53720 -TGVhZGVy 53721 -IGdyYWR1YWw= 53722 -KEJpZ0RlY2ltYWw= 53723 -IHRleHRhcmVh 53724 -bGV0aW9u 53725 -IEZpbmlzaGVk 53726 -IFBvbGU= 53727 -IHRhcHBpbmc= 53728 -Jig= 53729 -IGZsaXJ0 53730 -IHRlcnJpZmllZA== 53731 -IHBhZHk= 53732 -ZXJlZw== 53733 -ZWxkb20= 53734 -IHN0YXRpb25hcnk= 53735 -IHBvbnk= 53736 -IFJFR0lTVEVS 53737 -X2FjY2Vs 53738 -IEhlcno= 53739 -IG1hdHJpeg== 53740 -IENhZg== 53741 -eGFj 53742 -YXNjdXM= 53743 -IGVubGFyZ2U= 53744 -QUNIRUQ= 53745 -eXl2YWw= 53746 -IHNpYw== 53747 -IENhbmFs 53748 -OnY= 53749 -PT8s 53750 -IEltcHJvdmVtZW50 53751 -P30iLA== 53752 -TlNPYmplY3Q= 53753 -IGVzY2FwaW5n 53754 -IE51bGxhYmxl 53755 -IGjDpA== 53756 -d2FudA== 53757 -RWxpbWluYXI= 53758 -IENMTG9jYXRpb24= 53759 -IHJldXNlSWRlbnRpZmllcg== 53760 -QnVmZmVyU2l6ZQ== 53761 -w59lcg== 53762 -IEFza2Vk 53763 -J11dLAo= 53764 -IHNoaWVsZHM= 53765 -Z3JhbmQ= 53766 -IFRvd25zaGlw 53767 -IFB1Yk1lZA== 53768 -ZWN0bA== 53769 -Zml2ZQ== 53770 -IFJlYWN0aXZlRm9ybXNNb2R1bGU= 53771 -IEdMZW51bQ== 53772 -RGFy 53773 -aWZhY2U= 53774 -LWluZGVudA== 53775 -Rm9ybXVsYQ== 53776 -LnNuYXBzaG90 53777 -Q09NUEFSRQ== 53778 -IGJlbHRz 53779 -CWNhY2hl 53780 -bGRhdGE= 53781 -IGVkYWQ= 53782 -IEJPWA== 53783 -KGNhcnQ= 53784 -X0xBWU9VVA== 53785 -IGZmbHVzaA== 53786 -IExPUw== 53787 -IFNvcnRlZA== 53788 -LnNsaWRl 53789 -IHRpamQ= 53790 -IFRleGFucw== 53791 -IFB1cmNo 53792 -IExldmVscw== 53793 -IHNlbWFudGljcw== 53794 -IFRlaHJhbg== 53795 -Ym1w 53796 -LnVybGVuY29kZWQ= 53797 -X3hsYWJlbA== 53798 -KGd1bHA= 53799 -IEJ1dHRvbnM= 53800 -IEJyb2tlcg== 53801 -55uR5ZCs 53802 -JGVtYWls 53803 -2ZA= 53804 -IGNsYXNzaWNz 53805 -Y29tcG9zZQ== 53806 -KGJz 53807 -IHVuaGVhbHRoeQ== 53808 -RXhlcmNpc2U= 53809 -Y3JldHM= 53810 -IFBhcnM= 53811 -IERldGVybWluZXM= 53812 -YWZvcnQ= 53813 -KG9icw== 53814 -IG5hc3Q= 53815 -IGlocmVu 53816 -IHJveWFsdHk= 53817 -c2VyaWFsaXplcg== 53818 -aWV1eA== 53819 -ICAgICAgICAgICAgICAgICAgICAgIAo= 53820 -ZXhlY3V0aW9u 53821 -IHZpZXdDb250cm9sbGVy 53822 -IHJlcHJv 53823 -LnBl 53824 -IGNhcGl0YWxpemU= 53825 -5Ye7 53826 -IHR1bm5lbHM= 53827 -LkRBVEE= 53828 -cGlyaXQ= 53829 -Q29sbGVjdGlvbnM= 53830 -KX19 53831 -IE9E 53832 -IGZ1enp5 53833 -SW1tZWRpYXRl 53834 -bGo= 53835 -Oz8+Ig== 53836 -W3Zhcg== 53837 -IHZvbGF0aWxpdHk= 53838 -cmVnbG8= 53839 -IHByb2xpZmVyYXRpb24= 53840 -IG9yYWNsZQ== 53841 -IEN2 53842 -IG51bmNh 53843 -UFJJTlRG 53844 -IGJyZWFrcG9pbnQ= 53845 -LkVO 53846 -IGJlc3Rlbg== 53847 -IHJlYmVsbGlvbg== 53848 -UGF1c2Vk 53849 -IGZsb3du 53850 -IHZpY2luaXR5 53851 -d3JpZ2h0 53852 -LGNw 53853 -aXNjaW5n 53854 -b3VjaGVycw== 53855 -QXNo 53856 -eWFy 53857 -IEVq 53858 -cmVwcmVzZW50ZWQ= 53859 -b2RpYw== 53860 -LmNyb3Nz 53861 -IGNyZWF0aW9ucw== 53862 -IFBhYmxv 53863 -ZmVzdA== 53864 -IEhpbHRvbg== 53865 -UmVwb3J0ZXI= 53866 -IERpbA== 53867 -aWxlbmFtZXM= 53868 -IGV4cGVuZGl0dXJlcw== 53869 -X0VESVRPUg== 53870 -IEFyaWFs 53871 -IHBsdW5n 53872 -IHVubmFtZWQ= 53873 -T3JFbHNl 53874 -IHJlY3JlYXRl 53875 -IEhlYXJ0cw== 53876 -PmFsZXJ0 53877 -LmdldFBhc3N3b3Jk 53878 -IE11c3Rhbmc= 53879 -Vks= 53880 -IGFjY29tcGxpc2htZW50cw== 53881 -QXBwZW5kaW5n 53882 -IENheQ== 53883 -IFVzZXJNb2RlbA== 53884 -IHN1YnN5c3RlbQ== 53885 -TGVnYWw= 53886 -eW5jaHJvbml6ZQ== 53887 -X1BFUk1JU1NJT04= 53888 -IEFwYXJ0bWVudA== 53889 -bGlnZQ== 53890 -IGFmZmlsaWF0aW9u 53891 -KERFQlVH 53892 -VHM= 53893 -IENvbG9yaW5n 53894 -IFdvaG4= 53895 -bmljZQ== 53896 -KGxpc3Rh 53897 -4LE= 53898 -cGxveW1lbnQ= 53899 -44G+44Gf 53900 -5aW9 53901 -c3Vic3Q= 53902 -J11dWyc= 53903 -YWJvbA== 53904 -PSdf 53905 -4KeN4KY= 53906 -b3JwaGlzbQ== 53907 -LmxpdGVyYWw= 53908 -IFBsdWc= 53909 -IG13 53910 -b21hbA== 53911 -ICInIiw= 53912 -dXNp 53913 -IHNpZ2hlZA== 53914 -aWN1bHR1cmFs 53915 -Lios 53916 -IFByb3N0aXQ= 53917 -KGNvbnNvbGU= 53918 -SVBMRQ== 53919 -IFRyYXA= 53920 -WFI= 53921 -IEVkaXRvckdVSUxheW91dA== 53922 -X3ZvY2Fi 53923 -IGluY29tcGF0aWJsZQ== 53924 -IHVuY29uc3RpdHV0aW9uYWw= 53925 -LWxh 53926 -IGVyb3RpcXVl 53927 -IGRlcHV0aWVz 53928 -cXVpc2l0aW9ucw== 53929 -bmV3VmFsdWU= 53930 -YWRpYQ== 53931 -IGh3bmQ= 53932 -Z2luZ3M= 53933 -IFZhcw== 53934 -IEluY3JlbWVudA== 53935 -IEZsaW50 53936 -YW1iaWE= 53937 -X1BvaW50 53938 -LWRpc3BsYXk= 53939 -IEZ1bm55 53940 -LnRvYXN0 53941 -LmRhcms= 53942 -QmluZGluZ3M= 53943 -IGRlc2NyaXB0aXZl 53944 -YXJlbmQ= 53945 -LlJldA== 53946 -IHJlY3Vyc2l2ZWx5 53947 -IE1r 53948 -IFRJTEU= 53949 -LmNyZWF0ZVRleHROb2Rl 53950 -IFJBVw== 53951 -IGluZmx1eA== 53952 -54mp 53953 -VG9r 53954 -LWJvYXJk 53955 -UmVjb3JkaW5n 53956 -U3RyZW5ndGg= 53957 -IHJhaW5mYWxs 53958 -KGRk 53959 -LmZ4bWw= 53960 -bmV0cw== 53961 -LkltYWdpbmc= 53962 -IEJJT1M= 53963 -XSsi 53964 -T0U= 53965 -IHJlc2lkZW5jeQ== 53966 -WkU= 53967 -V0I= 53968 -LnNwYW4= 53969 -X2RlZmluZWQ= 53970 -Qk9U 53971 -Pm51bGw= 53972 -Zm9ybURhdGE= 53973 -Q3BwTWV0aG9kSW5pdGlhbGl6ZWQ= 53974 -X1VTRVJT 53975 -IE5vdmVs 53976 -aW5za2k= 53977 -PntA 53978 -ZXR0bw== 53979 -bmF0dXJhbA== 53980 -IFN0cmljdA== 53981 -Onc= 53982 -LnNhZmU= 53983 -IHRvd2Vscw== 53984 -4bqtdA== 53985 -LmdzdWI= 53986 -66M= 53987 -aW5xdQ== 53988 -IGFpZGVz 53989 -IGluY29t 53990 -Z2V0dGVy 53991 -IHdhc2hlcg== 53992 -YWN0b3JpZXM= 53993 -IGdldHRlcnM= 53994 -bWl0ZQ== 53995 -X3NvdXJjZXM= 53996 -IGhhcm1sZXNz 53997 -IHVub3M= 53998 -cHJlaGVuc2l2ZQ== 53999 -IG5vZG8= 54000 -IGdlb2dyYXBoaWNhbA== 54001 -IFNlbGVjdExpc3Q= 54002 -LlNjcmlwdA== 54003 -LkVudW1z 54004 -IEVOVEVS 54005 -d2FsZA== 54006 -IEJhcm9u 54007 -IHBhcnRpY3Vs 54008 -LmN1cnJlbnRQYWdl 54009 -QFRyYW5zYWN0aW9uYWw= 54010 -W2xpbmU= 54011 -CWRlcw== 54012 -SmFzb24= 54013 -LmdldENvdW50 54014 -IFBlbm55 54015 -IFBheWxvYWQ= 54016 -c2hhcnA= 54017 -W3JpZ2h0 54018 -dmVudGE= 54019 -IGFwbA== 54020 -IHByb2R1aXRz 54021 -IG90dA== 54022 -VHJhY2tz 54023 -LkFuZHJvaWQ= 54024 -IHNpbGljb25l 54025 -IEVMU0U= 54026 -YW5pbWF0aW9ucw== 54027 -dWx0dXJlSW5mbw== 54028 -IGJsdWVwcmludA== 54029 -b2ZzdHJlYW0= 54030 -IFtdW10= 54031 -IFNlcnZl 54032 -IHRyaWc= 54033 -CXNlcnZpY2U= 54034 -IFN0cmF0 54035 -IFNhdmFnZQ== 54036 -IG9ianM= 54037 -IE5vdGlmaWNhdGlvbnM= 54038 -LHBvcw== 54039 -VGhpbmc= 54040 -IFJCSQ== 54041 -b3BhdGh5 54042 -IG5hdWdodHk= 54043 -bGJz 54044 -ZXByb20= 54045 -PiIu 54046 -IHBpb25lZXI= 54047 -IGphcGFuZXNl 54048 -QXVk 54049 -IGFsbGV5 54050 -IFBldHNj 54051 -J10/Pg== 54052 -IEtpbGxlcg== 54053 -LmdldEFic29sdXRlUGF0aA== 54054 -X2NhcHM= 54055 -xas= 54056 -IHN1YnN0cmF0ZQ== 54057 -LmFzc2VydElu 54058 -7JWE 54059 -IHRoeXJvaWQ= 54060 -IERlbHV4ZQ== 54061 -IGZhY3RvcmlhbA== 54062 -IHByZXNzZXM= 54063 -IEFjY29t 54064 -PW9wZW4= 54065 -LmdldFM= 54066 -IGV4cGxvcmVy 54067 -IHJlc2lkZXM= 54068 -QXNzb2NpYXRlZA== 54069 -IHRyYW5zZm9ybWF0aW9ucw== 54070 -VHU= 54071 -IFJpY2hhcmRz 54072 -X2JpcnRo 54073 -PSN7 54074 -LXNwZQ== 54075 -KG5k 54076 -IHZpc3VhbHM= 54077 -X3N0YW1w 54078 -IHRlcm1pbmFscw== 54079 -cm91dGluZQ== 54080 -KioqLwo= 54081 -IEphYg== 54082 -S0w= 54083 -Q29udHJpYg== 54084 -IHNvdXRod2VzdA== 54085 -IFBlcA== 54086 -CWVudGl0eQ== 54087 -IGxpbmVy 54088 -LlN0YXR1c09L 54089 -IFNjaHVs 54090 -KENM 54091 -IG1pam4= 54092 -YXN0b3M= 54093 -X2RpZ2VzdA== 54094 -IHBlcnNpc3RlZA== 54095 -LWNvbnRhY3Q= 54096 -IG9kb3I= 54097 -IGRpc2NvdmVyaWVz 54098 -X0ZJRUxEUw== 54099 -Rmx5 54100 -IHJ6 54101 -IExpc3Rh 54102 -UmVzZXJ2ZWQ= 54103 -dGF4b25vbXk= 54104 -KXNlY3Rpb24= 54105 -LyIpCg== 54106 -L3JlcXVlc3Q= 54107 -IHNvbWVkYXk= 54108 -Y2l0aWVz 54109 -L2ZpcmU= 54110 -IG9iamVjdGlvbnM= 54111 -CURFQ0xBUkU= 54112 -Lm5hdmlnYXRpb25JdGVt 54113 -LnNldGRlZmF1bHQ= 54114 -cmV0dXJuVmFsdWU= 54115 -VUNDRUVERUQ= 54116 -IG9ibGlnZWQ= 54117 -IFFhZWRh 54118 -IGh5c3Rlcg== 54119 -ZXN0aGVz 54120 -ZGlzdGluY3Q= 54121 -w6B5 54122 -IENvbWJv 54123 -CXNm 54124 -IOKK 54125 -IGRpc2NyZXBhbg== 54126 -IGluc2lnbg== 54127 -IFJFU1VMVFM= 54128 -IFZhbGlkYXRpb25FcnJvcg== 54129 -IEh0dHBSZXNwb25zZVJlZGlyZWN0 54130 -CVFTdHJpbmc= 54131 -IGF1dG9mb2N1cw== 54132 -RHVy 54133 -IFJFTEVBU0U= 54134 -LWRvbGxhcg== 54135 -LkNvbW1pdA== 54136 -IGtow7RuZw== 54137 -IGxhdW5kZXI= 54138 -Lj0i 54139 -IOaWhw== 54140 -IGJ5ZQ== 54141 -LkdldEtleURvd24= 54142 -IGdpbw== 54143 -X3NpZA== 54144 -IGdxbA== 54145 -LmNt 54146 -X1NMT1Q= 54147 -LkdldEluc3RhbmNl 54148 -cmV1c2U= 54149 -LnNodXRkb3du 54150 -IGplcnNleXM= 54151 -X01Q 54152 -cGF0aWJpbGl0eQ== 54153 -IOiuvue9rg== 54154 -IHJlcGxhY2VtZW50cw== 54155 -IHByZWNlZGVuY2U= 54156 -IGJ1ZmZlcmVk 54157 -LmJz 54158 -X0dSRUVO 54159 -YnJhaW4= 54160 -w6FjaA== 54161 -YXZhaWxhYmlsaXR5 54162 -IEVURg== 54163 -IGZyZXQ= 54164 -aXN0aW5l 54165 -IGxpZnRz 54166 -RXhpc3Rpbmc= 54167 -IHN0ZXJlb3R5cGVz 54168 -IGVtcHQ= 54169 -bW9uZ28= 54170 -LnRyYWluaW5n 54171 -YWxpc3Q= 54172 -LklzRW5hYmxlZA== 54173 -ICIh 54174 -PD8K 54175 -dWlkbw== 54176 -IGludFZhbHVl 54177 -LmVsYXN0aWNzZWFyY2g= 54178 -TE9HSU4= 54179 -IHJlbGlhbmNl 54180 -IHZpZXdUeXBl 54181 -IGRpbWluaXNoZWQ= 54182 -U2FyYWg= 54183 -IEFwcHJvYWNo 54184 -X1dFQg== 54185 -IGRybQ== 54186 -IGNvbHVtbmlzdA== 54187 -TWFya3Vw 54188 -IGFxdcOt 54189 -IERpYW5l 54190 -IGN3 54191 -IFRpY2s= 54192 -Lm9ic2VydmU= 54193 -SVJPTg== 54194 -SW5CYWNrZ3JvdW5k 54195 -IGVib255 54196 -IENvdXJ0ZXN5 54197 -Om51bGw= 54198 -KioqKioqKi8KCg== 54199 -L3Jlc291cmNl 54200 -SXRlcmF0aW9u 54201 -ZGVmYXVsdFZhbHVl 54202 -YXR0ZW50aW9u 54203 -INGA0LDQsdC+0YI= 54204 -IHdhaXZlcg== 54205 -IHByb2R1aXQ= 54206 -IEdyYWRpZW50 54207 -IHBlcmNlbnRhZ2Vz 54208 -IFNBTA== 54209 -IE1k 54210 -KHNuYXBzaG90 54211 -CWlv 54212 -aWtlcnM= 54213 -V2VicGFjaw== 54214 -IHNldFBhc3N3b3Jk 54215 -IGRlZmVhdGluZw== 54216 -IEplZw== 54217 -ZWxhcHNlZA== 54218 -aG9sZHM= 54219 -X3NoYWRvdw== 54220 -IG9mZmVuZGVk 54221 -IFBhbnQ= 54222 -IENhbGxhYmxl 54223 -X0lORk9STUFUSU9O 54224 -ZmZlZQ== 54225 -KGVtcGxveWVl 54226 -IFlBTUw= 54227 -cG9zc2libHk= 54228 -IG1heGltYWw= 54229 -ZWxsdWxhcg== 54230 -IFNueWRlcg== 54231 -ZGVzY3JpcHRvcg== 54232 -IFBMRUFTRQ== 54233 -RGxnSXRlbQ== 54234 -IGFydGlsbGVyeQ== 54235 -YH0K 54236 -cG9zaXVt 54237 -IGxlZXI= 54238 -JWM= 54239 -IGRpc3Bvcw== 54240 -Lm11bA== 54241 -IGdlb2dyYXBoeQ== 54242 -IGdyYXBoaWNhbA== 54243 -IGRyYW5r 54244 -IG1vdGlvbnM= 54245 -IHJ1dGg= 54246 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 54247 -IHByb2R1Y3Rpb25z 54248 -IGNyZWF0ZVRpbWU= 54249 -IFNjcmlwdHVyZQ== 54250 -YmJi 54251 -dWNocw== 54252 -5LiN6IO9 54253 -LkJpZ0RlY2ltYWw= 54254 -c2l6ZXM= 54255 -X3NvbHZlcg== 54256 -X0Zyb20= 54257 -X2pvaW50 54258 -IHBhdGhsaWI= 54259 -IGdlYXJz 54260 -INGE0L7RgNC8 54261 -IGNvbmNlYWw= 54262 -IGRpZmZlcmVudGlhdGU= 54263 -PEdhbWVPYmplY3Q= 54264 -IGplZGVu 54265 -IGFsbw== 54266 -Z2xvYmFscw== 54267 -ZXJ2YXRpdmU= 54268 -IHBhZGQ= 54269 -IFBseQ== 54270 -X3R5 54271 -IHByZXNlbnRl 54272 -IHByb3ByaWV0 54273 -X2xz 54274 -IFB1bmNo 54275 -IENyYXdmb3Jk 54276 -YmVsb3c= 54277 -Q3BwR2VuZXJpYw== 54278 -IENPTlRST0w= 54279 -IG9jZWFucw== 54280 -IFJPVVQ= 54281 -IHJhbmRpbnQ= 54282 -CWFkZHI= 54283 -IEhvbmVzdA== 54284 -IGVudmVsb3A= 54285 -IHRyYXVtYXRpYw== 54286 -IExBVA== 54287 -IHRn 54288 -7Iqk7Yq4 54289 -RXh0ZW5kZWQ= 54290 -IHVuY2hlY2tlZA== 54291 -IG9ic3RydWN0 54292 -X3RpbWV6b25l 54293 -UGVyc2lzdGVudA== 54294 -IGxsZXY= 54295 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgo= 54296 -IEZsYQ== 54297 -LnBoeXNpY3M= 54298 -IGZvcmdlZA== 54299 -IExhdXI= 54300 -IG1vbm9wb2x5 54301 -IGNocmlzdG1hcw== 54302 -Z292 54303 -IFNtb2tl 54304 -W2Rm 54305 -IGJpc2hvcA== 54306 -bG9jYWxPYmplY3Q= 54307 -b3JyaA== 54308 -b250dmFuZ3N0 54309 -ZHJ5 54310 -IGVyZm9s 54311 -LWNl 54312 -IE9yZGVyZWREaWN0 54313 -IGh4 54314 -IFJFU0VU 54315 -U3Vj 54316 -IHJlY2tsZXNz 54317 -YWxhbWF0 54318 -QmlnSW50ZWdlcg== 54319 -IGJ1bGJz 54320 -IG11dGU= 54321 -5pS+ 54322 -LlVsdHJh 54323 -TG9u 54324 -IGNsZWFyVGltZW91dA== 54325 -PFJpZ2lkYm9keQ== 54326 -c3dpcGVy 54327 -IENvbWVz 54328 -XGRi 54329 -CW1w 54330 -IHJlc3Rz 54331 -TW92ZWQ= 54332 -IExvcmU= 54333 -LkRpbWVuc2lvbg== 54334 -IE1hbml0 54335 -Lmh4eA== 54336 -PT09PT09PQ== 54337 -cGl0Y2g= 54338 -ZmZpZWxk 54339 -c2tpbGxz 54340 -X2FsYnVt 54341 -dHJhbnNsYXRlZA== 54342 -IFhJ 54343 -IHZlaW4= 54344 -IERhdmlkc29u 54345 -IEF1Y2tsYW5k 54346 -eXNzZXk= 54347 -IGF1dGhlbnRpY2l0eQ== 54348 -IEFzc2lzdA== 54349 -IGNvbXByaXNl 54350 -Q3JlYXRlVGltZQ== 54351 -IHRyZW5jaA== 54352 -LndlZWs= 54353 -LS07 54354 -IFVJQWxlcnRDb250cm9sbGVy 54355 -X3JlbGF0ZWQ= 54356 -Q01T 54357 -cmVtZWx5 54358 -IGxleGVy 54359 -aXJtd2FyZQ== 54360 -RWxlbWVudHNCeQ== 54361 -LXVwcGVy 54362 -IHN0YWdu 54363 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 54364 -X3NuYXBzaG90 54365 -L1hNTFNjaGVtYQ== 54366 -X09yZGVy 54367 -IGFubmV4 54368 -X0VOQ09E 54369 -IEFsdG8= 54370 -YXJpb3Vz 54371 -REo= 54372 -IGFib3J0aW9ucw== 54373 -Q29tYmF0 54374 -IExpY2VuY2U= 54375 -dWdnZXN0ZWQ= 54376 -W0s= 54377 -LCkpCg== 54378 -KCcvLw== 54379 -LkNhbg== 54380 -c2Vjcw== 54381 -cXVvdGVz 54382 -X3RyeQ== 54383 -IFNhZ2U= 54384 -IE1vdg== 54385 -J29u 54386 -cmVnaXN0 54387 -IFdyaXRlcw== 54388 -IERpZ2VzdA== 54389 -CWNvbnRhaW5lcg== 54390 -LXByb2dyZXNz 54391 -IGdvYXQ= 54392 -X3NjaGVtZQ== 54393 -LkdldENoaWxk 54394 -IGFzeW0= 54395 -Lm15YmF0aXNwbHVz 54396 -YXRpY2E= 54397 -cGdzcWw= 54398 -X2Fzc2V0cw== 54399 -Pks= 54400 -IGFmaW4= 54401 -TlNT 54402 -IE5BVg== 54403 -KCcuJyw= 54404 -IGAi 54405 -IGF1ZGl0b3I= 54406 -X01PVVNF 54407 -IHdhbGxldHM= 54408 -IG1vdQ== 54409 -cnVucw== 54410 -ZXRlcmFuZ2Fu 54411 -IFJlc2VydmF0aW9u 54412 -IGV4cGVyaWVuY2lh 54413 -CXByb2Nlc3M= 54414 -LWltcG9ydA== 54415 -X1JldHVybg== 54416 -IE1hY3Jv 54417 -IFBlbmlz 54418 -cGl4ZWxz 54419 -IHNldEVtYWls 54420 -KE1pZ3JhdGlvbkJ1aWxkZXI= 54421 -KHhz 54422 -IEVzdG9u 54423 -IEJ1YmJsZQ== 54424 -QUxMT1c= 54425 -CWhhbmRsZXI= 54426 -JHJldA== 54427 -IGNvbXBsaW1lbnRhcnk= 54428 -LWNpdHk= 54429 -IGVsbG9z 54430 -IFNPVVJDRQ== 54431 -IEFkdmlzb3I= 54432 -b2xvZ8OtYQ== 54433 -IGZhZGVk 54434 -LnBj 54435 -X1JHQkE= 54436 -QUZY 54437 -IHJlcGF5 54438 -IEZhbGNvbnM= 54439 -X2lzc3Vl 54440 -b21pZG91 54441 -LmJhb21pZG91 54442 -IGluZnJpbmdlbWVudA== 54443 -dXJuaW5n 54444 -L3N0b3JhZ2U= 54445 -X3F1YW50 54446 -IFF0Q29yZQ== 54447 -IG1lbGw= 54448 -X2RlbnNpdHk= 54449 -IEtub3g= 54450 -IFN1cnZpdmFs 54451 -LmdldFVzZXJuYW1l 54452 -IGNvbW1lcmNpYWxseQ== 54453 -Z3Jhc3M= 54454 -IG1laXM= 54455 -5Lq/ 54456 -IFBlcm1pc3Npb25z 54457 -X1FVT1RFUw== 54458 -aXBob25l 54459 -IExPVA== 54460 -IHRocmlsbGVy 54461 -IENoYXBlbA== 54462 -IFJpcw== 54463 -Pmk= 54464 -LUlE 54465 -IHJpZ2h0bHk= 54466 -Q3J5cHQ= 54467 -IElzdGFuYnVs 54468 -cmVkcw== 54469 -X3Jlc2l6ZQ== 54470 -UG9wdWxhdGlvbg== 54471 -KGZldGNo 54472 -IEhPVA== 54473 -OmZpcnN0 54474 -IGdhZGdldHM= 54475 -UHlPYmplY3Q= 54476 -IG1lcmdpbmc= 54477 -ZHVjZWQ= 54478 -bGVnYXRlcw== 54479 -dWJlY3Rs 54480 -JS8= 54481 -YWxsZWU= 54482 -IHp1c2FtbWVu 54483 -LlByb3BUeXBlcw== 54484 -YXN0bw== 54485 -Oio= 54486 -cmVjZQ== 54487 -UmVzcG9uc2VUeXBl 54488 -L2dyb3Vw 54489 -IGJhcmJhcg== 54490 -IENhcm9saW5l 54491 -b3VyY2Vk 54492 -57uP 54493 -IGx1YnJpYw== 54494 -aW5zcGVjdGlvbg== 54495 -YW1tYWQ= 54496 -CUltYWdl 54497 -IGllcnI= 54498 -IGN1cnRhaW5z 54499 -X0FSQg== 54500 -IE9yYWw= 54501 -IGFsbGllZA== 54502 -IFN0YXR1c0NvZGU= 54503 -IENsZWFybHk= 54504 -UHJlZmVycmVkU2l6ZQ== 54505 -cXVpbmE= 54506 -IHNwb3M= 54507 -IG9wdGltaXNt 54508 -IGNvbXByYXI= 54509 -IGx1Zw== 54510 -IEJvb20= 54511 -Y29uZmlybWF0aW9u 54512 -X0RVUkFUSU9O 54513 -X2Jyb3dzZXI= 54514 -IHJlcGV0aXRpb24= 54515 -IGtlZXBlcg== 54516 -IGFkZFRv 54517 -KGpz 54518 -LlN0YXQ= 54519 -LkNvbmQ= 54520 -IEhlcm5hbmRleg== 54521 -cGFxdWU= 54522 -IHZvbHVudGFyaWx5 54523 -IGplcms= 54524 -IExleQ== 54525 -IGRvY3VtZW50bw== 54526 -X2RlYWQ= 54527 -IFRFQ0g= 54528 -IGluY2VwdGlvbg== 54529 -KCJ7fQ== 54530 -IG9uTG9hZA== 54531 -eGRk 54532 -IElTUA== 54533 -c3BlY2lmaWVk 54534 -IOusuA== 54535 -UFJPQ0VTUw== 54536 -KGFsZXJ0 54537 -Lk1N 54538 -IGNyZWF0ZVN0b3Jl 54539 -KHVuaXF1ZQ== 54540 -LmdldEJsb2Nr 54541 -656Y 54542 -dW5vcw== 54543 -IHRyb3BoaWVz 54544 -X2hvdmVy 54545 -IERhZGR5 54546 -Lk1l 54547 -IENPVVI= 54548 -T0JK 54549 -YXRlbWFsYQ== 54550 -IFBzaQ== 54551 -IG5vcm1hbHM= 54552 -YWNpZXI= 54553 -IE1CQQ== 54554 -IHBhd24= 54555 -z4U= 54556 -IHNwb250YW5lb3Vz 54557 -IGF1eGlsaWFyeQ== 54558 -IGluYXVndXJhbA== 54559 -IGZhc3Rpbmc= 54560 -IEZpbGVTeXN0ZW0= 54561 -IHplbg== 54562 -X0JMVUU= 54563 -IHN1YnRyZWU= 54564 -IHByZXByb2Nlc3M= 54565 -LXRyYWNr 54566 -Q2hhcmxlcw== 54567 -IGRlcG9zaXRlZA== 54568 -IHF1ZXJ5UGFyYW1z 54569 -0L7Qu9GM0LrQvg== 54570 -aWVtYnJl 54571 -IHByYXc= 54572 -eEZD 54573 -IHBhbmM= 54574 -X25vbQ== 54575 -aGVyb2Vz 54576 -Lmphdg== 54577 -OjokXw== 54578 -INin2YTZhQ== 54579 -U0dsb2JhbA== 54580 -5o+P6L+w 54581 -PXRlbXA= 54582 -ZXN0aQ== 54583 -IGNvbnN0cnVjdGl2ZQ== 54584 -IFNoaW0= 54585 -IERpcmVjdGlvbnM= 54586 -IEJpbmc= 54587 -ZGlydHk= 54588 -LXJ1bm5pbmc= 54589 -X2ZpbGVwYXRo 54590 -b3JkZXJJZA== 54591 -Z2FyZA== 54592 -X29yaWVudA== 54593 -IHNjb3V0 54594 -IHBzeWNob2xvZ2lzdA== 54595 -7LY= 54596 -IOWt 54597 -ZGVxdWU= 54598 -IEhlcm1pb25l 54599 -IFBvd2VyUG9pbnQ= 54600 -IGVsbGE= 54601 -IFVJQmFyQnV0dG9uSXRlbQ== 54602 -U3Vidmlld3M= 54603 -QFJlcG9zaXRvcnk= 54604 -IiIiCgoK 54605 -IHJldG91cg== 54606 -IGNpcmNh 54607 -R3JhcGhpYw== 54608 -IEdyYXR1aXQ= 54609 -ZGR5 54610 -IHRlY2huaWNpYW4= 54611 -IENsZWFudXA= 54612 -IHBlcnNvbm5l 54613 -IHJlc2lu 54614 -Lk11bHQ= 54615 -JG0= 54616 -IE9yY2hlc3RyYQ== 54617 -IHdoZWVsY2hhaXI= 54618 -LlND 54619 -CUdhbWVPYmplY3Q= 54620 -IG1vxbxl 54621 -T3BlbmVk 54622 -IGNoaWNrZW5z 54623 -b3Rhcw== 54624 -X3RlbXBlcmF0dXJl 54625 -IGRldGVjdGluZw== 54626 -IGFjcXVhaW50 54627 -IDw/PSQ= 54628 -Pl0= 54629 -IG1lbnN0cg== 54630 -IGR5ZQ== 54631 -Um9ib3Rv 54632 -LnVuaXRz 54633 -IFZpbnls 54634 -Y3VyYQ== 54635 -cnlwdG9u 54636 -ZWRk 54637 -PXRlc3Q= 54638 -IHRyb3Y= 54639 -Q29uZmlybWF0aW9u 54640 -IHRoZW9sb2d5 54641 -IEhvbGRpbmdz 54642 -dWF0aW5n 54643 -UHJlZGljdA== 54644 -W3VzZXI= 54645 -IDon 54646 -IFNlc3Nv 54647 -cGFyZW50SWQ= 54648 -Q29kZUF0 54649 -YWJibw== 54650 -IFRyZXZvcg== 54651 -IFF1aXQ= 54652 -X3NoaXBwaW5n 54653 -X1JB 54654 -IGtsZWluZQ== 54655 -56Y= 54656 -X0xhYmVs 54657 -IE9tYXI= 54658 -IEdSRUVO 54659 -LykK 54660 -cm9r 54661 -IHJvYXN0ZWQ= 54662 -X1JU 54663 -IOKAjg== 54664 -QFJ1bldpdGg= 54665 -Pk5O 54666 -IHRhbmQ= 54667 -Kycu 54668 -Y3J1ZA== 54669 -LmtleWJvYXJk 54670 -YXN0ZXJ5 54671 -QkFE 54672 -IENvbHVtbnM= 54673 -LkNvbXBhbnk= 54674 -IHNlbWluYXI= 54675 -IGdldENvbnRlbnRQYW5l 54676 -IGNhdGFzdHJvcGhpYw== 54677 -IGVtYnJvaWQ= 54678 -aWF0aXZl 54679 -IGNydWVsdHk= 54680 -Ymlz 54681 -IGluc2U= 54682 -IEJyb2tlbg== 54683 -CWZz 54684 -IG1WaWV3 54685 -0LDRhtC40Lg= 54686 -LWZhY2Vib29r 54687 -IGNhY2hlcw== 54688 -44CC44CCCgo= 54689 -IE9STQ== 54690 -IERpc3RyaWI= 54691 -IFNjZW5lTWFuYWdlcg== 54692 -X3RyYW5zaXRpb24= 54693 -b21leg== 54694 -IFNIRQ== 54695 -IHdvcmtsb2Fk 54696 -U3VwcG9ydGVkRXhjZXB0aW9u 54697 -IHJpZXM= 54698 -IOWc 54699 -KGNhdA== 54700 -SGFzTWF4TGVuZ3Ro 54701 -QXBwcw== 54702 -LlRBQkxF 54703 -IEtleVZhbHVlUGFpcg== 54704 -ZWRpZG8= 54705 -LlJlbmRlcmluZw== 54706 -IGVsZWN0cm9t 54707 -IGFyYml0cmF0aW9u 54708 -IHZhcmlhYmlsaXR5 54709 -YXBvbGxv 54710 -IHV0bW9zdA== 54711 -b3BlbnNzbA== 54712 -IGjDpQ== 54713 -KCcm 54714 -LlN0YW5kYXJk 54715 -IGRpc3RyYWN0aW9u 54716 -aWZheA== 54717 -IOuVjA== 54718 -dGhvc2U= 54719 -aXNwZW5z 54720 -dmFr 54721 -IFNVUA== 54722 -IElzUGxhaW5PbGREYXRh 54723 -LGtleQ== 54724 -ZnJhZ2lzdGljcw== 54725 -IEpveWNl 54726 -IEZpYmVy 54727 -LlNlcnZsZXRFeGNlcHRpb24= 54728 -X0FsbA== 54729 -IGJhY2tlcnM= 54730 -IEF0dHJpYnV0ZUVycm9y 54731 -ewoKCg== 54732 -QHlhaG9v 54733 -LWRpcmVjdG9yeQ== 54734 -IHVuaW5zdGFsbA== 54735 -IGZsdW9y 54736 -bGlxdWlk 54737 -IGzDoQ== 54738 -IGZyaWdodGVuaW5n 54739 -YWRhbg== 54740 -IEFVVA== 54741 -IHRhdHRvb3M= 54742 -IHByb3BhZ2F0aW9u 54743 -LnRyYW5zbGF0aW9u 54744 -0J/RgA== 54745 -X3NjaGVkdWxlcg== 54746 -44CC4oCc 54747 -IGNhaXJv 54748 -IEh0dHBDbGllbnRNb2R1bGU= 54749 -IE5EUA== 54750 -IEhpdHM= 54751 -IFRyYW5zZm9ybWF0aW9u 54752 -IENhZXNhcg== 54753 -c3RpbQ== 54754 -IEJ1cnRvbg== 54755 -d3lu 54756 -IGNvbW1hbmRlZA== 54757 -IENsb3RoaW5n 54758 -IFJ1bnRpbWVPYmplY3Q= 54759 -cmVhbGx5 54760 -Y2xh 54761 -LnNh 54762 -IFNoYW5ub24= 54763 -IGNvbW1pc3Npb25z 54764 -IEphbmV0 54765 -IGRpc2d1c3Rpbmc= 54766 -IG9wdGltdW0= 54767 -X3NvbA== 54768 -dXJvbnM= 54769 -IFNIQVJF 54770 -QXR0cnM= 54771 -IFNjaGU= 54772 -IEJpZ051bWJlcg== 54773 -IGNpZ2Fy 54774 -KGRlcHRo 54775 -IGZyYWM= 54776 -IEN1cnZl 54777 -TEFTVA== 54778 -IFNDUklQVA== 54779 -6rO8 54780 -TWFsbG9j 54781 -Lmdyb3VwYnk= 54782 -IExlc2xpZQ== 54783 -IHdoaWNoZXZlcg== 54784 -U21hcnR5 54785 -L3dl 54786 -IEFtcA== 54787 -LGlu 54788 -bG9wcw== 54789 -ZGVwZW5kZW5jeQ== 54790 -Y2VkdXJlcw== 54791 -IGB7 54792 -eGljbw== 54793 -Q29sbGVjdG9y 54794 -IGhhYw== 54795 -IERhcmtuZXNz 54796 -ZmZmZmZmZmY= 54797 -Jz0+Ig== 54798 -IHBsZWFzaW5n 54799 -Y29ubmVjdG9y 54800 -em9z 54801 -UENJ 54802 -dmFj 54803 -IEluY29ycG9y 54804 -IG5lZA== 54805 -X0ZBQ1RPUg== 54806 -LmZi 54807 -IG91bmNl 54808 -X3NhdmVk 54809 -INix 54810 -IGRlZWRz 54811 -IERvbHBoaW5z 54812 -IGJ1ZW4= 54813 -RVND 54814 -LHRpbWU= 54815 -X0FVVA== 54816 -ZWNz 54817 -IFNlbmF0b3Jz 54818 -Lm91dGVy 54819 -IFNlbGxpbmc= 54820 -IHJpbg== 54821 -PmAK 54822 -Lm9ic2VydmFibGU= 54823 -IGNvc3Rpbmc= 54824 -REc= 54825 -IHdpbmRpbmc= 54826 -IHNrYQ== 54827 -IGNpcmN1bGF0aW5n 54828 -IGZvcm1pZGFibGU= 54829 -YW1wbw== 54830 -IFJhaXNlZA== 54831 -IHZlZ2V0YXRpb24= 54832 -VUZGSVg= 54833 -S2lsbA== 54834 -cHRpdmU= 54835 -KHJ2 54836 -IENvdW50cmllcw== 54837 -IE5ha2Vk 54838 -IEpB 54839 -KSkiCg== 54840 -dWRhcw== 54841 -IGJhcms= 54842 -CWxldmVs 54843 -IGZvZXM= 54844 -PkFkZA== 54845 -WW91VHViZQ== 54846 -O3Q= 54847 -TkNZ 54848 -Q2x1Yg== 54849 -RWlu 54850 -LS0NCg== 54851 -IGNvbnN0cmFpbmVk 54852 -RVR3aXR0ZXI= 54853 -WUc= 54854 -RGVzY3JpcGNpb24= 54855 -VU5DSA== 54856 -IGVucXVldWU= 54857 -IGRpc2tz 54858 -IFdlbnQ= 54859 -IG11aXQ= 54860 -CWxvY2F0aW9u 54861 -IHJldmlzaW9ucw== 54862 -IEFDSw== 54863 -LWZpeGVk 54864 -dHJhc291bmQ= 54865 -XFRlc3Q= 54866 -U3RhcnRQb3NpdGlvbg== 54867 -LWh0bWw= 54868 -IHByb2JsZW1hcw== 54869 -X0lOVEVSUlVQVA== 54870 -IFNUT1JF 54871 -5qih 54872 -aWxpYXRlZA== 54873 -IFJQTQ== 54874 -W3RlbXA= 54875 -YWNodGVu 54876 -IGNpYw== 54877 -IEF1dG9tYXRpb24= 54878 -IGhpZ2hz 54879 -Lyg/ 54880 -OicpCg== 54881 -c3Bhcms= 54882 -cmVscw== 54883 -CW1vdg== 54884 -VVRFUw== 54885 -LkF1dGhvcml6YXRpb24= 54886 -IFNjaG5laWRlcg== 54887 -IGNoZWVrcw== 54888 -YWRkcmVzc2Vz 54889 -YXJkaW4= 54890 -IHJlbW92YWJsZQ== 54891 -LkJhZFJlcXVlc3Q= 54892 -aWNpb25hcg== 54893 -IERpZXNlbA== 54894 -dGhhbg== 54895 -L34= 54896 -IGRhenU= 54897 -UmVnaXN0cm8= 54898 -ZmZp 54899 -X0RMTA== 54900 -IG5pZXU= 54901 -IG1vaXN0dXI= 54902 -LWV2ZW50cw== 54903 -IHRocmlsbA== 54904 -LmdldEVudGl0eQ== 54905 -IHRvZ2c= 54906 -IHdhdg== 54907 -KWRpZA== 54908 -YXRr 54909 -KHN1YnN0cg== 54910 -IEluamVjdGlvbg== 54911 -X21i 54912 -LkRpdg== 54913 -IGVuZGVhdm9y 54914 -ICjCow== 54915 -IGNsdXR0ZXI= 54916 -IHVyZ2VuY3k= 54917 -IGluc3RydWN0b3Jz 54918 -LScs 54919 -LXN0YW5kYXJk 54920 -Y2Vt 54921 -CWhhbmRsZQ== 54922 -LmZ0 54923 -U3RlcGhlbg== 54924 -Um9u 54925 -44GZ44KL 54926 -c2Np 54927 -IEF0bW9z 54928 -IGNhdGVyaW5n 54929 -IGZpYXQ= 54930 -LlBlcmNlbnQ= 54931 -IENvbmdv 54932 -eGRm 54933 -Lm1vemlsbGE= 54934 -IHNlaGVu 54935 -LnNob3dUb2FzdA== 54936 -T09U 54937 -LXJlc3VsdA== 54938 -zIE= 54939 -IGdob3N0cw== 54940 -IEJ1ZW4= 54941 -IFJpZGVy 54942 -IERvY3RvcnM= 54943 -IHVyYW5pdW0= 54944 -IGxvdWRseQ== 54945 -IHBvaXNlZA== 54946 -IGZhdm9ycw== 54947 -KEFQ 54948 -TEVZ 54949 -IHNpY2tuZXNz 54950 -IGNoYXR0ZQ== 54951 -IGludGVncmF0aW5n 54952 -IFl1cA== 54953 -Q2xvc3VyZQ== 54954 -IFRhbGVz 54955 -IGxpbmVh 54956 -IGV5ZWw= 54957 -LkNyeXB0b2dyYXBoeQ== 54958 -dW5leHBlY3RlZA== 54959 -YWxlbWVudA== 54960 -Y2l0 54961 -ZXRBZGRyZXNz 54962 -TGVhZA== 54963 -eGNk 54964 -X25lZ2F0aXZl 54965 -X2NvcnI= 54966 -aWdyYXBo 54967 -LWNoYW5uZWw= 54968 -IGRpc2Nv 54969 -U2VlZGVy 54970 -YmVhbQ== 54971 -X2Rw 54972 -Q0ND 54973 -IFByb3ZpZGVk 54974 -IGpzb25EYXRh 54975 -X1dI 54976 -RklORQ== 54977 -Qlg= 54978 -LkRhdGFBY2Nlc3M= 54979 -IHRlbXB0ZWQ= 54980 -IGZpbmVk 54981 -aXNDaGVja2Vk 54982 -IGZyYXVkdWxlbnQ= 54983 -RnJp 54984 -IGRvbWlj 54985 -UXVpeg== 54986 -IFVuZGVyZ3JvdW5k 54987 -YWJyYXM= 54988 -IElEaXNwb3NhYmxl 54989 -IFBlcnNvbmE= 54990 -IHJvZ3Vl 54991 -IEJleQ== 54992 -Z2V0Q2xpZW50 54993 -ZWtlbg== 54994 -ICcnJw0K 54995 -V2lraQ== 54996 -KEh0dHBTdGF0dXM= 54997 -U3RyZXRjaA== 54998 -IEdlc3Q= 54999 -IO2VmA== 55000 -IGVudGl0bGVtZW50 55001 -IGRvZW4= 55002 -YmxvZ3M= 55003 -IHZpdHJv 55004 -Ik9o 55005 -IFN1bW1vbg== 55006 -IEJhY2tib25l 55007 -IGfDvA== 55008 -Z2V0Q29sdW1u 55009 -IFdJTkFQSQ== 55010 -CXZh 55011 -X1JFUVVJUkVE 55012 -LnRocm93 55013 -IHNldEN1cnJlbnQ= 55014 -ZHVjdGVk 55015 -KEZ1bmN0aW9u 55016 -ZWxzaW5raQ== 55017 -X1Blcg== 55018 -ZmxpZXM= 55019 -IGluY29tcGV0 55020 -IGp1xbw= 55021 -KCkl 55022 -IC0tLQo= 55023 -dW1hcw== 55024 -IE9sZGVy 55025 -IGRpc3B1dGVk 55026 -X1JFUVVJUkU= 55027 -Lm1hdG11bA== 55028 -dW5rZW4= 55029 -5LmL 55030 -44GL44KJ 55031 -IHR0bA== 55032 -dW5kZXJzY29yZQ== 55033 -IFBhdHJpY2lh 55034 -IHRhcGVy 55035 -IHNlaW5lcg== 55036 -IHNheWE= 55037 -5Y+w 55038 -aWVyaQ== 55039 -LnNlY3JldA== 55040 -IHhvcg== 55041 -IG1pdG9jaG9uZA== 55042 -IGNhcmRib2FyZA== 55043 -fWB9 55044 -LUJFR0lO 55045 -IGRhdmlk 55046 -b3Vsb3M= 55047 -IFBldGVyc2J1cmc= 55048 -ICIiLA0K 55049 -c2hlbGY= 55050 -LXdhdGVy 55051 -LWJ5dGU= 55052 -INC+0LHRitC10LrRgg== 55053 -IHN0aXJyaW5n 55054 -7Je0 55055 -IGNvbXB0 55056 -IFBvdGVudGlhbA== 55057 -UkFGVA== 55058 -IGVhcHBseQ== 55059 -IHN3aW5naW5n 55060 -IGZlYw== 55061 -QVJB 55062 -IHdhbmRlcmluZw== 55063 -IHByZWZlcnM= 55064 -SmVzdXM= 55065 -IHBpcmF0ZQ== 55066 -IElzaXM= 55067 -Lk1pbmltdW0= 55068 -IFZhbGU= 55069 -X0JU 55070 -cmVuY2hlZA== 55071 -Y29ycw== 55072 -KGl0ZW1WaWV3 55073 -IGfDpQ== 55074 -LkNvbnRhY3Q= 55075 -Vmlld0NoaWxk 55076 -aW5kc2F5 55077 -Y29uZmlncw== 55078 -RHVwbGljYXRl 55079 -4oCmSQ== 55080 -enlzdA== 55081 -KHRvZG8= 55082 -LlJlbW92ZUF0 55083 -X0RJRkY= 55084 -IEJvdHRsZQ== 55085 -IHZvbHRh 55086 -dHJhZmZpYw== 55087 -TGVl 55088 -IOyk 55089 -IHR1bmVz 55090 -IEVjdWFkb3I= 55091 -IFl1bg== 55092 -IHVuZGVyd2VudA== 55093 -aWNvbQ== 55094 -ICcnKXsK 55095 -LXBvbA== 55096 -ZmxhbW1hdG9yeQ== 55097 -TXV0YXRpb24= 55098 -IHJlY2Fw 55099 -X3ZlcnQ= 55100 -T1RJT04= 55101 -Q0RBVEE= 55102 -aWNpbmU= 55103 -X2JvdW5kYXJ5 55104 -U2NhbGFycw== 55105 -IFVsdGltYXRlbHk= 55106 -RVE= 55107 -bWV0YWw= 55108 -a3Nlcw== 55109 -bXBs 55110 -IGNvbnRlbg== 55111 -U29sZA== 55112 -RVNTQUdFUw== 55113 -IGJpbmRlcg== 55114 -IGxpbmVu 55115 -IE15QXBw 55116 -LW1ldGE= 55117 -CXJhaXNl 55118 -b3VsdHJ5 55119 -CW1vZHVsZQ== 55120 -5pi+56S6 55121 -bsOt 55122 -IHlycw== 55123 -IHBoeXNpYw== 55124 -LXBsYXRmb3Jt 55125 -IHN3aW5nZXJz 55126 -KGhlYWRlcnM= 55127 -Licp 55128 -IEJV 55129 -IEluY29udHJp 55130 -U2NlbmFyaW8= 55131 -QW1i 55132 -IHByZW1pw6hyZQ== 55133 -L2FydGljbGVz 55134 -IE1ham9yaXR5 55135 -Q0xVU0lWRQ== 55136 -b25vcg== 55137 -IGhhYsOtYQ== 55138 -5bee 55139 -IG1pZGk= 55140 -IExhYw== 55141 -LmZpbmRJbmRleA== 55142 -IFBhaW50aW5n 55143 -LmJvcmRlckNvbG9y 55144 -Kmo= 55145 -IGNvbmdlc3Rpb24= 55146 -X0RJQ1Q= 55147 -b2xsZQ== 55148 -YXJuYXRpb24= 55149 -KHRleHR1cmU= 55150 -IHVm 55151 -IEVpbnN0ZWlu 55152 -KFRocmVhZA== 55153 -IGluZG9vcnM= 55154 -c2NyYXRjaA== 55155 -IG1ha2Vu 55156 -LlNUQVJU 55157 -IEp1ZHk= 55158 -Zm9ydW1z 55159 -CgoKCgoKCgoK 55160 -QklMRQ== 55161 -IHZvdQ== 55162 -TVlTUUw= 55163 -IGdlcm5l 55164 -IEltcG9ydEVycm9y 55165 -IFN1cnJl 55166 -PG5hdg== 55167 -IERpZXNl 55168 -ZXdhcmU= 55169 -IOuqqA== 55170 -aW1wbGVtZW50ZWQ= 55171 -U0lHTg== 55172 -ICd7QA== 55173 -cnpl 55174 -Lm1pbmVjcmFmdGZvcmdl 55175 -LmlubmVySGVpZ2h0 55176 -YmVjaw== 55177 -IGN1cnJ5 55178 -IGZvcm11bGFz 55179 -YWdvZw== 55180 -ZW5kZXQ= 55181 -IFBhaWQ= 55182 -IFJvYmVydG8= 55183 -IHVucGFpZA== 55184 -PWhlYWRlcnM= 55185 -LlBvd2Vy 55186 -IGJyZWQ= 55187 -b3JFbHNl 55188 -b3hpZGU= 55189 -IGZpbmFsaXpl 55190 -c2V0Q29sb3I= 55191 -IFN0YWR0 55192 -KCdcXA== 55193 -aXNtaWM= 55194 -IGhlbGU= 55195 -LlByb3RvY29s 55196 -Lkhvc3Rpbmc= 55197 -X01lbnU= 55198 -X2NvbmRpdGlvbnM= 55199 -IHB1cmdl 55200 -LnhhbWw= 55201 -YmFyZQ== 55202 -RlJBTUU= 55203 -IGN1YmVz 55204 -IEpvaGFubmVz 55205 -b2NyYXRz 55206 -LkRpcmVjdG9yeQ== 55207 -KWE= 55208 -Pyk6 55209 -X0xJQlJBUlk= 55210 -IGdldFRva2Vu 55211 -IGVjaG9lZA== 55212 -PWg= 55213 -X3NvYw== 55214 -IEV2YWx1YXRl 55215 -IOq4sA== 55216 -IERlbGV0ZWQ= 55217 -RXU= 55218 -IGNsb25lZA== 55219 -c3RhdGlzdGljcw== 55220 -LkNhbnZhcw== 55221 -IGhhY2tlcg== 55222 -IGdhbmdz 55223 -LnJlc3VtZQ== 55224 -cGVhY2U= 55225 -0JLQstC10LTQuNGC0LU= 55226 -IFByb2NlZWRpbmdz 55227 -56U= 55228 -IGphcGFu 55229 -ID8+Pgo= 55230 -ICR7KHs= 55231 -LnJlY3RhbmdsZQ== 55232 -Z3c= 55233 -IE9yaWVudGF0aW9u 55234 -JW0= 55235 -LiIpKTsK 55236 -IExpZXV0ZW5hbnQ= 55237 -LnRydWU= 55238 -IGVsdA== 55239 -IERJUkVDVE9SWQ== 55240 -zq8= 55241 -LmRheXM= 55242 -dXR0Z2FydA== 55243 -IHVuZGVyd2Vhcg== 55244 -LCkK 55245 -Q0lE 55246 -aW1lbGluZQ== 55247 -IEJsZW5k 55248 -cGhhc2lz 55249 -IHBlcnNl 55250 -IGdsaXR0ZXI= 55251 -IHVuaXE= 55252 -IENvbWJvQm94 55253 -IHNlc3Npb25JZA== 55254 -dXN0ZXJpdHk= 55255 -SURHRQ== 55256 -0L7QsdGJ 55257 -0KQ= 55258 -cmVuZGVycw== 55259 -X3Bvc2l0aXZl 55260 -X3Nsb3Rz 55261 -YnJvYWRjYXN0 55262 -IE1vbGQ= 55263 -L0NvcmU= 55264 -IEJhbm5vbg== 55265 -VG9vbEJhcg== 55266 -YWJlbGxl 55267 -X2F3 55268 -b2xlY3VsZQ== 55269 -IGRlbGV0ZXM= 55270 -IMOhcmVh 55271 -IHByb3BvcnRpb25hbA== 55272 -TVc= 55273 -IHdhcnk= 55274 -IGludGVybWVkaQ== 55275 -ICoqKioqKioqKioqKioqKioqKioqKioqKg== 55276 -LlNUQVRVUw== 55277 -X3R3 55278 -IGFyb21h 55279 -IGFjdGl2aXNt 55280 -LklzTm90TnVsbA== 55281 -dWF0 55282 -IHBvc3REYXRh 55283 -IHBlbQ== 55284 -X2N0b3I= 55285 -IFJhcGlkcw== 55286 -LW9mZnNldG9m 55287 -IGluZWZmZWN0aXZl 55288 -IG9uRGVzdHJveQ== 55289 -IE1ldHJpY3M= 55290 -IHBhZGRpbmdMZWZ0 55291 -LWVuYWJsZWQ= 55292 -IEdvYWxz 55293 -eW5jaHJvbm91c2x5 55294 -IHllcg== 55295 -SXRlbUF0 55296 -IE1ZU1FM 55297 -Y2Vzbw== 55298 -LktpbmQ= 55299 -dGVj 55300 -KGJ1bmRsZQ== 55301 -IHJlZmVyZWU= 55302 -LiI7DQo= 55303 -IGNvbmV4 55304 -IGJpa2luaQ== 55305 -X0FQUExJQ0FUSU9O 55306 -IHN3ZWxsaW5n 55307 -IGJlYWRz 55308 -IGJhcmdhaW5pbmc= 55309 -LS0tLS0tLS0tLS0KCg== 55310 -IGtpdGE= 55311 -KmZ0 55312 -TWluaQ== 55313 -IFRvbmlnaHQ= 55314 -IG1hbmlwdWxhdGVk 55315 -TWlycm9y 55316 -IFBvc3RhbA== 55317 -IG1hcmU= 55318 -RFc= 55319 -IGNvbXBpbGluZw== 55320 -IGZvcmVuc2lj 55321 -LmdldFZpZXc= 55322 -ZXBpbmc= 55323 -Q29z 55324 -IGFjY3JlZGl0ZWQ= 55325 -IG9iamV0aXZv 55326 -Y2FyZXQ= 55327 -UGFpcnM= 55328 -KT4+ 55329 -IHNlw7E= 55330 -IHF1b3RhdGlvbg== 55331 -IEJyYW5kcw== 55332 -dWJp 55333 -eXB5 55334 -IElubGluZQ== 55335 -aW1ldGVycw== 55336 -V2ludmFsaWQ= 55337 -CWxpbms= 55338 -IEJlbGZhc3Q= 55339 -IE1lYXN1cmVtZW50 55340 -X05PVElGSUNBVElPTg== 55341 -IHJveQ== 55342 -IENHQ29udGV4dA== 55343 -IHdlZGRpbmdz 55344 -VVJOUw== 55345 -IHBvZGNhc3Rz 55346 -IFNlcmc= 55347 -IOuNsOydtO2EsA== 55348 -IGVhcm5lc3Q= 55349 -Y292ZXJhZ2U= 55350 -aXRlRGF0YWJhc2U= 55351 -RW1wbG95ZWVz 55352 -IERlbWFuZA== 55353 -IGNvbnRlbmlkbw== 55354 -IFFWZWN0b3I= 55355 -IiwiXA== 55356 -IEdlcmFsZA== 55357 -KClg 55358 -IGdyaWRCYWdDb25zdHJhaW50cw== 55359 -UkVTT1VSQ0U= 55360 -IFNhZw== 55361 -YWJpbGlkYWQ= 55362 -IGNvZXJj 55363 -b3VuY2VtZW50cw== 55364 -IElzbGU= 55365 -LmVkZ2U= 55366 -IGV4dGVy 55367 -KV1b 55368 -IFBsYXlsaXN0 55369 -IEJsaW5k 55370 -IFZpdGFs 55371 -IGxhdHRpY2U= 55372 -cmF0ZWQ= 55373 -ZGVwZW5kZW5jaWVz 55374 -IGBgYA== 55375 -IEthbmc= 55376 -bWFjaA== 55377 -LmZhZGU= 55378 -IEd1ZXNz 55379 -Kls= 55380 -TmF0dXJhbA== 55381 -Lk9r 55382 -IFJlbmFpc3NhbmNl 55383 -IHRodWlz 55384 -IGxpa2Vu 55385 -Kmg= 55386 -XCcs 55387 -LWNsb2Nr 55388 -IE9iamVjdGl2ZQ== 55389 -ZmluZE9yRmFpbA== 55390 -IERpcnR5 55391 -IHNjYW5k 55392 -IFZBUklBQkxF 55393 -IGNvbXBhcmF0aXZl 55394 -eXBhZA== 55395 -KFNvdXJjZQ== 55396 -ZWNv 55397 -IGp1c3F1 55398 -CWFwaQ== 55399 -QnVpbHQ= 55400 -ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMj 55401 -IGxhYmVsaW5n 55402 -IGhlYWRhY2hlcw== 55403 -IG11ZmY= 55404 -IE9yY2g= 55405 -IGhhdGVz 55406 -LWJyZWFraW5n 55407 -L2J1dHRvbg== 55408 -IEJ1eWluZw== 55409 -TWV0cmlj 55410 -IHVuc3BlY2lmaWVk 55411 -L2hlYWQ= 55412 -IHN0aW5n 55413 -IHJlaW5mb3JjZQ== 55414 -IENvbVZpc2libGU= 55415 -Ymxpbms= 55416 -IEFobWFk 55417 -ZGJn 55418 -X2xibA== 55419 -IGh0dA== 55420 -7JuQ 55421 -cm9wb2xpcw== 55422 -ICgoX18= 55423 -IHBlcm1l 55424 -IGFwcGFyZWw= 55425 -U1RSRUFN 55426 -Y2h0cw== 55427 -IHNlaW5z 55428 -ZmlsbFR5cGU= 55429 -7KO8 55430 -Uk9XU0VS 55431 -dW1waW5n 55432 -IE5pZ2VyaWFu 55433 -4oCUaXM= 55434 -X2xvZ2lj 55435 -Lk9yZGluYWw= 55436 -bG9zdA== 55437 -L3Vzcg== 55438 -QWY= 55439 -IEl0ZXJhdGU= 55440 -aWJz 55441 -YWFs 55442 -IHN5bW1ldHJpYw== 55443 -LGlucHV0 55444 -IFBMTA== 55445 -dXppb25l 55446 -Y2FwdGNoYQ== 55447 -IFRhbGU= 55448 -RXhwaXJlZA== 55449 -IE9iamVjdE1hcHBlcg== 55450 -Y2lkbw== 55451 -LmdldE5leHQ= 55452 -IG1lbmphZGk= 55453 -OnNlbGVjdGVk 55454 -IHJpZW4= 55455 -X3NlbmRlcg== 55456 -UHdk 55457 -IEZsaWNrcg== 55458 -LkphdmE= 55459 -X3ZvdGU= 55460 -X01vZGU= 55461 -LiR7 55462 -IGZ1Y2tz 55463 -IEFsaWJhYmE= 55464 -IGluc2lkZXI= 55465 -YWNpbWllbnRv 55466 -IGZyYW7Dp2Fpcw== 55467 -SlNPTkV4Y2VwdGlvbg== 55468 -IEp3dA== 55469 -TWl0 55470 -bGVpY2g= 55471 -IHByYWN0aXRpb25lcg== 55472 -L3NvdXJjZQ== 55473 -IG9nbmk= 55474 -IHBoaWxvc29waGVy 55475 -U25hY2tCYXI= 55476 -c3RlbGx1bmc= 55477 -KGJpdG1hcA== 55478 -IGFzdGVyb2lk 55479 -IG1hcGxl 55480 -dWNoYQ== 55481 -aXRlbUlk 55482 -IHN0ZWh0 55483 -T3JkZXJlZA== 55484 -ZW5idXJn 55485 -L3Rva2Vu 55486 -6YWN 55487 -IFdlYmI= 55488 -b3dhbmll 55489 -IFdBSVQ= 55490 -IEhEUg== 55491 -IEV2YQ== 55492 -QVRUTEU= 55493 -KG1hc3Rlcg== 55494 -IGVycw== 55495 -YWxvYWQ= 55496 -IHNtdHA= 55497 -dW5pcQ== 55498 -IGd1aXQ= 55499 -IFJhZmFlbA== 55500 -Imlu 55501 -KFVJ 55502 -KExheW91dEluZmxhdGVy 55503 -b3Jhbg== 55504 -IHNlcnZp 55505 -bmV6 55506 -IFRvcnJlcw== 55507 -Lk1pZGRsZUNlbnRlcg== 55508 -IG1vbGw= 55509 -IFRleHRBbGlnbg== 55510 -X3VwbG9hZGVk 55511 -IE1laHI= 55512 -IGhvbW8= 55513 -LWxpbmtlZA== 55514 -dW5uZXI= 55515 -X2xlbmd0aHM= 55516 -IGRpZmZ1c2U= 55517 -IEF1dG9tb3RpdmU= 55518 -WWVhcnM= 55519 -IGxpZW4= 55520 -W2NvdW50ZXI= 55521 -a2xhc3M= 55522 -0YHRgtC4 55523 -LkVuZ2luZQ== 55524 -IG1lbnk= 55525 -dWx0eg== 55526 -IGluZmFudHJ5 55527 -Vmlh 55528 -c2VjdHM= 55529 -LmRhc2hib2FyZA== 55530 -IHNwb25zb3JzaGlw 55531 -Lk1vZGlmaWVk 55532 -Oy0= 55533 -IFZlbG9jaXR5 55534 -dHJhY3RlZA== 55535 -KG1ldGFkYXRh 55536 -IHBsYWd1ZQ== 55537 -TlNVc2VyRGVmYXVsdHM= 55538 -YXBwcm92YWw= 55539 -cHJvYmFibHk= 55540 -LXNpeA== 55541 -X1ZJUw== 55542 -OicnLAo= 55543 -LmVuYw== 55544 -Lk1lc3NhZ2Vz 55545 -X1BST0dSRVNT 55546 -IG5lY2tsYWNl 55547 -IFRlbXBvcmFyeQ== 55548 -X21hcmt1cA== 55549 -IEZ1bmN0aW9uYWw= 55550 -IEpp 55551 -IHRlc3RDYXNl 55552 -ICgpOw0K 55553 -X0NlbGw= 55554 -IFJlc2lkZW50aWFs 55555 -IFJhaWx3YXk= 55556 -KCgmX19f 55557 -IGRlZmF1bHRzdGF0ZQ== 55558 -IGVpbm1hbA== 55559 -LmZhYw== 55560 -KmY= 55561 -IHBpY25pYw== 55562 -KGV2YWw= 55563 -IGZ1cm5hY2U= 55564 -YXNzb2NpYXRpb24= 55565 -eyEh 55566 -IENvbXBpbGU= 55567 -eGVi 55568 -RXZhbA== 55569 -gOyepQ== 55570 -KGNhbA== 55571 -IG1hcmtldGVycw== 55572 -X2hlbHBlcnM= 55573 -bG9jYWxjdHg= 55574 -IHlvZ3VydA== 55575 -IHZpdGE= 55576 -LGxlbmd0aA== 55577 -IElucHV0RGVjb3JhdGlvbg== 55578 -IGludGVydmVuZQ== 55579 -IGNvbXB1dGF0aW9uYWw= 55580 -RGVuaWVk 55581 -L2Vudmlyb25tZW50 55582 -aWlk 55583 -LkJveA== 55584 -LVRpbWU= 55585 -IGV4Y3VzZXM= 55586 -dHJhbnNwb3Nl 55587 -IG91dHJhZ2VvdXM= 55588 -KFNlcnZlcg== 55589 -ZGltcw== 55590 -Il0pOw0K 55591 -kJw= 55592 -IEVpc2Vu 55593 -KE9w 55594 -IGhhc2hsaWI= 55595 -KGxp 55596 -fiw= 55597 -xLFuZA== 55598 -IFNwaGVyZQ== 55599 -IEJlbGxh 55600 -LXRyYW5zaXRpb24= 55601 -LnJlYWRTdHJpbmc= 55602 -aGVhcmQ= 55603 -IFp1Y2tlcg== 55604 -IHdhbm4= 55605 -IGphaWxlZA== 55606 -IFRhbGVudA== 55607 -b3Bob2JpYQ== 55608 -wrY= 55609 -IG9wZXJhbmRz 55610 -U29tZW9uZQ== 55611 -IExpYnJhcmllcw== 55612 -cHJpbWFyeUtleQ== 55613 -16o= 55614 -VXI= 55615 -IG1hdGVz 55616 -INGI 55617 -LWR1dHk= 55618 -cG91cg== 55619 -PEVudGl0eQ== 55620 -PllvdQ== 55621 -Q3JlYXRvcnM= 55622 -V2l0aE5hbWU= 55623 -J2ludA== 55624 -IFJhdGlvbmFs 55625 -PUI= 55626 -LkF1dG9GaWVsZA== 55627 -IEZvdW5kZXI= 55628 -IE1lZ2Fu 55629 -LmltYWdlVmlldw== 55630 -Ym93cw== 55631 -IHdpdGhSb3V0ZXI= 55632 -IGxpYmVyYXRpb24= 55633 -IGZvcmFt 55634 -IGNpdGFz 55635 -b2NoZW4= 55636 -LnN3YXA= 55637 -IC4uCg== 55638 -LmN2dENvbG9y 55639 -IEF3YXJl 55640 -IHF1ZWVy 55641 -5aSE55CG 55642 -IEluZmluaXRl 55643 -L3N0cmluZw== 55644 -IGJsZW5kZWQ= 55645 -LUNvbA== 55646 -IHd5cw== 55647 -IHNpY2hlcg== 55648 -Lkxhc3ROYW1l 55649 -X3dhdGVy 55650 -X1JlbQ== 55651 -IGFydGhyaXRpcw== 55652 -LkFQUA== 55653 -IEV4cGFuc2lvbg== 55654 -eGRi 55655 -ZXN0cm8= 55656 -ZmF2aWNvbg== 55657 -VmVyaWZpZWQ= 55658 -IGRlbGl2ZXJpZXM= 55659 -YXJrZXQ= 55660 -IGdldEltYWdl 55661 -IEpQRUc= 55662 -IFRSSQ== 55663 -IEVsZXY= 55664 -ZnVzaW9u 55665 -IGpwZWc= 55666 -Y29sbGlzaW9u 55667 -IGRlc2NlbmQ= 55668 -LmZvcmU= 55669 -IExvZ3M= 55670 -IHBvbGljaW5n 55671 -dW50YXM= 55672 -Lmhvc3RuYW1l 55673 -YWNjZXB0ZWQ= 55674 -4KWL 55675 -IFdlbmR5 55676 -LnJlYWRGaWxl 55677 -IFNhbnRpYWdv 55678 -IEdvbA== 55679 -cmliYm9u 55680 -c3RyYXRpb24= 55681 -IHB1ZGQ= 55682 -IC8vXw== 55683 -aXNMb2FkaW5n 55684 -X1NFUklBTA== 55685 -IGluc3RhbnRpYXRlZA== 55686 -IHBvZHM= 55687 -IHdhcnJhbnRz 55688 -IGFkbWl0dGluZw== 55689 -CWNvbm5lY3Rpb24= 55690 -X2J1ZmZlcnM= 55691 -IEluY2g= 55692 -IFpFUk8= 55693 -d2VydA== 55694 -IENsYW4= 55695 -CWls 55696 -KHNoYWRlcg== 55697 -IHBpbGdy 55698 -IOWK 55699 -RHN0 55700 -X2JhcmFuZw== 55701 -Oicj 55702 -QnV0dG9uVGV4dA== 55703 -dGVyZQ== 55704 -X2FtdA== 55705 -IEZvcmV2ZXI= 55706 -LkxpbmtlZExpc3Q= 55707 -dWFyZHM= 55708 -dXJvdXM= 55709 -IFNlbmRlcg== 55710 -dmFyaWFudHM= 55711 -X21hZ2lj 55712 -IGFjY29tbW9kYXRpb25z 55713 -YXBHZXN0dXJlUmVjb2duaXplcg== 55714 -UHJvbXB0 55715 -ID8+DQoNCg== 55716 -IHJlcHJvZHVjZWQ= 55717 -X3ByZWNpc2lvbg== 55718 -IHJ1dA== 55719 -bW9uZHM= 55720 -O3g= 55721 -IH0sDQoNCg== 55722 -55S7 55723 -IFZpdGE= 55724 -IHByb3Bvc2Vz 55725 -IFBhcnRpdGlvbg== 55726 -SElORw== 55727 -ICN7QA== 55728 -IGVzc2E= 55729 -KGJhcg== 55730 -IFplbGRh 55731 -LmNhdGNo 55732 -X2V4Y2VwdA== 55733 -IG92ZXJ3aGVsbWluZ2x5 55734 -CVRFU1Q= 55735 -X0NPTlRBQ1Q= 55736 -X187 55737 -IFNlbWk= 55738 -IHRyYWJhbGhv 55739 -cmFkb3Vybw== 55740 -X3NxdWFyZWQ= 55741 -4LY= 55742 -JUQ= 55743 -IHByYXQ= 55744 -aXRleg== 55745 -KGVsZW1lbnRz 55746 -UGxhbnQ= 55747 -YWd1YQ== 55748 -IGlocmVy 55749 -LkNvbA== 55750 -IE1jTg== 55751 -IENvcmV5 55752 -T05FWQ== 55753 -Q2VsZQ== 55754 -cmVtZW50 55755 -IG1hbHQ= 55756 -IEx1aw== 55757 -57uf 55758 -UE1FTlQ= 55759 -IGFuYWx5emVy 55760 -IEhhbms= 55761 -X3VuaWNvZGU= 55762 -IGJ1cmlhbA== 55763 -IENlbHRpYw== 55764 -RUZG 55765 -TG90 55766 -d29u 55767 -IE51ZGU= 55768 -IE5hdGU= 55769 -IFNpbmdlcg== 55770 -IFNJVEU= 55771 -KGJpdA== 55772 -Yml6 55773 -IGRldG9u 55774 -UkVBRE1F 55775 -OkFkZA== 55776 -IEhvbGRpbmc= 55777 -e3JldHVybg== 55778 -bmNpYXM= 55779 -Pg0KDQoNCg== 55780 -cnVwdGlvbnM= 55781 -LnJlYWN0 55782 -dXJzYWw= 55783 -4Lib 55784 -IERPTkU= 55785 -aXZhdGVk 55786 -Lm5vdGVz 55787 -IHN0cmlwZXM= 55788 -cmlwcA== 55789 -aXJhbg== 55790 -IHNsYWI= 55791 -IEJ1cm5pbmc= 55792 -KGVudA== 55793 -LnNlYw== 55794 -R1U= 55795 -X2dvbGQ= 55796 -XSkpLg== 55797 -ZWxpbmVzcw== 55798 -0L7QsdGA0LDQ 55799 -IOKIgA== 55800 -IGNvc21pYw== 55801 -J10pOgo= 55802 -Y2Npb25lcw== 55803 -Y2lzaW9u 55804 -Y29tcGFyaXNvbg== 55805 -IEV2YW5nZWw= 55806 -IFNoaXJ0 55807 -bGFnZW4= 55808 -IGnFnw== 55809 -IGZpbGxlcg== 55810 -LnByb2Q= 55811 -IAkJCQkJ 55812 -INGE0YPQvdC60YbQuA== 55813 -IFplcm9Db25zdHJ1Y3Rvcg== 55814 -QXRB 55815 -XSkNCg0K 55816 -IGNvbnN0cnVjdG9ycw== 55817 -X1NIQVJFRA== 55818 -CWRldmljZQ== 55819 -IEFkdmljZQ== 55820 -OkAiJUA= 55821 -Pn0n 55822 -LklzRW1wdHk= 55823 -IGludHM= 55824 -bW9zdGF0 55825 -IFNpZ251cA== 55826 -Z2Vhcg== 55827 -KHBhdGhz 55828 -LHsi 55829 -L0RvY3VtZW50cw== 55830 -PENhdGVnb3J5 55831 -VUVTVA== 55832 -IGdldERlc2NyaXB0aW9u 55833 -ICJ7XCI= 55834 -IEpvZXk= 55835 -b2Rlbg== 55836 -X2d1ZXNz 55837 -RVVS 55838 -IGhlcnI= 55839 -IHNlZGFu 55840 -IHJlYWN0ZWQ= 55841 -X2Nsb25l 55842 -IFJldmVs 55843 -IGZvcmI= 55844 -UmVtYWluaW5n 55845 -XFNlcnZpY2Vz 55846 -IGF2aXM= 55847 -YmF0aW0= 55848 -emVwdA== 55849 -IERCTnVsbA== 55850 -Q29ubmVjdGlvbnM= 55851 -IGRpc3BvbmlibGU= 55852 -cGhpbg== 55853 -IHN0dQ== 55854 -IHNjaG9sYXJzaGlwcw== 55855 -LXNoYXJpbmc= 55856 -Zm9ybWluZw== 55857 -IEJyaQ== 55858 -VmFySW5zbg== 55859 -L3Nlc3Npb24= 55860 -IGFtYmlndW91cw== 55861 -IGFwcmVzZW50 55862 -X3Jk 55863 -c2l0ZXM= 55864 -L2FjdGlvbg== 55865 -dHJhY3Rvcg== 55866 -IGRpbGVtbWE= 55867 -IFNY 55868 -XS0tPgo= 55869 -IEphY2tldA== 55870 -UkFUSU9O 55871 -LmdldFNlbGVjdGVkSXRlbQ== 55872 -LWluaXQ= 55873 -IFJlZ2lzdGVycw== 55874 -X3NlcA== 55875 -IFRvb2xraXQ= 55876 -LmRpY3Q= 55877 -IHhsYWJlbA== 55878 -XFRhYmxl 55879 -dG9j 55880 -X2NvbWJv 55881 -IENvbXBhY3Q= 55882 -IHJ1Z2dlZA== 55883 -4KWH4KQ= 55884 -LW1hbmFnZW1lbnQ= 55885 -Jyl9fSI+Cg== 55886 -IFN0YW1w 55887 -xLFs 55888 -cm94 55889 -IGxhbmRzY2FwZXM= 55890 -X05PVEU= 55891 -bW9uYXJ5 55892 -Y2Fi 55893 -IG1vZXQ= 55894 -eGFm 55895 -cmNvZGU= 55896 -LWNsaQ== 55897 -X2dhdGU= 55898 -W2V2ZW50 55899 -U1BPUlQ= 55900 -Z2lh 55901 -IFNVUEVS 55902 -L0xvZ2lu 55903 -X3NodXRkb3du 55904 -aW50ZXJydXB0 55905 -IHByZXRlbmRpbmc= 55906 -IGZyaW5nZQ== 55907 -IFJlZHM= 55908 -IENVREE= 55909 -IFVOSVg= 55910 -dml0 55911 -IGJyaWc= 55912 -ZHJ2 55913 -IENvbm5lY3Rvcg== 55914 -VGhlcmVmb3Jl 55915 -IGxpYQ== 55916 -RGV0ZWN0aW9u 55917 -X2FjdG9y 55918 -IHRlbXBmaWxl 55919 -IGVjY2VudHJpYw== 55920 -LXJvbGU= 55921 -IHBhZHg= 55922 -ZGVudA== 55923 -V2VzdGVybg== 55924 -IOq3uA== 55925 -IEFwcGxpY2F0aW9uUmVjb3Jk 55926 -IGNhbXBhaWduaW5n 55927 -X3J1bm5lcg== 55928 -IENpdmlj 55929 -YWxlaWdo 55930 -IGRpcmVrdA== 55931 -LnN1bA== 55932 -ICAJCQk= 55933 -YW50ZW4= 55934 -IGlzc3Vlcg== 55935 -IGFzc2VydGlvbnM= 55936 -KG9yaWc= 55937 -QVRJTw== 55938 -IGxlYW5lZA== 55939 -w6Rz 55940 -LkRUTw== 55941 -ZXhwbG9kZQ== 55942 -Lk9ic2VydmFibGU= 55943 -IHN0YWdnZXJpbmc= 55944 -IGtpZG5hcHBlZA== 55945 -IHByb2dyYW1tZXJz 55946 -IElubm92 55947 -LnBhcmFtZXRlcg== 55948 -IGRvbWluYXRpb24= 55949 -IHNrZXB0aWM= 55950 -IOaYrw== 55951 -IGF2b2lkcw== 55952 -LlZlcmlmeQ== 55953 -dWJieQ== 55954 -IEFTTg== 55955 -IGZvcm1hdG8= 55956 -IEJlYXRsZXM= 55957 -X2JyYW5k 55958 -IGluc2V0 55959 -eW91dHU= 55960 -IHRvYw== 55961 -LWZpbmFs 55962 -U2hvd2luZw== 55963 -IERvdWI= 55964 -IE1lc2E= 55965 -QWRq 55966 -X21lZGl1bQ== 55967 -Q3JlYXRlcw== 55968 -KGVuZHBvaW50 55969 -CVVQ 55970 -YmJpZQ== 55971 -IHN0YWxr 55972 -LmRhdGFiaW5k 55973 -LlNjYW4= 55974 -YWdlbnRz 55975 -JCw= 55976 -aW5kaXZpZHVhbA== 55977 -Kykv 55978 -CXZt 55979 -KG5vdGlmaWNhdGlvbg== 55980 -IGluZXg= 55981 -IENsYXNzaWZpY2F0aW9u 55982 -cmVubw== 55983 -IG9saWc= 55984 -LXJhdGVk 55985 -IGZvcm11bGF0aW9u 55986 -Jyx7 55987 -IGFjZXB0 55988 -X3VucGFjaw== 55989 -X0NB 55990 -LlBvdw== 55991 -CWlt 55992 -IGFsdW1pbml1bQ== 55993 -QU5P 55994 -IHhu 55995 -IGPDs21v 55996 -IEluZ3JlZGllbnQ= 55997 -IHNlaXp1cmVz 55998 -5YWx 55999 -aWZpY2Fkb3I= 56000 -IHNpZ3VpZW50ZQ== 56001 -IEluZnJhZ2lzdGljcw== 56002 -IGR1cGxpY2F0ZWQ= 56003 -IERlZQ== 56004 -IG7DuA== 56005 -IEFDQ0VQVA== 56006 -KGNyYXRl 56007 -0LjRgtC10LvRjA== 56008 -LWxlc3M= 56009 -IGluZmluaXR5 56010 -QW5hbHl6ZXI= 56011 -LURheQ== 56012 -cml0dA== 56013 -KGNpbg== 56014 -IEd5 56015 -IG11bHRpcGxpZWQ= 56016 -dWNoaQ== 56017 -IEJhbGR3aW4= 56018 -L2lw 56019 -IHNob3J0Y3V0cw== 56020 -LkFERA== 56021 -IHZpZ29y 56022 -X2luc3RydWN0aW9u 56023 -KDs= 56024 -X2V0YQ== 56025 -6L+e 56026 -dXRvcmlhbHM= 56027 -IGJvb3N0aW5n 56028 -YnY= 56029 -IGFja25vd2xlZGdlcw== 56030 -TGlzdGVuaW5n 56031 -RkFR 56032 -O2I= 56033 -KCgt 56034 -IGFyY2hpdGVjdHM= 56035 -IHp3ZQ== 56036 -IHB1bHM= 56037 -IGdldENvdW50 56038 -dmVyYnM= 56039 -44Cc 56040 -KENvbGxlY3Rpb24= 56041 -a3Jl 56042 -IGp1cmlzZGljdGlvbnM= 56043 -X2JyaWRnZQ== 56044 -IENyYWNr 56045 -IERpZmZpY3VsdHk= 56046 -S08= 56047 -UmVzZXJ2YXRpb24= 56048 -X3JlcXVpcmVz 56049 -VG91cg== 56050 -44GX44Gf 56051 -LnNldEN1cnJlbnQ= 56052 -IGt5 56053 -IEFsYmFueQ== 56054 -IOin 56055 -bGxlcg== 56056 -YWduYQ== 56057 -d29ya2Vycw== 56058 -LmJsYW5r 56059 -IFByYXllcg== 56060 -TUlD 56061 -IHJlc2lsaWVuY2U= 56062 -VGVY 56063 -IExhbmd1YWdlcw== 56064 -c3R1ZHk= 56065 -CWN1cnI= 56066 -IGVuenltZXM= 56067 -U2x1Zw== 56068 -IO2MjA== 56069 -c3RyYWw= 56070 -IHR1bW9ycw== 56071 -IHNlZ3VuZGE= 56072 -PSd7 56073 -aW5zdHJ1Y3Rpb24= 56074 -IExpc3A= 56075 -L2luZm8= 56076 -ICJ7JA== 56077 -LDopLA== 56078 -IGd2 56079 -KEVycm9yTWVzc2FnZQ== 56080 -ICc9 56081 -fS0kew== 56082 -LkRvY3VtZW50cw== 56083 -IldlbGw= 56084 -IHJlbWluaXNjZW50 56085 -IGdheg== 56086 -aXJvcHI= 56087 -ZWhy 56088 -IHN1cHByZXNzZWQ= 56089 -ZXJzaA== 56090 -LnNjcm9sbFRv 56091 -IGNhZGVuYQ== 56092 -IGdhbWVTdGF0ZQ== 56093 -w61t 56094 -KGNvbnY= 56095 -IFRvbW9ycm93 56096 -IENDVA== 56097 -TW9uZ28= 56098 -dWxn 56099 -LkNhbWVyYQ== 56100 -LmhhbmRsZXJz 56101 -bXBo 56102 -IHN0aw== 56103 -IGdlbmV0aWNz 56104 -QUNJTkc= 56105 -VHJpdmlh 56106 -IEJhbQ== 56107 -KG1hcmtlcg== 56108 -LlN0cmV0Y2g= 56109 -IFN1bm5p 56110 -IEJldHR5 56111 -LnRvbGlzdA== 56112 -dW5saWtlbHk= 56113 -LlJlY3RhbmdsZQ== 56114 -b2Jzb2xldGU= 56115 -SUxPTg== 56116 -aW5uZXJUZXh0 56117 -ZW1ib3VyZw== 56118 -YU4= 56119 -IFZlaGljbGVz 56120 -dW5sb2Nr 56121 -OnV0Zg== 56122 -bm9i 56123 -IFNlZWluZw== 56124 -IE5FVkVS 56125 -IHRscw== 56126 -IGZpbGxlcw== 56127 -IGJlbmVmaXRlZA== 56128 -IENsaW50 56129 -Ki8pLA== 56130 -LmZvbGQ= 56131 -IHBvc2libGU= 56132 -QURFRA== 56133 -dGhvdXNl 56134 -LkRBTA== 56135 -IE9kZA== 56136 -cm9rZXM= 56137 -IFN1bm55 56138 -IFBhcnRpYWxFcQ== 56139 -X0J1ZmZlcg== 56140 -IExldmk= 56141 -bG9uZ3JpZ2h0YXJyb3c= 56142 -ZWxkb24= 56143 -Z2FnZXM= 56144 -X3dhcm4= 56145 -LkNyZWF0ZVRhYmxl 56146 -IERpcA== 56147 -X3F1ZXN0aW9ucw== 56148 -LmxvZ2lj 56149 -ICMi 56150 -PXsoKT0+ 56151 -IHRlcA== 56152 -IGp1aWN5 56153 -7IKs 56154 -ZW5rbw== 56155 -aWFsZWN0 56156 -2Yk= 56157 -IG9uYm9hcmQ= 56158 -IOaP 56159 -CXJ0 56160 -X1VURg== 56161 -IFFBY3Rpb24= 56162 -4oCe 56163 -KENvbXBvbmVudA== 56164 -KGF1ZGlv 56165 -LmhpdA== 56166 -Z3Rl 56167 -IHByb2dyYW1tZWQ= 56168 -c3RhdGVQYXJhbXM= 56169 -IHBvbHllc3Rlcg== 56170 -ZmlyZXM= 56171 -Ynlzcw== 56172 -XT0o 56173 -X3F1YWxpdHk= 56174 -T2ZEYXk= 56175 -IEZhaXJ5 56176 -IHllbGxlZA== 56177 -b3Bs 56178 -KHVzZXJOYW1l 56179 -IERpZmZlcmVuY2U= 56180 -IGV2YWx1YXRpb25z 56181 -aWZmYW55 56182 -IGN5Y2xpc3Rz 56183 -IGNpZGFkZQ== 56184 -IHRleHRib29r 56185 -IHByb2ZpbGluZw== 56186 -X18pLA== 56187 -ZGVh 56188 -LmFjdGl2YXRl 56189 -IGluZGljYXRpb25z 56190 -0JU= 56191 -VG91Y2hVcEluc2lkZQ== 56192 -IGludmFsdWFibGU= 56193 -IE1BU0s= 56194 -IGNvbnRlbmQ= 56195 -RnJlcQ== 56196 -IHJlY3J1aXRz 56197 -KGludGVydmFs 56198 -IFVzZXJQcm9maWxl 56199 -ICcuLy4uLw== 56200 -ZWR1 56201 -X0NhbGxiYWNr 56202 -IGFuYWxvZ3k= 56203 -IFRyb3BoeQ== 56204 -YXBwaGlyZQ== 56205 -VmlkZW9z 56206 -IENoZXI= 56207 -IEhhdg== 56208 -4oCmIg== 56209 -LnZhbGlkYXRvcg== 56210 -Z2Z4 56211 -IFVPYmplY3Q= 56212 -Y2xhc3NuYW1lcw== 56213 -dHJpYW5nbGU= 56214 -IEVuY29kZXI= 56215 -LnNweQ== 56216 -IHByZWRhdG9ycw== 56217 -PXN0YXR1cw== 56218 -LXNhZmU= 56219 -OiIsCg== 56220 -IEluY2x1ZGluZw== 56221 -IHt9Ow0K 56222 -KmNvcw== 56223 -IGVuZHVyZWQ= 56224 -LnN1bGFrZQ== 56225 -IG51cnNlcnk= 56226 -IGZyYWdyYW5jZQ== 56227 -IHJlYnVpbGRpbmc= 56228 -IG50aA== 56229 -IEZyYXNlcg== 56230 -LnNldERhdGU= 56231 -IFZpbmNl 56232 -X1JFU1Q= 56233 -IHZlbnRpbGF0aW9u 56234 -5rW3 56235 -Y3JpYmVz 56236 -LmFzbQ== 56237 -bHBWdGJs 56238 -IEFiZQ== 56239 -dWlzaW5l 56240 -LGFycmF5 56241 -CWNsYXNzTmFtZQ== 56242 -ZXJyYWxz 56243 -ICcKCg== 56244 -Q2hlY2tvdXQ= 56245 -IHNvbGljaXQ= 56246 -QXV4 56247 -X2NhcHR1cmU= 56248 -IHJpYnM= 56249 -cmFnb24= 56250 -dmlvbA== 56251 -dG9waWNz 56252 -RnVuY3Rpb25GbGFncw== 56253 -IE1hcnR5 56254 -YmlrZQ== 56255 -IFR1Y2tlcg== 56256 -KGtlcm5lbA== 56257 -IE9wcw== 56258 -Q2xvc2VPcGVyYXRpb24= 56259 -L2RlbW8= 56260 -aWxkYQ== 56261 -IGzDrW5lYQ== 56262 -QVBQSU5H 56263 -IHN1aXRlcw== 56264 -LnZpc2l0VmFySW5zbg== 56265 -dXJ1cw== 56266 -IE1pbnV0ZQ== 56267 -KG1hbmFnZXI= 56268 -IGJ1dHRlcmZseQ== 56269 -IGFwYXJl 56270 -IHdvbHZlcw== 56271 -SldU 56272 -IFNhbG9u 56273 -CWRlbGF5 56274 -LWVzbGludA== 56275 -aXNhdGlvbnM= 56276 -LnJwYw== 56277 -KXwo 56278 -IFNuYXBjaGF0 56279 -L21t 56280 -TU4= 56281 -Y2VyaWVz 56282 -LnRleHRBbGlnbm1lbnQ= 56283 -IEZyYW5rZnVydA== 56284 -IGFkbw== 56285 -KG5ld1ZhbHVl 56286 -KGFjY2Vzcw== 56287 -KEV4cHJlc3Npb24= 56288 -IFNpZ25Jbg== 56289 -IEhhaXRp 56290 -X3Rw 56291 -LnNldFBhcmFtZXRlcg== 56292 -TWludXRl 56293 -IG1hbnVhbHM= 56294 -cmljYW5lcw== 56295 -IFBUUg== 56296 -IE91dGVy 56297 -IGdldGxpbmU= 56298 -b2NhdGlvbnM= 56299 -X0NE 56300 -IEx5b24= 56301 -L2d1aQ== 56302 -X2xpdmU= 56303 -aWRhbg== 56304 -Lmdlb20= 56305 -IGJvcmRlckJvdHRvbQ== 56306 -aW11dGg= 56307 -X2NoZWNrcG9pbnQ= 56308 -IG1ldQ== 56309 -IElydmluZw== 56310 -IHBldXZlbnQ= 56311 -KE1BWA== 56312 -IEFSQ0g= 56313 -IHBvdg== 56314 -LnNvdXJjZWZvcmdl 56315 -IGphbWFpcw== 56316 -IGFyaw== 56317 -IEJhZ2hkYWQ= 56318 -IENMRUFS 56319 -TWVudUJhcg== 56320 -IHRyb2lz 56321 -Q0hFRFVMRQ== 56322 -ICMNCg== 56323 -KENhbGw= 56324 -JG9yZGVy 56325 -KE1hdGVyaWFs 56326 -IGVuY29udHJhZG8= 56327 -JGxpc3Q= 56328 -IE1FVEhPRFM= 56329 -LmJlZ2luVHJhbnNhY3Rpb24= 56330 -X01BRw== 56331 -U3R5bGVTaGVldA== 56332 -IG1ham9ycw== 56333 -IGluZGVmaW5pdGVseQ== 56334 -Y2xlYW51cA== 56335 -IGhvbWVsYW5k 56336 -KGR0bw== 56337 -RGF0ZXM= 56338 -UHJlc2VudGF0aW9u 56339 -IERL 56340 -PXtgLw== 56341 -CUtleQ== 56342 -KEJsb2Nr 56343 -X2NoZWNrYm94 56344 -bmVlZHM= 56345 -IG9uQ29tcGxldGU= 56346 -cmljbw== 56347 -IGdsZWljaA== 56348 -IHht 56349 -T09E 56350 -QmV0dGVy 56351 -IFNRTElURQ== 56352 -LkJvb2s= 56353 -eGFk 56354 -IEdvbmU= 56355 -CWRw 56356 -IGRldm90aW9u 56357 -IHN0bQ== 56358 -IG9ic2Vzcw== 56359 -IEJhY2tlbmQ= 56360 -UXVlcmllcw== 56361 -SWs= 56362 -Ly8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 56363 -IGRpdmlkZW5kcw== 56364 -LnBhcmVudEVsZW1lbnQ= 56365 -fSIpCgo= 56366 -IE1hdGVyaWFsUGFnZVJvdXRl 56367 -Om51bQ== 56368 -IGV4cGxpYw== 56369 -IE9M 56370 -bGVhc3Q= 56371 -T29wcw== 56372 -aW1lbnRvcw== 56373 -IGluc3VyZXJz 56374 -IGhlcm9pYw== 56375 -CWZpZWxkcw== 56376 -LmltZ3Vy 56377 -LmJ0bkNhbmNlbA== 56378 -IERldGVjdGl2ZQ== 56379 -KHNt 56380 -IE11dGFibGVMaXZlRGF0YQ== 56381 -LmxhYg== 56382 -KChb 56383 -IGhhaXJzdA== 56384 -IFRyYW5zYWN0aW9ucw== 56385 -5byA5aeL 56386 -IHN0ZENsYXNz 56387 -dWVudG8= 56388 -R0lT 56389 -X2NvZA== 56390 -SW5zdHJ1Y3Rpb25z 56391 -Q2FsbHM= 56392 -UG9pbnRlclR5cGU= 56393 -IFJ3 56394 -IGFzc29ydG1lbnQ= 56395 -IERJRw== 56396 -K3I= 56397 -X0NFUlQ= 56398 -IGluc3RhYmlsaXR5 56399 -IHZpYg== 56400 -b25hcw== 56401 -IHJva3U= 56402 -YXBlbGxpZG8= 56403 -IGFuZ2w= 56404 -cHJlbmV1cg== 56405 -IGZsdWlkcw== 56406 -aXNlYXNl 56407 -IGRlZWQ= 56408 -cXVpc3Q= 56409 -X0NPTlNUQU5U 56410 -IGVxdWlsaWJyaXVt 56411 -X2RlbGVnYXRl 56412 -IFF1YW50dW0= 56413 -cmVp 56414 -Q2FwYWJpbGl0aWVz 56415 -cmVjdGFuZ2xl 56416 -Pz48 56417 -YWxpZW4= 56418 -IEp1Zw== 56419 -RE5B 56420 -VGlja2V0cw== 56421 -T2NjdXJz 56422 -IEhhd2s= 56423 -LnNldEhvcml6b250YWxHcm91cA== 56424 -XENvbGxlY3Rpb24= 56425 -ZmZpdGk= 56426 -IHJlYXJy 56427 -LnNldFZlcnRpY2FsR3JvdXA= 56428 -IGNhdml0eQ== 56429 -IGFkdWx0ZQ== 56430 -RmFjYWRl 56431 -LXdo 56432 -IExPTA== 56433 -2LA= 56434 -IGdyYW5kcGFyZW50cw== 56435 -U3dpZnQ= 56436 -CXd4 56437 -5omA5pyJ 56438 -aWZlbg== 56439 -ZmZzZXQ= 56440 -QmV5b25k 56441 -Ly99Cgo= 56442 -IHdhZ2Vy 56443 -IGJ1cnk= 56444 -IGNvbW1lbmNl 56445 -cmVnaXN0cm8= 56446 -c2NpZW50 56447 -IFBlcmNlbnQ= 56448 -INC00L7Qu9C2 56449 -KGlkZW50aWZpZXI= 56450 -LnNldE1vZGVs 56451 -IHNlbGRvbQ== 56452 -bnRvbg== 56453 -IGFwcGxpYW5jZQ== 56454 -YW11cw== 56455 -cnlzbGVy 56456 -IHBhbnRpZXM= 56457 -ZW5ndWlucw== 56458 -IG1pbWlj 56459 -IG9uQ2hhbmdlZA== 56460 -IGFsY29ob2xpYw== 56461 -LnJlbG9hZERhdGE= 56462 -Q2hhcmdl 56463 -IEZheA== 56464 -IGpTY3JvbGxQYW5l 56465 -RW1wcmVzYQ== 56466 -IHNoYXR0ZXJlZA== 56467 -eGJh 56468 -Rm9udHM= 56469 -P3M= 56470 -IHBvc3RzZWFzb24= 56471 -cmV0YWlu 56472 -X3JhdGVz 56473 -IHJlcXVlc3RDb2Rl 56474 -LnRvZG8= 56475 -wrRz 56476 -Q0hL 56477 -IEtlZXBpbmc= 56478 -ZW5nZWFuY2U= 56479 -IHZzY29kZQ== 56480 -SVBQSU5H 56481 -RGVmYXVsdENsb3NlT3BlcmF0aW9u 56482 -X3JhaXNl 56483 -IE9jdWx1cw== 56484 -b2dyYW1z 56485 -cmFq 56486 -cGNp 56487 -IGNvcnJvc2lvbg== 56488 -LmhhbmRsZVN1Ym1pdA== 56489 -QWNjZXNzaWJsZQ== 56490 -IFBpYW5v 56491 -bGl0dGxl 56492 -QUNM 56493 -xIdl 56494 -LnVud3JhcA== 56495 -IENvbnZlcnM= 56496 -IExlYmVu 56497 -aW9uZWVy 56498 -IE1lcmNoYW50 56499 -IEpvcmdl 56500 -IGVtYnJhY2luZw== 56501 -IHZlbnRh 56502 -w6FzdA== 56503 -IHZpZW5l 56504 -PFFTdHJpbmc= 56505 -IGV4cGxvc2lvbnM= 56506 -IGRpc3R1cmJlZA== 56507 -LiI8 56508 -bWVtbw== 56509 -IEFib3JpZ2luYWw= 56510 -IGNvbXBsZXRv 56511 -VGV4UGFyYW1ldGVy 56512 -IHVvbWluaQ== 56513 -KGFnZW50 56514 -0YPRgA== 56515 -IFdob2xlc2FsZQ== 56516 -L2Ft 56517 -IEJvb2ttYXJr 56518 -ZHJhZ29u 56519 -IGdsb3Zl 56520 -ICIiKSk7Cg== 56521 -aXZhcmlhdGU= 56522 -bm93cmFw 56523 -SW5DaGlsZHJlbg== 56524 -LkJy 56525 -IGNvbmV4aW9u 56526 -IGJhY2tib25l 56527 -IGVjbGlwc2U= 56528 -IHBlcnNlY3V0aW9u 56529 -JzoKCg== 56530 -L2xpbms= 56531 -IFBlcm8= 56532 -YW5kYXM= 56533 -IFRlaw== 56534 -LiIpOw== 56535 -LWFuYWx5c2lz 56536 -IGVyYWQ= 56537 -TWFyc2hhbA== 56538 -IGFuY2hvcnM= 56539 -b2dlcg== 56540 -IGNvbnZlcmdlbmNl 56541 -c3RpY2t5 56542 -IG5hdmVn 56543 -aW50ZXJu 56544 -X0RFU0NSSVBUT1I= 56545 -IENvbnN1bHRhbnQ= 56546 -ICAgICAgICAgICAgICAgICAgICAgCg== 56547 -IEF1Y2g= 56548 -IGVycmU= 56549 -xZtsaQ== 56550 -IEhvcml6b24= 56551 -Y29sYQ== 56552 -SW5zdGFsbGF0aW9u 56553 -aG90bWFpbA== 56554 -Q05O 56555 -LkNvbGxlY3RvcnM= 56556 -Y2hz 56557 -KHRyYWNl 56558 -IEVuY3J5cHQ= 56559 -IC0tLS0tLQ== 56560 -IEJhc2VDb250cm9sbGVy 56561 -IGFndWE= 56562 -IHJlYWN0aXZl 56563 -aWRs 56564 -IGNsYXNzTmFtZXM= 56565 -CVNlc3Npb24= 56566 -IERvZGdlcnM= 56567 -SGFk 56568 -X2x2 56569 -SXNWYWxpZA== 56570 -IEhFTFA= 56571 -dXR0bw== 56572 -IFZlcmlmaWNhdGlvbg== 56573 -IGdldGVudg== 56574 -X3Bh 56575 -LmJtcA== 56576 -OmY= 56577 -IExvdWlzZQ== 56578 -KCc7 56579 -L3NvY2tldA== 56580 -R3JhbnRlZA== 56581 -LmNhbGVuZGFy 56582 -KElQ 56583 -IFBY 56584 -LlJvb20= 56585 -IHByb2dyYW1t 56586 -ZW5zaQ== 56587 -IHRhYmxlc3Bvb25z 56588 -IGxldmU= 56589 -IG1vc3Ry 56590 -LnRpcG8= 56591 -L2Fu 56592 -KGRp 56593 -IGJpb2Q= 56594 -IGRiQ29udGV4dA== 56595 -IEpTWA== 56596 -CXJlc3VsdHM= 56597 -LkVORA== 56598 -aHRl 56599 -bGlmeQ== 56600 -UHJlY2lzaW9u 56601 -6IqC 56602 -QVJTRVI= 56603 -KWRpZFJlY2VpdmVNZW1vcnlXYXJuaW5n 56604 -YXR0ZW1wdA== 56605 -SVNQ 56606 -JmE= 56607 -X1BPUA== 56608 -IFRhYw== 56609 -IHByZXBhcmVkU3RhdGVtZW50 56610 -INC30LDQv9C40YE= 56611 -IG93aW5n 56612 -LHN0YXJ0 56613 -IHJldmlld2Vy 56614 -IHJzdA== 56615 -IHByb3BUeXBlcw== 56616 -IHJvY2t5 56617 -X2xvY2FsZQ== 56618 -IFN0cmF0ZWdpZXM= 56619 -IFdlYmVy 56620 -LkNhc2NhZGU= 56621 -X2VxdWFsVG8= 56622 -IGNvc2Fz 56623 -IERlbGV0ZXM= 56624 -IE1heGlt 56625 -IHNocmltcA== 56626 -cmV0cmlldmU= 56627 -LkluY2x1ZGU= 56628 -SUdJTg== 56629 -IE9F 56630 -XSk7DQoNCg== 56631 -LmVudW1lcg== 56632 -IGNvZWY= 56633 -X051bGw= 56634 -UmE= 56635 -dHlhcmQ= 56636 -IFNoYXdu 56637 -a2VlcGVycw== 56638 -IHFx 56639 -X3Ni 56640 -b21lbnM= 56641 -IEV4ZWN1dGVz 56642 -IyI= 56643 -VFRZ 56644 -IFZhbHVlVHlwZQ== 56645 -KTsqLwo= 56646 -IEFic29sdXRlbHk= 56647 -IFRvdHRlbmhhbQ== 56648 -L2FydA== 56649 -IGJsZXNzaW5ncw== 56650 -IHN3aWZ0bHk= 56651 -YnVzdGVy 56652 -IGF2aWQ= 56653 -Q09NTQ== 56654 -LHRlbXA= 56655 -IH0/Pgo= 56656 -LWdyb3dpbmc= 56657 -IGRlZXBjb3B5 56658 -QWNr 56659 -ZWdnaWVz 56660 -IF9fKCI= 56661 -IG5vaXI= 56662 -dGVycm9yaXNt 56663 -IGFudGhlbQ== 56664 -YWdlbmN5 56665 -X1BBQ0tBR0U= 56666 -IENsb3N1cmU= 56667 -LnJlZ2lzdHJ5 56668 -IG1hbW1hbHM= 56669 -PEw= 56670 -VUlDb2xsZWN0aW9uVmlldw== 56671 -IExFRHM= 56672 -IHZvbGxleQ== 56673 -KEJ1ZmZlcg== 56674 -X05BVElWRQ== 56675 -bGliYw== 56676 -aW1wbG9kZQ== 56677 -U2Nyb2xsQmFy 56678 -IE1hcmlvbg== 56679 -LkNvbnRyYWN0cw== 56680 -X0F0 56681 -IFdlaW5zdGVpbg== 56682 -Y29tcGFyZVRv 56683 -IEhvc2U= 56684 -ZW5pdHk= 56685 -LmNyZWF0ZVF1ZXJ5 56686 -X3JvdXRlcg== 56687 -IHN0aW11bGk= 56688 -ICsrKQ== 56689 -IENoYW1w 56690 -IEJheWVybg== 56691 -YXNzYQ== 56692 -LnZh 56693 -IGRpc3RyaWJ1dG9ycw== 56694 -IGZpbGVwcml2YXRl 56695 -IGRlcGFydGVk 56696 -Y2NjYw== 56697 -QGNsaWNr 56698 -IEx1bmNo 56699 -Pkw= 56700 -IGJsdWV0b290aA== 56701 -LkRlZXA= 56702 -LXN0YW5kaW5n 56703 -w6FjaWw= 56704 -IHJvb2Z0 56705 -IFBhdGhz 56706 -X2l0ZXJhdGlvbnM= 56707 -SW52YWxpZEFyZ3VtZW50RXhjZXB0aW9u 56708 -LnNwaQ== 56709 -IFVJQWxlcnRBY3Rpb24= 56710 -dXll 56711 -c2lnbmlu 56712 -LnByaW9yaXR5 56713 -IEVzc2F5cw== 56714 -PSd7JA== 56715 -IOi/lOWbng== 56716 -X3NpZ25lZA== 56717 -LnBlcnNpc3Q= 56718 -IHJlZGVzaWdu 56719 -VG9Mb3dlcg== 56720 -IE5ld21hbg== 56721 -PXN0YXJ0 56722 -IElzcmFlbGlz 56723 -YXNpc3dh 56724 -U3BlZWNo 56725 -IG51bWVyb3M= 56726 -aGFuZGxlcnM= 56727 -IFdvbmc= 56728 -INC80LXRgtC+0LQ= 56729 -V2VpZ2h0cw== 56730 -IEd1amFy 56731 -dGVpbA== 56732 -IE5vbmV0aGVsZXNz 56733 -X0VGRkVDVA== 56734 -IHZlY3Q= 56735 -IE9zYw== 56736 -IGNvYXRz 56737 -IFdoZWF0 56738 -IGdlZWs= 56739 -IFBST1BFUlRZ 56740 -d29ybQ== 56741 -X2NvbnN0YW50cw== 56742 -IEJvdWxkZXI= 56743 -IFBhcm0= 56744 -Y29sZQ== 56745 -IGRlZmF1bHRDZW50ZXI= 56746 -IFJvdWdl 56747 -OkE= 56748 -eGNm 56749 -IFZlbmljZQ== 56750 -bWVkaWFu 56751 -IHJlZGVtcHRpb24= 56752 -RnJlc2g= 56753 -IGNvc20= 56754 -IGZpZ3Vy 56755 -IHJlZnVyYg== 56756 -Q09QRQ== 56757 -LmNk 56758 -IGNob3Jkcw== 56759 -IFNndA== 56760 -xY0= 56761 -VlBO 56762 -IFNFTkQ= 56763 -YWluZW4= 56764 -X2FjY291bnRz 56765 -IHRlbnRo 56766 -IGRpc3NvbHZlZA== 56767 -PEFwcA== 56768 -IENvdmVyYWdl 56769 -dXNlU3RhdGU= 56770 -w6lybw== 56771 -Li48 56772 -IOyjvA== 56773 -IGRyZWFtaW5n 56774 -IEZvcmVjYXN0 56775 -LkN1cnNvcnM= 56776 -IHZpc2Fz 56777 -L3NjcmlwdA== 56778 -X3N0YXJ0ZWQ= 56779 -IGdhc3Ry 56780 -KFBSTw== 56781 -XTsvLw== 56782 -LlRpbGU= 56783 -KnNpbg== 56784 -KEFkYXB0ZXI= 56785 -IFNhbmRyYQ== 56786 -X1NJRw== 56787 -YXJkYXNo 56788 -IE92YWw= 56789 -IGRlc2NyaXBjaW9u 56790 -KHNs 56791 -IERlc2NyaXB0b3I= 56792 -IGAk 56793 -L2ZyZWU= 56794 -IEtleXdvcmRz 56795 -IHR1ZG8= 56796 -aW9uYWxl 56797 -KGZvdW5k 56798 -Lnh5eg== 56799 -IEdlbmVyYXRpb25UeXBl 56800 -X0RJU0FCTEVE 56801 -KGFyZWE= 56802 -IGVsaXRlcw== 56803 -IGhvbWJyZQ== 56804 -KG1lc3NhZ2Vz 56805 -IFJhYw== 56806 -IGV4dGluZ3U= 56807 -IEVzdGE= 56808 -b3Bv 56809 -LnZlbA== 56810 -bW91c2VvdXQ= 56811 -IGNvbnZvbHV0aW9u 56812 -IEhhbmRsaW5n 56813 -IGNlaWxpbmdz 56814 -VGVr 56815 -IEFyZWFz 56816 -LndyaXRlcm93 56817 -PFZpZXc= 56818 -IENvcm5lbGw= 56819 -X0JJTg== 56820 -LmludmFsaWQ= 56821 -JycnDQo= 56822 -aWXFvA== 56823 -X1Bvc2l0aW9u 56824 -IGtpZGRpbmc= 56825 -UENPREU= 56826 -IHdhdGNoZXI= 56827 -bG94 56828 -IOKX 56829 -RGF2ZQ== 56830 -X2FsbG93 56831 -IGJpc2V4dWFs 56832 -IHVub3JkZXJlZA== 56833 -IFNjaHdl 56834 -X3NlZ21lbnRz 56835 -IHRlYXJpbmc= 56836 -SU5MSU5F 56837 -IHVuZGVz 56838 -Lmdvb2Rz 56839 -LmNhbQ== 56840 -IExX 56841 -CXdoZXJl 56842 -Q2FsY3VsYXRvcg== 56843 -LXRocmVhdA== 56844 -LWFsZXJ0 56845 -IFN1enVraQ== 56846 -IElQQQ== 56847 -IEF0dGFjaG1lbnQ= 56848 -QUNDRVNT 56849 -KGR0eXBl 56850 -T3Bw 56851 -X3N5bWJvbHM= 56852 -IGRhbnNrZQ== 56853 -bGFnZQ== 56854 -b3JnZXQ= 56855 -cmVzb2x1dGlvbg== 56856 -0LXRhw== 56857 -IFFDb2xvcg== 56858 -IEJhcnJldHQ= 56859 -0LDRhtC40Y8= 56860 -PVwn 56861 -IE5hdkNvbnRyb2xsZXI= 56862 -L3JlZg== 56863 -KGNvdW50cnk= 56864 -X0hEUg== 56865 -IHRlcnNlYnV0 56866 -cGV0aXRpb24= 56867 -IHN1Zg== 56868 -Y3JlZGl0cw== 56869 -4LmM 56870 -eG0= 56871 -IERhdmllcw== 56872 -LnJlZGRpdA== 56873 -IHdvdmVu 56874 -IE9ibA== 56875 -IEtN 56876 -IENvbnNpZGVyaW5n 56877 -ZW5zb3JlZA== 56878 -LnBlcmlvZA== 56879 -IGRkbA== 56880 -JHdw 56881 -IGV4dHJlbWlzdA== 56882 -O1wK 56883 -IGtpbQ== 56884 -YWxlcnM= 56885 -IHNwYW5uaW5n 56886 -IGNvaGVyZW50 56887 -IGNvbnNlZ3U= 56888 -LnRleHRMYWJlbA== 56889 -LmdlbmVyYWw= 56890 -X2Rhc2hib2FyZA== 56891 -0LvQtdC90LjQtQ== 56892 -a2ljaw== 56893 -X1BJRA== 56894 -IEV4dGVuc2lvbnM= 56895 -cmVnZXhw 56896 -IENsYXVzZQ== 56897 -X21vdg== 56898 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 56899 -IFJld2FyZA== 56900 -IExFR08= 56901 -QWs= 56902 -PS09LT0tPS0= 56903 -CXBhcnNlcg== 56904 -IG9uemU= 56905 -6YCA 56906 -4oCd44CC 56907 -X2JhbGw= 56908 -KHJocw== 56909 -IGNob3J1cw== 56910 -PGNvdW50 56911 -YXN1cmFibGU= 56912 -IHdpcmtsaWNo 56913 -IEVyaW4= 56914 -IE1TTkJD 56915 -IGV0dGVy 56916 -IENyb24= 56917 -X0ZMT1c= 56918 -ICwNCg== 56919 -IGNhbGlkYWQ= 56920 -IEZpbGVXcml0ZXI= 56921 -CXN0bXQ= 56922 -KEJ5dGU= 56923 -X3BhdA== 56924 -IHRlbGVzY29wZQ== 56925 -IGdyZWVk 56926 -IFRvcnQ= 56927 -KHdyaXRl 56928 -XGFwcGxpY2F0aW9u 56929 -CVJUTFI= 56930 -IENvbmZpZ3VyYXRpb25NYW5hZ2Vy 56931 -VW5peA== 56932 -RW5kVGltZQ== 56933 -SW5jbHVkZXM= 56934 -IEhhcnZlc3Q= 56935 -ZW5iZXJn 56936 -IEF1c3RyYWxpYW5z 56937 -IOuT 56938 -IHJu 56939 -IHJlcHV0YWJsZQ== 56940 -IGJsZW5kaW5n 56941 -VUxBVElPTg== 56942 -IEJyZW5kYW4= 56943 -ZGFk 56944 -IG3DuA== 56945 -IFdvbw== 56946 -X2Rj 56947 -VW5l 56948 -IHJ1ZQ== 56949 -d2l0aGlu 56950 -YW5nZXA= 56951 -IHBvdWNo 56952 -XCIiLA== 56953 -IFNpYw== 56954 -4oCdKSw= 56955 -YWx5emU= 56956 -IEdlZg== 56957 -Y292ZXJz 56958 -IGRibw== 56959 -cmVwbGFjZUFsbA== 56960 -CUxvZ2dlcg== 56961 -VHJ5aW5n 56962 -W3N0YXRl 56963 -LXBpZWNl 56964 -6ZaT 56965 -YmVoYXZpb3I= 56966 -YWxsb3dz 56967 -bHJ0 56968 -X3B5dGhvbg== 56969 -ZXJ0dXJh 56970 -LWNvdW50cnk= 56971 -IFRH 56972 -LlVJTWFuYWdlcg== 56973 -YmVucw== 56974 -YWxleA== 56975 -IEJyZWl0YmFydA== 56976 -YmFj 56977 -IHByZWRpY3Rz 56978 -IGdhYg== 56979 -IGNhcmRpbmFs 56980 -LlRpbWVVbml0 56981 -IFZpc2l0b3I= 56982 -IE1pbmc= 56983 -IGxpdnJl 56984 -IHBhcmVudElk 56985 -cG9ydHVu 56986 -IGRpbWVuc2lvbmFs 56987 -IFZlc3Q= 56988 -ZW5pYw== 56989 -4LM= 56990 -INmH 56991 -IEJMVUU= 56992 -IGl0ZW1Db3VudA== 56993 -IGZlYXRoZXJz 56994 -CXBzdG10 56995 -IFBvbGFy 56996 -ey8v 56997 -dW5kaQ== 56998 -0YPQtg== 56999 -emFy 57000 -RXJyb3JSZXNwb25zZQ== 57001 -7IOB 57002 -UmVwcmVzZW50YXRpb24= 57003 -Kl8= 57004 -K10= 57005 -cHJlcGVuZA== 57006 -ICc+ 57007 -IGxlZ2l0aW1hY3k= 57008 -IG9v 57009 -U2xpbmt5 57010 -IG5hdGlvbmFscw== 57011 -LndvcmRz 57012 -O3A= 57013 -dHJhcA== 57014 -b21hbmlw 57015 -IGN1ZXM= 57016 -IGdyYWR1YXRpbmc= 57017 -IHNlbWFwaG9yZQ== 57018 -Il0pOwoK 57019 -YWNleQ== 57020 -UkVFVA== 57021 -R3JhYg== 57022 -IEZlbGl4 57023 -KElk 57024 -X25laWdoYm9ycw== 57025 -IG1lYW5pbmdsZXNz 57026 -KGRlbA== 57027 -IGplZGVy 57028 -IENvbnRlbnRWYWx1ZXM= 57029 -LmFic29sdXRl 57030 -L2Ns 57031 -IHhi 57032 -ZGF0dW0= 57033 -IHRvcnR1cmVk 57034 -IHJ1YmJpbmc= 57035 -U2NvcmVz 57036 -IPCfmIk= 57037 -IGF2b25z 57038 -IGFtc3RlcmRhbQ== 57039 -RU9T 57040 -SGFs 57041 -IHRydXN0d29ydGh5 57042 -Iz0= 57043 -LkVYVFJB 57044 -IG1hbm8= 57045 -aXNpY2luZw== 57046 -LXN1cHBvcnQ= 57047 -CWN1cnNvcg== 57048 -IFNwbw== 57049 -YWltYXNzYWdl 57050 -TWlzc2lvbg== 57051 -W117Ig== 57052 -IHByaW50ZXJz 57053 -R1JFRU4= 57054 -IHRlZw== 57055 -IGFiZG9taW5hbA== 57056 -IQoKCgoKCg== 57057 -LlNob3J0 57058 -0LDQt9Cy 57059 -IEdpZnRz 57060 -fSIp 57061 -KGJpbmRpbmc= 57062 -eGNl 57063 -4oCR 57064 -aW5mb3M= 57065 -Rm9ybURhdGE= 57066 -IGRhcnQ= 57067 -IGVsZW1z 57068 -KGludg== 57069 -WUw= 57070 -dGlu 57071 -R0VORVI= 57072 -4buv 57073 -IFRha2Vu 57074 -dWNrbGU= 57075 -OmU= 57076 -IHNwZWN0cmFs 57077 -LmJhaWR1 57078 -LycpOwo= 57079 -IGdyZWVkeQ== 57080 -ZXNpb24= 57081 -LCwsLCwsLCw= 57082 -IC8+LAo= 57083 -SW50ZXJuYWxTZXJ2ZXJFcnJvcg== 57084 -TlNOb3RpZmljYXRpb25DZW50ZXI= 57085 -IEFp 57086 -IHNwaXQ= 57087 -IGF1Z21lbnRlZA== 57088 -IHN0YW5kYXJkVXNlckRlZmF1bHRz 57089 -RklOSVRZ 57090 -UmFjZQ== 57091 -OkM= 57092 -IFJFQ09SRA== 57093 -IEhpZ2hsaWdodA== 57094 -ICdg 57095 -IGRlZmljaXRz 57096 -IG5laQ== 57097 -IHJlc2VhcmNoZWQ= 57098 -VGE= 57099 -IGNvcHA= 57100 -LkdldEhhc2hDb2Rl 57101 -KToNCg0K 57102 -T25DbGljaw== 57103 -IFdlbGxpbmd0b24= 57104 -IHJldml2YWw= 57105 -5q+U 57106 -6Zeu 57107 -IE5TUw== 57108 -IGZvcm4= 57109 -IGludMOp 57110 -IEt1d2FpdA== 57111 -X2ZsaXA= 57112 -X2Jv 57113 -X1w= 57114 -IG9jY3VycmVuY2Vz 57115 -IFNjaWVudGlzdHM= 57116 -U1JD 57117 -b2dlbnM= 57118 -aWdyYW50 57119 -UkVNT1RF 57120 -IFNJRA== 57121 -Lm9wdHM= 57122 -dXZl 57123 -KCldKQo= 57124 -IGxpYmVydGFyaWFu 57125 -IEdsaWRl 57126 -bGVzZW4= 57127 -IGZvcm1l 57128 -b3dhbmlh 57129 -IGFubm95ZWQ= 57130 -RGVmcw== 57131 -IEV4ZWN1dG9y 57132 -IGNhc3Rz 57133 -LnNldENoZWNrZWQ= 57134 -IFNoYXJpbmc= 57135 -LlNlcmlhbGl6ZU9iamVjdA== 57136 -IHNlbGVjdG9ycw== 57137 -X09USEVS 57138 -66+4 57139 -KHN1cGVy 57140 -KE9T 57141 -X1ZFUklGWQ== 57142 -aWR1bnQ= 57143 -PGhlYWRlcg== 57144 -IC8+JzsK 57145 -IHZpZMOpbw== 57146 -IE5lZ3Jv 57147 -IExvcmRz 57148 -IFRvdXJz 57149 -IHNvZnRseQ== 57150 -LnJlY2VpdmU= 57151 -IEVSQw== 57152 -IGRhdGFTZXQ= 57153 -QmFkZ2U= 57154 -CUV2ZW50 57155 -IHBlcmw= 57156 -IHt9XA== 57157 -KHNlbnRlbmNl 57158 -T3JVcGRhdGU= 57159 -IGRpbWluaXNo 57160 -UElO 57161 -KGRyYXc= 57162 -LlRvRGF0ZVRpbWU= 57163 -LkVxdWFsVG8= 57164 -KHBpbg== 57165 -LXBlbmNpbA== 57166 -bHVlbnQ= 57167 -IENhbGxlcg== 57168 -IHBsYXlmdWw= 57169 -LScr 57170 -eGNh 57171 -c3dpY2s= 57172 -KXt9Cg== 57173 -fTokew== 57174 -IE1ldGg= 57175 -LmdldENlbGw= 57176 -LmJyZWFr 57177 -IHltYXg= 57178 -PSc8Pw== 57179 -LWpzb24= 57180 -IHByaW1laXJv 57181 -IGluZGljZQ== 57182 -44Kj 57183 -IFVOSVRZ 57184 -KGFi 57185 -0YbQuNC4 57186 -X0hBVkU= 57187 -LXllYXJz 57188 -IEVyZG9nYW4= 57189 -LXN0YWNr 57190 -IGRpc2NoYXJnZWQ= 57191 -IGJyZWF0aHRha2luZw== 57192 -IGdyYXNzcm9vdHM= 57193 -IEFzaWRl 57194 -aGVsbA== 57195 -IHNuYWtlcw== 57196 -L2xvZ291dA== 57197 -IG1pbldpZHRo 57198 -IEhlYXI= 57199 -IFN0b25lcw== 57200 -IFdpc2RvbQ== 57201 -IEV2ZW5pbmc= 57202 -X2JsYW5r 57203 -IFByb21vdGlvbg== 57204 -IE1NTQ== 57205 -IEJhcnM= 57206 -44K3 57207 -bmo= 57208 -X1RJ 57209 -IFNvY2lhbGlzdA== 57210 -IEVH 57211 -LW9wdA== 57212 -PVwiJA== 57213 -KGRpYWxvZw== 57214 -IGJlaG9sZA== 57215 -IGludHJpY2F0ZQ== 57216 -IGVyZWN0aWxl 57217 -RXh0cmFjdG9y 57218 -IHNjbA== 57219 -IGNsYXM= 57220 -KGhpc3Rvcnk= 57221 -aWRlbnRhbGx5 57222 -IHBuZXVt 57223 -UmFuZA== 57224 -IExhcHRvcA== 57225 -Y2FsbGVy 57226 -IEZsb29k 57227 -b3BlbmVk 57228 -dWRkZXI= 57229 -IEdldHRlcg== 57230 -X3dhbGs= 57231 -KHdlaWdodA== 57232 -IEFsZXhhbmRyaWE= 57233 -IHRhYmxlYXU= 57234 -VmFyaQ== 57235 -IC0tLS0tLS0t 57236 -6Iez 57237 -ZXdvcnRoeQ== 57238 -U3BlY2lmaWNhdGlvbg== 57239 -IHRocmVzaG9sZHM= 57240 -KCIiKTsKCg== 57241 -X2ZvdXI= 57242 -IFNhZGx5 57243 -IChfKQ== 57244 -aXNtYXRpYw== 57245 -IEphaWw= 57246 -dG9IYXZlQmVlbkNhbGxlZFdpdGg= 57247 -Lm1hcg== 57248 -IHByZXZpZXdz 57249 -IHNjYWZm 57250 -aW5kaWNhdG9y 57251 -IGNvZGVjcw== 57252 -IGF1dG9j 57253 -KHJ0 57254 -LmdldEhvdXJz 57255 -IFJI 57256 -IFN1cmdl 57257 -aXZhbWVudGU= 57258 -IGNvbnRlbmRlcg== 57259 -Q3BwR2VuZXJpY0NsYXNz 57260 -IDs7Xg== 57261 -OjoqOwo= 57262 -LXJlY29yZA== 57263 -IG1hbWE= 57264 -IGltZ3M= 57265 -LmlzTG9hZGluZw== 57266 -IG5lZWRsZXM= 57267 -IGVuY3VlbnRyYQ== 57268 -b2RhdGE= 57269 -IEJ1ZmZlcmVkSW1hZ2U= 57270 -CWphdmE= 57271 -IFRvbWI= 57272 -VU5JVFk= 57273 -IGxpbmdlcmll 57274 -IEphbWFpY2E= 57275 -YnVncw== 57276 -KioKCg== 57277 -IE1hbw== 57278 -LmJlZ2luUGF0aA== 57279 -IHByb3N0aXR1dA== 57280 -IFBoaWxpcHBpbmU= 57281 -X3Nm 57282 -X3Bvdw== 57283 -IFNjaG8= 57284 -eGRl 57285 -J8OpdA== 57286 -4oCZYXV0 57287 -YWlzb24= 57288 -IEZpbGVJbmZv 57289 -dHVybnN0aWxl 57290 -ZHJlYW0= 57291 -IGlWYXI= 57292 -c3ludGF4 57293 -aWxsaXNlY29uZHM= 57294 -cHJvZmlsZXM= 57295 -X1JFR0VY 57296 -INC00L4= 57297 -IENvbW11bg== 57298 -QmV0 57299 -aXB6aWc= 57300 -IE1lbW8= 57301 -Lmlkcw== 57302 -IHBob3RvZ3JhcGhlZA== 57303 -IGFwcHJveGltYXRpb24= 57304 -OnZhcmlhYmxlcw== 57305 -IG1vZGlmaWNhcg== 57306 -X1NNQUxM 57307 -IEhlbXA= 57308 -IGRpc3Jlc3BlY3Q= 57309 -IGNvbnRlc3RlZA== 57310 -IGlubm9jZW5jZQ== 57311 -aWxsaXM= 57312 -U3ltYm9scw== 57313 -IGluc3BpcmF0aW9uYWw= 57314 -IGRpc2NpcGxpbmFyeQ== 57315 -IFBlcm1hbmVudA== 57316 -IGRlc2Ny 57317 -IFVOREVS 57318 -0YHRiw== 57319 -cHJlc3Nvcg== 57320 -SU1FUg== 57321 -IG1vdW50cw== 57322 -IG1vcmFsbHk= 57323 -X1NFQ09ORA== 57324 -LmZpbGVOYW1l 57325 -44OX 57326 -IGNvbnN0cnVjdHM= 57327 -IFNVTg== 57328 -RVNQ 57329 -RmluYW5jaWFs 57330 -IE51cg== 57331 -w7RsZQ== 57332 -cmljdWxhcg== 57333 -IFVzZXJNYW5hZ2Vy 57334 -aWJpbGlkYWQ= 57335 -IG9uUmVzcG9uc2U= 57336 -IGZpbG1tYWtlcg== 57337 -IGFsb3Q= 57338 -X1RIUkVBRFM= 57339 -IGVudmlyb25tZW50YWxseQ== 57340 -Li4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u 57341 -IHJhc2g= 57342 -IEx5cmljcw== 57343 -IGlwYWlycw== 57344 -QmFja3Vw 57345 -U2lnbnVw 57346 -IEB7Cg== 57347 -SlVuaXQ= 57348 -d29ya2Zsb3c= 57349 -IENvbXBsZXRpb24= 57350 -IGludHVpdGlvbg== 57351 -8J0= 57352 -IG1pYQ== 57353 -IFNuYWNrYmFy 57354 -IFRpbg== 57355 -CWluc3RhbmNl 57356 -IE11c2ljYWw= 57357 -IHdlbGNvbWVz 57358 -IHJlZHJhdw== 57359 -X2NvbG91cg== 57360 -X1JFQUxUWVBF 57361 -X3NpbmNl 57362 -IEJ5dGVBcnJheU91dHB1dFN0cmVhbQ== 57363 -LWRlbWFuZA== 57364 -YXJldGg= 57365 -LnBhZA== 57366 -c2Vr 57367 -JywuLi4K 57368 -LWZpcmU= 57369 -Lnw= 57370 -IG51bWI= 57371 -IERPVUJMRQ== 57372 -QU1BR0U= 57373 -Y2htb2Q= 57374 -LWls 57375 -IGFsYXJtaW5n 57376 -Q29w 57377 -5aSH 57378 -aW52aXRl 57379 -X0lURU1T 57380 -IGxldWs= 57381 -IHJlZWw= 57382 -IGZ1bGZpbGxtZW50 57383 -UmVzdG9yZQ== 57384 -X3Jy 57385 -KGNsYXNzZXM= 57386 -IHBhZ2luZw== 57387 -eW1heA== 57388 -cmFwcGVk 57389 -7ZmU 57390 -fWB9Pgo= 57391 -IEhpcm8= 57392 -KFRSVUU= 57393 -YXN1cmVy 57394 -IGN1ZXI= 57395 -VWJlcg== 57396 -Lk9wZXJhdGlvbg== 57397 -IG9sYW4= 57398 -IHRocmlsbGluZw== 57399 -PFJlc3BvbnNl 57400 -IEZlbWlu 57401 -IHRyYXZlcnNhbA== 57402 -IHBvYw== 57403 -IHNldFN0YXR1cw== 57404 -ZGVjbGFy 57405 -c3RkYWZ4 57406 -IGFkZGljdGl2ZQ== 57407 -IEJ0bg== 57408 -IGV4cGxvc2l2ZXM= 57409 -IENvb2tpbmc= 57410 -IFBsYWludA== 57411 -IGFjY3VtdWxhdG9y 57412 -IEFwcG9pbnRtZW50 57413 -LHBhc3N3b3Jk 57414 -IEZBUg== 57415 -bHVldA== 57416 -RnVydGhlcm1vcmU= 57417 -ZGVjbHNwZWM= 57418 -X1N0YXRpY3M= 57419 -LkRpY3Rpb25hcnk= 57420 -Ij4nLg== 57421 -CXZhbGlk 57422 -IiIs 57423 -SW5zdHJ1bWVudA== 57424 -Pko= 57425 -IG5vc3Ry 57426 -IFJpZnQ= 57427 -X1BvcnQ= 57428 -IHZlY2Vz 57429 -W1sn 57430 -IHJhbGxpZXM= 57431 -LXNlcmllcw== 57432 -IHZ2 57433 -LnVj 57434 -IHJ0bg== 57435 -U3RhdGVDaGFuZ2Vk 57436 -KGlucw== 57437 -IENsYQ== 57438 -LS0tLS0tLS0tLS0tCg== 57439 -Y3Vz 57440 -IFJlbG9hZA== 57441 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 57442 -LnNlY29uZHM= 57443 -X2Rlc3RpbmF0aW9u 57444 -IHNjcmV3ZWQ= 57445 -PmM= 57446 -VGhpY2tuZXNz 57447 -RGVzaWduZXI= 57448 -IGdyaWRz 57449 -bsSF 57450 -KGNvb2tpZQ== 57451 -VHJpcA== 57452 -LU1vYmlsZQ== 57453 -IHZvbGw= 57454 -IGdlbml0YWw= 57455 -IGNvbmZpc2M= 57456 -IENvbmZlZGVyYXRl 57457 -IHdlYlZpZXc= 57458 -IG1pc2U= 57459 -IGNsZXI= 57460 -KHNlbGVjdGlvbg== 57461 -JGRhdGU= 57462 -IHNoYXJwZW4= 57463 -cmFnZW4= 57464 -QW5kVXBkYXRl 57465 -IHJlbWl4 57466 -IGh0b25z 57467 -Ulc= 57468 -TVBJ 57469 -IHJldHJpZXZhbA== 57470 -IHJpY2hlc3Q= 57471 -LkRlY29kZQ== 57472 -OmluaXRDb21wb25lbnRz 57473 -IFRWYWx1ZQ== 57474 -U2FpbnQ= 57475 -QGluY2x1ZGU= 57476 -IFBFUlNPTg== 57477 -LnNlcA== 57478 -IExEQVA= 57479 -Z2Jh 57480 -IGdyb8OfZQ== 57481 -IHJlbGlhYmx5 57482 -IERGUw== 57483 -LmdldEl0ZW1JZA== 57484 -IHByw6lzZW50 57485 -LmdldFRva2Vu 57486 -IGNoaW5lc2U= 57487 -IE1lYWw= 57488 -WU9V 57489 -Ij48Pz0k 57490 -KGNob2ljZQ== 57491 -IHBoZW5vbWVuYWw= 57492 -IFN0ZWVsZQ== 57493 -wqI= 57494 -IFBhY2thZ2VNYW5hZ2Vy 57495 -IFN5bmRyb21l 57496 -RGlyZWN0b3JpZXM= 57497 -aXZhcg== 57498 -LnVuc3Vic2NyaWJl 57499 -bGllw58= 57500 -bW9ubw== 57501 -X2Nvbm5lY3Rpb25z 57502 -X3ByZXNlbmNl 57503 -eW55 57504 -S25pZmU= 57505 -IGdyb292ZQ== 57506 -IHNjb29w 57507 -VEVNUEw= 57508 -YXNha2k= 57509 -LmhhbWNyZXN0 57510 -IGhhcmJvcg== 57511 -Y292 57512 -Kno= 57513 -IFh1 57514 -IHByb3Bvc2luZw== 57515 -IEZSQU1F 57516 -Q2hpcA== 57517 -IEVlbg== 57518 -IOyghA== 57519 -IHNtYXNoZWQ= 57520 -VW5zaWduZWQ= 57521 -KC4u 57522 -X2ZpbmlzaGVk 57523 -IGdldFN0YXR1cw== 57524 -IGZpYnJl 57525 -QXhlcw== 57526 -ICcvJyw= 57527 -eWFyZHM= 57528 -TURC 57529 -LWJz 57530 -aW50ZW50 57531 -IGJvb3N0ZXI= 57532 -LmRzdA== 57533 -LkRpYWxvZ1Jlc3VsdA== 57534 -IE1ldHM= 57535 -IGJlYXN0cw== 57536 -aW5jcmVtZW50cw== 57537 -LmthZmth 57538 -VUlBbGVydEFjdGlvbg== 57539 -LWV2ZXI= 57540 -X2JhbA== 57541 -IGhlbHQ= 57542 -IGZyZW9wZW4= 57543 -IFJlY3J1aXRtZW50 57544 -bGljdHM= 57545 -Zm9yZ2V0dGFibGU= 57546 -RGlzcGxheWVk 57547 -X1ZFTkRPUg== 57548 -Q29sbGVnZQ== 57549 -QVNDSUk= 57550 -IFNpbms= 57551 -IE1hY2Vk 57552 -IGN0b3I= 57553 -IGVzdMOjbw== 57554 -IFdpbmRzb3I= 57555 -X2NoZWNrZWQ= 57556 -X2RldGVjdA== 57557 -YXR0ZW5k 57558 -IHhtaW4= 57559 -IGluZGlzcGVucw== 57560 -L3BlcnNvbg== 57561 -X0RFVEFJTFM= 57562 -UkVESVQ= 57563 -SGF5 57564 -YWJvbGlj 57565 -IGZ1bmN0b29scw== 57566 -aWFpcw== 57567 -RlRQ 57568 -X1JlY3Q= 57569 -IEluZHk= 57570 -LXB1YmxpYw== 57571 -b2hhbg== 57572 -X21hbmFnZQ== 57573 -Q29tcHV0ZWQ= 57574 -7JeQ7ISc 57575 -IFNsaWNl 57576 -IGdheXM= 57577 -IGFsZXg= 57578 -YWl0cw== 57579 -IHJlY2VpcHRz 57580 -U1BFQw== 57581 -IEJFRk9SRQ== 57582 -IFByZWZpeA== 57583 -X3Zpc2l0 57584 -IHNwdW4= 57585 -TEVURUQ= 57586 -IGRvdw== 57587 -IGxlZ2FsaXphdGlvbg== 57588 -YWJiYWdl 57589 -IGNsYXc= 57590 -IFRjbA== 57591 -eGltYQ== 57592 -IGNvdmVydA== 57593 -Tmk= 57594 -IHRoYW5rZWQ= 57595 -IGFsbGVyZ2lj 57596 -bG92ZXI= 57597 -IEJyZWFzdA== 57598 -LmlzQWN0aXZl 57599 -IGdlYmVu 57600 -VkVSU0U= 57601 -Wk9ORQ== 57602 -CVJlc3VsdA== 57603 -JykuJw== 57604 -IGdlZQ== 57605 -IFNlcmlvdXNseQ== 57606 -cHVycGxl 57607 -IEVzcGHDsWE= 57608 -aWZpZQ== 57609 -LXBhY2s= 57610 -UGFydGljbGVz 57611 -ICcvLi4v 57612 -IG11bHRpbWVkaWE= 57613 -YXV0b2NvbXBsZXRl 57614 -IFRIUkVBRA== 57615 -IHJlZmVyZW5jaW5n 57616 -cmVldGluZ3M= 57617 -IHF1b3Rpbmc= 57618 -IGFzc2lzdGFudHM= 57619 -amVuaXM= 57620 -aGFwcHk= 57621 -IGxheXM= 57622 -bGliZnQ= 57623 -eGRh 57624 -IGZvdQ== 57625 -cGlhcg== 57626 -UmVjb21tZW5kZWQ= 57627 -IEJpcmRz 57628 -IFdhcnJhbnR5 57629 -w7xybGljaA== 57630 -LklOVklTSUJMRQ== 57631 -X2FuY2hvcg== 57632 -4oCdOg== 57633 -RmFudA== 57634 -X2RlZnM= 57635 -IGRyZWFtZWQ= 57636 -IF9fX19fX18s 57637 -cGxh 57638 -w6RmdA== 57639 -b2RrYQ== 57640 -xLFz 57641 -IGRhZGR5 57642 -c2NoZW1hcw== 57643 -PXplcm9z 57644 -IHJhdHQ= 57645 -CQkgICAgCQ== 57646 -aWVq 57647 -IGRyaWxscw== 57648 -LTw/ 57649 -QUJB 57650 -Lmxpbmtz 57651 -IERlcGVuZGVuY3lQcm9wZXJ0eQ== 57652 -Lmxvdw== 57653 -aGVlZA== 57654 -X0JMQUNL 57655 -L0FkbWlu 57656 -IGFtaWdvcw== 57657 -aW5nZWQ= 57658 -IE1pY2tleQ== 57659 -LkdldEF4aXM= 57660 -IE5lZWRlZA== 57661 -IEVuY29kZQ== 57662 -w6lyaWV1cg== 57663 -IE1hbmlsYQ== 57664 -IENvbGxlZw== 57665 -YWRhc3Rybw== 57666 -IGNoaWNhcw== 57667 -5L2g 57668 -IG9uZXNlbGY= 57669 -eGVh 57670 -ZHVr 57671 -IGd3 57672 -dXJnaWNhbA== 57673 -IENlbnRybw== 57674 -IGFlcw== 57675 -ZmVlbA== 57676 -IHRyb3Q= 57677 -IGVsZWN0cm9ucw== 57678 -IHJpdHVhbHM= 57679 -IEJpbGRlcg== 57680 -IGRlY29yYXRl 57681 -IFRva2VuVHlwZQ== 57682 -IGx1cmU= 57683 -QXBpQ2xpZW50 57684 -Z3JwYw== 57685 -IE9yYw== 57686 -Q29udGV4dE1lbnU= 57687 -UFJFRklY 57688 -LXRoZW1lZA== 57689 -X2ZpZm8= 57690 -LklucHV0U3RyZWFtUmVhZGVy 57691 -X3NwZWNpZmlj 57692 -IERTUA== 57693 -PXN1YnByb2Nlc3M= 57694 -L3NoZQ== 57695 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo= 57696 -IGRhdW50aW5n 57697 -IGNsZWFycw== 57698 -IE1vdmVz 57699 -IG15c3Rlcmllcw== 57700 -LWJlc3Q= 57701 -IFZ1 57702 -b2xpYg== 57703 -IElzaA== 57704 -IGNhcmFjdA== 57705 -KExhYmVs 57706 -IERlYmlhbg== 57707 -IEV4cGVyaW1lbnRhbA== 57708 -IGNhdg== 57709 -LlRvRGVjaW1hbA== 57710 -IFJob2Rlcw== 57711 -IEhhd2tz 57712 -IGZvdW50YWlu 57713 -X1BFTkRJTkc= 57714 -X1NV 57715 -IHd4U3RyaW5n 57716 -IFBldw== 57717 -LmNsaQ== 57718 -0YTQvtGA0Lw= 57719 -LndlYmtpdA== 57720 -X0NO 57721 -IDs7PQ== 57722 -CW5hbWVzcGFjZQ== 57723 -IHdQYXJhbQ== 57724 -IHB1cHBpZXM= 57725 -IHRlcm1pbm9sb2d5 57726 -IGFkZGljdGVk 57727 -IGZvcmdl 57728 -IEdhcmRuZXI= 57729 -IHBlc3NvYQ== 57730 -CVJlc3VsdFNldA== 57731 -IGF0dGVudQ== 57732 -YW5nZW1lbnQ= 57733 -X2luZHM= 57734 -Q2hp 57735 -YXJpdGg= 57736 -RW5jb2RpbmdFeGNlcHRpb24= 57737 -bW91c2Vkb3du 57738 -IEJFVFdFRU4= 57739 -d2VpZ2g= 57740 -IkZvcg== 57741 -LmRk 57742 -aXRlbA== 57743 -WU8= 57744 -IERpY2U= 57745 -dW5peA== 57746 -IE9idA== 57747 -IENlZGFy 57748 -IHNwZWNpbWVucw== 57749 -cG9ybg== 57750 -IHVub2ZmaWNpYWw= 57751 -6buR 57752 -c29tZXRpbWVz 57753 -IEJ1bGxk 57754 -dHJ1c3Q= 57755 -Z2V0UmVzdWx0 57756 -IHNtb2tlcnM= 57757 -IHNhbmR3aWNoZXM= 57758 -IGV4aA== 57759 -IEZhZGU= 57760 -X0RD 57761 -IG1hc3R1cmJhdGlvbg== 57762 -Zm9ydGF3ZXNvbWU= 57763 -VEhJTkc= 57764 -X2FuZHJvaWQ= 57765 -IGRlZGlj 57766 -LXNlbnNpdGl2ZQ== 57767 -IG5hY2t0 57768 -TElCSU5U 57769 -IGFnb24= 57770 -IERJU0FCTEU= 57771 -b25lc2lh 57772 -Ymllcw== 57773 -IFpJUA== 57774 -IGhhdW50ZWQ= 57775 -IGN1aWQ= 57776 -L2NhcnQ= 57777 -a29z 57778 -CVJUTFU= 57779 -IGhpbmRlcg== 57780 -IGFkaXBpc2ljaW5n 57781 -SUVOQ0U= 57782 -LmJhbms= 57783 -IEN5cHJ1cw== 57784 -bWl4ZWQ= 57785 -LmN5 57786 -LXNpbmdsZQ== 57787 -PGxlbg== 57788 -Q29taW5n 57789 -IGZhdWx0cw== 57790 -IGZvcmVzZWU= 57791 -Z2V0bGluZQ== 57792 -ImE= 57793 -IGJyYWc= 57794 -IGRpc2Nz 57795 -IHJpcGU= 57796 -IG7DpnI= 57797 -IEdH 57798 -U0hPVA== 57799 -ZGVyYWJhZA== 57800 -KGVkaXQ= 57801 -VG9MZWZ0 57802 -W10pOwo= 57803 -IGRvR2V0 57804 -dmF0dXJl 57805 -TmVlZGVk 57806 -IENoZW5n 57807 -Y2Np 57808 -RUZJ 57809 -IGZldWQ= 57810 -IGx1bmFy 57811 -LlNoYXBl 57812 -Tm9ib2R5 57813 -X1RSSUdHRVI= 57814 -Q3k= 57815 -Z3JvdW5kQ29sb3I= 57816 -IFJlbW92YWw= 57817 -KGJvdHRvbQ== 57818 -JG1zZw== 57819 -U0NJSQ== 57820 -cml0eg== 57821 -IGZyZW50ZQ== 57822 -IGNvbXBvc3Q= 57823 -YW5zd2VyZWQ= 57824 -IFJvZHI= 57825 -X0hUTUw= 57826 -IHNpbGhvdWV0dGU= 57827 -IFFVRVNU 57828 -IENhdGhlZHJhbA== 57829 -LkNvbW1lbnQ= 57830 -IE1u 57831 -LW5ldHdvcms= 57832 -LmdldEZpbGU= 57833 -LmdlbmVyYXRvcg== 57834 -IENoZWNrb3V0 57835 -X3pvb20= 57836 -IGVuY29kZVVSSUNvbXBvbmVudA== 57837 -X1RD 57838 -c29t 57839 -IFNlcmll 57840 -IGJhc2VVUkw= 57841 -CXJ1bg== 57842 -IGh1aA== 57843 -LnNlbGVjdGVkSW5kZXg= 57844 -IFNUQVI= 57845 -fi1+LQ== 57846 -YWJjZGVmZ2g= 57847 -Lm1hcHBpbmc= 57848 -PWRhdGV0aW1l 57849 -Q29vbA== 57850 -bmlt 57851 -IERpcmVjdGl2ZQ== 57852 -RmVkZXJhbA== 57853 -IG1lbnVJdGVt 57854 -INCQ 57855 -QW5uYQ== 57856 -IFJlY3JlYXRpb24= 57857 -cnlhbg== 57858 -LWFnZWQ= 57859 -emVyYmFp 57860 -4oCm4oCdCgo= 57861 -Y2FtcG8= 57862 -IG1pbmlhdHVyZQ== 57863 -ZGV0YWNo 57864 -bWVhbmluZw== 57865 -X2VtcA== 57866 -UGVhaw== 57867 -IGJjbQ== 57868 -IEh1bmdhcmlhbg== 57869 -IENhc2NhZGU= 57870 -IHNhY2tz 57871 -IHRydW5jYXRl 57872 -IOKWiOKWiA== 57873 -IHdoYWxlcw== 57874 -IHNvcnRhYmxl 57875 -IGFzc2VydHM= 57876 -IHNlYWxz 57877 -b2N5dGVz 57878 -XSkpKQo= 57879 -YWxhcm0= 57880 -cmVzc2luZw== 57881 -KHNpZ25hbA== 57882 -IGVtcGVyb3I= 57883 -CU9O 57884 -Y29tbWl0dGVl 57885 -IHRyaWxvZ3k= 57886 -LlRyYW5zYWN0aW9uYWw= 57887 -R3Jvdw== 57888 -X3VhcnQ= 57889 -IHN3aW5ncw== 57890 -IHNwZWN0YWNsZQ== 57891 -4oCZYXY= 57892 -IFNlbnRpbmVs 57893 -INmE 57894 -IFRvdQ== 57895 -IHdpZG93 57896 -Z2VyYWxk 57897 -LHVpbnQ= 57898 -IHVudXN1YWxseQ== 57899 -PENhcmQ= 57900 -IFJlc3RhcnQ= 57901 -bW9y 57902 -44GC44KK 57903 -aXhlZFJlYWxpdHk= 57904 -IGhhbmRndW4= 57905 -4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA 57906 -IGxpdGhpdW0= 57907 -UmVzb2x2ZQ== 57908 -Z2V0Qnl0ZXM= 57909 -L2Z1bmN0aW9ucw== 57910 -IHRhY2tsaW5n 57911 -T3V0bGluZWQ= 57912 -IH08Lw== 57913 -IFNleG8= 57914 -IEFuaw== 57915 -IHJhdGlvbmFsZQ== 57916 -cmVtb3ZlQXR0cg== 57917 -IG11bmljaXBhbGl0eQ== 57918 -IGFzc2F1bHRz 57919 -Q0hPT0w= 57920 -IFJlZQ== 57921 -IGJhdWQ= 57922 -pqw= 57923 -IGVuaGFuY2Vz 57924 -INC/0YDQtdC0 57925 -IGNvbmNlc3M= 57926 -Lmluc3RhZ3JhbQ== 57927 -LmdldFJlc3BvbnNl 57928 -c2VnbWVudHM= 57929 -IHdlbGxiZWluZw== 57930 -fTsKCgoK 57931 -aHVuZw== 57932 -44OG 57933 -IHJlbm92YXRlZA== 57934 -LmV4cGVjdGVk 57935 -IHJhZGlhbA== 57936 -IGNvbW11bmFs 57937 -dXNlck1hbmFnZXI= 57938 -K2E= 57939 -IGZ1bmRhbWVudGFscw== 57940 -LlRI 57941 -6II= 57942 -IHJhbnQ= 57943 -IFN0cmF3 57944 -IE9sZURi 57945 -YXppbw== 57946 -IGhhbWJ1cmc= 57947 -IHBhaW50cw== 57948 -IHRodW1icw== 57949 -IE51bGxQb2ludGVyRXhjZXB0aW9u 57950 -IGdyb3VwZQ== 57951 -IEhvbWVDb21wb25lbnQ= 57952 -IGJhbGxv 57953 -IElOSVRJQUw= 57954 -X2FyZQ== 57955 -IFBlcw== 57956 -dXJzZXM= 57957 -IGJhcmR6bw== 57958 -LmdldExlbmd0aA== 57959 -YW1vdG8= 57960 -Lm5vdGlmeURhdGFTZXRDaGFuZ2Vk 57961 -aWVuZXM= 57962 -ZW56aWU= 57963 -X2VtYg== 57964 -dW1uaQ== 57965 -c21vb3Ro 57966 -IERybw== 57967 -cGFzdGU= 57968 -IE5hcnI= 57969 -LS0tLQoK 57970 -z4k= 57971 -IEF1dG9y 57972 -IG91dHJvcw== 57973 -IExBQkVM 57974 -LnBh 57975 -LlN0dWRlbnQ= 57976 -KFhtbA== 57977 -IGV0aG5pY2l0eQ== 57978 -IEl2eQ== 57979 -44KI 57980 -X2Zha2U= 57981 -Pyg6 57982 -dXBsb2FkZWQ= 57983 -Z2V0TWFuYWdlcg== 57984 -LVFhZWRh 57985 -b2RpYWM= 57986 -Q29ubm9y 57987 -aWhhbg== 57988 -TUFU 57989 -KG1pZA== 57990 -IEFsYmFu 57991 -IHNvaXI= 57992 -Q29tYm8= 57993 -IFB1YmxpY2F0aW9u 57994 -b3BvdWxvcw== 57995 -cGlz 57996 -IHRlbXBsZXM= 57997 -b25neWFuZw== 57998 -X2NsaWVudHM= 57999 -IHJvZHM= 58000 -IHhj 58001 -aWprZW4= 58002 -IHJlYXA= 58003 -IOS4i+WNiA== 58004 -CWNvbm5lY3Q= 58005 -Rm9jdXNlZA== 58006 -LGNvdW50 58007 -aWV0ZXQ= 58008 -IGhhY2lh 58009 -X2FsbG9jYXRvcg== 58010 -IHRveGljaXR5 58011 -KHNlcXVlbmNl 58012 -IG51ZXN0cm9z 58013 -IFByaW5jaXBsZXM= 58014 -IGxsZQ== 58015 -YWxhcmlh 58016 -LndyaXRlU3RyaW5n 58017 -IEFGTA== 58018 -aWZuZGVm 58019 -IERvcw== 58020 -xZtjaWU= 58021 -IEFnZ3JlZ2F0ZQ== 58022 -IHNhY3JpZmljZXM= 58023 -X29mZnNldHM= 58024 -bGRi 58025 -IGxhdGNo 58026 -IGZ1bGxzY3JlZW4= 58027 -bWlzc2l2ZQ== 58028 -T1BUSU9OUw== 58029 -IFRlbGVwaG9uZQ== 58030 -IGFyc2VuYWw= 58031 -amVqZXI= 58032 -IEhvc3A= 58033 -IGZhdm91cml0ZXM= 58034 -cml2ZQ== 58035 -LmluY3JlbWVudA== 58036 -IGJ2 58037 -IEZhbnRhc3RpYw== 58038 -LnNheQ== 58039 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 58040 -IG1lZGljaW5hbA== 58041 -IERST1A= 58042 -IHBpdHk= 58043 -bWV0aXM= 58044 -IHdvbGxlbg== 58045 -IGJlZg== 58046 -X0Js 58047 -ID4+Cgo= 58048 -Ym93ZXI= 58049 -IHN3YXBwZWQ= 58050 -L2luc3RhbGw= 58051 -IHNpbmtz 58052 -ZXRyaXpl 58053 -IGRlY2xpbmVz 58054 -CW15c3Fs 58055 -IENTdHJpbmc= 58056 -IE1vdGlvbkV2ZW50 58057 -Lkxhbmd1YWdl 58058 -Um9hZA== 58059 -0YLQtdGA 58060 -YXNjaW1lbnRv 58061 -JykpLT4= 58062 -LmFib3V0 58063 -KGVkaXRvcg== 58064 -IFJhdGluZ3M= 58065 -aW5jb21l 58066 -xaFl 58067 -LmRlcXVldWVSZXVzYWJsZUNlbGw= 58068 -IEF1c3RyaWFu 58069 -IHN1bGxh 58070 -IFRyaWJ1bmFs 58071 -IERpZG4= 58072 -0L7QstCw0YA= 58073 -IGluc3BlY3Rpb25z 58074 -Qm9zcw== 58075 -IGNvY2t0YWlscw== 58076 -IGFwb2xvZ2l6ZWQ= 58077 -X3N1YnBsb3Q= 58078 -b3BhbA== 58079 -Kz0o 58080 -IHJlc29uYW5jZQ== 58081 -aWJ1 58082 -IOumrA== 58083 -cm9tYQ== 58084 -cmVzZXJ2ZQ== 58085 -cGxz 58086 -IFRhaA== 58087 -YXhpZXM= 58088 -T1BMRQ== 58089 -IERhcnJlbg== 58090 -IFpvbWJpZQ== 58091 -X01hcA== 58092 -IF0pCgo= 58093 -IFFp 58094 -IFNhaWw= 58095 -IHJlc3RyaWN0aXZl 58096 -IGVyb3Npb24= 58097 -LXBhcg== 58098 -V0hJVEU= 58099 -IG9sZHU= 58100 -IGFwZXJ0dXJl 58101 -IGJpdGNvaW5z 58102 -dGV4dG8= 58103 -IENvbWNhc3Q= 58104 -IHRpbWVsZXNz 58105 -ZW5raW5z 58106 -IGZlZWRlcg== 58107 -L3RtcA== 58108 -cmVzZGVu 58109 -Kydf 58110 -LkRlc3Ryb3k= 58111 -IMOnb2s= 58112 -IERPQ1VNRU5U 58113 -LmxuZw== 58114 -LnRhZ05hbWU= 58115 -IGt1bGxhbg== 58116 -ZWdyYXRl 58117 -ICgqLg== 58118 -57yW6L6R 58119 -IGhhbmRzaGFrZQ== 58120 -c29j 58121 -X2dlb21ldHJ5 58122 -IERhbWFzY3Vz 58123 -TWlub3I= 58124 -IEthZmth 58125 -7Jes 58126 -RmxvcmlkYQ== 58127 -X2NvbXB1dGU= 58128 -LmV4cHI= 58129 -IHBhcmFsbGU= 58130 -IERpYXo= 58131 -Y2ly 58132 -W3RhcmdldA== 58133 -IGpva2luZw== 58134 -IGdsb3I= 58135 -KHNldHE= 58136 -X2hhbmRsZXJz 58137 -SGFuZw== 58138 -IGZlcnI= 58139 -cmltaW5hbA== 58140 -CSAgICAJCQ== 58141 -ZW50aWVz 58142 -ZGVmaW5lcw== 58143 -LXRheA== 58144 -anNvbnA= 58145 -IFVQUw== 58146 -bWV0cm8= 58147 -X187Cg== 58148 -IFVnYW5kYQ== 58149 -XSkpOgo= 58150 -X3Rk 58151 -eGFl 58152 -bHc= 58153 -Lk9T 58154 -IExvZ2dlZA== 58155 -YWNpZA== 58156 -IE1heW8= 58157 -YXNwZWN0 58158 -IHZhZ2luYWw= 58159 -IGluaXRpYWxpemluZw== 58160 -IHN0ZXJvaWRz 58161 -ZmljdGlvbg== 58162 -R1JF 58163 -Z2VuZA== 58164 -IGxpYWJpbGl0aWVz 58165 -IExldHM= 58166 -TWVjaA== 58167 -KG5j 58168 -KGNoYW5nZQ== 58169 -IGNvbm5lY3RvcnM= 58170 -Oms= 58171 -IHRhc3Q= 58172 -ISIpOwoK 58173 -dGhpbmdz 58174 -cm9waHk= 58175 -bHVldG9vdGg= 58176 -IFNpZ25VcA== 58177 -LmN0cmw= 58178 -IHRoZXJlaW4= 58179 -b3JkYQ== 58180 -LmVzY2FwZQ== 58181 -aWdhdG9y 58182 -IHBldHJvbA== 58183 -IHNwZWNpbWVu 58184 -IGRlYnV0ZWQ= 58185 -LVBybw== 58186 -IGNyaXNlcw== 58187 -LmFkZFZpZXc= 58188 -64+Z 58189 -LWRvb3I= 58190 -IG1vbmV0 58191 -IG1pbGxpcw== 58192 -IHZpZXI= 58193 -SW50ZXJuYWxFbnVtZXJhdG9y 58194 -IGFkbWlucw== 58195 -IExhaXI= 58196 -emlu 58197 -Z2V0UXVlcnk= 58198 -dW1ibGVz 58199 -TElNSVQ= 58200 -IFZpZw== 58201 -X3Nvbmc= 58202 -PENoYXJhY3Rlcg== 58203 -Ojou 58204 -X2hvbQ== 58205 -X2Jw 58206 -IFN1cGVydmlzb3I= 58207 -c3VibWlzc2lvbg== 58208 -YWJpbGU= 58209 -IG5vaQ== 58210 -T3JDcmVhdGU= 58211 -IHBlZWw= 58212 -IG9uU3RhcnQ= 58213 -IHNlbnRpbWVudHM= 58214 -dmVoaWNsZXM= 58215 -IGNsYXNzcm9vbXM= 58216 -IHN6ZXI= 58217 -IGJlbmRpbmc= 58218 -IGxvbmdldml0eQ== 58219 -IGFjbA== 58220 -IEFsZXBwbw== 58221 -IFVN 58222 -IFJpY2h0 58223 -IG11bHRpcHJvY2Vzc2luZw== 58224 -RE9NQUlO 58225 -IiwiKw== 58226 -X1lFQVI= 58227 -IHNjcmFwZQ== 58228 -IHNvbGl0YXJ5 58229 -ICJdIjsK 58230 -L2Vycm9ycw== 58231 -7J6s 58232 -nOugpQ== 58233 -YmV0dGVy 58234 -CW51bWJlcg== 58235 -IExG 58236 -IEFjcm9zcw== 58237 -UHViTWVk 58238 -XCIi 58239 -IEV4Y2VsbGVuY2U= 58240 -IHVzYW5kbw== 58241 -IFVJUA== 58242 -QWN0aXZpdHlJbmRpY2F0b3I= 58243 -X1ZPSUQ= 58244 -IGJyZWVkcw== 58245 -772l 58246 -dWVzdGFz 58247 -IFRyZWFzdXJl 58248 -dXN0cmFsaWFu 58249 -KGZhY2U= 58250 -IFRlbm5pcw== 58251 -CUludA== 58252 -IEhhbnNlbg== 58253 -57U= 58254 -Okk= 58255 -IOKclA== 58256 -R1JBWQ== 58257 -T1VTRQ== 58258 -IGhlcGF0 58259 -oO0= 58260 -QUlS 58261 -w7PFvA== 58262 -IHF1ZXVlZA== 58263 -dmluY2lh 58264 -IENocm9taXVt 58265 -IGNvbXBldGVuY2U= 58266 -dW5nYWw= 58267 -aWxsaQ== 58268 -IGdldEJ5 58269 -IEZpbmRlcg== 58270 -IGluY2FwYWJsZQ== 58271 -IHNhZGQ= 58272 -IGNpdGVz 58273 -IENodXJjaGlsbA== 58274 -U2Rr 58275 -TW9yZW92ZXI= 58276 -QXNwTmV0 58277 -KEZsb2F0 58278 -JHBhc3N3b3Jk 58279 -IENvbm5vcg== 58280 -LXNlc3Npb24= 58281 -X2Rt 58282 -Kikp 58283 -IGRldXRzY2g= 58284 -IE5Y 58285 -IHBlcmtz 58286 -X1NPUlQ= 58287 -X1RPT0w= 58288 -X1ZJU0lCTEU= 58289 -LmFzcA== 58290 -5oiW 58291 -IEJyZWF0aA== 58292 -RGV0ZWN0 58293 -IER1ZWw= 58294 -LmNtYg== 58295 -W2l0 58296 -LlNldEJvb2w= 58297 -IG5hcmNpc3M= 58298 -IGFiaWRl 58299 -IGVqZW1wbG8= 58300 -IOKElQ== 58301 -IG1vcm5pbmdz 58302 -IGNvbXB1dGVz 58303 -LnNzbA== 58304 -anQ= 58305 -IG11Y2hvcw== 58306 -X1NT 58307 -W2VuZA== 58308 -IGJhc2lu 58309 -IGFsZ3Vub3M= 58310 -IENyb2F0aWE= 58311 -bGluZXdpZHRo 58312 -KHRhZ3M= 58313 -KGhpZGRlbg== 58314 -w61jaW8= 58315 -IGFwYXI= 58316 -INC2 58317 -5LiO 58318 -LmZvb2Q= 58319 -IFJ1cmFs 58320 -IGJyZWFkdGg= 58321 -5b2x 58322 -KHNlc3M= 58323 -KyIp 58324 -IFBhc3Rl 58325 -IHNlcnZpZG9y 58326 -IEJpdFNldA== 58327 -IFRyYW4= 58328 -bGF1cw== 58329 -dmV0dGU= 58330 -ZXllcw== 58331 -IENMSUNL 58332 -IFZJSUk= 58333 -IFR1cm5z 58334 -IExlQnJvbg== 58335 -IE11ag== 58336 -IERlZw== 58337 -IEFkdWx0cw== 58338 -X3N1aXRl 58339 -cHJvY2Vzc2FibGU= 58340 -IFBIWQ== 58341 -Z2hlc3Q= 58342 -LkZhaWw= 58343 -IFNsYWNr 58344 -Y2Vq 58345 -XENhcmJvbg== 58346 -IHN1cGVyc3Rhcg== 58347 -IGhvbGRpbmdz 58348 -KGZvcm1z 58349 -ICcjJw== 58350 -TXVsdGlw 58351 -KCJbJQ== 58352 -LXNvbGlk 58353 -L3VybA== 58354 -LXRpZXI= 58355 -W2xlbmd0aA== 58356 -IFN0cmVhbVdyaXRlcg== 58357 -IE1hcmtldHBsYWNl 58358 -Z2V0dGV4dA== 58359 -X1RJQ0s= 58360 -IEZvcmdl 58361 -IGJsYWNramFjaw== 58362 -IERPRVM= 58363 -IE1hdHRlcnM= 58364 -d2F2ZXM= 58365 -IHdoaXNwZXJlZA== 58366 -IGx1c2g= 58367 -7Jik 58368 -ZGlnaXRhbA== 58369 -IHdyaW5r 58370 -IEhvZ2Fu 58371 -IHJ1c3RpYw== 58372 -LkFwcGx5UmVzb3VyY2Vz 58373 -IEhhcmR5 58374 -b3NvbWVz 58375 -QVVU 58376 -LlNUQVRF 58377 -IG5hcnJhdGl2ZXM= 58378 -CXN0b3Jl 58379 -Ymli 58380 -CVNjYW5uZXI= 58381 -IENvZHk= 58382 -XFJlcG9zaXRvcmllcw== 58383 -IHJldW5pb24= 58384 -YW5kdW0= 58385 -4oCZaA== 58386 -IHNuaWZm 58387 -TlNCdW5kbGU= 58388 -IGNvbXByZWhlbmQ= 58389 -X1VTQUdF 58390 -X29jYw== 58391 -VVJSRU5DWQ== 58392 -Sk5J 58393 -IHNwZWNpYWxpemluZw== 58394 -IHZpc2lvbnM= 58395 -IGRvbG9yZQ== 58396 -IHbDoQ== 58397 -IENoZXZ5 58398 -IFN0eWxlZA== 58399 -aW1wYWN0 58400 -YWxsZW4= 58401 -IGthcnQ= 58402 -IFRhYmxldA== 58403 -c3R1ZmY= 58404 -cmVlc29tZQ== 58405 -0LDRgtC+0YA= 58406 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0K 58407 -X0FkbWlu 58408 -IGNlbGxwaG9uZQ== 58409 -IGF1dG9wbGF5 58410 -IGNhbWJpbw== 58411 -IG1hcml0aW1l 58412 -X0JPT1Q= 58413 -LXF1YXJ0ZXI= 58414 -IGxhdGluYQ== 58415 -IEFKQVg= 58416 -ZXF1aXY= 58417 -IEZyb250aWVy 58418 -IFhZ 58419 -fV0K 58420 -IFJvdWdo 58421 -LnByb3Rv 58422 -IGNvcnJlY3RuZXNz 58423 -IGZhY2ls 58424 -IFJlYWNoZWQ= 58425 -44Gd44Gu 58426 -VklT 58427 -LnBz 58428 -IHN0cm5jcHk= 58429 -IGRpZmZ1c2lvbg== 58430 -LnN0YXJ0QWN0aXZpdHk= 58431 -77+977+977+9 58432 -IGFjY29tcA== 58433 -QU1FU1BBQ0U= 58434 -aW1vbmlhbHM= 58435 -IEJsYXN0 58436 -YWJ5cmlu 58437 -IGRvbWU= 58438 -IGV4dHJhdg== 58439 -IHllbg== 58440 -IGN1bGluYXJ5 58441 -UFJJ 58442 -IENvbW11bml0aWVz 58443 -bmlk 58444 -X29wZXJhdGlvbnM= 58445 -Lmhz 58446 -IE1pbHRvbg== 58447 -IG5vaXNlcw== 58448 -QXV0b3Jlc2l6aW5nTWFzaw== 58449 -KGNpZA== 58450 -fQoKCgoKCg== 58451 -XX0sCg== 58452 -IERldGVjdGlvbg== 58453 -dGFibGE= 58454 -IGxpYmVydGllcw== 58455 -X0RZTkFNSUM= 58456 -d2dldA== 58457 -IFTDvHI= 58458 -IFBhc2NhbA== 58459 -VHJhbnNwYXJlbnQ= 58460 -RGVsYXllZA== 58461 -XSgp 58462 -IEhlcmJlcnQ= 58463 -PEFjdGlvblJlc3VsdA== 58464 -Y2hhbGxlbmdl 58465 -IG11c2hyb29t 58466 -Lmluc2VydEJlZm9yZQ== 58467 -IFJpbg== 58468 -IGh1bW91cg== 58469 -IGbDuA== 58470 -YXBpS2V5 58471 -YWxsb2NhdGVk 58472 -IGNvbmZlc3Npb24= 58473 -LiIsDQo= 58474 -CWFzc2VydFRoYXQ= 58475 -IFNPUlQ= 58476 -IExPUkQ= 58477 -IGV4cG9ydGVy 58478 -LnNldExldmVs 58479 -cG9rZW1vbg== 58480 -YXNodHJh 58481 -IGbDqQ== 58482 -dXJhdG9y 58483 -KE1TRw== 58484 -IHR1cA== 58485 -IEh1bGw= 58486 -IHlpZWxkZWQ= 58487 -LlN1YmplY3Q= 58488 -XFJvdXRl 58489 -IT8= 58490 -INGD0LTQsNC7 58491 -XFNlY3VyaXR5 58492 -LWFy 58493 -IGFsbGVnYXRpb24= 58494 -KFNldHRpbmdz 58495 -w6RuZGVy 58496 -IGVsbGlwc2U= 58497 -IFJldHJvZml0 58498 -IHJlZ3VsYXRpbmc= 58499 -IE1vbGx5 58500 -IExvaw== 58501 -X0N1c3RvbQ== 58502 -IFByb21v 58503 -aXNpbg== 58504 -IHJlc3VtZWQ= 58505 -IG1ldHJvcG9saXRhbg== 58506 -LmVycm9yTWVzc2FnZQ== 58507 -Oi0tLS0tLS0tLS0tLS08Lw== 58508 -Lm1s 58509 -c2NvcGlj 58510 -LnJlZnM= 58511 -YXB0b3Jz 58512 -IEluc3RydW1lbnRz 58513 -IHByb3BhZ2F0ZQ== 58514 -fS0+ 58515 -IHBhc2Fkbw== 58516 -dGhhbms= 58517 -X0RlbGV0ZQ== 58518 -IEJyaWdodG9u 58519 -LHVuc2lnbmVk 58520 -5L2c6ICF 58521 -IGFzcGlyYXRpb25z 58522 -LWhvdw== 58523 -Um9zZQ== 58524 -PSgo 58525 -X25lZWRlZA== 58526 -X3BsdXJhbA== 58527 -PEFwcGxpY2F0aW9u 58528 -IFdFRUs= 58529 -IFVubG9jaw== 58530 -IFRFTVA= 58531 -U291 58532 -IHNjaGl6b3BocmVuaWE= 58533 -IHRyb2xs 58534 -IGNvbXBsZW1lbnRhcnk= 58535 -IE5FVFdPUks= 58536 -IGJsaXI= 58537 -IHByb2dyZXNzRGlhbG9n 58538 -IiUo 58539 -IEF0dHJpYnV0ZVNldA== 58540 -CXRz 58541 -Lml0ZXJpdGVtcw== 58542 -6K+d 58543 -IGVzY3JpdA== 58544 -dm91cw== 58545 -X3BsYWNlcw== 58546 -SEs= 58547 -IHNlZ3Vpcg== 58548 -X2Z3 58549 -IFJvdW5kZWQ= 58550 -IGRpc3Bvc2l0 58551 -6KeG 58552 -cGFybQ== 58553 -d293 58554 -U1RSVUNUSU9O 58555 -LmFsbG93 58556 -IENoYXJTZXF1ZW5jZQ== 58557 -CWV4dGVybg== 58558 -IHByb3NlY3V0ZWQ= 58559 -IG1vcnRhcg== 58560 -IEp1ZGE= 58561 -LW1zZw== 58562 -IGVzdHVk 58563 -LmdldERlc2NyaXB0aW9u 58564 -IHNvdw== 58565 -YW1icmU= 58566 -IHJvbWE= 58567 -RW5o 58568 -Ym9udXM= 58569 -IHNxdWF0 58570 -IGRpc3RyYQ== 58571 -ZWRJbWFnZQ== 58572 -IHBlcHBlcnM= 58573 -LXBlcmZvcm1hbmNl 58574 -LAoKCg== 58575 -LGZpbGU= 58576 -IE1JTUU= 58577 -X2NvbmNhdA== 58578 -QUJT 58579 -LWZhc2hpb24= 58580 -IHVuZGVyY292ZXI= 58581 -T25lVG9NYW55 58582 -IHJlY2xhaW0= 58583 -Q09QWQ== 58584 -IGJpbmRz 58585 -IFRhcGU= 58586 -IGdvc3NpcA== 58587 -IEVxdWl0eQ== 58588 -L0NhcmQ= 58589 -LmFjdGl2 58590 -J2Ft 58591 -IGRyYWluYWdl 58592 -PFNjYWxhcnM= 58593 -IG9uQmluZFZpZXdIb2xkZXI= 58594 -KCk/Lg== 58595 -IHNvcnJvdw== 58596 -IEli 58597 -dXB5 58598 -X1VVSUQ= 58599 -IENoYXJt 58600 -IEVsZWN0aW9ucw== 58601 -Lm9uRGVzdHJveQ== 58602 -IEludGVyZXN0aW5nbHk= 58603 -b3VuZGluZ0JveA== 58604 -X2RldGVjdGlvbg== 58605 -LWhlbGQ= 58606 -X3Vua25vd24= 58607 -IHJlZnJhaW4= 58608 -IG3DqXRvZG8= 58609 -IGVCb29r 58610 -RU5PTUVN 58611 -IGRhbmc= 58612 -UHJvZmVzc2lvbmFs 58613 -IGRpY3Rpb25hcmllcw== 58614 -L215c3Fs 58615 -IFNUVUQ= 58616 -IG1hc3Nl 58617 -c2NhcGU= 58618 -IGRyZWk= 58619 -Om5hbWU= 58620 -LmxvZ28= 58621 -U2lnblVw 58622 -IHRhaHVu 58623 -KHRoZW1l 58624 -IEZlbW1l 58625 -IGJvbWJlcg== 58626 -IEphZGU= 58627 -IFRheQ== 58628 -IHN1Ym1hcmluZQ== 58629 -X2NsYXVzZQ== 58630 -enljaA== 58631 -IHNpbXVsdGFuZW91cw== 58632 -IGNhc29z 58633 -LmJvb2xlYW4= 58634 -KGxocw== 58635 -IGNvbnRpbmVudGFs 58636 -LXNhbGU= 58637 -CWVudg== 58638 -IEN1dGU= 58639 -IEZhY3RvcnlHaXJs 58640 -YWJ1cw== 58641 -L3ZhbHVl 58642 -IGphZHg= 58643 -IHN0ZXJu 58644 -Pj4KCg== 58645 -IHN1cmZhY2Vk 58646 -IOyggOyepQ== 58647 -cGxhdHo= 58648 -CWVtYWls 58649 -Y2VwdG9ycw== 58650 -Ij4o 58651 -IGVwaWxl 58652 -6K+7 58653 -IERlYnQ= 58654 -5ZGK 58655 -Tk9Q 58656 -Imh0dHBz 58657 -Omo= 58658 -Rm9ybUl0ZW0= 58659 -X0xJQ0VOU0U= 58660 -LmdldERvdWJsZQ== 58661 -IEFnZW5kYQ== 58662 -CWZpbmFsbHk= 58663 -KGZpbHRlcnM= 58664 -KGF2 58665 -576O 58666 -QVBFUg== 58667 -IGxhdmE= 58668 -0LXRgNC2 58669 -KSkpKQoK 58670 -IGZhdWx0eQ== 58671 -X25t 58672 -IHRyYXZh 58673 -KEJpdG1hcA== 58674 -IHNwZWVkaW5n 58675 -PicpLg== 58676 -IHNjcmVlbmVk 58677 -X3JvbGw= 58678 -IE1hY0Jvb2s= 58679 -IEFVRA== 58680 -IGRpYWdub3Nl 58681 -LkdlbmVyYXRl 58682 -IF5e 58683 -IHN0cnM= 58684 -W1Rlc3Q= 58685 -IHJhbnNvbQ== 58686 -IERIQ1A= 58687 -ZWxkZW4= 58688 -IGludGVycHJldGF0aW9ucw== 58689 -KCldLg== 58690 -ZmxhdE1hcA== 58691 -IGxpbmVIZWlnaHQ= 58692 -X21vdW50 58693 -IFdpemFyZHM= 58694 -IHNsdXRz 58695 -ZWhsZXI= 58696 -b2RhbA== 58697 -IG1pbGl0aWE= 58698 -5bI= 58699 -ZWFybmVk 58700 -IG1pc2VyeQ== 58701 -aW50dmFs 58702 -ZnVuZA== 58703 -IGhpZGVz 58704 -IGRpYXJy 58705 -IFdlc2xleQ== 58706 -IHhtbQ== 58707 -IHF1ZW0= 58708 -IEFyYWJz 58709 -aWZ0aA== 58710 -YXRlZ29yaXplZA== 58711 -RGlzcG9zYWJsZQ== 58712 -UHVyZQ== 58713 -X05PVElGWQ== 58714 -c25pcHBldA== 58715 -IEdhcnJldHQ= 58716 -LnJ1bm5pbmc= 58717 -LndlaWdodHM= 58718 -ICgtLQ== 58719 -IGludmFyaWFudA== 58720 -5LqL5Lu2 58721 -IEFsbG93ZWQ= 58722 -ZGlycw== 58723 -IHBhc3Npb25z 58724 -IGxhZA== 58725 -IEZsdXNo 58726 -bWVudXM= 58727 -OmJsb2Nr 58728 -IGNvbXByYQ== 58729 -LmNob21w 58730 -YWxsb2NhdG9y 58731 -IGN1cmF0ZWQ= 58732 -IEtub3dpbmc= 58733 -IFBhdHRlcnNvbg== 58734 -IHRlbGFo 58735 -J2V4 58736 -IGRvb21lZA== 58737 -IHBoaWxhbnRo 58738 -b3R0eQ== 58739 -LnN0eWxlcw== 58740 -T3duZWQ= 58741 -IGFsbGVyZ2llcw== 58742 -PXBhcmFtcw== 58743 -b2Nlc2U= 58744 -aXRlbGlzdA== 58745 -IFNlbmRpbmc= 58746 -YmVm 58747 -b3JyYXI= 58748 -IE7Do28= 58749 -IEZhcmdv 58750 -IEx1Yg== 58751 -IENvbWJpbmVk 58752 -X2dpdmVu 58753 -CQkJCQkgICAg 58754 -IHJlY29uY2lsaWF0aW9u 58755 -UGF0dGVybnM= 58756 -YXphcmQ= 58757 -IGJpb21hc3M= 58758 -IEhvdXNlcw== 58759 -cmVzcHVlc3Rh 58760 -Y2Nv 58761 -L3RvcGljcw== 58762 -IFl1aw== 58763 -IHdlYWtlbmVk 58764 -X2NhbGVuZGFy 58765 -IG11bGhlcmVz 58766 -IE1hcmw= 58767 -IHNpbmU= 58768 -IFRpbA== 58769 -IFNvdWxz 58770 -IERldXRzY2hl 58771 -IEZPTExPVw== 58772 -IHBpcGVsaW5lcw== 58773 -IEJldmVybHk= 58774 -X0RJUFNFVFRJTkc= 58775 -IiM= 58776 -IFByb3Rv 58777 -LmJpZw== 58778 -IFNhdmluZ3M= 58779 -IFRhbno= 58780 -anVu 58781 -IEdhbW1h 58782 -IFNhZGQ= 58783 -IGFkdmlzb3Jz 58784 -IHJvYXN0 58785 -IHVudGVycw== 58786 -dWRpZXM= 58787 -X2xvbg== 58788 -LXBvaW50ZXI= 58789 -IEVsZW1lbnRSZWY= 58790 -XEJ1aWxkZXI= 58791 -ZXhhbXBsZUlucHV0 58792 -LndlYmRyaXZlcg== 58793 -ZGF0YVR5cGU= 58794 -IFF1aXRl 58795 -IENlbHRpY3M= 58796 -dWls 58797 -LWRlZmVuc2U= 58798 -YmlzaA== 58799 -IFVJV2luZG93 58800 -IFN1ZGRlbmx5 58801 -LmhvdA== 58802 -LnJlYXNvbg== 58803 -IGfDtnI= 58804 -QU1E 58805 -Lk11bHRp 58806 -YXV0aGVudGljYXRlZA== 58807 -cmVnaW9ucw== 58808 -Oyg= 58809 -0LDRgNCw0Lw= 58810 -IEtpcmJ5 58811 -JHJvdXRl 58812 -UFJFQ0FURUQ= 58813 -IER1cmhhbQ== 58814 -b3dv 58815 -IFBlcmZvcm1z 58816 -IGRpc3JlZ2FyZA== 58817 -bnN0 58818 -IFBvbHM= 58819 -IGdldFA= 58820 -Il06 58821 -LWNvbG9yZWQ= 58822 -KEtleXM= 58823 -IEFsbGVn 58824 -X21vZGlmeQ== 58825 -X2xvYWRpbmc= 58826 -c3RyYWluZWQ= 58827 -IGF0cm9j 58828 -X3Bocg== 58829 -PFNwcml0ZQ== 58830 -IHNhdGlzZmFjdG9yeQ== 58831 -bWFuc2hpcA== 58832 -LnBpcGVsaW5l 58833 -VG9ueQ== 58834 -IHRoaWVm 58835 -cG9sYXRvcg== 58836 -KGxvY2s= 58837 -YnVyc3Q= 58838 -IE9wdGltaXphdGlvbg== 58839 -IHN1cmZpbmc= 58840 -Illlcw== 58841 -IGRlc2NlbmRlZA== 58842 -5pI= 58843 -X0NsZWFy 58844 -IGNyaWVz 58845 -IEZyb3plbg== 58846 -RElSRUNU 58847 -LUNvbg== 58848 -IExlaWNlc3Rlcg== 58849 -5aWz 58850 -T09N 58851 -PWRi 58852 -IGdldE1lc3NhZ2U= 58853 -PFN0dWRlbnQ= 58854 -X2JhdGNoZXM= 58855 -Lk1hc2s= 58856 -X2V0aA== 58857 -XCk= 58858 -IHNvbWE= 58859 -Q2F0Y2g= 58860 -W2No 58861 -T3duZXJz 58862 -aW5kbGU= 58863 -OmF1dG8= 58864 -LnZlcnQ= 58865 -aXZy 58866 -LnNldExvY2F0aW9u 58867 -IGZsdWVudA== 58868 -X0VORElBTg== 58869 -IENhcmxv 58870 -Y2VwdHM= 58871 -YWRkQWN0aW9u 58872 -Lm9hdXRo 58873 -PFVuaXR5RW5naW5l 58874 -cmVlbWVudHM= 58875 -LlNraXA= 58876 -PykKCg== 58877 -LmRlZmF1bHRQcm9wcw== 58878 -IGNhYmU= 58879 -IFNoZW4= 58880 -ZXJvc2lz 58881 -IFByb2ZpdA== 58882 -IHBvaXM= 58883 -X0NSRUFURUQ= 58884 -IHJlbW92ZUZyb20= 58885 -KHdz 58886 -P2FjdGlvbg== 58887 -KEZpZWxk 58888 -IGVycm9uZQ== 58889 -Lm1pbmltdW0= 58890 -IFJldHJpZXZlZA== 58891 -IGRhZG8= 58892 -IFBSSVZBVEU= 58893 -LXNwZWM= 58894 -IGd6aXA= 58895 -cGRhdGE= 58896 -IHBvc1k= 58897 -KGxvdw== 58898 -IHF1YWxxdWVy 58899 -L2Nsb3Vk 58900 -6rKM 58901 -KGNvbW1vbg== 58902 -IEFyYmVpdA== 58903 -b3JnYW5pc2F0aW9u 58904 -IHRpZHk= 58905 -IFJvbGFuZA== 58906 -KHBo 58907 -LnpvbmU= 58908 -IGdlbnRsZW1lbg== 58909 -xrDhu6Nj 58910 -5bGx 58911 -IGVuY2xvc3VyZQ== 58912 -IE1hbmFmb3J0 58913 -CUNvbG9y 58914 -U3RlbmNpbA== 58915 -Tmlj 58916 -IHRoZW9yZW0= 58917 -IFZH 58918 -IGNvbG91cmVk 58919 -VkJveExheW91dA== 58920 -dWxzaXZl 58921 -RHJhZ29u 58922 -Y2Zm 58923 -ZXRlc3Q= 58924 -ZW5zYQ== 58925 -b2ZkYXk= 58926 -LkF6dXJl 58927 -OlVJQ29udHJvbEV2ZW50VG91Y2hVcEluc2lkZQ== 58928 -X3VwZGF0ZXM= 58929 -IHRyZW5keQ== 58930 -dWdhcw== 58931 -d2Vha1NlbGY= 58932 -IHJpZGdl 58933 -aWJyaQ== 58934 -IOy2lA== 58935 -KENH 58936 -IE1vbmtleQ== 58937 -LndyaXRlSW50 58938 -LnRpbWVkZWx0YQ== 58939 -Vmlld0NvbnRyb2xsZXJBbmltYXRlZA== 58940 -IFByb3ZpZGVuY2U= 58941 -44GI 58942 -IGJsZW5kcw== 58943 -L1N1YnRocmVzaG9sZA== 58944 -IEFwcGw= 58945 -IGF0YW4= 58946 -IHJlbG9hZERhdGE= 58947 -dW1ib3Ryb24= 58948 -c3TDvHQ= 58949 -T0F1dGg= 58950 -IEdpdmluZw== 58951 -IOyEpA== 58952 -IEZpbm5pc2g= 58953 -Y2hlY2tpbmc= 58954 -LkVtYmVk 58955 -c2VxdWVsaXpl 58956 -IGluaXRpYWxpemVz 58957 -IE9zbG8= 58958 -2LY= 58959 -Z2V0RXh0ZW5zaW9u 58960 -X0FMVA== 58961 -KGJsYW5r 58962 -IGZhdGFsRXJyb3I= 58963 -IGRlbWlzZQ== 58964 -KioqKioK 58965 -IFhT 58966 -KEFG 58967 -IEVucw== 58968 -YW50aGE= 58969 -IFBPUg== 58970 -IG5pY2g= 58971 -Lk5hbWVk 58972 -IGdpZ2FudGlj 58973 -IE9ic2VydmF0b3J5 58974 -LlJlc29sdmU= 58975 -IFBheW1lbnRz 58976 -Z3VpbGQ= 58977 -IGN1cnJlbnRTdGF0ZQ== 58978 -PT09PT09PT09PT09PT09Cg== 58979 -IFNleQ== 58980 -cERhdGE= 58981 -IGRlYWRsaW5lcw== 58982 -IGNlbnRyYWxpemVk 58983 -IFNjaG9sYXJzaGlw 58984 -X3N1cHBvcnRlZA== 58985 -LmNocm9tZQ== 58986 -KCldKTsK 58987 -IGN5YW4= 58988 -IENhZ2U= 58989 -QXV0aG9ycw== 58990 -Xw0K 58991 -L29z 58992 -a2lt 58993 -ZGVl 58994 -LnRleA== 58995 -IHlvdXJzZWx2ZXM= 58996 -IG1ncg== 58997 -IGFsaw== 58998 -LWluc3RhbGw= 58999 -IGRyYWZ0aW5n 59000 -IHJ1bW9y 59001 -IHN0YXR1ZXM= 59002 -UG9vbGluZw== 59003 -b2xpbmE= 59004 -QUFBQUFBQUE= 59005 -LyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 59006 -IGV4dHJlbWlzdHM= 59007 -Q2FsY3Vs 59008 -aWdodGhvdXNl 59009 -SW5zZXQ= 59010 -KElOUFVU 59011 -IHN5bmNocm9uaXphdGlvbg== 59012 -aXZpcnVz 59013 -LmF4ZXM= 59014 -IEdhcA== 59015 -LUFu 59016 -X1RlbXBsYXRl 59017 -IGdhbWVy 59018 -IENyaWNrZXQ= 59019 -IGxpbnQ= 59020 -IGF1dGhvcml0YXJpYW4= 59021 -TlNVSW50ZWdlcg== 59022 -IHJlZG8= 59023 -IGFkaXBpc2Npbmc= 59024 -X0ZFVENI 59025 -Y2hlaWQ= 59026 -IEZhbmc= 59027 -LmluZGljZXM= 59028 -dG9uZQ== 59029 -0LTQtdC7 59030 -IHt7LS08 59031 -YnJhaGlt 59032 -IHNhbGE= 59033 -Z2V0Q29kZQ== 59034 -IGNvbW11bmljYXRlZA== 59035 -c3RhcnRzV2l0aA== 59036 -ZXJ0eg== 59037 -UmVhZGFibGU= 59038 -SXRlbUlk 59039 -b3JlZmVycmVy 59040 -Y3JlZGlibGU= 59041 -w6FyaWE= 59042 -IGNvbWJpbmVSZWR1Y2Vycw== 59043 -KiovCgo= 59044 -IGJsaXNz 59045 -IGFkb3Ju 59046 -ZGVwZW5kcw== 59047 -IFJPT00= 59048 -IGZyYW1pbmc= 59049 -ID8nLA== 59050 -YXV0eQ== 59051 -X3BvdA== 59052 -X3RhYnM= 59053 -RXhhY3Q= 59054 -LCIs 59055 -ICd9JzsK 59056 -IGFyYml0cg== 59057 -YWhyYWlu 59058 -LmdldFN0cmluZ0V4dHJh 59059 -ICRc 59060 -IG91dHB1dFN0cmVhbQ== 59061 -IGNvbW1lbmM= 59062 -YW51cw== 59063 -Y2h5 59064 -PEVtcGxveWVl 59065 -IGhleGF0cmlnZXNpbWFs 59066 -IG5hY2lvbmFs 59067 -KHNlcmlhbGl6ZXJz 59068 -X3B1dGNoYXI= 59069 -X1NBRkU= 59070 -ZW50aWFsQWN0aW9u 59071 -SXRlbVNlbGVjdGVkTGlzdGVuZXI= 59072 -LkRpc3BhdGNo 59073 -Q29uZmxpY3Q= 59074 -X2Fib3V0 59075 -b3NhdXI= 59076 -Qm91bmRhcnk= 59077 -IGNsZWFyQ29sb3I= 59078 -KExvY2F0aW9u 59079 -IE1PTlRI 59080 -IFRhc3Rl 59081 -LUdlbmVyYWw= 59082 -IFdBUg== 59083 -IGVyaGFsdGVu 59084 -LXNhdmluZw== 59085 -IGNvdXBsaW5n 59086 -LXRyaWdnZXI= 59087 -bW90b3I= 59088 -IHl5eXk= 59089 -IFBhdGVudA== 59090 -cHRv 59091 -IG1pc2RlbWVhbm9y 59092 -dmFzaW9u 59093 -IEFkbWlyYWw= 59094 -4LmJ4Liy 59095 -X1BXUg== 59096 -IGRldmFzdGF0ZWQ= 59097 -Zm9saW9z 59098 -SVRVREU= 59099 -dXJyZWN0 59100 -IHJvYm90aWM= 59101 -IFNhbmN0 59102 -IEhhd2FpaWFu 59103 -LlJvdXRl 59104 -LWNvbmRpdGlvbg== 59105 -IHJr 59106 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioK 59107 -Y3JlYXRlRWxlbWVudA== 59108 -IEtvcA== 59109 -aWduYW50 59110 -LnJvbGxiYWNr 59111 -IHNhbHVk 59112 -Xycs 59113 -IEFOU0k= 59114 -RXhjZXB0 59115 -IERyYXdhYmxl 59116 -LlV0Y05vdw== 59117 -Ijpbewo= 59118 -IGtvbGU= 59119 -THVh 59120 -IEJlbGlldmU= 59121 -Q29tcHV0 59122 -IGhhbGx1Yw== 59123 -IFNpZ25z 59124 -cnN0 59125 -Lmh1 59126 -IEtOT1c= 59127 -V2k= 59128 -IEJyYXNz 59129 -IFJhcw== 59130 -QGhvdG1haWw= 59131 -IHNlZGltZW50 59132 -IGFwaw== 59133 -IOyDgQ== 59134 -X3JlZ2lvbnM= 59135 -IHBvZGl1bQ== 59136 -PEJvb2s= 59137 -0LbQtQ== 59138 -IHNpeHRlZW4= 59139 -IEFsaWFz 59140 -IGluZnJhcmVk 59141 -IFZhbmRlcg== 59142 -IExlYWRpbmc= 59143 -dWNpbmc= 59144 -LDosOg== 59145 -X2hvcg== 59146 -d2F0 59147 -IGTDqWNvdQ== 59148 -X1dpZGdldA== 59149 -U291bmRz 59150 -X25hdmlnYXRpb24= 59151 -IHNjaG5lbGw= 59152 -KGdlbmVyYXRvcg== 59153 -dWNlbmU= 59154 -IHJlbWFrZQ== 59155 -SVB2 59156 -IHLDqWFs 59157 -X0lOQ1JFTUVOVA== 59158 -IGh5cG90aGV0aWNhbA== 59159 -X2FuZw== 59160 -IG9mcw== 59161 -ICEK 59162 -LmNvbXBsZXRlZA== 59163 -R2V0VHlwZQ== 59164 -IGtvbW1lbg== 59165 -w6FsaWRv 59166 -YWRkT24= 59167 -IHrFgg== 59168 -VUxB 59169 -X2luZGljYXRvcg== 59170 -J10KCgo= 59171 -YXBhY2hl 59172 -X1NlbGVjdA== 59173 -IEdyZWVuZQ== 59174 -V2hhdHM= 59175 -X2FuaW0= 59176 -IHJlcGV0aXRpdmU= 59177 -bXVjaA== 59178 -IFRocmVzaG9sZA== 59179 -IGxm 59180 -KENhdGVnb3J5 59181 -Y29uZQ== 59182 -TWl4 59183 -X01FVEFEQVRB 59184 -YXlzaWE= 59185 -TmVpZ2hib3Jz 59186 -CQoJCQo= 59187 -SVBIRVI= 59188 -IEZyYWc= 59189 -IENlbGxz 59190 -IG5hbWVzcGFjZXM= 59191 -KGJhY2s= 59192 -IFJlc3RhdXJhbnRz 59193 -c3Zj 59194 -INC70Lg= 59195 -b3RlY2g= 59196 -LXNs 59197 -pb8= 59198 -IFdU 59199 -IFJlZHVjdGlvbg== 59200 -IGRvdHRlZA== 59201 -CWZvdW5k 59202 -IFRFQU0= 59203 -Qm9ybg== 59204 -IE11c2g= 59205 -IENvbXBhcmFibGU= 59206 -IGhpdGNo 59207 -QVRP 59208 -IG1heEhlaWdodA== 59209 -YmVnaW5UcmFuc2FjdGlvbg== 59210 -w612 59211 -X2Ju 59212 -IGhlcmQ= 59213 -IHJldmVyc2Fs 59214 -IEhvbmQ= 59215 -ZGVsaW1pdGVy 59216 -IGNvbmZ1c2U= 59217 -IGhvcHM= 59218 -IGNlbnRyb2lk 59219 -IGNvdXJ0cm9vbQ== 59220 -LmRlY29yYXRvcnM= 59221 -IG1waQ== 59222 -IEltcHJvdmVk 59223 -SU5ORVI= 59224 -IEJhbmdhbG9yZQ== 59225 -IFRhbWI= 59226 -IGJvYXN0 59227 -KCkpKQ0K 59228 -IGlsbGljaXQ= 59229 -IE1vcm9jY28= 59230 -Z3JlZ2F0b3I= 59231 -X3Jlc3VtZQ== 59232 -IGNyYWNrZG93bg== 59233 -IHBvcnRyYWl0cw== 59234 -L2hpZ2g= 59235 -KFwn 59236 -IGF5dWQ= 59237 -X2ZlZWRiYWNr 59238 -IGNhdGU= 59239 -L2F2YXRhcg== 59240 -IGhlYg== 59241 -UG9pbnRDbG91ZA== 59242 -IOWSjA== 59243 -IDwhWw== 59244 -IGdldFJlc291cmNlcw== 59245 -fTp7 59246 -T3BlcmF0aW5n 59247 -IEZvZw== 59248 -CXRhYg== 59249 -IFJlc2VhcmNoZXJz 59250 -IGZhYnJpY2F0aW9u 59251 -LmRhdGFzZXRz 59252 -IENhbXBv 59253 -IEthdWY= 59254 -IGRsbA== 59255 -bGlndA== 59256 -XSkpOwoK 59257 -c3RlbGxlbg== 59258 -QUNLRVQ= 59259 -bHZs 59260 -IEdsb3J5 59261 -LmRhdGVUaW1l 59262 -IGNvbW11dGU= 59263 -IG9uQ3JlYXRlVmlld0hvbGRlcg== 59264 -IFhFbGVtZW50 59265 -IFRva2Vucw== 59266 -PHRoZWFk 59267 -X3BpY2s= 59268 -7KQ= 59269 -dm9u 59270 -ZGVwYXJ0dXJl 59271 -KHJlbmRlcmVy 59272 -cGhvbmVOdW1iZXI= 59273 -KFBlcnNvbg== 59274 -Z2VuZXM= 59275 -IExhcnM= 59276 -ICl7Cgo= 59277 -IEpzb25SZXN1bHQ= 59278 -IG1ldG9kbw== 59279 -Vk9LRQ== 59280 -LmdldFVzZXJJZA== 59281 -QWNjZWxlcg== 59282 -CXJlcXVpcmVk 59283 -IGNoYW1waW9uc2hpcHM= 59284 -QnVpbGRDb250ZXh0 59285 -L3Rhc2s= 59286 -L3JlbGVhc2Vz 59287 -Q2F0ZWdvcmlh 59288 -X292ZXJsYXk= 59289 -IHNjYXJjZQ== 59290 -X2xpbQ== 59291 -bmdy 59292 -YWhsZW4= 59293 -IEFydGlmaWNpYWw= 59294 -c3ByZWFk 59295 -IGJvd2xpbmc= 59296 -LmFuYWx5c2lz 59297 -U01UUA== 59298 -CXBhc3N3b3Jk 59299 -IGJhdGhz 59300 -XSkpewo= 59301 -Y3VycmVudGx5 59302 -YWNpZW50ZQ== 59303 -X3NlcGFyYXRvcg== 59304 -IGRlYmVy 59305 -IERpc2FibGVk 59306 -acOocmVz 59307 -IOKV 59308 -X3Byb2Nlc3Npbmc= 59309 -IHByb3Rlc3Rpbmc= 59310 -IFJPVA== 59311 -Z3JhYg== 59312 -INC30LDQug== 59313 -IHByb2FjdGl2ZQ== 59314 -d29yZHByZXNz 59315 -IFNldmVy 59316 -aW5kZW4= 59317 -IHdpa2lwZWRpYQ== 59318 -KXsNCg0K 59319 -X3dpbmRvd3M= 59320 -aXNsYXRpb24= 59321 -IHVucmVzdA== 59322 -IGRpc21pc3NhbA== 59323 -Lk5VTQ== 59324 -X0ZBU1Q= 59325 -aXNzdWVk 59326 -IEZBQ0U= 59327 -X3VuZGVy 59328 -IHBsdWdnZWQ= 59329 -IOWw 59330 -IGLEmWR6aWU= 59331 -IElDQw== 59332 -IGNvbWJ1c3Rpb24= 59333 -IGtpc3NlZA== 59334 -IHN0YXJyZWQ= 59335 -IFdhdHRz 59336 -IHNwaWVsZW4= 59337 -LXB1cnBvc2U= 59338 -IEV2YWw= 59339 -YXJnZXM= 59340 -LHJlc3VsdA== 59341 -dGVjaG5vbG9neQ== 59342 -IG5hdGlvbmFsaXR5 59343 -aWN1cw== 59344 -IE51Zw== 59345 -INGC0L4= 59346 -CQkJCQkJCSAg 59347 -Y29sbw== 59348 -IGdhc3Rybw== 59349 -YW50ZWVk 59350 -T0xJRA== 59351 -LmJpYXM= 59352 -X3RlbGU= 59353 -Lmluc3BlY3Q= 59354 -IHZlaWw= 59355 -LmZvb3Rlcg== 59356 -IG5lZ2xpZ2VuY2U= 59357 -IGp1ZGdtZW50cw== 59358 -Um9vbXM= 59359 -eW5u 59360 -CWNvdW50ZXI= 59361 -b2NjdXBhdGlvbg== 59362 -IOeUnw== 59363 -dW5hcw== 59364 -ICheKSg= 59365 -TGFtYmRh 59366 -ZmVs 59367 -LlBhcmFtcw== 59368 -INC00L7QsdCw0LI= 59369 -c2V0TGF5b3V0 59370 -IGRlcG9ydGF0aW9u 59371 -IGxvY2FsT2JqZWN0 59372 -IFBoYXJtYWNldXRpY2Fs 59373 -Y2VwdGl2ZQ== 59374 -IE5vbWU= 59375 -RXF1aXBtZW50 59376 -RmFu 59377 -VW5pdmVyc2Fs 59378 -CXNvY2tldA== 59379 -IGdyaW4= 59380 -IGV4cG9zZXM= 59381 -IGhhYmVy 59382 -IHNpbmNlcmVseQ== 59383 -IGNhbXM= 59384 -IG3DvA== 59385 -ZW5pYQ== 59386 -RW1lcg== 59387 -Q3J5cHRv 59388 -U2xvdw== 59389 -KHhocg== 59390 -IT0o 59391 -LXNlcnZpY2Vz 59392 -IFBX 59393 -IHByZW5kcmU= 59394 -IG3DpGRjaGVu 59395 -ZW1vbnM= 59396 -0L7Qt9Cy0YDQsNGJ 59397 -Lk1hbmFnZXI= 59398 -7Jk= 59399 -IGdyYWY= 59400 -LXJh 59401 -bWV0cmljYWw= 59402 -L2Zs 59403 -IGNlbWV0ZXJ5 59404 -Z2Vucw== 59405 -IHDFmQ== 59406 -IE15U3FsQ29tbWFuZA== 59407 -LVRv 59408 -IHbDpQ== 59409 -IGFpcnN0 59410 -b21lbnR1bQ== 59411 -IHNlcnZv 59412 -bWlsbGlvbg== 59413 -IE1pcmFuZGE= 59414 -IlNoZQ== 59415 -IGFkdm9jYXRpbmc= 59416 -LWNhcHRpb24= 59417 -IEF0dHJpYnV0aW9u 59418 -IHdlbGNoZQ== 59419 -X3ZlbmRvcg== 59420 -CVN0YXR1cw== 59421 -YXJyaXM= 59422 -IHByaW50aw== 59423 -IiwiIw== 59424 -IHJlbGF0aXY= 59425 -aWZmZXJlbmNlcw== 59426 -aXp6ZXM= 59427 -IGRlY2ltYWxz 59428 -IFByb3Y= 59429 -Lm1heGltdW0= 59430 -QXJu 59431 -IGhlbGljb3B0ZXJz 59432 -X0JPVFRPTQ== 59433 -Y2h1cmU= 59434 -b2Rpbmdz 59435 -Jyg= 59436 -IikpKTsNCg== 59437 -KGJlYW4= 59438 -LmZk 59439 -RnVuZA== 59440 -IGhhbmdz 59441 -YXBwaWQ= 59442 -L2tlcm5lbA== 59443 -LnBvaQ== 59444 -Lk1pblZhbHVl 59445 -LXZhbGlkYXRpb24= 59446 -THVrZQ== 59447 -Y2Rm 59448 -IEZ1bmVyYWw= 59449 -IFNhbXBsZXM= 59450 -CWRl 59451 -IHRvYXN0cg== 59452 -IHRheGFibGU= 59453 -IGNsdXN0ZXJpbmc= 59454 -ICdcJw== 59455 -IHJlc3RyYWludA== 59456 -ZWNlZA== 59457 -Y2hhaW5z 59458 -44CC77yI 59459 -X0dSQVBI 59460 -IGZ1ZWxlZA== 59461 -6ZyA 59462 -SHA= 59463 -5aSN 59464 -VGlsZXM= 59465 -IGF1bnF1ZQ== 59466 -SkM= 59467 -IGhvc3RhZ2U= 59468 -IEVzaw== 59469 -IG1hdg== 59470 -IGdlc3Rpb24= 59471 -IGJhbm5lcnM= 59472 -fXsk 59473 -LmludFZhbHVl 59474 -LiciCgo= 59475 -X01BVFJJWA== 59476 -IGNlYXNlZA== 59477 -IEdPRA== 59478 -X0NBTUVSQQ== 59479 -LkFsbG93VXNlcg== 59480 -dHJhY2tlZA== 59481 -Q29vaw== 59482 -YmFpcnJv 59483 -KGNvbXBhbnk= 59484 -IHZpZXdwb2ludA== 59485 -LmdldFdyaXRlcg== 59486 -IE5ldHM= 59487 -d2l2ZXM= 59488 -ICgpKQo= 59489 -ZXhhbXBsZU1vZGFs 59490 -CWNoaWxk 59491 -IG15dGhvbG9neQ== 59492 -IC8vIg== 59493 -X2F4ZXM= 59494 -aWJvbGQ= 59495 -LkRhcms= 59496 -IE1heHdlbGw= 59497 -IGdwb2ludGVy 59498 -b2xpY2l0dWQ= 59499 -QmF0 59500 -dWxuZXI= 59501 -YmFsYW5jZWQ= 59502 -bWFpbGVy 59503 -IGNvbnRlbXBvcg== 59504 -5omL5py6 59505 -KCJfXw== 59506 -ICIpIg== 59507 -cmVhcg== 59508 -IEh1YW5n 59509 -XScpCg== 59510 -16k= 59511 -RlRB 59512 -IENhbGxpbmdDb252ZW50aW9u 59513 -IE91dHB1dHM= 59514 -UGs= 59515 -LlJlZmVyZW5jZQ== 59516 -bGVjdHVhbA== 59517 -ICk6Cgo= 59518 -IGJyYWNlbGV0 59519 -dWdlcg== 59520 -CUVycm9y 59521 -U3dlZXQ= 59522 -KCIvIik7Cg== 59523 -aHg= 59524 -IHVucmVhc29uYWJsZQ== 59525 -SW50ZXJwcmV0ZXI= 59526 -IGxvZnQ= 59527 -X3Byb2R1Y3Rv 59528 -IHNvY2lldGFs 59529 -LlBhcnNlcg== 59530 -IEFkYXB0 59531 -LmZvbw== 59532 -KHdoZXJl 59533 -LkZlYXR1cmU= 59534 -IFlhbWFoYQ== 59535 -Z2xhc3M= 59536 -Rm9yZ2U= 59537 -IHByb2hpYml0cw== 59538 -IGNhcGFjaXRpZXM= 59539 -IO2VqOyImA== 59540 -IHBlcm11dGF0aW9u 59541 -IGlobQ== 59542 -Rmxk 59543 -ZWxpYWw= 59544 -PT09PT09PT09PT0K 59545 -QENvbmZpZ3VyYXRpb24= 59546 -IGdlYXJlZA== 59547 -aW9zbw== 59548 -aWVzdGE= 59549 -dHJhbnNsYXRpb25z 59550 -SW5wdXRDaGFuZ2U= 59551 -UG9wdWxhcg== 59552 -IFBMVVM= 59553 -IHZm 59554 -X0ZyZWU= 59555 -YmJveA== 59556 -IGNhdXNhbA== 59557 -UElMRQ== 59558 -IHNjaMO2 59559 -IGlyb25pYw== 59560 -TWly 59561 -LkA= 59562 -5Y2X 59563 -IOiH 59564 -UmV3 59565 -dWxlbmNl 59566 -Zmxlbg== 59567 -IGNhbkFjdGl2YXRl 59568 -LXJlc3BvbnNl 59569 -IGFjY2VudHM= 59570 -aWdub3JlZA== 59571 -wrBG 59572 -LkRlcGVuZGVuY3lJbmplY3Rpb24= 59573 -CXBvaW50 59574 -IGNvbnRpbmdlbnQ= 59575 -IHNxdWFzaA== 59576 -IHBhcm1z 59577 -IENlbWV0ZXJ5 59578 -IGRlbHRhVGltZQ== 59579 -IERPUw== 59580 -IHZhbmlzaGVk 59581 -0LDRgNCw0LzQtdGC 59582 -IERQUw== 59583 -dGZvb3Q= 59584 -IFp1cw== 59585 -X0lOU1RBTEw= 59586 -R0FO 59587 -IGFyYg== 59588 -IG11bmljaXBhbGl0aWVz 59589 -SW50b0NvbnN0cmFpbnRz 59590 -QXV0b3Jlc2l6aW5nTWFza0ludG9Db25zdHJhaW50cw== 59591 -LGltYWdl 59592 -X2lnbm9yZQ== 59593 -IGRhbmdlcm91c2x5 59594 -cXVpc2E= 59595 -cGx1Y2s= 59596 -IGhhcnVz 59597 -dXBwZQ== 59598 -SHR0cEV4Y2VwdGlvbg== 59599 -QnJhY2tldA== 59600 -LicnCgo= 59601 -IFRvbA== 59602 -IFZpZXdlcg== 59603 -emJvbGxhaA== 59604 -LkNvZGVBbmFseXNpcw== 59605 -w6xuaA== 59606 -IGNvcnJlY3RhbWVudGU= 59607 -LmRh 59608 -IEFsZ2Vy 59609 -15A= 59610 -YmF1bQ== 59611 -IFBhbnRoZXI= 59612 -cGFydGljaXBhbnQ= 59613 -5b+F 59614 -LXN1cA== 59615 -IGVtdWxhdG9y 59616 -IGZhZGluZw== 59617 -IFdvbHZlcg== 59618 -Y3JlYXRlcw== 59619 -IGJvb2tpbmdz 59620 -LlF1ZXN0aW9u 59621 -p+ihjA== 59622 -IHN0cmVzc2Vz 59623 -IHJld3JpdHRlbg== 59624 -LlBJUEU= 59625 -ZWRlcw== 59626 -IGNiZA== 59627 -IjoiLw== 59628 -IGVuaGFuY2VtZW50cw== 59629 -X3N5 59630 -QklO 59631 -IFNsaXA= 59632 -SW5zcGVjdA== 59633 -IFdlZw== 59634 -IGNvbmdyZWdhdGlvbg== 59635 -IF86 59636 -X3Jt 59637 -RnJhbWVidWZmZXI= 59638 -ICcmIw== 59639 -IEZhbGxvdXQ= 59640 -SXNSZXF1aXJlZA== 59641 -IFBlYXJzb24= 59642 -IEZBQ1Q= 59643 -IHJlbGll 59644 -CWJveA== 59645 -IFNoZXBoZXJk 59646 -IFdpa2lMZWFrcw== 59647 -IENvbGxlY3Rvcg== 59648 -IHJlc2l6ZWQ= 59649 -bWV0aG9kTmFtZQ== 59650 -IGV2ZW50VHlwZQ== 59651 -IEF0aGVu 59652 -RGVzY3JpcHRvcnM= 59653 -IGJlcnM= 59654 -LW9wZXI= 59655 -IEluaXRpYWxseQ== 59656 -5aE= 59657 -X0JUTg== 59658 -ICAgICAgICAgDQo= 59659 -w6Fi 59660 -X2NhbXBhaWdu 59661 -X3dhdGNo 59662 -Rm9yZA== 59663 -LWRhdGVwaWNrZXI= 59664 -IHZpc2M= 59665 -IHNhdHU= 59666 -X3Ntcw== 59667 -IGNvbnRhZG9y 59668 -LXN2Zw== 59669 -IERPSQ== 59670 -JGFyZ3M= 59671 -IGtub2I= 59672 -LkJPTEQ= 59673 -IGRlYmF0ZWQ= 59674 -aW1ncw== 59675 -c29ja29wdA== 59676 -dHJ1dGg= 59677 -IEZlZXM= 59678 -IGhXbmQ= 59679 -X2Zvb2Q= 59680 -IGFicmFz 59681 -IG5vdGlvbnM= 59682 -IFRvZA== 59683 -OmNyZWF0ZQ== 59684 -IENvbmZsaWN0 59685 -VXN1YXJpb3M= 59686 -T1RPUw== 59687 -IG1zbQ== 59688 -S0hUTUw= 59689 -KFso 59690 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 59691 -IH1d 59692 -d2l6YXJk 59693 -IG1pZW50cmFz 59694 -IGRhdGFMaXN0 59695 -IGVtZXJnZXM= 59696 -xINuZw== 59697 -LlJlYWRJbnQ= 59698 -UEdB 59699 -SUxMSVNF 59700 -SUVudW1lcmF0b3I= 59701 -KHR1cGxl 59702 -Q2hyaXN0bWFz 59703 -TG9va0FuZEZlZWw= 59704 -b2dlbmVyYXRlZA== 59705 -ICMKCg== 59706 -Y29udHJvbGxlZA== 59707 -IGV4cXVpc2l0ZQ== 59708 -IGFjZXN0 59709 -UmVhZFdyaXRl 59710 -R2Fpbg== 59711 -44CN44CM 59712 -IGNvcHlyaWdodGVk 59713 -IGRvb20= 59714 -LlRhYmxlTGF5b3V0UGFuZWw= 59715 -IERvcnQ= 59716 -IGNoaWxp 59717 -IHdlcms= 59718 -IEVWRU5UUw== 59719 -IEJlYWNvbg== 59720 -IHNoaXBtZW50cw== 59721 -IHNlYmFnYWk= 59722 -dXBvbg== 59723 -dXRvbQ== 59724 -LmNvbnZlcnRlcg== 59725 -LkRyb3BUYWJsZQ== 59726 -PXt9Cg== 59727 -Zmlj 59728 -fgoK 59729 -IGxlc2JpYW5z 59730 -X25h 59731 -Rm9yZWlnbg== 59732 -CXRoZW4= 59733 -L21z 59734 -IG9yaQ== 59735 -Z2V0UHJvcGVydHk= 59736 -CXNucHJpbnRm 59737 -aGVzaW9u 59738 -44Gk 59739 -In0sIg== 59740 -IGFjcnlsaWM= 59741 -UGVycw== 59742 -QEVuYWJsZQ== 59743 -SXNs 59744 -KENhcmQ= 59745 -LlN0YWNr 59746 -TGljZW5zZWQ= 59747 -X0dVSUQ= 59748 -OnRpdGxl 59749 -IGh1c3Q= 59750 -IHByaW5jaXBhbFRhYmxl 59751 -YW5pdGl6ZQ== 59752 -L2VtYmVk 59753 -IGVuc3VyZWQ= 59754 -IEVHTA== 59755 -2YjYsQ== 59756 -IOWIhg== 59757 -LywK 59758 -IGZ1bmRyYWlzZXI= 59759 -S2V5TmFtZQ== 59760 -IG1hcmNoZWQ= 59761 -X1ZBTFVFUw== 59762 -IFNjZW5hcmlv 59763 -IG1ldGlj 59764 -X2Fzc29jaQ== 59765 -IFBhc3Rvcg== 59766 -CQkJCQkJCQkJCQkJCQkJCQkJ 59767 -ZXJhdGU= 59768 -IGludml0YXRpb25z 59769 -cXVvaXNl 59770 -IGJsYW1pbmc= 59771 -IGRhcmluZw== 59772 -VU1NWQ== 59773 -IHJpY2hlcg== 59774 -ZW1ha2Vy 59775 -IElkZW50aWZpY2F0aW9u 59776 -IOyduA== 59777 -IEJpbmRpbmdGbGFncw== 59778 -Y2hhcw== 59779 -IHJlc2lsaWVudA== 59780 -X3Bn 59781 -IHJlbGVn 59782 -IElSQQ== 59783 -U1RF 59784 -IHRyYWN0b3I= 59785 -LWxvYWRpbmc= 59786 -IFByZXZpb3VzbHk= 59787 -IFZhY2M= 59788 -L2Jl 59789 -IG7DpXI= 59790 -IHVybGVuY29kZQ== 59791 -IE5vcmZvbGs= 59792 -LlJlbGVhc2U= 59793 -IE5ldXRyYWw= 59794 -5Lit5Zu9 59795 -IEFybGluZ3Rvbg== 59796 -IGFsbGVnZXM= 59797 -IFdyaXRlcnM= 59798 -VGVzdGVy 59799 -IFJhbGx5 59800 -IGPDoQ== 59801 -CVByaW50 59802 -IOKHkg== 59803 -IFVzZXJDb250cm9sbGVy 59804 -IFNlZWtpbmc= 59805 -LlZBTA== 59806 -TGlzdE5vZGU= 59807 -X2Zm 59808 -IFBoaWxsaXA= 59809 -RkFDVA== 59810 -IGNhcmFtZWw= 59811 -IE11bHRpcA== 59812 -IENvbXBhcmVk 59813 -IFNlcmJpYQ== 59814 -n7M= 59815 -IHJldml2ZQ== 59816 -IEthbnll 59817 -IHZlcmdl 59818 -IEJ1bGdhcmlh 59819 -Z2V0Qm9keQ== 59820 -IHw+ 59821 -Y2VwaA== 59822 -LkRhdGVUaW1lUGlja2Vy 59823 -LiI7Cgo= 59824 -IFRpZQ== 59825 -LGl0ZW0= 59826 -IG1lbm4= 59827 -R2Fz 59828 -b2NoYQ== 59829 -X3ZpcnR1YWw= 59830 -IG1hc3RlcnBpZWNl 59831 -X3NlcXVlbmNlcw== 59832 -TFRF 59833 -IFN1Ym1pc3Npb24= 59834 -Q2FsbGVy 59835 -JFw= 59836 -U3BvcnQ= 59837 -YWd1cw== 59838 -Q29uc3RyYWludE1ha2Vy 59839 -IGNvbG9j 59840 -IHdpZw== 59841 -INCj 59842 -CUFycmF5 59843 -TG9va3M= 59844 -IEdUQQ== 59845 -LnN0ZXBz 59846 -YXRjaGV3YW4= 59847 -X3Jhbmdlcw== 59848 -ZXh0QWxpZ25tZW50 59849 -IEJyZW5uYW4= 59850 -IGFic3RyYWN0aW9u 59851 -dWxlckFuZ2xlcw== 59852 -Lm1pc2M= 59853 -IGFudGlib2RpZXM= 59854 -IGV4cG9uZW50aWFs 59855 -IENIQU5ORUw= 59856 -ZXhwZW5zZQ== 59857 -J3k= 59858 -IGRldGVjdGl2ZXM= 59859 -IHB1cnBvcnRlZA== 59860 -WVNURU0= 59861 -IHJhZGlvYWN0aXZl 59862 -IExhdGluYQ== 59863 -LkVuY29kaW5n 59864 -LlRBRw== 59865 -eGlu 59866 -RGVncmVl 59867 -dXJhY2lvbg== 59868 -cHJpY2Vz 59869 -IFJlZmVyZW50aWFsQWN0aW9u 59870 -IHJhcml0eQ== 59871 -IHBpbGVz 59872 -Z2VuZGU= 59873 -X3Byb2plY3Rz 59874 -X2dsb2JhbHM= 59875 -LnN0YXJ0VGltZQ== 59876 -IOq1rA== 59877 -U0VDVElPTg== 59878 -X3B1Ymxpc2g= 59879 -RmF1bHQ= 59880 -RERM 59881 -X3ByaW9y 59882 -TW9t 59883 -IHRoaWNrZXI= 59884 -IHNlcXVlbGl6ZQ== 59885 -IGVzc2VudGlhbHM= 59886 -c3RyYXM= 59887 -aW50cg== 59888 -PigoKQ== 59889 -Lm1hbmFnZW1lbnQ= 59890 -ZWls 59891 -6Zet 59892 -QXdhcmU= 59893 -LkNpdHk= 59894 -IEFyYml0 59895 -X0RN 59896 -X2tleWJvYXJk 59897 -TE9iamVjdA== 59898 -LXdlYnBhY2s= 59899 -IE5ld3BvcnQ= 59900 -IHByaW5jaXBhbENvbHVtbg== 59901 -bGVnYW50 59902 -IHBhbGxldA== 59903 -IGZyYWN0dXJl 59904 -IGdtYWls 59905 -Lk1ldGE= 59906 -QWJvdmU= 59907 -LktleUV2ZW50 59908 -aml0 59909 -X21hY3Jv 59910 -X1BVU0g= 59911 -4bup 59912 -L2NvbnRyb2xsZXI= 59913 -5Yqg6L29 59914 -IHN1cGVyZmljaWFs 59915 -ZXh0ZXJpdHk= 59916 -IG1lbnNhZ2Vt 59917 -V2luZA== 59918 -aXN0b24= 59919 -Lm9wZW5hcGk= 59920 -0LjRgNC+0LI= 59921 -IFNlcmlhbGl6ZXI= 59922 -dWN0aXZl 59923 -IHphcg== 59924 -UGxhY2Vz 59925 -LlN0YXRpYw== 59926 -QmE= 59927 -IGluYWR2ZXJ0 59928 -IEluZG9uZXNpYW4= 59929 -X0lQVg== 59930 -KGhvcml6b250YWw= 59931 -IGdldFRpdGxl 59932 -aWRlcHJlc3M= 59933 -IENvbnNvbGVDb2xvcg== 59934 -aXBlcnM= 59935 -JG91dA== 59936 -IGZlc3RpdmU= 59937 -IGV2ZW5pbmdz 59938 -LkdldERhdGE= 59939 -dWl0a2E= 59940 -IE1hbnVhbHM= 59941 -dXNzZWQ= 59942 -X01heA== 59943 -LkNoYXQ= 59944 -IEFpcmNyYWZ0 59945 -PWNvbQ== 59946 -Rk9VTkQ= 59947 -YXBybw== 59948 -IHRyZWFzdXJlcw== 59949 -X2FsaXZl 59950 -IGdhZGdldA== 59951 -ZWtpbmc= 59952 -QnV0dG9uRG93bg== 59953 -QnJvd3NhYmxl 59954 -LlBFUk1JU1NJT04= 59955 -UEFTU1dPUkQ= 59956 -IEhBU0g= 59957 -ZsOp 59958 -XFRlc3RDYXNl 59959 -TE9TUw== 59960 -b3RoZXJz 59961 -LEo= 59962 -IGFzc2hvbGU= 59963 -d2Vyaw== 59964 -IG3Dow== 59965 -Lmll 59966 -ZXZpbA== 59967 -a29udGFrdGU= 59968 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8K 59969 -PXN5cw== 59970 -CWxvY2s= 59971 -LS07Cgo= 59972 -X0ZVTg== 59973 -RmlsbENvbG9y 59974 -w7Nh 59975 -cHJlbmQ= 59976 -IGNvbXByZXNzb3I= 59977 -TW90aGVy 59978 -IEFyY2hlcg== 59979 -LmdvdG8= 59980 -IHfDvHJkZQ== 59981 -IGJhbWJvbw== 59982 -77yO 59983 -IFRyZWVz 59984 -IGJ1bXBlcg== 59985 -IHNhdXNhZ2U= 59986 -IEVsYXN0aWNzZWFyY2g= 59987 -IGhvcml6b250YWxseQ== 59988 -IEd1bA== 59989 -SW1tdXRhYmxl 59990 -IGxvc2Vy 59991 -IGFib3J0ZWQ= 59992 -LWRlbW8= 59993 -IEhhdGNo 59994 -IHVuZGU= 59995 -IHByb2Nlc3Nv 59996 -LWNhbGw= 59997 -SW5jb21l 59998 -5YM= 59999 -X3JldHVybnM= 60000 -J10uIic= 60001 -KHN3 60002 -Q0JT 60003 -YW1pbGllcw== 60004 -IFlvdXJzZWxm 60005 -IEhvbHQ= 60006 -Lk1PTg== 60007 -4KeH 60008 -0YjQtQ== 60009 -YW5vbg== 60010 -IEZvbnRBd2Vzb21l 60011 -cHJvZHVjZXI= 60012 -anI= 60013 -IG1hdQ== 60014 -CWludGVy 60015 -IGRpc2hvbmVzdA== 60016 -IG1hZ25h 60017 -IENvbGxlY3RpdmU= 60018 -IHZyYWltZW50 60019 -IGNob2l4 60020 -c3RheQ== 60021 -IHdlbGRpbmc= 60022 -cmlzaW5n 60023 -LG1pbg== 60024 -IEZhdGU= 60025 -Z2xvYg== 60026 -UkdCQQ== 60027 -IGRldHRl 60028 -VmVu 60029 -IGVtYmFycmFzc21lbnQ= 60030 -LkRFTEVURQ== 60031 -Z3JlZ2Fy 60032 -LXJlbmRlcg== 60033 -KGJ1Y2tldA== 60034 -Ij4KCgo= 60035 -LndhaXRLZXk= 60036 -QnVzeQ== 60037 -IGRpZmZlcmVudGlhdGlvbg== 60038 -IENTVA== 60039 -LkNvbnN0YW50 60040 -IGxpbmVOdW1iZXI= 60041 -KG1hdGNoZXM= 60042 -IHdlYnNvY2tldA== 60043 -IGJhcnJlZA== 60044 -IHB1ZWRlcw== 60045 -TW9ubw== 60046 -Q09SRQ== 60047 -SUlE 60048 -ICAgIA0KDQo= 60049 -IHDDumJsaWNv 60050 -bGVhbmluZw== 60051 -IGNsZWFuc2luZw== 60052 -IGNyaXM= 60053 -IERldmlscw== 60054 -X1NFVFRJTkc= 60055 -dW50YXJ5 60056 -Lik7Cg== 60057 -CiAgIAo= 60058 -W2N1cnI= 60059 -dHN5 60060 -IEFsZXhpcw== 60061 -cml0ZWw= 60062 -IHBldHJvbGV1bQ== 60063 -LnByZXByb2Nlc3Npbmc= 60064 -bWF0dGVy 60065 -Rm9yUmVzdWx0 60066 -LWxpY2Vuc2U= 60067 -IHRyYXZlbGxlcnM= 60068 -IERpc3BhdGNoZXI= 60069 -ZW5uaWZlcg== 60070 -IGRpZ2VzdGl2ZQ== 60071 -UEVE 60072 -aGliaXRpb24= 60073 -TUFTQ29uc3RyYWludE1ha2Vy 60074 -IFdhdHQ= 60075 -QmVuZWY= 60076 -LnNldFZpZXc= 60077 -ZHRv 60078 -VEVF 60079 -IFBlbG9zaQ== 60080 -X0VYVFJB 60081 -IG1lZGFscw== 60082 -eGhy 60083 -Zm9yZWNhc3Q= 60084 -IG5hcmdpbg== 60085 -b3Vucw== 60086 -LWZpbGw= 60087 -X0NVUlNPUg== 60088 -IHN1cGVydmlzZWQ= 60089 -IHR1cmY= 60090 -IEVkZ2Fy 60091 -UE9TSVRJT04= 60092 -IGNhdGVnb3J5SWQ= 60093 -4ok= 60094 -X0VS 60095 -4bunYQ== 60096 -U2hvd24= 60097 -Lmxs 60098 -X1BPTElDWQ== 60099 -KCksJw== 60100 -IFByZXY= 60101 -IFN0cmluZ0ZpZWxk 60102 -CUdsb2JhbA== 60103 -YXNzZWQ= 60104 -VGhyb3VnaG91dA== 60105 -b3N0cmluZ3N0cmVhbQ== 60106 -LmF3dGV4dHJh 60107 -IHNsb3Blcw== 60108 -IFNlcXVlbnRpYWw= 60109 -IGdpb3Ju 60110 -IHplbGY= 60111 -IHZlcnNhdGlsaXR5 60112 -bGVuZWNr 60113 -LmNnaQ== 60114 -IGRvdWJsaW5n 60115 -IEJhbmdrb2s= 60116 -IGJ1dXJ0 60117 -IHVzdcOhcmlv 60118 -c3R1ZGlv 60119 -IGpldW5lcw== 60120 -IG11dGVk 60121 -IGlwcw== 60122 -X2ZyYWN0aW9u 60123 -JiYo 60124 -IHN0dW50 60125 -Jyk7Pz48Lw== 60126 -IExpZ2E= 60127 -IHF1YWxpdMOp 60128 -QXNzaWduYWJsZQ== 60129 -IHdvcmthcm91bmQ= 60130 -IHNwdXI= 60131 -IHNsZXc= 60132 -X0dF 60133 -IEFncmljdWx0dXJhbA== 60134 -IHJlbGVudGxlc3M= 60135 -KFF1ZXJ5 60136 -IFNlY3Rpb25z 60137 -IHJldmlld2Vycw== 60138 -UmFpbg== 60139 -ZGxn 60140 -YXNzZXJ0RmFsc2U= 60141 -IG5vbWluZWVz 60142 -X18pLg== 60143 -LmR5bmFtaWM= 60144 -IFBCUw== 60145 -Q2hhbmdpbmc= 60146 -IHNsaWdodGVzdA== 60147 -IE1hbmc= 60148 -fT4NCg== 60149 -IGV2YXBvcg== 60150 -YmFibGU= 60151 -IFBSSUNF 60152 -IOaz 60153 -bHVjZW50 60154 -IHZhbXA= 60155 -IFRlY2huaWNpYW4= 60156 -IHVuaXF1ZW5lc3M= 60157 -TWVz 60158 -dXJiYW4= 60159 -LnBhcmFtZXRyaXpl 60160 -IFJlcGxheQ== 60161 -U2Vzc2lvbnM= 60162 -ZW1icg== 60163 -LUFtZXJpY2Fucw== 60164 -X1BST1hZ 60165 -IHBpYW4= 60166 -IHRyaWU= 60167 -IERlc3RydWN0b3I= 60168 -R2FtZVN0YXRl 60169 -IElNRg== 60170 -Y2hpbg== 60171 -IHBvcnRl 60172 -IFN3YWw= 60173 -5Z+O 60174 -U3Vic3RyaW5n 60175 -aW1pbmc= 60176 -L0xpYnJhcnk= 60177 -IGZyaWdodGVuZWQ= 60178 -d3JpdGVz 60179 -IHJlY3Vyc29z 60180 -YXJSZXN1bHQ= 60181 -X0lOSVRJQUxJWg== 60182 -IEJhZGdl 60183 -X2NyYw== 60184 -RWlnaHQ= 60185 -IERJU1RJTkNU 60186 -IHRocm8= 60187 -QFhtbA== 60188 -IExlZ2VuZGFyeQ== 60189 -LXR3aXR0ZXI= 60190 -X2Vhc3k= 60191 -ICsrKw== 60192 -KERBVEE= 60193 -LkxvY2FsZQ== 60194 -IGvDpA== 60195 -IG51cnQ= 60196 -IGNydWlz 60197 -X2lvcw== 60198 -IHNlbnNpbmc= 60199 -X0xpbmU= 60200 -CiAgICAgICAgICAgICAgICAgICAgCg== 60201 -cG9uZw== 60202 -b2xlb24= 60203 -IHdpbGRjYXJk 60204 -55So5oi35ZCN 60205 -IGJlZ2dpbmc= 60206 -Um9k 60207 -IMOO 60208 -X0NFTEw= 60209 -UmVzZWFyY2hlcnM= 60210 -LnNlbGVjdG9y 60211 -X2luZw== 60212 -IGFzcGlyaW5n 60213 -IGltbW9ydGFs 60214 -IHltaW4= 60215 -X3JvYm90 60216 -IHBsdXI= 60217 -QlRD 60218 -IERJRA== 60219 -IHBpZXJjaW5n 60220 -KnU= 60221 -X0RFRklORUQ= 60222 -IFRoaQ== 60223 -aXRhaXJl 60224 -KG1lZGlh 60225 -LW9ucw== 60226 -IGNoZWZz 60227 -ICIqLg== 60228 -L0FQ 60229 -IHJhem9y 60230 -IHNlYXJjaERhdGE= 60231 -ID0m 60232 -IOOAgg== 60233 -IG1vdXJu 60234 -dGluZ2hhbQ== 60235 -IG9saQ== 60236 -IFZlcm5vbg== 60237 -X1JT 60238 -nuaApw== 60239 -IGbDoWNpbA== 60240 -YW5nZW4= 60241 -Y2VsYWlu 60242 -IGFpbA== 60243 -bGVzdA== 60244 -IFFDT01QQVJF 60245 -Z2Fpbg== 60246 -IM61 60247 -IEtvYg== 60248 -IEZhdWx0 60249 -X2NvbmZpZ3M= 60250 -57uT5p6c 60251 -Lis= 60252 -Y2FsYXI= 60253 -KGNvbG9ycw== 60254 -TXVs 60255 -X0FSVA== 60256 -IGV4cGVyaW1lbnRpbmc= 60257 -ZXJtZW4= 60258 -IEFuZ2xv 60259 -LkZpeGVkU2luZ2xl 60260 -U2Vh 60261 -IGN0eHQ= 60262 -LnNsaWRlcg== 60263 -Q29sbGFwc2U= 60264 -R3JleQ== 60265 -IGZsZA== 60266 -LXByb29m 60267 -LmNhcGFjaXR5 60268 -Z2V0UGFyZW50 60269 -IENvbXBsaWFuY2U= 60270 -IGJ1cmds 60271 -LXJlYw== 60272 -IG92ZXJ3cml0dGVu 60273 -TVU= 60274 -IHJvdXRlcnM= 60275 -CU1vZGVs 60276 -IGZhbnRhc2llcw== 60277 -YXZpYW4= 60278 -X3ByZWM= 60279 -IFNjYW5kaW4= 60280 -IC8vPA== 60281 -L29jdA== 60282 -IGNlcmVtb25pZXM= 60283 -TW9udGhz 60284 -dW5keQ== 60285 -IHF1ZWQ= 60286 -IE5vdQ== 60287 -IFZpYnI= 60288 -LnJnYg== 60289 -IGNpdHJ1cw== 60290 -IGJyYWNlcw== 60291 -LXVwcGVyY2FzZQ== 60292 -Z2V0VGFibGU= 60293 -IGRvcG8= 60294 -IEtlcnI= 60295 -X0NISUxE 60296 -LWNsb3Vk 60297 -CU1hdHJpeA== 60298 -IGdhcmRlbmluZw== 60299 -U2luZw== 60300 -YWxtb3N0 60301 -UmVxdWlyZW1lbnRz 60302 -dWd1YXk= 60303 -KFByb3BlcnR5 60304 -c3Vic2NyaWJlcg== 60305 -RkFTVA== 60306 -cmVhY3Rpb24= 60307 -KGxw 60308 -KX0pCg== 60309 -YCku 60310 -LndhbGxldA== 60311 -X2V4Y2hhbmdl 60312 -Lk1heGltdW0= 60313 -IFZlcmI= 60314 -4pSB 60315 -KCk8 60316 -77ybCg== 60317 -Uk9U 60318 -Q0FSRA== 60319 -dWJpdA== 60320 -e0A= 60321 -X2tlbA== 60322 -IFRvb2x0aXA= 60323 -TXlTUUw= 60324 -TWFpbkFjdGl2aXR5 60325 -YXJm 60326 -IG1hbGlnbg== 60327 -IHNlaW5lbg== 60328 -YXBpc3Q= 60329 -IDwl 60330 -TWV0aG9kSW1wbA== 60331 -TWls 60332 -IE1pY2s= 60333 -LmRlcGVuZA== 60334 -PElE 60335 -IHByZWRpY3RpdmU= 60336 -IEFQUExJQ0FUSU9O 60337 -bGVm 60338 -ZGltZW5zaW9ucw== 60339 -IGNvbm9jZXI= 60340 -L2NvbmY= 60341 -IFRyYWN5 60342 -Rm90bw== 60343 -X3JlbWFpbmluZw== 60344 -PWZpbGU= 60345 -IHBhZ2VJbmRleA== 60346 -IFBhcmlzaA== 60347 -IHRleGFz 60348 -IE1BR0lD 60349 -IEhldw== 60350 -ZGlmZmVyZW5jZQ== 60351 -IGFsdHVyYQ== 60352 -Y3Vt 60353 -CWRhdGFUeXBl 60354 -IGNhcmFjdGVyZXM= 60355 -YXZpb3Vycw== 60356 -IFZPSUQ= 60357 -6L+R 60358 -UFVCTElD 60359 -Qmlv 60360 -IHN0cmluZ0J5QXBwZW5kaW5n 60361 -UGFyc2VFeGNlcHRpb24= 60362 -IFN1ZmY= 60363 -IE5vcnRvbg== 60364 -L2RldGFpbHM= 60365 -Lm51bGw= 60366 -Pj4m 60367 -CW9r 60368 -LWxvdw== 60369 -LnVzdWFyaW8= 60370 -bmVzdGVk 60371 -WEI= 60372 -T1VSUw== 60373 -LkJvcmRlckNvbG9y 60374 -IGJyb3c= 60375 -INCV 60376 -Y29ycg== 60377 -IFJlZHNraW5z 60378 -LmdldFRhZw== 60379 -LmdldFRyYW5zYWN0aW9u 60380 -IHN0aWdtYQ== 60381 -aGFyZHQ= 60382 -IFBsYXllclByZWZz 60383 -YWxzeQ== 60384 -dWNzb24= 60385 -TGFuZ3VhZ2Vz 60386 -IE9saXZpYQ== 60387 -IHRhYw== 60388 -IGJsaQ== 60389 -IGNhdmFs 60390 -IGNvbnNvbGlkYXRlZA== 60391 -IHBlcmls 60392 -IGRlbGU= 60393 -IGZvcm11bGF0ZWQ= 60394 -IGhpZ2h3YXlz 60395 -LnNwYXdu 60396 -PT0k 60397 -IE5pZXQ= 60398 -IHZlZ2dpZXM= 60399 -eXBv 60400 -LXJ1bGU= 60401 -IFZpZQ== 60402 -L2VwbA== 60403 -IGVuZmFudHM= 60404 -c3RyaW5nTGl0ZXJhbA== 60405 -IHRvdWdoZXN0 60406 -YnV5ZXI= 60407 -IGNvdmFyaWFuY2U= 60408 -IGlsaQ== 60409 -IFNvcGhpZQ== 60410 -IEJBQg== 60411 -ICIpLA== 60412 -IFVr 60413 -Y3VycmVudEluZGV4 60414 -X3VzZXJkYXRh 60415 -LmNvZGVj 60416 -IFB1bmphYg== 60417 -IFNOUA== 60418 -bG9s 60419 -YWR2YW5jZQ== 60420 -IGNvbWZ5 60421 -SnNvbklnbm9yZQ== 60422 -IGZhc2hpb25hYmxl 60423 -IElDT04= 60424 -IG9yYQ== 60425 -IFByaWNpbmc= 60426 -PG51bQ== 60427 -IElSQw== 60428 -RVJW 60429 -IE1laW4= 60430 -IElEaWN0aW9uYXJ5 60431 -QURPVw== 60432 -aXNOZXc= 60433 -IERldm9u 60434 -YXRs 60435 -KHJlcXVlc3RDb2Rl 60436 -CVByZXBhcmVkU3RhdGVtZW50 60437 -SU1QT1JU 60438 -IG1hcml0YWw= 60439 -X1NFTEVDVEVE 60440 -Z2V0UmVzcG9uc2U= 60441 -YXJEb3du 60442 -QlY= 60443 -aWJOYW1l 60444 -IFBBVENI 60445 -w6TDpG4= 60446 -IGRhYXI= 60447 -IEZpbGVNb2Rl 60448 -IG1hcnR5 60449 -LlNwcmluZ0FwcGxpY2F0aW9u 60450 -Y2VuZQ== 60451 -YW1wb2xpbmU= 60452 -Z2V0U2l6ZQ== 60453 -UmVzdGFydA== 60454 -5pWI 60455 -LnByb2plY3Rz 60456 -IEV0aGlvcGlh 60457 -IHN0YXR1c2Vz 60458 -VElPTg== 60459 -KGJn 60460 -IFh1bml0 60461 -VGVtcG9yYXJ5 60462 -IEVuZ2FnZW1lbnQ= 60463 -IHhm 60464 -IHByb3hpZXM= 60465 -IGdlbmVzaXM= 60466 -UGFnZXJBZGFwdGVy 60467 -IFNsYXZl 60468 -IHN1bmdsYXNzZXM= 60469 -IENobG9l 60470 -IGtvamk= 60471 -YWRlbQ== 60472 -CUpTT05PYmplY3Q= 60473 -zrM= 60474 -IGhvcnM= 60475 -Knc= 60476 -w7Ny 60477 -ZXNjaA== 60478 -IGNyaXRpY2lzZWQ= 60479 -emlhbA== 60480 -IFNhbGVt 60481 -LlZlcnRpY2Fs 60482 -IFJhc2g= 60483 -PkU= 60484 -dGVyaW5n 60485 -L3NjcmVlbnM= 60486 -IGhlaWdodGVuZWQ= 60487 -0LDRgNGC 60488 -QXV0aG9yaXRpZXM= 60489 -X2Jib3g= 60490 -w7xuc3Q= 60491 -LmZvbnRTaXpl 60492 -IEJPT0xFQU4= 60493 -ZGl2aWRl 60494 -IFNsb3Zlbg== 60495 -dWNlcg== 60496 -2ZI= 60497 -c3R1Yg== 60498 -IG5hdmlnYXRpbmc= 60499 -OmFuaW1hdGVk 60500 -X05PVw== 60501 -X3ZlY3Q= 60502 -fXsK 60503 -QCg= 60504 -IHRlbGVjb20= 60505 -IGNvbnRyYWN0aW5n 60506 -IEFzc2FuZ2U= 60507 -IGV4dHJhY3Rpbmc= 60508 -IGdyw7Y= 60509 -Y29icmE= 60510 -LkRJUw== 60511 -IGNyYWI= 60512 -IHR3aXRjaA== 60513 -IHZlcnRz 60514 -IHJlamVjdHM= 60515 -CWZvcm1hdA== 60516 -IHJlZ2VuZXJhdGlvbg== 60517 -LlN5cw== 60518 -c29sdmU= 60519 -CWRpYWxvZw== 60520 -c2hp 60521 -bWV0ZXI= 60522 -KGJlc3Q= 60523 -dmFsaWRhdG9ycw== 60524 -IG9ud2FyZHM= 60525 -IGd1cnU= 60526 -IG1vZGVyYXRvcg== 60527 -b3dpZWQ= 60528 -ZXhwZXJpbWVudA== 60529 -cnVi 60530 -IG1xdHQ= 60531 -IENhdWNhcw== 60532 -IG5hdGlvbmFsaXNt 60533 -IG1hbmdl 60534 -CUltR3Vp 60535 -L0VkaXQ= 60536 -IGluaA== 60537 -IGludGVsbGln 60538 -ZXJva2Vl 60539 -CWV4cG9ydA== 60540 -IGRpc2NyaW1pbmF0ZQ== 60541 -c3VidHJhY3Q= 60542 -IE1vb2RsZQ== 60543 -ZW5zZXI= 60544 -IEd1aWRlcw== 60545 -UkFQ 60546 -LWhvdA== 60547 -X2dycA== 60548 -LnBpY3R1cmU= 60549 -WEE= 60550 -IGluaXRWaWV3 60551 -X0NvbW0= 60552 -IG92ZXJkb3Nl 60553 -ICsKCg== 60554 -IFNpbGVudA== 60555 -c2hvd3M= 60556 -IGludGVycG9sYXRl 60557 -Rm9ybWF0aW9u 60558 -IGJpc2M= 60559 -bWFya2V0cw== 60560 -KFND 60561 -WmU= 60562 -IE5ldHdvcmtpbmc= 60563 -IGFkcmVuYWw= 60564 -IEd1bnM= 60565 -ZXRlb3I= 60566 -RGVjbGFyZWQ= 60567 -b3JnZXRvd24= 60568 -IGthcmVuYQ== 60569 -L3Bhc3N3b3Jk 60570 -X2FkZHJlc3Nlcw== 60571 -SVRFUkFM 60572 -QnV6eg== 60573 -IENvbndheQ== 60574 -KGNhc2U= 60575 -UFdE 60576 -aGVpcm8= 60577 -KGFjdA== 60578 -KioNCg== 60579 -KCkpOwoKCg== 60580 -IGFudg== 60581 -IC4uCgo= 60582 -KE1lbnVJdGVt 60583 -KG1haWw= 60584 -X3NlY3Rpb25z 60585 -CW5ldA== 60586 -IHBsdXQ= 60587 -IHdyZW5jaA== 60588 -L29iamVjdA== 60589 -IElzdA== 60590 -IFZJUw== 60591 -L3B1Yg== 60592 -YWx0ZW4= 60593 -IGd1aXRhcnM= 60594 -IGFudGliaW90aWM= 60595 -77yW 60596 -wrk= 60597 -ICIrIg== 60598 -Zm9ybXVsYQ== 60599 -IGJhYmVz 60600 -IFByb21wdA== 60601 -IGVuaW0= 60602 -L3BsYXllcg== 60603 -CXJlZg== 60604 -IGJ5xIc= 60605 -IGNvbnN1bWVz 60606 -IEhhc3Q= 60607 -IFRhbw== 60608 -ICcpKQo= 60609 -IGNsYW0= 60610 -IHRoaWdocw== 60611 -IG1vdGlm 60612 -QXBpT3BlcmF0aW9u 60613 -IFdM 60614 -Z2V0Qw== 60615 -CWZsYWdz 60616 -b2ludG1lbnRz 60617 -IGVjb25vbWljYWw= 60618 -bmVlZGxl 60619 -eGxz 60620 -cHJhY3RpY2U= 60621 -dXR6ZXI= 60622 -dGltZW9mZGF5 60623 -LW91dHB1dA== 60624 -IGZpbmRCeUlk 60625 -IEJ1ZGR5 60626 -0J7Rgg== 60627 -U2V2ZW4= 60628 -IEJhcms= 60629 -IGVudm95 60630 -X2FsZ29yaXRobQ== 60631 -5Yip 60632 -IGJhbGxpc3RpYw== 60633 -56e7 60634 -cmFkZXM= 60635 -CWRvYw== 60636 -cm9kdWNpbmc= 60637 -IEVhdGluZw== 60638 -VW5tb3VudA== 60639 -L2RhdGFUYWJsZXM= 60640 -X2JvbnVz 60641 -IGxpdHQ= 60642 -cHBz 60643 -KWxvY2FsT2JqZWN0 60644 -cGVyZg== 60645 -IEhlbHZldGljYQ== 60646 -c2h1dGRvd24= 60647 -L21s 60648 -LnRva2Vucw== 60649 -IEhhcmRjb3Jl 60650 -LHJvdw== 60651 -L2Jn 60652 -U2NhbGVy 60653 -4oCUYXM= 60654 -X2xvZ2l0cw== 60655 -4oCZaW50 60656 -CUFwcA== 60657 -SW1wbGljaXQ= 60658 -LkZwcmludGY= 60659 -RVRP 60660 -IHRlcnJh 60661 -IHBvc3Nlc3Npbmc= 60662 -LnJzdHJpcA== 60663 -LCks 60664 -PXllcw== 60665 -IFN0cmlwZQ== 60666 -Pz0= 60667 -bmV1dHJhbA== 60668 -Lmdvb2Q= 60669 -IGtlbm5lbg== 60670 -IFN1bmc= 60671 -ZmF1bHQ= 60672 -eXN0YXRlY2hhbmdl 60673 -Q2FuYWRpYW4= 60674 -JywnIi4k 60675 -IE1pdHM= 60676 -w6ZuZA== 60677 -IFNUUlVDVA== 60678 -IFVSTFdpdGhTdHJpbmc= 60679 -IENvbXBhc3M= 60680 -IC0tCgo= 60681 -IE5TTGF5b3V0Q29uc3RyYWludA== 60682 -fG1pbg== 60683 -LWFkanVzdA== 60684 -IHJlYnVpbHQ= 60685 -TElHSFQ= 60686 -L3Nl 60687 -LW1vdW50 60688 -dnBu 60689 -dmFsaWRhdGVk 60690 -KFFPYmplY3Q= 60691 -IGlnbml0aW9u 60692 -IENoYXJnZXJz 60693 -UllQVE8= 60694 -XWluaXRXaXRoRnJhbWU= 60695 -IEZsdWlk 60696 -IGNhZHJl 60697 -IG5vbWluYXRpb25z 60698 -TmVpbGw= 60699 -IEhvdQ== 60700 -IGN1cnJlbnRz 60701 -X2dlbmU= 60702 -KGlucA== 60703 -UGFyaXM= 60704 -esSZ 60705 -YWdncmVnYXRl 60706 -IGFzc29j 60707 -d2VldGVk 60708 -ZXJyYXQ= 60709 -4oCTCgo= 60710 -ICcvJywK 60711 -Zml4dHVyZQ== 60712 -IEhpZ2hlc3Q= 60713 -YW1iaWVudA== 60714 -IGNobW9k 60715 -IGNvbnRl 60716 -IHNlbnN1YWw= 60717 -IGdhcm1lbnQ= 60718 -emVycw== 60719 -IFBvd2VyZWQ= 60720 -ZG9tYWlucw== 60721 -UmV3YXJk 60722 -aW9tYW5pcA== 60723 -IGNvY2twaXQ= 60724 -b3V0ZmlsZQ== 60725 -IGJ1aWx0aW4= 60726 -IGluc2lzdGluZw== 60727 -LnZhcnM= 60728 -emlwY29kZQ== 60729 -IO+/ve+/ve+/ve+/vQ== 60730 -ZmFpbHM= 60731 -IGNvbnNvbGlkYXRpb24= 60732 -X29pZA== 60733 -UGxhbmV0 60734 -ID0iLA== 60735 -CWVs 60736 -VUlMVA== 60737 -w6R0eg== 60738 -YWZhcmk= 60739 -IE1jQ2w= 60740 -VGltZWxpbmU= 60741 -RXN0YQ== 60742 -IGZyYW0= 60743 -WUU= 60744 -IGNlcmVicmFs 60745 -T2ZNb250aA== 60746 -IFByZWdu 60747 -INC60LvQsNGB0YE= 60748 -ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgCg== 60749 -IEZyZXM= 60750 -QXBwcm92ZWQ= 60751 -LlNwZWNpYWw= 60752 -IFByb3Rlc3RhbnQ= 60753 -IGFsbGVyZ3k= 60754 -X3BjbQ== 60755 -CUNvcHlyaWdodA== 60756 -IHN1cGVyQ2xhc3M= 60757 -InN0cmNvbnY= 60758 -IE1vaGFtZWQ= 60759 -ICcvLw== 60760 -Rm9yZUNvbG9y 60761 -QXJ0aHVy 60762 -IEp1bmdsZQ== 60763 -IHZlaW5z 60764 -U2Fk 60765 -IGJhY2t1cHM= 60766 -IE9waW5pb24= 60767 -w7t0 60768 -IGludGVybWl0dA== 60769 -b2R5bg== 60770 -IENocmlzdGluYQ== 60771 -IGFuZHJl 60772 -IGV2YWN1YXRpb24= 60773 -cGFsZXR0ZQ== 60774 -aG9yc2U= 60775 -IFJlc2lkZW50 60776 -IEhhc3Nhbg== 60777 -Lk5pbA== 60778 -IGFpc2xl 60779 -IEdyb3dpbmc= 60780 -IGJsb2dpbmZv 60781 -L3NxbA== 60782 -X2lvY3Rs 60783 -U2NhbGluZw== 60784 -IE1vbmFk 60785 -X2NwcA== 60786 -IEh1dGNo 60787 -IEFwcGxlV2ViS2l0 60788 -RXhwZW5zZQ== 60789 -X0pPQg== 60790 -IHBvaW50bGVzcw== 60791 -RnJvbUJvZHk= 60792 -YW50YWw= 60793 -IGRlcGljdGluZw== 60794 -IENFTEw= 60795 -IHJlZmlu 60796 -IENOQw== 60797 -7LmY 60798 -X2RpbWVuc2lvbnM= 60799 -IFNBTg== 60800 -IGFmdA== 60801 -IGZvb3RzdGVwcw== 60802 -Y2NvbGk= 60803 -X1BIT05F 60804 -L21hdGg= 60805 -LWtpbmQ= 60806 -IE1lYW5z 60807 -aWNoYWVs 60808 -Lmd1bmE= 60809 -IGluYXVndXJhdGlvbg== 60810 -LWRyaXZpbmc= 60811 -KGRlbGV0ZQ== 60812 -IHRvdGFsQ291bnQ= 60813 -X01D 60814 -LkV4dGVuc2lvbg== 60815 -Q29tbWVyY2lhbA== 60816 -IHpJbmRleA== 60817 -PEN1c3RvbWVy 60818 -Imc= 60819 -LXNoYXJl 60820 -IHBhY3Q= 60821 -YWdhcmE= 60822 -IFNJTA== 60823 -X21vZGVz 60824 -IE1vbGVjdWxhcg== 60825 -IHN5c3RlbWF0aWNhbGx5 60826 -PEc= 60827 -X3Njcg== 60828 -IE9ybw== 60829 -YXNlcnM= 60830 -IGJpYw== 60831 -IGRlc3Ryb3lz 60832 -UElQRQ== 60833 -LlN0YXJ0UG9zaXRpb24= 60834 -IGPhu6dh 60835 -aXJleg== 60836 -LkJ1bmlmdQ== 60837 -X0Z1bmN0aW9u 60838 -IHPDvA== 60839 -X2Z1dHVyZQ== 60840 -IFdlYWx0aA== 60841 -IE5hdHVyYWxseQ== 60842 -5oC7 60843 -X3llcw== 60844 -IGFicnVwdGx5 60845 -U3RyaW5nRW5jb2Rpbmc= 60846 -IENHUG9pbnRNYWtl 60847 -IHpo 60848 -IGltcGVyc29u 60849 -IHBpdm90YWw= 60850 -IFNvbWFsaWE= 60851 -IHNlZ21lbnRhdGlvbg== 60852 -X0FOQUw= 60853 -IExvZ2luQ29tcG9uZW50 60854 -Q29uc3VsdA== 60855 -IHRydW5jYXRlZA== 60856 -XSI7Cg== 60857 -LmdldENvbmZpZw== 60858 -IGludGVybnNoaXA= 60859 -QmFieQ== 60860 -6rCc 60861 -IHN0cmVuZ3RoZW5lZA== 60862 -X01J 60863 -YmFza2V0 60864 -IG5pY2h0cw== 60865 -IFRWcw== 60866 -IFNoYW4= 60867 -44K1 60868 -cmFjdXNl 60869 -LlJlTFU= 60870 -L2ludGVyZmFjZXM= 60871 -IGdldEl0ZW1Db3VudA== 60872 -IHJldGlyaW5n 60873 -IHNwZWNpYWxz 60874 -IGVudGl0eU1hbmFnZXI= 60875 -YmVsaWVm 60876 -IHNvbGRlcg== 60877 -ZGF1Z2h0ZXI= 60878 -aWprbA== 60879 -IHV0aWxpemVz 60880 -LmZpeGVk 60881 -U1U= 60882 -IGRyYXN0aWM= 60883 -IGhhY2tz 60884 -Z3J1bmQ= 60885 -IE1V 60886 -IFN0YXJ0ZXI= 60887 -LkNvbXBvbmVudHM= 60888 -X21vdG9y 60889 -R29sZGVu 60890 -IGxvZGdl 60891 -ICkpOw== 60892 -IENvcmludGg= 60893 -0LjRh9C10YHRgtCy0L4= 60894 -w7NuaWNv 60895 -Z3JlU1FM 60896 -IEZsdWVudA== 60897 -IG1hcmM= 60898 -LkxvYWRTY2VuZQ== 60899 -Lkdyb3Vwcw== 60900 -IGVyaA== 60901 -IEF1dHVtbg== 60902 -U3RvcHBlZA== 60903 -IGl0YWxpYW5v 60904 -IG1pbmlvbnM= 60905 -IEFzc2VydGlvbnM= 60906 -IG11eA== 60907 -QnU= 60908 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 60909 -CXVw 60910 -cmVhZHlzdGF0ZWNoYW5nZQ== 60911 -X01ldGE= 60912 -IGN1cnJlbnREYXRl 60913 -IENoYXBtYW4= 60914 -VW5kbw== 60915 -U2Vhbg== 60916 -YXBy 60917 -IHBhcm0= 60918 -X2ljb25z 60919 -IFN0YQ== 60920 -w6F6 60921 -IHN1YmRpdmlzaW9u 60922 -IGFsdGVyaW5n 60923 -UE5H 60924 -cG9uZW50aWFs 60925 -IHBvc3RncmVz 60926 -IEJEUw== 60927 -LWV4aXN0ZW50 60928 -IEJyYWRmb3Jk 60929 -IE9NWA== 60930 -X1dISVRF 60931 -X1BST0dSQU0= 60932 -cWM= 60933 -IHR5cGluZ3NTbGlua3k= 60934 -IFBpY3M= 60935 -X01FVEE= 60936 -SVRURVI= 60937 -X3N1YnNjcmlwdGlvbg== 60938 -SVJPTk1FTlQ= 60939 -IEh5dW5kYWk= 60940 -KCk7CgoKCg== 60941 -INiz 60942 -IGphYw== 60943 -IGVsaW1pbmF0ZXM= 60944 -KX0pOwo= 60945 -IGNvbXByZW5k 60946 -CWluc2VydA== 60947 -X2ZhY2Vz 60948 -Ij4k 60949 -IGViYXk= 60950 -IGNhcHRpdmU= 60951 -cGxpYW50 60952 -IENhbGN1bGF0ZXM= 60953 -b2x0YQ== 60954 -ZXN0aW5n 60955 -X3JldmlzaW9u 60956 -IG3DunM= 60957 -K20= 60958 -IiwiIiwi 60959 -V0hBVA== 60960 -IGNvbXBhc3Npb25hdGU= 60961 -aGFyZ2E= 60962 -W3JhbmRvbQ== 60963 -IG1vZHVsbw== 60964 -KHNu 60965 -IG9jY3VwYXRpb25z 60966 -Ly8vLwo= 60967 -CWJvYXJk 60968 -IEJhbGs= 60969 -d2nEhQ== 60970 -IFdpZmk= 60971 -LlByb2ZpbGU= 60972 -Om1hag== 60973 -CW1hdA== 60974 -TE9DS1M= 60975 -KGpCdXR0b24= 60976 -ICgnJA== 60977 -TXVy 60978 -5oyJ 60979 -YmJsZQ== 60980 -IGZyb2c= 60981 -LWhpZGU= 60982 -IGJyb2FkY2FzdGVy 60983 -4Lie 60984 -aGFsZWQ= 60985 -IGFtdXNpbmc= 60986 -X3ByZWRpY3Rpb25z 60987 -X2ludHI= 60988 -IGVhZ2xl 60989 -0LDRgtC10LvRjA== 60990 -IGdldExpc3Q= 60991 -cHNpbG9u 60992 -IGNoYXJhY3Rlcml6YXRpb24= 60993 -QVJEUw== 60994 -IHJlbG9jYXRpb24= 60995 -IHJ1bGVycw== 60996 -UEFZ 60997 -IERlZmluaXRlbHk= 60998 -X0FjdGlvbg== 60999 -IGNsb3N1cmVz 61000 -IGZhY3R1YWw= 61001 -b2R5bmFtaWM= 61002 -IHByZWNhdXRpb25z 61003 -bmllag== 61004 -IFBhcnRpZXM= 61005 -IFN1YmFydQ== 61006 -IGNvdXNpbnM= 61007 -YXJiZWl0 61008 -Lm1vbmV5 61009 -Z3VudGE= 61010 -KGFuZA== 61011 -Z2V0aXRlbQ== 61012 -LlN0eWxlUHJpb3JpdHk= 61013 -IHNsaWQ= 61014 -c2luZ2xldG9u 61015 -IGdhcm4= 61016 -IFBBUw== 61017 -IGRheno= 61018 -YcW8 61019 -IGJvZ3Vz 61020 -IE1vZw== 61021 -IHJpdmFscnk= 61022 -aXNvbA== 61023 -IGxhbmRtYXJrcw== 61024 -w7Fhcw== 61025 -QmVybg== 61026 -IFNhY2hz 61027 -ICIpCgo= 61028 -IGhvc3RpbGl0eQ== 61029 -X21leA== 61030 -bWVyZQ== 61031 -TW90 61032 -cGljdHVyZUJveA== 61033 -RGVmZW5zZQ== 61034 -IGFmZmlkYXZpdA== 61035 -b3RoZXJ3aXNl 61036 -LmRpcmVjdG9yeQ== 61037 -X1VuaXR5RW5naW5l 61038 -LWJsb2c= 61039 -LnNraW4= 61040 -cGhlbQ== 61041 -QXBlbGxpZG8= 61042 -ZXJjaGFudA== 61043 -W2NsYXNz 61044 -IHdhcnQ= 61045 -LiJb 61046 -YWxldXI= 61047 -L2JhY2s= 61048 -ICAgIAkgICA= 61049 -IHByZWNpcGl0YXRpb24= 61050 -IG9ic3RydWN0aW9u 61051 -IHBPYmo= 61052 -IHJ1cHQ= 61053 -VUNLRVQ= 61054 -YXll 61055 -5o6S 61056 -Z3g= 61057 -IGVjbA== 61058 -IHNlY3JlY3k= 61059 -L0hlYWRlcg== 61060 -IExlc2I= 61061 -IGxlaQ== 61062 -IEJ1bGxldGlu 61063 -IGdpdmVhd2F5 61064 -LkhvbWU= 61065 -X1JPT00= 61066 -Ilc= 61067 -IGNvd29yaw== 61068 -X3Jh 61069 -IEN5Y2xpbmc= 61070 -IFBhdw== 61071 -IHB1cGls 61072 -L2FyY2g= 61073 -IEZpbGVVdGlscw== 61074 -6aaW 61075 -cnNw 61076 -IGZyZWVkb21z 61077 -IExlYXI= 61078 -fWApLg== 61079 -IGJvd2xz 61080 -L2Jsb2Nr 61081 -X2xvZ2dpbmc= 61082 -IG1ldGhhbmU= 61083 -IGhvcm5z 61084 -IHdvbmRlcmZ1bGx5 61085 -IGFsdGVyYXRpb25z 61086 -IGV4aWxl 61087 -bHNlbg== 61088 -X3BhdXNl 61089 -X0xBTkdVQUdF 61090 -IFVTREE= 61091 -X215c3Fs 61092 -X0FNT1VOVA== 61093 -IExJRkU= 61094 -IHlvdW5nc3RlcnM= 61095 -IHJpb3Rz 61096 -W0U= 61097 -IHVuZm9yZ2V0dGFibGU= 61098 -LH0sCg== 61099 -RGlzcG9zZWQ= 61100 -IEFzc2Fzc2lu 61101 -VU5H 61102 -IE5ld3Nw 61103 -VXNlclNlcnZpY2U= 61104 -OmFsb2Fk 61105 -Kycs 61106 -IHNldHRsZXJz 61107 -IHNjcmVhbXM= 61108 -IGluY29udmVuaWVuY2U= 61109 -LlJvdGF0ZQ== 61110 -IGphcnM= 61111 -IFB1enpsZQ== 61112 -IG1lc3Q= 61113 -YXJzaQ== 61114 -IFNoYXJtYQ== 61115 -fCg= 61116 -LmRz 61117 -IFNhY3JlZA== 61118 -X2V2dA== 61119 -IGV4cHJlc3Nlcw== 61120 -IGhvY2g= 61121 -IER1Y2g= 61122 -LmNhbGxz 61123 -dGhy 61124 -IFNoZWZmaWVsZA== 61125 -LkFsZXJ0RGlhbG9n 61126 -IHJhZGljYWxseQ== 61127 -IHRyb3Vz 61128 -IHByZXZhaWxpbmc= 61129 -IFdXSUk= 61130 -4oCZbg== 61131 -ZW5zZWx5 61132 -IFllc3RlcmRheQ== 61133 -IFNpcml1cw== 61134 -IGtpbGxlcnM= 61135 -IEZGVA== 61136 -IG92YWw= 61137 -Jyk6DQo= 61138 -IOygleuztA== 61139 -b3VyYWdl 61140 -IENoZWNrYm94 61141 -V29ya2Jvb2s= 61142 -LmRlZmVy 61143 -X2Zsb29y 61144 -IGNvdW5jaWxs 61145 -IG5vcnNrZQ== 61146 -bW9pbA== 61147 -b3JlYQ== 61148 -IG1hcmtldGVk 61149 -X1NVUg== 61150 -eEFB 61151 -IHN0YWluZWQ= 61152 -ZXV0 61153 -IE1lbmc= 61154 -IGllZWU= 61155 -LmV4dGVybg== 61156 -ZWdpZQ== 61157 -IHJhcHA= 61158 -IFB5b25neWFuZw== 61159 -J2NsYXNz 61160 -TW9i 61161 -IGluaXRpYWxWYWx1ZQ== 61162 -X3dhdmU= 61163 -IGphYg== 61164 -IG1hc2N1bGluZQ== 61165 -IGFtcGxpZmllcg== 61166 -IHR0eQ== 61167 -UGF0aENvbXBvbmVudA== 61168 -X3h0 61169 -IEdGUA== 61170 -L3NlYw== 61171 -CWRpc3BhdGNo 61172 -bWFya2Rvd24= 61173 -IFNjaG4= 61174 -Ym9sZQ== 61175 -wrfCtw== 61176 -bW91c2Vtb3Zl 61177 -IGVyck1zZw== 61178 -IGFzaWdu 61179 -X21vbm8= 61180 -VG9TZWxlY3Rvcg== 61181 -IFp1 61182 -KFJlY3Q= 61183 -IEVycm9yQ29kZQ== 61184 -bGF0aW4= 61185 -YW5naWJsZQ== 61186 -dnRr 61187 -Q0dTaXpl 61188 -UG9rZW1vbg== 61189 -IGNsYXNzbWF0ZXM= 61190 -IGF0dHJhY3Rz 61191 -IFRhdHRv 61192 -dWx0YW4= 61193 -b2zDs2c= 61194 -IGhhbHRlZA== 61195 -4KSo 61196 -IEthcnQ= 61197 -IHVl 61198 -X0luaXRTdHJ1Y3R1cmU= 61199 -VGVzdENsYXNz 61200 -IEFpcmJuYg== 61201 -XyIs 61202 -IGNoYXJjb2Fs 61203 -IGlwYw== 61204 -IFN0cmV0Y2g= 61205 -LmdsaWRl 61206 -bGF0ZXNBdXRvcmVzaXppbmdNYXNrSW50b0NvbnN0cmFpbnRz 61207 -IHBvdGlvbg== 61208 -SVRUTEU= 61209 -IGNvdW50ZXJ0 61210 -X2hk 61211 -cHJlcGFyZWQ= 61212 -QWRz 61213 -IFZhbXBpcmU= 61214 -cm9ib3Rz 61215 -LkNyZWF0ZUluZGV4 61216 -U3RhdHVzTGFiZWw= 61217 -IHR1Y2tlZA== 61218 -YWbDvHI= 61219 -VXQ= 61220 -IHN3ZWF0ZXI= 61221 -X0ZO 61222 -ICAgICAgICAgICAgICAgIAk= 61223 -YXRha2E= 61224 -IGV5ZWJyb3dz 61225 -YWNvZXM= 61226 -dWRlbg== 61227 -LkxpbmVhckxheW91dE1hbmFnZXI= 61228 -IHN3YXk= 61229 -IG11bHRpbg== 61230 -KCkpKSkK 61231 -IE5TVUludGVnZXI= 61232 -IE15QmFzZQ== 61233 -UGFydG5lcg== 61234 -dXRzY2hlbg== 61235 -IENhdGVy 61236 -LnNldEJhY2tncm91bmRDb2xvcg== 61237 -IGFjY29tcGxpc2htZW50 61238 -X3Byb2JsZW0= 61239 -LmR0ZA== 61240 -IHBhZ2VOdW1iZXI= 61241 -IGphY2tldHM= 61242 -IGNyb3BwZWQ= 61243 -dWVscw== 61244 -IEhlcA== 61245 -IGNhcHBlZA== 61246 -Kk1hdGg= 61247 -X2NhbGxiYWNrcw== 61248 -IHB1YmI= 61249 -IEJydW5zd2ljaw== 61250 -LnJlc3BvbmQ= 61251 -WyJf 61252 -IGJlZGRpbmc= 61253 -aHl0aG0= 61254 -T1g= 61255 -KHNwZWVk 61256 -IHBlc3RpY2lkZXM= 61257 -IC0tLS0tLS0= 61258 -LkJsdWU= 61259 -IG5vb2RsZXM= 61260 -IEdvZXM= 61261 -IHNhdmVy 61262 -b3h5 61263 -X2NvbXBsZXRpb24= 61264 -IFN3aW5nZXI= 61265 -IGdldERhdGU= 61266 -IG1pbmRlZA== 61267 -aW50ZWdyYXRpb24= 61268 -IExvdHVz 61269 -KHN0b3A= 61270 -KCcsJyk7Cg== 61271 -IGZsb29kcw== 61272 -IFdvcmtmbG93 61273 -IGVydXB0ZWQ= 61274 -TWFjcm8= 61275 -IFNhdWNl 61276 -IGV2ZW50TmFtZQ== 61277 -XElucHV0 61278 -QnJlYWtpbmc= 61279 -CXdoZW4= 61280 -X3B3 61281 -SU5ERVI= 61282 -IFdlbGxuZXNz 61283 -IHZveGVs 61284 -IE1lbGw= 61285 -IE1FRElB 61286 -U0VOUw== 61287 -IEZ1bmRz 61288 -IE1pbGQ= 61289 -PEFycmF5 61290 -LXRoaXM= 61291 -dW1wZWQ= 61292 -L2Z3 61293 -IERiQ29udGV4dA== 61294 -V0k= 61295 -Z2lybHM= 61296 -SE9X 61297 -Jyk7Pz4K 61298 -IHRlbXB0aW5n 61299 -IHRlc3RhbWVudA== 61300 -IGJpYmxl 61301 -IGNvbnN1bHRlZA== 61302 -IEluZGV4RXJyb3I= 61303 -6KiY 61304 -IGtleXBhZA== 61305 -aXp6bw== 61306 -KG9r 61307 -IHdoYXRzYXBw 61308 -IFJlbW90ZUV4Y2VwdGlvbg== 61309 -IHRlYW1lZA== 61310 -4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU4oCU 61311 -wrss 61312 -IGdldFRpbWU= 61313 -ZGlhZw== 61314 -aXNzeQ== 61315 -IGhlZA== 61316 -IGtub3Rz 61317 -am9t 61318 -IGZ1bm5lbA== 61319 -LW1haWxz 61320 -IGV4cG9ydGluZw== 61321 -IFZM 61322 -IEthcm4= 61323 -IEJ1ZGRoaXNt 61324 -IEFsbGFu 61325 -X1JBRElVUw== 61326 -IHdvcmRpbmc= 61327 -IEZvcmdldA== 61328 -IENvcm9uYQ== 61329 -aXBoeQ== 61330 -IGxpbWJ1cmc= 61331 -dWdneQ== 61332 -IFVzZXJSZXBvc2l0b3J5 61333 -aW1pbg== 61334 -KGVsZQ== 61335 -IGxhYmVsbGVk 61336 -56S+ 61337 -IEhlcm1hbg== 61338 -LnFx 61339 -ICIpKTsK 61340 -aWViZXI= 61341 -LlRyYW5zbGF0ZQ== 61342 -cnlu 61343 -IGRlc2Vudg== 61344 -dW1k 61345 -U2ltcGx5 61346 -CW1vZGU= 61347 -UnBj 61348 -IFZhbGVuY2lh 61349 -IHN0YWZmZXJz 61350 -IHNlbHY= 61351 -IFNwaWtl 61352 -IGRlbGlj 61353 -IGVydQ== 61354 -X0RU 61355 -SnVkZ2U= 61356 -4buV 61357 -IEJhc2lu 61358 -Lm11dGFibGU= 61359 -InVybA== 61360 -IHRhcmlmZg== 61361 -IFNsZWV2ZQ== 61362 -IGZsYXJl 61363 -LmRyb3BvdXQ= 61364 -IGJyaWRlcw== 61365 -KSksDQo= 61366 -X2NvbnN0cmFpbnRz 61367 -ZGVzdHJ1Y3Q= 61368 -T3V0bGluZQ== 61369 -IGRpc2FwcGVhcnM= 61370 -X2xvY2tlZA== 61371 -IE5TTG9jYWxpemVkU3RyaW5n 61372 -Y2tl 61373 -CW51bGw= 61374 -YWRyZXNzZQ== 61375 -IHRvcHBpbmc= 61376 -IEpva2Vy 61377 -YmlzaG9w 61378 -0L3QvtGB0YLRjA== 61379 -YW5kZXJpbmc= 61380 -X2FtcA== 61381 -PXRpbWU= 61382 -X1NwYWNl 61383 -X1BVTEw= 61384 -Jz0= 61385 -IGFudGlxdQ== 61386 -IGNhY2g= 61387 -X19fCgo= 61388 -T05FUw== 61389 -0L7Rjw== 61390 -IHVucmVhZA== 61391 -LnBvbGljeQ== 61392 -b29vb29vb28= 61393 -65+s 61394 -IHVzdGVk 61395 -IFJlY2U= 61396 -IGFsbGVt 61397 -44O844K5 61398 -IFRob3VnaHRz 61399 -dmVpbGxhbmNl 61400 -aXN0cmF0ZQ== 61401 -X2xhbmU= 61402 -IGZhbWVk 61403 -LkdldE5hbWU= 61404 -IHNtb290aGVy 61405 -IFF1YWxpZmllZA== 61406 -YXplcnM= 61407 -X2dlbw== 61408 -RmF4 61409 -IE1pbmRz 61410 -IFJhaXNlcw== 61411 -IHRyYW5zY3JpcHRz 61412 -Q29udmVyc2F0aW9u 61413 -IHJlbWFya2Vk 61414 -64KY 61415 -ZGxpbmc= 61416 -IGRlcGxveWluZw== 61417 -IHNoYXJlZEFwcGxpY2F0aW9u 61418 -IGtw 61419 -Rm9udEF3ZXNvbWVJY29u 61420 -X2R1bW15 61421 -cmVpYmVu 61422 -IEphbmVpcm8= 61423 -RGlyZWN0aW9ucw== 61424 -LmdldEJlYW4= 61425 -c2Fzcw== 61426 -IGNvbW1hbmRlcnM= 61427 -dmF0aW9u 61428 -ZXJyb3JDb2Rl 61429 -IEFsbG95 61430 -LmxvY2FsaXplZA== 61431 -0JE= 61432 -IGRpc2h3YXNoZXI= 61433 -IFNvdXA= 61434 -TnU= 61435 -X0RlZmF1bHQ= 61436 -IHVuZXZlbg== 61437 -IC8+IjsK 61438 -LUJhc2Vk 61439 -IHNlYW1sZXNzbHk= 61440 -LW51bGw= 61441 -IFhD 61442 -IHN0ZXc= 61443 -KGRlbGF5 61444 -QVRPUlM= 61445 -IFdoZWVsZXI= 61446 -Ijw/ 61447 -IENoYW5kbGVy 61448 -IHJldGFsaWF0aW9u 61449 -IGJ1ZGRpZXM= 61450 -LXNpemluZw== 61451 -IEVpbnM= 61452 -IC4uLiw= 61453 -cXVldGU= 61454 -IERPQw== 61455 -IGZhbHNlbHk= 61456 -IGZsYXRz 61457 -TklDQUxM 61458 -IGxpYnI= 61459 -QmVOdWxs 61460 -aW11bGF0aW9u 61461 -CVF1ZXJ5 61462 -X3V0 61463 -IHBsYXF1ZQ== 61464 -YmlsZA== 61465 -IHNjcmVhbWVk 61466 -Lm12Yw== 61467 -LldpZGdldA== 61468 -IGRpZmZlcmluZw== 61469 -L3N1cHBvcnQ= 61470 -X1ZPTFVNRQ== 61471 -Lm5vZGVUeXBl 61472 -CVdyaXRl 61473 -IHLDs3du 61474 -Ym9va21hcms= 61475 -X0NPTk4= 61476 -IENyZWVk 61477 -IGluaGliaXRpb24= 61478 -IFJlaGFi 61479 -dXZyZQ== 61480 -IGR1bXBz 61481 -b3dlag== 61482 -X3BsYWNlaG9sZGVy 61483 -IEhXTkQ= 61484 -IGRlcm1hdA== 61485 -LmRldGFjaA== 61486 -IGZpbmFsaXplZA== 61487 -Z2VyaWVz 61488 -aWRhaw== 61489 -X3Byb2c= 61490 -IHVwZGF0ZVVzZXI= 61491 -bHlz 61492 -Lkdvb2dsZQ== 61493 -IGx1ZWdv 61494 -IGFudHM= 61495 -5qCH6aKY 61496 -IERSTQ== 61497 -0LvQtdC9 61498 -LWRi 61499 -ZXJyaWNr 61500 -X2xu 61501 -Li5c 61502 -aWtpdA== 61503 -IERpZW4= 61504 -IHBhcmFtZXRyb3M= 61505 -a2V5cHJlc3M= 61506 -IEtlcmFsYQ== 61507 -IGRyYWluZWQ= 61508 -ZsO8Zw== 61509 -IGNhcGl0 61510 -X2F1Zw== 61511 -dGFudA== 61512 -TmF2QmFy 61513 -IHJvbGxiYWNr 61514 -IGxleQ== 61515 -4LiI 61516 -IEJTUA== 61517 -IFByZWRpY3Rvcg== 61518 -IHdhZ29u 61519 -ICJ8Ig== 61520 -U2VydmU= 61521 -LkRvbmU= 61522 -IER1cmNo 61523 -UHJvdmlkZQ== 61524 -CXNjb3Jl 61525 -X09E 61526 -LndlYXBvbg== 61527 -IHVuaXZlcnNhbGx5 61528 -IGluanVuY3Rpb24= 61529 -X1NDUk9MTA== 61530 -Lk1hdHJpeA== 61531 -IE1vbmdvQ2xpZW50 61532 -YnVmZmVycw== 61533 -IGJhZGdlcw== 61534 -IHNoYXJrcw== 61535 -IFNoYXJr 61536 -TU9ERUw= 61537 -LlJFQUQ= 61538 -CXRhZw== 61539 -IHN0cnRvdXBwZXI= 61540 -RVJHWQ== 61541 -Ymlhcw== 61542 -IGFjY291bnRJZA== 61543 -IEVtbWFudWVs 61544 -IHJlc29ydHM= 61545 -IHN2bg== 61546 -d2FybmluZ3M= 61547 -X0lF 61548 -TEFT 61549 -IG51bGxh 61550 -CWFz 61551 -IGRlbWVhbg== 61552 -4oCcQXM= 61553 -QXV0aG9yaXplZA== 61554 -IHRlbmRlbmNpZXM= 61555 -LXNldHRpbmc= 61556 -IHByZWxvYWQ= 61557 -IGNubg== 61558 -4oCcTm8= 61559 -JSkKCg== 61560 -PVQ= 61561 -dXN0bw== 61562 -IEZJUkU= 61563 -cmVzZWFyY2g= 61564 -INCT 61565 -IExlc3NvbnM= 61566 -LkFwcGVuZEZvcm1hdA== 61567 -IGluaXRpYXRpb24= 61568 -IENvdXM= 61569 -YXJlcg== 61570 -cHJvamVjdGlvbg== 61571 -IFNoZWV0cw== 61572 -IEZvbGQ= 61573 -UmVkZGl0 61574 -RGVsZXRpbmc= 61575 -IHphbQ== 61576 -IE5ldXJhbA== 61577 -IEZlY2hh 61578 -IMKu 61579 -IHRhc3RlZA== 61580 -IEVuZW1pZXM= 61581 -IEpvaG5zdG9u 61582 -IGRhbmNlcnM= 61583 -IGRpc2FibGluZw== 61584 -IHBldHR5 61585 -IFdlbGQ= 61586 -Ly0t 61587 -KHNwcml0ZQ== 61588 -SUdP 61589 -YXJnb3V0 61590 -IHF1YXJ0ZXJiYWNrcw== 61591 -ZGlzcGF0Y2hlcg== 61592 -IFN1c3RhaW5hYmxl 61593 -ZW5hcmlvcw== 61594 -IFNraQ== 61595 -IGZhY3Rv 61596 -aWxsaW4= 61597 -X2V4dGVuc2lvbnM= 61598 -ybU= 61599 -Pkg= 61600 -ZWFzdA== 61601 -LmFpcg== 61602 -4oCcQnV0 61603 -T2JqZWN0Q29udGV4dA== 61604 -c3VjY2Vzc2Z1bGx5 61605 -X2xhbmQ= 61606 -IGZvbGRz 61607 -X0NPT1JE 61608 -IHN1YnBv 61609 -LmdldEFkZHJlc3M= 61610 -aW5zdHI= 61611 -TWF0ZXJpYWxz 61612 -0YPRgdGC 61613 -ZGVwb3NpdA== 61614 -LWxhc3Q= 61615 -X0dSQVk= 61616 -PWZpbmQ= 61617 -IG11dGFudA== 61618 -IGxlc2JpZW5uZQ== 61619 -bGV0Y2hlcg== 61620 -Uk9VR0g= 61621 -dXJla2E= 61622 -LmNhcHR1cmU= 61623 -IGVubg== 61624 -IChbWw== 61625 -IEZsdQ== 61626 -IHRhc2tJZA== 61627 -IEh1c3NlaW4= 61628 -LmZvbGRlcg== 61629 -IGF1c3Rlcml0eQ== 61630 -SVNUUkFUSU9O 61631 -X0ltcGw= 61632 -5rOo5oSP 61633 -IGRlY3JlZQ== 61634 -LWNoYXQ= 61635 -IGltcGxpY2F0aW9u 61636 -IGd1ZXNzZXM= 61637 -dWxrYW4= 61638 -QW5hbHl0aWNz 61639 -LnBsdXM= 61640 -Q09NTUFORA== 61641 -0LXQu9C4 61642 -wrsKCg== 61643 -X1NJVEU= 61644 -IGVxdWFsVG8= 61645 -U3VwcG9ydEZyYWdtZW50TWFuYWdlcg== 61646 -IFJlY29yZGluZw== 61647 -5a6M5oiQ 61648 -IGJhZ2dhZ2U= 61649 -IHBpdGNoZXJz 61650 -IEVo 61651 -b3F1ZQ== 61652 -CWNudA== 61653 -ID0+JA== 61654 -L2Zvbw== 61655 -SVJB 61656 -IFNhdGVsbGl0ZQ== 61657 -Ym9yYWg= 61658 -IH19Igo= 61659 -IEVuZHM= 61660 -IFNwcmF5 61661 -LHBhcmFt 61662 -LkNocm9tZQ== 61663 -KnE= 61664 -dGhvdWdodA== 61665 -aWJyYXRlZA== 61666 -IHRoaWV2ZXM= 61667 -IGJlbmVmaWNpYXJpZXM= 61668 -RW50ZXJlZA== 61669 -b3R0ZXN2aWxsZQ== 61670 -IHZldGVyaW4= 61671 -QnlJRA== 61672 -cXVpcGU= 61673 -dW1wdGlvbg== 61674 -LXVuaXQ= 61675 -RXhlY3V0aW9uQ29udGV4dA== 61676 -QHM= 61677 -IEdpb3Y= 61678 -LlRvb2xUaXA= 61679 -X2ZyaWVuZA== 61680 -KGF0dHJpYnV0ZXM= 61681 -IGR1bXBpbmc= 61682 -IEpD 61683 -X0RPQ1VNRU5U 61684 -IEFybW91cg== 61685 -KGluc2VydA== 61686 -Lkhvcml6b250YWxBbGlnbm1lbnQ= 61687 -IFFlZA== 61688 -44GE44G+44GZ 61689 -L2dpdA== 61690 -IFlZWVk= 61691 -IENhcmRpZmY= 61692 -IGFwYQ== 61693 -b3JnYW5pYw== 61694 -IFdoZXJlYXM= 61695 -IOad 61696 -IE1pYQ== 61697 -IGRlbW9saXRpb24= 61698 -IHNjYXJz 61699 -IHBhaQ== 61700 -IHJldHJpZXM= 61701 -IHJx 61702 -IERlbmlz 61703 -KFV0aWxz 61704 -IGFsbGV2aWF0ZQ== 61705 -IFBJQw== 61706 -aWR1ZQ== 61707 -IGFja25vd2xlZGdpbmc= 61708 -IC8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8= 61709 -56Gu5a6a 61710 -xKs= 61711 -XEpzb24= 61712 -LmJpbmFyeQ== 61713 -IHh0eXBl 61714 -c2lnbmFscw== 61715 -IEFwcGVhcmFuY2U= 61716 -JnI= 61717 -fXM= 61718 -Q2k= 61719 -IElsbHVt 61720 -cG9yYXRl 61721 -aG9n 61722 -IGluZGV4T2Y= 61723 -XENvbW1hbmQ= 61724 -X3BhcmFsbGVs 61725 -IFNoZXJsb2Nr 61726 -7YM= 61727 -ICIiKQ0K 61728 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v 61729 -IGNyaXRpY2l6ZQ== 61730 -IFNvYXA= 61731 -IE1hdGNoZXI= 61732 -IGdyaWxsZWQ= 61733 -KlQ= 61734 -IGFkb3Jl 61735 -dWxsaW5n 61736 -IGplZG9jaA== 61737 -X3JlZnM= 61738 -bGVhbnVw 61739 -IEpBWEI= 61740 -IHJvc2Vz 61741 -IExpYW0= 61742 -c2l6ZWk= 61743 -IGdldGNoYXI= 61744 -IHRhcmRl 61745 -LXRvb2x0aXA= 61746 -IHF1YWxpZmllcg== 61747 -IEludGVybWVkaWF0ZQ== 61748 -X1dpbmRvdw== 61749 -IE1hbHRh 61750 -RGlzY29ubmVjdA== 61751 -ZXdoZXJl 61752 -Q2FtcG8= 61753 -IGlycmF0aW9uYWw= 61754 -bGVkbw== 61755 -IERO 61756 -QVJHVg== 61757 -IG91dHJv 61758 -IHRoaXJ0ZWVu 61759 -Sm9zZXBo 61760 -TUFS 61761 -L2ds 61762 -SmVzcw== 61763 -IFBzeWNoaWF0 61764 -IHBhZGRpbmdCb3R0b20= 61765 -LWxvb3A= 61766 -L2ZvbnRz 61767 -X3NlZW4= 61768 -VGVhbXM= 61769 -UmVhY3RET00= 61770 -KG1hbg== 61771 -KHhwYXRo 61772 -LmdldFNpbXBsZU5hbWU= 61773 -Pigq 61774 -IFB2dA== 61775 -IGVsZGVycw== 61776 -IHBpZXM= 61777 -LnVzZXJBZ2VudA== 61778 -LXJlZ2lvbg== 61779 -IEdyZWVrcw== 61780 -KGZyYWdtZW50 61781 -c3R1 61782 -IGNvdW5jaWxz 61783 -IHN0YW1pbmE= 61784 -IEdvZGRlc3M= 61785 -6KW/ 61786 -IHBoaWxvc29waGVycw== 61787 -IHBlcnNvbmU= 61788 -IExvc2U= 61789 -IENMUg== 61790 -IERvY3M= 61791 -IHNvYWs= 61792 -IEhPTERFUg== 61793 -IGJlbGxz 61794 -aGFzaENvZGU= 61795 -UkFURQ== 61796 -X1dFSUdIVA== 61797 -aW5vdXM= 61798 -ZW5kcmE= 61799 -b3Bob2JpYw== 61800 -IHByb3Nl 61801 -IGZpbmVseQ== 61802 -L29hdXRo 61803 -KHNwYWNl 61804 -YWRnZQ== 61805 -IE1hbWE= 61806 -IHN0cmluZ0J1ZmZlcg== 61807 -IHN0aW50 61808 -IG1pc21h 61809 -IHZpbGxhaW5z 61810 -IENyaW1lYQ== 61811 -IGRpcGxvbWE= 61812 -INC/0L7RgdC7 61813 -IEJlYQ== 61814 -KGpvaW4= 61815 -IO2VtA== 61816 -Q0hBVA== 61817 -cGVyaW5n 61818 -IENyb3M= 61819 -IG1vbmtleXM= 61820 -IHByZWRz 61821 -eWxh 61822 -LCws 61823 -IHZpYnJhdG9y 61824 -IE5V 61825 -5YWI 61826 -ZmFudA== 61827 -emV0 61828 -IGJpZXRldA== 61829 -dW5mdA== 61830 -c3dvcnRo 61831 -LkZsb3c= 61832 -IHBzeWNoZWQ= 61833 -IENvbnRpbmVudGFs 61834 -PnQ= 61835 -IHF1aWx0 61836 -LlVQ 61837 -IGV4cGFuc2l2ZQ== 61838 -RGlzcG9zZQ== 61839 -KGxhbmd1YWdl 61840 -Q2Fwcw== 61841 -X1pPTkU= 61842 -IHJlY3ljbGU= 61843 -IE1hbmFnZWQ= 61844 -Y3VycmVudENvbG9y 61845 -LmJyb2FkY2FzdA== 61846 -c2lnbklu 61847 -LnByb20= 61848 -bGx1 61849 -dWVibG8= 61850 -IHB1bmNoZXM= 61851 -IGF1dG9tYXQ= 61852 -IGFzc2lnbmluZw== 61853 -IGNyZWF0ZVVzZXI= 61854 -IEFsbGllZA== 61855 -IGNvbmR1Y3Rvcg== 61856 -gqg= 61857 -IHNhZGRsZQ== 61858 -IGRuaQ== 61859 -b21lZGljYWw= 61860 -LVdlc3Q= 61861 -UG9zaXRpdmVCdXR0b24= 61862 -IGl0YWxpYw== 61863 -P1s= 61864 -KHRyaWdnZXI= 61865 -IGVsZXBoYW50cw== 61866 -IjoiIiwi 61867 -IGNhbGliZXI= 61868 -cmFmdGVk 61869 -ZGlnaXRz 61870 -IG1hcnNoYWw= 61871 -bWlsbGlzZWNvbmRz 61872 -bWFya2Vycw== 61873 -bW9t 61874 -L3BsYWNl 61875 -IGhvbGlzdGlj 61876 -OnQ= 61877 -Iyw= 61878 -IGJvdG8= 61879 -IG5hdXNlYQ== 61880 -IFNob290aW5n 61881 -aXRlY2g= 61882 -IHRleHRTdGF0dXM= 61883 -PENsYXNz 61884 -IERlc2NyaWJl 61885 -IGJ1ZmZldA== 61886 -Z2ls 61887 -IGxvZ2l0cw== 61888 -c3RkY2FsbA== 61889 -bW9kcw== 61890 -IFNrdWxs 61891 -IEJhcmU= 61892 -aG9wZQ== 61893 -IEludHI= 61894 -RmFpcg== 61895 -CXB0 61896 -IGFjb21wYW5o 61897 -IGZraw== 61898 -X3JwYw== 61899 -SW5zdGFsbGVk 61900 -X2Fucw== 61901 -LmdldE1pbnV0ZXM= 61902 -4oCmIgoK 61903 -LXRocmVhZA== 61904 -IHByZXNjaG9vbA== 61905 -QUlMUw== 61906 -IGRpZmZpYw== 61907 -KGNvbnZlcnQ= 61908 -IE5hdGg= 61909 -IERPSg== 61910 -IHJlZ2ltZXM= 61911 -IGVudGh1c2lhc3Q= 61912 -IHdhcnJhbnRpZXM= 61913 -IGZhc2NpbmF0ZWQ= 61914 -X2JpbmRpbmc= 61915 -X05vdA== 61916 -b2Z0ZW4= 61917 -X1JX 61918 -L21haWw= 61919 -IHRpdGxlTGFiZWw= 61920 -IHZpbGxhZ2Vycw== 61921 -IEppYW5n 61922 -IHN3YWdnZXI= 61923 -LlJvd0luZGV4 61924 -X2ltZ3M= 61925 -cmFweQ== 61926 -VkVSQUdF 61927 -LlVw 61928 -IG5vb3A= 61929 -Y2lv 61930 -CVNU 61931 -IGRlY3JlbWVudA== 61932 -IG1hZ25lc2l1bQ== 61933 -X3JvdGF0ZQ== 61934 -U2l0 61935 -IG5pZXV3ZQ== 61936 -IHRlcm1lZA== 61937 -7ZWp64uI64uk 61938 -IHVyZw== 61939 -X3RvdWNo 61940 -IHN3YXJt 61941 -IGNsYXZl 61942 -dGhlc3Q= 61943 -IExhZg== 61944 -SFg= 61945 -IEh1bGs= 61946 -IHBsYWludGV4dA== 61947 -IFNvZmE= 61948 -Z2V0U2Vzc2lvbg== 61949 -TGVk 61950 -IGVjb3N5c3RlbXM= 61951 -aGVp 61952 -IEtpbGxz 61953 -IGh1c2JhbmRz 61954 -0YXRgNCw0L0= 61955 -KGRvbQ== 61956 -X3RpbGVz 61957 -TmliTmFtZQ== 61958 -IGRvbmF0aW5n 61959 -LmFjYw== 61960 -IGxpZmVzcGFu 61961 -LmJu 61962 -X1JHQ1RY 61963 -5qU= 61964 -YW5zZW4= 61965 -IG1vZGVsbGluZw== 61966 -TGF5b3V0UGFyYW1z 61967 -IG9uQ2hhbmdlVGV4dA== 61968 -cnNh 61969 -LWxvY2F0aW9u 61970 -LlBl 61971 -KGJ1cw== 61972 -KHNvbmc= 61973 -IHByb2R1aw== 61974 -IFNIT1VMRA== 61975 -IENK 61976 -IHNvcw== 61977 -IEhvbWVDb250cm9sbGVy 61978 -LmxvYWRlZA== 61979 -KERvY3VtZW50 61980 -LnNvY2lhbA== 61981 -dGlsZXM= 61982 -IGxhbWU= 61983 -PWRm 61984 -LnBhcnNlTG9uZw== 61985 -IHByYWM= 61986 -IGRldG94 61987 -IFZF 61988 -IHB1bnRvcw== 61989 -IGRvY3Ry 61990 -IGFuY29y 61991 -Q0FQRQ== 61992 -IGNtYg== 61993 -54S2 61994 -Kiki 61995 -Oi8vLw== 61996 -VmFsdWVUeXBl 61997 -IG1vcnRnYWdlcw== 61998 -O3E= 61999 -IFJvY2tldHM= 62000 -c3BvcnQ= 62001 -VUdD 62002 -Y3Rz 62003 -44KB 62004 -aWV1cg== 62005 -IEFwcGVhbA== 62006 -KG5i 62007 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8= 62008 -SU1BVElPTg== 62009 -IENyZXM= 62010 -IE1hbmlw 62011 -Q2F1c2U= 62012 -YXR5cGVz 62013 -bWFudWZhY3R1cmVy 62014 -Iy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 62015 -IHNwb3I= 62016 -ZXNvbg== 62017 -IHB1bmNoZWQ= 62018 -IGJvb2ttYXJrcw== 62019 -IEJ1bGs= 62020 -Q29tcGxldGVMaXN0ZW5lcg== 62021 -IFRhbGtpbmc= 62022 -IEVybmVzdA== 62023 -IHJ1YmJpc2g= 62024 -a2lsbHM= 62025 -IERFRklO 62026 -IG5laWdoYm91cmluZw== 62027 -YXJsbw== 62028 -IFBDQQ== 62029 -CW1hdHJpeA== 62030 -bG9r 62031 -IGF0bGFz 62032 -IEd1cg== 62033 -IHd5bg== 62034 -LW5lZ2F0aXZl 62035 -IHR1bA== 62036 -IHJlbGlj 62037 -IFZvbHRhZ2U= 62038 -IFByZWlz 62039 -IEpOSUNBTEw= 62040 -IFBNSUQ= 62041 -YWtldA== 62042 -CWF0dHI= 62043 -IGV0aXF1 62044 -IE1K 62045 -IEdtYWls 62046 -Y2xy 62047 -X2V4ZWN1dGlvbg== 62048 -6ZSu 62049 -cG9zaXRvcg== 62050 -LmFm 62051 -TnI= 62052 -R2VvcmdpYQ== 62053 -VG9wb2xvZ3k= 62054 -IHBlcmNow6k= 62055 -IG11c2xpbQ== 62056 -IGVwaWRlbWk= 62057 -IHNhYm90 62058 -YWN0dXM= 62059 -IOuMgA== 62060 -IElPRXJyb3I= 62061 -LmVzdA== 62062 -cHJlZnM= 62063 -IEtyaXNo 62064 -LlJlYWRLZXk= 62065 -TkFTQQ== 62066 -dcOnw6Nv 62067 -X0Ri 62068 -dW1lcmF0b3I= 62069 -V2lkZQ== 62070 -KHN0YXRlbWVudA== 62071 -LmVuZHBvaW50 62072 -Li4uLi4uLi4u 62073 -IFsq 62074 -c3RyZWFtcw== 62075 -bXRpbWU= 62076 -UHg= 62077 -YXRy 62078 -IHRwbA== 62079 -Um9tYW4= 62080 -IHNjZW5pYw== 62081 -Lm56 62082 -IFNlY29uZHM= 62083 -c3VibWVudQ== 62084 -IOyLpO0= 62085 -X2J1bmRsZQ== 62086 -IGRlxJ8= 62087 -IFNpc3RlcnM= 62088 -cHJlZmVyZW5jZXM= 62089 -IHBvcnRh 62090 -QWR2aXNvcg== 62091 -bWF4TGVuZ3Ro 62092 -IEdSRUFU 62093 -X18oCg== 62094 -b2xlc3Q= 62095 -IExhYmVscw== 62096 -IGVuZmVy 62097 -ICAgICAgCgo= 62098 -IFRoZWZ0 62099 -X0ZJTEw= 62100 -IFdpc2U= 62101 -KWFwcGxpY2F0aW9u 62102 -dW5hbWk= 62103 -PigpKQo= 62104 -QUREUkVTUw== 62105 -QlNU 62106 -ZXR6dA== 62107 -IFFncw== 62108 -U2Vuc2U= 62109 -RXhjZXB0aW9uSGFuZGxlcg== 62110 -IENodQ== 62111 -LmdldE93blByb3BlcnR5 62112 -IGV4ZXJjaXNlZA== 62113 -aW90aWM= 62114 -IFJlbGVhc2Vz 62115 -IHBpbnRlcmVzdA== 62116 -b2xpZQ== 62117 -aXNvZnQ= 62118 -IHNlcXVlbmNpbmc= 62119 -IHBhZHJl 62120 -XSkpOw0K 62121 -KHJhZGl1cw== 62122 -Lm1lZA== 62123 -YWludGllcw== 62124 -Lk9iamVjdE1vZGVs 62125 -IGVtcGxl 62126 -IHNlZ3Vybw== 62127 -U3RhcnM= 62128 -IHF1YWxpdGF0aXZl 62129 -bGVtbg== 62130 -4bux 62131 -PiIpLg== 62132 -IGd4 62133 -LWNlcnQ= 62134 -IEFTVE0= 62135 -IGZ1bGxuYW1l 62136 -IHRlbGVtZXRyeQ== 62137 -IENhbWJvZGlh 62138 -X3Vs 62139 -IENsYXJl 62140 -Q1VTVE9N 62141 -UUM= 62142 -IFVucw== 62143 -IEhUVFBT 62144 -IFBhcmtpbnNvbg== 62145 -YW5jeWJveA== 62146 -JywnLg== 62147 -VHVl 62148 -LmdldExhc3Q= 62149 -IGFiaQ== 62150 -xIVk 62151 -QXN0 62152 -IEVkaXRpbmc= 62153 -LlVuaXR5 62154 -am1w 62155 -IG1hdHM= 62156 -IHNoYXJlZFByZWZlcmVuY2Vz 62157 -Q2FwdGFpbg== 62158 -LnBhZ2VTaXpl 62159 -IHJ0bA== 62160 -IGFubWVsZA== 62161 -UnVudGltZU9iamVjdA== 62162 -IGRlbWFuZGU= 62163 -KCI7 62164 -c2VpdGU= 62165 -LWhlYWRlZA== 62166 -IEtyYQ== 62167 -IEZPTlQ= 62168 -YFw= 62169 -Q2xhc3NOb3RGb3VuZEV4Y2VwdGlvbg== 62170 -LmF2Zw== 62171 -YXRpY2Fs 62172 -QWo= 62173 -IHBlcm1pdHRpbmc= 62174 -UHJvag== 62175 -RVJSUQ== 62176 -IGNyZWFtcGll 62177 -IEJ1eWVy 62178 -LW1vZHVsZXM= 62179 -IFN1bmRheXM= 62180 -fGAK 62181 -IGRheXRpbWU= 62182 -ICso 62183 -IGdsaXRjaA== 62184 -IE9wZXJhbmQ= 62185 -IHRveGlucw== 62186 -aW55YQ== 62187 -RE5T 62188 -IFNhcw== 62189 -Q2FrZQ== 62190 -IE5hdGlvbmFscw== 62191 -LmFkZFRv 62192 -IHNpbmtpbmc= 62193 -IGNvbXByZWhlbnNpb24= 62194 -IHNjb3I= 62195 -YWdlbWVudHM= 62196 -IHRhcmQ= 62197 -IG1hcmNoaW5n 62198 -IE1UVg== 62199 -IHNhbmU= 62200 -Q3JlYXRlSW5mbw== 62201 -4bqv 62202 -IGVuZEluZGV4 62203 -CWxheW91dA== 62204 -IOWQjQ== 62205 -U0lURQ== 62206 -IFRIRVJF 62207 -IFt7Jw== 62208 -b3BhdGhpYw== 62209 -IHRyYW5zbWl0dGVy 62210 -L2JvZHk= 62211 -IHB1bmQ= 62212 -IENsb3Npbmc= 62213 -IHNldGF0dHI= 62214 -IGJvdW5kZWQ= 62215 -QXRsYXM= 62216 -c3VtaW5n 62217 -KHRpbWVz 62218 -cGFyZXI= 62219 -eW5vbQ== 62220 -ZmVpdA== 62221 -IGZyZW0= 62222 -LWxlZw== 62223 -IEJyYXM= 62224 -PiM= 62225 -IOy2nOugpQ== 62226 -IElOU1RBTkNF 62227 -IENvdWNo 62228 -X2hvc3Rz 62229 -bGlrZWxpaG9vZA== 62230 -Lk1hcmtlcg== 62231 -IE1hc2tz 62232 -IGNlcmVhbA== 62233 -dXRpbGl0aWVz 62234 -IGVsZW1lbnRhbA== 62235 -IGRpc3RvcnRlZA== 62236 -aW5hY3RpdmU= 62237 -Y3J5 62238 -V0w= 62239 -VVBQT1JURUQ= 62240 -LlRocm93cw== 62241 -L3NjaGVtYQ== 62242 -c2VyaWU= 62243 -LiInLA== 62244 -IEJlbmVkaWN0 62245 -LXBpY2tlcg== 62246 -aWdncw== 62247 -IFBpcmF0ZQ== 62248 -5ZGo5pyf 62249 -IFRoZW1h 62250 -IFNvdXRoYW1wdG9u 62251 -IGFycmF5V2l0aA== 62252 -IFBhdWxh 62253 -IHByZWRpY3Rvcg== 62254 -LUFzcw== 62255 -LnVzZXJpZA== 62256 -IHBlcmk= 62257 -IGV4YWdnZXJhdGVk 62258 -dXJhdGU= 62259 -YXJzZWlsbGU= 62260 -IENvbmNlbnQ= 62261 -IFBpaw== 62262 -IEBfOwoK 62263 -IGZvcm1hdGlvbnM= 62264 -IGRlbm9taW4= 62265 -Ii8+Lgo= 62266 -ZW5kZWRvcg== 62267 -IHBhbmNyZQ== 62268 -IGFtdA== 62269 -IG9uUmVzdW1l 62270 -b25EZWxldGU= 62271 -IEJDSA== 62272 -KSgi 62273 -bW92ZW1lbnQ= 62274 -IHBvdGFzc2l1bQ== 62275 -PCEtLVs= 62276 -IG1lbWVz 62277 -X1NFVFVQ 62278 -X2dhbW1h 62279 -IGNvbG9yV2l0aFJlZA== 62280 -IGdyYXZlcw== 62281 -IHN0YXR1dGVz 62282 -IGFxdWFyaXVt 62283 -IExhbWFy 62284 -IHhBeGlz 62285 -V2VicGFja1BsdWdpbg== 62286 -X2ZvbGQ= 62287 -Lmdlbw== 62288 -IEZlZXQ= 62289 -LXNwZWFraW5n 62290 -6aKd 62291 -X2Nvcw== 62292 -IEF2ZWM= 62293 -YW5zdA== 62294 -IEVFUFJPTQ== 62295 -IGRlYWxlcnNoaXA= 62296 -IFVudGVybmVobWVu 62297 -LEludGVnZXI= 62298 -IMOqdGVz 62299 -LmB8YAo= 62300 -dmluZQ== 62301 -IEtuaWZl 62302 -X3ZlcnRpY2Fs 62303 -LkRvd25sb2Fk 62304 -IG92ZXJzaXplZA== 62305 -bGlk 62306 -IHBpbGxhcg== 62307 -Y2F1Z2h0 62308 -IGZsYWdnZWQ= 62309 -KHJvdXRlcg== 62310 -KFJFRw== 62311 -IGJhcmJlY3Vl 62312 -YnJvd3Nl 62313 -IEZpdHpnZXJhbGQ= 62314 -INC/0YDQvtCy 62315 -aXJpZQ== 62316 -IGVyc3Rl 62317 -ZWxpYg== 62318 -X1BSRVNT 62319 -IGhlYWxlZA== 62320 -IGhhdXQ= 62321 -PnhwYXRo 62322 -IFdlbg== 62323 -Z3J1bnQ= 62324 -LktleXdvcmQ= 62325 -LWhhc3BvcHVw 62326 -bnc= 62327 -U1o= 62328 -Z2FiZQ== 62329 -SW50ZXJhY3Rpb25FbmFibGVk 62330 -cHJlY2g= 62331 -IHByaW1v 62332 -c3RyaXBl 62333 -YWx0ZWQ= 62334 -X0JPUkRFUg== 62335 -ZmluZEJ5 62336 -X2Fubm90YXRpb24= 62337 -V2ViU29ja2V0 62338 -QnVy 62339 -IGRpcGxvbWFjeQ== 62340 -KHRk 62341 -IFNpbXBs 62342 -ZGV0ZWN0 62343 -cGVyZm9ybWFuY2U= 62344 -IGNhcmJvaHlkcmF0ZXM= 62345 -L2lvdXRpbA== 62346 -LS0tLS0tKw== 62347 -X3Ny 62348 -bWVldGluZw== 62349 -IHwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo= 62350 -X1Zhcg== 62351 -IHJvdmVy 62352 -IGNhc2k= 62353 -IE1hdGNoZXM= 62354 -cXJ5 62355 -X0JPT0s= 62356 -IHByZXN1bWVk 62357 -IE3DqXQ= 62358 -L2l0ZW1z 62359 -IENyZWRlbnRpYWxz 62360 -XSkuCg== 62361 -IEthcmRhc2g= 62362 -QWRtaW5pc3Ry 62363 -IFNsb3Zhaw== 62364 -KCcsJykK 62365 -IGNvbnF1ZXN0 62366 -UGVyc2lzdA== 62367 -IERyYWlu 62368 -Ymlq 62369 -IGRvdg== 62370 -IHPDuGdlcg== 62371 -V29uZGVy 62372 -QVNFVA== 62373 -W21pbg== 62374 -Z3VuYQ== 62375 -Z3Jvd24= 62376 -IH0pCgoK 62377 -QVVE 62378 -IGJlbGlldmVy 62379 -aXNlcnM= 62380 -KHNlbnQ= 62381 -SmFja3Nvbg== 62382 -IHBhaXM= 62383 -IGN1ZGFNZW1jcHk= 62384 -IGZsYXNoZXM= 62385 -YmVyZQ== 62386 -IG11bHRpZg== 62387 -IENhcmdv 62388 -RWxlbWVudHNCeVRhZ05hbWU= 62389 -KGVwb2No 62390 -IEt1bmRlbg== 62391 -UmVjb2duaXRpb24= 62392 -IFNldFZhbHVl 62393 -IFN1bnNoaW5l 62394 -QUNQ 62395 -OnN0cg== 62396 -IGFtYmlndQ== 62397 -IO2VnA== 62398 -LWxpbmVhcg== 62399 -IFdPVw== 62400 -KGN1c3RvbQ== 62401 -IGlzRW5hYmxlZA== 62402 -QkFU 62403 -X2RpYWc= 62404 -X0dVSQ== 62405 -SGVhdA== 62406 -IGFzc2VtYmxpZXM= 62407 -IENldHRl 62408 -L2NhcmQ= 62409 -IERlY2xhcmU= 62410 -IHVwaGVsZA== 62411 -IENsYXVk 62412 -LWZsb3c= 62413 -IGhvb2t1cA== 62414 -SVJR 62415 -RmF0aGVy 62416 -RGVsZXRlcw== 62417 -KSk7Ly8= 62418 -IFBUU0Q= 62419 -KTsNDQo= 62420 -ZWdhbA== 62421 -LmFycm93 62422 -IE1QVQ== 62423 -w7Nq 62424 -IG1vdGl2YXRl 62425 -IEthdGhlcmluZQ== 62426 -LmZyYW1lcw== 62427 -IHRoaQ== 62428 -PFJlc3VsdA== 62429 -LmdyYXk= 62430 -IEt1c2huZXI= 62431 -IENlbWVudA== 62432 -IEJ1cmw= 62433 -SW50ZXJ2aWV3 62434 -PSciLg== 62435 -UE9XRVI= 62436 -IENEcw== 62437 -IFsmXSg= 62438 -IGNoYW5nZXI= 62439 -Pj4sCg== 62440 -LXdl 62441 -IENMSw== 62442 -IEFkcmk= 62443 -IGNpbA== 62444 -PVg= 62445 -IHNlbmRv 62446 -IENlbHNpdXM= 62447 -YmxvY2tlZA== 62448 -T3V0T2ZCb3VuZHM= 62449 -LiE= 62450 -b3Byb2plY3Q= 62451 -YW5kZXM= 62452 -ZWRpdGluZw== 62453 -IHB1bXBlZA== 62454 -KCk7fQo= 62455 -4Ka/ 62456 -X0VWRU5UUw== 62457 -IEZyaWVkbWFu 62458 -ID4v 62459 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 62460 -IHRlbXB0YXRpb24= 62461 -IElwc3Vt 62462 -IENlcw== 62463 -IG5vdGljaW5n 62464 -X2VsZQ== 62465 -QWNjZW50 62466 -IE52aWRpYQ== 62467 -IGFtdXNlbWVudA== 62468 -IGludHJvZHVjdG9yeQ== 62469 -CXJldHZhbA== 62470 -IGxpbA== 62471 -aXJpbQ== 62472 -ZW5xdWV1ZQ== 62473 -LWhpc3Rvcnk= 62474 -IGNvdW5zZWxvcg== 62475 -VFJBTlNGRVI= 62476 -X1ZlY3Rvcg== 62477 -Y2F0ZWdvcnlJZA== 62478 -cGVyeQ== 62479 -RklMVEVS 62480 -KHJlbW90ZQ== 62481 -IHNlcGFyYXQ= 62482 -IEVtYmVkZGVk 62483 -IEJhY29u 62484 -dGVycmFmb3Jt 62485 -IHJlc3BlY3RhYmxl 62486 -aWNoYQ== 62487 -YWlj 62488 -Kydc 62489 -IHN0cmF5 62490 -0LXQvdC40Lk= 62491 -IEF1ZGl0b3I= 62492 -ZW50aWNhdG9y 62493 -IGNsb2Fr 62494 -IFVOS05PV04= 62495 -IEFtZW4= 62496 -dm94 62497 -YXN0cmVldA== 62498 -Li4uXQ== 62499 -IGAl 62500 -LXByb3BlcnR5 62501 -IFF1YWxjb21t 62502 -ZWRpdGVk 62503 -IGRpc2NyZWV0 62504 -LU11c2xpbQ== 62505 -LnJlY2lwZQ== 62506 -IHZhbmRhbA== 62507 -IHXFvHk= 62508 -c2VuaGE= 62509 -LGlz 62510 -IFBvbXBl 62511 -IEtuaWNrcw== 62512 -KCknLA== 62513 -KHRi 62514 -IEhJRA== 62515 -IHBldw== 62516 -IGNhcnJvdHM= 62517 -IHBvbGljeW0= 62518 -Lmxp 62519 -IHR3ZW50aWV0aA== 62520 -X3Byb21wdA== 62521 -c2NlbmFyaW8= 62522 -LkpGcmFtZQ== 62523 -IE1RVFQ= 62524 -IEluZGl2aWR1YWxz 62525 -dG9NYXRjaFNuYXBzaG90 62526 -w61zdGljYXM= 62527 -IkQ= 62528 -IGZvZA== 62529 -IHJpY2h0 62530 -IFphcg== 62531 -IHJlc3VycmVjdGlvbg== 62532 -IG1pbGl0YXI= 62533 -IE1hbmFnZXJz 62534 -X0dSSUQ= 62535 -bm9ubnVsbA== 62536 -QkVSVA== 62537 -T3V0cHV0cw== 62538 -ICAgIAoKCg== 62539 -IHByZWRlY2Vzc29ycw== 62540 -IGlzU2VsZWN0ZWQ= 62541 -IGN5YmVyc2VjdXJpdHk= 62542 -5YaZ 62543 -Lm1j 62544 -UXVp 62545 -IGFsbGVnaW5n 62546 -IHRpYw== 62547 -TWFudWZhY3R1cmVy 62548 -IEVuaGFuY2Vk 62549 -IEJpeg== 62550 -IHJlYWRPbmx5 62551 -w7Ru 62552 -IGx1bWJlcg== 62553 -YWVk 62554 -IHJhaW5z 62555 -cHJvdmlkZQ== 62556 -TGF0ZQ== 62557 -IHBlZGVzdHJpYW5z 62558 -amF2 62559 -QWN0aXZhdGlvbg== 62560 -J0JyaWVu 62561 -IHZhY2FuY3k= 62562 -Ly8t 62563 -IGJsYWRkZXI= 62564 -IGFnaWxl 62565 -IHN0ZWFscw== 62566 -IHJlZ2lzdHJhcg== 62567 -IGVsZWN0b3JhdGU= 62568 -R292ZXJubWVudA== 62569 -J109Ig== 62570 -YWxidW1z 62571 -ZWxlY3Rpb24= 62572 -YWJs 62573 -IE9yaWVudA== 62574 -IHBpcmF0ZXM= 62575 -IGxvb3Bo 62576 -CXJlYWRlcg== 62577 -IMO6bHRpbW8= 62578 -IFBldHJv 62579 -INGB0YLRgNCw0L3QuNGG 62580 -IHNhbXA= 62581 -aW52ZXJzZQ== 62582 -LmdyYWRsZQ== 62583 -IERvbnQ= 62584 -eG9u 62585 -IGNyZWFk 62586 -ZXJ0aWxpdHk= 62587 -cmdjdHg= 62588 -IHBvbMOtdGljYQ== 62589 -VmFsdWVDaGFuZ2Vk 62590 -QXBpUmVzcG9uc2U= 62591 -Y29tYm8= 62592 -IFVY 62593 -IGRhaGE= 62594 -J2Fu 62595 -LW15 62596 -4oCcTXk= 62597 -cGVl 62598 -bGF0bG9uZw== 62599 -XEJhc2U= 62600 -Lndpaw== 62601 -IFBPVA== 62602 -IHB1bmN0dWF0aW9u 62603 -cXVz 62604 -aW55aW4= 62605 -PW1pbg== 62606 -IG51Y2xldXM= 62607 -IGNvbmNlc3Npb25z 62608 -LmF2ZXJhZ2U= 62609 -dXNlcmluZm8= 62610 -IHRhYmxlc3Bvb24= 62611 -IE5laWdoYm9yaG9vZA== 62612 -KFRocm93YWJsZQ== 62613 -PnY= 62614 -b3Z5 62615 -WFhYWFhYWFg= 62616 -aXN0aQ== 62617 -IGJhcnQ= 62618 -77u/Cg== 62619 -RW5jcnlwdA== 62620 -PWVuZA== 62621 -IGluY3Vy 62622 -IHBlcnRpbmVudA== 62623 -X01JTk9S 62624 -KSI+Cg== 62625 -Y2hpZWY= 62626 -IHZk 62627 -KGAK 62628 -dXJneQ== 62629 -YWJ5cmludGg= 62630 -IFNoYXBlcw== 62631 -IHZhZ3k= 62632 -LmRkcw== 62633 -bWVtY21w 62634 -CUl0 62635 -c2VtZXN0ZXI= 62636 -IEVtaXQ= 62637 -IGluc2Fu 62638 -IGJydXNoZWQ= 62639 -X0ZBVEFM 62640 -ImVycm9ycw== 62641 -IGRpc3J1cHRpdmU= 62642 -JW4= 62643 -IGNvbXBvc2l0aW9ucw== 62644 -IGJhY2hlY2E= 62645 -IGRpc2FncmVlbWVudA== 62646 -UHJvdGVjdA== 62647 -TElLRQ== 62648 -LkZpbGVOb3RGb3VuZEV4Y2VwdGlvbg== 62649 -IHdlaXRlcmU= 62650 -IE1vbmFjbw== 62651 -Xzw/ 62652 -IG1vZGVsZWQ= 62653 -c3RlZWw= 62654 -ZWVudGg= 62655 -IFtdKS4= 62656 -KHJlZ2V4 62657 -ZW5pZQ== 62658 -LkZsdXNo 62659 -LnBvcHVw 62660 -IE92ZXJz 62661 -LkRlYnVnZ2Vy 62662 -PmA7Cg== 62663 -bml0ZQ== 62664 -LnF1b3Rl 62665 -IGNvZw== 62666 -IHdha2Vz 62667 -IFdyZXN0bGluZw== 62668 -SW50cm8= 62669 -IHNlcmRl 62670 -IHJldXNhYmxl 62671 -IENvbXBvdW5k 62672 -SW1wbE9wdGlvbnM= 62673 -CUl0ZW0= 62674 -IG51bU9m 62675 -IENIUg== 62676 -IEJvbHRvbg== 62677 -UExVUw== 62678 -Ym91bmRpbmc= 62679 -KCsr 62680 -ICIsIjsK 62681 -IEd1ZXN0cw== 62682 -IGRlcHJpdmVk 62683 -IG1lbG9keQ== 62684 -WklQ 62685 -Pj4oKQ== 62686 -IGNvbmNlZGVk 62687 -X2RpZQ== 62688 -IGpveXN0aWNr 62689 -IGFuYXRvbXk= 62690 -IFRvb2xTdHJpcA== 62691 -IEVub3VnaA== 62692 -Iio= 62693 -aW50b3No 62694 -aGFiaQ== 62695 -IFN5cmFjdXNl 62696 -IEluY3JlYXNlZA== 62697 -TXVz 62698 -LnBhdGllbnQ= 62699 -IGluY3JlbWVudHM= 62700 -IFBJWA== 62701 -IGJvb3R5 62702 -LnByaXZhdGU= 62703 -ZXJ0b2lyZQ== 62704 -IGN1dHRlcg== 62705 -IGJla2Fu 62706 -IGRyYXdlcnM= 62707 -X0FMSUFT 62708 -QW5pbWF0aW5n 62709 -X2Fuc3dlcnM= 62710 -LmF0dGFjaw== 62711 -d3JpdGVycw== 62712 -IGdhYW4= 62713 -aWtvbg== 62714 -CWNvbnRyb2xsZXI= 62715 -IGZhY2FkZQ== 62716 -k+WQjQ== 62717 -LHN0YXR1cw== 62718 -LmZl 62719 -IHBvc3Rwb25lZA== 62720 -IEZvbnRz 62721 -IEJlbmNobWFyaw== 62722 -aWRlbnRhbA== 62723 -IGNoaWxsaW5n 62724 -IEtpZXY= 62725 -IGJydXNoZXM= 62726 -LXdoZWVs 62727 -IEhpcmU= 62728 -KHByb2M= 62729 -IGNoZW1vdGhlcmFweQ== 62730 -INCx0YvRgtGM 62731 -IE5vbGFu 62732 -KGllcnI= 62733 -IEp1ZGU= 62734 -LUF1Zw== 62735 -dW1ub3M= 62736 -Y29udmVyc2F0aW9u 62737 -IEJlaGF2aW9yU3ViamVjdA== 62738 -YmF1Z2g= 62739 -IGd1aXRhcmlzdA== 62740 -Lm9mZmVy 62741 -IGFjY3VzZQ== 62742 -cGFyZA== 62743 -cmVmZg== 62744 -LlJlYWN0 62745 -IHVjaGFy 62746 -IG9mZnNldG9m 62747 -JHN0YXR1cw== 62748 -L2VtYWls 62749 -LmNvbm5lY3RlZA== 62750 -Lys= 62751 -QHFx 62752 -YXJhdmVs 62753 -IGZ2 62754 -LlBlcnNpc3RlbnQ= 62755 -ZW5zdGVpbg== 62756 -Li4uXQoK 62757 -LmdyaWRWaWV3 62758 -IEpPQg== 62759 -LScuJA== 62760 -LmxheW91dENvbnRyb2w= 62761 -IGNhcmc= 62762 -IEtvdA== 62763 -X2VxdWFscw== 62764 -IHdpdGhkcmV3 62765 -QVRFU1Q= 62766 -LWJ1dHRvbnM= 62767 -CVVQUk9QRVJUWQ== 62768 -IFVJR3JhcGhpY3M= 62769 -IFB1YmxpY2F0aW9ucw== 62770 -IElOVEVSTg== 62771 -IGV0aGFub2w= 62772 -w6RuZ2Vy 62773 -U0VORA== 62774 -CXNsb3Q= 62775 -0LvQtdC90LjRjw== 62776 -IHBhc28= 62777 -X2V4dGVuZGVk 62778 -b3J0aGFuZA== 62779 -KHNoZWV0 62780 -IHByb2NlZHVyYWw= 62781 -IGtpZG5hcHBpbmc= 62782 -Ly8tLS0tLS0tLS0tLS0tLS0t 62783 -W21zZw== 62784 -T2NjdXJyZWQ= 62785 -QWxpY2U= 62786 -IENBU1Q= 62787 -IGthdGE= 62788 -5rOo5YaM 62789 -Y2hlYXA= 62790 -aWNpdHk= 62791 -IHJlYWRpbmVzcw== 62792 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 62793 -IFNZTg== 62794 -IE1hZ2dpZQ== 62795 -cmljYQ== 62796 -IHlp 62797 -IFR3ZQ== 62798 -aWdub24= 62799 -YW5kZW4= 62800 -IGpxdWVyeQ== 62801 -IHN0YXJ0WQ== 62802 -IGF2ZW51ZQ== 62803 -QW50aA== 62804 -X2NhcHRpb24= 62805 -IFJvd3M= 62806 -wq/Cr8Kvwq8= 62807 -c2VxdWVuY2Vz 62808 -0LjRhA== 62809 -KCIvIikK 62810 -Y3JhdGU= 62811 -IFNhZ2E= 62812 -SnVk 62813 -IGZhY2V0cw== 62814 -X3NjYWxlZA== 62815 -UnVieQ== 62816 -IFBR 62817 -IGNydXM= 62818 -SXJhbg== 62819 -LnNxdWVlemU= 62820 -CWZk 62821 -IHBlcmNl 62822 -IGRhdGFw 62823 -Xl5eXg== 62824 -X1NDT1BF 62825 -IFNhbG1vbg== 62826 -IHRhaWxsZQ== 62827 -IFZhbG9y 62828 -QUdFTUVOVA== 62829 -UnA= 62830 -IEd1YXJkaWFucw== 62831 -IHJlYWRGaWxl 62832 -IG5lZ3Jv 62833 -IG9icmE= 62834 -LlBhcmNlbA== 62835 -Q0FDSEU= 62836 -cmV0Y2hlZA== 62837 -Y3Jt 62838 -cXJzdA== 62839 -b3VmbA== 62840 -7ZqM 62841 -Lm5vbQ== 62842 -c3NpZA== 62843 -IHNhZmVzdA== 62844 -LkVycm9ycw== 62845 -X3BuZw== 62846 -Q29udmVydGVyRmFjdG9yeQ== 62847 -PFNlbGY= 62848 -IHNlcGFyYXRlcw== 62849 -X2pCdXR0b24= 62850 -IG1pc3VzZQ== 62851 -ZXhjZXB0aW9ucw== 62852 -IFt7Ig== 62853 -IFBBRA== 62854 -562+ 62855 -a0h6 62856 -PWVu 62857 -IGjDoG5n 62858 -SFo= 62859 -IFhhdmllcg== 62860 -e2lk 62861 -IHN0YWlyY2FzZQ== 62862 -dGV4dGZpZWxk 62863 -L2RvY2tlcg== 62864 -KHRhYmxlTmFtZQ== 62865 -IHRlbGVjb21tdW5pY2F0aW9ucw== 62866 -b25zbw== 62867 -b2Ns 62868 -UGFyZW50cw== 62869 -L3BhcnNlcg== 62870 -LWRyb3A= 62871 -KHN0eWxlcw== 62872 -X21vZGlmaWVy 62873 -UmVxdWVzdElk 62874 -LmJyYW5k 62875 -IENvaW5z 62876 -IGt1bnQ= 62877 -Lkdy 62878 -IEhJU1RPUlk= 62879 -KGRyb3A= 62880 -QnJhZA== 62881 -IHNla3Np 62882 -X3Nkaw== 62883 -IGluc3BlY3RlZA== 62884 -cHJlZGljYXRl 62885 -LmZp 62886 -R09S 62887 -IGNvY29h 62888 -IElRdWVyeWFibGU= 62889 -LS0tPC8= 62890 -IGRlcm5pZXI= 62891 -IFVzZXJEZWZhdWx0cw== 62892 -X1RT 62893 -IGVvcw== 62894 -IGJsZW5kZXI= 62895 -IGxvdWRlcg== 62896 -U3BhbmlzaA== 62897 -bGluZXI= 62898 -XHdpZGdldHM= 62899 -IHNjaGVtYXM= 62900 -X0NBUFRVUkU= 62901 -Lm1pY3Jv 62902 -44Kt 62903 -IPCfkQ== 62904 -IGFuZGVy 62905 -YWx0dW5n 62906 -ID09Jw== 62907 -IGVuZm9yY2luZw== 62908 -IEV4aXN0 62909 -dXZ3 62910 -aXJ0c2NoYWZ0 62911 -IEdyZWF0ZXN0 62912 -IE1vc3Vs 62913 -X3Bv 62914 -IHNpbW1lcg== 62915 -IHByb2dyZXNzZWQ= 62916 -IHJvdGFyeQ== 62917 -IG50bw== 62918 -Tm9pc2U= 62919 -IGNoYXNlZA== 62920 -IGluc3RpbmN0cw== 62921 -UHVibGljS2V5 62922 -IHNuYXBzaG90cw== 62923 -IFN1cGVydg== 62924 -Lm1hYw== 62925 -IEJpYmxp 62926 -Li4uKQoK 62927 -CW9sZA== 62928 -S0VO 62929 -IENsaW0= 62930 -IFByb2dyZXNzRGlhbG9n 62931 -bGljYW50cw== 62932 -X3NsaWRl 62933 -K2g= 62934 -IGVtcG93ZXJlZA== 62935 -SW5qZWN0b3I= 62936 -IGluZmx1ZW56YQ== 62937 -IHBsYW5ldGFyeQ== 62938 -V2lsbGlhbXM= 62939 -IG1vbmQ= 62940 -ZW5hbg== 62941 -LnJhbmRvbVVVSUQ= 62942 -KFBvc2l0aW9u 62943 -IGhvbWJyZXM= 62944 -IGluc2VjdXJl 62945 -IHZlcmJz 62946 -X3JlY3RhbmdsZQ== 62947 -SU5TVEFMTA== 62948 -IFBhcnNlRXhjZXB0aW9u 62949 -X1RB 62950 -JGZpZWxk 62951 -LkltYWdlSWNvbg== 62952 -IEd1amFyYXQ= 62953 -LWxpdmVk 62954 -X3NvbWU= 62955 -IGNsaXBwaW5n 62956 -LmdldENvbXBvbmVudA== 62957 -LmNsb3Nlc3Q= 62958 -LmxpdmU= 62959 -IGluY2lk 62960 -DQoJCQ0K 62961 -IHByb2R1dG9z 62962 -X211c2lj 62963 -U3FsQ29ubmVjdGlvbg== 62964 -IFByZWRpY3Rpb24= 62965 -IFhU 62966 -LW5vdGVz 62967 -IEpld2Vscnk= 62968 -cmVtZW4= 62969 -KHJlYXNvbg== 62970 -U25hcA== 62971 -QWZmaW5lVHJhbnNmb3Jt 62972 -YW5nZWxvZw== 62973 -IGRpY3RhdGU= 62974 -IHpvc3Rh 62975 -QmFyQ29udHJvbGxlcg== 62976 -L3Nob3A= 62977 -ZWlk 62978 -LXN3 62979 -Q291cnNlcw== 62980 -Zm9udFdlaWdodA== 62981 -IEhvZmZtYW4= 62982 -X051bQ== 62983 -S1I= 62984 -IFdpbGxpZQ== 62985 -YXJrYW4= 62986 -LXNjYWw= 62987 -IGF1ZGl0aW9u 62988 -LmRpc2M= 62989 -IHR3aXN0cw== 62990 -IGRlcGljdHM= 62991 -IGJhbnlhaw== 62992 -IEtpdHM= 62993 -IEhlemJvbGxhaA== 62994 -bm9ydGg= 62995 -IEdSRQ== 62996 -w7Zn 62997 -cXVvaQ== 62998 -LXRocmVhdGVuaW5n 62999 -IHdvcm1z 63000 -IFBO 63001 -IHNleGRhdGU= 63002 -IG1vbnVtZW50cw== 63003 -TU1D 63004 -Ym90cw== 63005 -IFNETEs= 63006 -ZGVhdGg= 63007 -IHBpdHM= 63008 -X2Nob2ljZXM= 63009 -KHNvbHV0aW9u 63010 -IHByb2NsYWltZWQ= 63011 -IFFpbmc= 63012 -IHNzY2FuZg== 63013 -c3RyYXRlZ3k= 63014 -ZGVhdXg= 63015 -IEZpc2NoZXI= 63016 -X0lW 63017 -IGlud2FyZA== 63018 -RGF0ZVBpY2tlcg== 63019 -IHNld2Vy 63020 -IGV1cm9w 63021 -IGhvbWVsZXNzbmVzcw== 63022 -LlNwcmluZ0Jvb3RBcHBsaWNhdGlvbg== 63023 -IFNwYWNlWA== 63024 -IGluZm9ybWluZw== 63025 -ICch 63026 -IHBsYXN0ZXI= 63027 -SW5pdGlhbGl6YXRpb24= 63028 -LmJldGE= 63029 -IFBlcnNvbnM= 63030 -dWdnbGluZw== 63031 -IHNoYW1wb28= 63032 -IEplaA== 63033 -IHNlcnI= 63034 -IG1heFNpemU= 63035 -IHN0aXRjaGVz 63036 -W3BhdGg= 63037 -LnJldA== 63038 -IFByZXQ= 63039 -TmVpbA== 63040 -Q29udmVydGVk 63041 -IE1hemRh 63042 -UE9TSVQ= 63043 -VG9vbGtpdA== 63044 -IFJFQURNRQ== 63045 -Q3VzdG9tQXR0cmlidXRlcw== 63046 -YXJjaGl2bw== 63047 -LlBhaW50 63048 -Z2V0T2JqZWN0 63049 -SVE= 63050 -LldlYkRyaXZlcg== 63051 -IGFudGlib2R5 63052 -IExpbWE= 63053 -aW5jb3JyZWN0 63054 -RnJhY3Rpb24= 63055 -IERlYWRsaW5l 63056 -c2VuZE1lc3NhZ2U= 63057 -Lk9mZnNldA== 63058 -ZWRpbw== 63059 -INeQ 63060 -IHNtb290aGluZw== 63061 -LmJv 63062 -IENFTlQ= 63063 -ZWxhc3RpYw== 63064 -LmNoYXJDb2RlQXQ= 63065 -UmVmcmVzaExheW91dA== 63066 -QUdFRA== 63067 -KTtcCg== 63068 -IFtdKQoK 63069 -IHRhcHM= 63070 -RFY= 63071 -4oCV 63072 -IENveQ== 63073 -IG91dHdlaWdo 63074 -J2dj 63075 -XEV4Y2VwdGlvbnM= 63076 -IEdyYW1tYXI= 63077 -IEd1YXRlbWFsYQ== 63078 -IEd1cnU= 63079 -IHRlag== 63080 -IGZyaWVuZHNoaXBz 63081 -IGNvcGluZw== 63082 -KHVwZGF0ZWQ= 63083 -X2R4 63084 -QW5hbA== 63085 -LU1heQ== 63086 -IG1hdGNobWFraW5n 63087 -IGp1bnRv 63088 -UEFDS0FHRQ== 63089 -IHJlbnRz 63090 -IOiHqg== 63091 -Y2FrZXM= 63092 -44CCJywK 63093 -cmVuZGluZw== 63094 -X0ZyYW1ld29yaw== 63095 -LSk= 63096 -KHVwbG9hZA== 63097 -IG9wb3J0dW4= 63098 -IGNhdXNh 63099 -IHByb2xpZmlj 63100 -Um93Q291bnQ= 63101 -IG5hY2t0ZQ== 63102 -IFNveQ== 63103 -U2h1dGRvd24= 63104 -6Ig= 63105 -X0VYUEk= 63106 -IEhhcmJvdXI= 63107 -IHRvcmU= 63108 -XE1lc3NhZ2U= 63109 -L1U= 63110 -T01CUkU= 63111 -LnNlZ21lbnQ= 63112 -IGNvbWVk 63113 -cm9tYW4= 63114 -IHNlZ8O6bg== 63115 -U2lnbWE= 63116 -IHNraWluZw== 63117 -IFRlcnJhaW4= 63118 -IGJlbmNobWFya3M= 63119 -IEF0dGVudGlvbg== 63120 -IH0qLwoK 63121 -IGdlaWw= 63122 -IGNhcnRvb25z 63123 -IGF0dHJpYnV0aW9u 63124 -IHJvdG9y 63125 -ZW5oYQ== 63126 -IM6z 63127 -IHRyYWo= 63128 -IGPDtG5n 63129 -IHNoYWtlcw== 63130 -IENsZW1zb24= 63131 -IGJydXRhbGl0eQ== 63132 -IDsNCg0K 63133 -IGVpZ2h0ZWVu 63134 -IEF3YXJlbmVzcw== 63135 -KHJlc3Q= 63136 -IHZpb2xpbg== 63137 -X1JPVVRF 63138 -LkZpZWxkTmFtZQ== 63139 -IEFkZQ== 63140 -aXppYQ== 63141 -IEhlbG0= 63142 -IHR5aW5n 63143 -IFByb2dyZXNzQmFy 63144 -YXV0b3I= 63145 -IGxvbmRvbg== 63146 -Jnc= 63147 -Z29v 63148 -SVNUUlk= 63149 -L0NyZWF0ZQ== 63150 -IFVTSU5H 63151 -IEdY 63152 -IEVGRkVDVA== 63153 -RmNu 63154 -IEVuY3J5cHRpb24= 63155 -Q0VE 63156 -ZmluZQ== 63157 -LWFycmF5 63158 -IHB1c2hWaWV3Q29udHJvbGxlcg== 63159 -QCQ= 63160 -VXBsb2FkZWQ= 63161 -LXdyaXRl 63162 -LmdldFBhZ2U= 63163 -X2VzdGFkbw== 63164 -QU5UTFI= 63165 -IFZpZXdEYXRh 63166 -ICR7KA== 63167 -IGFsbW9uZA== 63168 -IExvZ2ljYWw= 63169 -IHNob290ZXJz 63170 -IOygnA== 63171 -IHB1ZmY= 63172 -IHVuY29tbWVudA== 63173 -IGN1c3RvbWl6YWJsZQ== 63174 -xINy 63175 -RGlyZWN0aXZl 63176 -CWlkeA== 63177 -Q2hhbGxlbmdl 63178 -IHN1bW1hcml6ZQ== 63179 -IEF2Zw== 63180 -LlVzZXJJRA== 63181 -LmRpc3BhdGNoRXZlbnQ= 63182 -IGNvb2tlcg== 63183 -IGNvbm5lY3Rpb25TdHJpbmc= 63184 -IHNocmlua2luZw== 63185 -amFk 63186 -IFRoZW1lcw== 63187 -YW5kYXRvcnk= 63188 -IGR1YmlvdXM= 63189 -IGNlcA== 63190 -c3Bpbm5lcg== 63191 -IHN1YnJlZGRpdA== 63192 -IGlpaQ== 63193 -L2NhY2hl 63194 -ZGVmZXI= 63195 -IHN1YnN0aXR1dGVk 63196 -IGd1bm1hbg== 63197 -Y2xpbmc= 63198 -IOyw 63199 -KGN0cmw= 63200 -T3JkZXJJZA== 63201 -X2VuZw== 63202 -IGZpbG1tYWtlcnM= 63203 -IGZvcndhcmRpbmc= 63204 -IHN0cmFuZGVk 63205 -IExlYW4= 63206 -IOunjA== 63207 -KFVuaXQ= 63208 -IGRpZFNldA== 63209 -bGFrZQ== 63210 -Z3JvdW5kcw== 63211 -5Zug 63212 -IHVucmVnaXN0ZXI= 63213 -IG1pbmhh 63214 -IFZlZ2Fu 63215 -CWlWYXI= 63216 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo= 63217 -b3R0bGU= 63218 -SVBD 63219 -IHByYWdtYQ== 63220 -IElJRA== 63221 -X01pbg== 63222 -JTsiPgo= 63223 -X3JhbQ== 63224 -ZHJpdmVycw== 63225 -IENoaWNr 63226 -IGNscg== 63227 -X0JVRkY= 63228 -INCy0YvQsQ== 63229 -TWVyYw== 63230 -anV2ZW4= 63231 -IHNoaW0= 63232 -0YvRhQ== 63233 -IHRoZW9yZXRpY2FsbHk= 63234 -L2ZvcnVt 63235 -IHNwaWRlcnM= 63236 -IGdvb3Nl 63237 -IFBob3Rvbg== 63238 -IHByb2ZpY2llbmN5 63239 -IENsZXJr 63240 -X2ZpZw== 63241 -Q29uY2Vybg== 63242 -KGNvc3Q= 63243 -IHJlZGQ= 63244 -LmVudmlyb25tZW50 63245 -Q3JvcA== 63246 -IOKJpQ== 63247 -eWVjdG9z 63248 -LkJhdGNoTm9ybQ== 63249 -LWNvbXA= 63250 -JGltYWdl 63251 -IE5pa29u 63252 -IGRtZw== 63253 -Wzo6LQ== 63254 -UExM 63255 -dW5jaW9z 63256 -Zm9jdXNlZA== 63257 -IHR1bw== 63258 -IGh2b3JkYW4= 63259 -IGF0dGFpbmVk 63260 -IHByb3RlY3Rvcg== 63261 -IEthbnQ= 63262 -IHNob3Jlcw== 63263 -IEV0aGFu 63264 -X3NjaG9vbA== 63265 -IG5lYXRseQ== 63266 -LlNoYXBlcw== 63267 -IE5lbQ== 63268 -aGNw 63269 -LicvJy4k 63270 -IE3DqXhpY28= 63271 -c3RydWN0dXJpbmc= 63272 -IGxha2g= 63273 -IGFkcmVzc2U= 63274 -JywnIw== 63275 -IEhhc2tlbGw= 63276 -X0VOR0lORQ== 63277 -IHJlcGVudA== 63278 -IGN1Y2s= 63279 -LkZJRUxE 63280 -IFNrZQ== 63281 -QEBAQA== 63282 -SGl0cw== 63283 -IGltcGxhbnRz 63284 -IENvbnN0aXR1dGlvbmFs 63285 -IFBIUFVuaXQ= 63286 -IHRvaWxldHM= 63287 -LmFsYnVt 63288 -5LiL6L29 63289 -CXNldFN0YXRl 63290 -KCItLS0tLS0tLS0tLS0tLS0t 63291 -LkFtb3VudA== 63292 -ZWN0dXJl 63293 -IFRob3VzYW5kcw== 63294 -TmVpdGhlcg== 63295 -IHByZXNldHM= 63296 -IEFzc3VtZQ== 63297 -KGZhY3Rvcnk= 63298 -IGxpY2s= 63299 -IGdvYWxrZWVwZXI= 63300 -PFN0YXRl 63301 -LXNlY3VyaXR5 63302 -X2ll 63303 -ZXNrdG9w 63304 -IEx2 63305 -IFN5bXBob255 63306 -LnNhbXBsZXM= 63307 -IGh5cGVydGVuc2lvbg== 63308 -xYJ1 63309 -Lmp1c3Q= 63310 -TWVuc2FqZQ== 63311 -IT0t 63312 -PFRLZXk= 63313 -IHNweWluZw== 63314 -LGRhdGU= 63315 -b3JnYW5pemVk 63316 -ICAgICAgICAgIA0K 63317 -KGN1ZGE= 63318 -X01ldGFkYXRh 63319 -dWJpc2hp 63320 -LUJlbno= 63321 -X0Fzcw== 63322 -IEVsc2VJZg== 63323 -IGxlc2lvbnM= 63324 -IFByZXN0b24= 63325 -VGVjaG5pY2Fs 63326 -IHBsYXRpbnVt 63327 -L3Bp 63328 -SW5kZXhlcw== 63329 -IHBhcmFwaA== 63330 -IG92ZXJ0aHJvdw== 63331 -aXBhdGVk 63332 -b250b2xvZ3k= 63333 -IGRlbW9ncmFwaGljcw== 63334 -IGNhbmU= 63335 -IHByb2ZpdGFiaWxpdHk= 63336 -IGVzdGFibGlzaG1lbnRz 63337 -XSY= 63338 -OmFic29sdXRl 63339 -ZW50cmFkYQ== 63340 -VHA= 63341 -IHNoYXJlaG9sZGVy 63342 -Lidf 63343 -5aaC5p6c 63344 -bnBq 63345 -dnJpcg== 63346 -IEVYRUM= 63347 -IFBvbGljaWVz 63348 -IGZlbGxvd3NoaXA= 63349 -IENHUmVjdEdldA== 63350 -X3JlY2lwZQ== 63351 -X1JFQw== 63352 -dW51 63353 -IHJvYmJlZA== 63354 -IHR1cm1vaWw= 63355 -KTo6 63356 -LnN0YXJ0RGF0ZQ== 63357 -IGV2YWN1YXRlZA== 63358 -LWVxdQ== 63359 -IGZvdXJ0ZWVu 63360 -QFNwcmluZ0Jvb3RBcHBsaWNhdGlvbg== 63361 -IOaVsOaNrg== 63362 -bmFudHM= 63363 -dGhyZW4= 63364 -U29ueQ== 63365 -REZT 63366 -LWNpZ2FyZXQ= 63367 -IGFnZ3JhdmF0ZWQ= 63368 -IG5lZGVybGFuZA== 63369 -IEZ1ag== 63370 -dWNlcw== 63371 -L3VzZQ== 63372 -dW1tZXI= 63373 -KFNURA== 63374 -6rCE 63375 -Kj4m 63376 -LnBlcmNlbnQ= 63377 -aWFudHM= 63378 -IEN0 63379 -VkFT 63380 -X1RIRU1F 63381 -IHNuaXBlcg== 63382 -X0VM 63383 -LXdvcmtlcnM= 63384 -U25vdw== 63385 -IEF1cmE= 63386 -aWVnbw== 63387 -IEdsb2I= 63388 -TmFtZWRRdWVyeQ== 63389 -X0JH 63390 -IExpdmVEYXRh 63391 -IFNlbmRNZXNzYWdl 63392 -IHJlc3BvbmRzVG9TZWxlY3Rvcg== 63393 -ZW5jZXJz 63394 -aW5zdHJ1Y3Rpb25z 63395 -KEl0 63396 -5ZG95ZGo5pyf 63397 -IEdvbWV6 63398 -Y2hhcmdlcw== 63399 -LkdlbmVyYXRlZFZhbHVl 63400 -IE1hY3Jvbg== 63401 -KFBPUlQ= 63402 -IFByb2Nlc3Nlcw== 63403 -Lm9uUmVzdW1l 63404 -IGZpZQ== 63405 -QnVpbGRlcnM= 63406 -KWdldA== 63407 -X3dhbGxldA== 63408 -IGNhbmM= 63409 -IE1vYmlsaXR5 63410 -IGFsYXJtcw== 63411 -cm9zaXM= 63412 -YW1hw7Fv 63413 -IHBpcw== 63414 -IOODuw== 63415 -U2hh 63416 -IGNvbmZlc3NlZA== 63417 -KElORk8= 63418 -KCcsJw== 63419 -X1NlcnZlcg== 63420 -IGJsYXN0ZWQ= 63421 -IEZhcm1lcnM= 63422 -cnV6 63423 -Y2tlZGl0b3I= 63424 -X0lNUExFTUVOVA== 63425 -IG1vdHRv 63426 -IENBUkU= 63427 -IHlkaw== 63428 -Qm9uZQ== 63429 -IGFkZW3DoXM= 63430 -KyIvIis= 63431 -UHJvcFR5cGVz 63432 -X1Na 63433 -LnBhaW50 63434 -LnBpeGVs 63435 -IE1lc3NhZ2VUeXBl 63436 -IHR3ZWFrcw== 63437 -YC4KCg== 63438 -VmVyaWZpY2F0aW9u 63439 -bmVjaw== 63440 -YmVycmE= 63441 -IG1pbmRmdWw= 63442 -U3Vydg== 63443 -IDotCg== 63444 -IGFueXdheXM= 63445 -IEFkbWlzc2lvbg== 63446 -YWNjZXNzaWJsZQ== 63447 -RmxhdEJ1dHRvbg== 63448 -ICInIik7Cg== 63449 -IGhhaGE= 63450 -VG9Qb2ludA== 63451 -IGJ1cmdlcnM= 63452 -Z2V0U3RhdGU= 63453 -XEhlbHBlcg== 63454 -IEZVTkNU 63455 -IEVMRU1FTlQ= 63456 -IENFUlQ= 63457 -IEFDQ09VTlQ= 63458 -Y2hhcmdpbmc= 63459 -X2NhbmRpZGF0ZQ== 63460 -X3JlY2VudA== 63461 -IEluc3RydWN0b3I= 63462 -IGRydW5rZW4= 63463 -WVNRTA== 63464 -b3JhdGl2ZQ== 63465 -IjoiIg== 63466 -IHRhZ05hbWU= 63467 -X05FRw== 63468 -IHFw 63469 -IFVuZGVmaW5lZA== 63470 -IGdyZWFzZQ== 63471 -CSAgCQ== 63472 -IGVhZ2VybHk= 63473 -VGV4UGFyYW1ldGVyaQ== 63474 -ZGlzdHJpYnV0ZWQ= 63475 -QWRtaW5pc3RyYXRvcg== 63476 -RGlzdHJpYnV0aW9u 63477 -IERlY29tcA== 63478 -IFRyYW5zZm9ybWVy 63479 -LmJ0blNhdmU= 63480 -IEdvcw== 63481 -KEVudW0= 63482 -Y2Fpcm8= 63483 -LWNp 63484 -L3JlcG9ydA== 63485 -IFBvc3Rlcg== 63486 -X2RlcGVuZGVuY3k= 63487 -IGV4cGxvaXRz 63488 -c2V0Rmxhc2g= 63489 -IHh0 63490 -IGpld2VsbGVyeQ== 63491 -IGRhaQ== 63492 -X1JBTQ== 63493 -IGJlcnJpZXM= 63494 -IGdyYW5ueQ== 63495 -RmF0YWw= 63496 -w6lhbA== 63497 -LW1vc3Q= 63498 -LlZpc3VhbEJhc2lj 63499 -IFBlbmQ= 63500 -YmVp 63501 -amFr 63502 -OyovCg== 63503 -Qm95 63504 -PlNlbGVjdA== 63505 -aW5kcmljYWw= 63506 -VGVjaG5vbG9neQ== 63507 -IEFsbGlzb24= 63508 -ZGF0YXR5cGU= 63509 -J2Nsb2Nr 63510 -IGtvc3Q= 63511 -IGJham8= 63512 -LkNvdW50cnk= 63513 -WmVuZA== 63514 -LndyYXBwZXI= 63515 -4L0= 63516 -IEZpbGlwaW5v 63517 -b2NyZQ== 63518 -U1NI 63519 -IFNBTVBMRQ== 63520 -X2luaXRpYWxpemVk 63521 -KTs/Pgo= 63522 -IHBvcm5vc3Q= 63523 -ZXNhbg== 63524 -IEN1dHRpbmc= 63525 -IG1peGVz 63526 -X2FnYWlu 63527 -IGZvcm11bGFyaW8= 63528 -W1Y= 63529 -IHRlbGVmb25v 63530 -L3Vz 63531 -IGxvYWREYXRh 63532 -LnJlZmVyZW5jZXM= 63533 -IG1hcFZpZXc= 63534 -KyJf 63535 -IFNRTGl0ZURhdGFiYXNl 63536 -aXRvbg== 63537 -Q29sdW1uVHlwZQ== 63538 -IEV2ZXJ0b24= 63539 -LlJlc3VsdHM= 63540 -L25vdA== 63541 -IGdldEZpbGU= 63542 -aGVyaXRhbmNl 63543 -IGdldEhlaWdodA== 63544 -JHVzZXJuYW1l 63545 -d2l0aGRyYXc= 63546 -Xyk7DQo= 63547 -LnV0 63548 -IFFBcHBsaWNhdGlvbg== 63549 -dXJuYWw= 63550 -LWRvd25sb2Fk 63551 -YnVyZ2Vy 63552 -cHJlY2k= 63553 -IFRoYW5rZnVsbHk= 63554 -LkVWRU5U 63555 -IGdyZWF0bmVzcw== 63556 -IGxvb3NlbHk= 63557 -IG1hc2g= 63558 -IGdlaGVu 63559 -X2FudA== 63560 -IGltcGVuZGluZw== 63561 -LmlzUHJlc2VudA== 63562 -IHN0YWlucw== 63563 -SU1T 63564 -LmJhY2tlbmRz 63565 -IGlycmlnYXRpb24= 63566 -IFRhdA== 63567 -L3Rlc3Rz 63568 -IEtpbmdzdG9u 63569 -LnRyYW5zbGF0ZXNBdXRvcmVzaXppbmdNYXNrSW50b0NvbnN0cmFpbnRz 63570 -IHZvbWl0aW5n 63571 -LXJlcXVpcmVk 63572 -IGJsYXpl 63573 -IFN0YWZmb3Jk 63574 -UklE 63575 -L2Z3bGluaw== 63576 -IGthbGU= 63577 -c29sZA== 63578 -KHByb2dyZXNz 63579 -KGNoYXJ0 63580 -IGN5c3Q= 63581 -IGRpbGlnZW5jZQ== 63582 -L21w 63583 -IGNsZXJneQ== 63584 -IEJyb3dzZXJSb3V0ZXI= 63585 -IEFQSw== 63586 -IENPTlRBQ1Q= 63587 -QmFySXRlbQ== 63588 -LURpc3Bvc2l0aW9u 63589 -IE1vdG9yb2xh 63590 -X3NhbA== 63591 -IFdvb2Rlbg== 63592 -IFRIRVk= 63593 -IGNvbW1lbnRhdG9ycw== 63594 -IGNvbW1lcmNpYWxz 63595 -PW1vZGVs 63596 -LiIpLAo= 63597 -IFBsdWdpbnM= 63598 -ZGFpbg== 63599 -aGVhZGVk 63600 -IENvb3JkaW5hdGVz 63601 -SmFuZQ== 63602 -IFByZWZlcnJlZA== 63603 -IHBvZGVtb3M= 63604 -LmlzQmxhbms= 63605 -IFN0YXA= 63606 -IHdzcA== 63607 -IENPTEw= 63608 -X2JpZA== 63609 -IHByb2Jlcw== 63610 -dWFuaWE= 63611 -KHN5bQ== 63612 -IGN1ZXJwbw== 63613 -IG1hbmlwdWxhdGluZw== 63614 -IGFtYXppbmdseQ== 63615 -LkRBWQ== 63616 -dW1wdGVjaA== 63617 -YWNvYmlhbg== 63618 -VGVybWluYXRl 63619 -IHN0YXRpb25lZA== 63620 -U2V0QnJhbmNo 63621 -U2NyZWVuc2hvdA== 63622 -ZXN0aGVzaWE= 63623 -IHdhbGtlcg== 63624 -I2Zyb20= 63625 -Y29vcmRpbmF0ZQ== 63626 -X2ludGVyZXN0 63627 -IGhlbHBsZXNz 63628 -CXB1Yg== 63629 -bmdh 63630 -X0V4 63631 -IG53 63632 -IHRleHR1YWw= 63633 -IHBsdWdz 63634 -IG1pbmlvbg== 63635 -bWFyZXM= 63636 -PD4K 63637 -QUNB 63638 -Q29tcGFueU5hbWU= 63639 -KGVj 63640 -IExhbmRzY2FwZQ== 63641 -X1BST1ZJREVS 63642 -Y3c= 63643 -lIQ= 63644 -QWNjb3VudElk 63645 -JDo= 63646 -IFBlcnNvbmFsbHk= 63647 -cHJvcGVydHlOYW1l 63648 -IEt1Yg== 63649 -J2k= 63650 -IEdpdWw= 63651 -IHByaW9yaXRpemU= 63652 -Rk9STUFOQ0U= 63653 -IFBhcmFkZQ== 63654 -KVwK 63655 -c3RkYm9vbA== 63656 -IGFsZXJ0RGlhbG9n 63657 -IExlaA== 63658 -LmNhdGFsb2c= 63659 -IHdlYmluYXI= 63660 -IGltcG9ydGVy 63661 -cHJvamVjdElk 63662 -VFlQTw== 63663 -X18NCg== 63664 -R1c= 63665 -c3VtbWVy 63666 -IHNpbmlzdGVy 63667 -LmZhaWxlZA== 63668 -IGJlc29pbg== 63669 -aXNtYW4= 63670 -REVTVA== 63671 -IG5o4bqtcA== 63672 -IG1vxbxuYQ== 63673 -X2luc3Ry 63674 -IHBhdmVk 63675 -IHByZWZpeGVz 63676 -IHJhbXBhbnQ= 63677 -IHlBeGlz 63678 -IOazqA== 63679 -X21pZGRsZQ== 63680 -IHNjaG9sYXJseQ== 63681 -IHByb3N0aXR1dGVz 63682 -IG1vcmFsZQ== 63683 -LnBlcm1pc3Npb25z 63684 -LmdldExpc3Q= 63685 -IHJlamVjdGluZw== 63686 -IGxvb3Bpbmc= 63687 -IFNwZWNpZmljYXRpb25z 63688 -IGltbWVuc2VseQ== 63689 -IE1lZGlhbg== 63690 -KGNoYWlu 63691 -IGNsaWNo 63692 -L2ZsdXR0ZXI= 63693 -YWNm 63694 -LnVybG9wZW4= 63695 -dXR0ZXJzdG9jaw== 63696 -IHNwZWN0cmE= 63697 -IGFkbWly 63698 -L21heA== 63699 -LkVtaXQ= 63700 -KHdlaWdodHM= 63701 -acSZ 63702 -SW5zdGFsbGluZw== 63703 -SnU= 63704 -IEZlbGw= 63705 -IEZSRQ== 63706 -LmRlbg== 63707 -IEJpZ0ludA== 63708 -Ij5A 63709 -ICopOwoK 63710 -IEJpb2xvZ2ljYWw= 63711 -IHBhdGVudGVk 63712 -LnBhZ2luYXRpb24= 63713 -LnJvbGw= 63714 -IER1bA== 63715 -IGRlc2Fycm9sbG8= 63716 -UmVnYXJkbGVzcw== 63717 -mOydtA== 63718 -IHJvYmU= 63719 -0J3QtQ== 63720 -IEJveWQ= 63721 -LyoqKioqKioqKioqKioqKioqKioqKioqKg== 63722 -cmVjZWlwdA== 63723 -IEFzc2lnbmVk 63724 -YXR0ZW5kYW5jZQ== 63725 -LWNob2ljZQ== 63726 -ZXRzeQ== 63727 -X2Vsc2U= 63728 -LG5leHQ= 63729 -X2V4aXN0aW5n 63730 -ICcnKSwK 63731 -IGxpYmVydGlu 63732 -dHJhaXRz 63733 -YXR0ZQ== 63734 -Q29tcGFyYWJsZQ== 63735 -IENvdg== 63736 -IEFkb2xlcw== 63737 -LHRoZQ== 63738 -IExvYWRlZA== 63739 -fHI= 63740 -PWluZGV4 63741 -IEdhc3Q= 63742 -IGluamVjdG9y 63743 -CXN0b3A= 63744 -LWdvb2dsZQ== 63745 -IGZldGFs 63746 -IGFsbG8= 63747 -eWxlZnQ= 63748 -Z2V0UGFyYW1ldGVy 63749 -4oCd4oCU 63750 -X3NlY3Rvcg== 63751 -LlV0aWxpdHk= 63752 -b3Njb3Bl 63753 -LmVhc2U= 63754 -IE1hZ25ldGlj 63755 -QXJyYXlPZg== 63756 -IGZlYXJmdWw= 63757 -IEluZmVy 63758 -IEZ1aw== 63759 -Sm9obnNvbg== 63760 -JGFycmF5 63761 -IHNhaXM= 63762 -X2NvbnRy 63763 -RGVzY3Jp 63764 -IERldGFpbGVk 63765 -X2xlYXZl 63766 -X1JPVA== 63767 -IG7DpGNo 63768 -IGthbWk= 63769 -RENBTEw= 63770 -OmVx 63771 -IG1vbms= 63772 -X29ianM= 63773 -KFNlcnZpY2U= 63774 -ZmluYW5jZQ== 63775 -IHBvZGVt 63776 -X3Jlc3RvcmU= 63777 -IGRlY29yYXRvcnM= 63778 -IGFkdmlzaW5n 63779 -INC/0LDRgA== 63780 -LnBlcm0= 63781 -IEhhaQ== 63782 -IGZr 63783 -dW50ZWVycw== 63784 -IFJUV0Y= 63785 -X2l4 63786 -QUNT 63787 -IGJyZWFrb3V0 63788 -ZGlyZWNjaW9u 63789 -IFN1bnNldA== 63790 -X2Z4 63791 -b2xrYXRh 63792 -LXJhZGlv 63793 -SGV0 63794 -LnV0aWxpdGllcw== 63795 -X2Jhc2lz 63796 -KGtpbmQ= 63797 -IENvbmM= 63798 -VGh1bWI= 63799 -IE1pY2hl 63800 -ZGVsaXZy 63801 -IGd1dGU= 63802 -IEZpbGVQYXRo 63803 -IFRyaWJl 63804 -XCIp 63805 -X2N1ZGE= 63806 -RGlmZmVyZW5jZQ== 63807 -IE1vbnN0ZXJz 63808 -IHNldFR5cGU= 63809 -LkNvbnRlbnRUeXBl 63810 -IGR1bQ== 63811 -RW52ZWxvcGU= 63812 -YWd0 63813 -IHVubG9hZA== 63814 -X2NoZWNrZXI= 63815 -IHJlc3Rv 63816 -X3Blb3BsZQ== 63817 -UHJpY2Vz 63818 -UHJvZmlsZXM= 63819 -KClc 63820 -RlVO 63821 -ICIjIg== 63822 -IFBhdHRlcm5z 63823 -IFNQRA== 63824 -X1JPV1M= 63825 -T3JpZw== 63826 -YmxhZGU= 63827 -IGzDqQ== 63828 -JWk= 63829 -Kysr 63830 -TGlmZWN5Y2xl 63831 -LS0tLS0tLS0tLS0tLS0tCg== 63832 -VGFy 63833 -VGhhbk9y 63834 -JnE= 63835 -IGNyaXRpY2lzbXM= 63836 -LXBo 63837 -RWxlbWVudEV4Y2VwdGlvbg== 63838 -X2d1ZXN0 63839 -IOu2 63840 -X0Fz 63841 -IENhcnJ5 63842 -X0JJRw== 63843 -YWtldXA= 63844 -X3JldHJ5 63845 -IG7DqWNlc3M= 63846 -IE1JU1M= 63847 -aXN1 63848 -IFNwaXJpdHVhbA== 63849 -XyRf 63850 -IHJlZmxlY3Rpb25z 63851 -PHQ= 63852 -IGZ1bsOnw6Nv 63853 -IG1vbmFyY2g= 63854 -IFBhdGVs 63855 -X3ZvbHRhZ2U= 63856 -IHJhaW55 63857 -Y291cnQ= 63858 -IHVsdHJhc291bmQ= 63859 -aU9T 63860 -X0FMV0FZUw== 63861 -V28= 63862 -X0JMRU5E 63863 -b2tzZW4= 63864 -IHRyYXZlbGVy 63865 -IGRhdGFUYWJsZQ== 63866 -c2V0Q3VycmVudA== 63867 -V29ya2Zsb3c= 63868 -LnllbGxvdw== 63869 -XSkt 63870 -QUJTUEFUSA== 63871 -X2l0ZXJhdGlvbg== 63872 -0LTRgA== 63873 -IHViaWM= 63874 -IG1lYXRz 63875 -L2Vt 63876 -IERpc29yZGVy 63877 -IGVudmlhcg== 63878 -U0VP 63879 -IGhlYXZlbnM= 63880 -X3N0dWI= 63881 -IGFkcmVzcw== 63882 -IFRyaWU= 63883 -IExpbmRzYXk= 63884 -bGVp 63885 -IHBsYXRh 63886 -LnNldHRpbmc= 63887 -IGVsZWs= 63888 -ICgkew== 63889 -QXV0b21hdGlj 63890 -IGRvd25zdGFpcnM= 63891 -UElY 63892 -aWNpb25hbA== 63893 -YWJhbA== 63894 -LXN0b3JhZ2U= 63895 -aWNoaWVy 63896 -IEFscGhhYmV0 63897 -LGxhYmVs 63898 -QAo= 63899 -IGludGVzdGluYWw= 63900 -IHZhcmE= 63901 -Lm1h 63902 -IHByb2du 63903 -IG5lcGhldw== 63904 -VGltaW5n 63905 -Y2xhc3NuYW1l 63906 -IGxvY29t 63907 -IFNhbWFudGhh 63908 -IEFjY29yZGluZ2x5 63909 -IFhDVGVzdENhc2U= 63910 -IFBsYWlucw== 63911 -IExlbmlu 63912 -bm9w 63913 -IFR5c29u 63914 -IHJlbmFs 63915 -b2luZQ== 63916 -KFRlc3RDYXNl 63917 -IExvbWI= 63918 -QmFuZw== 63919 -IHZvbHVt 63920 -X2dlbmRlcg== 63921 -IGx1dA== 63922 -IO+8 63923 -Q29uZmlndXJlcg== 63924 -IHN0cm9rZVdpZHRo 63925 -Lkh0dHBTZXJ2bGV0 63926 -fHg= 63927 -LkpTY3JvbGxQYW5l 63928 -IGNvbnNvcnQ= 63929 -LmJ1bXB0ZWNo 63930 -dHJpZGdlcw== 63931 -IGJlbmVmaWNpYXJ5 63932 -PXJlcXVpcmU= 63933 -cmVuYw== 63934 -IE9V 63935 -ZW50YXJpbw== 63936 -IHVyZ2Vz 63937 -4oCUbm90 63938 -Q2FtcGFpZ24= 63939 -ZHJl 63940 -IFJpdmVyc2lkZQ== 63941 -CXRi 63942 -IG91dHB1dEZpbGU= 63943 -IGFic3Q= 63944 -IHN0cnVjdHM= 63945 -IHJ2YWw= 63946 -XCI+Ig== 63947 -IGFjcXVpc2l0aW9ucw== 63948 -QkxBQ0s= 63949 -IHRydW5j 63950 -IGFubm90YXRlZA== 63951 -c2V0VXA= 63952 -VE9LRU4= 63953 -IENvY2E= 63954 -RGlzYXBwZWFy 63955 -OnZhbHVl 63956 -IGFpZGVk 63957 -dHRs 63958 -bHV4 63959 -IGFjdWVyZG8= 63960 -IEZpbmdlcg== 63961 -Lkdlb21ldHJ5 63962 -XScpOwo= 63963 -Lmdm 63964 -VFhU 63965 -IFNjb3RpYQ== 63966 -YXZyYQ== 63967 -IHZpcA== 63968 -IHdob3BwaW5n 63969 -LWdpcmw= 63970 -IGN1cnNlZA== 63971 -XVst 63972 -IGNpcmN1bGF0ZWQ= 63973 -dW5jdHVyZQ== 63974 -b3JtYW4= 63975 -IG1BZGFwdGVy 63976 -IOKAlAoK 63977 -RmlsZU1hbmFnZXI= 63978 -KGlQYXJhbQ== 63979 -SW1hZ2VCdXR0b24= 63980 -REFR 63981 -QXJtb3I= 63982 -IHNwYXQ= 63983 -LmpzZGVsaXZy 63984 -IG1pc29n 63985 -LmVjb3Jl 63986 -J119Cg== 63987 -aW1wb3J0cw== 63988 -IGRpbm9zYXVy 63989 -LUZyZWU= 63990 -IGFubm9u 63991 -IHRyaWJ1bmFs 63992 -WWE= 63993 -Lmd1aWQ= 63994 -bW9zdGx5 63995 -PT09PQo= 63996 -IGltYWdlbQ== 63997 -U3VpdA== 63998 -a2Fz 63999 -IENoYW5uZWxz 64000 -QnVkZ2V0 64001 -IERpdmlkZQ== 64002 -amVt 64003 -IEdyaQ== 64004 -IGluZGljYXRpdmU= 64005 -XEZhY3Rvcnk= 64006 -LnJlcG9zaXRvcmllcw== 64007 -IEFNUA== 64008 -LnNucA== 64009 -IGHDpw== 64010 -Ims= 64011 -IMK1 64012 -ZGVjb2RlZA== 64013 -X2FyYw== 64014 -LUNsYXVzZQ== 64015 -IEFkag== 64016 -IG5ld0FycmF5 64017 -KEdFVA== 64018 -IGxhdGlu 64019 -IHd6 64020 -OnVpbnQ= 64021 -5Yir 64022 -Ii4u 64023 -Q29ubmVjdGluZw== 64024 -ZW5ub24= 64025 -5bm2 64026 -IFNlcw== 64027 -IGJlbG9uZ2luZ3M= 64028 -Kycm 64029 -CXNldHRpbmdz 64030 -SU5W 64031 -IHDDqQ== 64032 -IGFkdWx0aG9vZA== 64033 -YW1ibGU= 64034 -X21hc2tz 64035 -LXJlc29sdXRpb24= 64036 -cmF0cw== 64037 -IO2BtA== 64038 -IHZvZw== 64039 -IFNobw== 64040 -IENvdmVuYW50 64041 -IHJlbWluZGluZw== 64042 -b3JuYWRv 64043 -aWFk 64044 -5byC 64045 -Q3JlYXRpdmU= 64046 -IFNUWUxF 64047 -IGFub21hbHk= 64048 -XEFwcGxpY2F0aW9u 64049 -IG1hbmlmZXN0YXRpb24= 64050 -IE5hbm8= 64051 -TWFwVmlldw== 64052 -aWRlYWw= 64053 -YWNoaW5lcnk= 64054 -IFZhdWdo 64055 -cHJpbnRlcg== 64056 -VmVyZGFuYQ== 64057 -L2NvbXBvbmVudA== 64058 -IGFkZENoaWxk 64059 -IGxlYXJuZXI= 64060 -IGRlY3J5cHRlZA== 64061 -IHRpZ2h0ZXI= 64062 -5p2f 64063 -IGplag== 64064 -IC4KCgoK 64065 -IExvYmJ5 64066 -bGVw 64067 -w6Rubg== 64068 -bGVpZ2g= 64069 -L3JvdXRlcw== 64070 -IGNhbm9weQ== 64071 -IEZpc2NhbA== 64072 -Ojsi 64073 -IGJ1cmRlbnM= 64074 -L2Z1bGw= 64075 -IENTUg== 64076 -LlNoYXJlZFByZWZlcmVuY2Vz 64077 -L3RyZWU= 64078 -IGRyb2l0 64079 -SW1wbGVtZW50 64080 -R2V0Q3VycmVudA== 64081 -KHB1c2g= 64082 -JHg= 64083 -0Y/Qtw== 64084 -QUNJVFk= 64085 -PT09PT09PT09PQo= 64086 -amM= 64087 -X2hyZWY= 64088 -LmdldFJvb3Q= 64089 -IEtE 64090 -KGxz 64091 -W2NudA== 64092 -IGRhbGw= 64093 -KGJw 64094 -IEVX 64095 -S2V5RXZlbnQ= 64096 -bG9iZQ== 64097 -IGh0bWxlbnRpdGllcw== 64098 -IGZhbHRh 64099 -IHZhbHZlcw== 64100 -IHNpemluZw== 64101 -UG9ybg== 64102 -IHNob3dFcnJvcg== 64103 -IEZyaWQ= 64104 -IMOH 64105 -LnJhbmRu 64106 -IHRhbnRy 64107 -IHNheA== 64108 -dXJvdmlzaW9u 64109 -dGhlb24= 64110 -X1JDQw== 64111 -eEZE 64112 -SW5pdFN0cnVjdA== 64113 -IGNhbm5lZA== 64114 -IHF1YW50aWRhZGU= 64115 -LldBUk5JTkc= 64116 -IEJyaXR0 64117 -LXJlZ2lzdGVy 64118 -YWN0aXZlbHk= 64119 -IE5hdGFsaWU= 64120 -44G/ 64121 -IENPTk5FQ1Q= 64122 -emVr 64123 -IG1pbGxvbmVz 64124 -XWludA== 64125 -ICcsJyw= 64126 -IHByaW4= 64127 -IjpbLQ== 64128 -IC8vLg== 64129 -IGludGltaWRhdGluZw== 64130 -cmF6aW9uZQ== 64131 -LmlibQ== 64132 -IEpha2FydGE= 64133 -0LzQtdGA 64134 -IGxvYWRDaGlsZHJlbg== 64135 -X1VQTE9BRA== 64136 -IFdlZWtz 64137 -IGdldFRleHQ= 64138 -IPCfkg== 64139 -IF1dCg== 64140 -IENvc3Rz 64141 -xJlw 64142 -cGF5bWVudHM= 64143 -Lk1vdmll 64144 -bGg= 64145 -tIg= 64146 -X2NlcnRpZmljYXRl 64147 -PXE= 64148 -bGlicmFyaWVz 64149 -IEFlcg== 64150 -YXVzcw== 64151 -CWZhaWw= 64152 -T1VORFM= 64153 -c2VuZEtleXM= 64154 -IHNjYW1z 64155 -d2FydHM= 64156 -SGlzdA== 64157 -IEVzc2V4 64158 -IGZ1cnk= 64159 -IHRpdHJl 64160 -IENvcGVuaGFnZW4= 64161 -IHByZWRlZmluZWQ= 64162 -c2Nw 64163 -c2VycmF0 64164 -LmVuc3VyZQ== 64165 -aWxlZQ== 64166 -TWVyaXQ= 64167 -X1VOTE9DSw== 64168 -IENvcnJlY3Rpb24= 64169 -Tm9ybWFsaXphdGlvbg== 64170 -IOS/ruaUuQ== 64171 -IHN0b29s 64172 -IOWIoOmZpA== 64173 -U2hvcnRjdXQ= 64174 -Y2hvc2Vu 64175 -IGJ1bGx5 64176 -IGZ1bmNpw7Nu 64177 -44O844Or 64178 -IOeUn+WRveWRqOacnw== 64179 -LmFsaWFz 64180 -PlRvdGFs 64181 -IFNURU0= 64182 -cGVuZw== 64183 -Y2FsZXI= 64184 -cGVyZmVjdA== 64185 -IGJvbmRpbmc= 64186 -UGhvbmVz 64187 -IHB1bHA= 64188 -67aA 64189 -SUVXUw== 64190 -IERlZXI= 64191 -X0xDRA== 64192 -IENvbmNvcmQ= 64193 -V2l6YXJk 64194 -IG9mcmVj 64195 -IEVtZXJhbGQ= 64196 -dGVuZXNz 64197 -bmF2aWdhdG9y 64198 -VGhlb3J5 64199 -IGd1YXJkYXI= 64200 -IGZ1bGZpbA== 64201 -IFVuYXV0aG9yaXplZA== 64202 -IEJvdXQ= 64203 -CWhvc3Q= 64204 -IFJpYg== 64205 -KGZ0 64206 -RG9jcw== 64207 -LmdldEJvZHk= 64208 -5b+D 64209 -IFJpdmVyYQ== 64210 -IHdhdmluZw== 64211 -IHBlcmZpbA== 64212 -Qm91bmRpbmdDbGllbnRSZWN0 64213 -LmZh 64214 -cGFnZWQ= 64215 -IEFmZmlsaWF0ZQ== 64216 -IHByb2xldA== 64217 -fS0+ew== 64218 -KHNjb3Jlcw== 64219 -IHZpdGFl 64220 -e05hbWU= 64221 -c2NoZWR1bGVy 64222 -X1NBTg== 64223 -IE5lYw== 64224 -IEJlZWY= 64225 -X3Rj 64226 -TElO 64227 -IEV2ZW50VHlwZQ== 64228 -IEJ1ZmZlcmVkV3JpdGVy 64229 -IHNvZnRlcg== 64230 -IFZvdGluZw== 64231 -IEdlc3R1cmVEZXRlY3Rvcg== 64232 -IHVuc2Vlbg== 64233 -IFNDTw== 64234 -IGVsbw== 64235 -Y29tYmluZQ== 64236 -X21ha2VDb25zdHJhaW50cw== 64237 -IHVuZGVyZ29uZQ== 64238 -IE9mZmljaWFscw== 64239 -LG9wdA== 64240 -IGxheWVyZWQ= 64241 -ScOTTg== 64242 -IGJhbmtlcnM= 64243 -IHNlZ3JlZ2F0aW9u 64244 -IHJ1c3NpYW4= 64245 -IHZlbnRhbmE= 64246 -Z2V0S2V5 64247 -U2FudGE= 64248 -LlRvb2xTdHJpcFNlcGFyYXRvcg== 64249 -IEFlcm9z 64250 -LnB1dEludA== 64251 -IGluZm9ybXM= 64252 -X2JpbGw= 64253 -66aE 64254 -LnNldE1heA== 64255 -IH0+Cg== 64256 -IElQUw== 64257 -IEFsaWM= 64258 -In0KCg== 64259 -IHVzaGVy 64260 -IE5ndXllbg== 64261 -IGFic29sdXQ= 64262 -IGd1YXJkZWQ= 64263 -IFJlYmVs 64264 -IFp3 64265 -IEFubnVuY2k= 64266 -IHByw6E= 64267 -YWJjZGVmZ2hpamts 64268 -IFZlcmlmaWVk 64269 -W2l4 64270 -IHRpZXJz 64271 -w6J0 64272 -LiIpDQo= 64273 -aWp1 64274 -bGl2aW5n 64275 -R1BT 64276 -LlRlc3RUb29scw== 64277 -U2l6ZVBvbGljeQ== 64278 -IG1hc3NhZ2Vz 64279 -YXNzZXJ0SW5zdGFuY2VPZg== 64280 -IHBvc3PDrXZlbA== 64281 -IGJ1c2M= 64282 -IEp1ZGFpc20= 64283 -IGluZGlzcGVuc2FibGU= 64284 -IE1vc3RseQ== 64285 -SVRB 64286 -IGdldENvbnRlbnQ= 64287 -QnJvd3NlclJvdXRlcg== 64288 -LWNvdW50ZXI= 64289 -IG9idGVu 64290 -IC8+KTsK 64291 -0LjQuw== 64292 -aGVhZGxpbmU= 64293 -KGhvbWU= 64294 -YWxpY2U= 64295 -bGRyZQ== 64296 -X01vZHVsZQ== 64297 -Q29tcGFuaWVz 64298 -TlBD 64299 -IHRvcnNv 64300 -LmNvbnM= 64301 -CWFkZHJlc3M= 64302 -X3B1cmNoYXNl 64303 -IEJhcmQ= 64304 -Z3N0 64305 -LWFuaW1hdGlvbg== 64306 -X3BhaWQ= 64307 -LnNwZWNpYWw= 64308 -IGRlbGlt 64309 -IHRha2VvdmVy 64310 -KGhhbmQ= 64311 -ZW51aW5l 64312 -LWdyZXk= 64313 -IEFCSQ== 64314 -U2Vzc2lvbkZhY3Rvcnk= 64315 -aW5zdGFsbGVy 64316 -X0RJU1RBTkNF 64317 -IEZhdm9yaXRlcw== 64318 -oIA= 64319 -Jz57 64320 -IExhdXJlbnQ= 64321 -0YfQtdGC 64322 -IHN0cmlwc2xhc2hlcw== 64323 -IGVzdGFiYQ== 64324 -JnQ= 64325 -LnBhbg== 64326 -IFBBUlRZ 64327 -IEJhbGk= 64328 -Y3Np 64329 -KG1lbW9yeQ== 64330 -IFRvZG9z 64331 -IFNPQVA= 64332 -YWduZXQ= 64333 -CWJlZm9yZQ== 64334 -T3B0aW9uc1Jlc29sdmVy 64335 -aWJlbg== 64336 -INmF2YY= 64337 -IGFkZGl0aXZl 64338 -IE1lbGVl 64339 -IE1hbml0b2Jh 64340 -IFBlcmNlbnRhZ2U= 64341 -PSgt 64342 -LmtpbGw= 64343 -IGx4 64344 -YW5jYQ== 64345 -IGZvdG9ncmFm 64346 -IGJsYW5j 64347 -IFJlc2lkZW50cw== 64348 -cGluaw== 64349 -SEJveExheW91dA== 64350 -LnVuaW9u 64351 -IEhZ 64352 -IGNvbnRlbnRWaWV3 64353 -LWZhdA== 64354 -CWhhcw== 64355 -66OM 64356 -IHdoaXBwZWQ= 64357 -dmVuZG9ycw== 64358 -dWJyZQ== 64359 -SVRIRVI= 64360 -LmZ1bmN0aW9uYWw= 64361 -INCy0LXRgA== 64362 -Q2FuY2VsZWQ= 64363 -LWNu 64364 -SW5PdXQ= 64365 -LlJvd1N0eWxlcw== 64366 -IHRyYXRh 64367 -IEluZG9vcg== 64368 -LWZhc2hpb25lZA== 64369 -IEJvb3Ro 64370 -LkxhYmVsQ29udHJvbA== 64371 -IHBvcGU= 64372 -IENhcm5lZ2ll 64373 -bmVyZ2ll 64374 -IEJY 64375 -44CCIiwK 64376 -IFdlYnN0ZXI= 64377 -CWRpdg== 64378 -TmFycg== 64379 -IGNvbmp1Zw== 64380 -a2lk 64381 -IG1vZGVyYXRpb24= 64382 -IGFteQ== 64383 -IFNvbHZl 64384 -VklD 64385 -IEVa 64386 -aWxsYWM= 64387 -IENpcGhlcg== 64388 -IEFjY2VwdGVk 64389 -TEFCRUw= 64390 -IHdyYXRo 64391 -IG1pblZhbHVl 64392 -IGthxbw= 64393 -IERhdWdodGVy 64394 -KS5e 64395 -KGRj 64396 -IHJlc29sdmVz 64397 -c2Nzcw== 64398 -YWJvdXRz 64399 -dWx0aXBhcnRGaWxl 64400 -IGZlYXRz 64401 -IGxhdW5kZXJpbmc= 64402 -IGNvbXBhw7E= 64403 -IHNlZ3VyaWRhZA== 64404 -IGhvYmJpZXM= 64405 -LWZhY2luZw== 64406 -InZhbHVl 64407 -Z2V0SW1hZ2U= 64408 -U3FsU2VydmVy 64409 -IHdpdGhTdHlsZXM= 64410 -PkRhdGU= 64411 -IEV4cGVk 64412 -JGpzb24= 64413 -6ZO+ 64414 -IEFDVElPTlM= 64415 -U2Vuc2l0aXZl 64416 -Ymxhc3Q= 64417 -IMO2ZmY= 64418 -ZnRl 64419 -Q1RTVFI= 64420 -IExvZ0xldmVs 64421 -Y29udHJhY3Rz 64422 -LmRqYW5n 64423 -Ij4NDQo= 64424 -RVRZUEU= 64425 -IG9iamM= 64426 -X1NPVU5E 64427 -X3NwYWNpbmc= 64428 -X2NsYXNzaWZpZXI= 64429 -IHJvYw== 64430 -Q2xhc3NpYw== 64431 -IOuztA== 64432 -X2ludmVyc2U= 64433 -LWFjcmU= 64434 -IEZJTA== 64435 -IERWRHM= 64436 -IHN3YWxsb3dlZA== 64437 -dmlsbGE= 64438 -IFJlcGxpZXM= 64439 -RmlyZWJhc2U= 64440 -IHBoeXNpcXVl 64441 -CXRoYXQ= 64442 -IFJlc2l6ZQ== 64443 -Pj4+Pj4+Pg== 64444 -TmVhcmx5 64445 -LmFydGlzdA== 64446 -LXs= 64447 -Pz4NCg0K 64448 -Lmxy 64449 -Lmly 64450 -KFsk 64451 -aWFubmU= 64452 -CW9i 64453 -LCcl 64454 -IGtuZXg= 64455 -IGNvcnJv 64456 -IE93ZW5z 64457 -PW5pbA== 64458 -bGF5cw== 64459 -YXBn 64460 -w5Y= 64461 -RU5P 64462 -SGVucnk= 64463 -SnVzdGlu 64464 -ZWxlY3RyaWM= 64465 -IE5vcmRpYw== 64466 -5oyH 64467 -IGV4Y2x1ZGVz 64468 -RXVyb3BlYW4= 64469 -IHRlbnRz 64470 -KFN0cmluZ1V0aWxz 64471 -KHBlZXI= 64472 -eXN0b3Jl 64473 -UG9ja2V0 64474 -ZnVlbA== 64475 -ZXR1cw== 64476 -IE1hcmlu 64477 -0YDRg9C6 64478 -6K+E 64479 -IFBlbnM= 64480 -IGluZWZmaWNpZW50 64481 -IGV0ZXJuaXR5 64482 -Licm 64483 -IFBhY2thZ2Vz 64484 -IEFwcENvbmZpZw== 64485 -IG11bHRpZA== 64486 -Y3Vsbw== 64487 -IGJvcnJvd2Vycw== 64488 -IERlYmJpZQ== 64489 -IGZyb250cw== 64490 -Sko= 64491 -ICIuLi8uLi8uLi8uLi8= 64492 -ICIrCg== 64493 -PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0= 64494 -IEdhdmlu 64495 -IG1pc2g= 64496 -4pWR 64497 -X0FUVEFDSw== 64498 -SW5kZXBlbmQ= 64499 -4K+N4K4= 64500 -w6Fm 64501 -Z2Fycw== 64502 -IFBhcnRpY2lwYXRpb24= 64503 -VmVyYm9zZQ== 64504 -U3By 64505 -U3Zn 64506 -KFZhbHVlRXJyb3I= 64507 -IHJlY29uY2lsZQ== 64508 -CURCRw== 64509 -bWVldA== 64510 -IExvZ2luUGFnZQ== 64511 -LXVudXNlZA== 64512 -IGpvbmc= 64513 -IGFuY29yYQ== 64514 -INij 64515 -Plo= 64516 -PXc= 64517 -IFJlbm8= 64518 -dmll 64519 -b3Rpb25FdmVudA== 64520 -IExpc3RUaWxl 64521 -X1J1bnRpbWU= 64522 -IHVwaG9sZA== 64523 -IE9idGFpbg== 64524 -cHJvdmlkZWQ= 64525 -IERhdGVQaWNrZXI= 64526 -IENHSQ== 64527 -IEJsYWNrQmVycnk= 64528 -YWNobw== 64529 -IElzYWlhaA== 64530 -5pW0 64531 -IEFiZHVsbGFo 64532 -IHVwcA== 64533 -IHVybHBhdHRlcm5z 64534 -CXNpemVvZg== 64535 -IHBpc3NlZA== 64536 -IHByZWZlcnJlZFN0eWxl 64537 -QVBQRVI= 64538 -IFZC 64539 -IFRlcmVzYQ== 64540 -b2duaXRv 64541 -RU1Z 64542 -IGVsZWdhbmNl 64543 -IENsYXl0b24= 64544 -YXRpdm9z 64545 -IEFuYWxvZw== 64546 -IGdhdXNzaWFu 64547 -IEhpYmVybmF0ZQ== 64548 -W11b 64549 -IHN3ZWV0bmVzcw== 64550 -IE5pZWxzZW4= 64551 -IER1dGVydGU= 64552 -KHNlbA== 64553 -LCs= 64554 -IGV4dHJhb3JkaW4= 64555 -Zmxha2U= 64556 -W0RvdWJsZQ== 64557 -Ly8vDQo= 64558 -IG11Y2hhcw== 64559 -IEJyb2FkY2FzdGluZw== 64560 -QXNzb2NpYXRpb24= 64561 -ZXhlcmNpc2U= 64562 -LlJlbGF0aXZl 64563 -IHViaXF1aXRvdXM= 64564 -U0JBVENI 64565 -xLFuYQ== 64566 -LWZvb2Q= 64567 -IGNyeXN0YWxs 64568 -0YPQsQ== 64569 -ICd+ 64570 -INCR 64571 -IGR1bms= 64572 -IHpp 64573 -IE11Zw== 64574 -IGRlY2VwdGlvbg== 64575 -IEVtYWNz 64576 -CiAgICAKICAgIAo= 64577 -IMSRxrDhu6Nj 64578 -IFdvbHZlcw== 64579 -YW1lbnRp 64580 -ICcpWw== 64581 -Zm9ybWF0cw== 64582 -UmVjdg== 64583 -RGV0YWlsZWQ= 64584 -KEhXTkQ= 64585 -X3RyaWFs 64586 -YWdyYW50 64587 -T20= 64588 -Y29uc2Npb3Vz 64589 -IG9zcA== 64590 -cXXDqQ== 64591 -IGdvbg== 64592 -IG1lcmVrYQ== 64593 -YXJlbmRyYQ== 64594 -TWluZQ== 64595 -LmxpbmtlZGlu 64596 -IGZpZm8= 64597 -Lm1vbml0b3I= 64598 -IHJ1bmU= 64599 -bW5vcA== 64600 -IHNwZWN1bGF0ZQ== 64601 -ZWds 64602 -IHZhc2N1bGFy 64603 -LnRlY2g= 64604 -IG1hZ21h 64605 -IGxlc3Q= 64606 -dW1hbm4= 64607 -IERyaXZlck1hbmFnZXI= 64608 -IG9ydA== 64609 -IGxpbmdlcmluZw== 64610 -IG9zdHJlYW0= 64611 -IHNwYXJrbGluZw== 64612 -LmNvbm5lY3Rvcg== 64613 -IHRhaWxz 64614 -IGtlcm5lbHM= 64615 -VVNFUk5BTUU= 64616 -CWNj 64617 -IG9uU2VsZWN0 64618 -L01QTA== 64619 -dGFwZQ== 64620 -LmRqYW5nb3Byb2plY3Q= 64621 -R2VuZQ== 64622 -4oCZaW4= 64623 -L2ZpbHRlcg== 64624 -LWVudmVsb3Bl 64625 -IGFwcGxhdXNl 64626 -IHJlZ2lzdHJvcw== 64627 -IENvcnk= 64628 -b2ZmbGluZQ== 64629 -LXNob3Q= 64630 -bGVzYw== 64631 -b3RlbnQ= 64632 -IG51bWVyYXRvcg== 64633 -LmVmZmVjdA== 64634 -cGxhY2VtZW50cw== 64635 -IEFGQw== 64636 -LlNlcXVlbmNl 64637 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0K 64638 -eW50aGlh 64639 -IEdyaWZmaXRo 64640 -ZWxtYW4= 64641 -c2V0RGVzY3JpcHRpb24= 64642 -IE5pZ2h0cw== 64643 -Lm9yZGVycw== 64644 -IGAsCg== 64645 -IFNhbGFk 64646 -amlhbmc= 64647 -IHJlY3Vy 64648 -IFNUQVRJQw== 64649 -LXNwb25zb3JlZA== 64650 -eWxlbmU= 64651 -LGVtYWls 64652 -X18pKQ== 64653 -KSIpLg== 64654 -Q0VMTA== 64655 -YW1tZW50 64656 -TEFZ 64657 -LHN0ZA== 64658 -LnByZWY= 64659 -LkNvcg== 64660 -cmVkbw== 64661 -IEZ1Y2tlZA== 64662 -IHJ1c3M= 64663 -IGVzdGFibGlzaGVz 64664 -bnZhcmNoYXI= 64665 -LkdldEZpbGVOYW1l 64666 -IHBlbWI= 64667 -IFNhdWQ= 64668 -X3BhY2tldHM= 64669 -Lmludm9pY2U= 64670 -LmdldFRvdGFs 64671 -SG9tZUNvbnRyb2xsZXI= 64672 -IHTDtg== 64673 -YWdoZXI= 64674 -LmVudA== 64675 -LkFic29sdXRlQ29uc3RyYWludHM= 64676 -IGdlbnVz 64677 -IEJhYnlsb24= 64678 -IC4uLy4uLw== 64679 -IE1pZG5pZ2h0 64680 -IHdn 64681 -IGRhbmNlcg== 64682 -LWltbQ== 64683 -ZGlyZQ== 64684 -aGF6aQ== 64685 -Y2VydGlmaWNhdGU= 64686 -IG1EYXRh 64687 -IGN1cmVk 64688 -c3Zu 64689 -IkI= 64690 -aWJyZQ== 64691 -IGRyYWZ0cw== 64692 -Q2FwaXRhbA== 64693 -IGNvbmNpc2U= 64694 -IFBlYWNo 64695 -IHxc 64696 -IHBwbQ== 64697 -X2NvbnRhaW5z 64698 -QXV0b3I= 64699 -QXV0b1NpemU= 64700 -X2xi 64701 -IHNvbGVtbg== 64702 -IGZpbmdlcnQ= 64703 -IEluZGljYXRvcg== 64704 -IFN2 64705 -UGFyaw== 64706 -JHR5cGU= 64707 -X01JU1M= 64708 -YW5udWFs 64709 -UGFpZA== 64710 -bWFzdGVycw== 64711 -IFdE 64712 -IHZ1ZWw= 64713 -IGVqYWM= 64714 -CWdsdXQ= 64715 -IHVuZmluaXNoZWQ= 64716 -ZXN0ZWVt 64717 -Z3JvdXBCb3g= 64718 -UmVtb3Zpbmc= 64719 -IGVpbmlnZQ== 64720 -IFNjcmlwdHM= 64721 -Z2V0dG8= 64722 -LkhhbmRsZUZ1bmM= 64723 -Il0pLA== 64724 -IGRpc2FkdmFudGFnZXM= 64725 -LWZyb250 64726 -PnA= 64727 -c2V0T25DbGlja0xpc3RlbmVy 64728 -IGxhbmRsb3Jkcw== 64729 -IE3DvA== 64730 -IHByZXByb2Nlc3Npbmc= 64731 -KX0+ 64732 -LWNvbnRleHQ= 64733 -LGJvb2w= 64734 -UVVJVA== 64735 -ICIpIik7Cg== 64736 -IFdlYnNpdGVz 64737 -IENoYXJsb3R0ZXN2aWxsZQ== 64738 -TGF0Y2g= 64739 -LmRpcmVjdGl2ZQ== 64740 -IEh1ZmZpbmd0b24= 64741 -X2RpcnR5 64742 -ZXhwaXJhdGlvbg== 64743 -IFRQTQ== 64744 -IGVkeA== 64745 -IFdlYkRyaXZlcldhaXQ= 64746 -IGFkbWlyZWQ= 64747 -IGxpc3RlbnM= 64748 -IFZpbA== 64749 -ZGlmZmVyZW50 64750 -IGxpdmVsaWhvb2Q= 64751 -IFdhcmNyYWZ0 64752 -IHBvc2ljaW9u 64753 -IGltcGVhY2htZW50 64754 -SmF5 64755 -IHBvc2l0aXZlcw== 64756 -IGp1bmdl 64757 -IFNNQg== 64758 -L2luY2x1ZGVz 64759 -KCcuLi8uLi8uLi8= 64760 -QXJndW1lbnROdWxsRXhjZXB0aW9u 64761 -ZGVzY3JpY2Fv 64762 -QUJDREU= 64763 -LUFB 64764 -IGludmFkZWQ= 64765 -IGFtZXJpY2E= 64766 -dWVkZQ== 64767 -IFBoYXNlcg== 64768 -IHNjb3Jlcg== 64769 -IGRpc2NvdXJhZ2Vk 64770 -dGhpbg== 64771 -IGFiZG9tZW4= 64772 -IElQUA== 64773 -IEhhbXB0b24= 64774 -L0RlbGV0ZQ== 64775 -W3NyYw== 64776 -Q1N0cmluZw== 64777 -IE51bg== 64778 -IGVwaXRo 64779 -4oC7 64780 -LnRhYmxlcw== 64781 -IEhlaW4= 64782 -IHdoaXJs 64783 -IGNsYXJpZmljYXRpb24= 64784 -IHdlZGdl 64785 -IGjDpHI= 64786 -IFRpbmE= 64787 -IHRod2FydA== 64788 -IENvc3R1bWU= 64789 -aW9uYWdl 64790 -Q29k 64791 -X2FjbA== 64792 -IHJlc2g= 64793 -IE1lcmN5 64794 -IERpeG9u 64795 -IGRlc2Fycm9sbA== 64796 -VmlyZ2lu 64797 -KiopJg== 64798 -IExlbm92bw== 64799 -IGVyYXNlZA== 64800 -ZW50aW9ucw== 64801 -IHNsaXBwaW5n 64802 -5Zub 64803 -IGNyYXZpbmc= 64804 -cGxhbnRz 64805 -IGdldHRleHQ= 64806 -IG1hc3NpdmVseQ== 64807 -IFJlbmFtZQ== 64808 -Lmhlcm8= 64809 -44K7 64810 -IHRvbWFy 64811 -IENPU1Q= 64812 -IFByYWN0aWNlcw== 64813 -Lk1lZGlhVHlwZQ== 64814 -IEZ1bmRpbmc= 64815 -RmluZQ== 64816 -aWdlcmlh 64817 -VW5j 64818 -IHN3YXBwaW5n 64819 -PicuCg== 64820 -aW50ZXJw 64821 -YXJ0aWZhY3Q= 64822 -IEJhZ3M= 64823 -LnZpZXdNb2RlbA== 64824 -cXVvdGVk 64825 -CUxvbmc= 64826 -X1NDT1JF 64827 -IHNhdnZ5 64828 -bmVsbGU= 64829 -a2zDpA== 64830 -Q291bnRz 64831 -2q8= 64832 -RmllbGRUeXBl 64833 -b2thYmxl 64834 -IFJUTA== 64835 -I2luZGV4 64836 -ICV7 64837 -IGFyaXN0 64838 -LkdldE1hcHBpbmc= 64839 -KEFkYXB0ZXJWaWV3 64840 -PSIiKQo= 64841 -IGRpc2lu 64842 -IFRvdWNoYWJsZU9wYWNpdHk= 64843 -IE1PWg== 64844 -IER1bm4= 64845 -Q2FwYWJpbGl0eQ== 64846 -YWtoc3Rhbg== 64847 -VUlWaWV3Q29udHJvbGxlcg== 64848 -KHNvY2tmZA== 64849 -IEphY3F1ZXM= 64850 -PXRr 64851 -YXJQYXJhbXM= 64852 -Y29uZGE= 64853 -IGFkdm9jYXRlZA== 64854 -IHBlbmV0cmF0ZQ== 64855 -SkVDVElPTg== 64856 -IOuwmA== 64857 -IEZJTkQ= 64858 -IGVhcm5z 64859 -YXBwZW4= 64860 -6rE= 64861 -IHRocm91Z2hwdXQ= 64862 -IHBlbnNpb25z 64863 -IGZ1c3M= 64864 -SFRUUFJlcXVlc3Q= 64865 -bnV0cw== 64866 -b2NodA== 64867 -LWVzdGFibGlzaGVk 64868 -IEFMSUdO 64869 -IGpzcGI= 64870 -RGlzcA== 64871 -X2VtYmVkZGluZ3M= 64872 -IHJlcHQ= 64873 -IFlvcmtlcg== 64874 -w7JuZw== 64875 -IGpvdXJuZXlz 64876 -IEFwcHJvdmFs 64877 -CVNFTEVDVA== 64878 -KEdyYXBo 64879 -0LzQuA== 64880 -IGRvbGxz 64881 -IHNleGlzdA== 64882 -IHBhbnM= 64883 -IG1wbA== 64884 -IG9wZXJhdGl2ZQ== 64885 -IFRvcnJlbnQ= 64886 -WU0= 64887 -IFBhc3Npb24= 64888 -5pat 64889 -LmNvbXBpbGVy 64890 -CUNTdHJpbmc= 64891 -PWNvbG9y 64892 -b3JpYW5DYWxlbmRhcg== 64893 -IEtub2Nr 64894 -IGhhaWxlZA== 64895 -L3N0YXRl 64896 -IHNldHVwdG9vbHM= 64897 -IE1hcmU= 64898 -IHN5bmNocm9uaXpl 64899 -IFN3aXBl 64900 -IGdhbWJsZQ== 64901 -LCcnXV1dLAo= 64902 -IGRlZmVjdGl2ZQ== 64903 -X09CSkM= 64904 -IGRlbmlt 64905 -IHRhZA== 64906 -IEtpbWJlcg== 64907 -IG5ldXJvbG9naWNhbA== 64908 -w6puY2lhcw== 64909 -CWNi 64910 -LnNldFBhc3N3b3Jk 64911 -IFBsZWFzYW50 64912 -IFBoaQ== 64913 -LXRhZ3M= 64914 -IGNvbnRhZw== 64915 -IENvcmFs 64916 -IGRpc3RyYWN0 64917 -aXRpemVy 64918 -IHN1bnJpc2U= 64919 -c2V0SWQ= 64920 -IENoZW5uYWk= 64921 -IE9ncmU= 64922 -X0hJU1RPUlk= 64923 -UFJFU1NJT04= 64924 -X1NVRkZJWA== 64925 -ZHVwbGljYXRl 64926 -LmF1dGhTZXJ2aWNl 64927 -IHNwYWNlZA== 64928 -IEJlbmdhbHM= 64929 -U29sdmVy 64930 -IGJ1cmVhdWNyYWN5 64931 -X2hpdHM= 64932 -INGC0LjQvw== 64933 -IGPDqQ== 64934 -IGRpc2dyYWNl 64935 -6KeS 64936 -aXNPcGVu 64937 -Q2hlbQ== 64938 -X2xpY2Vuc2U= 64939 -X2hvc3RuYW1l 64940 -X0JSRUFL 64941 -IGZpZXJ5 64942 -OkQ= 64943 -L2xpbnV4 64944 -VGl0dWxv 64945 -UmFkaWFucw== 64946 -aXpvbnM= 64947 -UmFt 64948 -b2RpYW4= 64949 -aWFuZ2xl 64950 -IG5pbmph 64951 -RXZlcnlib2R5 64952 -KCI+ 64953 -IHRha8W8ZQ== 64954 -IGdyb3VuZGJyZWFraW5n 64955 -IGRpcmln 64956 -SFRNTEVsZW1lbnQ= 64957 -IFVuY29tbWVudA== 64958 -Y2hlaW4= 64959 -IOeUn+WRveWRqOacn+WHveaVsA== 64960 -JSIK 64961 -IHRpcG9z 64962 -Q2hhckNvZGU= 64963 -IFByb2R1Y3Rv 64964 -ZmFpdA== 64965 -J2w= 64966 -LXRodW1ibmFpbA== 64967 -dXN1 64968 -X2Zvcm11bGE= 64969 -LlRPUA== 64970 -LmJ1eQ== 64971 -IG1pZXV4 64972 -Q2VudHVyeQ== 64973 -cGVp 64974 -IHRic3A= 64975 -LVBhY2lmaWM= 64976 -b2dp 64977 -IGZhdHRv 64978 -IGZhbnRhc3Q= 64979 -IFNBTEU= 64980 -LmFkcw== 64981 -IHBpbGxhcnM= 64982 -X3RyaXA= 64983 -IHR1YQ== 64984 -IGFwZWxsaWRv 64985 -LnNldENlbGxWYWx1ZQ== 64986 -ICgoXw== 64987 -IE5pbmE= 64988 -PGM= 64989 -aW5pdW0= 64990 -ZGZ1bmRpbmc= 64991 -LXdvcmtpbmc= 64992 -IEVzdGFkb3M= 64993 -IE1hbGk= 64994 -PGY= 64995 -dXJhbmNlcw== 64996 -cGFnaW5h 64997 -X1BL 64998 -IHVuYXJtZWQ= 64999 -b2dnbGVk 65000 -Q2FuZGlkYXRl 65001 -UmF0aGVy 65002 -IGZyYW5jaGlzZXM= 65003 -IGNvdmVuYW50 65004 -wqo= 65005 -aXBwaW5lcw== 65006 -R3Vu 65007 -LWZlaXJh 65008 -IGxpbmVhZ2U= 65009 -X0dSQU5URUQ= 65010 -Z2VucmVz 65011 -LkVsYXBzZWQ= 65012 -IGxhcmdv 65013 -0Js= 65014 -LXJlYWR5 65015 -X3Byb2Nlc3NlZA== 65016 -bGFuZ3M= 65017 -w7ptZXJvcw== 65018 -ZnE= 65019 -L25wbQ== 65020 -X3Nydg== 65021 -IGF0dGVuZGFudA== 65022 -aXZpZA== 65023 -ZXZpY2U= 65024 -QUJJ 65025 -KGJpbmFyeQ== 65026 -X1ZBTElEQVRF 65027 -IGFkZEl0ZW0= 65028 -X2NvZWY= 65029 -YWxlYg== 65030 -b2dyYXBoaWNhbGx5 65031 -Qm9yZGVyQ29sb3I= 65032 -IGFzc2F5 65033 -IGNhdGNoRXJyb3I= 65034 -IENocnlzbGVy 65035 -b2do 65036 -IGtleVZhbHVl 65037 -ZGVjaXNpb24= 65038 -LW9mZnM= 65039 -IGxpZWd0 65040 -KERhdGFUeXBl 65041 -IGlyaXM= 65042 -IGV1cA== 65043 -cmlnZXI= 65044 -b25pY2E= 65045 -IHJvcGVz 65046 -IG5hcnJvd2x5 65047 -IFF1YWRy 65048 -IGVwdWI= 65049 -ZXN0aW5hbA== 65050 -LXR1cm4= 65051 -IGxhbmdz 65052 -55uR5ZCs6aG16Z2i 65053 -IHF1ZWxsbw== 65054 -LGFyZ3M= 65055 -aWdhdGU= 65056 -IFNlZW1z 65057 -IGZvcnRl 65058 -Q0xJ 65059 -X0xPQURJTkc= 65060 -LlJ1bGU= 65061 -IHlvdXRocw== 65062 -KHh4 65063 -IEFzc3VtaW5n 65064 -YWdoZXR0aQ== 65065 -KQoKCgoK 65066 -IG9uT3B0aW9uc0l0ZW1TZWxlY3RlZA== 65067 -T2NjdXA= 65068 -IGRldHJpbWVudGFs 65069 -IGlubmF0ZQ== 65070 -IEJhcnJlbA== 65071 -dWVuY2lh 65072 -IG9uQmx1cg== 65073 -IGxpYnM= 65074 -W2xhc3Q= 65075 -IGNwZg== 65076 -LlRpbWVvdXQ= 65077 -ZXN0YXRpb24= 65078 -IHdpZWw= 65079 -IHV0aWxpemFy 65080 -IGRpc2d1aXNl 65081 -IER1bQ== 65082 -T0NJ 65083 -T05HTw== 65084 -ICg/LA== 65085 -IFBhdGlv 65086 -VmVydGV4QXJyYXk= 65087 -LmF1dGhvcml6YXRpb24= 65088 -cm96 65089 -IEhvcw== 65090 -LlNwYWNl 65091 -IFZpcnVz 65092 -KGtleXdvcmQ= 65093 -VE9DT0w= 65094 -X0NPTlRST0xMRVI= 65095 -IEJsb2NrZWQ= 65096 -IENob3A= 65097 -d2nEmQ== 65098 -XFJvdXRpbmc= 65099 -L3BhY2thZ2U= 65100 -IHBlcnN1YWRlZA== 65101 -YmVpdHM= 65102 -TENE 65103 -IG11Yw== 65104 -X0ZPUldBUkQ= 65105 -IG91dGxhdw== 65106 -IHphdw== 65107 -X3ZlaGljbGU= 65108 -IEplbnNlbg== 65109 -LkdyZWVu 65110 -IC8vLy8v 65111 -SVJDTEU= 65112 -LWJ1c2luZXNz 65113 -LkhpZGRlbg== 65114 -IGtvbm50ZQ== 65115 -cHE= 65116 -IHBhcmVjZQ== 65117 -IGxhbmRzY2FwaW5n 65118 -IERlY29yYXRpb24= 65119 -IEdSQQ== 65120 -X3Byb2ZpbGVz 65121 -IEZsZW0= 65122 -Q0xJQ0s= 65123 -IEZBSUxVUkU= 65124 -IGlvbnM= 65125 -X1RpbWVy 65126 -LkRvZXM= 65127 -IGJvdW5jaW5n 65128 -dXBweQ== 65129 -dWxpcw== 65130 -L2Fn 65131 -IEdhcm4= 65132 -IGh1ZA== 65133 -IHJlc3BvbmRlcg== 65134 -IHN0cmNocg== 65135 -IGNob2tl 65136 -IHN0YXNo 65137 -X2NoZWNrc3Vt 65138 -IHN0YW1wZWQ= 65139 -QEdldE1hcHBpbmc= 65140 -LkJ5dGVBcnJheQ== 65141 -IER5cw== 65142 -YXRlcm5pdHk= 65143 -KHJi 65144 -IGVkaXRUZXh0 65145 -IGVyZWN0aW9u 65146 -IGNlc3M= 65147 -X2V2ZXJ5 65148 -X2dhdGV3YXk= 65149 -ICciLg== 65150 -IHN0YWZmaW5n 65151 -IGludm9pY2Vz 65152 -aW5pY2lv 65153 -fV0sCg== 65154 -LHZhcg== 65155 -eWNpbg== 65156 -IERpb24= 65157 -ICUlCg== 65158 -Jywo 65159 -LXNwYW4= 65160 -IHRow6BuaA== 65161 -IGJvcm5l 65162 -IEthdGhsZWVu 65163 -6L+e5o6l 65164 -X2N1YmU= 65165 -IGluZm9ybWHDp8O1ZXM= 65166 -bmdlcg== 65167 -L0ZpbGU= 65168 -IGRhcmE= 65169 -IG1M 65170 -KioqKioqCg== 65171 -IG1hcmtpbmdz 65172 -YmJl 65173 -IHJlY3VycmVudA== 65174 -IFJhbmtpbmc= 65175 -X2ludGVncmFs 65176 -XT4K 65177 -IHVuYW5pbW91c2x5 65178 -IGRpcGxvbWF0cw== 65179 -IElPUw== 65180 -OyI+PD8= 65181 -IE1hdHRl 65182 -IFJhbGVpZ2g= 65183 -IEltcHJvdmU= 65184 -ZXhpc3RlbnQ= 65185 -IGZha2Vy 65186 -IEhpZ2hsYW5k 65187 -c3RlbQ== 65188 -LW1z 65189 -TGlzdE9m 65190 -Lkxpc3RlbmVy 65191 -KHdhaXQ= 65192 -X1JTVA== 65193 -VW5h 65194 -IG9jY3VwYXRpb25hbA== 65195 -LW1lbW9yeQ== 65196 -IFN1cmY= 65197 -IGJydXRl 65198 -X0VsZW1lbnQ= 65199 -ZGRkZA== 65200 -IERlY3Jl 65201 -LnBzaQ== 65202 -LWRldmVs 65203 -IE9uVHJpZ2dlckVudGVy 65204 -VG9EZWxldGU= 65205 -IGhlcmFsZA== 65206 -IHNvY2lhbGVz 65207 -IGJvb3N0ZWQ= 65208 -Lkl0b2E= 65209 -KiI= 65210 -IGFudGlkZXByZXNz 65211 -IE1hdmVy 65212 -X18pKQo= 65213 -KER1cmF0aW9u 65214 -ZXN0YXRl 65215 -YnJhdGU= 65216 -Q2xh 65217 -IOS4ig== 65218 -65CY 65219 -cmnDqHJl 65220 -YnJlYWtlcg== 65221 -X2xlZw== 65222 -fWVsc2VpZg== 65223 -X2Z1bmNz 65224 -dcOt 65225 -LnBhZ2VZ 65226 -Y3JlYXR1cmU= 65227 -IGNhbm5hYmlu 65228 -IEFzdHJv 65229 -bG9jYWxz 65230 -IExBUw== 65231 -X2NvbnZlcnNpb24= 65232 -IENSVUQ= 65233 -LnNraWxs 65234 -IHN0cmF0ZWdpc3Q= 65235 -LnBvbA== 65236 -KHNlZ21lbnQ= 65237 -IHBlZQ== 65238 -fSIpOwoK 65239 -LnByZXZpZXc= 65240 -SmFt 65241 -IGhlZnR5 65242 -aXZhdGluZw== 65243 -R3JpZENvbHVtbg== 65244 -IGN1ZGQ= 65245 -IGluamVjdGlvbnM= 65246 -IE5JTA== 65247 -LW9sZHM= 65248 -ZmxhdGlvbg== 65249 -IExlYWZz 65250 -IHNwaGVyaWNhbA== 65251 -IGZhbGxvdXQ= 65252 -YW1pbmVy 65253 -IDo6PQ== 65254 -LnBvaW50ZXI= 65255 -LU1hcnQ= 65256 -IG1hdHRl 65257 -IGNvcXVpbmU= 65258 -IGRpc2NvbnRpbnVlZA== 65259 -IFJFR0lPTg== 65260 -LlJpZ2h0VG9MZWZ0 65261 -IHNxdWVlemVk 65262 -X1BPSU5UUw== 65263 -YmVzdG9z 65264 -LWxhc3Rpbmc= 65265 -KHV0aWxz 65266 -PEJhc2U= 65267 -IHBhcmRvbg== 65268 -U3RyaWRl 65269 -Y2Ry 65270 -IG5hcnJhdG9y 65271 -dm9sdXRpb24= 65272 -IHVzZXJJbnB1dA== 65273 -X2NvbnRhY3Rz 65274 -KGVuZW15 65275 -IENoYW1iZXJz 65276 -emllbA== 65277 -IGJsb2NrU2l6ZQ== 65278 -QW5pbWF0aW9uc01vZHVsZQ== 65279 -IGltbWVyc2l2ZQ== 65280 -IG91dGluZw== 65281 -dWVzdG9z 65282 -VHdlZW4= 65283 -IGtlcA== 65284 -IHLDqXN1bHQ= 65285 -IEJvbGx5d29vZA== 65286 -RExM 65287 -IFN1cmVseQ== 65288 -LlJvd1N0eWxl 65289 -KHRt 65290 -X2dlbmVyYXRpb24= 65291 -IFN0aXI= 65292 -IGRhdGFTbmFwc2hvdA== 65293 -Y2h1cmNo 65294 -IGNvbmZpZGVudGlhbGl0eQ== 65295 -X3N1c3BlbmQ= 65296 -dmlw 65297 -IEthdGh5 65298 -44Km 65299 -IHZpb2xlbnRseQ== 65300 -cGV0cw== 65301 -IG1lc3NlZA== 65302 -IHRleHRib29rcw== 65303 -ICAgICAgICAJCQk= 65304 -5raI5oGv 65305 -IExhcmF2ZWw= 65306 -IEFyY2FkZQ== 65307 -IGVudGg= 65308 -IGJlbmlnbg== 65309 -X0RST1A= 65310 -LWVuYWJsZQ== 65311 -4oCdKS4= 65312 -dXZ3eHl6 65313 -X2xpc3Rpbmc= 65314 -IE5JQw== 65315 -44GV44GE 65316 -KCIuIiw= 65317 -LXJvdW5kZWQ= 65318 -LXBhY2Vk 65319 -cGF0cmljaw== 65320 -U2VsZQ== 65321 -LmdldEZpcnN0 65322 -LkVYSVQ= 65323 -ZXRlcm1pbmF0ZQ== 65324 -R3JhbQ== 65325 -Ly8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 65326 -LmV4dGVybmFs 65327 -IHdyb25nZG9pbmc= 65328 -IEVsbQ== 65329 -IHNhbms= 65330 -VGVlbg== 65331 -IFRob21zb24= 65332 -cHJpb3I= 65333 -amV0YQ== 65334 -IEFEUw== 65335 -IFBlcnNpc3RlbmNl 65336 -IEZvbGs= 65337 -e1wi 65338 -Ym9uZA== 65339 -X1NQRUNJQUw= 65340 -X0xBVA== 65341 -b25la3Np 65342 -IG1vdGhlcmJvYXJk 65343 -IHNoZWFy 65344 -RnVsbFNjcmVlbg== 65345 -Kks= 65346 -KEJsdWVwcmludA== 65347 -TWV0aG9kSW5mbw== 65348 -QmVjb21l 65349 -IGhhaWw= 65350 -IERvYg== 65351 -IGdlbmVyb3NpdHk= 65352 -ID8iOwo= 65353 -IHdoaXNrZXk= 65354 -IHRoaW5uZXI= 65355 -IENw 65356 -IGludGVyc2VjdGlvbnM= 65357 -Q3JpdA== 65358 -cmFpc2Fs 65359 -cmVmZmVu 65360 -V2hlbmV2ZXI= 65361 -IGNvbW1lbmNlZA== 65362 -VHJhbnNmb3JtYXRpb24= 65363 -L3dyaXRl 65364 -PSIiIg== 65365 -KGxk 65366 -IG5vcnNr 65367 -QU1FTlQ= 65368 -LnNoYXJlZEluc3RhbmNl 65369 -X2hvdXNl 65370 -IGdsRW5hYmxl 65371 -6L2v 65372 -IG5hbw== 65373 -IGRlcG9zaXRpb24= 65374 -IGRpbm9zYXVycw== 65375 -IHRpbWVTdGFtcA== 65376 -X18pOwoK 65377 -LlJpYmJvbg== 65378 -IExpbmRzZXk= 65379 -OnVzZXI= 65380 -IMOA 65381 -X2Zvcm1z 65382 -bWluYXRpbmc= 65383 -IE9saXY= 65384 -IGTDqWJ1dA== 65385 -YmFyY29kZQ== 65386 -c2ltaWxhcg== 65387 -IHBsYXRlYXU= 65388 -IGluZGVt 65389 -UmVhbG0= 65390 -IGZlcnRpbGl6ZXI= 65391 -IGNhcGU= 65392 -IGNoYW1wYWduZQ== 65393 -IHNlbGZpZQ== 65394 -IHBsYWlubHk= 65395 -IGNhdGFzdHJvcGhl 65396 -IGJldHJheWVk 65397 -dmVyc2libGU= 65398 -VXBkYXRlVGltZQ== 65399 -Lk91dHB1dFN0cmVhbQ== 65400 -Ymlhc2Vk 65401 -Ym91bmNl 65402 -IFNwb3J0aW5n 65403 -Q29vcmRpbmF0b3I= 65404 -ZGV2ZWxvcGVycw== 65405 -IHRyYWNlcg== 65406 -IG11c3RhcmQ= 65407 -U1E= 65408 -X3Rlcm1pbmFs 65409 -IGNvb2xlZA== 65410 -IGF2b2lkYW5jZQ== 65411 -TG9naWNhbA== 65412 -IHllbGw= 65413 -X3JvdXRlcw== 65414 -IGFydGVyeQ== 65415 -IEJlYXJpbmdz 65416 -Lm12cA== 65417 -LkdVSQ== 65418 -VUlTY3JlZW4= 65419 -eW1t 65420 -aXTDpA== 65421 -KClbIg== 65422 -IEF6ZXJiYWk= 65423 -IGNvbmRpdGlvbmVy 65424 -IHdhZw== 65425 -IHNjYWxw 65426 -dmluY2lhbA== 65427 -b3dsZXI= 65428 -LicpOwoK 65429 -QkxVRQ== 65430 -IMKnwqc= 65431 -Qm9zdG9u 65432 -IExpbmtlZEhhc2hNYXA= 65433 -RG9jdW1lbnRhdGlvbg== 65434 -LkxlcnA= 65435 -IGRlbm5l 65436 -IGhlc2l0YXRpb24= 65437 -IENlbGVicml0eQ== 65438 -IEh5ZGU= 65439 -IGNvbW1hbmRpbmc= 65440 -YWNlbGx1bGFy 65441 -IHBhdmVtZW50 65442 -IEhhbW1vbmQ= 65443 -YXNzaWM= 65444 -UExVR0lO 65445 -IHJldm9rZWQ= 65446 -RG9jdW1lbnRv 65447 -LnBob3Rvcw== 65448 -IFdpbGxvdw== 65449 -IFZpa2luZw== 65450 -IHVwZnJvbnQ= 65451 -IExpZmV0aW1l 65452 -ICVb 65453 -RHJlYW0= 65454 -5aS0 65455 -IGFjY2VsZXJhdG9y 65456 -UGVyc29uYQ== 65457 -X3RvcGljcw== 65458 -77yJ44CB 65459 -IChfLg== 65460 -IHPDqWN1cg== 65461 -IEt3 65462 -X2Nhc2g= 65463 -IHNvb3RoaW5n 65464 -IExvdmVseQ== 65465 -IEhlcnM= 65466 -ZWxvbg== 65467 -TElDRU5TRQ== 65468 -X2NhY2hlZA== 65469 -LnNoYQ== 65470 -UkZD 65471 -LkZpbGVJbnB1dFN0cmVhbQ== 65472 -LUFs 65473 -IHVzZXJMaXN0 65474 -IG7DpHI= 65475 -SGlsbGFyeQ== 65476 -IHBhZ28= 65477 -LlBsdWdpbg== 65478 -IENvdmU= 65479 -X3lhbWw= 65480 -X3JzcA== 65481 -J3Bvc3Q= 65482 -LWR1cmF0aW9u 65483 -IHNlbnRpZG8= 65484 -IG1pbkhlaWdodA== 65485 -IHR1cnJldA== 65486 -LWVuZXJneQ== 65487 -IOeJ 65488 -0YDRg9Cz 65489 -b3RlY2E= 65490 -X3F1YWw= 65491 -U2VsZWN0aXZl 65492 -IEJFTE9X 65493 -CWFkbWlu 65494 -IH19LAo= 65495 -J3VzZXI= 65496 -U1ZH 65497 -IGN1bG8= 65498 -KFdvcmxk 65499 -LWJpbmRpbmc= 65500 -bmJy 65501 -IFNlbmRz 65502 -IHN1cHJlbWFjeQ== 65503 -IHNrYXRpbmc= 65504 -IGNyZWVr 65505 -IGFjY3VzYXRpb24= 65506 -YXBnb2xseQ== 65507 -LklERU5USVRZ 65508 -IG1hbmRhdGVk 65509 -IGdvd24= 65510 -IHdpZHRocw== 65511 -IExTVQ== 65512 -L3ZlcnNpb24= 65513 -IFJlYWRlcnM= 65514 -IFJvbmFsZG8= 65515 -IGJhZmY= 65516 -IGA7Cg== 65517 -R0xJU0g= 65518 -KGRvdA== 65519 -IE9wZXJhdG9ycw== 65520 -LlNjZW5lTWFuYWdlbWVudA== 65521 -bWVyYw== 65522 -X3JlcG9ydHM= 65523 -LWNlbnRyaWM= 65524 -IENlaWxpbmc= 65525 -PXsh 65526 -bW9ueQ== 65527 -IEFERFJFU1M= 65528 -5a+56LGh 65529 -TWF0Y2hpbmc= 65530 -IHVuaw== 65531 -IGtleUNvZGU= 65532 -ICcvJyk= 65533 -KWRhdGE= 65534 -IFZvbHVudGVlcg== 65535 -IGxheg== 65536 -IEd1YW5n 65537 -IENhbmRpZGF0ZXM= 65538 -RW5zdXJl 65539 -aWFnZQ== 65540 -c3VjYw== 65541 -Q2VydGFpbg== 65542 -IGxlZnRvdmVy 65543 -aW5pbg== 65544 -LWVsZW1lbnRz 65545 -cGlrZQ== 65546 -IHNsaWRlc2hvdw== 65547 -LnRvb2xTdHJpcFNlcGFyYXRvcg== 65548 -LnBoYXNl 65549 -IGVudGVydGFpbmVk 65550 -IENhcnJpZQ== 65551 -IE1vaGFtbWFk 65552 -LmxvZ2dlZA== 65553 -IHNjcm9sbFRvcA== 65554 -IEFiYmV5 65555 -aW1vbnk= 65556 -KHJlc3VsdFNldA== 65557 -IGFkaGVzaXZl 65558 -X0RBTUFHRQ== 65559 -IGlvY3Rs 65560 -YnJvd24= 65561 -SU5TVA== 65562 -LkNsb25l 65563 -IGxvb21pbmc= 65564 -RGVzZXJpYWxpemU= 65565 -IGx1eg== 65566 -cXJzdHV2d3h5eg== 65567 -LmlkZW50 65568 -SGVhdnk= 65569 -IGRpbw== 65570 -5piv5ZCm 65571 -IEZ1cm4= 65572 -6YKu 65573 -emltbWVy 65574 -44O844OJ 65575 -c3BlYWtlcg== 65576 -IEdlZA== 65577 -IHVuaWRlbnRpZmllZA== 65578 -SW50ZXJmYWNlT3JpZW50YXRpb24= 65579 -IFN1cnZpdm9y 65580 -ZGVlbg== 65581 -IEJvcmc= 65582 -dG9Eb3VibGU= 65583 -X2J3 65584 -IHB1Ymxpc2hlcw== 65585 -X0FMRVJU 65586 -YW5ncw== 65587 -aWVyZXM= 65588 -IGhlaQ== 65589 -IElDb25maWd1cmF0aW9u 65590 -IGNvbnN0aXR1dGVk 65591 -V0FUQ0g= 65592 -cHJpdmF0aW9u 65593 -IEdyYW5pdGU= 65594 -LlRleHRBbGlnbm1lbnQ= 65595 -X2t3 65596 -OyIsCg== 65597 -Y290 65598 -IE5ld2Fyaw== 65599 -cm9hY2g= 65600 -KW9iag== 65601 -Q29tcGlsYXRpb24= 65602 -Q2F0ZWdvcnlJZA== 65603 -LnNldFVzZXI= 65604 -aXZ5 65605 -IEltYWdpbmc= 65606 -aWdodGVk 65607 -IHdnZXQ= 65608 -IG1vdXRocw== 65609 -Lmxpbg== 65610 -IFJhZGlvQnV0dG9u 65611 -LkNtZA== 65612 -c3Nl 65613 -IG1lc2hlcw== 65614 -IFNvbGU= 65615 -LnJlY29yZHM= 65616 -IGFudGlz 65617 -KG1vbg== 65618 -INGH0LjRgdC70L4= 65619 -gq0= 65620 -IOyeiOuKlA== 65621 -QWxsQXJnc0NvbnN0cnVjdG9y 65622 -IHN1cnJlYWw= 65623 -IE1hcnJpZWQ= 65624 -IHhwYXRo 65625 -XGY= 65626 -QnJpbmc= 65627 -IHlhaG9v 65628 -IEV0c3k= 65629 -X2RhaWx5 65630 -IHRocm93YWJsZQ== 65631 -IFBsYXNtYQ== 65632 -L1B1YmxpYw== 65633 -aW1pemVCb3g= 65634 -IHZlcw== 65635 -IHRyb20= 65636 -X3Jocw== 65637 -LWFscGhh 65638 -IEFyYm9y 65639 -KSkt 65640 -RmlzaA== 65641 -ZmVlZHM= 65642 -IGNhbGY= 65643 -IFNlcmdlYW50 65644 -KGVudW0= 65645 -IFJhbXNleQ== 65646 -IElkZW50aWZ5 65647 -LmluaXRTdGF0ZQ== 65648 -IGZsdWN0dWF0aW9ucw== 65649 -X0FUVFJJQlVURVM= 65650 -IHB3bQ== 65651 -RVNB 65652 -Y3Bm 65653 -U2ltdWxhdGlvbg== 65654 -IHlvdXRoZnVs 65655 -IEluZmFudHJ5 65656 -IGdsYW5jZWQ= 65657 -IFByb3Blcg== 65658 -5LmJ 65659 -IEtyYWZ0 65660 -Q2l0 65661 -b29wcw== 65662 -PXVybA== 65663 -cG9zdGluZw== 65664 -ZGVjbGFyaW5n 65665 -IHBOb2Rl 65666 -SmF2YXNjcmlwdA== 65667 -CQkJCQoJCQkJCg== 65668 -LmNvb3JkaW5hdGVz 65669 -cmlldA== 65670 -IFNx 65671 -X0NBVA== 65672 -IFBhcGE= 65673 -YW5kaQ== 65674 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v 65675 -TWVldGluZw== 65676 -IOyekA== 65677 -SW1hZ2Vu 65678 -w6lyaWVuY2U= 65679 -QWdncmVnYXRl 65680 -LnBvbHk= 65681 -IHdhdmVk 65682 -IGludmVycw== 65683 -c2VhcmNoTW9kZWw= 65684 -IHRyb2xscw== 65685 -W2xldmVs 65686 -IExvd2U= 65687 -dWxsbw== 65688 -KHBsYWNl 65689 -IE5BU0NBUg== 65690 -IG9yYml0YWw= 65691 -LnN0b3J5 65692 -IGF1dGhvcml0YXRpdmU= 65693 -LnRleHRWaWV3 65694 -IGFscGg= 65695 -X3JlZHVjZQ== 65696 -IEZyYW1lcw== 65697 -IEJyb20= 65698 -cmVkaQ== 65699 -KE1ldGhvZEltcGxPcHRpb25z 65700 -bWFjZW4= 65701 -VG90 65702 -IG1pZGQ= 65703 -2Y8= 65704 -IEJhc2VNb2RlbA== 65705 -IFZlZ2E= 65706 -ID8+Igo= 65707 -IFJpZ2lkYm9keQ== 65708 -LnNldENvbnRlbnRUeXBl 65709 -YWFT 65710 -QmFzZWxpbmU= 65711 -IGJsYW5rZXRz 65712 -c2Fw 65713 -IGNhc3VhbGx5 65714 -VW5pdmVycw== 65715 -IFRyYXk= 65716 -IEFpcmVz 65717 -IG1heFk= 65718 -X1BST1BFUlRJRVM= 65719 -IGhlbG1ldHM= 65720 -wqY= 65721 -X2Rlc2Ny 65722 -c2hpbnQ= 65723 -X0NQUA== 65724 -dW1v 65725 -YWRheQ== 65726 -KHBsb3Q= 65727 -ZW56eW1l 65728 -IEV4Y2VwdGlvbnM= 65729 -X3Zpc3VhbA== 65730 -Ol0KCg== 65731 -KHRhcmdldEVudGl0eQ== 65732 -cGhlcmVz 65733 -dW5hbg== 65734 -IHNlbG9u 65735 -d2ls 65736 -IFJlbmRlcmluZw== 65737 -S0M= 65738 -IGNvbnN0aXR1ZW5jeQ== 65739 -U0NSSUJF 65740 -ZXN5 65741 -IEZlbGxvd3NoaXA= 65742 -5Y+4 65743 -IGZ1dHVybw== 65744 -IGFybW9yZWQ= 65745 -bGlzdGU= 65746 -b3Jhcw== 65747 -bXVsdGlwbHk= 65748 -Z2VtZQ== 65749 -Y29lZg== 65750 -0L7QsdGA0LDQtg== 65751 -IERlbGl2ZXI= 65752 -ZW5nbw== 65753 -LnVzZXJTZXJ2aWNl 65754 -T05VUw== 65755 -Lm9ucmVhZHlzdGF0ZWNoYW5nZQ== 65756 -ICIvIiw= 65757 -YW1iaW8= 65758 -X1Byb2plY3Q= 65759 -Jyk/Pg== 65760 -IGZsaXBwaW5n 65761 -d29tZW4= 65762 -LkNyb3Nz 65763 -IGhvbGxhbmQ= 65764 -IGNpbmVtYXRpYw== 65765 -IHdoaXN0bGVibA== 65766 -IGxpbmd1aXN0aWM= 65767 -LkdldHRlcg== 65768 -IG3DpG5uZXI= 65769 -IExlZ28= 65770 -IFNjaHVtZXI= 65771 -YXNzZXNzbWVudA== 65772 -X2Noaw== 65773 -IHJlY29tbWVuZGluZw== 65774 -LnNjYWxh 65775 -IEd1YXJhbnRlZQ== 65776 -IEBf 65777 -LkFVVEg= 65778 -IHlQb3M= 65779 -bGF0ZXg= 65780 -IEFsYmVydG8= 65781 -5q2l 65782 -dGhvcmE= 65783 -4Li34LmI 65784 -VVJMRXhjZXB0aW9u 65785 -R2hvc3Q= 65786 -LlRvb2xiYXI= 65787 -IGVuZGlhbg== 65788 -6Zeo 65789 -c3RyYWN0aW9ucw== 65790 -RmlsZU5vdEZvdW5kRXhjZXB0aW9u 65791 -IHN0aW11bGF0aW5n 65792 -YnNlcnZpY2U= 65793 -YXTDs3Jpbw== 65794 -aXRpb3Vz 65795 -IGF1dGhTZXJ2aWNl 65796 -X1RSQU5TRkVS 65797 -IHJlZGlyZWN0VG8= 65798 -IG1lbnNlbg== 65799 -IFNQTA== 65800 -IMK7LA== 65801 -IGFjZXQ= 65802 -X0JhY2s= 65803 -4KSV 65804 -YWFj 65805 -IFJpb3Q= 65806 -X0ZC 65807 -IFph 65808 -UGxhdGU= 65809 -IGxhYmVsVGV4dA== 65810 -INCy0YDQtdC8 65811 -aHRvbg== 65812 -IE1jQQ== 65813 -IEFwcGVuZGl4 65814 -IEtvaw== 65815 -IGludGVydmlld2luZw== 65816 -X3NwZWxs 65817 -IFN1YmplY3Rz 65818 -IGJ1cm5lcg== 65819 -5a+8 65820 -aWxsaWFu 65821 -IGJ1bXBz 65822 -UGFzc2Vk 65823 -IENvbnRyaWJ1dG9y 65824 -WW8= 65825 -Ymxh 65826 -IHNvdXQ= 65827 -LmV4Yw== 65828 -Tm90aWZpZXI= 65829 -c2hpdg== 65830 -LlVuaXRUZXN0aW5n 65831 -dWVsbGVz 65832 -X1NMRUVQ 65833 -CW9wdHM= 65834 -IHByZXNjcmlwdGlvbnM= 65835 -IHJldmlzZQ== 65836 -RURJVE9S 65837 -IGFubsOpZXM= 65838 -X3BrZw== 65839 -IFRyYWNrcw== 65840 -4LmI4Liy 65841 -PWZvcm1z 65842 -LlJVTg== 65843 -IGFzZWc= 65844 -IHDDoQ== 65845 -IGplcw== 65846 -R3Jl 65847 -YWNy 65848 -T2ZmaWNpYWxz 65849 -dWtlcw== 65850 -Y29tcGFuaWVz 65851 -XFF1ZXJ5 65852 -IFByaW50YWJsZQ== 65853 -5a6i 65854 -X1ZP 65855 -IGRlaXg= 65856 -IGRldmljZUlk 65857 -IGRpc3R1cmJhbmNl 65858 -bmlzdA== 65859 -Lmlzbw== 65860 -cGFyYWxsZQ== 65861 -LWRlc2NyaWJlZGJ5 65862 -IExpZg== 65863 -IGJyZWFzdGZlZWRpbmc= 65864 -IGZlbWluaXN0cw== 65865 -bGVncm91bmQ= 65866 -IGRhbWU= 65867 -IGNvbXB1bHNvcnk= 65868 -TUVSQ0hBTlRBQklMSVRZ 65869 -LXJlc3VsdHM= 65870 -Zm9ybWVkVVJMRXhjZXB0aW9u 65871 -OlsK 65872 -LWludGVyZXN0 65873 -IHPDpA== 65874 -IG5vc3RhbGdpYQ== 65875 -IGNsYXJpZmllZA== 65876 -IFBIT1RP 65877 -IHJldmlzaXQ= 65878 -IGNhcHN1bGVz 65879 -IHNoaW5lcw== 65880 -IGNyYWZ0c20= 65881 -c3ViamVjdHM= 65882 -ICAgICAgICAgICANCg== 65883 -5LiN6IO95Li656m6 65884 -IFNjaHdhcnR6 65885 -cmV1 65886 -IG1hZHJpZA== 65887 -LnBlbmRpbmc= 65888 -IExJTg== 65889 -IHVuc3Q= 65890 -CW12 65891 -IHZpdmFzdHJlZXQ= 65892 -IHNwb2ls 65893 -w7hq 65894 -64u5 65895 -IGJ1ZW5h 65896 -IGRpZ2l0YWxXcml0ZQ== 65897 -c3Vicw== 65898 -IFVOSVZFUlM= 65899 -IFN1aWNpZGU= 65900 -PEd1aWQ= 65901 -LmVsZW0= 65902 -X2NvbnN0cnVjdA== 65903 -IGFtaWRzdA== 65904 -IOuP 65905 -LWVzdGVlbQ== 65906 -IEludGVncml0eQ== 65907 -LmZtbA== 65908 -T3V0T2ZCb3VuZHNFeGNlcHRpb24= 65909 -LVNlbWl0aXNt 65910 -QmV0YQ== 65911 -LWdvaW5n 65912 -U2VnbWVudHM= 65913 -IE1hZQ== 65914 -IFBlcnNvbmFsaXR5 65915 -dXJiYXRpb24= 65916 -5Y+z 65917 -IHNlcnZpY2luZw== 65918 -IGJpcG9sYXI= 65919 -X1NUQUdF 65920 -LkpQRw== 65921 -Jyl9fSI+ 65922 -aXNobHk= 65923 -SVZFUlk= 65924 -IEluc3BpcmVk 65925 -LnNlcnY= 65926 -KGRhdGFz 65927 -IGRpdmlkZXM= 65928 -PFJlYWw= 65929 -dmVydHVyZQ== 65930 -IG1vdGl2YXRpb25z 65931 -dmVydGU= 65932 -RU5DSA== 65933 -ZmRz 65934 -IHJldm9sdA== 65935 -d2VidG9rZW4= 65936 -aW5zdGVhZA== 65937 -CW9wdA== 65938 -IE1hcmlqdWFuYQ== 65939 -X2FkYw== 65940 -YmFv 65941 -W1NlcmlhbGl6ZUZpZWxk 65942 -IGdyYWZmaXRp 65943 -LWFvcw== 65944 -ZW1pYWg= 65945 -IGbDrXM= 65946 -IGV0aGlj 65947 -J2FsbA== 65948 -OmtleQ== 65949 -65Ok 65950 -IHJlc3RyaWN0aW5n 65951 -IFhIVE1M 65952 -ZXJlbw== 65953 -dW5kb3M= 65954 -CWVuZGlm 65955 -WzosOiw= 65956 -IHN0ZWhlbg== 65957 -YWtoaXI= 65958 -IGp1aWNlcw== 65959 -ZGF0YVNvdXJjZQ== 65960 -X21r 65961 -LmRlbGV0ZWQ= 65962 -Q29uZ3Jlc3M= 65963 -aW1tZWw= 65964 -RWxlY3RyaWM= 65965 -YW9z 65966 -IE92ZXJsYXk= 65967 -IEFDTFU= 65968 -cm5k 65969 -ZXNzZXM= 65970 -IEx1eGVtYm91cmc= 65971 -cGFyc2VGbG9hdA== 65972 -IGd1dHM= 65973 -Y2xhc3NpZmllZA== 65974 -IGRlZlN0eWxl 65975 -IFRjcA== 65976 -cGVhdGluZw== 65977 -Q2hhcnRz 65978 -X3Vy 65979 -X2xhdGVzdA== 65980 -KSEK 65981 -Y2F0aW9u 65982 -LkdldGVudg== 65983 -KGxvb3A= 65984 -IHVubA== 65985 -X2R0eXBl 65986 -emXFhA== 65987 -KEpOSUVudg== 65988 -LmZldGNob25l 65989 -IHNpZ21vaWQ= 65990 -IE9MRA== 65991 -IE1pbmlzdA== 65992 -7YE= 65993 -IEvDtg== 65994 -IGZyYWN0aW9ucw== 65995 -IHNpeg== 65996 -PT09PT0K 65997 -LlByaW50V3JpdGVy 65998 -X0FkZHJlc3M= 65999 -IEF1ZGllbmNl 66000 -Q29tbw== 66001 -IEJydWlucw== 66002 -LmFjdGl2aXRpZXM= 66003 -IGFuY2VzdHJ5 66004 -0YPQu9GM0YI= 66005 -CVJldHVybg== 66006 -cHVu 66007 -IGdyYXBlcw== 66008 -SUxvZw== 66009 -IGRpam8= 66010 -IFBlcmtpbnM= 66011 -IFZNd2FyZQ== 66012 -X2F1dGhlbnRpY2F0ZWQ= 66013 -w650cmU= 66014 -b3ZlcndyaXRl 66015 -IEhk 66016 -IGdhbGF4aWVz 66017 -YWNodQ== 66018 -SHJlZg== 66019 -W0Q= 66020 -IHBhcmNl 66021 -TGF0TG5n 66022 -X3BhdHRlcm5z 66023 -IFNIT1JU 66024 -IHJ1bW91cnM= 66025 -Y291bnR5 66026 -IEdSSUQ= 66027 -IFsv 66028 -IFNreXJpbQ== 66029 -RGF0YUdyaWRWaWV3VGV4dEJveENvbHVtbg== 66030 -IGNlbg== 66031 -IGN1Y3VtYmVy 66032 -LklOVA== 66033 -X0NPTkZJUk0= 66034 -IGN0bA== 66035 -cGVybA== 66036 -aWxsb3M= 66037 -IEFDQQ== 66038 -IEdlb3JnZXRvd24= 66039 -X2NhbGxhYmxl 66040 -IENyYWZ0cw== 66041 -L2Nv 66042 -IGluYm91bmQ= 66043 -IFRlY2huaXF1ZXM= 66044 -c2V0Q2hlY2tlZA== 66045 -IHBuYW1l 66046 -Y29tcHV0 66047 -U3RlZWw= 66048 -IGhhbmRoZWxk 66049 -IEFsYW0= 66050 -YWJzdHJhY3RtZXRob2Q= 66051 -6aKR 66052 -SU5Z 66053 -YmF0dGxl 66054 -X0VWVA== 66055 -IGNldXg= 66056 -IGF0b2Y= 66057 -IEFieXNz 66058 -X3ZhbGlkYXRvcg== 66059 -IGhhaXJz 66060 -VmVydGV4QXR0cmliQXJyYXk= 66061 -IGNvbW1vbnM= 66062 -LWJpbmQ= 66063 -TXVp 66064 -IGNvc21ldGljcw== 66065 -IG1pcmFj 66066 -Lm1hcmtlcg== 66067 -U0NBTEU= 66068 -LldvcmQ= 66069 -LXVs 66070 -IERpdmVyc2l0eQ== 66071 -IEREUw== 66072 -LmN3ZA== 66073 -X3h5eg== 66074 -IENvbXB1dGVz 66075 -KGNsaWNrZWQ= 66076 -VEVNUExBVEU= 66077 -IHpvbmluZw== 66078 -IGZpbnM= 66079 -IFBK 66080 -ZXh0Vmlldw== 66081 -Q2hhcmFjdGVyaXN0aWM= 66082 -aWdhdG9ycw== 66083 -IHByb2NsYWlt 66084 -IHByaXN0aW5l 66085 -IGRhdGFzdG9yZQ== 66086 -IGRpc2NvdXJhZ2U= 66087 -X25zZWM= 66088 -IG5pbmV0ZWVudGg= 66089 -IGNlbHVp 66090 -Sm9uYXRoYW4= 66091 -IGFtcGg= 66092 -IENyb3NzaW5n 66093 -IEh1bWFucw== 66094 -IEJvb2tlcg== 66095 -w6JjZQ== 66096 -Z2V0UG9zdA== 66097 -IE1vbnRlcg== 66098 -IEZsYXZvcg== 66099 -TWVkaWFUeXBl 66100 -IuKAlA== 66101 -IEFyY2hhZQ== 66102 -QHJldHVybg== 66103 -LWF3YXJl 66104 -b3J1 66105 -LVRoZQ== 66106 -YW1wbGVk 66107 -S0Y= 66108 -LlRlbXA= 66109 -IERyZQ== 66110 -KHtf 66111 -cG9seWdvbg== 66112 -IMOm 66113 -IERlZmVuZGVy 66114 -77yY 66115 -Xyks 66116 -LlVuc3VwcG9ydGVk 66117 -X14o 66118 -KElEQw== 66119 -JHY= 66120 -IHdvcnRobGVzcw== 66121 -IFNFRw== 66122 -aWxpa2k= 66123 -Tm9BcmdzQ29uc3RydWN0b3I= 66124 -IE1lcmNo 66125 -IG5vcA== 66126 -IGZvcmdldHRpbmc= 66127 -IGRvcGFtaW5l 66128 -anVhbA== 66129 -ZW9u 66130 -IFJlYXNvbnM= 66131 -c29ydEJ5 66132 -KCctJyw= 66133 -LXN5bmM= 66134 -ZWNlZG9y 66135 -S1A= 66136 -KGNvb3Jk 66137 -KENoYXQ= 66138 -XCQ= 66139 -ZXN0cmluZw== 66140 -Y2Vm 66141 -LmhhbmRsZUVycm9y 66142 -24zYrw== 66143 -0YHQug== 66144 -IGhhbmRj 66145 -ZWxpamtl 66146 -IFNwaXI= 66147 -IEJ1Y2tz 66148 -IFFSZWN0 66149 -U2V0Rm9udA== 66150 -LmV4ZWNTUUw= 66151 -OjoKCg== 66152 -IHN1aWNpZGFs 66153 -c2VlaW5n 66154 -IGNpZGVy 66155 -UHJvZ3Jlc3NEaWFsb2c= 66156 -IG1vbGRpbmc= 66157 -CXRyYWNl 66158 -IGVtcGhhc2l6ZXM= 66159 -IG11bHRpcGxlcw== 66160 -X1BU 66161 -X091dHB1dA== 66162 -Y2FwaXRhbA== 66163 -TmVlZHM= 66164 -X0RJUkVDVElPTg== 66165 -LmlzVmlzaWJsZQ== 66166 -IHJlc3Rl 66167 -IG92YXI= 66168 -KHNoYXJlZA== 66169 -LWNvbXBvc2U= 66170 -LmJhY2t3YXJk 66171 -CXJlY3Q= 66172 -QW1hemluZw== 66173 -LmRpZFJlY2VpdmVNZW1vcnlXYXJuaW5n 66174 -U0VSVklDRQ== 66175 -IEluanVyeQ== 66176 -QnJhaW4= 66177 -IGF1c2dl 66178 -KHBl 66179 -Ly8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 66180 -b3JwdGlvbg== 66181 -X01BSUw= 66182 -b2hh 66183 -IHNubw== 66184 -IGJvaWxlZA== 66185 -aWxkZW5hZmls 66186 -IFdlbGZhcmU= 66187 -IFF1YXJ0eg== 66188 -IGNhcHRjaGE= 66189 -IFdFU1Q= 66190 -IE1hemU= 66191 -IGdyYXBoZW5l 66192 -IHBlcms= 66193 -IG1pc3RyZXNz 66194 -LkZvcm1TdGFydFBvc2l0aW9u 66195 -IGV4cGVyaW1lbnRhdGlvbg== 66196 -KikoKA== 66197 -IGJyb2FkY2FzdHM= 66198 -IHJlbW92ZUFsbA== 66199 -CUdVSQ== 66200 -5YOP 66201 -YWJjZGVmZ2hpamtsbW5vcA== 66202 -IHVuaW5z 66203 -QVNQ 66204 -K3c= 66205 -bXVy 66206 -IGRpbmU= 66207 -IGFyb3U= 66208 -IGVzY2FwZXM= 66209 -IFRvYmFjY28= 66210 -Lm5hbWVk 66211 -IFBhdHJlb24= 66212 -X0ZBQ0U= 66213 -X3NwaW5uZXI= 66214 -bW92aW5n 66215 -X3ZvdGVz 66216 -T2hpbw== 66217 -LmVuY29kaW5n 66218 -RGVncmVlcw== 66219 -IlRv 66220 -IHByZXN0aWdl 66221 -b3NwaGVyZQ== 66222 -IExhbmNhc3Rlcg== 66223 -77yX 66224 -IG9uQ2FuY2Vs 66225 -IEhJUw== 66226 -0J7RiNC40LHQutCw 66227 -IG9yY2hlc3Ry 66228 -IHJlZnJlc2hlZA== 66229 -RGF0aW5n 66230 -KG11 66231 -IEplZA== 66232 -IEVkaXRvcmlhbA== 66233 -U2V0QnJhbmNoQWRkcmVzcw== 66234 -Q3BwVHlwZURlZmluaXRpb24= 66235 -IEJyb254 66236 -IGdhdGhlcmluZ3M= 66237 -ICcnDQo= 66238 -cG9zdERhdGE= 66239 -IEZyYW0= 66240 -Q2xpcGJvYXJk 66241 -IFhQYXRo 66242 -cmF5cw== 66243 -IGJha2VyeQ== 66244 -IHJvd0NvdW50 66245 -IGxvd3M= 66246 -YW5kV2hlcmU= 66247 -X3ZlcnNpb25z 66248 -IEd1bm4= 66249 -IHdlZXI= 66250 -IGNvbnRleHR1YWw= 66251 -IEtleUNvZGU= 66252 -IFNhc2thdGNoZXdhbg== 66253 -IFBoaWxseQ== 66254 -IE1vdXRo 66255 -IGRvUG9zdA== 66256 -IHBlcmNlbnRpbGU= 66257 -IGJ1ZmZlclNpemU= 66258 -KGZyZXE= 66259 -JHNtYXJ0eQ== 66260 -aWVydGU= 66261 -aXNzYW50 66262 -X2Zwcw== 66263 -IGludGltYWN5 66264 -X2Jvb2tpbmc= 66265 -IGRlY29tcG9zaXRpb24= 66266 -dW5pY2lwaW8= 66267 -IE5TSW5kZXhQYXRo 66268 -IEtS 66269 -IHR1cmJpbmU= 66270 -LXByb20= 66271 -X0NBUlQ= 66272 -KGNvb3Jkcw== 66273 -ZWNvbQ== 66274 -IGNvd2FyZA== 66275 -IHdheXBvaW50 66276 -LUNvbGE= 66277 -IHByb2ZvdW5kbHk= 66278 -IEVSUA== 66279 -Ym91bmRhcnk= 66280 -IHBvb3Jlcg== 66281 -L2V4YW1wbGU= 66282 -IHJlbmNvbnRy 66283 -IG5pY2Vy 66284 -54E= 66285 -LWNoYWlu 66286 -IEVudGl0eVN0YXRl 66287 -IGdyYWRpbmc= 66288 -QUxJR04= 66289 -IFBpY2tz 66290 -LmFr 66291 -LXZlY3Rvcg== 66292 -IEVudHJpZXM= 66293 -IFNlcmdpbw== 66294 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 66295 -T0RC 66296 -IOW9 66297 -IGNvcm9uYXJ5 66298 -IHNoYXZlZA== 66299 -IGFxdWU= 66300 -ZW1wbG95ZXI= 66301 -IHBhcmNo 66302 -IG1lYXN1cmFibGU= 66303 -IGJvaXM= 66304 -am9pbmluZw== 66305 -IHZvbGNhbm8= 66306 -Ok0= 66307 -LnRocmVzaG9sZA== 66308 -IERveWxl 66309 -dmVyYm9zaXR5 66310 -IOKWug== 66311 -IHNwb3VzZXM= 66312 -IHJlc3VtZXM= 66313 -TmF0 66314 -ek0= 66315 -X0VuYWJsZQ== 66316 -IFVTRUQ= 66317 -IENhcmV5 66318 -CWZw 66319 -UGF0cmljaw== 66320 -IE9zdw== 66321 -UG9zc2libGU= 66322 -LmxlYWRpbmc= 66323 -YWhydW5n 66324 -4pmqCgo= 66325 -CQkJCQkJCQkJIA== 66326 -44CC44CM 66327 -LmFkZEVkZ2U= 66328 -IGVjeA== 66329 -J0xCTA== 66330 -IFRDTA== 66331 -IGJpcnRocw== 66332 -IHRoZWF0cmljYWw= 66333 -IHBpag== 66334 -Z3JlYXRlcg== 66335 -IEZTdHJpbmc= 66336 -QkVE 66337 -7ZmY 66338 -LkNhc3Q= 66339 -Q1g= 66340 -L01haW4= 66341 -cGVhdGVy 66342 -IHBlcnN1YXNpdmU= 66343 -Y29udG8= 66344 -eGxzeA== 66345 -X0FCUw== 66346 -IEJ1bg== 66347 -bWFuYWdlZFR5cGU= 66348 -0LPQvg== 66349 -IFNjYWxh 66350 -cmFkb3I= 66351 -IHJlY29nbml6YWJsZQ== 66352 -dHJ1 66353 -IHRq 66354 -XE1hcHBpbmc= 66355 -X0JPQVJE 66356 -IHRvSnNvbg== 66357 -IGJvd2Vs 66358 -KWQ= 66359 -J30p 66360 -KGhXbmQ= 66361 -aHJz 66362 -Y2FudA== 66363 -X18oKQoK 66364 -IGludGVycm9nYXRpb24= 66365 -bGljYXRpdmU= 66366 -CQkJCgo= 66367 -IFR3aW5z 66368 -IEFP 66369 -QmlyZA== 66370 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 66371 -cGVyaGFwcw== 66372 -b2ZpbGU= 66373 -IHBlbmM= 66374 -IHRyZWVOb2Rl 66375 -IHRvcGljYWw= 66376 -LXByaXZhdGU= 66377 -54m5 66378 -IERpc2N1c3M= 66379 -IGRlc24= 66380 -UnVh 66381 -LlZFUlRJQ0FM 66382 -44CN44Go 66383 -SUZPUk0= 66384 -IGNvdXJ0eWFyZA== 66385 -INGB0LXRgA== 66386 -ICMjIwo= 66387 -IGVtcG93ZXJpbmc= 66388 -IEZhY2lsaXRpZXM= 66389 -XCIsXA== 66390 -vZQ= 66391 -Ok9iamVjdA== 66392 -IFZvdGVz 66393 -aXNlbA== 66394 -IGV1Y2g= 66395 -b3JzdA== 66396 -KENsb25l 66397 -LmNvb2tpZXM= 66398 -JHRtcA== 66399 -KGluZGljZXM= 66400 -ZXJnZW5jeQ== 66401 -IHBsYWd1ZWQ= 66402 -IERpYQ== 66403 -eWNsaWM= 66404 -fSkp 66405 -6rK9 66406 -IGR1ZWw= 66407 -IGhldGVyb3NleHVhbA== 66408 -LmFkZENvbXBvbmVudA== 66409 -U0VDUkVU 66410 -bGVybw== 66411 -Y29uc3RyYWludHM= 66412 -IGdldENvbm5lY3Rpb24= 66413 -IExlYmVucw== 66414 -IFBvbg== 66415 -IENocm9uaWNsZXM= 66416 -ICAgICAgICAgICAgICAgICAgICAgICAgDQo= 66417 -IE1vdXJpbmhv 66418 -IG9jY3VwYW5jeQ== 66419 -X3NsYXZl 66420 -T1JJWkVE 66421 -CVk= 66422 -LmhpZ2hsaWdodA== 66423 -X3NlbnNpdGl2ZQ== 66424 -IHNwZWN0cm8= 66425 -LmVuY3J5cHQ= 66426 -IHNwb2lsZXJz 66427 -LlNpemVNb2Rl 66428 -IHByb2Zlc3Npb25hbGlzbQ== 66429 -Pklu 66430 -RXhwaXJlcw== 66431 -QXU= 66432 -IEhWQUM= 66433 -cmVsYXRpb25z 66434 -IEFUSw== 66435 -X0dFTkVSQUw= 66436 -IFNpZ2h0 66437 -IGtpdGNoZW5z 66438 -OlJlZ2lzdGVy 66439 -IGVkbQ== 66440 -IHRvbGVyYXRlZA== 66441 -IFNFU1NJT04= 66442 -aWVyeg== 66443 -IElOU1Q= 66444 -LnBhdGhz 66445 -IHBlcnBldHJhdG9ycw== 66446 -ZWJw 66447 -cGVjdGluZw== 66448 -ZWR1Y2F0ZWQ= 66449 -IFBpb25lZXI= 66450 -X1JFVg== 66451 -IGJ1c3R5 66452 -c3RhdHVzZXM= 66453 -UmVzcG9uZA== 66454 -c2h1ZmZsZQ== 66455 -IFRpbmRlcg== 66456 -RXhhY3RseQ== 66457 -aWxsaXNlY29uZA== 66458 -INC30L3QsNGH0LXQvdC40LU= 66459 -KEFjY291bnQ= 66460 -LiY= 66461 -aXpy 66462 -YXNzdW1pbmc= 66463 -CU9wdGlvbmFs 66464 -U2VuaGE= 66465 -IGVucm9s 66466 -dHVy 66467 -IGFycm9nYW50 66468 -IEpPYmplY3Q= 66469 -b2xpdGhpYw== 66470 -bWFwcGVk 66471 -IHRpcHBlZA== 66472 -LlVQREFURQ== 66473 -w6htZXM= 66474 -R05VQw== 66475 -V1g= 66476 -IG1vbmtz 66477 -LmJvcmRlcldpZHRo 66478 -IFNodXRkb3du 66479 -IEhhcm1vbnk= 66480 -Y2xhc3NpZmljYXRpb24= 66481 -IGRlcXVldWVSZXVzYWJsZUNlbGw= 66482 -IF07DQo= 66483 -Lkdlbg== 66484 -IGxhdm9ybw== 66485 -IExlb25hcmRv 66486 -ICYp 66487 -IGRlcG9pcw== 66488 -IFZvbHQ= 66489 -RXRo 66490 -IExlb25l 66491 -IE5lZGVybGFuZA== 66492 -IEVYVFJB 66493 -UmVzb2x2ZWQ= 66494 -IHBlbmluc3VsYQ== 66495 -X1ZN 66496 -R2Vy 66497 -2KfYrw== 66498 -LnByb21wdA== 66499 -LmFsaWdu 66500 -aW5nZ2E= 66501 -ZmlsbXM= 66502 -SEFORExF 66503 -IGNhcnRz 66504 -KFNvbWU= 66505 -PEF1ZGlv 66506 -IGVubGFyZ2VtZW50 66507 -IGdyb2Nlcmllcw== 66508 -LWhvbGRlcg== 66509 -IGlycml0YXRpb24= 66510 -Q29tbXVuaWNhdGlvbg== 66511 -IHByaW1hcmllcw== 66512 -aHR1Yg== 66513 -X2luaWNpbw== 66514 -IGNvb3JkaW5hdGluZw== 66515 -KHF1 66516 -IGZhaXM= 66517 -IHZpc3Rv 66518 -Z3VpZGVk 66519 -IHZsYW4= 66520 -IGVzcHJlc3Nv 66521 -w6h0ZQ== 66522 -c2VoZW4= 66523 -X3Blbmc= 66524 -IHJvb2Zpbmc= 66525 -IEFsaXZl 66526 -QXhpc1NpemU= 66527 -IHN0dW4= 66528 -IHJlc3RlZA== 66529 -dWxsZXRz 66530 -IE1hbGF5c2lhbg== 66531 -LFVuaXR5RW5naW5l 66532 -IGVudnk= 66533 -J107DQoNCg== 66534 -IE9zdA== 66535 -X2p1bXA= 66536 -IGNvbnRyYXNlw7Fh 66537 -Ing= 66538 -CVBhZ2U= 66539 -KVsi 66540 -IFNJUA== 66541 -IEdlb2dyYXBoaWM= 66542 -IGNhdWN1cw== 66543 -X1RFUg== 66544 -4oCdOw== 66545 -UG9zdEV4ZWN1dGU= 66546 -aW1zaG93 66547 -IENPTVBBTlk= 66548 -IE5lYWw= 66549 -IEhlYXJpbmc= 66550 -KGFjdG9y 66551 -Qmlk 66552 -LlBS 66553 -LlByb2R1Y3Rz 66554 -IEVtbQ== 66555 -IOab 66556 -IHB1bHNlcw== 66557 -X0VW 66558 -L2V4cA== 66559 -X21vdGlvbg== 66560 -IGdiYw== 66561 -IG5hdmlnYXRpb25Db250cm9sbGVy 66562 -IENvdXJ0cw== 66563 -IEljb25EYXRh 66564 -d3U= 66565 -X3Jm 66566 -IFJhZ2U= 66567 -LWZsYXQ= 66568 -IEhpbXNlbGY= 66569 -X2NodW5rcw== 66570 -IG92ZXJzaA== 66571 -IGNpZg== 66572 -KElz 66573 -cGVha2Vy 66574 -IENQVXM= 66575 -aXJlY3Rvcg== 66576 -LHRpdGxl 66577 -LnNldERlc2NyaXB0aW9u 66578 -IGVhcnRocXVha2Vz 66579 -IHdu 66580 -Z2x5cGg= 66581 -dWx1bWk= 66582 -IHNwZWVkeQ== 66583 -IGVzcGFjaW8= 66584 -IGVtdWxhdGU= 66585 -IFwiJA== 66586 -X0lORg== 66587 -Y2FsbG9j 66588 -LXF1ZXJ5 66589 -KHZhbHM= 66590 -IHNlYWI= 66591 -IGhhdm9j 66592 -IEludGVyc3RhdGU= 66593 -IHRyaWFuZ3VsYXI= 66594 -YmluZGluZ3M= 66595 -CQkJCQkgICAgIA== 66596 -IAkg 66597 -YmNyeXB0 66598 -IGNyZWRpdG9ycw== 66599 -IHNlbWlm 66600 -bGxl 66601 -aWVuemE= 66602 -IEtlbGxlcg== 66603 -IG1vbnN0cg== 66604 -IE1hcmNvcw== 66605 -KHJlaW50ZXJwcmV0 66606 -IGhpdmU= 66607 -U2Ny 66608 -X2hyZXN1bHQ= 66609 -IOyhsA== 66610 -IFNxbERhdGFSZWFkZXI= 66611 -YW5ub3VuY2U= 66612 -X3ByZWZlcmVuY2Vz 66613 -IHRydXN0cw== 66614 -RXJvdA== 66615 -LXdvcmtlcg== 66616 -IHR3ZWVu 66617 -IFN0cmVldHM= 66618 -gq3soJw= 66619 -IEZyYW56 66620 -IOKApi4= 66621 -VUlUZXh0RmllbGQ= 66622 -LmdldEl0ZW1z 66623 -IHRvbHVh 66624 -4oCcT3Vy 66625 -IHPhu5E= 66626 -IHZpcnR1ZXM= 66627 -IHBvdWx0cnk= 66628 -PXJvdw== 66629 -Y29kZWQ= 66630 -Tm9TdWNo 66631 -IGtvZA== 66632 -bHNp 66633 -IGtldG8= 66634 -IGdyb3VwTmFtZQ== 66635 -YXNu 66636 -IHVuY29tcA== 66637 -IHRleHRpbGU= 66638 -dG9vbFN0cmlw 66639 -LlBvcGVu 66640 -IHByb3N0aXR1dGU= 66641 -IHByb21vdGVy 66642 -Ijt9Cg== 66643 -IGNvbGxpZGVy 66644 -QnJva2Vy 66645 -ZGF0YXNldHM= 66646 -CU5TU3RyaW5n 66647 -YW5nbGVy 66648 -UklFUw== 66649 -YXRvbXM= 66650 -IHJlbmRleg== 66651 -YXBv 66652 -IOuE 66653 -Lmdj 66654 -IFNPTUU= 66655 -IGZnZXRz 66656 -R0xF 66657 -IHphbA== 66658 -IE9wcG9zaXRpb24= 66659 -aGFuZGxlU3VibWl0 66660 -X21hdGg= 66661 -IHNwcmU= 66662 -IHNob3J0ZW5lZA== 66663 -IGNhdmVz 66664 -U01T 66665 -LWNvbnNjaW91cw== 66666 -IFNhdmVz 66667 -LkJhY2tncm91bmRJbWFnZUxheW91dA== 66668 -IGVsZWN0cm9tYWduZXRpYw== 66669 -KGl0ZXJhdG9y 66670 -IHVuYmU= 66671 -amVjdG9yaWVz 66672 -IG1lZGlhbnRl 66673 -IMOubnQ= 66674 -Iiwt 66675 -IEFTTQ== 66676 -6K6w5b2V 66677 -IGNvbmZpbmVtZW50 66678 -4oCmCgoK 66679 -RXhjZXB0aW9ucw== 66680 -LW1ham9y 66681 -IFZhbmlsbGE= 66682 -IExPQ0FUSU9O 66683 -IGVsdXNpdmU= 66684 -VUFSSU8= 66685 -IElOTElORQ== 66686 -IHByb2R1Y3ROYW1l 66687 -X3F1ZXJpZXM= 66688 -Li4uIjsK 66689 -IFhpYW8= 66690 -V2luZG93VGl0bGU= 66691 -bGV0dGVz 66692 -IHBlcnBldHVhbA== 66693 -U2V2ZXJpdHk= 66694 -IEFjaGlldmVtZW50 66695 -w6JuY2lh 66696 -IHJlbWluZGVycw== 66697 -c29ydGFibGU= 66698 -IGFmZm9yZGVk 66699 -IGluZmx1ZW5jaW5n 66700 -IFR1bm5lbA== 66701 -LmxlYXJuaW5n 66702 -IFF1w6k= 66703 -cGhldGFtaW5l 66704 -LkJBRA== 66705 -Lm1ldGFtb2RlbA== 66706 -LWRldmljZQ== 66707 -IEtvbnRha3Q= 66708 -4pSB4pSB 66709 -LXN1bW1hcnk= 66710 -KCc8Pw== 66711 -KTw9 66712 -IHdpc2VseQ== 66713 -X290 66714 -Om1vZGVs 66715 -IFVX 66716 -IE9wZW5TU0w= 66717 -IEpwYVJlcG9zaXRvcnk= 66718 -Q29uZXhpb24= 66719 -VE9U 66720 -LmNyZWF0ZWRBdA== 66721 -KHRyYWluaW5n 66722 -IGJpc2hvcHM= 66723 -IHZlbnR1cmVz 66724 -LkVucXVldWU= 66725 -IFRoZXJtYWw= 66726 -IEJyZXdlcnk= 66727 -b3Rlbg== 66728 -IEZhdGFs 66729 -X3N1cHBseQ== 66730 -IGNvbmRpdGlvbmVk 66731 -IHN1cGVyaW9yaXR5 66732 -IElicmFoaW0= 66733 -IGNvcnBv 66734 -dW91c2x5 66735 -IFByYWN0aWNhbA== 66736 -Ly9b 66737 -IEFmcmljYW5z 66738 -IEJhaHJhaW4= 66739 -IHN0ZXJpbA== 66740 -IENsYXNzTm90Rm91bmRFeGNlcHRpb24= 66741 -LlJlZ2lvbg== 66742 -IHRyYW5zaXRpb25hbA== 66743 -IGludGVycHJldGluZw== 66744 -LlNvdW5k 66745 -IGZyb250YWw= 66746 -IGhhcnZlc3Rpbmc= 66747 -fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4= 66748 -YXRhaXJl 66749 -Lkh0dHBTdGF0dXM= 66750 -S00= 66751 -IEVyb3Rpc2NoZQ== 66752 -IGVyb3Rpc2tl 66753 -RmlnaHQ= 66754 -UGFja2FnZU5hbWU= 66755 -IENBQ0hF 66756 -d2luZ0NvbnN0YW50cw== 66757 -IFppbW1lcm1hbg== 66758 -L2Nhcg== 66759 -IFF1cmFu 66760 -TWV0YWw= 66761 -IHVzZXJNYW5hZ2Vy 66762 -IG1hc3Rlcnk= 66763 -KFVVSUQ= 66764 -IHZpZXdXaWxsQXBwZWFy 66765 -IHN1bW1lZA== 66766 -KC0o 66767 -ICAgICAgIAoK 66768 -VGFrZW4= 66769 -IGNsb2Nrd2lzZQ== 66770 -IENhZsOp 66771 -KGxldHRlcg== 66772 -IENyb3NzUmVm 66773 -IEFzdG9u 66774 -IEFzc2VtYmx5VmVyc2lvbg== 66775 -6Z2e 66776 -bnRz 66777 -ICQoJ1s= 66778 -X1JBVElP 66779 -aWNpZW50ZQ== 66780 -IHJpY2h0aWc= 66781 -IHBlZGln 66782 -KGl4 66783 -0YHRi9C7 66784 -QXNzaWduYWJsZUZyb20= 66785 -Ym91bmRlZA== 66786 -IGFsa2Fs 66787 -X3ByaWNlcw== 66788 -IGfFgg== 66789 -YW5jaGlzZQ== 66790 -X3JlY2VpdmVy 66791 -SUdBVElPTg== 66792 -X3B1bGw= 66793 -IFN0YXRpc3RpY2Fs 66794 -X3Rvb2xiYXI= 66795 -YW1pZGU= 66796 -IEFzeW5jVGFzaw== 66797 -cmV0YQ== 66798 -IOyi 66799 -IFJFQUxMWQ== 66800 -IGJ1cnN0cw== 66801 -IElucXVpcnk= 66802 -IGJpZ290 66803 -c2FuaXRpemU= 66804 -IEhvbWVy 66805 -UXXDqQ== 66806 -IFJvdXRpbmc= 66807 -LmNvbGxlY3Rpb25WaWV3 66808 -IEJpbGxpb24= 66809 -U1RSVUNUT1I= 66810 -LmVqYg== 66811 -IGVuY2g= 66812 -LnNldFRpbWVvdXQ= 66813 -UnVi 66814 -LXJvYWQ= 66815 -Lm91dHB1dHM= 66816 -Y29udGVzdA== 66817 -IHNwaGVyZXM= 66818 -IHJlc3VycmVjdA== 66819 -Ii4i 66820 -IElyaXM= 66821 -IOya 66822 -IFhL 66823 -IFJhcml0eQ== 66824 -IElTZXJ2aWNl 66825 -YXRoYQ== 66826 -IOWH 66827 -IHByZXZhaWw= 66828 -CXBw 66829 -Lkxv 66830 -Z2V0V2lkdGg= 66831 -IHd3 66832 -IHdpY2h0aWc= 66833 -QEdldHRlcg== 66834 -IEpheXM= 66835 -IHNwZWN1bGF0aXZl 66836 -KGF0dA== 66837 -IHRlZGlvdXM= 66838 -IHNjcmF0Y2hlcw== 66839 -IHBlbMOtY3Vs 66840 -IGJvcm91Z2g= 66841 -IG3Dsw== 66842 -UmVwcmVzZW50 66843 -YXRvcml1bQ== 66844 -KENhbWVyYQ== 66845 -IGNvbHVtbk5hbWU= 66846 -IHJlaXRlcmF0ZWQ= 66847 -IENhc3Rpbmc= 66848 -LmdldEhlYWRlcg== 66849 -IOKAnFs= 66850 -IEp1aWNl 66851 -Y2h1 66852 -LkhUTUw= 66853 -IEFudHdvcnQ= 66854 -R0x1aW50 66855 -CUl0ZXJhdG9y 66856 -IEFOQUw= 66857 -IHVucG9wdWxhcg== 66858 -KExvY2FsZQ== 66859 -IG1pdGlnYXRpb24= 66860 -IGFkcmVz 66861 -4bq3 66862 -fSx7Cg== 66863 -IFNjaHdhcg== 66864 -X1BBSVI= 66865 -PigpLAo= 66866 -b3V2 66867 -IEFsZg== 66868 -eEVG 66869 -55yB 66870 -IGVzY3Jp 66871 -TE9VUg== 66872 -U0VMRg== 66873 -IFRtYXg= 66874 -VHJl 66875 -bG90cw== 66876 -ICguLi4p 66877 -XSsk 66878 -IGFtZXJpYw== 66879 -L3JlZmVyZW5jZQ== 66880 -IE9keXNzZXk= 66881 -IE1pbmVz 66882 -IGFnb3Jh 66883 -IHByb3BoZWN5 66884 -IE9wcG9ydHVuaXRpZXM= 66885 -cHJvZmVzc2lvbmFs 66886 -KHByb3h5 66887 -cGhhbnVtZXJpYw== 66888 -IEVkaXRlZA== 66889 -b2xvZ25h 66890 -LmlzT3Blbg== 66891 -KHZlcnRpY2Vz 66892 -IFJpY2t5 66893 -X292ZXJsYXA= 66894 -Pjs= 66895 -LkRPTQ== 66896 -e31f 66897 -IENPTVBVVA== 66898 -cmVkaXJlY3RUbw== 66899 -IHNoYWtlbg== 66900 -IHJhdGlvbg== 66901 -IG5lbGw= 66902 -X2Jj 66903 -IE5lcg== 66904 -YW5kUmV0dXJu 66905 -IGVyZWN0ZWQ= 66906 -Q2hpZWY= 66907 -IGRpbmVybw== 66908 -IGphc21pbmU= 66909 -LS0tLS0tLS0tLS0tLQo= 66910 -ZmFybQ== 66911 -IEhhdGU= 66912 -VEFTSw== 66913 -QU5ORVI= 66914 -J11dXQo= 66915 -IE5pZ2Vs 66916 -aGliaXQ= 66917 -IFFUZXh0 66918 -Lkxlbg== 66919 -IHRlxbw= 66920 -c2xpZGVz 66921 -ZmVsdA== 66922 -IFJFVg== 66923 -X2hvbGQ= 66924 -IENvdXBsZQ== 66925 -ZXNjYXBlZA== 66926 -LWV4cG9ydA== 66927 -Pkk= 66928 -ZXdpc2g= 66929 -KEFwaQ== 66930 -ICghWw== 66931 -Tm91cw== 66932 -T1RPUg== 66933 -IHNlYWxpbmc= 66934 -V2ll 66935 -IGthbm5zdA== 66936 -K3htbA== 66937 -IG14QXJyYXk= 66938 -IGFkbWlyYXRpb24= 66939 -Lm5i 66940 -IGpld2Vs 66941 -LlRlYW0= 66942 -IHByb3NlY3V0ZQ== 66943 -LnhtbGJlYW5z 66944 -Y2h3 66945 -KGJhY2tncm91bmQ= 66946 -IEF2aXY= 66947 -CWZpbGw= 66948 -IGRpc3Bhcml0eQ== 66949 -4Lo= 66950 -X0FQUEVORA== 66951 -IFB2UA== 66952 -44OQ 66953 -IFZpdmU= 66954 -IGdyYW5kc29u 66955 -LmFkZEVsZW1lbnQ= 66956 -QXRvbWlj 66957 -IHByaW1hcnlLZXk= 66958 -IGNvbnRpbmVudHM= 66959 -IEZ1Y2tpbmc= 66960 -JScK 66961 -QG1haWw= 66962 -IGN1bHR1cmFsbHk= 66963 -YW5nYW5lc2U= 66964 -7KCE 66965 -Zm9sbG93ZXJz 66966 -IHVybg== 66967 -IHJhY2tz 66968 -IFNBRkU= 66969 -Ly8NCg0K 66970 -KCIvew== 66971 -X0lOSVRJQUw= 66972 -X1Jlc3BvbnNl 66973 -RXZlbnREYXRh 66974 -Jz4k 66975 -c3RhcnRz 66976 -4Kk= 66977 -IHRoYWltYXNzYWdl 66978 -IHNwZWNpYWxpemF0aW9u 66979 -IOyEpOyglQ== 66980 -ZWRv 66981 -IGNvbXBlbnNhdGVk 66982 -X2NoYXJzZXQ= 66983 -fS57 66984 -L2VudGl0aWVz 66985 -X2Zr 66986 -LS0tLS0tCgo= 66987 -YXNjYXI= 66988 -IGNlbGxGb3JSb3dBdEluZGV4UGF0aA== 66989 -IFByb3Bvc2Fs 66990 -IE90dG8= 66991 -IF9fX19f 66992 -ICIqIg== 66993 -IHRvb2xraXQ= 66994 -IGV4cGVjdGFuY3k= 66995 -RG93bkxpc3Q= 66996 -LWRh 66997 -IHByb3ZvY2F0aXZl 66998 -IG1laW8= 66999 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ== 67000 -KCgpPT57Cg== 67001 -JGxpbms= 67002 -aW5jYXJl 67003 -IGljeQ== 67004 -IEhpc3Q= 67005 -QWNjZXB0ZWQ= 67006 -IGNsb25lcw== 67007 -IFFB 67008 -IGNvbmZvcnQ= 67009 -IHByb3ByaW8= 67010 -IFZvZw== 67011 -KG1hcms= 67012 -X1NlYXJjaA== 67013 -IGVuZHdoaWxl 67014 -ICQj 67015 -44GX44GL 67016 -X0xU 67017 -SW5zdGFuY2VJZA== 67018 -YmFyZA== 67019 -cm5l 67020 -cmVnb3I= 67021 -IG5vcmdl 67022 -XDo= 67023 -0YDRg9C3 67024 -LmJ0bkFkZA== 67025 -IHBpbGxvd3M= 67026 -IFBhcmFtZXRlckRpcmVjdGlvbg== 67027 -SGFuZGxlcw== 67028 -IGRlYWxpbmdz 67029 -IGNvbnZleA== 67030 -IENoYXJpdHk= 67031 -Lk51bWVyaWNVcERvd24= 67032 -IFNrZWxldG9u 67033 -IFp1Y2tlcmJlcmc= 67034 -ZXNlbg== 67035 -IEZBQQ== 67036 -X3N0ZQ== 67037 -IGh1bWlk 67038 -am0= 67039 -Y2hn 67040 -LmdldExvY2Fs 67041 -IHRhbmRlbQ== 67042 -aXN0bGVz 67043 -X210 67044 -LmFjY291bnRz 67045 -IEluc3BlY3Rpb24= 67046 -IEZyYXVk 67047 -IGvDvA== 67048 -IHN5bmNocm9ub3Vz 67049 -IFJpY2FyZG8= 67050 -IEh1ZQ== 67051 -IENvbm5lY3Rpb25z 67052 -SU1FTlQ= 67053 -b2NoYXN0aWM= 67054 -XGRhdGE= 67055 -IEVudGVycHJpc2Vz 67056 -LXNpbXBsZQ== 67057 -IGltYWdlRGF0YQ== 67058 -IFVtYg== 67059 -LXNjcmlwdA== 67060 -L2dlbmVyYWw= 67061 -QVBU 67062 -IFR1dA== 67063 -aW1pemF0aW9u 67064 -IGlkYWRl 67065 -IEtlbQ== 67066 -ZWxzaWY= 67067 -LkFMSUdO 67068 -IFRvcmllcw== 67069 -IEJhc2ls 67070 -b2dvbmFs 67071 -aGFjaw== 67072 -TnVsbE9yRW1wdHk= 67073 -IiksCgo= 67074 -44OD44OI 67075 -ICclJw== 67076 -X1JG 67077 -ZWdvdA== 67078 -LmFzcGVjdA== 67079 -KFByb2plY3Q= 67080 -TEVOR1RI 67081 -cGxlbWVudGFyeQ== 67082 -X3ByZWRz 67083 -IEhvbGRz 67084 -Y2Fycmllcg== 67085 -CWxheWVy 67086 -QXR0YWNoZWQ= 67087 -LXByZXNpZGVudA== 67088 -aW5kaA== 67089 -J10uJyI= 67090 -LkFDQ0VTUw== 67091 -IENFTlRFUg== 67092 -UXVhbGlmaWVk 67093 -IG9zdHI= 67094 -LlN5bWJvbA== 67095 -dGFodW4= 67096 -IExBTkc= 67097 -X2J1c2luZXNz 67098 -CVN0YXJ0 67099 -ZXJyZQ== 67100 -IGFzaGVz 67101 -IEFkdmVydGlzZW1lbnQ= 67102 -Lkhvdw== 67103 -IC8vLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 67104 -IG9ibGl2 67105 -IGJsZWVk 67106 -IHN2bw== 67107 -Lm5vZGVOYW1l 67108 -IGl0ZW1OYW1l 67109 -IEJBTks= 67110 -w61jdWxvcw== 67111 -IEVtbXk= 67112 -IERvbWluaWNhbg== 67113 -JylbJw== 67114 -IHJlYWxsb2M= 67115 -dWxzZXM= 67116 -6L6T5Ye6 67117 -IE9mZmVyaW5n 67118 -64ql 67119 -LXByb2dyYW0= 67120 -INGB0L7QvtCx0Yk= 67121 -TU9W 67122 -IG5vZGVJZA== 67123 -0LXQvw== 67124 -Zmx1aWQ= 67125 -IHRlYXNl 67126 -w7hyZQ== 67127 -IGNvbXJhZGVz 67128 -IHVucmVsaWFibGU= 67129 -IHBvc3RJZA== 67130 -Z2V0SUQ= 67131 -b2dyYXBocw== 67132 -VGFuaw== 67133 -IFFWRVJJRlk= 67134 -IGZsb2F0ZWQ= 67135 -X1RISVM= 67136 -Y2ltaWVudG8= 67137 -IE5pY2Fy 67138 -c2hy 67139 -Qm91bmRpbmdCb3g= 67140 -IGlub3JkZXI= 67141 -IEdsb3Nz 67142 -V2l0aFRpdGxl 67143 -dW5jaW8= 67144 -IHBlcnNpc3Rz 67145 -IGRpcmVjdHM= 67146 -YWNjacOzbg== 67147 -U2FtcGxlcg== 67148 -IGJsYWNrbGlzdA== 67149 -IGFEZWNvZGVy 67150 -IGludm9rZXM= 67151 -X3NraW4= 67152 -Pklm 67153 -dHJ1bmNhdGU= 67154 -LlNpbg== 67155 -c29vbg== 67156 -IGRpc2Zy 67157 -CVZlYw== 67158 -IyNf 67159 -LnNjaG9vbA== 67160 -IGJsaW5kcw== 67161 -IGFjYWI= 67162 -IHBhdGhldGlj 67163 -IHZvbGNhbmlj 67164 -IHJkZg== 67165 -IGN1bHRpdmF0ZWQ= 67166 -IFVJTmF2aWdhdGlvbkNvbnRyb2xsZXI= 67167 -IGlwdA== 67168 -IGdsYW5k 67169 -IGV2aWRlbnRseQ== 67170 -UGh5cw== 67171 -IHN3YW1w 67172 -IGltYWdlTmFtZQ== 67173 -LkxheWVy 67174 -dWZl 67175 -LFsn 67176 -IENyaW1zb24= 67177 -6YCg 67178 -PGZvb3Rlcg== 67179 -IGJpa2luZw== 67180 -INC00LDQvdC90YvQtQ== 67181 -bW92ZXM= 67182 -Y3Jj 67183 -aWxsYXRpb24= 67184 -IGxhdXJl 67185 -0YDQsNCx0L7Rgg== 67186 -0YPQug== 67187 -IENhaW4= 67188 -IHB5cw== 67189 -IGNvbGxpZGU= 67190 -IHxffA== 67191 -KHNwYW4= 67192 -IGdpbmc= 67193 -IG9iZWRpZW5jZQ== 67194 -b3V0ZXJz 67195 -U29vbg== 67196 -IFdoaXRuZXk= 67197 -IEltcG9ydHM= 67198 -OlVJVGFibGVWaWV3 67199 -KiY= 67200 -IGJr 67201 -V2l0aEVycm9y 67202 -LWV4dA== 67203 -X1JET05MWQ== 67204 -X3RyYWNraW5n 67205 -bm9vcGVuZXI= 67206 -w7xucw== 67207 -IEd0a1dpZGdldA== 67208 -c2ti 67209 -U0FWRQ== 67210 -T2Jz 67211 -KCcuJylb 67212 -IGF1dGhvcmVk 67213 -LS8= 67214 -TG91aXM= 67215 -LmdldE91dHB1dFN0cmVhbQ== 67216 -IGdlbmVyYWxpemVk 67217 -7Yw= 67218 -IGFydGlzYW4= 67219 -KGNwcw== 67220 -IERtaXQ= 67221 -0LvQuNGG 67222 -LkltYWdlTGF5b3V0 67223 -IHN1Y2hlbg== 67224 -XX0s 67225 -LmNvbGxpZGVy 67226 -VGFiUGFnZQ== 67227 -XT1b 67228 -aHlkcm8= 67229 -X3N0cmlw 67230 -IGxpY2tpbmc= 67231 -IGJvb3N0cw== 67232 -IHNrZXB0aWNpc20= 67233 -IGpvZ28= 67234 -IGNvbXBldGVk 67235 -IOuCtA== 67236 -Tm9kZVR5cGU= 67237 -WEY= 67238 -IHBvc3NpYmlsaXQ= 67239 -LWNvcHk= 67240 -IHRyaXR1cg== 67241 -IEF0dGFja3M= 67242 -IG7Dqw== 67243 -SURBRA== 67244 -b2dyYXBoaWVz 67245 -VGltZVN0YW1w 67246 -b3R5cGluZw== 67247 -LUFwcg== 67248 -INC/0L7Qu9GM0LfQvtCy0LDRgtC10LvRjw== 67249 -ICI7Ig== 67250 -IEhhbGU= 67251 -L2FwaXM= 67252 -IDpdCg== 67253 -X2hkbA== 67254 -IERpYWw= 67255 -CUNvbmZpZw== 67256 -X0ZSQUdNRU5U 67257 -X0VkaXQ= 67258 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 67259 -IGNhbmRpZGFjeQ== 67260 -IENvbXByZXNzaW9u 67261 -X2xvc3Nlcw== 67262 -Kj4oJg== 67263 -SW50ZWdyYWw= 67264 -IHBhcm9keQ== 67265 -IGluaXRpYWxpc2U= 67266 -ZmlsbHM= 67267 -IGFsdHJp 67268 -X0VMRU1FTlRT 67269 -YWRhc3RyYXI= 67270 -Y29ycmVv 67271 -IHdhdHQ= 67272 -X0RSVg== 67273 -IEZvcmdvdA== 67274 -IGdldENvbnRleHQ= 67275 -IHNob3J0YWdlcw== 67276 -IE9DVA== 67277 -d2VldGFsZXJ0 67278 -IE9wZW5z 67279 -Kmw= 67280 -IEtpdHR5 67281 -4oCZw6l0 67282 -IFBpY2Fzc28= 67283 -LnRvQnl0ZUFycmF5 67284 -0L7Qu9GD0Yc= 67285 -IERFTg== 67286 -5aeT5ZCN 67287 -V2ludGVy 67288 -YW50YW4= 67289 -X19b 67290 -UHJpbQ== 67291 -IHJvb2Z0b3A= 67292 -IEJpbGxib2FyZA== 67293 -dGVzdENhc2U= 67294 -cHJvZHV0bw== 67295 -LXRodW1i 67296 -IHJlc2V0cw== 67297 -Z2Vibg== 67298 -PkVycm9y 67299 -LmRlcGFydG1lbnQ= 67300 -IGVhcnJpbmdz 67301 -IENhcm91c2Vs 67302 -KGV4YW1wbGU= 67303 -CWVt 67304 -XENvbnRhaW5lcg== 67305 -IEVsdmlz 67306 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 67307 -RW5nbGFuZA== 67308 -Y3JlZGl0ZWQ= 67309 -X2NvbnN0cnVjdG9y 67310 -IGxvcg== 67311 -IERhd3Nvbg== 67312 -QnVybg== 67313 -IEJyaWdhZGU= 67314 -IE11dGV4 67315 -IFRyYW5zaXRpb25hbA== 67316 -IE1vdXNlRXZlbnQ= 67317 -Z3Jvdw== 67318 -Lm1pbnV0ZQ== 67319 -IEdNTw== 67320 -PVtdLA== 67321 -IHN1c2hp 67322 -IGFlc3RoZXRpY3M= 67323 -T0NVUw== 67324 -IFNFTEY= 67325 -IEFzc2VydGlvbkVycm9y 67326 -IE1DVQ== 67327 -IGhpbnRUZXh0 67328 -IHNlYXc= 67329 -bmdsZQ== 67330 -IGV4cGVsbGVk 67331 -UFJPUEVSVFk= 67332 -KS48Lw== 67333 -LW9wZXJhdGlvbg== 67334 -IEltbXVu 67335 -IGxpY2Vucw== 67336 -aWJpYQ== 67337 -IGJpZXRlbg== 67338 -IGdyaXBz 67339 -Q0hBTk5FTA== 67340 -X0VSUk9SUw== 67341 -X3JlY3Vyc2l2ZQ== 67342 -VWx0aW1hdGVseQ== 67343 -IE1hamVzdHk= 67344 -IGRlYWN0aXZhdGU= 67345 -IEVYQU1QTEU= 67346 -dWNpb25lcw== 67347 -IGN1cnJlbnRWYWx1ZQ== 67348 -IGV2YWx1YXRlcw== 67349 -L0dyYXBoaWNz 67350 -InRleHQ= 67351 -X3BhbGV0dGU= 67352 -IFRNUA== 67353 -IEJlZHM= 67354 -LkNvcw== 67355 -4Lix4LiZ 67356 -PXRvcmNo 67357 -IFBBQ0tBR0U= 67358 -aWxsYXJk 67359 -LmNw 67360 -leyduA== 67361 -LWFwcHJvdmVk 67362 -IE5vcnRod2VzdGVybg== 67363 -PHRleHRhcmVh 67364 -IENvbXBhdGlibGU= 67365 -X1JEV1I= 67366 -LlF1YW50aXR5 67367 -QElk 67368 -X29yaWVudGF0aW9u 67369 -Z2V0VXJs 67370 -IHRyYW5zbGF0aW5n 67371 -IFdlYXZlcg== 67372 -IGpzb25BcnJheQ== 67373 -IGVtYmxlbQ== 67374 -LklzTnVsbA== 67375 -IENoYXJ0cw== 67376 -W119 67377 -Z2Fl 67378 -X25lc3RlZA== 67379 -dGVtcHM= 67380 -cGF0aG5hbWU= 67381 -Q1c= 67382 -LXdyaXR0ZW4= 67383 -IFBBUks= 67384 -KGNvbmQ= 67385 -X2FsYXJt 67386 -IGdlcmU= 67387 -IEdpeg== 67388 -IE5nYg== 67389 -IC5f 67390 -YXBwaW5lc3M= 67391 -IERlcGxveW1lbnQ= 67392 -aVBhZA== 67393 -Il1d 67394 -IHN0cnN0cg== 67395 -IHRvbnVtYmVy 67396 -KGRs 67397 -CXdvcmQ= 67398 -W3Rv 67399 -X0ZJWEVE 67400 -RXhwaXJhdGlvbg== 67401 -OnJldHVybg== 67402 -T250 67403 -PlBsZWFzZQ== 67404 -Z2V0VGl0bGU= 67405 -LnNwbGl0ZXh0 67406 -Y29tYmluZWQ= 67407 -T2Q= 67408 -IG5vdmVsdHk= 67409 -IlM= 67410 -IHN2bQ== 67411 -Q292ZXJhZ2U= 67412 -IEh1dA== 67413 -IHJlc2lzdGVk 67414 -IGVsbG8= 67415 -IG3DtmNodGU= 67416 -S2F5 67417 -Lmxpa2U= 67418 -Y2Npb25l 67419 -IHJlc2VtYmw= 67420 -RGVhdGhz 67421 -IGVwaXQ= 67422 -KHJnYg== 67423 -LkNsYXNzZXM= 67424 -INC00L7RgdGC 67425 -Y2FwdHVyZXM= 67426 -XStc 67427 -YW1pZW50 67428 -IFBhc28= 67429 -LlNlbmRNZXNzYWdl 67430 -IFJlbmF1bHQ= 67431 -IE5hcmVuZHJh 67432 -dG91dA== 67433 -IGhhZGRl 67434 -IFR3ZWVu 67435 -w6VkZQ== 67436 -IG91dGZpZWxk 67437 -Lz48Lw== 67438 -QFw= 67439 -IER1cmFudA== 67440 -IGFicmU= 67441 -X3N0b3J5 67442 -IHBlcmZ1bWU= 67443 -Q3BwVHlwZURlZmluaXRpb25TaXplcw== 67444 -INC/0LDRgNCw0LzQtdGC 67445 -Y2hlbWVz 67446 -IFNhZGRhbQ== 67447 -cHJlbm9t 67448 -dXNwZW5kZWQ= 67449 -IEJlbmVmaXQ= 67450 -IHNjZXB0 67451 -X01vdmU= 67452 -IE5hag== 67453 -LU9u 67454 -cnVk 67455 -SW1hZ2VQYXRo 67456 -wq4s 67457 -IGFuYWx5c2Vk 67458 -IE9H 67459 -ZWxsZWljaHQ= 67460 -YmlyZHM= 67461 -ZWt0ZQ== 67462 -IEFsaXNvbg== 67463 -IGF0aGVpc3Q= 67464 -eyU= 67465 -YWJo 67466 -LXBob3Rv 67467 -aW5zdHJ1bWVudA== 67468 -IGhpbnRlZA== 67469 -IE9mZmxpbmU= 67470 -KSIpOwoK 67471 -X1BSRUY= 67472 -IHN0eWxpc3Q= 67473 -IEt1YmVybmV0ZXM= 67474 -IGZlcnY= 67475 -CgoKCgoKCgoKCgoKCgo= 67476 -KCI9Ig== 67477 -LmdldE0= 67478 -IG5vdGV3b3J0aHk= 67479 -IHNjb3V0aW5n 67480 -X3RyYW5zbGF0ZQ== 67481 -IGJlZ2lubmluZ3M= 67482 -IEx1bw== 67483 -IHFs 67484 -X2FsaWduZWQ= 67485 -IGVydw== 67486 -dWFycw== 67487 -X1BhdGg= 67488 -LicuJA== 67489 -IGhvYw== 67490 -IGRlcnA= 67491 -bG9p 67492 -IE1jS2lu 67493 -6K+05piO 67494 -Lz0= 67495 -TGlua0lk 67496 -c3RkZGVm 67497 -cmVkdWNlcnM= 67498 -aXNhbnM= 67499 -Lmhpc3Q= 67500 -Jy8+Cg== 67501 -IFRveGlj 67502 -IGRpc2FwcGVhcmluZw== 67503 -IGNpcw== 67504 -KGRv 67505 -IG1haW5TY3JlZW4= 67506 -X0JBTks= 67507 -IGRlbW9uc3RyYXRvcnM= 67508 -IFBhbGV0dGU= 67509 -dWVseQ== 67510 -UmFyZQ== 67511 -IHJlc2lkaW5n 67512 -IGFtYmllbnRl 67513 -IG1pc20= 67514 -LXF1ZXN0aW9u 67515 -IG9wcHJlc3NlZA== 67516 -IGxldHJh 67517 -PGR5bmFtaWM= 67518 -IEZvdG9z 67519 -LXBvbGljeQ== 67520 -aXN0ZW0= 67521 -LmV4Y2hhbmdl 67522 -c3RyZQ== 67523 -JC8s 67524 -7ZWY6riw 67525 -JAoK 67526 -IFJlbmU= 67527 -IHRvdXRlZA== 67528 -LUNvcmU= 67529 -IENyYW4= 67530 -IFRyYWRlcg== 67531 -IGRldw== 67532 -IGZsYXA= 67533 -CWZpbGVuYW1l 67534 -IGlubWF0ZQ== 67535 -KE1vY2s= 67536 -IFNvYg== 67537 -aXNibg== 67538 -IG5vZQ== 67539 -IEZvcmJpZGRlbg== 67540 -IGVsZXM= 67541 -IGRpbmc= 67542 -X3Nh 67543 -KSovCg== 67544 -YXJpZQ== 67545 -IFN1cHBvcnRz 67546 -IG1vZHVsYXRpb24= 67547 -IGVuc2w= 67548 -IFNoYWRvd3M= 67549 -cHJpbmNpcGFs 67550 -YW5nZW50 67551 -LUphbg== 67552 -IFBhbnRz 67553 -LHRy 67554 -IGZpdHRl 67555 -IGdhcm1lbnRz 67556 -TWFyZ2lucw== 67557 -TFRS 67558 -IE1peQ== 67559 -dmVudHVz 67560 -IE3DtmdsaWNo 67561 -W2F0dHI= 67562 -L3Jlc3BvbmQ= 67563 -IHR0aw== 67564 -IG9sZHXEnw== 67565 -IENvbnNl 67566 -UHJlbWl1bQ== 67567 -IGZyYW5jYWlzZQ== 67568 -X2hvcml6b250YWw= 67569 -X2li 67570 -IEZhcmU= 67571 -IGhhcnZlc3RlZA== 67572 -ZW5kaXI= 67573 -KGhpdA== 67574 -PiovCg== 67575 -IElSZXBvc2l0b3J5 67576 -eWxpZQ== 67577 -IGRldGVjdHM= 67578 -Om5v 67579 -4pi0 67580 -IGRpc2XDsQ== 67581 -IHVuc2VyZW4= 67582 -IG1vY2tpbmc= 67583 -c291dGg= 67584 -cmF0ZXM= 67585 -IGh5cG9j 67586 -IFNob3J0bHk= 67587 -IEJsYWNrcw== 67588 -0YLQuNGA0L7Qsg== 67589 -IEFTQVA= 67590 -cmViYmU= 67591 -aWVj 67592 -LkFkZERheXM= 67593 -IGVwaXM= 67594 -LWluZmxhbW1hdG9yeQ== 67595 -LW5ldA== 67596 -IHBhbGw= 67597 -65Q= 67598 -IGlzc3VhbmNl 67599 -IGNvbnRlbnRpb3Vz 67600 -LkFyZWFz 67601 -0LjQu9GM 67602 -IGNvbnRpZ3VvdXM= 67603 -W2FjdGlvbg== 67604 -IGV4cHJlcw== 67605 -ISIpCgo= 67606 -VUxP 67607 -IHdyZQ== 67608 -IHN1YmRpdg== 67609 -IHR1cm5hcm91bmQ= 67610 -IGFjY2Vs 67611 -IFVuaXY= 67612 -IFVuaXZlcnNpZGFk 67613 -c2V0dA== 67614 -ZGVzY3I= 67615 -LkdlbmVyYXRpb24= 67616 -IHBhdHJpb3Q= 67617 -IGZhcw== 67618 -KioqKgo= 67619 -UVA= 67620 -IOWN 67621 -b3BwZWw= 67622 -IGp1ZWdvcw== 67623 -LmRyYXdTdHJpbmc= 67624 -LWNvbmZpcm0= 67625 -CSAgICAgICAgICAgICA= 67626 -PFByb3Bz 67627 -IGZhbWlsbGU= 67628 -IEhlbG1ldA== 67629 -ZXJ0aWFyeQ== 67630 -YXRoaQ== 67631 -IGN1bHRpdmF0ZQ== 67632 -IGR1cGxpY2F0aW9u 67633 -IHNweU9u 67634 -Ki8pCg== 67635 -IEh1bmdlcg== 67636 -T3J0aA== 67637 -IHBpbnBvaW50 67638 -IEhhZw== 67639 -IHRpbWV0YWJsZQ== 67640 -bWFyZ2luVG9w 67641 -IHJlY2lwcm8= 67642 -ZmVsbA== 67643 -IFBlcnNpc3RlbnQ= 67644 -44Gp 67645 -cGx1cmFs 67646 -cXVldWVk 67647 -IGdyYWNpYXM= 67648 -w6F0aWNv 67649 -IGhhcmRzaGlw 67650 -IEFwYXJ0bWVudHM= 67651 -IEp1bms= 67652 -IFJldmU= 67653 -X01zaw== 67654 -IHN1cHJh 67655 -IEFUUA== 67656 -IHNldFNob3c= 67657 -5a2X56ym5Liy 67658 -IE5vdHRpbmdoYW0= 67659 -U3RldmVu 67660 -IE11bmQ= 67661 -cmFuZ2Vz 67662 -IHVwbG9hZHM= 67663 -IGJmcw== 67664 -cHo= 67665 -dWx0aW1hdGU= 67666 -IEVmZmljaWVuY3k= 67667 -QU1J 67668 -5b6E 67669 -X1JFUEVBVA== 67670 -IGFjYWRlbWlh 67671 -LnRvb2xTdHJpcEJ1dHRvbg== 67672 -VG9FbmQ= 67673 -cnZpbmU= 67674 -IFRoeQ== 67675 -IEVsZWN0b3JhbA== 67676 -IFJFUVVJUkVE 67677 -IHBsdW5nZQ== 67678 -IFJldm9sdXRpb25hcnk= 67679 -IFRlbnQ= 67680 -IGdyZW5hZGU= 67681 -IjpbeyI= 67682 -IG1vdXI= 67683 -UG93 67684 -IGV2YW5nZWxpY2Fs 67685 -VEVDVEVE 67686 -IG92ZXJ0dXJu 67687 -CUlucHV0 67688 -cmVjb21tZW5k 67689 -JUM= 67690 -IHNsYWc= 67691 -IEJoYXI= 67692 -X2VuY3J5cHQ= 67693 -IFdhcmZhcmU= 67694 -KGFnZQ== 67695 -QVRFR09SSUVT 67696 -bWlsZQ== 67697 -IGhlYXZlbmx5 67698 -YW1tZXI= 67699 -KCkpWw== 67700 -YWRlcmE= 67701 -aGc= 67702 -IExBVw== 67703 -IHBhY2thZ2VOYW1l 67704 -X3R5cGVEZWZpbml0aW9u 67705 -KGJl 67706 -REJOdWxs 67707 -X3Rhcg== 67708 -IGhldXJpc3RpYw== 67709 -IFdhbnRlZA== 67710 -IFN0dWI= 67711 -IGtpdHQ= 67712 -UkVD 67713 -IHBhc2Fy 67714 -Lm5ld0J1aWxkZXI= 67715 -CWdyYXBo 67716 -aW9zYQ== 67717 -LmNvbHVtbkhlYWRlcg== 67718 -IHNldE9wZW4= 67719 -IFRoaXJ0eQ== 67720 -ICIlLg== 67721 -QWxiZXJ0 67722 -IHNhbWE= 67723 -IHJvY2tpbmc= 67724 -Q29tcGxl 67725 -TVY= 67726 -fCgpCg== 67727 -X3JlYWRz 67728 -KHZhcmFyZ2lu 67729 -b3Vsb3VzZQ== 67730 -IFNJTUQ= 67731 -IGNhcmJvaHlkcmF0ZQ== 67732 -d2hvbGU= 67733 -LE5vbmU= 67734 -i+ivlQ== 67735 -IENoYW5k 67736 -Y3phcw== 67737 -X3F1ZXJ5c2V0 67738 -IGV4aXN0ZW50aWFs 67739 -IGVkaWJsZQ== 67740 -IGFnaWxpdHk= 67741 -IFdpbGxpcw== 67742 -IGh5bQ== 67743 -IEJyaWxs 67744 -0LjRhQ== 67745 -IE5vdEZvdW5kRXhjZXB0aW9u 67746 -ICgoKQ== 67747 -QVBTSE9U 67748 -IHN1YnN0YW50aXZl 67749 -X3R5cGVEZWZpbml0aW9uU2l6ZQ== 67750 -IHZhY2FuY2llcw== 67751 -RU5HSU5F 67752 -IGFuZGVycw== 67753 -IHN5bWI= 67754 -IGV0cmVl 67755 -KS5f 67756 -IHRyYW5zcG9ydGluZw== 67757 -aW1wcw== 67758 -L2NvcA== 67759 -YWN0YWJsZQ== 67760 -X2ZsdXg= 67761 -IG5ld0luc3RhbmNl 67762 -YXRvaXJl 67763 -IGNvbHVtbkluZGV4 67764 -IEdpbw== 67765 -IHN1YnRpdGxlcw== 67766 -LldpbkZvcm1z 67767 -0LvRj9C10Lw= 67768 -IGFsZXJ0ZWQ= 67769 -IHN0cmlwcGluZw== 67770 -d2VuZHVuZw== 67771 -IE1ldGhvZEludm9jYXRpb24= 67772 -RXJyb3JIYW5kbGVy 67773 -U2Nyb2xsYmFy 67774 -UG9ydGZvbGlv 67775 -Y29uc3Vt 67776 -IENPTU1PTg== 67777 -TGY= 67778 -X2Jhc2Vk 67779 -b2NhbHk= 67780 -IGVmZmV0 67781 -dnZt 67782 -cmlwc2k= 67783 -IGZsb3VyaXNo 67784 -Y2h0ZXI= 67785 -PT09PT09PT09Cg== 67786 -IHJlcXVlcg== 67787 -LnF1ZXN0aW9ucw== 67788 -KCI/ 67789 -IHBvc1g= 67790 -IFBDUg== 67791 -IE9yZ2FuaXphdGlvbnM= 67792 -cHLDvA== 67793 -RXhhbQ== 67794 -IEluY29ycG9yYXRlZA== 67795 -X3BocmFzZQ== 67796 -IHByYXllZA== 67797 -IGhvbWVvd25lcg== 67798 -IFRhag== 67799 -eng= 67800 -IElkZWFsbHk= 67801 -X01BQ0hJTkU= 67802 -IFJlbW92aW5n 67803 -Q29lZmZpY2llbnQ= 67804 -IGVkdWNhdGluZw== 67805 -ID8+Jg== 67806 -IHBvdXJz 67807 -aXJhbQ== 67808 -X3BlYWs= 67809 -IG5lc3Rpbmc= 67810 -YWJ5dGU= 67811 -bmF0dXJl 67812 -IGFmcw== 67813 -IFJvbw== 67814 -Y2FyZ28= 67815 -b2JqZXQ= 67816 -IGZyZWVpbmc= 67817 -cXVha2U= 67818 -RGVuc2l0eQ== 67819 -IGRlc2NyaWNhbw== 67820 -LyoqKioqKioq 67821 -IGRhc2hlZA== 67822 -IGdyb8Of 67823 -b29reQ== 67824 -IFBFT1BMRQ== 67825 -X1Bvc3Q= 67826 -IGNlcnZpY2Fs 67827 -IEFkanVzdGFibGU= 67828 -ZW5zdWFs 67829 -IFJldmlzZWQ= 67830 -KHJlZmVyZW5jZQ== 67831 -CUJhc2U= 67832 -ZXNzaW0= 67833 -TWFpbnQ= 67834 -IGdldFNpemU= 67835 -IFNhbmR3aWNo 67836 -cmFkaWVudA== 67837 -c2luaw== 67838 -Oi8vJw== 67839 -X3R0 67840 -RlBT 67841 -IEFybWVuaWFu 67842 -cHJldlN0YXRl 67843 -X0xJTkVT 67844 -IHRpZ2h0ZW4= 67845 -PFs= 67846 -XTw8Ig== 67847 -IFRyYWZm 67848 -IGxpcXVpZHM= 67849 -IGFyY3M= 67850 -X0NvbW1hbmQ= 67851 -QHByb3RvY29s 67852 -LWlzaA== 67853 -IHJ1YmJlZA== 67854 -QkJD 67855 -L2ZpcmViYXNl 67856 -QXBwQmFy 67857 -PFg= 67858 -IFNJTkdMRQ== 67859 -LlN0YXR1c0ludGVybmFsU2VydmVyRXJyb3I= 67860 -IHZlcnRl 67861 -L3F1ZXJ5 67862 -IGdldENvbmZpZw== 67863 -IERpcmVjdFg= 67864 -cGh5c2ljcw== 67865 -eWNvcA== 67866 -IGJyZWFrZXI= 67867 -LXZvbHVtZQ== 67868 -ZGF0YVRhYmxl 67869 -4oCZZQ== 67870 -cmlvdHQ= 67871 -IEV0ZXJuYWw= 67872 -Z2V0SGVpZ2h0 67873 -IG9uSXRlbUNsaWNr 67874 -IHF1YXRlcm5pb24= 67875 -IGtpbmt5 67876 -ZGVzZXJpYWxpemU= 67877 -KFNwcmluZw== 67878 -IHBlYWNlZnVsbHk= 67879 -X0RldmljZQ== 67880 -KE1hdHJpeA== 67881 -acOocmVtZW50 67882 -KHR5cA== 67883 -LnZhYWRpbg== 67884 -LmdldE1ldGhvZA== 67885 -IOKAnQoK 67886 -IHRocmVhZGVk 67887 -IEZhbW91cw== 67888 -IEdhbWI= 67889 -IOyngA== 67890 -INCk 67891 -IGZha3Q= 67892 -IGVjaHQ= 67893 -X3Vi 67894 -LkpwYVJlcG9zaXRvcnk= 67895 -IHVuZ2U= 67896 -LWVuZGluZw== 67897 -IENBTUVSQQ== 67898 -Y3JlZGVudGlhbA== 67899 -IFBhc3Nwb3J0 67900 -CVJUREJH 67901 -IGV4dHJhZA== 67902 -LW9yaWdpbg== 67903 -IHNhY3JpZmljZWQ= 67904 -IFNjaHVsdHo= 67905 -IFR1cnRsZQ== 67906 -LmNlbnRlclg= 67907 -IHNob3djYXNpbmc= 67908 -IGJ6dw== 67909 -eXJv 67910 -aXNOdWxs 67911 -LmlzRGlyZWN0b3J5 67912 -bWFpbnQ= 67913 -X2Jp 67914 -IFNwcmluZ2Vy 67915 -fSgpCgo= 67916 -aXNzdWVy 67917 -LWFybQ== 67918 -ZXNr 67919 -bGluaGE= 67920 -IGtvcnQ= 67921 -YWphcw== 67922 -YWxpbms= 67923 -KEJ1dHRvbg== 67924 -IFJlc3RvcmF0aW9u 67925 -IGluY3I= 67926 -IFpob3U= 67927 -CSAgICAgICAgCQ== 67928 -IERpc2NsYWltZXI= 67929 -IGt2aW5ub3I= 67930 -IERhcmU= 67931 -IDwtPg== 67932 -6K+m 67933 -CQkJCQkJCQkJCQo= 67934 -LkNsYW1w 67935 -CXNjb3Bl 67936 -IE11bQ== 67937 -PDw8PDw8PA== 67938 -L3t7 67939 -X2FydGlzdA== 67940 -IFJlYWN0aW9u 67941 -IE5pY2tlbA== 67942 -X1JlbW92ZQ== 67943 -KCgoKA== 67944 -64yA 67945 -IGR5bmFzdHk= 67946 -IFRocm93cw== 67947 -IENvdWw= 67948 -X3JuZw== 67949 -IERvaw== 67950 -Lmxpc3RWaWV3 67951 -IFR1Y3Nvbg== 67952 -KHRvaw== 67953 -IFBoaWxpcHBl 67954 -VG9TaG93 67955 -IGRpZXRh 67956 -IFVsdHI= 67957 -LlRpY2s= 67958 -IEdldFR5cGU= 67959 -aWV0ZQ== 67960 -IExlYWg= 67961 -SGFyZHdhcmU= 67962 -IENvbXByZWhlbnNpdmU= 67963 -Q09NTU9O 67964 -IGluZHVzdHJp 67965 -aXJpY2Fs 67966 -LWJlZHJvb20= 67967 -IGd5cm8= 67968 -INC60L7RgA== 67969 -IC0vCg== 67970 -Y291cg== 67971 -IEJydXNoZXM= 67972 -TXVsdGlwbGllcg== 67973 -IHVzZXJkYXRh 67974 -IFJlY29nbg== 67975 -IG9ibGlnYXRlZA== 67976 -IExldmlu 67977 -YW5jZXN0b3I= 67978 -IG1lbmluZw== 67979 -IFVk 67980 -LGpzb24= 67981 -KGFzc2lnbg== 67982 -IG5kYXJyYXk= 67983 -X2Nvcm5lcg== 67984 -QEFsbEFyZ3NDb25zdHJ1Y3Rvcg== 67985 -6aqM6K+B56CB 67986 -YWRvcnM= 67987 -IHJlc3BvbmRlbnQ= 67988 -R09SSVRI 67989 -IHRlbmdv 67990 -IHNldE1lc3NhZ2U= 67991 -IElQTw== 67992 -YXJyYXlz 67993 -IEFHQUlO 67994 -J1s= 67995 -ICItLy8= 67996 -w6Rt 67997 -44CCXA== 67998 -Lm9uY2U= 67999 -Y3VycmVudFRpbWU= 68000 -R292 68001 -IGdldG9wdA== 68002 -bWx4 68003 -IFRvbmU= 68004 -J11dOwo= 68005 -IHByZWRhdG9y 68006 -V3k= 68007 -L2VudGl0eQ== 68008 -IG1hbnRyYQ== 68009 -KT49 68010 -b2dyYWQ= 68011 -IG1lbGFu 68012 -IHNvcnRCeQ== 68013 -IERFRklORQ== 68014 -UHJvdGVjdGVk 68015 -Y2RlY2w= 68016 -Jz4iLiQ= 68017 -PGN2 68018 -Y3JpcmU= 68019 -LVRydW1w 68020 -IHVjZmlyc3Q= 68021 -Y2Fzc2VydA== 68022 -IGFja25vd2xlZGdlbWVudA== 68023 -IElOVg== 68024 -IFVOVQ== 68025 -LnNxdWFyZXVw 68026 -IFNheA== 68027 -cmV0dGU= 68028 -KCkKCgoK 68029 -IERhdGFCYXNl 68030 -IFBhdHJpb3Q= 68031 -X1Jvdw== 68032 -IEV4aGliaXRpb24= 68033 -IGRldGFpbmVlcw== 68034 -IFN0cmluZ0lP 68035 -X0RFTg== 68036 -TW9kaWZpZXJz 68037 -YXNhcg== 68038 -aXJ0aW5n 68039 -IHRyYW5xdWls 68040 -KGVuYw== 68041 -IOOCsw== 68042 -bmNvZGVy 68043 -X3VudXNlZA== 68044 -IEJpYW4= 68045 -VmVyYg== 68046 -X2V4Y2VycHQ= 68047 -L2V4cG9ydA== 68048 -IFNleHQ= 68049 -RHM= 68050 -QU1QTA== 68051 -T2ZTdHJpbmc= 68052 -X3RyYWNrcw== 68053 -d2o= 68054 -b3Rvbmlu 68055 -IElURQ== 68056 -SVZFTg== 68057 -LW9yaWdpbmFs 68058 -IEZJTkFM 68059 -X18pCgoK 68060 -IGVuc2U= 68061 -IFV0dA== 68062 -Oioq 68063 -IFN1cnJleQ== 68064 -IEthaXNlcg== 68065 -YWRtaW5pc3RyYXRvcg== 68066 -LWxhcmdlc3Q= 68067 -IGxldHp0ZW4= 68068 -IGNoYWluZWQ= 68069 -J0g= 68070 -IGRvY3VtZW50aW5n 68071 -IExlY3R1cmU= 68072 -Ukg= 68073 -b2xsYXBzZWQ= 68074 -c2tpcnRz 68075 -ZWxkZXI= 68076 -IFNpeHRo 68077 -IGFsbGVnaWFuY2U= 68078 -SVNPU3RyaW5n 68079 -VXNhZ2VJZA== 68080 -LmhhcmR3YXJl 68081 -IHBhcmk= 68082 -IHfDpGhyZW5k 68083 -IHJkcg== 68084 -IGhqZW0= 68085 -TE9PUg== 68086 -IExQQVJBTQ== 68087 -INC80L7QttC10YI= 68088 -IGhvbWFnZQ== 68089 -b3V0c2lkZQ== 68090 -IENoYXJTZXQ= 68091 -PEdhbWU= 68092 -77yZ 68093 -X01VVEVY 68094 -KSkvKA== 68095 -X3Jlb3JkZXJlZA== 68096 -dGV4dElucHV0 68097 -QU5DRUQ= 68098 -IFRlZQ== 68099 -IGNvcm5lcmJhY2s= 68100 -UXVlcnlTdHJpbmc= 68101 -IGxvbmdpdHVkaW5hbA== 68102 -IEhvbGlkYXlz 68103 -QUJDREVGRw== 68104 -LktleVByZXNz 68105 -LnVs 68106 -eWRybw== 68107 -IFRhdGU= 68108 -CXJvdXRlcg== 68109 -c3BvdHM= 68110 -IHBhdWw= 68111 -LXByZXY= 68112 -IGtub3dpbmdseQ== 68113 -IEt1cmRz 68114 -IEV1cm9w 68115 -LmNlcnQ= 68116 -QklH 68117 -KGNvZWZm 68118 -IENsYXVz 68119 -L2V4YW1wbGVz 68120 -IEZhcm1z 68121 -IC8vKA== 68122 -U1BBTg== 68123 -IGNpcmN1cw== 68124 -IE1JUw== 68125 -IFRyYWl0cw== 68126 -LWNsZWFy 68127 -IHJlZ2ltZW4= 68128 -IGJhY2tncm91bmRJbWFnZQ== 68129 -dXNhaGE= 68130 -X01ldGFkYXRhVXNhZ2VJZA== 68131 -IHJoZQ== 68132 -Q2xpbg== 68133 -IERvbWluaWM= 68134 -Lm5leHREb3VibGU= 68135 -KGRldGFpbA== 68136 -VGhyZWFkUG9vbA== 68137 -IENhcnBlbnRlcg== 68138 -c29ydGluZw== 68139 -IGdvdmVybm9ycw== 68140 -IHNpbmdlcnM= 68141 -dW5saW5r 68142 -IHJpbmdpbmc= 68143 -IHNjaGVtYXRpYw== 68144 -IGVycm1zZw== 68145 -IGJlYg== 68146 -LiIr 68147 -IEluY3JlYXNlcw== 68148 -IkFsbA== 68149 -IGFjb250ZQ== 68150 -emlh 68151 -LlRleHRDaGFuZ2Vk 68152 -IFRvRG8= 68153 -LDopOwo= 68154 -bmFnZQ== 68155 -Y2hs 68156 -b3dlbA== 68157 -IGdlcmFkZQ== 68158 -X2ZmdA== 68159 -IGVzdGFtb3M= 68160 -U1RBUg== 68161 -IGRpc2d1c3Q= 68162 -Z3Jhbg== 68163 -cG9ydHVuaXR5 68164 -IGF1dG9iaQ== 68165 -e317Cg== 68166 -IENvdXBvbnM= 68167 -X0dBSU4= 68168 -IFRDSEFS 68169 -L3Bhc3M= 68170 -55Sx 68171 -IGZvb3R3ZWFy 68172 -KGJvdW5kcw== 68173 -YXB1cw== 68174 -Y2l0ZQ== 68175 -Qk9PVA== 68176 -IENvZGVj 68177 -bG9ndWU= 68178 -LXByb3BlcnRpZXM= 68179 -YXV0b21hdGlvbg== 68180 -IFNob2U= 68181 -c3BlY3Q= 68182 -KG1t 68183 -IEtldA== 68184 -W3BhcmFt 68185 -IGJhc2ls 68186 -IEFuZ3VsYXJGaXJl 68187 -IGFkdmVudHVyb3Vz 68188 -X1VDbGFzcw== 68189 -IGluZHVsZ2U= 68190 -CWN1ZGE= 68191 -IGluc3VsdGluZw== 68192 -LkV4cHJlc3Npb25z 68193 -IG9uQ3JlYXRlT3B0aW9uc01lbnU= 68194 -VUVM 68195 -IGJpdGluZw== 68196 -KCFf 68197 -IEVuY3ljbG9wZWRpYQ== 68198 -IGJlcnQ= 68199 -IFZlcmE= 68200 -IEJpYmxpY2Fs 68201 -aW5zaWNz 68202 -X1NJTVBMRQ== 68203 -IHNhbGlkYQ== 68204 -cmVxdWVzdGVk 68205 -IENvbXBvc2l0aW9u 68206 -LkF0b2k= 68207 -KEtleUV2ZW50 68208 -ZXJlYQ== 68209 -IGRlcG9ydGVk 68210 -IFF1cg== 68211 -IG5pcHBsZXM= 68212 -aXNBcnJheQ== 68213 -INGD0LrQsNC3 68214 -IGJyaW5r 68215 -bWV0cm9z 68216 -RW51bWVyYXRpb24= 68217 -IEJ1aWxkcw== 68218 -ZXJ0b3M= 68219 -IHNhaW50cw== 68220 -LmRlcGxveQ== 68221 -ZXRoZXJldW0= 68222 -IGtpbmRlcmdhcnRlbg== 68223 -dmFuaXplZA== 68224 -IGNvbWJpbg== 68225 -IHBvdXZvaXI= 68226 -S2lu 68227 -YXLEsQ== 68228 -IC4uLi4u 68229 -77y+ 68230 -Lkdv 68231 -IHF1aXJreQ== 68232 -xLFuZGFu 68233 -IGFjdGlvblR5cGVz 68234 -IFFVRVJZ 68235 -VGF5bG9y 68236 -IFJL 68237 -dGF0 68238 -LnBhY2tldA== 68239 -IElNUE9SVEFOVA== 68240 -IGN1c2hpb25z 68241 -YnVsaw== 68242 -ZHVjdGl2ZQ== 68243 -YmVuZWY= 68244 -b2NyaXN5 68245 -IGZ1ZXJvbg== 68246 -IGN1cnNlcw== 68247 -IGZpbGluZ3M= 68248 -ZWxpZXI= 68249 -KD86 68250 -X2RyaXZl 68251 -IGNvbnRhY3Rv 68252 -IFBhcmt3YXk= 68253 -dmlkZXM= 68254 -Z25l 68255 -YXZhZ2U= 68256 -XFwu 68257 -ZnVsbE5hbWU= 68258 -ZGxs 68259 -IHNob2Nrcw== 68260 -ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 68261 -X3B4 68262 -QFdlYg== 68263 -LlBlcnNpc3RlbmNl 68264 -IHN1bms= 68265 -LnRvb2x0aXA= 68266 -YXV0aWNhbA== 68267 -TmV3c2xldHRlcg== 68268 -IHdhaXRlcg== 68269 -IGlucXVpcmU= 68270 -0LDQtdGC0YHRjw== 68271 -KCdfXw== 68272 -dG9n 68273 -SUVOVEFUSU9O 68274 -IGNvbXBhbnlJZA== 68275 -IEJhc2ljcw== 68276 -CUpMYWJlbA== 68277 -IG1hY09T 68278 -IE1hdHM= 68279 -X3RlbA== 68280 -LXByZWZpeA== 68281 -IG11dGF0ZQ== 68282 -fScp 68283 -Y2hlbmc= 68284 -IE1pbGl0 68285 -IiY= 68286 -ZmluZGluZw== 68287 -IERhdGFMb2FkZXI= 68288 -LkdQSU8= 68289 -IExldnk= 68290 -IHNuZWFrZXJz 68291 -IGNyw6lk 68292 -YXduZXI= 68293 -eGlh 68294 -L3NpbXBsZQ== 68295 -Q0hS 68296 -IGZsb3RhdGlvbg== 68297 -LnNlbnNvcg== 68298 -QnJhemls 68299 -IFNlYXNvbnM= 68300 -IFNwZWFr 68301 -LWJhbGw= 68302 -IE11dGF0aW9u 68303 -dWtrYW4= 68304 -IE9tYWhh 68305 -4oCZb24= 68306 -IEN1b21v 68307 -IEp1ZGljaWFs 68308 -IGNoZWNrcG9pbnRz 68309 -IEZyZW0= 68310 -CUlk 68311 -ZWdyaXR5 68312 -X2Fm 68313 -QE5vQXJnc0NvbnN0cnVjdG9y 68314 -IHRhYmVsYQ== 68315 -WyM= 68316 -bm90YQ== 68317 -IEZhY3RvcnM= 68318 -KGdyb3Vwcw== 68319 -aXN3YQ== 68320 -SVZP 68321 -IHNjcmk= 68322 -YWNldA== 68323 -IE1laA== 68324 -KGNsYXp6 68325 -IFs8 68326 -cGVyaWFs 68327 -IHN1cnBhc3NlZA== 68328 -IGpva2Vk 68329 -IHJ1ZA== 68330 -IGltYmFsYW5jZQ== 68331 -IEZyYWdl 68332 -c3Nw 68333 -IGluZGljdGVk 68334 -Lm1hcmtldA== 68335 -O20= 68336 -IHJlcGFpcmluZw== 68337 -LW5vdGU= 68338 -RGVidWdnZXI= 68339 -KFdlYg== 68340 -IHNpbmdz 68341 -IExveQ== 68342 -IERFU0lHTg== 68343 -LkNvbXA= 68344 -LWNvbnRyb2xsZXI= 68345 -IGF2b2NhZG8= 68346 -IEJvd2ll 68347 -Y29udGFkb3I= 68348 -dWxpbmdz 68349 -dWNob3M= 68350 -c3BlY2lmaWVy 68351 -IFZvbHZv 68352 -IGRlbW9z 68353 -IFByb2R1dG8= 68354 -Lk5vdEZvdW5k 68355 -IG5pw7Fvcw== 68356 -IEJvbHM= 68357 -X291dGVy 68358 -U2hlcg== 68359 -QVVUTw== 68360 -IGpvdg== 68361 -IEZyZWRkaWU= 68362 -b3JpYXM= 68363 -IGFmZWN0 68364 -IGZhY2lsaXRhdGluZw== 68365 -IGRvbWluYXRpbmc= 68366 -UGFyY2VsYWJsZQ== 68367 -JywnLQ== 68368 -bW9vbg== 68369 -IG1ldGFzdA== 68370 -IHNjYXJm 68371 -IFRoZXJt 68372 -Q2FsbEJhY2s= 68373 -0YHRgtCw0LI= 68374 -LkltcG9ydA== 68375 -IGJldHJheWFs 68376 -aWN1bG9z 68377 -IHdlacOf 68378 -5YyF 68379 -X14= 68380 -d2lmaQ== 68381 -IFNFTlNPUg== 68382 -X0JVU1k= 68383 -JGI= 68384 -X0ZJTkQ= 68385 -IHBsYXN0aWNz 68386 -IENPTlZFUlQ= 68387 -CWNhbGw= 68388 -IFByYWd1ZQ== 68389 -IGdhcm5lcmVk 68390 -X2xlYXJuaW5n 68391 -c2hvb3Q= 68392 -J10pKQ0K 68393 -IEdpbmdlcg== 68394 -PXBk 68395 -LHRlc3Q= 68396 -UHJvZml0 68397 -IGVzdGltYXRvcg== 68398 -IGJyZWU= 68399 -IC8vPC8= 68400 -X2hhdmU= 68401 -IEtvZA== 68402 -X0lNTQ== 68403 -aXp6YXM= 68404 -bWlnaHR5 68405 -154= 68406 -IE9uQ2xpY2tMaXN0ZW5lcg== 68407 -44OH 68408 -IFNjaWVudGlzdA== 68409 -RmlsdGVyZWQ= 68410 -YXZs 68411 -aGF5 68412 -X2dlbmVyYXRlZA== 68413 -XScK 68414 -IEF1dGhvcml0aWVz 68415 -OnBhcmFt 68416 -IHN0YXR0 68417 -LW1hdGVyaWFs 68418 -IGxpZGVy 68419 -IENyb3A= 68420 -IEJ1bmlmdQ== 68421 -IG5leHRQcm9wcw== 68422 -b3J6 68423 -X29yZA== 68424 -PHg= 68425 -X0lPQ1RM 68426 -IE11c2NsZQ== 68427 -CWV4ZWM= 68428 -RU5BTUU= 68429 -X2xldHRlcnM= 68430 -IyMjIyM= 68431 -IENz 68432 -J109PSI= 68433 -ICInKQ== 68434 -Q2xlYW51cA== 68435 -LnN0cnVjdHVyZQ== 68436 -zro= 68437 -6YCa6L+H 68438 -J107Pz4i 68439 -IExhdGl0dWRl 68440 -YmJpbmc= 68441 -IGJhbmFuYXM= 68442 -cmVjdGlvbnM= 68443 -IFJhbmRhbGw= 68444 -TllTRQ== 68445 -IGFwcmVuZA== 68446 -LlJlc3BvbnNlRW50aXR5 68447 -IHRlc3REYXRh 68448 -XGU= 68449 -IFdL 68450 -LkFkZENvbXBvbmVudA== 68451 -X3J1bnM= 68452 -w6dvaXM= 68453 -LW1pbmk= 68454 -Zm9sZGVycw== 68455 -IGxvc2Vycw== 68456 -IFRvd2Vycw== 68457 -LUVuY29kaW5n 68458 -OnI= 68459 -Y2hvb3Nlcg== 68460 -IGZsYXR0ZW5lZA== 68461 -0YHRgtCw0L3QvtCy 68462 -CVB5 68463 -5Lic 68464 -IGRhbW5lZA== 68465 -RGVwdA== 68466 -d2Vk 68467 -IHBpc2M= 68468 -Z2llcw== 68469 -X2dhbWVz 68470 -Lm1hc3M= 68471 -KEVxdWFs 68472 -IG5hdGl2ZXM= 68473 -LnRodW1ibmFpbA== 68474 -bHRy 68475 -IGVxbA== 68476 -X2luY29tZQ== 68477 -CWhlYWRlcnM= 68478 -LWhhaXJlZA== 68479 -IG1lZGlvY3Jl 68480 -IFdpdGhkcmF3 68481 -IGJpdHRl 68482 -2b4= 68483 -PWlu 68484 -b2NrZWQ= 68485 -RnVsbHk= 68486 -IFRFTVBMQVRF 68487 -w7pkZQ== 68488 -T2Rk 68489 -aWxsZXo= 68490 -VGVsZXBob25l 68491 -IAoJCQo= 68492 -KCInIg== 68493 -X3NjaGVk 68494 -ZXJuZQ== 68495 -wr4= 68496 -LnBpY2s= 68497 -IE1TSQ== 68498 -CWZm 68499 -RGlzY292ZXJ5 68500 -IENPRA== 68501 -IExhY2s= 68502 -IHNlbnNhdGlvbmFs 68503 -bW90aA== 68504 -IExlZ2lzbGF0aXZl 68505 -0Y0= 68506 -IHZpYWJpbGl0eQ== 68507 -IGdldEVtYWls 68508 -IHVuYW5pbW91cw== 68509 -IHBlbGxldA== 68510 -ICIoKQ== 68511 -Y29hdA== 68512 -YWdvb24= 68513 -IEFMV0FZUw== 68514 -XHVD 68515 -X3N0ZG91dA== 68516 -QW5keQ== 68517 -IG5ld0xpc3Q= 68518 -IE1haGFyYXNodHJh 68519 -LF9f 68520 -PXVzZXJuYW1l 68521 -IHNjcmlwdGluZw== 68522 -IFRtaW4= 68523 -PEFjdGlvbg== 68524 -PXt9LA== 68525 -c3ltYm9scw== 68526 -IGZlbmNpbmc= 68527 -IHbDrWRlb3M= 68528 -IE1hdXJpY2U= 68529 -Y29ybGli 68530 -IGtlbQ== 68531 -In0pLAo= 68532 -IENsYXNzaWNhbA== 68533 -Y29sbGVnZQ== 68534 -IEhvbWVwYWdl 68535 -IH19Cgo= 68536 -X01zcA== 68537 -IENvbXBsYWludA== 68538 -IHNhbmR5 68539 -QXNpYW4= 68540 -X3NlcmlhbGl6ZXI= 68541 -IExhaA== 68542 -IGJ1ZHM= 68543 -b2xvZ25l 68544 -IHJlc3BvbnNlRGF0YQ== 68545 -b3BoaWxl 68546 -a2F0ZWdvcmk= 68547 -RW5kZWQ= 68548 -bGVjdGlj 68549 -IGNsYXdz 68550 -Li4uJyk7Cg== 68551 -IHBsYW5uZXJz 68552 -IFphaw== 68553 -IEdsb3Zlcw== 68554 -Iil9 68555 -IGZhc2hpb25lZA== 68556 -YnJvbg== 68557 -IG5ld2NvbWVycw== 68558 -dmFuYQ== 68559 -IHBpZXJ3cw== 68560 -UmVjZWlwdA== 68561 -LWVudg== 68562 -IHJ1dGE= 68563 -IEZhcm1lcg== 68564 -b2RvcmU= 68565 -bXVp 68566 -IHJvbWFudA== 68567 -IGluZmxpY3Q= 68568 -IHNlbWluYXJz 68569 -PWN2 68570 -KHN0b2Nr 68571 -IGV4dHJhY3Rvcg== 68572 -IFRpZmZhbnk= 68573 -X3V2 68574 -LmNvbnRhY3Rz 68575 -JyksKCc= 68576 -IHNvbHZlcw== 68577 -LkNvbm5lY3Rpb25TdHJpbmc= 68578 -L2RlYnVn 68579 -IEF2ZXJ5 68580 -44Oj 68581 -IG1heFg= 68582 -U3Bhcms= 68583 -PHRoaXM= 68584 -IGhpa2Vz 68585 -S2V5VmFsdWVQYWly 68586 -IFF1aWV0 68587 -c3RhYg== 68588 -IEtvbW1lbnQ= 68589 -bHljZXI= 68590 -IE1TTQ== 68591 -IExhbnRlcm4= 68592 -IGNvbmp1bnRv 68593 -aHNp 68594 -TVVMVA== 68595 -V2l0aER1cmF0aW9u 68596 -YXR0YWNoZWQ= 68597 -IEFzdGVy 68598 -CXBvaW50cw== 68599 -IFNpYmVy 68600 -IE1ldGhvZGlzdA== 68601 -L3NpdGVz 68602 -IGZvcnR1bmVz 68603 -UGFydGljaXBhbnQ= 68604 -IGN1c3RvbWVySWQ= 68605 -KWluaXQ= 68606 -X3NlcnZlcnM= 68607 -IHdlYXZl 68608 -IFRSQUlO 68609 -IGhhcmFzc2Vk 68610 -7J6R 68611 -YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo= 68612 -X2Zhcg== 68613 -QWxjaGVteQ== 68614 -LmxpbmVXaWR0aA== 68615 -IHRoZXJhcGlzdHM= 68616 -IExvYg== 68617 -ZXF1aXBtZW50 68618 -IHJlY2h0 68619 -Lm1pcG1hcA== 68620 -Lm5pY2tuYW1l 68621 -IHVudG91Y2hlZA== 68622 -QUdPTg== 68623 -IFNhdWw= 68624 -IHdvcmtzaGVldHM= 68625 -IFZldGVyYW4= 68626 -b3VkZW4= 68627 -YWNsYXNz 68628 -X2FzbQ== 68629 -IHRlbXBs 68630 -IEV4cGVuc2U= 68631 -ZWlnaHQ= 68632 -I1NCQVRDSA== 68633 -em9uZXM= 68634 -LnBhcnRz 68635 -YXRyaWNl 68636 -bGF3cw== 68637 -dG9CZURlZmluZWQ= 68638 -RWZmZWN0aXZl 68639 -IFBpZWNlcw== 68640 -YXJ0aQ== 68641 -IGluaGliaXRvcnM= 68642 -CXBhcmFtZXRlcnM= 68643 -IHRlbGVncmFt 68644 -Ym91cmc= 68645 -X25vdGlmaWNhdGlvbnM= 68646 -IHBvc2l0aW9uYWw= 68647 -LWRlYWxz 68648 -IC8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 68649 -IHNoYWRlcnM= 68650 -XT0k 68651 -IGRlY28= 68652 -ZXR5cGVz 68653 -Y2xhcmU= 68654 -IEdTTQ== 68655 -LnV0aWxpdHk= 68656 -VG9TdHI= 68657 -YWZlbg== 68658 -IFht 68659 -X3BhcnRpY2xlcw== 68660 -IGZsdWZmeQ== 68661 -TWFya2V0aW5n 68662 -IHN0YW5kaW5ncw== 68663 -PwoKCgoKCg== 68664 -VU1BTg== 68665 -X1BBWU1FTlQ= 68666 -CVRpbWU= 68667 -cmF3bg== 68668 -b3Jybw== 68669 -IGVlcnN0ZQ== 68670 -IHBhZ2VOdW0= 68671 -IENPUA== 68672 -IHBsYWdpYXI= 68673 -VXBsb2FkZXI= 68674 -JHNlbGY= 68675 -bGF0ZXI= 68676 -ZXJpYWxpemVk 68677 -IGFsaWduU2VsZg== 68678 -IOKZpQ== 68679 -LmFycmF5Y29weQ== 68680 -IG5vc290cm9z 68681 -CWdwaW8= 68682 -IHBsb3R0ZWQ= 68683 -aXRlcmF0aW9ucw== 68684 -IFJlbGF4 68685 -Y2lwaGVy 68686 -R2lmdA== 68687 -IEJldHQ= 68688 -IFhS 68689 -IHN0cmlwZWQ= 68690 -KGVudmlyb25tZW50 68691 -ZWdlcnM= 68692 -X1JFU0VSVkVE 68693 -IGvDtm5udGU= 68694 -IGluZmVycmVk 68695 -UGRm 68696 -c29ycnk= 68697 -cGFyYXRl 68698 -LkNvbmNhdA== 68699 -IGxpcGlk 68700 -LkJP 68701 -IG9ybQ== 68702 -IENvbnNvcnQ= 68703 -IG92ZXJzZWVpbmc= 68704 -IGFtYmVy 68705 -IHBsZXRob3Jh 68706 -CUFjdGlvbg== 68707 -cXVlcnF1ZQ== 68708 -IGh1aXM= 68709 -ID1b 68710 -IHByb2dyZXNzZXM= 68711 -anVkdWw= 68712 -IGNvbnZlcnRpYmxl 68713 -LmVtYmVkZGluZw== 68714 -IHs/Pgo= 68715 -IHJlZHV4 68716 -W2xhYmVs 68717 -OiIpOw0K 68718 -Lm9ubGluZQ== 68719 -cXVhcnRlcmVk 68720 -IHNjaG9vbGluZw== 68721 -ICJcIiI= 68722 -W2xpc3Q= 68723 -QWxhbg== 68724 -J30KCg== 68725 -eXBzdW0= 68726 -IHN0cml2aW5n 68727 -IFJlc3BvbnNpYmxl 68728 -IO2MjOydvA== 68729 -LkludFB0cg== 68730 -cmlrZXM= 68731 -ZW52aWxsZQ== 68732 -LnNldExheW91dE1hbmFnZXI= 68733 -IFBhc3Nlbmdlcg== 68734 -IGRpc29i 68735 -IGZlcm1lbnQ= 68736 -LlBpeGVs 68737 -Pign 68738 -IGNvbnRlbmRlcnM= 68739 -LWJldGE= 68740 -IGFmZmlybWF0aXZl 68741 -0L3QvtGB0YLQuA== 68742 -aWHDp8Ojbw== 68743 -UmVjb21tZW5k 68744 -aW1pdGVycw== 68745 -X3lsaW0= 68746 -IHN1YnNpZHk= 68747 -IGVyYg== 68748 -RmlsZVNpemU= 68749 -KHNy 68750 -IHBvb3Jlc3Q= 68751 -IHZvaQ== 68752 -U2lk 68753 -IHNsaXBz 68754 -X21pbnV0ZXM= 68755 -IHVn 68756 -xqFu 68757 -IG5hdMO8cmxpY2g= 68758 -44Oe 68759 -YmVhcg== 68760 -fV8kew== 68761 -IGZpc3Nl 68762 -IGRpc2NyaW1pbmF0b3J5 68763 -CQkgIAo= 68764 -IENvaWw= 68765 -X2lmYWNl 68766 -LnZlcg== 68767 -IG1pbmVk 68768 -IGFzc2Fzc2lu 68769 -IHVuc2V0dA== 68770 -LnJlcXVlc3Rz 68771 -LlVT 68772 -aW1hZ2VVcmw= 68773 -IHN0cmF0ZWdpY2FsbHk= 68774 -LWJhbmQ= 68775 -IHRyb3VzZXJz 68776 -WEQ= 68777 -ey8= 68778 -bGVjdGlvbnM= 68779 -YCgp 68780 -IlA= 68781 -IHNrZXRjaGVz 68782 -Y2xpZW50SWQ= 68783 -IFNyYw== 68784 -b3BlbmluZw== 68785 -UHV0aW4= 68786 -IFBvZXRyeQ== 68787 -IFBST00= 68788 -SUxMSVNFQ09ORFM= 68789 -IGJvb21pbmc= 68790 -U2ltaWxhcmx5 68791 -Omxhc3Q= 68792 -Lndvcmtlcg== 68793 -LmdldElE 68794 -LlNQ 68795 -c2VydmVycw== 68796 -b2N1bGFy 68797 -IHNwaW5hY2g= 68798 -SVNL 68799 -w7A= 68800 -J10pWw== 68801 -IGNoaWVmcw== 68802 -IGdyb8OfZW4= 68803 -cmlldmluZw== 68804 -LmFzaw== 68805 -LXN1cg== 68806 -VlY= 68807 -Lz4iOwo= 68808 -KHJlbW92ZQ== 68809 -IEtM 68810 -IEhhbGV5 68811 -QFJlc3BvbnNlQm9keQ== 68812 -LSY= 68813 -U3dhZ2dlcg== 68814 -IHpuYWo= 68815 -Lm9uRXJyb3I= 68816 -cmVnbw== 68817 -ZWxpeA== 68818 -IEFWQUlMQUJMRQ== 68819 -IHNlcGVydGk= 68820 -aWFw 68821 -X21pc3M= 68822 -IHN1cmdlcmllcw== 68823 -IGltcGFydGlhbA== 68824 -IENvdA== 68825 -YWt0aW9u 68826 -IHdoaXRlbGlzdA== 68827 -INCw0LI= 68828 -X21peA== 68829 -IEJlZHJvb21z 68830 -IHByaW1laXJh 68831 -IHNpZ25pZmljYQ== 68832 -L2J5 68833 -IHN0YXJ0bGluZw== 68834 -IFNQRQ== 68835 -dWNjacOzbg== 68836 -TnVtZXI= 68837 -SUJN 68838 -LmZyYWdtZW50cw== 68839 -UmVudA== 68840 -IHLDs3duaWXFvA== 68841 -LkFVVE8= 68842 -LkZvckVhY2g= 68843 -IFpodQ== 68844 -IEN1bm5pbmc= 68845 -IFdhcm4= 68846 -IEJI 68847 -X0RPV05MT0FE 68848 -QnlLZXk= 68849 -KeKAlA== 68850 -IGNvbW1hbmRl 68851 -X0FOUw== 68852 -Q2hyb24= 68853 -RklU 68854 -X2F0b21z 68855 -X1NLSVA= 68856 -IHZhcA== 68857 -KEJveA== 68858 -IGxkYXA= 68859 -dW5wcm9jZXNzYWJsZQ== 68860 -SVRJT05T 68861 -w6lyw6k= 68862 -LG1zZw== 68863 -IG91dHNldA== 68864 -IGRyaWxsZWQ= 68865 -IGTDqXZlbG9wcA== 68866 -IENvYXQ= 68867 -IEJlbmdoYXpp 68868 -SG9va3M= 68869 -IE1pc3NpbGU= 68870 -X1Jlc2V0 68871 -Pi88 68872 -ICItIgo= 68873 -KCk9PnsK 68874 -IEhvY2g= 68875 -LmF3YWl0 68876 -QWRyZXNzZQ== 68877 -IGRpZ2l0YWxseQ== 68878 -IlRoZXNl 68879 -b3BsZXZlbA== 68880 -IGFzeW5jaHJvbm91c2x5 68881 -IER1Y2tz 68882 -UkVTUA== 68883 -SVJP 68884 -LmZpeA== 68885 -IFJhZGFy 68886 -dmVydGlzZQ== 68887 -w61zZXM= 68888 -SXRlcmF0aW9ucw== 68889 -bW91c2V1cA== 68890 -bWludA== 68891 -RklSU1Q= 68892 -IHBheXBhbA== 68893 -X3VwZ3JhZGU= 68894 -V3JhcHBlZA== 68895 -Ow0NDQo= 68896 -K3M= 68897 -IGNhdGNoZXI= 68898 -Lk9w 68899 -X05PVElDRQ== 68900 -cGFyYWxsZWxlZA== 68901 -Q1ZF 68902 -Zm9yZ290 68903 -IHBhbm9y 68904 -IG9mZnJl 68905 -IGVub3JtZQ== 68906 -KCkNCg0KDQo= 68907 -YWRpYXRvcg== 68908 -YWRkQWxs 68909 -W3RleHQ= 68910 -KHV0aWw= 68911 -LlByb21pc2U= 68912 -YW5pc20= 68913 -X29mZmVy 68914 -RU5ESUY= 68915 -ZG90cw== 68916 -IEtybw== 68917 -IHNwZWxsZWQ= 68918 -IGFwcE5hbWU= 68919 -QWN0aXZpdGllcw== 68920 -IFNwaWNl 68921 -ZWF0ZWQ= 68922 -IHNrYg== 68923 -IGvDtno= 68924 -IHRvcmNodmlzaW9u 68925 -Q2l2aWw= 68926 -IGhvcw== 68927 -X0hlbHBlcg== 68928 -acSH 68929 -X3Vuc2lnbmVk 68930 -6K66 68931 -4oCcQW5k 68932 -CWtmcmVl 68933 -LnJhaXNl 68934 -IGNhbGxl 68935 -IExhbnM= 68936 -IGFudGln 68937 -XCI+IjsK 68938 -YnJhbmNoZXM= 68939 -bG9ncmFkb3Vybw== 68940 -IHN0YWxsZWQ= 68941 -YWx5emVk 68942 -RGVyaXZlZA== 68943 -Om5vdA== 68944 -IGdpYmk= 68945 -IFR1cm5idWxs 68946 -LnVzZXJEYXRh 68947 -KFRhYmxl 68948 -IERlcml2ZWQ= 68949 -CWNvbmY= 68950 -IGFsZ2Fl 68951 -IGthZmth 68952 -IG5ha25l 68953 -IEhlYXRpbmc= 68954 -IFRpcmU= 68955 -YWR1bHQ= 68956 -IERhdGVGb3JtYXQ= 68957 -b3Bj 68958 -ZW5zYWdlbQ== 68959 -LlRvb2xz 68960 -Lk1peGVkUmVhbGl0eQ== 68961 -cmFp 68962 -IFdvbmRlcmZ1bA== 68963 -KV0pCgo= 68964 -aWFyZA== 68965 -VGhlbWVQcm92aWRlcg== 68966 -IGV2ZW50RGF0YQ== 68967 -I2Fk 68968 -LmdldFVybA== 68969 -IHRvb2xib3g= 68970 -IG92ZXJyaWRpbmc= 68971 -Q09OVEVOVA== 68972 -LXByb2R1Y3Rz 68973 -d2lsZA== 68974 -X2V4cGFuZA== 68975 -aW5haXJl 68976 -QnJ1 68977 -b2xscw== 68978 -INGN0YLQvg== 68979 -Y3Rlc3Q= 68980 -IHB1bmNoaW5n 68981 -RFJW 68982 -X3NwYWNlcw== 68983 -IFN1cGVyaW50ZW5kZW50 68984 -IGxheXVp 68985 -KGZlZWQ= 68986 -dG9k 68987 -IHZo 68988 -IGluc3VsdHM= 68989 -IFN1Yw== 68990 -aWtz 68991 -VG9ycmVudA== 68992 -Lmty 68993 -X2FjdGl2YXRl 68994 -k5g= 68995 -amVl 68996 -aW1lcnM= 68997 -cnVpdHM= 68998 -IHByZWNpbmN0 68999 -LlJlcXVpcmVk 69000 -IHNhdGlzZmllcw== 69001 -IGNoZWVyaW5n 69002 -IGFycml2 69003 -CXJlYw== 69004 -IENvYmI= 69005 -IGNvbmN1c3Npb24= 69006 -dWpldA== 69007 -Tm90Rm91bmRFcnJvcg== 69008 -SmVhbg== 69009 -IHBob3Rvbg== 69010 -Pl8= 69011 -IEJhcmNs 69012 -YW1k 69013 -ICV9Cg== 69014 -PVwiIw== 69015 -SW50ZXJu 69016 -IENvbW1pdHRlZXM= 69017 -LmJlbA== 69018 -bnVtbWVy 69019 -IGxldml0cmE= 69020 -X3ZlcmJvc2U= 69021 -KGNvZGVj 69022 -IFN0aXRjaA== 69023 -PSIiOw0K 69024 -IHJlZ3JldHM= 69025 -IG11bHRpbmF0aW9uYWw= 69026 -IHJlc3RydWN0dXJpbmc= 69027 -IE1FTg== 69028 -eW5jaHJvbml6YXRpb24= 69029 -IG1lZGlhdG9y 69030 -a2ly 69031 -UHJpbmNl 69032 -IGluaGliaXQ= 69033 -IGdvc3Q= 69034 -IE1NQw== 69035 -IHNpZGVk 69036 -X2Rhcms= 69037 -KGJsb2I= 69038 -PkxvcmVt 69039 -PiIpOwoK 69040 -c2Nhbm5lcg== 69041 -OmlubGluZQ== 69042 -LmNhcm91c2Vs 69043 -b3RpZGU= 69044 -IFdXVw== 69045 -IGRydW1tZXI= 69046 -LmZhbWlseQ== 69047 -IG9yZGluYWw= 69048 -5b2T5YmN 69049 -IGRpcGxvbWF0 69050 -IHN1cHBsZW1lbnRhbA== 69051 -IGRhZsO8cg== 69052 -IEZBVA== 69053 -IFlvbmc= 69054 -aGFwdXM= 69055 -IEp1bmN0aW9u 69056 -emw= 69057 -LlVzZUZvbnQ= 69058 -IGhhc2hNYXA= 69059 -LVJl 69060 -ICIqKg== 69061 -LnNldEJhY2tncm91bmRSZXNvdXJjZQ== 69062 -IGltcGVyZmVjdA== 69063 -LkZpbmRFbGVtZW50 69064 -IExMUA== 69065 -IG11cmRlcmVy 69066 -IHRleHRl 69067 -aXPDqQ== 69068 -YWN0aWNz 69069 -VG95 69070 -R3JhbnQ= 69071 -X2Rpc2Nvbm5lY3Q= 69072 -IGJyYXNpbGU= 69073 -IGVtZXJnZW5jaWVz 69074 -X2x2bA== 69075 -IEAiXA== 69076 -fSovCgo= 69077 -X1NPQw== 69078 -Tk9STUFM 69079 -L2dhbGxlcnk= 69080 -YXNpY3M= 69081 -RXZlbnR1YWxseQ== 69082 -IGdyYXA= 69083 -IGNyaXN0 69084 -IHByb2plY3Rvcg== 69085 -IGdlb21ldA== 69086 -IGRldGVjdG9ycw== 69087 -IGNyaXRpY2l6aW5n 69088 -IGNoaWNrcw== 69089 -IEhpag== 69090 -L2ZyYW1l 69091 -LW1vbmV5 69092 -ImRlc2NyaXB0aW9u 69093 -IHRleHRpbmc= 69094 -IHNleGlzbQ== 69095 -IE1WQw== 69096 -LWdlbmVyYWw= 69097 -IG92ZXJ0dXJuZWQ= 69098 -IG1vdmVy 69099 -IFBocmFzZQ== 69100 -IFVOVVNFRA== 69101 -IEVudHJlcHJlbmV1cg== 69102 -VEVHUg== 69103 -ZWxsaXBzZQ== 69104 -TWFya2Rvd24= 69105 -X18oKg== 69106 -IEthcmRhc2hpYW4= 69107 -cHBlbGlu 69108 -IEdvdHQ= 69109 -IGR5c3Q= 69110 -IFJlZHV4 69111 -SG9sYQ== 69112 -PyEKCg== 69113 -IFJlYWx0eQ== 69114 -U3VydmV5 69115 -IE1jR3JlZ29y 69116 -X2hhbmRsZXM= 69117 -IGludHJpZ3VlZA== 69118 -IGdldFVybA== 69119 -IGRldmlzZWQ= 69120 -IFBheXBhbA== 69121 -IHRoaW5rZXJz 69122 -IFN0YXR1c0Jhcg== 69123 -IEVsaWc= 69124 -IGNvbXBsZXhlcw== 69125 -INC60L7QtA== 69126 -c3RvY2tz 69127 -LWluaXRpYWxpemVk 69128 -IHNjYW5kYWxz 69129 -IGNvbWZvcnRpbmc= 69130 -IFJvY2tz 69131 -IGxpb25z 69132 -bG9jYXRvcg== 69133 -IV0= 69134 -IFBvbnk= 69135 -RGF0dW0= 69136 -IEZldA== 69137 -IG9mZnNldFk= 69138 -IFJFVFVSTlM= 69139 -IGJyZWFjaGVz 69140 -VGltZUludGVydmFs 69141 -IHZpZWxlbg== 69142 -VmVyc2U= 69143 -IGthZA== 69144 -IGdhYXQ= 69145 -KCItIiw= 69146 -IG1vdXNlWQ== 69147 -KFBvc3Q= 69148 -IFVo 69149 -ZWxpZ2libGU= 69150 -YWx0YQ== 69151 -IHV0aWxpc2U= 69152 -ZmFjdHM= 69153 -SElQ 69154 -IG9yY2hlc3RyYQ== 69155 -IFNwYWNlcw== 69156 -aXNwaWVs 69157 -IG11bHRpcGFydA== 69158 -LW9wYWNpdHk= 69159 -U2VhcmNoaW5n 69160 -IFBsYXRv 69161 -VmlzaW9u 69162 -IGx1bA== 69163 -IEFwcHJlbnQ= 69164 -57uc 69165 -W3JhbmQ= 69166 -LWRpc2FibGVk 69167 -IEZsZXRjaGVy 69168 -IHRyYW5zcG9ydHM= 69169 -JmU= 69170 -dHBhcmFt 69171 -cG9sZQ== 69172 -IEJ1ZW5vcw== 69173 -w7pibGljYQ== 69174 -aW50ZXJhY3Rpb24= 69175 -IGhvYg== 69176 -IGluZmxpY3RlZA== 69177 -bGl0ZQ== 69178 -IFBBUkFNRVRFUlM= 69179 -IFN0YW0= 69180 -KG14 69181 -IEF1dG9NYXBwZXI= 69182 -aWxpYW4= 69183 -IHF1aXR0aW5n 69184 -PXt9 69185 -IEpvbmFz 69186 -IGxvY2FsaXR5 69187 -IFNpbGVuY2U= 69188 -X2ZsdXR0ZXI= 69189 -IG5icg== 69190 -bGl0ZXI= 69191 -IE5vcm1hbGl6ZQ== 69192 -IGFjdW0= 69193 -QnJhaW5z 69194 -ZXF1aXA= 69195 -XT09Ig== 69196 -IGRlc3Rpbm8= 69197 -IERpb3M= 69198 -Lk11bHRpbGluZQ== 69199 -YWdyZWU= 69200 -KQoKCgoKCgoK 69201 -IHN0ZWxsZW4= 69202 -IGN1cmx5 69203 -Lk9mZmljZQ== 69204 -LWFib3V0 69205 -ICcuLy4uLy4uLw== 69206 -IFVUSUw= 69207 -IFJw 69208 -4oC6 69209 -IG1hcGE= 69210 -LkRP 69211 -YWdhbA== 69212 -LndpbmRvd3M= 69213 -IGFkdmVyc2VseQ== 69214 -Llh0cmFMYXlvdXQ= 69215 -bWVkaWNhbA== 69216 -IHVuc3Vy 69217 -dGhlcm1hbA== 69218 -Lk1vZGVsQWRtaW4= 69219 -LmFjdHVhbA== 69220 -c2V0Q29udGVudA== 69221 -IHBvc3RmaXg= 69222 -UFc= 69223 -IENoYWlycw== 69224 -IGdyYW1t 69225 -IGNvbXBsaWM= 69226 -RElTUExBWQ== 69227 -IE1vb3Nl 69228 -aGFhcg== 69229 -QUxFUw== 69230 -IGxkYQ== 69231 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqCg== 69232 -ICcvJwo= 69233 -QVNO 69234 -IEJhcmJlcg== 69235 -IG1haW5z 69236 -IG1haW5XaW5kb3c= 69237 -0LDQt9Cy0LDQvdC40LU= 69238 -IGVtYW4= 69239 -X2NvbGxlY3Q= 69240 -IHJlbXBs 69241 -LnRheA== 69242 -YmFo 69243 -IFBzeWNoaWF0cnk= 69244 -RGVzY3JpcHRpb25z 69245 -IGV4ZWN1dGlvbnM= 69246 -CUxPR0dFUg== 69247 -JkU= 69248 -OmJn 69249 -IGtk 69250 -LmRhbWFnZQ== 69251 -IG5pc2k= 69252 -5qy+ 69253 -IENhbWVs 69254 -aW5pZGFk 69255 -IExpZmVzdHlsZQ== 69256 -IFRISVJE 69257 -IOCkuA== 69258 -IHBvbHlnb25z 69259 -IGF0dGlyZQ== 69260 -YWxlbnQ= 69261 -X1VTQVJU 69262 -IG1hbGFyaWE= 69263 -bG9icw== 69264 -IF19Cg== 69265 -KHJlZ2lzdGVy 69266 -LXBz 69267 -X29wdGltaXplcg== 69268 -KEFMT0FE 69269 -IHZhcGU= 69270 -LnNvY2s= 69271 -kOiXjw== 69272 -JHByb2R1Y3Q= 69273 -KEVSUg== 69274 -Y2twdA== 69275 -YnVxdWVycXVl 69276 -IH19Ij57ew== 69277 -IEhpdmU= 69278 -IE1hc2g= 69279 -IEVwaWQ= 69280 -IEx1bmQ= 69281 -X3RyYW5zYWN0aW9ucw== 69282 -IHN1YmNsYXNzZXM= 69283 -RWFzZQ== 69284 -X0Nsb3Nl 69285 -X2NoZWNrb3V0 69286 -IicsCg== 69287 -U2VjdG9y 69288 -b2lzZQ== 69289 -LXRlbXA= 69290 -KSIp 69291 -aHlwZXI= 69292 -ZXJjdWw= 69293 -c3RhY2twYXRo 69294 -X05S 69295 -SUxMRQ== 69296 -IHJlbGFjacOzbg== 69297 -IE1hdHRo 69298 -X0NPREVD 69299 -IGhhbmRsZUVycm9y 69300 -X09uZQ== 69301 -YWxib3Jn 69302 -CQkgICAgICAgICA= 69303 -IFVwbG9hZGVk 69304 -Tm0= 69305 -Ly89 69306 -KlM= 69307 -X0VYUEVDVA== 69308 -IGZyYWN0aW9uYWw= 69309 -Q291 69310 -IHNjYWxhYmxl 69311 -IENJRA== 69312 -PFBvc3Q= 69313 -CXRocmVhZA== 69314 -aGFyZHdhcmU= 69315 -LmNoYW5nZWQ= 69316 -LkVsZW1lbnRBdA== 69317 -IGFydGljdWxhdGU= 69318 -ZWRvcmVz 69319 -RXN0YWJsaXNo 69320 -PXtbCg== 69321 -ISo= 69322 -IFNK 69323 -TWV0ZXI= 69324 -LnJlcA== 69325 -IFZPTA== 69326 -IE91 69327 -bMOp 69328 -IHBuZXVtb25pYQ== 69329 -X3BpY2tlcg== 69330 -ZXhwbG8= 69331 -IOyekQ== 69332 -IFN3aW0= 69333 -ZHJlc3M= 69334 -c3Rvcmllcw== 69335 -L25hdg== 69336 -VmE= 69337 -INit 69338 -L3NlbGY= 69339 -IHZldGVyaW5hcnk= 69340 -KERlbnNl 69341 -CWJvb3N0 69342 -IElzTm90 69343 -IHRydXN0aW5n 69344 -IExlYmFuZXNl 69345 -JHJlcXVlc3Q= 69346 -eGZmZmZmZg== 69347 -X3JlbW92ZWQ= 69348 -IHVwZGF0ZXI= 69349 -2KfY 69350 -RE9XTkxPQUQ= 69351 -IEltbWVkaWF0ZWx5 69352 -IHJvYW1pbmc= 69353 -IEhvcm55 69354 -LmNvZGlnbw== 69355 -IEZpZ3VyZXM= 69356 -IHBhbnRyeQ== 69357 -KHNhbXBsZXM= 69358 -IEJFTA== 69359 -IHNldENvbnRlbnQ= 69360 -dW1vcg== 69361 -5pSv5LuY 69362 -X01JTlVT 69363 -IHVubGVhc2hlZA== 69364 -IHByb2ZpY2llbnQ= 69365 -CVVJ 69366 -LkV4Y2VwdGlvbnM= 69367 -IHNyYW5k 69368 -UHJlc3N1cmU= 69369 -LmFzc2VydE5vdA== 69370 -KHNlcmlhbGl6ZXI= 69371 -CXR4dA== 69372 -UG9ydHM= 69373 -IG5lY2VzYXJpbw== 69374 -IHJldml2ZWQ= 69375 -IG1pbGVzdG9uZXM= 69376 -Y2Fubw== 69377 -RXNjb3J0 69378 -IGVudGVuZA== 69379 -QVBF 69380 -aXBj 69381 -LmF0b21pYw== 69382 -IFBlbWI= 69383 -IHJlYWNoYWJsZQ== 69384 -IGthbnM= 69385 -d2hhdGV2ZXI= 69386 -TGlzdEJveA== 69387 -IENseQ== 69388 -cGljdHVyZWQ= 69389 -IEVsZWN0cm8= 69390 -YWJpYw== 69391 -IGZ1bms= 69392 -IGRpYXJyaGVh 69393 -IOeZ 69394 -IFNvbHZlcg== 69395 -IEJhYw== 69396 -IHNrZWxldGFs 69397 -IO+C 69398 -IEZpbGVOb3RGb3VuZEV4Y2VwdGlvbg== 69399 -ICIpWw== 69400 -IFRyYWl0 69401 -dWRva3U= 69402 -LS0tLS0tLS0tLQoK 69403 -QW5nZWw= 69404 -YWdy 69405 -IHNpbXBsZXM= 69406 -IGJhbmM= 69407 -IEFsZXJ0cw== 69408 -IENvbmZpcm1hdGlvbg== 69409 -IEFseQ== 69410 -Y2FsbGJhY2tz 69411 -IGZ1bmt0aW9u 69412 -IGdyYWZ0 69413 -WVBE 69414 -L0FGUA== 69415 -V0s= 69416 -a3Vy 69417 -Q0tFVA== 69418 -IFNsYXRl 69419 -IFN0ZWY= 69420 -CVJ1bnRpbWU= 69421 -IEVTTA== 69422 -IHByZWFjaGluZw== 69423 -QnJvYWQ= 69424 -IHNldERlc2NyaXB0aW9u 69425 -YXplbA== 69426 -PQoK 69427 -IGphY2twb3Q= 69428 -IC8vIQo= 69429 -dmlhcg== 69430 -IGVpZA== 69431 -IGF0aXY= 69432 -IHJlZmxleGl2aXR5 69433 -Lkxpc3Rlbg== 69434 -IGx5cmlj 69435 -IHZlcms= 69436 -IGNvbGx1c2lvbg== 69437 -YXphYXI= 69438 -IHdpbms= 69439 -IE11ZA== 69440 -L29wZXJhdG9y 69441 -IGV4dGVybmFsbHk= 69442 -IGJhcnU= 69443 -IGJhc2tldHM= 69444 -dGlja2Vy 69445 -KHBob3Rv 69446 -X2V2ZW4= 69447 -IHNwb25nZQ== 69448 -IGhlaWdodEZvcg== 69449 -Z2V0Q2hpbGQ= 69450 -X2Zvcm1hdHM= 69451 -LkV4ZWN1dGlvbg== 69452 -X1Byb3BlcnR5 69453 -cmVwb3M= 69454 -dGhlaWQ= 69455 -X1BIWVM= 69456 -IGV2aWRlbmNlZA== 69457 -LmhlYWRpbmc= 69458 -QW5ndWxhcg== 69459 -IFZlbnVl 69460 -IEhPVVNF 69461 -IEVzdG9uaWE= 69462 -0LzQsA== 69463 -cmdhbml6YXRpb24= 69464 -L2RldmljZQ== 69465 -SVJS 69466 -X3RoZW4= 69467 -YXJlbQ== 69468 -IGFnZ2k= 69469 -RU1PTg== 69470 -INGB0Lo= 69471 -IEVwaA== 69472 -IE1TUA== 69473 -IGxvZ2ZpbGU= 69474 -LWxlYWRpbmc= 69475 -YXRoYW0= 69476 -IHVubWF0Y2hlZA== 69477 -IFNpdHVhdGlvbg== 69478 -KCl7fQo= 69479 -CWNoYW5nZQ== 69480 -IENoYXB0ZXJz 69481 -LlJFU1VMVA== 69482 -IG9l 69483 -RVRZ 69484 -X3ZpZA== 69485 -Li4uJyw= 69486 -IGFsdGVybmF0aXZlbHk= 69487 -X1dT 69488 -IFBsZW50eQ== 69489 -IENyYXRl 69490 -YXNpb25hbGx5 69491 -IExhd24= 69492 -IElNTQ== 69493 -IFZhbml0eQ== 69494 -IFZvb3I= 69495 -5ZCv 69496 -IG1pag== 69497 -c3RlcnJlaWNo 69498 -IFJERg== 69499 -IENyaXRlcmlvbg== 69500 -Lkludg== 69501 -LlN0ZXA= 69502 -X0ZyYW1l 69503 -IEVOVU0= 69504 -774= 69505 -SG9wZWZ1bGx5 69506 -TmF2Q29udHJvbGxlcg== 69507 -IOy2lOqwgA== 69508 -IFZhZGVy 69509 -IHJ1dGhsZXNz 69510 -JGtleQ== 69511 -Y2t0 69512 -aW5lbQ== 69513 -aWxlbnQ= 69514 -IHJlc3BlY3Rpbmc= 69515 -bGNk 69516 -KGJ0 69517 -IEVsbGlvdA== 69518 -IFVuaWRvcw== 69519 -KENoYW5uZWw= 69520 -IGVpdXM= 69521 -IGFzdHJvbmF1dHM= 69522 -IEhvc3Rpbmc= 69523 -IGNhc3Rl 69524 -IGhhcm1lZA== 69525 -b3VwbGVz 69526 -PFJvbGU= 69527 -LkRlc2M= 69528 -LWNvdXJzZQ== 69529 -IENhcnRvb24= 69530 -aWxlZ2Vk 69531 -IG15c3RpY2Fs 69532 -IOex 69533 -KGZpZWxkTmFtZQ== 69534 -V0lUSE9VVA== 69535 -LHN1bQ== 69536 -J2FjYw== 69537 -CXJvd3M= 69538 -IGdldFBhc3N3b3Jk 69539 -IGNvY2tz 69540 -cGl2b3Q= 69541 -bmFtZW9m 69542 -IGZlYXNpYmlsaXR5 69543 -IGNvbW1lbmNlbWVudA== 69544 -IERvbWU= 69545 -LkpTT05FeGNlcHRpb24= 69546 -IEh5ZGVyYWJhZA== 69547 -IExpc3RlZA== 69548 -IENvbXB1dGVycw== 69549 -W3ZhbA== 69550 -IGlzb3Q= 69551 -CXdpbg== 69552 -IG5laA== 69553 -KElOVA== 69554 -UmVwdWJsaWNhbg== 69555 -INC/0YDQvtCy0LXRgA== 69556 -RmF0 69557 -IGVxdWl2 69558 -IERhdHVt 69559 -YXN0aQ== 69560 -IHNvaWxz 69561 -dXB1bmN0dXJl 69562 -cHJlc3NpdmU= 69563 -XykpOwo= 69564 -Lldhcm4= 69565 -IGhhcmI= 69566 -Lm9uT3B0aW9uc0l0ZW1TZWxlY3RlZA== 69567 -IGNsb3du 69568 -IE9XTg== 69569 -IGV4YW1pbmF0aW9ucw== 69570 -IEV4aXN0aW5n 69571 -am91cmQ= 69572 -IGNvbmNlc3Npb24= 69573 -IEZpcmViYXNlRGF0YWJhc2U= 69574 -IHVwdGFrZQ== 69575 -IGVubGlzdGVk 69576 -IENhcmI= 69577 -IGZ1cw== 69578 -IGFidXNpbmc= 69579 -LnByb2R1Y3Rpb24= 69580 -eW5jaA== 69581 -aWx5bg== 69582 -cmVmdW5k 69583 -LWhhdmU= 69584 -KGFyZ3VtZW50 69585 -IGZzY2FuZg== 69586 -Y29uY2VwdA== 69587 -X0xBTkU= 69588 -IGVuZ2FnZXM= 69589 -IEV4YWN0bHk= 69590 -YWx0dXJh 69591 -KEFkZHJlc3M= 69592 -IHN5bm9ueW1vdXM= 69593 -VG93bg== 69594 -IFBheW5l 69595 -cm9pdA== 69596 -cGVyaWVuY2Vz 69597 -cGFydGljbGVz 69598 -X2Jk 69599 -IEdyaW5kZXI= 69600 -TWFuYWdlZE9iamVjdENvbnRleHQ= 69601 -KGJi 69602 -W3RtcA== 69603 -LWNvbnM= 69604 -YW9rZQ== 69605 -IHN0ZXdhcmQ= 69606 -IFZpZXdDaGlsZA== 69607 -LmRyYXdMaW5l 69608 -IFdBUk4= 69609 -IHB1ZXM= 69610 -bW9kYXRpb24= 69611 -IHpz 69612 -QWdyZWdhcg== 69613 -ICIuIiw= 69614 -LmNlbnRlclk= 69615 -IGZsYXdsZXNz 69616 -IGRldXRzY2hl 69617 -IExpcXU= 69618 -aXRlaXQ= 69619 -X2ludHJv 69620 -LXVzZWQ= 69621 -LHRhcmdldA== 69622 -IEhERA== 69623 -ICUr 69624 -b3JlbnQ= 69625 -L09iamVjdA== 69626 -IGRpc3J1cHRlZA== 69627 -w6J0ZQ== 69628 -IGFjY2Vzbw== 69629 -IExvd2VzdA== 69630 -IFdpbGxpYW1zb24= 69631 -X2NyZWF0b3I= 69632 -U2VsbA== 69633 -IEJVRw== 69634 -X3JlcHI= 69635 -6ICM 69636 -IGFyY2hhZW9sb2dpY2Fs 69637 -b21lcnM= 69638 -IEVsb24= 69639 -IFNjcm9sbFZpZXc= 69640 -IGxpbmVzdHlsZQ== 69641 -aXNSZXF1aXJlZA== 69642 -aXNrbw== 69643 -X3Ji 69644 -ZsO8aA== 69645 -ICAgCQk= 69646 -KGRlZmluZQ== 69647 -IFNDTQ== 69648 -IERJRkY= 69649 -X2Jz 69650 -cGVuZGljdWxhcg== 69651 -cGFjZWQ= 69652 -IEpvdXJuYWxpc20= 69653 -LkpTT05BcnJheQ== 69654 -IERhdGFBY2Nlc3M= 69655 -TWFyaWE= 69656 -IELDvA== 69657 -SEVMTA== 69658 -IE1BVFJJWA== 69659 -T0xUSVA= 69660 -YXBzaWJsZQ== 69661 -XToKCg== 69662 -bmFpcmVz 69663 -X2hpc3RvZ3JhbQ== 69664 -IGZsYWly 69665 -aGF2aW5n 69666 -IFVzZXJJRA== 69667 -IFJlbGF0aW9uc2hpcHM= 69668 -UmVwbGFjZW1lbnQ= 69669 -IHJzYQ== 69670 -IGVucmljaGVk 69671 -IHJlaGVhcnM= 69672 -IHfDpHJl 69673 -IGxvYWRlcnM= 69674 -IEVsZW5h 69675 -IFdhdGNoaW5n 69676 -CWpvYg== 69677 -TkVXUw== 69678 -L3NldHRpbmdzZGlhbG9n 69679 -aXZlYw== 69680 -X0VRVUFMUw== 69681 -VGVtcGxhdGVOYW1l 69682 -IEJPRFk= 69683 -LmFkYXB0ZXJz 69684 -d29mZg== 69685 -Y29tYm9Cb3g= 69686 -Lk5ld1JlYWRlcg== 69687 -fHJlcXVpcmVk 69688 -X3Byb2JhYmlsaXR5 69689 -ICg6Og== 69690 -IGNyYXo= 69691 -IFVG 69692 -VGVzdElk 69693 -IGVzcGVjaWZpYw== 69694 -aWJlbA== 69695 -cGF3bg== 69696 -640= 69697 -IE1hcnI= 69698 -IHN0YXJ0WA== 69699 -X3NpdGVz 69700 -Lz4KCg== 69701 -IGltcGxpY2F0ZWQ= 69702 -KGlubmVy 69703 -IGVmZm9ydGxlc3NseQ== 69704 -wq10aW9u 69705 -YXdhcmQ= 69706 -IGhvdmVyaW5n 69707 -cHJp 69708 -JHRlbXBsYXRl 69709 -dWFuZw== 69710 -IGF1dG9tYXRl 69711 -ICoqLwoK 69712 -aWJsaQ== 69713 -IG51dHJpdA== 69714 -KS4o 69715 -ZWVlZQ== 69716 -QXBpQ29udHJvbGxlcg== 69717 -L293bA== 69718 -IFdvbWVucw== 69719 -LWRvdWJsZQ== 69720 -IE9yZGVyaW5n 69721 -c3Bt 69722 -TW9kZXI= 69723 -Lk5hdGl2ZQ== 69724 -IEJlcmdlcg== 69725 -ZXNkYQ== 69726 -ZXJkaW5ncw== 69727 -X2VjaG8= 69728 -IHN1bW1hcml6ZWQ= 69729 -IGVsZXZhdGU= 69730 -X3F1YWQ= 69731 -IHdvbw== 69732 -dWxhbnQ= 69733 -UHJvcGVydHlWYWx1ZQ== 69734 -IHBsaXN0 69735 -IEdSQVBI 69736 -IFNUREVSUg== 69737 -KScpLg== 69738 -QXNzZXJ0aW9u 69739 -bGlua3BsYWlu 69740 -IGFjY2VsZXJhdGluZw== 69741 -IHNuaXBwZXRz 69742 -IFNhbG1hbg== 69743 -YWJjZA== 69744 -LmVjaG8= 69745 -X2lkeHM= 69746 -IHBjbQ== 69747 -b2NhbHlwdGlj 69748 -X2Nvb3JkaW5hdGU= 69749 -KHByZXZpb3Vz 69750 -LXNob3J0 69751 -LnN1YnRyYWN0 69752 -KEJpdA== 69753 -P3Q= 69754 -IE5vdGVib29r 69755 -IEthdHJpbmE= 69756 -aWZmZXJlbnRpYWw= 69757 -c2lsZW50 69758 -dGVybWluYXRlZA== 69759 -IHRhbmdlbnQ= 69760 -OlQ= 69761 -IGNvc8Os 69762 -IHBhcmFub2lk 69763 -IGRlcHJpdmF0aW9u 69764 -L3t7JA== 69765 -IGhlbWlzcGhlcmU= 69766 -IHJlaW5zdA== 69767 -ZWN6 69768 -dGVycg== 69769 -IFBMQVRGT1JN 69770 -IHRyb3VibGVzaG9vdGluZw== 69771 -IHZhbGlkYXRpbmc= 69772 -IE9yaW9u 69773 -YXN1cmluZw== 69774 -0LjQvdCw 69775 -IGh1YnM= 69776 -YXJlbmNl 69777 -IENoYWxsZW5nZXM= 69778 -IHplYWw= 69779 -U3Bv 69780 -IFNjcmVlbnM= 69781 -IG11bmRhbmU= 69782 -IER1bms= 69783 -ICMjIyMj 69784 -IFJFRkVS 69785 -b25ldA== 69786 -LmNhc2U= 69787 -LXBvc2l0aXZl 69788 -SU5URUdFUg== 69789 -Lm1ldHJvTGFiZWw= 69790 -U0FO 69791 -IHByb2Zlc3Npb25z 69792 -IHR5cmVz 69793 -UGFsaW5kcm9tZQ== 69794 -IFNFQ09ORA== 69795 -LkdSRUVO 69796 -IFNuYXBzaG90 69797 -VUxL 69798 -X2NpZA== 69799 -JEk= 69800 -IGN1bnQ= 69801 -ZXN0cnVjdGlvbg== 69802 -UHN5Y2g= 69803 -IEh0dHBSZXNwb25zZU1lc3NhZ2U= 69804 -ZW1iYWxp 69805 -X3Jldmlld3M= 69806 -U2VsZWN0YWJsZQ== 69807 -X1BSRVNFTlQ= 69808 -IEpzb25SZXF1ZXN0 69809 -IFRoZXRh 69810 -X2ludGVycA== 69811 -UmFzdGVy 69812 -I2Vycm9y 69813 -LG9iag== 69814 -IHR3ZWV0aW5n 69815 -X0dQVQ== 69816 -X3RvZGF5 69817 -X3NlY3M= 69818 -bmVlcw== 69819 -LmdldFN5c3RlbVNlcnZpY2U= 69820 -IHZub2Rl 69821 -IFJlZ3VsYXRvcnk= 69822 -IEZhaHJlbmhlaXQ= 69823 -IHNjYWxlcg== 69824 -X21hcmtldA== 69825 -LmFsbG9jYXRl 69826 -dGlja2V0cw== 69827 -YXRhaw== 69828 -IFBpa2U= 69829 -IExvcg== 69830 -ZGl0b3I= 69831 -IGxvY2F0aW9uTWFuYWdlcg== 69832 -IGluaXREYXRh 69833 -IFdhcmU= 69834 -IEluY2lkZW50 69835 -IGNvbW1lbnRhdG9y 69836 -dWVudGVz 69837 -IEluZmxhdGU= 69838 -IOWG 69839 -IGFjdGl2aWRhZA== 69840 -IEJq 69841 -RU5VTQ== 69842 -IHJldXNlZA== 69843 -INC80LXQvQ== 69844 -IHNlc2nDs24= 69845 -LicpKTsK 69846 -44GT44KT 69847 -L2dl 69848 -YWdhaW5zdA== 69849 -LGxpbmU= 69850 -KFVubWFuYWdlZFR5cGU= 69851 -KT0i 69852 -IHl0 69853 -dWRpYW50ZXM= 69854 -cm9sbGFibGU= 69855 -5aGr 69856 -X0NPTExFQ1RJT04= 69857 -b2xpcw== 69858 -dW1iZXJsYW5k 69859 -KCIiIgo= 69860 -IHppcHBlcg== 69861 -DAo= 69862 -L3NpZ251cA== 69863 -IHN0cmFuZHM= 69864 -cmF4 69865 -LmNvbnN1bWVy 69866 -IHVuY2VydGFpbnRpZXM= 69867 -RGVidWdFbmFibGVk 69868 -IGRlZmVhdHM= 69869 -IGRydg== 69870 -IHJlYWxpc20= 69871 -YWdyYW1z 69872 -WEU= 69873 -IEhhemFyZA== 69874 -LW5lZWRlZA== 69875 -KHRhYmxlVmlldw== 69876 -LkVsZW1lbnRz 69877 -IFNBUg== 69878 -CWVsZW0= 69879 -KHBrZw== 69880 -U2ltb24= 69881 -VGludENvbG9y 69882 -IFBoZW4= 69883 -X0VNUA== 69884 -2Iw= 69885 -Pz4KCgo= 69886 -X2F0dHJpYg== 69887 -IGJveFNoYWRvdw== 69888 -IENHQWZmaW5lVHJhbnNmb3Jt 69889 -IENhbmJlcnJh 69890 -IHN0YXJ0UG9z 69891 -IFJhaw== 69892 -CWNlcnI= 69893 -IFRhbnphbmlh 69894 -dW9uZw== 69895 -Y2Fm 69896 -LmJhc2ljQ29uZmln 69897 -b2lucw== 69898 -Q29udGFpbmVk 69899 -PXNldA== 69900 -X2dpdA== 69901 -CXBhY2tldA== 69902 -IGNvZg== 69903 -KFRS 69904 -5qC85byP 69905 -KHt9KQo= 69906 -IGRpcmVjY2lvbg== 69907 -IHBsYXlsaXN0cw== 69908 -IGFmZmluZQ== 69909 -LnNldFNlbGVjdGlvbg== 69910 -IGFtbW9u 69911 -IGNvbnF1ZXJlZA== 69912 -IFJhbW9z 69913 -IFBTUA== 69914 -PXN1bQ== 69915 -IGNvcnJlbGF0aW9ucw== 69916 -IHJvYWRtYXA= 69917 -IGV4dGluY3Q= 69918 -IGFkdmlzYWJsZQ== 69919 -IGJvbWJlcnM= 69920 -IFVJUmVzcG9uZGVy 69921 -X0JQ 69922 -INCx0YPQtNC10YI= 69923 -IFByZW1pZXJl 69924 -IFJV 69925 -dHJhc2g= 69926 -KGNsanM= 69927 -Z251 69928 -LlBhZ2Vz 69929 -IGluc3BlY3RvcnM= 69930 -TWV4aWNv 69931 -IFZlcmU= 69932 -UHJlYw== 69933 -IFNjYWw= 69934 -aXNwZXJz 69935 -UnVubmFibGU= 69936 -Lm9yaWc= 69937 -IHNhaWxvcnM= 69938 -UGFyc2luZw== 69939 -IFZpc2l0b3Jz 69940 -JnR5cGU= 69941 -cG9wb3Zlcg== 69942 -PCgpLA== 69943 -IG93ZXM= 69944 -IHJlYWN0cw== 69945 -IERlZmluZWQ= 69946 -IHJlYWxtZW50ZQ== 69947 -IGRpY3RhdG9yc2hpcA== 69948 -YWRtaW5pc3Ry 69949 -aWRlbmQ= 69950 -PUw= 69951 -c3RyY2FzZWNtcA== 69952 -XSU= 69953 -0L7Qs9GA0LDQvA== 69954 -ZWR1bGE= 69955 -LWRlc2lnbmVk 69956 -Q09WRVI= 69957 -X0NoYW5uZWw= 69958 -IHByb2pldG8= 69959 -eW1vb24= 69960 -Q0hLRVJSUQ== 69961 -6YeK 69962 -IHZlcmlmeWluZw== 69963 -L2tleQ== 69964 -LmZyb21DaGFyQ29kZQ== 69965 -LkJpdA== 69966 -X2J1ZGdldA== 69967 -ICUi 69968 -dmV5b3I= 69969 -IHl1bQ== 69970 -IGV4dHJlbWVz 69971 -X0NSRQ== 69972 -Z2V0U3RhdHVz 69973 -c3Vic2VjdGlvbg== 69974 -IHNvYWtlZA== 69975 -IGdlbmF1 69976 -X0NIQVJBQ1RFUg== 69977 -5oyB 69978 -LW9ubGluZQ== 69979 -LnRvQ2hhckFycmF5 69980 -Y2VyZXI= 69981 -Il0sIg== 69982 -IHN0cm9sbA== 69983 -IFl1YW4= 69984 -IFdhbmRlcg== 69985 -IHNpc3RlbQ== 69986 -X3Vj 69987 -KG5vbWJyZQ== 69988 -Y2hhbnRtZW50 69989 -KGNsb3Nl 69990 -bWV0aA== 69991 -LXNlY3JldA== 69992 -cHNldWRv 69993 -Q291bnR5 69994 -Q09OVFJPTA== 69995 -IHNvbHZlbnQ= 69996 -IHNvYXJpbmc= 69997 -IHNwaWVz 69998 -TmF2SXRlbQ== 69999 -IHJlc2VtYmxhbmNl 70000 -KGJpdHM= 70001 -IGNlbGx1bA== 70002 -IGFzc29jaWF0aXZl 70003 -Lmltd3JpdGU= 70004 -LmNvb3JkaW5hdGU= 70005 -XSwk 70006 -KHNr 70007 -Ki8p 70008 -IG1vY2tz 70009 -IGp1bmc= 70010 -X0RPQw== 70011 -LXJ1bnRpbWU= 70012 -IEdpdmVz 70013 -dW5q 70014 -KHNlZw== 70015 -KFtc 70016 -IG5haA== 70017 -X2V4cGVjdA== 70018 -Um93SW5kZXg= 70019 -KGZvcmNl 70020 -IEdldFZhbHVl 70021 -IHN1bW1hcmllcw== 70022 -X1NIQVJF 70023 -LXRyYWluZWQ= 70024 -IEJsYW5j 70025 -IGZpdHRpbmdz 70026 -IHdhdGVyZnJvbnQ= 70027 -Lk5vdGU= 70028 -IFdhbmQ= 70029 -b3ZlcmU= 70030 -cHJlZGljdGlvbg== 70031 -IGNzcg== 70032 -LnRvcEFuY2hvcg== 70033 -IFN0cm9rZQ== 70034 -X0ZpbHRlcg== 70035 -YXRoZQ== 70036 -ICJcXCI= 70037 -IEFGRg== 70038 -PSIvIj4= 70039 -LlJlcXVlc3RNZXRob2Q= 70040 -kJzntKI= 70041 -IHdpdG5lc3Npbmc= 70042 -QXBwYXJlbnRseQ== 70043 -IG1kaQ== 70044 -c3RpY2tz 70045 -IEFsdg== 70046 -w6TDnw== 70047 -X2NvbnRpbg== 70048 -IGJvaWxlcnM= 70049 -IE1hcnhpc3Q= 70050 -SU9D 70051 -bmVybw== 70052 -aW5uYWNsZQ== 70053 -TGl0 70054 -Y2Vj 70055 -S2V5UHJlc3M= 70056 -R2V0RGF0YQ== 70057 -IGlzbnQ= 70058 -0YDQvtCy0LXRgA== 70059 -IHFyeQ== 70060 -Um9vdEVsZW1lbnQ= 70061 -IE5TQ29kZXI= 70062 -LmdldE51bQ== 70063 -IHRocmVlc29tZQ== 70064 -VXNlcw== 70065 -LiJf 70066 -IENvbnRpbnVvdXM= 70067 -IHBvcHVsaXN0 70068 -IFBzeWNob2xvZ2ljYWw= 70069 -X2N5Y2xlcw== 70070 -IGlmZGVm 70071 -aXBoZXJhbHM= 70072 -CSAgICAgICAgICA= 70073 -IGFkdmlzZXM= 70074 -IENvbXBhbmlvbg== 70075 -dHJpZ2h0 70076 -IGdyb3dlcnM= 70077 -IFNPQ0tFVA== 70078 -eW1jZQ== 70079 -UlNT 70080 -bWVtYmVyT2Y= 70081 -VG91Y2hhYmxl 70082 -X2FycmF5cw== 70083 -IGp1bXBlcg== 70084 -IGhlcnBlcw== 70085 -IFRpdHM= 70086 -IFRlbGVmb24= 70087 -X1BBTkVM 70088 -dWdlbg== 70089 -5YyX5Lqs 70090 -LlNpdGU= 70091 -X3VucmVnaXN0ZXI= 70092 -X2Nocg== 70093 -LnRm 70094 -LWh1bWFu 70095 -IGFzb2Np 70096 -IHF1ZWVucw== 70097 -QW50aG9ueQ== 70098 -IHN0cmluZ2VudA== 70099 -IG1vbGVzdA== 70100 -c2V0SWNvbg== 70101 -SEVFTA== 70102 -SEVMUA== 70103 -RERT 70104 -LmNtcw== 70105 -SVNUUklCVVQ= 70106 -Y2llcw== 70107 -LmZvckNoaWxk 70108 -LmNoaw== 70109 -IE90dG9tYW4= 70110 -IFRQUA== 70111 -IG1pbw== 70112 -IEJ1Zg== 70113 -Ym9h 70114 -VmVyc2lvbnM= 70115 -KGxvY2FsZQ== 70116 -IFJhaWxyb2Fk 70117 -YmNj 70118 -LyoqPA== 70119 -LXBhaWQ= 70120 -IGNlbGVyeQ== 70121 -YXRpc2NoZQ== 70122 -Z2V0T3B0aW9u 70123 -b3Jpb3VzbHk= 70124 -IGFkYXB0ZXJz 70125 -U3RvcmVz 70126 -L3NhdmU= 70127 -IEJhc2lz 70128 -0Y7Rgg== 70129 -IExhZA== 70130 -X3JlbGF0aW9uc2hpcA== 70131 -IENsdWJz 70132 -IOCo 70133 -OiI8PA== 70134 -X01JU0M= 70135 -VmlzdWFsaXphdGlvbg== 70136 -IG1pcnJvcmVk 70137 -ZXNwZXI= 70138 -U3RyTG4= 70139 -IHJlc3BvbnNlT2JqZWN0 70140 -5ZCR 70141 -LmVuY29kZXI= 70142 -LS0tLS0tLS0tCgo= 70143 -IGdyaWRWaWV3 70144 -X2luZGVudA== 70145 -YW50d29ydA== 70146 -IGFycml2YWxz 70147 -IFNldHRsZW1lbnQ= 70148 -Vmlld0luaXQ= 70149 -LXZhbHVlcw== 70150 -IHdhdGVyZmFsbA== 70151 -IGluY2FyY2VyYXRpb24= 70152 -IFRlZW5z 70153 -CXNpZ24= 70154 -aW1tdW5l 70155 -LnNlY29uZGFyeQ== 70156 -IHZpZGVvZXI= 70157 -IOi+k+WFpQ== 70158 -IGludGltaWRhdGlvbg== 70159 -ZW5kYWxl 70160 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMj 70161 -IGluc2lnaHRmdWw= 70162 -IHNhbmRz 70163 -IHBob3RvZ3JhcGhpYw== 70164 -UGFnaW5hdG9y 70165 -IGRpc2NpcGxpbmVk 70166 -X1RMUw== 70167 -XSkpLA== 70168 -cmxlbg== 70169 -PGNlbnRlcg== 70170 -X1BDTQ== 70171 -S2VsbHk= 70172 -LWJpbGxpb24= 70173 -LmN4 70174 -IGpldXg= 70175 -IGZpbGVMaXN0 70176 -IFFEaWFsb2c= 70177 -dHJhY3RpdmU= 70178 -RHQ= 70179 -IGVzdHJvZ2Vu 70180 -IHN0YXJjaA== 70181 -X2VtaXQ= 70182 -INC30LDQv9GA0L7RgQ== 70183 -IFF1YXJ0 70184 -IGluYWR2ZXJ0ZW50bHk= 70185 -IHRyb25n 70186 -c2hpcG1lbnQ= 70187 -IE5PUg== 70188 -IFNjcmVlbmluZw== 70189 -IERpc2Nvbm5lY3Q= 70190 -bWVubw== 70191 -IFdvcnN0 70192 -IE5y 70193 -e2s= 70194 -c3Bs 70195 -X2N0cg== 70196 -LnNvcnRlZA== 70197 -LXBsYWNlaG9sZGVy 70198 -KCk7Ig== 70199 -aHVyc3Q= 70200 -LWhpdA== 70201 -LnNvbHZl 70202 -566X 70203 -IHVuZGVhZA== 70204 -IHdoaW1z 70205 -IGdldERlZmF1bHQ= 70206 -IE5pa2tp 70207 -YXNzZW1ibGU= 70208 -IHJlbG9jYXRlZA== 70209 -LXJldA== 70210 -SXRhbGlhbg== 70211 -OlN5c3RlbQ== 70212 -LnNjaGVkdWxlcg== 70213 -4oCcU28= 70214 -Rm9yYmlkZGVu 70215 -QVZPUg== 70216 -emlhxYI= 70217 -LkFkYW0= 70218 -CWNhbnZhcw== 70219 -IHBhcnRuZXJpbmc= 70220 -IGd5bW4= 70221 -IG1hbmlj 70222 -RGlmZmVyZW50 70223 -IMOlcmh1cw== 70224 -IGZlcnRpbGU= 70225 -Y2xm 70226 -LQ0K 70227 -LnJldmlldw== 70228 -b2RhYmxl 70229 -IEJvdW5kcw== 70230 -b2Jhbw== 70231 -IFBhcGVyYmFjaw== 70232 -IG1vZGlmaWM= 70233 -Y2hlY2twb2ludA== 70234 -IEFwcEJ1bmRsZQ== 70235 -IHN0YWJpbGl6ZQ== 70236 -IEF1ZGlvQ2xpcA== 70237 -bW9udGhseQ== 70238 -LmJlaA== 70239 -IGZsb3I= 70240 -IGJvbmRlZA== 70241 -IFdvcmtvdXQ= 70242 -Y29taW5ncw== 70243 -IHJhYmJpdHM= 70244 -IEJBTA== 70245 -Q0NS 70246 -X3Z1ZQ== 70247 -IExldml0cmE= 70248 -IGxpYmVydGluZQ== 70249 -IGNoYWxsZW5nZXI= 70250 -IFZhY2F0aW9u 70251 -VG9G 70252 -fSQv 70253 -X0RyYXc= 70254 -IGZlbmNlcw== 70255 -IGRhdGFzb3VyY2U= 70256 -IHBhcGVs 70257 -c2xpY2s= 70258 -X21lcw== 70259 -IFVJU3Rvcnlib2FyZFNlZ3Vl 70260 -KFRhZw== 70261 -IOWvuQ== 70262 -ICctJyk= 70263 -X0NMQVNTRVM= 70264 -KFJlbmRlcg== 70265 -CWZ3cml0ZQ== 70266 -VUVE 70267 -QUVT 70268 -KGpzb25QYXRo 70269 -IHNsb3dz 70270 -PkRlc2NyaXB0aW9u 70271 -IGVucmljaG1lbnQ= 70272 -IGl0ZW1wcm9w 70273 -IFBvdmVydHk= 70274 -IGFic29yYmluZw== 70275 -IFBzeWNobw== 70276 -5rGf 70277 -LC4KCg== 70278 -SW52ZXJzZQ== 70279 -IGFkanVk 70280 -aWdpZEJvZHk= 70281 -emlvbmk= 70282 -ICInLiQ= 70283 -5LiN5a2Y5Zyo 70284 -VGhhaQ== 70285 -IHNsYWlu 70286 -IGJydXRhbGx5 70287 -IFBlcnNwZWN0aXZl 70288 -IFJldGlyZW1lbnQ= 70289 -JHJz 70290 -IHNlcnZpY2VOYW1l 70291 -IOyI 70292 -LXByb2Nlc3Npbmc= 70293 -YnJhbmRz 70294 -OmVycm9y 70295 -KHByb3BlcnR5TmFtZQ== 70296 -IEJvZWg= 70297 -L2Nt 70298 -L3JlYWQ= 70299 -QU1C 70300 -IHJvdGF0aW9ucw== 70301 -LndvcmtzcGFjZQ== 70302 -Onk= 70303 -IHVwaG9s 70304 -dW5reQ== 70305 -IEJyYWNl 70306 -L21ldGE= 70307 -IEJyYXZl 70308 -YWNqZQ== 70309 -KFVJbnQ= 70310 -IHZpZWlsbGU= 70311 -cmFkaQ== 70312 -X2R5bg== 70313 -Tlc= 70314 -bG9zZXI= 70315 -ZXJ1c2Zvcm0= 70316 -IEJhcnRvbg== 70317 -IGZhcmVz 70318 -IE11aw== 70319 -4buHdQ== 70320 -IEF1ZGlvU291cmNl 70321 -KChf 70322 -LkJpZw== 70323 -Lm9yZ2FuaXphdGlvbg== 70324 -IFRyaWNr 70325 -IGJsdXNo 70326 -KFRZUEU= 70327 -IFJlbGF0aXZlTGF5b3V0 70328 -bGVjdHJvbg== 70329 -XX0i 70330 -IFphcA== 70331 -IFR3ZWx2ZQ== 70332 -Okw= 70333 -IHN0aWZmbmVzcw== 70334 -X0hFTA== 70335 -IHNwZXA= 70336 -KGNvZGVy 70337 -IHRhbWFuaG8= 70338 -IGFudGlveGlkYW50 70339 -IGhvc3BpdGFsaXplZA== 70340 -R1BD 70341 -IHNjcnV0aW4= 70342 -4buBbg== 70343 -IFNa 70344 -IEp1bGl1cw== 70345 -IFNhYmI= 70346 -ZWxvcg== 70347 -KG1j 70348 -6YeM 70349 -IFBpbnM= 70350 -IG1vZGVyYXRlbHk= 70351 -IEvDvA== 70352 -b3JnYW5pemF0aW9ucw== 70353 -IFNDT1JF 70354 -IHNjb3Vy 70355 -IGNob3I= 70356 -IFVJRWRnZUluc2V0cw== 70357 -IHNrdWxsZQ== 70358 -X29wZXJhbmQ= 70359 -LmdzdGF0aWM= 70360 -L25naW54 70361 -IGdldFdpZHRo 70362 -QmF0dGVyeQ== 70363 -IFNldHRlcg== 70364 -bUE= 70365 -KFJlc291cmNlcw== 70366 -X3BsYXlsaXN0 70367 -IG1hbmdv 70368 -IE9SRA== 70369 -YW5raW5k 70370 -ZXdheXM= 70371 -Pyks 70372 -IEdMVVQ= 70373 -IGp1c3Rl 70374 -IHBheWVy 70375 -KGNhbQ== 70376 -IFRlYWNo 70377 -IEZsdXg= 70378 -IG91dHNwb2tlbg== 70379 -IFN0cmluZ1V0aWw= 70380 -IFpoYW8= 70381 -LkhlbHBlcg== 70382 -IGVzdGlsbw== 70383 -IEFudGhyb3A= 70384 -IEd1YXJkcw== 70385 -Vm9jw6o= 70386 -Olsn 70387 -CXByb2R1Y3Q= 70388 -dXBkYXRlZEF0 70389 -IGluc3BpcmVz 70390 -cXc= 70391 -QkxFTQ== 70392 -YWtpc3Rhbg== 70393 -IGN6xJk= 70394 -LWhlYXJ0ZWQ= 70395 -IENvbXBlbnNhdGlvbg== 70396 -0LjQsw== 70397 -IGNvbWE= 70398 -IEZpYXQ= 70399 -IHhtbGh0dHA= 70400 -IHJlZmVycmFscw== 70401 -IHNwZWN0YXRvcnM= 70402 -IFRvcw== 70403 -aXNvcw== 70404 -SU1QTEVNRU5U 70405 -IGVudHJlcHJlbmV1cmlhbA== 70406 -IFNjb3V0cw== 70407 -IEFsb25l 70408 -YnJva2Vy 70409 -UHJvZHVjdElk 70410 -IEtvYmU= 70411 -IGNoYXVk 70412 -L2ZlYXR1cmVz 70413 -IHJvb21tYXRl 70414 -IFByb2plY3Rpb24= 70415 -YXZvdXJpdGVz 70416 -X0pPSU4= 70417 -IEFWQw== 70418 -X3BoeXM= 70419 -S2V5UHJlc3NlZA== 70420 -LDw= 70421 -IHVucmVhY2hhYmxl 70422 -IENpdGF0aW9u 70423 -W2NoYW5uZWw= 70424 -c3RhcnRzd2l0aA== 70425 -IEphZ3VhcnM= 70426 -LklzRmFsc2U= 70427 -bWVtYmVyc2hpcA== 70428 -QXR0ZW50aW9u 70429 -IHJlbW9kZWxpbmc= 70430 -IENpbmR5 70431 -IGNsaW5pY2FsbHk= 70432 -IG1pbGxlbm5pYWxz 70433 -IM60 70434 -IHJmbA== 70435 -ZW5ldA== 70436 -IG9icmln 70437 -IHZvbHVudGVlcmluZw== 70438 -Q3JlZGl0cw== 70439 -CWFy 70440 -IHJlc2lzdGluZw== 70441 -IFByb2R1a3Q= 70442 -PT09Ig== 70443 -IGNvbmVjdA== 70444 -IHJpag== 70445 -INeU 70446 -IHB1YmxpY0tleQ== 70447 -IG95 70448 -IEJ1dHQ= 70449 -X21pc2M= 70450 -IEJlc3Rl 70451 -IFBMQw== 70452 -IOafpQ== 70453 -IEJveEZpdA== 70454 -IiIu 70455 -VGVzdEZpeHR1cmU= 70456 -IGNoYXR0ZXI= 70457 -IGRvb3J3YXk= 70458 -eXNpemU= 70459 -INGH0YI= 70460 -SUNUVVJF 70461 -PScuLi8= 70462 -c2hvd24= 70463 -X3dlYXRoZXI= 70464 -IExvZ01hbmFnZXI= 70465 -XX0iCg== 70466 -IGNvbG91cmZ1bA== 70467 -IHJ1bW9yZWQ= 70468 -IGzDpQ== 70469 -IHByb2Jz 70470 -CWJ1aWxk 70471 -IOWmgg== 70472 -LnJldg== 70473 -IGludGVyY2VwdGVk 70474 -R2F5 70475 -TGlzdENvbXBvbmVudA== 70476 -IHBpw6g= 70477 -IkF0 70478 -IGFnYXI= 70479 -IEd1bmQ= 70480 -X0FFUw== 70481 -7IM= 70482 -jpjsnbQ= 70483 -IGF1dGhvcmlzZWQ= 70484 -IENoYWxs 70485 -X2xvZ291dA== 70486 -Y3Jvbg== 70487 -YXRlZ2llcw== 70488 -cGVyc2lzdGVudA== 70489 -IEFuZEFsc28= 70490 -dXN6 70491 -X3Jlc3RhcnQ= 70492 -IGRlY2lk 70493 -emY= 70494 -IHBhZ2luYXRvcg== 70495 -b2xsZXI= 70496 -IEhH 70497 -T3BhcXVl 70498 -c2VhdQ== 70499 -IE9NSVQ= 70500 -IFRoaWNrbmVzcw== 70501 -IEFpcndheXM= 70502 -X2RlbQ== 70503 -eXRpYw== 70504 -IHByb3Rlc3RlZA== 70505 -IHVwcmlzaW5n 70506 -IHN1aW5n 70507 -IFNoZWxieQ== 70508 -LmVuZXJneQ== 70509 -IGFsbGVsZQ== 70510 -LWJpZw== 70511 -U3RyaW5nQnVpbGRlcg== 70512 -IHNpZGVsaW5lcw== 70513 -IFRV 70514 -X2Fp 70515 -LkhPUklaT05UQUw= 70516 -IHJhZ2luZw== 70517 -LnRvTG9jYWxl 70518 -Lm11c3Q= 70519 -eEZGRg== 70520 -Lm5paA== 70521 -ICd7fSc= 70522 -2YjYrw== 70523 -IHB1bG1vbmFyeQ== 70524 -IOWPkQ== 70525 -IG7Dum1lcm9z 70526 -IE5hcG9sZW9u 70527 -X01ldGhvZEluZm8= 70528 -bGFzdGluZw== 70529 -IGV4cG9zdXJlcw== 70530 -IGVtYmFyaw== 70531 -X3VkcA== 70532 -S2lkcw== 70533 -X0NPTk5FQ1RFRA== 70534 -IHdlZWRz 70535 -UE9PTA== 70536 -IGtyaWo= 70537 -IG51aXM= 70538 -Sk5JRVhQT1JU 70539 -YWFhYWFhYWE= 70540 -IO2P 70541 -5Lu9 70542 -IHJlcGxlbg== 70543 -IFRyaWFscw== 70544 -d2FzaA== 70545 -cnV0 70546 -LWJlZm9yZQ== 70547 -X0FUVEFDSE1FTlQ= 70548 -VU5U 70549 -XFZhbGlkYXRpb24= 70550 -VG9u 70551 -IGhlYWRpbmdz 70552 -UHJvYmFibHk= 70553 -IGZhYnJpY2F0ZWQ= 70554 -U29ja2V0QWRkcmVzcw== 70555 -IGxldHRyZQ== 70556 -KSI+ 70557 -IHZhY2NpbmF0ZWQ= 70558 -Omh0dHA= 70559 -IGNvbmRvbA== 70560 -c2hlZA== 70561 -IFNwaWVsZQ== 70562 -44OU 70563 -RGVwbG95 70564 -LkNvbnRyYWN0 70565 -LWJv 70566 -Iy8= 70567 -IGludGVyY2VwdGlvbg== 70568 -IGlzYm4= 70569 -IG1hbm5lcnM= 70570 -L2Fj 70571 -CUNoZWNr 70572 -X2Zn 70573 -IGVuZFBvaW50 70574 -X3dlYXBvbg== 70575 -IHVuaW50ZW50aW9u 70576 -IHF1aXRz 70577 -X01JQw== 70578 -YXBpcm8= 70579 -IGJhbGxvb25z 70580 -IGdyYWRz 70581 -bWFycmllZA== 70582 -IDwqPg== 70583 -IGRpc3RvcnQ= 70584 -X01FU1NBR0VT 70585 -IFBTQQ== 70586 -X1BE 70587 -YWxzZXg= 70588 -IERpYWxvZ3Vl 70589 -IHJlZ2lzdHJhdGlvbnM= 70590 -IE9yaWdpbnM= 70591 -IGZsYW5r 70592 -PzsKCg== 70593 -OwoKCgoK 70594 -XS0k 70595 -IERlc3M= 70596 -LlN0YXR1c0JhZFJlcXVlc3Q= 70597 -IGluaGFiaXRlZA== 70598 -IGdpbHQ= 70599 -IFNURENBTEw= 70600 -LnRoZXRh 70601 -JCQkJA== 70602 -aWNsYXNz 70603 -QXBhcnQ= 70604 -Lmxpc3RCb3g= 70605 -IEJlbGFydXM= 70606 -IGRlbmVu 70607 -IFN1c3NleA== 70608 -CWRlbA== 70609 -X0VD 70610 -bmVhcmVzdA== 70611 -XE9yZGVy 70612 -UGFja2FnZXM= 70613 -Zm9ybWVybHk= 70614 -Ke+8jA== 70615 -6LSj 70616 -U2V4eQ== 70617 -IGhvcnJvcnM= 70618 -Uk9BRENBU1Q= 70619 -QXBwcm94 70620 -RGVzaw== 70621 -QU1FRA== 70622 -Lk5vcm1hbGl6ZQ== 70623 -X3B1Ymxpc2hlZA== 70624 -IERlYm9yYWg= 70625 -56eR 70626 -IHBvdW5kaW5n 70627 -IEVzcGVy 70628 -IERhbmNpbmc= 70629 -IExPT1A= 70630 -IFJveWFscw== 70631 -IGluc3VyZQ== 70632 -IEludmVzdG9ycw== 70633 -IHRoZW9sb2dpY2Fs 70634 -QXBwb2ludG1lbnQ= 70635 -IGNhdGVnb3JpY2Fs 70636 -IGNyYW4= 70637 -VmFsaWRpdHk= 70638 -IHJlc3BvbmRlcnM= 70639 -ICgpDQo= 70640 -ZXBhZA== 70641 -QklUUw== 70642 -IExhbWJlcnQ= 70643 -c3VtbQ== 70644 -YWNpZGFk 70645 -IGxvZ2dlZElu 70646 -PVc= 70647 -LkxvY2FsaXphdGlvbg== 70648 -cmlkbw== 70649 -JyIpCg== 70650 -IFdlYlZpZXc= 70651 -bG90aA== 70652 -IHRlYXNlcg== 70653 -IENhbmQ= 70654 -IGVwaWxlcHN5 70655 -SW5jcmVhc2U= 70656 -aXZpdHlNYW5hZ2Vy 70657 -ZW50cmFudA== 70658 -VGVsZWZvbm8= 70659 -LmN1cnJlbnRTdGF0ZQ== 70660 -IE5vZWw= 70661 -ICAgICAgICAgICAgCQk= 70662 -IGV4aGF1c3Rpb24= 70663 -ZWxpYW4= 70664 -IGNvdmV0ZWQ= 70665 -LXByb2R1Y3Rpb24= 70666 -KHN0ZGlu 70667 -IHByZWZlcmFibGU= 70668 -IG9mZmVuZGluZw== 70669 -KGNvbW1pdA== 70670 -CWFs 70671 -IHJlbG9jYXRl 70672 -IGFub21hbA== 70673 -IERpc2Vhc2Vz 70674 -IEZvcmc= 70675 -IFdJRkk= 70676 -IEtpbGxpbmc= 70677 -cXY= 70678 -IGZtYXA= 70679 -IGxsZXZhcg== 70680 -dGl0cmU= 70681 -LmVtcA== 70682 -LCRf 70683 -YXZy 70684 -Q2FuQmU= 70685 -X21h 70686 -IEhhd2tpbnM= 70687 -X1JPVVQ= 70688 -IGxvYWRJbWFnZQ== 70689 -IFdhaA== 70690 -IERlbXM= 70691 -IGluZGVudGF0aW9u 70692 -cHJlY2F0aW9u 70693 -IOaWh+S7tg== 70694 -IEJ1ZGFwZXN0 70695 -IHV0Yw== 70696 -KGhvdXJz 70697 -IHRyYW5ueQ== 70698 -QW5z 70699 -ennEhw== 70700 -LnZlaGljbGU= 70701 -Q29pbnM= 70702 -IEJyYXVu 70703 -CVJlc3BvbnNl 70704 -IHZyaWo= 70705 -IHN0cmFuZ2VseQ== 70706 -IEZhc2M= 70707 -XFNlc3Npb24= 70708 -TW91c2VMaXN0ZW5lcg== 70709 -IFJvbGxz 70710 -4bqnbg== 70711 -LmdycGM= 70712 -SW50ZWdlckZpZWxk 70713 -CWFmeA== 70714 -RG9ja0NvbnRyb2w= 70715 -JVw= 70716 -JTsi 70717 -IGdpZ2c= 70718 -IGJvcnJvd2Vy 70719 -IGRpc3BvbmlibGVz 70720 -X1JFQ1Q= 70721 -IFRoaW4= 70722 -IHBlYXJs 70723 -eEZC 70724 -IHJpcHBsZQ== 70725 -IGtIeg== 70726 -LmFjcXVpcmU= 70727 -Ymlvcw== 70728 -dGFibGVGdXR1cmU= 70729 -L2FudGxy 70730 -b3JhY2xl 70731 -IEFSRUE= 70732 -IGludGVuc2VseQ== 70733 -IHByb3RvYnVm 70734 -IExFTkc= 70735 -IEhlYWRxdWFydGVycw== 70736 -YXRoZWQ= 70737 -TWluZA== 70738 -aW5peg== 70739 -CVBhdGg= 70740 -WE1MTG9hZGVy 70741 -IGFsbG9jYXRpb25z 70742 -LnNsb3Q= 70743 -UHJvY0FkZHJlc3M= 70744 -IHJvbGVJZA== 70745 -Oyc7Cg== 70746 -IEJSRUFL 70747 -IFBlcmZvcm1pbmc= 70748 -Lk9yZGluYWxJZ25vcmVDYXNl 70749 -LWds 70750 -Omg= 70751 -IGRvd25sb2FkYWJsZQ== 70752 -IFN1YnNjcmliZXI= 70753 -YW5zZQ== 70754 -IGNoYXJhY3Rlcml6ZQ== 70755 -IHNocnVnZ2Vk 70756 -IHNjcA== 70757 -IGd1c3Rh 70758 -IG1ldGFsbA== 70759 -IGxhYm9yYXRvcmllcw== 70760 -IFhpbg== 70761 -IE1vdG9yY3ljbGU= 70762 -IGVnZXQ= 70763 -IGZpbmFuY2Vk 70764 -IE1PRElGWQ== 70765 -KlI= 70766 -QWk= 70767 -IGV4dHJlbWlzbQ== 70768 -IEhhbGlmYXg= 70769 -IHZhbW9z 70770 -JG51bQ== 70771 -IGltcGFydA== 70772 -YnJpY2s= 70773 -IOexuw== 70774 -IGZ1ZXJh 70775 -IFJPTEU= 70776 -LkNvbmN1cnJlbnQ= 70777 -X09QRVJBVE9S 70778 -IGN5bmljYWw= 70779 -IFJlZ2luYQ== 70780 -Z2V0RXJyb3I= 70781 -2KM= 70782 -YnN1Yg== 70783 -SmFwZ29sbHk= 70784 -IGluaGliaXRvcg== 70785 -SnVzdGljZQ== 70786 -44U= 70787 -TmV2ZXJ0aGVsZXNz 70788 -LXNlbQ== 70789 -Lm9nZw== 70790 -cmVxdWVudA== 70791 -IG5vc3Nv 70792 -SGFpcg== 70793 -LkxpYnJhcnk= 70794 -bWRpcg== 70795 -IGhhcmk= 70796 -IFRhcmE= 70797 -IFBvcnRv 70798 -bmV0aW5ldA== 70799 -IGFsbGlhbmNlcw== 70800 -ZWxsc2NoYWZ0 70801 -X1N1cmZhY2U= 70802 -CVZpZXc= 70803 -YXR1cmRheXM= 70804 -IHBvcGNvcm4= 70805 -X1BBUlNF 70806 -IFJpcHBsZQ== 70807 -IHBoYW50b20= 70808 -IG1vbmRv 70809 -LmNyZWF0ZUNsYXNz 70810 -IEtvcmVhbnM= 70811 -IGZhc2U= 70812 -IFdvY2hlbg== 70813 -IEVxdWlw 70814 -LWVpZ2h0 70815 -IFN0YXRlbWVudHM= 70816 -IGFkYXB0aW5n 70817 -UHJlY2lv 70818 -IEN1cmU= 70819 -IGNhbWJpYXI= 70820 -5rCR 70821 -IGhleGFkZWNpbWFs 70822 -c3BpcmFjeQ== 70823 -YmlsdA== 70824 -IFl1Zw== 70825 -IC0tLT4= 70826 -IFBQQw== 70827 -aXN6 70828 -YWtlRnJvbU5pYg== 70829 -IERpc3A= 70830 -IEF0aGxldGljcw== 70831 -IG5pZ2h0Y2x1Yg== 70832 -R09PRA== 70833 -LnNldEdlb21ldHJ5 70834 -K1s= 70835 -L3NlbmQ= 70836 -IGJpbmFyaWVz 70837 -IHLDoXA= 70838 -OnJlcQ== 70839 -LWNvbnN1bWluZw== 70840 -ZXJ0aW1l 70841 -VVBEQVRFRA== 70842 -X251bGxhYmxl 70843 -VklO 70844 -dWxpYQ== 70845 -Y3lhbg== 70846 -IG1pc3VuZGVyc3RhbmRpbmc= 70847 -b3JpY2Fs 70848 -ZGVncmVlcw== 70849 -TGVhZGluZw== 70850 -LkFS 70851 -aWNrZXN0 70852 -TnVldm8= 70853 -dWZvcmlh 70854 -IGdvb2RpZXM= 70855 -IGZvcmVz 70856 -KCk8PCI= 70857 -YWRlbWlj 70858 -QWN0aW9uQ3JlYXRvcnM= 70859 -c2VydmVybmFtZQ== 70860 -KG50 70861 -ZGJDb250ZXh0 70862 -IGFpcmJvcm5l 70863 -IGV4aGliaXRpb25z 70864 -Y2VsZQ== 70865 -IHRlbGE= 70866 -PE1vdmll 70867 -KCd7fQ== 70868 -RXhwbGFuYXRpb24= 70869 -IGhPYmplY3Q= 70870 -IGJlYXJlcg== 70871 -ZW5zaWJseQ== 70872 -bmlw 70873 -IEplcm9tZQ== 70874 -IENa 70875 -IGRhdGVGb3JtYXR0ZXI= 70876 -w6ljaWFs 70877 -U2V0TmFtZQ== 70878 -b3VjZQ== 70879 -IHJlZ3Jlc3M= 70880 -JkM= 70881 -KCkiPg== 70882 -LnNldFByZWZlcnJlZFNpemU= 70883 -IE1JRA== 70884 -IEFsZXNz 70885 -IGhvcnNlcG93ZXI= 70886 -IGF0bQ== 70887 -IFBhY2thZ2luZw== 70888 -IGNpcGhlcnRleHQ= 70889 -UmVxdWVzdE1ldGhvZA== 70890 -IGJlaWRlbg== 70891 -6KM= 70892 -IFBPVw== 70893 -LldyaXRlSGVhZGVy 70894 -ZGlyZWN0b3I= 70895 -LWJ1dA== 70896 -44Gg44GV44GE 70897 -aW5jZXI= 70898 -X2Ru 70899 -ISEhISE= 70900 -IG1hbnVmYWN0dXJlcw== 70901 -LlRleHRVdGlscw== 70902 -IGNvbnNjaW91c2x5 70903 -IGJvdW5jZWQ= 70904 -Y3VsdHVyZQ== 70905 -IFNwYXI= 70906 -IFBpcGVy 70907 -LnByZXNz 70908 -LW93bmVy 70909 -IGV2YWx1YXRvcg== 70910 -IFNUUkVBTQ== 70911 -LlBpY3R1cmVCb3hTaXplTW9kZQ== 70912 -IHN1Z2Fycw== 70913 -U2NyZWVuV2lkdGg= 70914 -IG5leHRTdGF0ZQ== 70915 -IGl2b3J5 70916 -IGJydW5jaA== 70917 -ZGVuc2l0eQ== 70918 -X09X 70919 -IENvcm9uYXZpcnVz 70920 -IENGUg== 70921 -YmFr 70922 -XENhdGVnb3J5 70923 -5pWw57uE 70924 -IGludm9rZXZpcnR1YWw= 70925 -fSgpCg== 70926 -IHN1amV0 70927 -LW1hcmtlcg== 70928 -aXNkaWdpdA== 70929 -IE1vYmls 70930 -IEpzb25SZXF1ZXN0QmVoYXZpb3I= 70931 -X1JFTU9URQ== 70932 -LmV4aXN0c1N5bmM= 70933 -IHJpY2hlcw== 70934 -LnByZXNlbnRlcg== 70935 -IGdsQ29sb3I= 70936 -IGhhbnlh 70937 -IGZvcnRyZXNz 70938 -IGZsYXNoZWQ= 70939 -dml6 70940 -cmVxdWVudGx5 70941 -YnVhdA== 70942 -JGNvbg== 70943 -Pnw= 70944 -LkZ1bmM= 70945 -IGh1bW9yb3Vz 70946 -dWVt 70947 -LlpFUk8= 70948 -IFNUTA== 70949 -IEJ1aw== 70950 -L3NhbXBsZQ== 70951 -IEdyb3M= 70952 -UmVjaXBlcw== 70953 -IGluZmxhdGVk 70954 -IHN3dW5n 70955 -OkY= 70956 -RmFjaW5n 70957 -LlRoZW1l 70958 -0L3QuNC6 70959 -IHNwbGVuZGlk 70960 -IHJlcXVlc3RJZA== 70961 -LkNlbnRlclNjcmVlbg== 70962 -L2F1dG9sb2Fk 70963 -ZW1iZWRkZWQ= 70964 -X2RlcGFydA== 70965 -IFBvcnRz 70966 -4LmD 70967 -0LDQudC0 70968 -ZGlzY3Vzc2lvbg== 70969 -X2NvbnN1bQ== 70970 -IHNjb3V0cw== 70971 -IGNvbGFib3I= 70972 -LlN0YWdl 70973 -Lm5hbm8= 70974 -ZWxkb3Jm 70975 -IGdlbWFjaHQ= 70976 -ICAgICAgICAgICAgICAgICAgICAgICAgICAK 70977 -IHBvbGljeW1ha2Vycw== 70978 -X1BLVA== 70979 -LFRo 70980 -b2t5 70981 -X1VJRA== 70982 -UGluZw== 70983 -IG9yY2hlc3Q= 70984 -IG9wdGljcw== 70985 -dWhhbg== 70986 -IFhPUg== 70987 -IGVzcGHDsW9s 70988 -IEFkaWRhcw== 70989 -cm5n 70990 -bWFucw== 70991 -LnZzdGFjaw== 70992 -IGdldGF3YXk= 70993 -IGhpZXJhcmNoaWNhbA== 70994 -YW5vaWE= 70995 -IEJpdG1hcEZhY3Rvcnk= 70996 -cmVhbG0= 70997 -CWFw 70998 -X2FwcHM= 70999 -LWRpdmlkZXI= 71000 -LmRyYXdlcg== 71001 -IEhBUkQ= 71002 -J107Pz4K 71003 -LXBhY2tlZA== 71004 -5rK7 71005 -X1NUUlVDVFVSRQ== 71006 -W1k= 71007 -aVBhcmFt 71008 -KGVx 71009 -IGVuY29tcGFzc2Vz 71010 -IFwKCg== 71011 -LT5b 71012 -JnV0bQ== 71013 -Z3JvdXBvbg== 71014 -c3RyYXRl 71015 -RFk= 71016 -b21vcnBoaWM= 71017 -Jzpb 71018 -IGdyYXZpdGF0aW9uYWw= 71019 -IE1pY2hh 71020 -IFRlbmNlbnQ= 71021 -IGNvYWNoZWQ= 71022 -7Lac 71023 -0YPQvNC10L3Rgg== 71024 -L21vYmlsZQ== 71025 -TW91c2VEb3du 71026 -YnVk 71027 -IFlhcw== 71028 -IFByb3ZpZGVycw== 71029 -Tlo= 71030 -CXJlcG9ydA== 71031 -ZXJybXNn 71032 -IGltYWdlUGF0aA== 71033 -YWN0ZXJpYWw= 71034 -IE1hbmdh 71035 -d2lja2x1bmc= 71036 -KHVzdWFyaW8= 71037 -IikpOw0KDQo= 71038 -LyoqKg== 71039 -IG9yZ2FuaXNl 71040 -SW5kZXhlZA== 71041 -X1FVQUw= 71042 -KFB5T2JqZWN0 71043 -IHN1cnJlbmRlcmVk 71044 -UE9DSA== 71045 -IE5PVEVT 71046 -XFwi 71047 -LWpvYg== 71048 -IHNldmVudHk= 71049 -IyMjIwo= 71050 -IE1hbm9y 71051 -IGRvd25yaWdodA== 71052 -IHRpbWVmcmFtZQ== 71053 -aW5zdXJhbmNl 71054 -Y2hlY2tlcg== 71055 -IFNFQ1JFVA== 71056 -IGVjaG9lcw== 71057 -IENhcm1lbg== 71058 -LnNldEhvcml6b250YWxBbGlnbm1lbnQ= 71059 -IGlzQ2hlY2tlZA== 71060 -IFRPUg== 71061 -X25u 71062 -KCco 71063 -RmV0Y2hSZXF1ZXN0 71064 -IFByaW50ZWQ= 71065 -Rmx1aWQ= 71066 -IFNUQUNL 71067 -R0VT 71068 -YWlnbmVk 71069 -aWdvcg== 71070 -LlVua25vd24= 71071 -Q0JD 71072 -IENhcmxzb24= 71073 -LlVSSQ== 71074 -IHBsaWdodA== 71075 -L3N0YXJ0 71076 -IFBlcnNvbm5lbA== 71077 -IFBSRUZJWA== 71078 -LCoq 71079 -IGxpbWl0ZQ== 71080 -X2hlYXQ= 71081 -Je+8jA== 71082 -IERvbm5l 71083 -Z2V0Tm9kZQ== 71084 -IFNjaWVudG9sb2d5 71085 -IGNvbWV0 71086 -IHdlbmln 71087 -QXNpZGU= 71088 -IE1QRUc= 71089 -Jz8= 71090 -dmFyaWFibHk= 71091 -LmVuZERhdGU= 71092 -IHVuY29udA== 71093 -IFNjb3Jlcw== 71094 -IExvZ2luRm9ybQ== 71095 -LmdlbmVyYXRlZA== 71096 -LGNo 71097 -LW1hcg== 71098 -IE5lZA== 71099 -IGV2ZW50SWQ= 71100 -K3A= 71101 -IFNJTg== 71102 -L3Jlc2V0 71103 -LlJFQUNU 71104 -IE1lc3Np 71105 -X1JBTks= 71106 -LndyaXRlRmlsZQ== 71107 -IGNyaXBw 71108 -ZXN0aGV0aWM= 71109 -RVJTSVNU 71110 -IHJlaW1idXJzZW1lbnQ= 71111 -Q3VycmVudFZhbHVl 71112 -IHVuaW4= 71113 -RG93bkxhdGNo 71114 -IHBhZGRpbmdSaWdodA== 71115 -IHN0b2NrZWQ= 71116 -Lycu 71117 -IHJlcGF5bWVudA== 71118 -dHJhaw== 71119 -L2JhY2tlbmQ= 71120 -INC40LfQvNC10L0= 71121 -Q1NS 71122 -IHByZXZlbnRpdmU= 71123 -IHBhbnRhbGxh 71124 -X3RyaW0= 71125 -UGVkaWRv 71126 -aG9zcGl0YWw= 71127 -IG1hbmFnZWFibGU= 71128 -cm91dGVQYXJhbXM= 71129 -dGV4dHVyZXM= 71130 -Li4uLi4uCgo= 71131 -IHPDqWxlY3Rpb24= 71132 -TmFtZVZhbHVlUGFpcg== 71133 -IHBvbGx1dA== 71134 -TW9kZXM= 71135 -IExhdWQ= 71136 -amF5 71137 -IFVycw== 71138 -IHNpZ25lcg== 71139 -IEpK 71140 -IENoZXJva2Vl 71141 -X0VYSVNUUw== 71142 -IGR3YXI= 71143 -ICgkKCcj 71144 -IHJlZWY= 71145 -Pnsk 71146 -IEJheWxvcg== 71147 -IE1vZGVsU3RhdGU= 71148 -LV8= 71149 -IFN0cnVjdHVyZXM= 71150 -IHNvdXZlbnQ= 71151 -U3BlY2lmeQ== 71152 -KHBpcGU= 71153 -IGZyYWNraW5n 71154 -IEdQQQ== 71155 -IGJlbGU= 71156 -CQkJCQkJCSAgIA== 71157 -IE1pbm9yaXR5 71158 -IHR1ZA== 71159 -IG9wZW5uZXNz 71160 -IElsbHVzdHJhdGVk 71161 -IG94aWRhdGlvbg== 71162 -IE5L 71163 -CVVwZGF0ZQ== 71164 -IEVNUw== 71165 -IFRlZGR5 71166 -IGdlbmVyYWxz 71167 -CU1hdA== 71168 -IHJhZGlvcw== 71169 -IEFudGlxdWU= 71170 -Y29ub215 71171 -IFNxdWFkcm9u 71172 -KScsJw== 71173 -5aOw 71174 -IHlvdXJl 71175 -IE1haW5QYWdl 71176 -IGJlaGF2aW91cnM= 71177 -ZW5naHQ= 71178 -KEAiJUAiLA== 71179 -IHRlc3RjYXNl 71180 -IENvbXBpbGF0aW9u 71181 -IGZsYXZvdXJz 71182 -IEV4dGVuZA== 71183 -aWxsYXRvcg== 71184 -IGNvaA== 71185 -IHNwbGluZQ== 71186 -IEtH 71187 -LXBheQ== 71188 -IGNvbW11bmlzbQ== 71189 -IEJ1c2luZXNzZXM= 71190 -b2NraW5n 71191 -Lk1heExlbmd0aA== 71192 -YXNzYW5kcmE= 71193 -cXVpcmluZw== 71194 -YWRkZW4= 71195 -IEplYg== 71196 -X2ZhdWx0 71197 -W2ZpbGU= 71198 -IHByb21pbmVuY2U= 71199 -ZGlzY2lwbGluYXJ5 71200 -4oCUdGhleQ== 71201 -X2V4dGVudA== 71202 -IFZJQw== 71203 -IGVudGFpbHM= 71204 -LnBhcnRuZXI= 71205 -IGhpcHBvYw== 71206 -TGVhZ3Vl 71207 -55S3 71208 -d2lwZQ== 71209 -LXNwaW5uZXI= 71210 -IHNhbHV0ZQ== 71211 -IFN1cmdpY2Fs 71212 -KG91dHB1dHM= 71213 -d29ya2Vk 71214 -W3N0cmxlbg== 71215 -YXBwb2ludGVk 71216 -IEhlZw== 71217 -IEFDUEk= 71218 -KFte 71219 -dWFsYQ== 71220 -X3RvbA== 71221 -IFJpdA== 71222 -LlBheW1lbnQ= 71223 -a293c2tp 71224 -IHdhbG1hcnQ= 71225 -cmVxdWlyZW1lbnRz 71226 -IEZJTlNFUQ== 71227 -X0JBQ0tHUk9VTkQ= 71228 -IE9zYm9ybmU= 71229 -KGVycm9yTWVzc2FnZQ== 71230 -UmVwb3J0aW5n 71231 -IGF1Y3Rpb25z 71232 -IGNvbWJvcw== 71233 -IE5vdGljZWQ= 71234 -X29jdA== 71235 -IHByaW1lcm8= 71236 -dGFpcmU= 71237 -X2hy 71238 -INC80L7QtA== 71239 -IGNvbnRyYWRpY3Rvcnk= 71240 -PSJA 71241 -YWNoaW5lcw== 71242 -KG9wdGFyZw== 71243 -IFBlbmd1aW4= 71244 -IEFiYmFz 71245 -IHN1YmxpbWU= 71246 -IHBhZ2VhYmxl 71247 -IERlZmVuc2l2ZQ== 71248 -IGRpc3RpbmN0bHk= 71249 -IEF1dG9tYXRpY2FsbHk= 71250 -VW5kZXJzdGFuZGluZw== 71251 -RXF1YWxpdHlDb21wYXJlcg== 71252 -Z290YQ== 71253 -ICI6Og== 71254 -IHB1bHZlcg== 71255 -IEJhdHRsZXM= 71256 -IHVucGFyYWxsZWxlZA== 71257 -VENIQQ== 71258 -IGNvbnN0cnVlZA== 71259 -LWFmZg== 71260 -IHByZWN1cnNvcg== 71261 -LWxmcw== 71262 -IG1hZHVyYXM= 71263 -IERhaXN5 71264 -IEFyYmVpdHM= 71265 -Lk1hbmFnZW1lbnQ= 71266 -CUlu 71267 -IHJvYmVz 71268 -IHNww6lj 71269 -4oCcKA== 71270 -IG1hdGVybml0eQ== 71271 -ZXh0ZW50 71272 -IFNwYWNlcg== 71273 -RGlkQXBwZWFy 71274 -CXVz 71275 -LmdldFJlcXVlc3REaXNwYXRjaGVy 71276 -KGNvbHM= 71277 -IHBsdW1tZXQ= 71278 -7IU= 71279 -IHsKCgoK 71280 -w6lyaWNh 71281 -IFNpemVz 71282 -LmVudW0= 71283 -LkhpZ2hsaWdodA== 71284 -ICEhfTwv 71285 -QVRURVJZ 71286 -IFNvcm9z 71287 -R0xmbG9hdA== 71288 -44KE 71289 -IEplbm5pbmdz 71290 -Pz8KCg== 71291 -IFJvbWVv 71292 -ID8+CgoK 71293 -V2Vubg== 71294 -IGNsaW1heA== 71295 -IGNyZW0= 71296 -X3RoYXQ= 71297 -W+KApg== 71298 -X2RvbWFpbnM= 71299 -X1JFUExZ 71300 -IGNvbXBsZXRh 71301 -VkVTVA== 71302 -X3BhcnRpY2xl 71303 -IHNvcA== 71304 -IGZhdGFsaXRpZXM= 71305 -aW1wbGlmeQ== 71306 -IFNLRg== 71307 -IGluZnVzaW9u 71308 -IEphdmllcg== 71309 -IGJhbGxldA== 71310 -IGFtaWdv 71311 -LndhbnQ= 71312 -IGNvbGxhZ2Vu 71313 -IExhd3llcg== 71314 -LlN0YXRlbWVudA== 71315 -LnJ0 71316 -YmFhcg== 71317 -RW5kUG9pbnQ= 71318 -IEJlaw== 71319 -U0hJUA== 71320 -IHBhdHJpYXJjaA== 71321 -IEF1bnQ= 71322 -X1RN 71323 -IG3DrW4= 71324 -IG1hc3RlcmVk 71325 -V1hZWg== 71326 -IGVzcG9z 71327 -PWxvZ2dpbmc= 71328 -IHJpZ2h0ZW91c25lc3M= 71329 -dG9ycmVudA== 71330 -IGJzdA== 71331 -X0NIQUlO 71332 -IG91dHNraXJ0cw== 71333 -KHJvdGF0aW9u 71334 -ICcuJyk= 71335 -aWdyYW50cw== 71336 -K2xzaQ== 71337 -IENDVFY= 71338 -X1BIQVNF 71339 -LmF6dXJl 71340 -X1Byb2Nlc3M= 71341 -dmFl 71342 -IFRyb3BpY2Fs 71343 -IEFua2FyYQ== 71344 -aW1hZ2VWaWV3 71345 -X1JVTk5JTkc= 71346 -ICopX18= 71347 -4bq/bg== 71348 -KGNsaQ== 71349 -c2NhdHRlcg== 71350 -IHNjaGU= 71351 -UmVnaXN0cmFy 71352 -IGFpcmluZw== 71353 -IHB5cGxvdA== 71354 -aXNpw7Nu 71355 -L2N1c3RvbWVy 71356 -IHNpbXBsZW1lbnQ= 71357 -IGNsYXNzeQ== 71358 -IERXQw== 71359 -IEJhc2hhcg== 71360 -IERFVkVMTw== 71361 -IFZpY2s= 71362 -YXZhaWw= 71363 -IEjDtg== 71364 -X2V4dGVuZA== 71365 -ZHJGYw== 71366 -LmlzTm90Qmxhbms= 71367 -IHBsYWlz 71368 -fH0K 71369 -IHBvcm5vZmls 71370 -bGFicw== 71371 -IGhhdXM= 71372 -IG9yaWdpbmF0aW5n 71373 -IHN1cnJvdW5kcw== 71374 -IFFVQUw= 71375 -bWVn 71376 -L2xvZ2dlcg== 71377 -W29iag== 71378 -IGlycmVzcG9uc2libGU= 71379 -IFB1YmxpY0tleQ== 71380 -SE9ORQ== 71381 -Oicv 71382 -aWJveA== 71383 -IEZWZWN0b3I= 71384 -fHsK 71385 -YXRhbG9hZGVy 71386 -aGF3a3M= 71387 -SERS 71388 -IGVzY2FsYXRpb24= 71389 -IFBvZHNEdW1teQ== 71390 -ZWxpdGU= 71391 -IHByZXN1cA== 71392 -Q2FjaGVk 71393 -Pkc= 71394 -Lm9wdGltaXplcg== 71395 -IFZpc2libGU= 71396 -tIA= 71397 -IG5lbg== 71398 -IHBjcw== 71399 -IElkbGU= 71400 -W0FueQ== 71401 -IGtleWJvYXJkcw== 71402 -IENPTVBPTkVOVA== 71403 -IHRpdGFuaXVt 71404 -KG11dA== 71405 -IExlZGdlcg== 71406 -IHByb3NwZXJvdXM= 71407 -ZXRyb2ZpdA== 71408 -X0xM 71409 -X3BhdGllbnQ= 71410 -IHBkYXRh 71411 -IGtvbnRha3Rl 71412 -U3dpcGU= 71413 -IGNoZWVyZnVs 71414 -IEhvbmR1cmFz 71415 -Il1bJA== 71416 -IGhlbW9ycmg= 71417 -IjoiKw== 71418 -IGxlYXNpbmc= 71419 -IGluc3RhbGxz 71420 -IFBheA== 71421 -IExvZ2lzdGljcw== 71422 -IGtpbmV0aWM= 71423 -IFBob24= 71424 -X21vdmVtZW50 71425 -CWJ5dGVz 71426 -IGNpbmNv 71427 -IE1hZG5lc3M= 71428 -Iikr 71429 -IEpF 71430 -X2lq 71431 -U2NlbmVNYW5hZ2Vy 71432 -IEJ1c3Q= 71433 -cHRlc3Q= 71434 -YWVh 71435 -IGJlc3Nlcg== 71436 -w61n 71437 -0LTQuNC9 71438 -KHRhc2tz 71439 -KCIoIg== 71440 -c2V0VHlwZQ== 71441 -KG91dGZpbGU= 71442 -CXJlc2V0 71443 -IEFSQw== 71444 -IG3DunNpY2E= 71445 -IFNoZWxm 71446 -IG1pblk= 71447 -cGNo 71448 -IHdlaWJlcg== 71449 -aXNzb3I= 71450 -IHRyb3V2ZQ== 71451 -CUJ1dHRvbg== 71452 -IHJlZ2VuZXJhdGVk 71453 -xaNp 71454 -aW1hY2hpbmVyeQ== 71455 -YmxvY2tpbmc= 71456 -LmRhdGFUYWJsZXM= 71457 -X2ZyYWM= 71458 -IEFkdmFudGFnZQ== 71459 -LnZpc2l0TWV0aG9k 71460 -6YeN5paw 71461 -IGV4dHJhcG9s 71462 -IHRlYXNpbmc= 71463 -IEhpdGNo 71464 -IEdlZWs= 71465 -RVNDTw== 71466 -IHdpY2g= 71467 -CWF4 71468 -X2RlY29y 71469 -IHNjcmVlbldpZHRo 71470 -IFNvcGhpYQ== 71471 -Rm9yZ290 71472 -LnVuaQ== 71473 -IFZlbnR1cmU= 71474 -X2NvbGxpc2lvbg== 71475 -IGxhd21ha2Vy 71476 -KEVkaXQ= 71477 -YmxlcnM= 71478 -IGdldE5leHQ= 71479 -4oCUeW91 71480 -TWVkaWFQbGF5ZXI= 71481 -IEhvcmRl 71482 -IENvbmdyZXNzbWFu 71483 -b2JzZXJ2YXRpb25z 71484 -CXByb3BlcnR5 71485 -IDwtLQ== 71486 -Q3JlYXRlZEF0 71487 -dWJ5dGU= 71488 -IHF1YXJhbnRpbmU= 71489 -IGRpc3RyZXNzZWQ= 71490 -X0FQQg== 71491 -IEdvb2RtYW4= 71492 -44Kr 71493 -IHJlY29tZW5k 71494 -X1BSSU5URg== 71495 -RE9ORQ== 71496 -QmluZGFibGU= 71497 -cnN0cmlw 71498 -Y2VudGFqZQ== 71499 -IFVuZXhwZWN0ZWQ= 71500 -IFNDSE9PTA== 71501 -IFByb2Zlc3Npb25hbHM= 71502 -IEdQVXM= 71503 -TGVzc29u 71504 -RXhjbHVzaXZl 71505 -IGF0cmF2 71506 -IERhbms= 71507 -IExhd3llcnM= 71508 -IFdhbHRvbg== 71509 -Pltd 71510 -IGFsb3Vk 71511 -PSIuLi8uLi8uLi8= 71512 -IGRlYmF0aW5n 71513 -IEFWRw== 71514 -X1ZPTA== 71515 -L2NnaQ== 71516 -LmRlZw== 71517 -Omc= 71518 -LkluZm9m 71519 -TWVhc3VyZVNwZWM= 71520 -LnNvbmc= 71521 -bXRyZWU= 71522 -dWxscw== 71523 -Sm9yZGFu 71524 -IENvdmVycw== 71525 -IGF0dHJpYnV0YWJsZQ== 71526 -IGplZGlz 71527 -aWF0cmljcw== 71528 -IHJvdHRlcmRhbQ== 71529 -IG1lbGQ= 71530 -IENvbnRlbnRUeXBl 71531 -IG1hbnRsZQ== 71532 -IGFsaWNl 71533 -X2R1cGxpY2F0ZQ== 71534 -L0ludGVybmFs 71535 -IGZpbGVzaXpl 71536 -CWZpcmU= 71537 -cmVzZQ== 71538 -b25kZXJl 71539 -IGZhbWlsaWFyaXR5 71540 -IENyZXN0 71541 -IGthcm1h 71542 -IHRvcmlubw== 71543 -IG1lc2E= 71544 -L3RlbXA= 71545 -IGNoaXI= 71546 -IE92ZXJmbG93 71547 -IHRlbmVtb3M= 71548 -dW5paw== 71549 -TkVYVA== 71550 -QWxsZQ== 71551 -IG54dA== 71552 -TWFydA== 71553 -IGF0bA== 71554 -IHBlcmlvZG8= 71555 -X3lvdQ== 71556 -IH0pKS4= 71557 -aW50ZXN0aW5hbA== 71558 -LkFkYXB0ZXJWaWV3 71559 -IGhlc2l0YW50 71560 -IGNvbXBhcmF0aXZlbHk= 71561 -LlVJbnQ= 71562 -KHZpZXdNb2RlbA== 71563 -IHNhbmdhdA== 71564 -IFJlc3BvbnNpdmU= 71565 -IFphY2s= 71566 -4oU= 71567 -SkFWQQ== 71568 -IEZ1bGxlcg== 71569 -IOKdpA== 71570 -LkNvbnN1bWVy 71571 -IGFuaw== 71572 -IHJlYWN0b3Jz 71573 -ZnVjaw== 71574 -X3JhdA== 71575 -IHNlc3Npb25GYWN0b3J5 71576 -X2JhY2t3YXJk 71577 -IHNjcmFtYmxlZA== 71578 -CXRo 71579 -IGluc2Vuc2l0aXZl 71580 -IGNoYW1wcw== 71581 -IG5naW54 71582 -IGNvbmhlYw== 71583 -IEphc3Blcg== 71584 -LmZt 71585 -U3RyaWN0RXF1YWw= 71586 -YWNoc2Vu 71587 -LU5vdg== 71588 -bGFzc2Vu 71589 -LmludGVncmF0aW9u 71590 -KGxibA== 71591 -Q29tcG9zZQ== 71592 -IEZvbg== 71593 -w5o= 71594 -R3JhdGlz 71595 -IExpbWU= 71596 -IEFkYXB0ZXJWaWV3 71597 -IHBvaXNvbmVk 71598 -YW5jaG9ycw== 71599 -6K6+6K6h 71600 -J10/PiI= 71601 -IHByb2N1cg== 71602 -SXRhbHk= 71603 -Lk1PTlRI 71604 -IExVQQ== 71605 -IExpdGh1YW5pYQ== 71606 -IEhlYWRz 71607 -X0NIVU5L 71608 -IFBVU0g= 71609 -QXNwZWN0UmF0aW8= 71610 -IHdlZw== 71611 -IHZpZHM= 71612 -IFdlaW4= 71613 -CUlOVA== 71614 -c2Vzc2lvbklk 71615 -SW5kdXN0cnk= 71616 -IGRlbm91bmNlZA== 71617 -SktMTQ== 71618 -IFZhbmVzc2E= 71619 -LklkZW50aWZpZXI= 71620 -cHJvcHJp 71621 -INC40LM= 71622 -IHTDqWNu 71623 -IG1vc2FpYw== 71624 -U3RyZWFtUmVhZGVy 71625 -LVRo 71626 -Zm9ydGg= 71627 -IGFkaGVyZW5jZQ== 71628 -YmF0ZQ== 71629 -IGtuaWdodHM= 71630 -c291bmRz 71631 -IHNhbGxl 71632 -T01FVA== 71633 -44K544OI 71634 -LXRt 71635 -IFJoZQ== 71636 -LkZpbGVPdXRwdXRTdHJlYW0= 71637 -5YiG57G7 71638 -IEVORw== 71639 -aG9saWRheQ== 71640 -IENvbmdyYXR1bGF0aW9ucw== 71641 -KSgK 71642 -IGFnZ3JlZ2F0ZXM= 71643 -SE9PSw== 71644 -ZXdpcmU= 71645 -U2VuYXRvcg== 71646 -IGVtYmVkZGluZ3M= 71647 -ZXB5 71648 -KENPTQ== 71649 -IHJvYmJlcg== 71650 -w6R0ZXI= 71651 -d2FuZw== 71652 -X3RlYWNoZXI= 71653 -IHJlc2VudG1lbnQ= 71654 -IGxldHR1Y2U= 71655 -ZXJyZXVy 71656 -KGlj 71657 -IFRhY3RpY2Fs 71658 -IENvbnRyYWN0cw== 71659 -IG3Dpm5k 71660 -IHNpdGlvcw== 71661 -IGJhc3RhbnRl 71662 -IG51ZXZvcw== 71663 -CU5kckZj 71664 -IHByaXZhdGVLZXk= 71665 -dWNjaA== 71666 -TU1kZA== 71667 -IOi+k+WHug== 71668 -dW1iYQ== 71669 -QGZvcmVhY2g= 71670 -OiIpOwoK 71671 -IHNsaXBwZXJ5 71672 -IEtleXN0b25l 71673 -IHBpb25lZXJpbmc= 71674 -X3RyaWFuZ2xl 71675 -KCIK 71676 -CQkJCQkJCQkgIA== 71677 -IEludGVydmVudGlvbg== 71678 -U0NJ 71679 -IGNKU09O 71680 -IHRlcm1pbmF0aW5n 71681 -67mE 71682 -IGJhYnlz 71683 -U3Vic2V0 71684 -IOuh 71685 -IHNldWxlbWVudA== 71686 -IG11ZXN0cmE= 71687 -RW50cmU= 71688 -5Lul5LiK 71689 -bmdv 71690 -ImJ5dGVz 71691 -UVJTVA== 71692 -IHlwb3M= 71693 -cGVyc29uYQ== 71694 -IERlcGxveQ== 71695 -Y2Vl 71696 -IOCu 71697 -LmdvYWw= 71698 -IGhhYml0YXRz 71699 -IGlzQWRtaW4= 71700 -IGV4cGxvaXRpbmc= 71701 -IHZlbnRpbA== 71702 -IEJhbGxz 71703 -2KfYqA== 71704 -IG1pbmRmdWxuZXNz 71705 -KGt3YXJncw== 71706 -IHJlc2VtYmxpbmc= 71707 -IGNob2ly 71708 -IG9uQmFja1ByZXNzZWQ= 71709 -IFNFQ1VSSVRZ 71710 -L2d0ZXN0 71711 -IGp1c3RpY2Vz 71712 -IGludGVnZXJWYWx1ZQ== 71713 -YmxhaA== 71714 -IEFpbQ== 71715 -X2ZpbmFsaXpl 71716 -a2Vo 71717 -IENvbXBsZXhpdHk= 71718 -IGF1Z3VzdA== 71719 -Z2V0RWxlbWVudHNCeVRhZ05hbWU= 71720 -IHByZWFjaA== 71721 -IHByb251bmNpYXRpb24= 71722 -IFRyYXNo 71723 -LXBlcmNlbnQ= 71724 -X1BSSVY= 71725 -IEh1bnRz 71726 -IEN1cnNl 71727 -dWVsbGVu 71728 -IGhlYXZ5d2VpZ2h0 71729 -WGk= 71730 -CXNlbGVjdGVk 71731 -IE1jQ295 71732 -5byC5bi4 71733 -fD0K 71734 -IEJhdHRsZWZpZWxk 71735 -SXRlbUltYWdl 71736 -IGRlZHVjdGlvbnM= 71737 -IEVsZW1lbnRhbA== 71738 -KCkpOy8v 71739 -IEJ1cms= 71740 -fSkNCg0K 71741 -c3dpZnQ= 71742 -L2Z1bmN0aW9u 71743 -VXN1YWxseQ== 71744 -X1N0 71745 -X2ZlYXRz 71746 -IElzVmFsaWQ= 71747 -IHphZA== 71748 -SW1hZ2VDb250ZXh0 71749 -IGNsYXNzbmFtZQ== 71750 -IGRvbm5lcg== 71751 -IC0tPgoKCg== 71752 -IG1vdG9yY3ljbGVz 71753 -KycvJys= 71754 -IHNldEJhY2tncm91bmQ= 71755 -XENNUw== 71756 -LkFsbEFyZ3NDb25zdHJ1Y3Rvcg== 71757 -IExleGluZ3Rvbg== 71758 -LmV4YW1wbGVz 71759 -IFB1cnM= 71760 -UHVzaE1hdHJpeA== 71761 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09 71762 -LmFkZFRhcmdldA== 71763 -cG9yYQ== 71764 -RnVsbHNjcmVlbg== 71765 -IGdvb2Y= 71766 -aGxlbg== 71767 -w6RnZQ== 71768 -IENVUkw= 71769 -IEludGVyZXN0aW5n 71770 -IHJldHJpZXZlcw== 71771 -X09iag== 71772 -aW5uZXNz 71773 -LS0tLS0KCg== 71774 -LnRzdg== 71775 -KElN 71776 -IEJyYXZlcw== 71777 -X0lTUg== 71778 -b3N0aQ== 71779 -4buT 71780 -IEV4dGVyaW9y 71781 -IENvdXJ0bmV5 71782 -IHJlc2lkdWVz 71783 -VGllcg== 71784 -Lio7DQoNCg== 71785 -OmJsYWNr 71786 -d2ViVmlldw== 71787 -InBhdGg= 71788 -IG1hc2E= 71789 -XSE9Jw== 71790 -IE1hdGNoaW5n 71791 -ZHVy 71792 -SnZt 71793 -PWNvbnRleHQ= 71794 -X1JJTkc= 71795 -IHByb3BvbmVudHM= 71796 -IFFTdHJpbmdMaXRlcmFs 71797 -IGluZmxhdGU= 71798 -PEZsb2F0 71799 -IERvbm92YW4= 71800 -KElP 71801 -SE9SVA== 71802 -IGRpc2FncmVlZA== 71803 -aXNreQ== 71804 -YXNraW5n 71805 -X1ZFQw== 71806 -SEFTSA== 71807 -IG1hdGhz 71808 -IExhc3RseQ== 71809 -IGRlcHJlc3Npbmc= 71810 -LmVzdGFkbw== 71811 -IGhhbG8= 71812 -X2JsZQ== 71813 -IEdhYnJp 71814 -PFRSZXN1bHQ= 71815 -IHRyb29w 71816 -IGVudW1z 71817 -IFNFUklBTA== 71818 -bnVtZXJ1c2Zvcm0= 71819 -IENoaWM= 71820 -LWV4ZWM= 71821 -IGJhY2tsb2c= 71822 -IEJyYXZv 71823 -UG9wTWF0cml4 71824 -IEJydXQ= 71825 -IGJsb3F1ZQ== 71826 -IGp1bml0 71827 -IFdoaWxzdA== 71828 -0YbQuNGP 71829 -ZmV3 71830 -rIE= 71831 -IFZhcmlldHk= 71832 -IFBvbGl0aWNv 71833 -ZXhlbXBsZQ== 71834 -VXNlckNvbnRyb2xsZXI= 71835 -IGhhcmRlbmVk 71836 -YWtlbnM= 71837 -IFNlZWRlcg== 71838 -b3dhcmRz 71839 -Y2hlY2tzdW0= 71840 -IFNhaQ== 71841 -VkVSVEVY 71842 -UmVzcG9uc2Vz 71843 -cGxvZGU= 71844 -LWhhcmQ= 71845 -U3BlY2llcw== 71846 -UmVuZGVyVGFyZ2V0 71847 -X0NIQVQ= 71848 -IHNob3djYXNlcw== 71849 -aXRpbWF0ZQ== 71850 -X0ZPUkVBQ0g= 71851 -X0NPTkZJR1VSQVRJT04= 71852 -ZWJh 71853 -IEVzc2VudGlhbGx5 71854 -KHBvbHk= 71855 -LWxlYXJuaW5n 71856 -IGfDpXI= 71857 -X3N1Y2M= 71858 -KE1hdA== 71859 -IGNvaWxz 71860 -YnJhcw== 71861 -IGFtYQ== 71862 -X21hdGNoaW5n 71863 -aW5kdXN0cnk= 71864 -IE5vcnJpcw== 71865 -IEV4cG9zdXJl 71866 -IHBlcnZhc2l2ZQ== 71867 -IGRleg== 71868 -5peP 71869 -IGVsZWN0cm9uaWNhbGx5 71870 -RERS 71871 -IFN0aW0= 71872 -INGE0LDQudC70LA= 71873 -IG1hZHJl 71874 -bmVtb25pYw== 71875 -a2ljaA== 71876 -IEZyYWdlbg== 71877 -IFJ1bmU= 71878 -IG9uVG91Y2g= 71879 -CXNjYWxl 71880 -IFBoYXJtYWM= 71881 -IE1hbmRhdG9yeQ== 71882 -IFN0bw== 71883 -IEJyYW0= 71884 -X0xlZnQ= 71885 -X1NUQVI= 71886 -KX19Ig== 71887 -c2Npb3VzbHk= 71888 -0LXQt9GD0LvRjNGC 71889 -56uZ 71890 -Z3Jhdml0eQ== 71891 -K0M= 71892 -fTw= 71893 -QU5HRVM= 71894 -IGNvbnRyYWN0aW9u 71895 -IFdhbGxwYXBlcg== 71896 -LkZhY2U= 71897 -IHByw7N4aW1v 71898 -LmZpZw== 71899 -bGFuZ2xl 71900 -INC/0LXRgNC10Lw= 71901 -X0NSRUFU 71902 -QmFzaWNhbGx5 71903 -IGF3YWl0cw== 71904 -IENIQVJBQ1RFUg== 71905 -IHZwbg== 71906 -SG9u 71907 -IGV2aXRhcg== 71908 -IFVuZG8= 71909 -UVM= 71910 -IEVkbXVuZA== 71911 -IG1pcmFjbGVz 71912 -IFRpbWluZw== 71913 -IFZlbmV6dWVs 71914 -LlNxcnQ= 71915 -b2lkYWw= 71916 -IGVycnM= 71917 -LS0tLS0tLS0KCg== 71918 -IERFQ0xBUkU= 71919 -IHZpZ29yb3Vz 71920 -YXJnb24= 71921 -IGFnZ3JlZ2F0ZWQ= 71922 -IFNoYXJrcw== 71923 -IEN5cnVz 71924 -IHJlcHLDqXM= 71925 -bWF0Y2hlcg== 71926 -IGd1aUFjdGl2ZQ== 71927 -PyIpCg== 71928 -IEpOSQ== 71929 -LmNoYXJzZXQ= 71930 -J3w= 71931 -IGdvYXRz 71932 -aW5kcmU= 71933 -LmdldERheQ== 71934 -IHBhcnNlcw== 71935 -IElocmVu 71936 -X18uJy8= 71937 -aWxlZ2Vz 71938 -bmF2aWdhdGU= 71939 -IEJ1ZmZ5 71940 -UEhQVW5pdA== 71941 -IG1hc3Nh 71942 -YWx0YXI= 71943 -JyldLAo= 71944 -IG92ZXJzZWVz 71945 -IHt9DQoNCg== 71946 -IFdMQU4= 71947 -Y2xpcGJvYXJk 71948 -X0luc3RhbmNl 71949 -IGdsYWRseQ== 71950 -KHNlcmllcw== 71951 -IHZhZA== 71952 -IGdldFBhZ2U= 71953 -W29m 71954 -LkludGVydmFs 71955 -aW51cw== 71956 -Y2hhckF0 71957 -b2xlbQ== 71958 -YWludGluZw== 71959 -LkFG 71960 -X21pbm9y 71961 -X0lM 71962 -O3k= 71963 -IFRlbGVjb20= 71964 -IFBvbmQ= 71965 -IG1tYXA= 71966 -L14= 71967 -IFlhaw== 71968 -IFJhYmJp 71969 -ZW5vcw== 71970 -CUNvbnRleHQ= 71971 -LnZlYw== 71972 -KEF0dHJpYnV0ZQ== 71973 -IGNhdGVnb3JpemVk 71974 -IGRpYWJldGlj 71975 -KHJhbms= 71976 -IHBhw61zZXM= 71977 -IEAiIjsK 71978 -IGppa2E= 71979 -YXJzaXR5 71980 -IC8o 71981 -LkhlbHA= 71982 -LWJhbm5lcg== 71983 -IEJ5cm9u 71984 -IHVucmVhbGlzdGlj 71985 -IHxf 71986 -IFN0b3B3YXRjaA== 71987 -IGV4ZW1wdGlvbnM= 71988 -L2NhcmRz 71989 -IHRvc3RyaW5n 71990 -bmdpbmU= 71991 -IHNwcmF3bGluZw== 71992 -IGx0ZA== 71993 -IFVuZGVyc3RhbmQ= 71994 -INGC0LXQutGB0YI= 71995 -ZXdpdG5lc3M= 71996 -IGNhbGxCYWNr 71997 -LVllYXI= 71998 -RnVlbA== 71999 -PSo= 72000 -IGludmVudG9y 72001 -IGJlc3RzZWxsaW5n 72002 -IGhhcmRuZXNz 72003 -IFR1cw== 72004 -IGtleW5vdGU= 72005 -IGJlYXU= 72006 -X2Fib3J0 72007 -IHByb3Bvcg== 72008 -IGNvbWVyYw== 72009 -X1JFRkVS 72010 -UGFz 72011 -aGF2ZW4= 72012 -LWZpeA== 72013 -Q2Fub25pY2Fs 72014 -IGxvb2tvdXQ= 72015 -RXhwbG9yZXI= 72016 -IGNlcmNv 72017 -KHNlbnNvcg== 72018 -IEpzb25TZXJpYWxpemVy 72019 -IHZva3Nlbg== 72020 -IGJyaWdodGVzdA== 72021 -IHN0YWJiaW5n 72022 -LkJl 72023 -LmFkZFByb3BlcnR5 72024 -IEh1bXBo 72025 -IGlzQXV0aGVudGljYXRlZA== 72026 -5rKh 72027 -IHBvcmVz 72028 -IGplZ28= 72029 -IFNob3dpbmc= 72030 -ID8+Ij4NCg== 72031 -X0NPU1Q= 72032 -aWxpbmVhcg== 72033 -IFdvcmtzcGFjZQ== 72034 -IHNwZWw= 72035 -YWdvZ3Vl 72036 -IE1pbGxlbm5pdW0= 72037 -IFBvcHVsYXRl 72038 -IG5pZA== 72039 -LnBhcnNlQ29sb3I= 72040 -U29sYXI= 72041 -IEdhZA== 72042 -IOykkQ== 72043 -IEthbXA= 72044 -CXJt 72045 -IGJlbno= 72046 -IEhvbmVzdGx5 72047 -IGVsZWN0cm9kZQ== 72048 -IFByYWlyaWU= 72049 -IFBST0ZJTEU= 72050 -IE9yaWVudGFs 72051 -IE9MRUQ= 72052 -L2NvcHlsZWZ0 72053 -YXdhaWk= 72054 -KHByb2R1Y3Rz 72055 -KVw8 72056 -LWNyZWF0ZWQ= 72057 -Lk1hbnlUb01hbnk= 72058 -Ikhvdw== 72059 -INCy0YvQvw== 72060 -IG1pdG9jaG9uZHJpYWw= 72061 -X3Rlc3Rpbmc= 72062 -KGNyZWF0ZWQ= 72063 -IGdldEZpZWxk 72064 -X0VWQUw= 72065 -XS4i 72066 -IEZTTQ== 72067 -IFJpdGE= 72068 -IOWPguaVsA== 72069 -IGPDtHQ= 72070 -IEluc2lnaHQ= 72071 -CW15c3FsaQ== 72072 -X3RpbWluZw== 72073 -SURP 72074 -KSkpKSkK 72075 -Q09WRVJZ 72076 -LmltYWc= 72077 -Q0RG 72078 -bHVzdA== 72079 -aWNrdA== 72080 -X0ZQ 72081 -LicsJw== 72082 -Z2Nj 72083 -IGt1cno= 72084 -X3B3bQ== 72085 -IG9kcG93aWVk 72086 -IEJhcnJpZXI= 72087 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgo= 72088 -cGFr 72089 -LUlzcmFlbA== 72090 -IFJ1dGdlcnM= 72091 -IHNlbGVjdGVkSXRlbQ== 72092 -IFJhbWlyZXo= 72093 -RmFybQ== 72094 -IGNhbGVuZGFycw== 72095 -Z3ppcA== 72096 -IGJsb2NrYnVzdGVy 72097 -IFBseW1vdXRo 72098 -55yM 72099 -cmVzcG9uc2Vz 72100 -LkRpYWxvZ0ludGVyZmFjZQ== 72101 -LWdyYW5k 72102 -IGdldFNvdXJjZQ== 72103 -IGRlanRpbmdz 72104 -IHRpZXRlbg== 72105 -IGNvbmRlbW5hdGlvbg== 72106 -IGNvbnRpbnVhcg== 72107 -Lk1vY2tNdmM= 72108 -L2VuZ2xpc2g= 72109 -IE1lZGlhUGxheWVy 72110 -Y29tcHV0ZWQ= 72111 -IENsaXBwZXJz 72112 -KGRlbGVnYXRl 72113 -LlNsZg== 72114 -IOuhnA== 72115 -IFRpZGU= 72116 -IGlocmVt 72117 -IFdhbg== 72118 -0YPRjtGJ 72119 -fT48 72120 -RGlzY3Vzc2lvbg== 72121 -IHdhdHRz 72122 -LW1pbnVz 72123 -IEp1bGlldA== 72124 -6ZuF 72125 -IGNvbmNsdWRpbmc= 72126 -YW5kc2NhcGU= 72127 -IMO6bHRpbWE= 72128 -IERFUlA= 72129 -IHNpZ25VcA== 72130 -IFNlY29uZGx5 72131 -V0FJVA== 72132 -bGRz 72133 -LmNhbGxiYWNrcw== 72134 -KGhvdXI= 72135 -aW1hdG9ycw== 72136 -dm9sZW50 72137 -QUFG 72138 -ZWRyaXZlcg== 72139 -IE1hdGhlbWF0aWM= 72140 -PFR1cGxl 72141 -IC8+Jw== 72142 -e2o= 72143 -X0FCT1JU 72144 -RXRoZXI= 72145 -IGVkdWNhdG9y 72146 -IHByZWNhdXRpb24= 72147 -IGZpbmdlcnRpcHM= 72148 -Z2V0VmFy 72149 -Y2FtYXRhbg== 72150 -LWRlYnVn 72151 -IFJBRg== 72152 -W2FyZw== 72153 -IHJhY2Vk 72154 -IHRzdW5hbWk= 72155 -LmZsaW5r 72156 -IGdseWM= 72157 -dWtv 72158 -IE11bHRpcGx5 72159 -IHJlZGlzdHJpYnV0aW9u 72160 -QUdP 72161 -IFJvdXRpbmU= 72162 -IG9wcg== 72163 -KGxvd2Vy 72164 -IEZ1bmt0aW9u 72165 -LmRr 72166 -IGVndA== 72167 -X0JBU0lD 72168 -c3lzY2FsbA== 72169 -IExTRA== 72170 -IER1cGxpY2F0ZQ== 72171 -X3NlbGw= 72172 -IGVycm9ySGFuZGxlcg== 72173 -X2lwcw== 72174 -IGVydg== 72175 -YW5uaWU= 72176 -KHJlc291cmNlTmFtZQ== 72177 -IGJvdHRsZWQ= 72178 -IGNyYXdsaW5n 72179 -ZWdtZW50 72180 -LnNldFRhZw== 72181 -IHJzcw== 72182 -IFF1YXJyeQ== 72183 -X2V4YWN0 72184 -Lmp3dA== 72185 -IEJvYXJkcw== 72186 -b3Bp 72187 -IG5hc2Fs 72188 -IFhZWg== 72189 -LnVk 72190 -Tm9ydGhlcm4= 72191 -IGFjdGl2YXRpbmc= 72192 -ZWR4 72193 -b3ZhaA== 72194 -IGluZHg= 72195 -QWxlcnREaWFsb2c= 72196 -IHRpZW5lcw== 72197 -YW5ueWE= 72198 -X3Bhbg== 72199 -KGRlY2ltYWw= 72200 -LkRpY3Q= 72201 -IHN1YnNpZGlhcmllcw== 72202 -UHJvZHVjdE5hbWU= 72203 -RmV3 72204 -ZGF0bw== 72205 -b2RpZWQ= 72206 -LXVuZGVy 72207 -IOqygw== 72208 -54mI5pys 72209 -YXRpc20= 72210 -W01hdGg= 72211 -Lic8 72212 -KGluZmlsZQ== 72213 -IGRlbm90ZXM= 72214 -JGNsYXNz 72215 -X1NFQ1VSSVRZ 72216 -IHNld2FnZQ== 72217 -bWVsb24= 72218 -KENoYXJhY3Rlcg== 72219 -L2dpdGh1Yg== 72220 -IGdsYXJpbmc= 72221 -Lkd1aWQ= 72222 -X3NwYXJzZQ== 72223 -IE1hcmdpbg== 72224 -X2Rucw== 72225 -IG1laW5lcg== 72226 -IGxlZnRpc3Q= 72227 -CWxvYw== 72228 -YWJ5dGVz 72229 -IGVxdWlwbWVudHM= 72230 -ZXhwbw== 72231 -IFNvbWVyc2V0 72232 -RUs= 72233 -5o2i 72234 -IGxlY3R1cmVy 72235 -IG1lbWlsaWtp 72236 -5qC4 72237 -57Sg 72238 -cHJvbg== 72239 -OnBvaW50ZXI= 72240 -Ym9ycm93 72241 -IFByb3RlY3RpdmU= 72242 -X2Nm 72243 -INCV0YHQu9C4 72244 -YnBw 72245 -JzsKCgoK 72246 -YXR1cmFsbHk= 72247 -X05BVg== 72248 -IHBlcHRpZGU= 72249 -PmQ= 72250 -IGlmc3RyZWFt 72251 -X0ZBQ1RPUlk= 72252 -Jyk7Ly8= 72253 -am9pbmVk 72254 -bW9uZw== 72255 -IHRpbWVzcGVj 72256 -IGRlc3RhYmls 72257 -IGF1dG9w 72258 -LWxpbWl0 72259 -cHVibGljYXRpb24= 72260 -IERlbm4= 72261 -Lk1lbW9yeQ== 72262 -KHNrYg== 72263 -IEFuYWhlaW0= 72264 -X1JFVFVSTlRSQU5TRkVS 72265 -b3VldXI= 72266 -KF8oJw== 72267 -bGVndA== 72268 -aXN0aW5ndQ== 72269 -CXByaXY= 72270 -IHJlZGlyZWN0cw== 72271 -TXQ= 72272 -IGFsbGVlbg== 72273 -IFBvaW50Rg== 72274 -IG9taW4= 72275 -IGNpdHQ= 72276 -IFRhZ2U= 72277 -IFdhbGxz 72278 -4buJ 72279 -IG9jY3VweWluZw== 72280 -eEJG 72281 -cmFuZ2xl 72282 -IHJlbGF0aW9uYWw= 72283 -LW9yZw== 72284 -IGpwZw== 72285 -LWRlcml2ZWQ= 72286 -IG1hbGZ1bmN0aW9u 72287 -IEJlbnNvbg== 72288 -KHNjcm9sbA== 72289 -IFhE 72290 -SG9seQ== 72291 -KGNvbW1hbmRz 72292 -IHRpcHBpbmc= 72293 -IHByaW1pdGl2ZXM= 72294 -IHNleGxl 72295 -Q2FsbENoZWNr 72296 -IE1BU1RFUg== 72297 -X1RFQU0= 72298 -LnNldFJlcXVlc3RIZWFkZXI= 72299 -X3NwZWNz 72300 -IHNlcmdl 72301 -Lk1hc3Rlcg== 72302 -IGltcw== 72303 -LlNwcmluZ0Jvb3RUZXN0 72304 -cGF5cGFs 72305 -IFdBTlQ= 72306 -Lkluc3Q= 72307 -IENhcnBldA== 72308 -IHdyb25nbHk= 72309 -KCQoJy4= 72310 -IGJpbGQ= 72311 -LlJvbGw= 72312 -IFVyYg== 72313 -LWNhbg== 72314 -44GP44Gg44GV44GE 72315 -b2xpYmVyYWw= 72316 -PCEtLTw= 72317 -4oCUZm9y 72318 -IG5lZ2F0ZQ== 72319 -KG5vcm0= 72320 -YWVj 72321 -X3NhbGFyeQ== 72322 -cGxhaW50ZXh0 72323 -b2Rlc2s= 72324 -IEJvc2No 72325 -U2NpZW50aXN0cw== 72326 -aW5kZXhlcw== 72327 -IG1weg== 72328 -IGdyb3VuZHdhdGVy 72329 -fX0pOwo= 72330 -0LDQu9C40Lc= 72331 -IGVybw== 72332 -IHByZXNjcmliZQ== 72333 -IEV4dHI= 72334 -PEFycmF5TGlzdA== 72335 -IGF0cm9jaXRpZXM= 72336 -QXJlYXM= 72337 -IFRJbnQ= 72338 -KHBsYXllcnM= 72339 -IGRhdGFi 72340 -IHd5bQ== 72341 -44Gb 72342 -IGR1YXM= 72343 -X3Bvc3NpYmxl 72344 -IGluc3RydWN0aW9uYWw= 72345 -aXRpb25lcg== 72346 -L2F1ZGlv 72347 -ICAgICAgICAgICAgICAgIAoK 72348 -c3RvcmVk 72349 -T01QSQ== 72350 -IGFwcHJlbnRpY2Vz 72351 -VGVuYW50 72352 -IENvdXQ= 72353 -IGNvbnRyYWNlcHRpb24= 72354 -TG9hbg== 72355 -X3Zpc2liaWxpdHk= 72356 -J3x8 72357 -LlBhcnNlRXhjZXB0aW9u 72358 -IGNvaW5jaWRl 72359 -LmdldFdpbmRvdw== 72360 -IE1hcnRpYWw= 72361 -X3Rscw== 72362 -L2Jvb2tz 72363 -IG91dHJhZ2Vk 72364 -ICh+KA== 72365 -c3Ryc3Ry 72366 -IEJveGVz 72367 -6YO9 72368 -44Ol 72369 -Uk9J 72370 -RnVuY3Rpb25hbA== 72371 -IFByb2Q= 72372 -PFRlc3Q= 72373 -IHZpZGVvdA== 72374 -IGFtb3Jl 72375 -YWJicg== 72376 -IE1vbnVtZW50 72377 -IHJlaW5mb3JjZW1lbnQ= 72378 -IENvY29udXQ= 72379 -LnNlbmRTdGF0dXM= 72380 -Lmtl 72381 -IExlYXA= 72382 -X2FydGljbGVz 72383 -UGll 72384 -IElydmluZQ== 72385 -QUJDREVGR0hJ 72386 -IEV4cGxhbmF0aW9u 72387 -Z3JvdXBCeQ== 72388 -IG92ZXJoZQ== 72389 -IGFuw6Fs 72390 -IGNsYXNzaWZpZXJz 72391 -IE1peGVy 72392 -L2NvbG9ycw== 72393 -IFVzZXJEYXRh 72394 -X0FSUk9X 72395 -X3ZsYW4= 72396 -LkNyZWF0ZURpcmVjdG9yeQ== 72397 -IEhhaw== 72398 -IEJvbmVz 72399 -IEFwaVJlc3BvbnNl 72400 -IE1vb2R5 72401 -REFD 72402 -Z2V0Yw== 72403 -6LaF 72404 -LkZpcmU= 72405 -6aM= 72406 -IGhpdHRlcg== 72407 -ZnJlc2g= 72408 -4LmB 72409 -IENoaWxkaG9vZA== 72410 -eG9y 72411 -LWh0dHA= 72412 -IE1PUg== 72413 -LnNlbmRLZXlz 72414 -X3NoYXBlcw== 72415 -IFVwcw== 72416 -IEFycmVzdA== 72417 -YXp6aQ== 72418 -X29wY29kZQ== 72419 -Lk5vbWJyZQ== 72420 -IHByw7Nw 72421 -IHp4 72422 -IHRyZW1lbmRvdXNseQ== 72423 -U3BhY2Vz 72424 -ZWNj 72425 -IHZlbHZldA== 72426 -IG1lbW9yaWE= 72427 -IExBUA== 72428 -LkRyYXdMaW5l 72429 -IHRhcmdldFR5cGU= 72430 -cmVzdHJpY3Rpb24= 72431 -IERSVg== 72432 -W3RvcA== 72433 -IeKAmQ== 72434 -L2NoYXQ= 72435 -IHNvbmlj 72436 -VG9yb250bw== 72437 -b3dp 72438 -LmRvY3M= 72439 -IEluaXRpYWxpc2U= 72440 -IDwh 72441 -LnRibA== 72442 -LlByZXBhcmVkU3RhdGVtZW50 72443 -L2RvbQ== 72444 -LnJvdA== 72445 -X1BST00= 72446 -S2VlcGluZw== 72447 -IGhhcmdh 72448 -IGpvcm4= 72449 -IGlkZW50aWZpYWJsZQ== 72450 -W2lw 72451 -UGluaw== 72452 -X0hlYWRlcg== 72453 -w5E= 72454 -YWRsZQ== 72455 -572R57uc 72456 -c2VxdWVudA== 72457 -QWN0aXZhdGVk 72458 -dG1wbA== 72459 -IFBhbGw= 72460 -IGZhdGFsbHk= 72461 -fX0pCg== 72462 -UG9wb3Zlcg== 72463 -IE1jTGFyZW4= 72464 -Q2hhbmdlZEV2ZW50QXJncw== 72465 -IEZvcm1hdGlvbg== 72466 -TmFt 72467 -bmV3c2xldHRlcg== 72468 -LmZyb21TdHJpbmc= 72469 -X2ltbQ== 72470 -QVBQRUQ= 72471 -LG5vZGU= 72472 -KGRldA== 72473 -IHBhcmFsbGVscw== 72474 -IGxhc2Vycw== 72475 -IGNob2NvbA== 72476 -L3BvcnQ= 72477 -YWZmZW4= 72478 -KGRldGFpbHM= 72479 -IHJlcGxpY2F0ZWQ= 72480 -QXNTdHJlYW0= 72481 -YXJtYWM= 72482 -XV09 72483 -YWxhY2g= 72484 -X3Nlc3Npb25z 72485 -QWxnb3JpdGhtRXhjZXB0aW9u 72486 -IHZlcmJvc2l0eQ== 72487 -LkNvbHVtblN0eWxlcw== 72488 -KFVTRVI= 72489 -IHNsZWVwcw== 72490 -IGFxdWF0aWM= 72491 -X2J1bGs= 72492 -PScuLw== 72493 -b3VybsOpZQ== 72494 -IE1TRA== 72495 -IEJsb2M= 72496 -IEdsZQ== 72497 -IHJlcHJlc3Npb24= 72498 -IGVudG9uY2Vz 72499 -CQkgICAgICAgICAgICAgICAgICAg 72500 -WU5D 72501 -LkFsbG93R2V0 72502 -IHR1cnRsZXM= 72503 -ICd+Lw== 72504 -ZXNzb24= 72505 -IERJRQ== 72506 -IEFxdWE= 72507 -IFNFUQ== 72508 -Ozs7Ozs7Ozs7Ozs7Ozs7Ow== 72509 -LnB1dHM= 72510 -IE1BSw== 72511 -KEN1c3RvbWVy 72512 -IGRlc3NlcnRz 72513 -IGVtYmVsbA== 72514 -IHRheGVk 72515 -5bqX 72516 -IHNjaGw= 72517 -cmVzY28= 72518 -IEZyb2c= 72519 -IFBlbmRpbmdJbnRlbnQ= 72520 -X0xvY2Fs 72521 -L3NlY3VyaXR5 72522 -IFJveA== 72523 -IHNwb2lsZWQ= 72524 -X1dJTkRPV1M= 72525 -SmVubmlmZXI= 72526 -IGRhdGk= 72527 -VW5sb2Fk 72528 -LmdyaWR4 72529 -KHN0YWdl 72530 -4buX 72531 -U3FsQ29tbWFuZA== 72532 -Lm14 72533 -IGJsaXR6 72534 -IEZvcnRyZXNz 72535 -IEJyb3dzZXJBbmltYXRpb25zTW9kdWxl 72536 -d2luZQ== 72537 -TlNF 72538 -LXJhbmtpbmc= 72539 -eXJl 72540 -IGxpbmthZ2U= 72541 -w6Fr 72542 -kZw= 72543 -YXRzYXBw 72544 -IEN5Y2w= 72545 -IGVjb2xvZ3k= 72546 -IGJsYXRhbnQ= 72547 -IFBlcmY= 72548 -IFhpYW9taQ== 72549 -IERvcnRtdW5k 72550 -cmVzdWx0U2V0 72551 -IGdpw6A= 72552 -IGZhdWNldA== 72553 -IERhbHRvbg== 72554 -IGZyZWVz 72555 -QlVGRg== 72556 -LnBhcmFsbGVs 72557 -IEFzdHJvcw== 72558 -IFZFQ1RPUg== 72559 -IHN0YW5kb3V0 72560 -w7Ntbw== 72561 -IGZyYW1lYm9yZGVy 72562 -X1BBUkFNRVRFUlM= 72563 -IEZhbGs= 72564 -IERpZ2l0 72565 -IGVsZWN0csOzbmljbw== 72566 -IHZlcnI= 72567 -VUlBbGVydFZpZXc= 72568 -KFNxbA== 72569 -LUlORg== 72570 -IikpKTs= 72571 -JycK 72572 -KEVGRkVDVA== 72573 -IFp1bQ== 72574 -X0RQ 72575 -KV07DQo= 72576 -IGFudGVubg== 72577 -IGFiYnJldmlhdGlvbg== 72578 -IHNlaXNtaWM= 72579 -X1RSQU5TTA== 72580 -tZw= 72581 -Lk1pbGxpc2Vjb25k 72582 -LGxhdA== 72583 -IEFuY2g= 72584 -X01vZA== 72585 -QWxyaWdodA== 72586 -ZGRh 72587 -IMKl 72588 -VU5ETEU= 72589 -INC30LDQsw== 72590 -IHN1bGZ1cg== 72591 -IFNpdGg= 72592 -IE5pbWJ1cw== 72593 -IEV4YW1pbmF0aW9u 72594 -X3dpZmk= 72595 -fWApOwoK 72596 -IHNlbnNhdGlvbnM= 72597 -YWZz 72598 -X0NMUg== 72599 -IGluZmluaXRlbHk= 72600 -IHN5c3TDqG1l 72601 -X2ZvbnRz 72602 -SW1wYWN0 72603 -UG93ZXJlZA== 72604 -IDw9Pg== 72605 -X25lZWQ= 72606 -REVDUkVG 72607 -IC8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8v 72608 -IFJlcG8= 72609 -Z2V0U2VydmljZQ== 72610 -JG4= 72611 -X3BjdA== 72612 -RXJyZXVy 72613 -IE5HT3M= 72614 -ICoKCgo= 72615 -LmF0YW4= 72616 -X1RNUA== 72617 -IGNvbGxhcHNpbmc= 72618 -IHNobw== 72619 -X1BDSQ== 72620 -Lm9wZXI= 72621 -KGFkag== 72622 -IGdpb3Y= 72623 -Piku 72624 -IGluY29udHJv 72625 -YXJkYQ== 72626 -IGFwZXg= 72627 -IG1lZGlkYQ== 72628 -IFNoZWlraA== 72629 -IEFybWVuaWE= 72630 -YXNzb2NpYXRl 72631 -LXdvdw== 72632 -IFR1cm5pbmc= 72633 -IEZyZXVk 72634 -IEZvb2w= 72635 -IExEUw== 72636 -LS0tLS0tLQoK 72637 -b2xzb24= 72638 -LkZJTEU= 72639 -X2RldGVjdG9y 72640 -RG9taW4= 72641 -IGRlcGxveW1lbnRz 72642 -IGZhcmV3ZWxs 72643 -KGJpbmQ= 72644 -IG5vdmljZQ== 72645 -dGRvd24= 72646 -IGdldEVsZW1lbnQ= 72647 -IHZlbGl0 72648 -YXN0aGFu 72649 -CWNoYW5uZWw= 72650 -X0ZSQU1FQlVGRkVS 72651 -LnRyYWlsaW5n 72652 -LnNldEVkaXRhYmxl 72653 -Oyw= 72654 -IElERg== 72655 -X1BC 72656 -Z2V0TGFzdA== 72657 -IENvYXN0YWw= 72658 -IEhhbmR5 72659 -bGluZ2Vy 72660 -44Gn44KC 72661 -UGVyc2lzdGVuY2U= 72662 -LmdldFNlcnZpY2U= 72663 -INC+0Lo= 72664 -IG5vdHdpdGhzdGFuZGluZw== 72665 -KFBS 72666 -VU1C 72667 -J10pKXsNCg== 72668 -ZW1icmFuY2U= 72669 -ZXhjZXJwdA== 72670 -YXF1 72671 -X2Jsb2M= 72672 -IFByb3Zpc2lvbg== 72673 -IE1jRG9u 72674 -IEdvbGRiZXJn 72675 -IGNvbXBvbmVudFdpbGxVbm1vdW50 72676 -IGJhc2VQYXRo 72677 -LWZpcmVk 72678 -IGZvbGxhbmRv 72679 -IFRpbGVz 72680 -QGVuZGZvcmVhY2g= 72681 -RU5DSUw= 72682 -IEJveGluZw== 72683 -aXF1ZXI= 72684 -QWNoaWU= 72685 -RW51bXM= 72686 -QmFzZVVybA== 72687 -KHNjYW4= 72688 -IFBhc3NpdmU= 72689 -YWJlbGxh 72690 -L3Nu 72691 -Lm51bWVyaWNVcERvd24= 72692 -IHZlcm4= 72693 -bG9jYWxpemVk 72694 -IE1peg== 72695 -IHJlc3VsdExpc3Q= 72696 -L3Z1ZQ== 72697 -RVJWSUNF 72698 -Lm9k 72699 -IGxpZ24= 72700 -IFN0cmluZ1Rva2VuaXplcg== 72701 -IHRyYWc= 72702 -QWNjb3JkaW9u 72703 -IG5vcmVmZXJyZXI= 72704 -bXNjb3JsaWI= 72705 -w6F0aXM= 72706 -Ynl0ZXI= 72707 -IHNob3dkb3du 72708 -IHNlbWFpbmU= 72709 -IC0tPg0KDQo= 72710 -IE1haG0= 72711 -fSI7Cgo= 72712 -IGRx 72713 -IFB1Ymxpc2hlcnM= 72714 -IEFtcGw= 72715 -IERhbmllbGxl 72716 -IHRlcm4= 72717 -6LW3 72718 -bm/Fm8SH 72719 -ZWlu 72720 -IEFzeW5jU3RvcmFnZQ== 72721 -dW5nZXI= 72722 -cm91dw== 72723 -IHNjaXNzb3Jz 72724 -L2Fzc2VydA== 72725 -LmJ1Y2tldA== 72726 -L2FyY2hpdmU= 72727 -X01hbg== 72728 -IGludG9sZXI= 72729 -ICgpPT4= 72730 -INCS0Ys= 72731 -IHNhaQ== 72732 -Lnh5 72733 -LiINCg== 72734 -IHVyaW5hcnk= 72735 -ZXN1Yg== 72736 -SVNUSUNT 72737 -IM66 72738 -IGNvbXBsaW1lbnRz 72739 -IHR5cGluZ3NKYXBnb2xseQ== 72740 -aWhhcg== 72741 -RXhwYW5zaW9u 72742 -IFNlcnZpbmc= 72743 -X3N0dWRlbnRz 72744 -IFhCT09MRQ== 72745 -KGls 72746 -IOyymA== 72747 -IGrDsw== 72748 -KHRvbA== 72749 -KEpT 72750 -CUNH 72751 -IERSQVc= 72752 -dHdpZw== 72753 -IG9hdA== 72754 -X3Ntb290aA== 72755 -IENTTA== 72756 -IG9zb2I= 72757 -IGVuc3Vpbmc= 72758 -IGJhbmtlcg== 72759 -IEJhY2twYWNr 72760 -X3Bpbmc= 72761 -IHdpc2hsaXN0 72762 -PWF4 72763 -CSAgIAo= 72764 -RGlzbmV5 72765 -c3RlYWR5 72766 -Ij4l 72767 -IHByb3BoZXRz 72768 -IFpY 72769 -IG1pbmltYWxpc3Q= 72770 -LlBMQUlO 72771 -U2VhdHRsZQ== 72772 -Lm9yZGluYWw= 72773 -IFBJUEU= 72774 -IHJldG9ybmE= 72775 -IGp1Z2Fkb3I= 72776 -IEJyZXQ= 72777 -IOKUnA== 72778 -IHBsdXNo 72779 -VUxBVE9S 72780 -U29ydGluZw== 72781 -LmdyaWR5 72782 -ZWN0b215 72783 -X2FjdGl2 72784 -cmFjaw== 72785 -SW50ZXJhY3RpdmU= 72786 -IEFudGFyY3RpY2E= 72787 -IHZlbmdlYW5jZQ== 72788 -ZW5zbw== 72789 -X2tub3du 72790 -dXBwbGllcg== 72791 -Lk1vZHVsZXM= 72792 -IENvbm5lY3Rpb25TdGF0ZQ== 72793 -6ZqQ6JeP 72794 -QEZpbmRCeQ== 72795 -IHBsYWNlcg== 72796 -XG1vZGVs 72797 -PCgpPg== 72798 -LmlzU3VjY2Vzc2Z1bA== 72799 -LWdvb2Q= 72800 -Yno= 72801 -IERyYWNv 72802 -QXNzaXN0YW50 72803 -LWV4dHJh 72804 -0LDQsdC70LjRhg== 72805 -IGh5cG9jcmlzeQ== 72806 -IHRzdA== 72807 -IEFncg== 72808 -JHR4dA== 72809 -IGxvZ2lzdGlj 72810 -bGljZW5zZWQ= 72811 -IEhvZg== 72812 -IHRhdA== 72813 -KGl2 72814 -IGludG94aWM= 72815 -cG9zdElk 72816 -X3N0cmlrZQ== 72817 -IGh1bWlsaWF0aW9u 72818 -cGNvZGVz 72819 -InN5bmM= 72820 -KHJlY2lwZQ== 72821 -K04= 72822 -cmVudGU= 72823 -CUNsaWVudA== 72824 -eWNvcGc= 72825 -IFp1cmljaA== 72826 -IFByb2ZpbGVz 72827 -Q291bnRyaWVz 72828 -IHBpY3Q= 72829 -IHJvbGxvdXQ= 72830 -cmVxdWVuY2llcw== 72831 -IHBhdGNoZWQ= 72832 -IGNhcnRyaWRnZXM= 72833 -IHNoYWRpbmc= 72834 -SmFy 72835 -IHNhbHZhZ2U= 72836 -IFRheGVz 72837 -IHN0YW5kYnk= 72838 -YXBvcmFu 72839 -RWlnZW4= 72840 -LmFuZ3VsYXI= 72841 -IE5lc3RlZA== 72842 -5Lqr 72843 -IGlzVmlzaWJsZQ== 72844 -IER3aWdodA== 72845 -X0JSQU5DSA== 72846 -LkRlbGF5 72847 -IGtlbmQ= 72848 -IGZhY2lsaXRhdGVk 72849 -LmZsYXRNYXA= 72850 -IHNhbnRh 72851 -CVNlbmQ= 72852 -L21lc3NhZ2Vz 72853 -IG9mVHlwZQ== 72854 -CXN3YXA= 72855 -I3BsdA== 72856 -IFR1cmtz 72857 -TkVT 72858 -IHByb2dyZXNzaXZlbHk= 72859 -IFJlc2lkZW5jZQ== 72860 -IFRSRUU= 72861 -IG5vZW4= 72862 -ZGlv 72863 -IG5lbGxl 72864 -IHNvZ2Fy 72865 -aXR0aQ== 72866 -d2Vla2x5 72867 -IGFtYmlndWl0eQ== 72868 -X1NldHRpbmdz 72869 -V2FyZQ== 72870 -Lm5lbw== 72871 -X0RTVA== 72872 -IOaWuQ== 72873 -cHJlcA== 72874 -bG9iYnk= 72875 -QGVtYWls 72876 -L21vdmll 72877 -IGZ1bmtj 72878 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgCg== 72879 -wq1z 72880 -IGd1YXJkaWFucw== 72881 -LXBvcw== 72882 -IGNvbmZpZ3VyaW5n 72883 -IENQUw== 72884 -IERldXM= 72885 -IHZpZMOpb3M= 72886 -X2VtcHJlc2E= 72887 -IHNsYXBwZWQ= 72888 -PE1vZGVs 72889 -IHVuZGVyc2NvcmVz 72890 -VWg= 72891 -LmFjY2Vzc1Rva2Vu 72892 -U0VUUw== 72893 -IFNwYXJzZQ== 72894 -IENhbGQ= 72895 -OnBhdGg= 72896 -IFNlcnZlcnM= 72897 -PWJhdGNo 72898 -IGtuaXR0aW5n 72899 -IHhh 72900 -IHNlYXJjaEJhcg== 72901 -IHNuYWc= 72902 -IGluZnVzZWQ= 72903 -LmJhbQ== 72904 -bGV2ZXI= 72905 -IHRheG9ub215 72906 -w44= 72907 -IGF0dGFjaGluZw== 72908 -IGhlcm4= 72909 -X05PUA== 72910 -Q2xpY2thYmxl 72911 -KFBhcnNl 72912 -IER5bmFtbw== 72913 -LWJ1aWxkZXI= 72914 -IGRlcmVn 72915 -IHNjYXR0ZXJpbmc= 72916 -6L+b6KGM 72917 -YW56aQ== 72918 -IFNoZXBhcmQ= 72919 -Ij4nLAo= 72920 -X1hERUNSRUY= 72921 -IEJ1enpGZWVk 72922 -X01BUkdJTg== 72923 -UExPWQ== 72924 -LnNtYWxs 72925 -IG1pbWVUeXBl 72926 -IGhvbG9n 72927 -CWNhbWVyYQ== 72928 -bGlhcw== 72929 -IHN1c3BlbnNl 72930 -b2R5bmFt 72931 -YmF1 72932 -IGdyYXZleWFyZA== 72933 -X25hbWVk 72934 -IjoiJw== 72935 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 72936 -IGdhbWVPdmVy 72937 -IExFTkdUSA== 72938 -CXNjcmVlbg== 72939 -IGRvSW5CYWNrZ3JvdW5k 72940 -X2RlcGVuZGVuY2llcw== 72941 -IHJ0Yw== 72942 -L3Vw 72943 -X1JPTQ== 72944 -SGFsbA== 72945 -IGRlZmljaWVuY2llcw== 72946 -KHRl 72947 -JyM= 72948 -X2VxdWl2 72949 -IHByZW9yZGVy 72950 -IEF4ZQ== 72951 -0L7QvNGD 72952 -LnNlbmRGaWxl 72953 -IGZpbHQ= 72954 -IExpbWl0cw== 72955 -IENhdmFsaWVycw== 72956 -LmRpc2NvdW50 72957 -4oaQ 72958 -IFdpdA== 72959 -UVJTVFVW 72960 -IGlq 72961 -IHRlZ2Vu 72962 -IDoiLA== 72963 -ZGlmZmljdWx0eQ== 72964 -cHVua3Q= 72965 -IEVtYWlscw== 72966 -Y2hsb3I= 72967 -KGZ1bg== 72968 -LlVpbnQ= 72969 -IFN0YWxs 72970 -X3ZlcmlmaWVk 72971 -dUQ= 72972 -RmlsZVR5cGU= 72973 -IHBsZWFzdXJlcw== 72974 -IGp1ZGljaWFyeQ== 72975 -IHNoYW0= 72976 -aXB1cg== 72977 -X1BMVVM= 72978 -b2ZmZXJz 72979 -KGZvbw== 72980 -X0dU 72981 -CWNvcmU= 72982 -RU5USU9O 72983 -IExpYmVyYXRpb24= 72984 -Q29tbWFuZExpbmU= 72985 -X2RlcGFydG1lbnQ= 72986 -LkFy 72987 -X25laWdoYm9y 72988 -IFN1Ym1pdHRlZA== 72989 -IDwhLS1b 72990 -IGxvY2F0aW5n 72991 -Lk1hcHBlcg== 72992 -X3N0cmVuZ3Ro 72993 -Wy4uLiw= 72994 -IEphbA== 72995 -L2xvYWQ= 72996 -IGJ1ZmZz 72997 -IG1vdG9yaXN0cw== 72998 -CWNz 72999 -YXNjZW5kaW5n 73000 -IFdoYXRzYXBw 73001 -IE5hc3M= 73002 -X0NPTFVNTlM= 73003 -TGVvbg== 73004 -cHBl 73005 -ZWx0YXM= 73006 -IHRqZWplcg== 73007 -X0tFWVdPUkQ= 73008 -cXVhbGlmaWNhdGlvbg== 73009 -aHJh 73010 -IHJpZGljdWxvdXNseQ== 73011 -JGluZm8= 73012 -RkVBVFVSRQ== 73013 -ZG9lc24= 73014 -IEtX 73015 -IEVudW1lcmFibGVTdHJlYW0= 73016 -X01BVA== 73017 -IFN0cmVhbUxhenk= 73018 -IHNjcmF0Y2hpbmc= 73019 -LnRpY2tldA== 73020 -IHNob3J0Y29taW5ncw== 73021 -ZWxsaXBzaXM= 73022 -PWN1cnJlbnQ= 73023 -IGNyZXN0 73024 -IHdob3Jl 73025 -IFBldHJvbGV1bQ== 73026 -Y29udGV4dHM= 73027 -IOat 73028 -LXB5dGhvbg== 73029 -KGpzb25PYmplY3Q= 73030 -IFByaXNt 73031 -IHlhY2h0 73032 -t6g= 73033 -Zmxhc2hkYXRh 73034 -IGxlaWNodA== 73035 -IE1vcnRvbg== 73036 -IHN0ZXJsaW5n 73037 -X2l0cg== 73038 -X3Vk 73039 -RmFjZXM= 73040 -IGhpcmVz 73041 -ZmZh 73042 -Jyx7Cg== 73043 -LWNhbWVyYQ== 73044 -X1JFQVNPTg== 73045 -IEhlbGVuYQ== 73046 -cnVn 73047 -aWdodGx5 73048 -IHBlcm11dGF0aW9ucw== 73049 -IFRvcmFo 73050 -IOaYr+WQpg== 73051 -CXJlY29yZA== 73052 -w4A= 73053 -LmdtYWls 73054 -Rm9ydHVuYXRlbHk= 73055 -KE1vZA== 73056 -T2NjdXJyZW5jZXM= 73057 -IGRlcHJlY2k= 73058 -IHZhZ3VlbHk= 73059 -L1o= 73060 -Vk4= 73061 -LnRw 73062 -X2dlbmVy 73063 -IHs6P30iLA== 73064 -d2FobA== 73065 -SUtF 73066 -IExlZ2lzbGF0aW9u 73067 -IGhpbnRlcg== 73068 -IGFkZWw= 73069 -KGhpZ2g= 73070 -5o+Q5Lqk 73071 -L2RvbWFpbg== 73072 -LnRpbGVz 73073 -IFRpYmV0YW4= 73074 -IFN0ZXJlbw== 73075 -IGZpbGVTaXpl 73076 -Z3J1cG8= 73077 -aWFl 73078 -U0NQ 73079 -IHZvdWNoZXJz 73080 -IFBhbmRvcmE= 73081 -IGRpc21heQ== 73082 -IGzDqWc= 73083 -IEJlaGF2aW9yYWw= 73084 -Y3Jhbg== 73085 -TmVzdGVk 73086 -YWNjb20= 73087 -IE5haA== 73088 -IEJhbHRpYw== 73089 -IERFU1Q= 73090 -IGtpc3Nlcw== 73091 -Vmlu 73092 -IHByb3Zva2U= 73093 -X0NvbnRleHQ= 73094 -IHdlZWtkYXlz 73095 -dXJnZW5jZQ== 73096 -TGlr 73097 -IHBsYXph 73098 -IGJsZXY= 73099 -IHJlYWZm 73100 -X1RpdGxl 73101 -KEd0aw== 73102 -IGNlbGxl 73103 -Iz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0= 73104 -IEpvb21sYQ== 73105 -Ij4vLw== 73106 -TW9udGhseQ== 73107 -LnRvRG91Ymxl 73108 -KGVudHJpZXM= 73109 -IE5SRg== 73110 -KGdjZg== 73111 -IE1pZGRsZXdhcmU= 73112 -fS17 73113 -X0hJREU= 73114 -IGxvd2Vycw== 73115 -KFNlbGY= 73116 -5Y+R6YCB 73117 -IGlzTG9nZ2VkSW4= 73118 -IGJpb2RpdmVyc2l0eQ== 73119 -IG11c2NoaQ== 73120 -KGNhbmRpZGF0ZQ== 73121 -IEFuc2k= 73122 -CXNt 73123 -L2lt 73124 -Kycp 73125 -Y2Rj 73126 -IGFsZ3VuYQ== 73127 -IHNhY3JpZmljaW5n 73128 -L3ZlbmRvcnM= 73129 -L0FQSQ== 73130 -QWR2ZXJ0aXNpbmc= 73131 -IEdFTkVSQVRFRA== 73132 -IERpc29yZGVycw== 73133 -IFNlcmlhbGl6YXRpb24= 73134 -IHNhdmFnZQ== 73135 -IOm7 73136 -IEluc2lnaHRz 73137 -IHJldm9rZQ== 73138 -IGp1cm9ycw== 73139 -c3VpdA== 73140 -IENhbXBpbmc= 73141 -X3Byb2ZpdA== 73142 -YnVjaA== 73143 -LkFjdGlvbnM= 73144 -IElERUE= 73145 -b2x1bHU= 73146 -TGlrZXM= 73147 -67KI7Zi4 73148 -LkJMTA== 73149 -dsOk 73150 -IGNhcmRp 73151 -IGRpc3Byb3BvcnRpb25hdGVseQ== 73152 -IGluc2FuaXR5 73153 -LmVvZg== 73154 -IFBsYXR6 73155 -LmZpcnN0bmFtZQ== 73156 -IFNsYXNo 73157 -X0NG 73158 -amFuZHJv 73159 -IEdhdWdl 73160 -IFN1bmRlcg== 73161 -IEJ1bm55 73162 -X3Vt 73163 -6IGU57O7 73164 -IGlQaG9uZXM= 73165 -IEJJTw== 73166 -IGtobw== 73167 -eEZB 73168 -IEZyaWVuZHNoaXA= 73169 -IGNhbG1seQ== 73170 -X3Rocg== 73171 -X0FuaW0= 73172 -IHJhaXNvbg== 73173 -L3Jvb3Q= 73174 -LmdldEJ5SWQ= 73175 -IFNhdmFubmFo 73176 -IEludGVycHJldA== 73177 -a2lsbGVy 73178 -CXdn 73179 -XSld 73180 -0YPQtdGC 73181 -S2V5VmFsdWU= 73182 -W0c= 73183 -c3RyZXRjaA== 73184 -LXBsYXlpbmc= 73185 -JTsNCg== 73186 -IHBsYW5r 73187 -IHBlYWNo 73188 -IERlcnJpY2s= 73189 -0LTRgNC10YE= 73190 -IFNoYW0= 73191 -QVBQTElDQVRJT04= 73192 -LnByb2dyZXNzQmFy 73193 -IHRyYW5zaXRpb25pbmc= 73194 -X2RyYWc= 73195 -LlJlcXVlc3RCb2R5 73196 -Lk1vYmlsZQ== 73197 -Sm9uZXM= 73198 -LlBob3Rv 73199 -IGF4bGU= 73200 -enVn 73201 -L29wdGlvbnM= 73202 -XV0pCgo= 73203 -CW5v 73204 -W2hyZWY= 73205 -IGFncmVnYXI= 73206 -IFNlcnZpY2VFeGNlcHRpb24= 73207 -bmluZ2Vu 73208 -RGlmZmljdWx0eQ== 73209 -Qk9PTEVBTg== 73210 -QWRkcw== 73211 -LWhhbmRsZXI= 73212 -IEdhdA== 73213 -IEVib255 73214 -4bqtbg== 73215 -YnJpZ2h0 73216 -IGNvcnBzZXM= 73217 -LkNoZWNrZWRDaGFuZ2Vk 73218 -IG1hdGluZw== 73219 -IEhhcnRmb3Jk 73220 -IHpvdQ== 73221 -IGR1ZGVz 73222 -X2FsZw== 73223 -IEp1bGk= 73224 -b2N1cA== 73225 -INC/0YDQsNCy 73226 -IEthdHk= 73227 -X0ludGVybmFsQXJyYXk= 73228 -LkNvbHVtbkhlYWRlcnNIZWlnaHRTaXplTW9kZQ== 73229 -TWV0aG9kTWFuYWdlcg== 73230 -IFJlZGU= 73231 -IGxpc3RJdGVt 73232 -LkJvdW5kcw== 73233 -IGF2ZW51ZXM= 73234 -IENvZ25pdGl2ZQ== 73235 -RXh0ZW5k 73236 -dGVjaG5pY2Fs 73237 -4oCa 73238 -c25ha2U= 73239 -RnJvbUNsYXNz 73240 -aWxlc3M= 73241 -ID17 73242 -dXJldHRl 73243 -L3RocmVhZA== 73244 -RklFTERT 73245 -SVZJTkc= 73246 -IFBPU0lY 73247 -X2Fr 73248 -IC4uLy4uLy4uLw== 73249 -TXA= 73250 -IGFub255bW91c2x5 73251 -VGFyZ2V0RXhjZXB0aW9u 73252 -YWZmZXI= 73253 -YW55dGhpbmc= 73254 -Imlz 73255 -Z3Jlc28= 73256 -IExhcmE= 73257 -aXphZG9z 73258 -IG1pbmc= 73259 -LnRh 73260 -X3Rocm93 73261 -Umg= 73262 -IHNvbGlkaXR5 73263 -bmFobWU= 73264 -aWNoYWdl 73265 -IG1vdW5k 73266 -b2xpbw== 73267 -YXJ5YQ== 73268 -QVNVUkU= 73269 -IHdvaGw= 73270 -IGZ1cm5pc2hpbmdz 73271 -LnNlY3Rpb25z 73272 -IGFwb2xvZ2llcw== 73273 -YXBpa2V5 73274 -IFNjcmV3 73275 -IFdhcnNhdw== 73276 -L2dyYXBo 73277 -IFNBVEE= 73278 -eXNlcw== 73279 -L2J1dHRvbnM= 73280 -0LXQvdC+ 73281 -VUdIVA== 73282 -IHBvcm5zdGFy 73283 -UGljdHVyZUJveA== 73284 -X1RleHR1cmU= 73285 -IGHDsQ== 73286 -IG5lcmQ= 73287 -LWNvbm5lY3RlZA== 73288 -IG91dHNpZGVycw== 73289 -IG9wZXJhdGl2ZXM= 73290 -YWJibGU= 73291 -L21hbg== 73292 -IHBsZWFk 73293 -XERi 73294 -IENvdmVyZWQ= 73295 -PVM= 73296 -IEZsYW1lcw== 73297 -77+l 73298 -X3RpdGxlcw== 73299 -IHJldHJhY3Q= 73300 -IGNvbGxhYm9yYXRpbmc= 73301 -IGJlaGFuZA== 73302 -LkRhdGFHcmlkVmlld0NvbHVtbkhlYWRlcnNIZWlnaHRTaXplTW9kZQ== 73303 -IGxhYm9yZQ== 73304 -IHRvdGFsUHJpY2U= 73305 -IHNwb2lsZXI= 73306 -IGRpcHBlZA== 73307 -Iikpew0K 73308 -X1NC 73309 -IExlaQ== 73310 -IGluY2x1c28= 73311 -dmVsbA== 73312 -CXBs 73313 -SW5hY3RpdmU= 73314 -IFVTU1I= 73315 -b25kZW4= 73316 -IHJvdXRlZA== 73317 -LnN0cnVjdA== 73318 -4Ks= 73319 -IE1hbGlr 73320 -IEhFWA== 73321 -IEN1c3Q= 73322 -X1BFUkNFTlQ= 73323 -X2VwaXNvZGU= 73324 -5ouJ 73325 -VkVSUw== 73326 -IGNydWlzaW5n 73327 -Qm9va21hcms= 73328 -4oCmCgoKCg== 73329 -Y2hlY2tCb3g= 73330 -b3VmbGFnZQ== 73331 -IG5vbnplcm8= 73332 -IGFwcm94 73333 -IFB1cmR1ZQ== 73334 -Y29vbg== 73335 -bGVncw== 73336 -IExvdHRlcnk= 73337 -U2xm 73338 -SEFW 73339 -Pms= 73340 -PkFu 73341 -IHNsZW5kZXI= 73342 -c2NoZWQ= 73343 -VGVsZWdyYW0= 73344 -Umljaw== 73345 -X1N0cnVjdA== 73346 -X0JD 73347 -IGN1c3RvbWFyeQ== 73348 -IERhbW9u 73349 -dXJjaGFzZWQ= 73350 -IGtvYg== 73351 -IHRpb24= 73352 -KHByb21wdA== 73353 -IGltYg== 73354 -eEND 73355 -CVdlYkVsZW1lbnQ= 73356 -IGhlbW9z 73357 -4Kaw 73358 -IENOQkM= 73359 -IEFMTE9X 73360 -57Gz 73361 -IEVOQw== 73362 -LnNjYWxhdGVzdA== 73363 -IFRCRA== 73364 -Z2V0UmVmZXJlbmNl 73365 -IEltcG9ydGVk 73366 -4Liw 73367 -IGl3 73368 -b2xvbg== 73369 -bWls 73370 -Oi8vJHs= 73371 -Lk1hbmlmZXN0 73372 -IGxo 73373 -IGl0ZW1MaXN0 73374 -X2Fkcw== 73375 -SW5zcGVjdGFibGU= 73376 -IFRvbGVkbw== 73377 -IERpc2FzdGVy 73378 -VXBkYXRlZEF0 73379 -KScpLA== 73380 -IFBBTg== 73381 -RmlsZUNob29zZXI= 73382 -IHl1YW4= 73383 -aXRt 73384 -INC10LPQvg== 73385 -IElibg== 73386 -SGF0 73387 -X3Vsb25n 73388 -YXBs 73389 -IFVydWd1YXk= 73390 -w6lueQ== 73391 -IENyYWlnc2xpc3Q= 73392 -ZG9jaA== 73393 -IGJpbGU= 73394 -IHByb2R1a3Q= 73395 -IGVsZWN0cm9seQ== 73396 -LkNvdXJzZQ== 73397 -IG1x 73398 -dW5jdHVhdGlvbg== 73399 -LyoqKioqKioqKioqKioqKio= 73400 -dWp1 73401 -TU1NTQ== 73402 -X0xFRw== 73403 -IG5ldXRyb24= 73404 -IHBsdXJhbGl0eQ== 73405 -ICsrJA== 73406 -Zm91bmRhdGlvbg== 73407 -LkNvbHVtblN0eWxl 73408 -IEhvb3Zlcg== 73409 -LkFDVA== 73410 -IEJyYXo= 73411 -bGVzc29ucw== 73412 -ZsO8aHI= 73413 -4KSC 73414 -IENsYXNzaWNz 73415 -cmFpZw== 73416 -IG1o 73417 -IGtldHRsZQ== 73418 -U3RyaWtl 73419 -ZXJkYWxl 73420 -RU5UQQ== 73421 -IFRhYmxlQ29sdW1u 73422 -IFNoYWtl 73423 -IFdG 73424 -IExpY2Vuc2luZw== 73425 -dWHDp8Ojbw== 73426 -IHNlY2FyYQ== 73427 -IG5ld1ZhbA== 73428 -U2VsZWNjaW9u 73429 -UHJlZmFi 73430 -ZmlnaHRlcg== 73431 -TGF1bmNoaW5n 73432 -JyI7DQo= 73433 -Lmxvbg== 73434 -LnV0Y25vdw== 73435 -IEh1bmRyZWRz 73436 -ZXN0ZWFk 73437 -IE92ZXJ3YXRjaA== 73438 -X0FGVEVS 73439 -IHJlbW5hbnRz 73440 -KS5c 73441 -IGxvYmJ5aXN0cw== 73442 -IHVuaW50ZW5kZWQ= 73443 -IOuQ 73444 -eXN6 73445 -IGxpYnJvcw== 73446 -LXBhZ2Vz 73447 -SU5URVJGQUNF 73448 -IGRldGVybWluaXN0aWM= 73449 -IFVOSVFVRQ== 73450 -IGV0dMOk 73451 -U2luZ2xlTm9kZQ== 73452 -CQkJCQkJCQ0K 73453 -LXN0YXQ= 73454 -IGhhc2hpbmc= 73455 -L2FjY2Vzcw== 73456 -dGVsbA== 73457 -CXVzZXJuYW1l 73458 -IERhdG9z 73459 -Qml0Q29udmVydGVy 73460 -Omhvc3Q= 73461 -IGFsdGVybmF0aW5n 73462 -IOKAi+KAiw== 73463 -IHdhdmVmb3Jt 73464 -PEVsZW1lbnQ= 73465 -IENhbnRvbg== 73466 -IGRlc3RhYw== 73467 -dGVudA== 73468 -LmdldE1heA== 73469 -IHN0ZW5jaWw= 73470 -IEFjcXVpc2l0aW9u 73471 -LkdlbmVyYXRpb25UeXBl 73472 -IE1FUg== 73473 -X2NvbWJpbmU= 73474 -IFtdLg== 73475 -X0JJVE1BUA== 73476 -bGRy 73477 -IGNhbnY= 73478 -IEpWTQ== 73479 -cGFycw== 73480 -IGRvd25oaWxs 73481 -RGV0YWlsc1NlcnZpY2U= 73482 -KE5BTUU= 73483 -IHJlanV2ZW4= 73484 -X3dpdGhpbg== 73485 -QWNjZXNzb3J5 73486 -IFPDqQ== 73487 -L2luYw== 73488 -IildCgo= 73489 -UHVibGljYXRpb24= 73490 -X3JvaQ== 73491 -IG1vYnM= 73492 -Lk5vQXJnc0NvbnN0cnVjdG9y 73493 -IGV2ZW50b3M= 73494 -LnZlbmRvcg== 73495 -X1NFTEVDVE9S 73496 -w6lmb25v 73497 -PSJb 73498 -IGxhYXQ= 73499 -IGJsdXJyZWQ= 73500 -IEJvcmRlclNpZGU= 73501 -eEZGRkZGRg== 73502 -X3dyaXR0ZW4= 73503 -IGplbnRl 73504 -L3Rpbnk= 73505 -Lndw 73506 -LnN0eWxlYWJsZQ== 73507 -IENoYXJnZXI= 73508 -IGJhdGhpbmc= 73509 -IFBhbmRh 73510 -w6lsaQ== 73511 -IHBhY2llbnRl 73512 -IGdpb2NoaQ== 73513 -IFZpZXdTdGF0ZQ== 73514 -Y2dp 73515 -LmxvZ2ljYWw= 73516 -RG9uYWxkVHJ1bXA= 73517 -LGNvcHk= 73518 -ZW1t 73519 -X0xpbms= 73520 -IGluc2lnbmlmaWNhbnQ= 73521 -ZmZtcGVn 73522 -L3BheQ== 73523 -X3F1aXQ= 73524 -SU9EZXZpY2U= 73525 -IEV4aXN0cw== 73526 -IGNvb2tz 73527 -anVuY3Rpb24= 73528 -IFRYVA== 73529 -KGVndA== 73530 -YW5pdQ== 73531 -X3BhcnRuZXI= 73532 -IGZhY3VsdA== 73533 -IFVuaWZpZWQ= 73534 -L3NiaW4= 73535 -IE5laA== 73536 -IEthemFraHN0YW4= 73537 -cG9zdGNvZGU= 73538 -IHZlZ2Fz 73539 -IHNlaW5lbQ== 73540 -fV0s 73541 -dGV0 73542 -LXBheW1lbnQ= 73543 -IENvbW1lbnRhcnk= 73544 -IGd1aWRlbGluZQ== 73545 -KTsk 73546 -IENvbnNvcnRpdW0= 73547 -57O757uf 73548 -dmlzbw== 73549 -IEJpbGxpbmc= 73550 -aWNpYXI= 73551 -IFR5cGVJbmZv 73552 -CXRyYW5z 73553 -PFRleHR1cmU= 73554 -YXRob20= 73555 -bGF1Z2hz 73556 -IGludGVyY2VwdGlvbnM= 73557 -KEVWRU5U 73558 -Rm9yZWNhc3Q= 73559 -VHJhcA== 73560 -dHJ4 73561 -IFdoaXRlcw== 73562 -c3VibWl0dGVk 73563 -YWxnbw== 73564 -IHRyYW5zcG9ydGVy 73565 -b3VuZGFyeQ== 73566 -IEluaGVyaXRz 73567 -IENvbmV4aW9u 73568 -LmNsaWVudFg= 73569 -CXByb2plY3Q= 73570 -aGVhcnRiZWF0 73571 -LW90aGVy 73572 -ICc7DQo= 73573 -w6ty 73574 -b3JwaW9u 73575 -KGNvcnM= 73576 -IEVMRUNU 73577 -IFBlcmU= 73578 -IHVzZU1lbW8= 73579 -ZXdyaXRlcg== 73580 -IHNxdWlydA== 73581 -L2V4dGVuc2lvbnM= 73582 -L2Fz 73583 -LkNMSUVOVA== 73584 -IGdvdXJtZXQ= 73585 -IGF1dG9Db21wbGV0ZQ== 73586 -UkVW 73587 -IGJyYWtpbmc= 73588 -X1NFTEVDVElPTg== 73589 -44Oh44Oz44OI 73590 -X2xpZmU= 73591 -X2dyb3VuZA== 73592 -X3Rlcg== 73593 -c25z 73594 -IFNQT1JU 73595 -kuGe 73596 -5rs= 73597 -VW5pcXVlSWQ= 73598 -IGRyaXA= 73599 -X0JST1dTRVI= 73600 -LW1ldGVy 73601 -ZW5kZXo= 73602 -IGV4aGF1c3RpdmU= 73603 -KFNL 73604 -IEJ1cmxpbmd0b24= 73605 -d29vcmQ= 73606 -KHBvdw== 73607 -IHNlYXJjaFRleHQ= 73608 -hYw= 73609 -aGVlbHM= 73610 -c3RlbGxlcg== 73611 -LnNpZw== 73612 -WU9VUg== 73613 -LmFsaQ== 73614 -IERhdGFDb2x1bW4= 73615 -IHByb2plY3ROYW1l 73616 -X2ZlY2hh 73617 -IHJlZnVuZHM= 73618 -IHRvcG8= 73619 -IENISUxE 73620 -IE1hcmJsZQ== 73621 -IGZvckNlbGw= 73622 -IHBlc3NpbQ== 73623 -IGNyaXNweQ== 73624 -aWZlc3R5bGVz 73625 -IG92ZXJkdWU= 73626 -b2xhcml0eQ== 73627 -IGFtYXTDuHI= 73628 -TWQ= 73629 -UFJFU1M= 73630 -IGluc3VyZXI= 73631 -b2NyYXQ= 73632 -IGZhY2lsaXRhdGVz 73633 -Lw0KDQo= 73634 -IGh1cmRsZXM= 73635 -X0hJ 73636 -TGV0dGVycw== 73637 -bWluZWNyYWZ0 73638 -YXh0ZXI= 73639 -eWs= 73640 -IGVjb27Ds20= 73641 -INC90LDRhw== 73642 -IFNXSVRDSA== 73643 -Q29uc3VsdGE= 73644 -IE5vcmE= 73645 -Q0tFUg== 73646 -X0NU 73647 -LmFwcHNwb3Q= 73648 -IC8vLS0= 73649 -CUJPT1NU 73650 -X2NvdXJzZXM= 73651 -IHdpbGxpbmdseQ== 73652 -66eM 73653 -ZmZk 73654 -ZmlsZXI= 73655 -IE1lYXN1cmVz 73656 -IGxlYXNlcw== 73657 -IERvcm90aHk= 73658 -Ol0u 73659 -c3Vic2NyaXB0aW9ucw== 73660 -IGNob2lz 73661 -IGFsYW4= 73662 -IGFicmly 73663 -LlBvcHVw 73664 -RXN0aW1hdGVk 73665 -IFBMQU4= 73666 -4LWN 73667 -IEVMRg== 73668 -IGRpc3RhbmNpbmc= 73669 -CWFuc3dlcg== 73670 -IHJ1Z3M= 73671 -S2k= 73672 -4Z+S4Z4= 73673 -R3VpbGQ= 73674 -ZXh0cmFz 73675 -Y3Bz 73676 -TW9ja3M= 73677 -IHRla3N0 73678 -Kmc= 73679 -LnJlcXVlc3RGb2N1cw== 73680 -IGFsdGVyYXRpb24= 73681 -IENhdGVnb3JpYQ== 73682 -aW1tZXJz 73683 -IERyb3Bib3g= 73684 -IEFkZHI= 73685 -5byV 73686 -ZGVwcw== 73687 -Lk1lc3NhZ2VCb3g= 73688 -ISwK 73689 -LmdldEI= 73690 -IG1pZ3JhdGVk 73691 -IEhvYmJ5 73692 -IE1n 73693 -LlZlcnRleA== 73694 -IGZvcmdpdmVu 73695 -IERlVg== 73696 -IHdlcmQ= 73697 -IEFyYWJpYW4= 73698 -IFNtb2tpbmc= 73699 -IHN0cmF3YmVycnk= 73700 -IENNUA== 73701 -ZGJs 73702 -IERIUw== 73703 -LWVycm9ycw== 73704 -LnBhZw== 73705 -IFJORw== 73706 -IHNoYXZl 73707 -IHR3ZWU= 73708 -IGFzc2VydE51bGw= 73709 -IERlbnNpdHk= 73710 -ZG9qbw== 73711 -YWlubWVudA== 73712 -IHBq 73713 -LllFQVI= 73714 -ICopKTsK 73715 -aWJyYXJpZXM= 73716 -SmV0cw== 73717 -RXhlY3V0aXZl 73718 -X2RlbnNl 73719 -LmdldENvbnRlbnRQYW5l 73720 -Y2hhbmRsZQ== 73721 -YWluYQ== 73722 -LXJlZmVyZW5jZQ== 73723 -IGxpYXI= 73724 -IEhFQUxUSA== 73725 -W3Rlc3Q= 73726 -LmlzbmFu 73727 -Q2hhcmxpZQ== 73728 -IHB1cHBlcg== 73729 -IGtpcg== 73730 -OmhpZGRlbg== 73731 -aXNWaXNpYmxl 73732 -IGtvbXQ= 73733 -IGFjcXVhaW50ZWQ= 73734 -IERydWlk 73735 -KENz 73736 -Lmxhc3RuYW1l 73737 -RFNB 73738 -IGRpc3NvbHZl 73739 -57yW5Y+3 73740 -VmFyaW91cw== 73741 -IERleA== 73742 -X2FuZ2xlcw== 73743 -L2FwaW1hY2hpbmVyeQ== 73744 -IGV4cGxvZGluZw== 73745 -KENoYXJTZXF1ZW5jZQ== 73746 -IEhpc3Bhbg== 73747 -KyspewoK 73748 -Lk1vZGVsU2VyaWFsaXplcg== 73749 -UVJTVFVWV1hZWg== 73750 -54K55Ye7 73751 -PXNldHRpbmdz 73752 -4KWB 73753 -UENT 73754 -IElOVEVSTkFM 73755 -IEhVR0U= 73756 -IG1pY3Jvc2NvcGU= 73757 -aXNBZG1pbg== 73758 -XHY= 73759 -LnJlcXVpcmVOb25OdWxs 73760 -0L7Qu9C+0LI= 73761 -aWNlcmNh 73762 -X1NFTlQ= 73763 -IGRlcGljdGlvbg== 73764 -IFVzZXJDb250cm9s 73765 -IE1lbW9y 73766 -IEFsbG9jYXRpb24= 73767 -IEJlZGZvcmQ= 73768 -IOabtA== 73769 -IHRvcm1lbnQ= 73770 -YXplZXJh 73771 -LlRvZGF5 73772 -IFJlZ2FyZGluZw== 73773 -X0VOQw== 73774 -X1JBTkRPTQ== 73775 -TG9nTGV2ZWw= 73776 -PVI= 73777 -IEdyZWVubGFuZA== 73778 -IHN0cmFpbmVk 73779 -IG1hZ25ldHM= 73780 -IGFsZXJ0Q29udHJvbGxlcg== 73781 -IENocm9uaWM= 73782 -X3JlZ2lzdGVyZWQ= 73783 -IGxpag== 73784 -IEVudHJ5UG9pbnQ= 73785 -IFJlZ2ltZW50 73786 -dWNpZA== 73787 -IENvdWxkbg== 73788 -IEFjdGluZw== 73789 -X3JheQ== 73790 -IG5hYg== 73791 -LXNlcGFyYXRlZA== 73792 -IHBubA== 73793 -Q29hY2g= 73794 -QVRZUEU= 73795 -IHN1cHBsZW1lbnRhdGlvbg== 73796 -YWNlcnM= 73797 -ZmxlZXQ= 73798 -SW5wdXRCb3JkZXI= 73799 -IFN0cnVjdHVyYWw= 73800 -IGRlaW5l 73801 -IGJyZXdlcmllcw== 73802 -YW5vaQ== 73803 -IHRyYW5zbGF0b3Jz 73804 -IGVpZ2VuZW4= 73805 -IGRhbmNlcw== 73806 -dGFt 73807 -IENvb3BlcmF0aW9u 73808 -X3JlcXVlc3RlZA== 73809 -IE1hZ2ljYWw= 73810 -CUxFRlQ= 73811 -ICIiKSwK 73812 -Ky0rLSstKy0rLSstKy0rLQ== 73813 -IE5vaXI= 73814 -IEVzdGltYXRl 73815 -IFRocmVhZFBvb2w= 73816 -IEhlY2s= 73817 -ICcqLg== 73818 -VHVya2V5 73819 -IHN1Y2NlZWRpbmc= 73820 -ZHJ1Zw== 73821 -dmlv 73822 -IHBvbmVy 73823 -IEphZA== 73824 -aXp6bHk= 73825 -ZXZlcnl0aGluZw== 73826 -IHt9KS4= 73827 -IEluc3RpdHV0ZXM= 73828 -IG51b3Zv 73829 -IGluaXRXaXRoVGl0bGU= 73830 -IGx1YUw= 73831 -b3duaWs= 73832 -IHRob3I= 73833 -IGtsYXI= 73834 -IG5vdG9yaW91c2x5 73835 -IGRvbmc= 73836 -ZW1lbnM= 73837 -X3Byb2plY3Rpb24= 73838 -X0dSRQ== 73839 -LmV5ZQ== 73840 -IHdhdGVyaW5n 73841 -IFRpaw== 73842 -b1M= 73843 -IFN0cmFuZ2Vy 73844 -ICANCg0K 73845 -cGFnaW5n 73846 -X2ludGVyc2VjdA== 73847 -IENvbG9uaWFs 73848 -TGlzYQ== 73849 -LnVubGluaw== 73850 -IG1pcA== 73851 -YW51dHM= 73852 -YW1hem9u 73853 -IElERU5U 73854 -c3Rhc3k= 73855 -Snd0 73856 -LS0tLS0tKy0tLS0tLSs= 73857 -IEVWUA== 73858 -Q29udGVudExvYWRlZA== 73859 -CUJJVA== 73860 -LnBhcmVudHM= 73861 -IGFsbG9jYXRpbmc= 73862 -IEdPTEQ= 73863 -fWA7Cgo= 73864 -QUxBUg== 73865 -IHByZWNpc2E= 73866 -RGlzdGluY3Q= 73867 -c2Vp 73868 -IHN1YnBvZW5h 73869 -IHBvbXA= 73870 -IFBvbG8= 73871 -Y29l 73872 -dmo= 73873 -LndvcmtmbG93 73874 -ZXN0cmU= 73875 -IGNvbm5leGlvbg== 73876 -aW1ldHlwZQ== 73877 -LlJvd0NvdW50 73878 -IERoYWJp 73879 -IGVtaXRz 73880 -LkJvcmRlclNpemU= 73881 -KHBvbGljeQ== 73882 -LG1lc3NhZ2U= 73883 -T25Jbml0 73884 -KShf 73885 -IGZpbmVy 73886 -W251bWJlcg== 73887 -IHNjcmlwdHVyZQ== 73888 -UmVmbGVjdA== 73889 -LXRvb2xiYXI= 73890 -KFBBVEg= 73891 -IEVOVFJZ 73892 -KC4uLikK 73893 -LWRvbWFpbg== 73894 -KHN0cmlw 73895 -KSgq 73896 -IGNvbnZleWVk 73897 -IGF0dGVudGl2ZQ== 73898 -w6hnZQ== 73899 -X0xE 73900 -IEdyYW50cw== 73901 -LWhpZ2hsaWdodA== 73902 -IGJyZXRocmVu 73903 -2YjZhA== 73904 -IGRlcXVldWVSZXVzYWJsZUNlbGxXaXRoSWRlbnRpZmllcg== 73905 -YXB1bHQ= 73906 -LmJvdHRvbUFuY2hvcg== 73907 -IG9wY2lvbg== 73908 -IG91dEZpbGU= 73909 -cmVhdGluZw== 73910 -ZGlu 73911 -X3NhbXBsZXI= 73912 -CWdsRW5hYmxl 73913 -cHR5cGU= 73914 -X0NPTkRJVElPTg== 73915 -LWVmZmljaWVudA== 73916 -Jm8= 73917 -IGpj 73918 -0Kc= 73919 -L0Zvcm0= 73920 -KWZyYW1l 73921 -IGJpbmdl 73922 -X2Nsb3N1cmU= 73923 -SU1B 73924 -KG5leHRQcm9wcw== 73925 -CWNk 73926 -IGdldE1lbnU= 73927 -IGdldFN1cHBvcnRBY3Rpb25CYXI= 73928 -IG1hbmlmb2xk 73929 -WlI= 73930 -Y2hhbmdlcg== 73931 -YXNzaW5n 73932 -ZGlzaA== 73933 -IE1vdQ== 73934 -Lm5ldGZsaXg= 73935 -IHBvc3Rjb2Rl 73936 -IHdvbWI= 73937 -IEFycw== 73938 -4oCmKQ== 73939 -IGxpbmVXaWR0aA== 73940 -RGVhbA== 73941 -YXJhcw== 73942 -IEdyYW50ZWQ= 73943 -IGhvYXg= 73944 -IGRpcmVjdGlvbmFs 73945 -LktleUNoYXI= 73946 -ID09Ig== 73947 -IFZlcmRl 73948 -X0tQ 73949 -IHN1cnJvZ2F0ZQ== 73950 -IERVSQ== 73951 -dXB5dGVy 73952 -IHBlbnNl 73953 -IFJBTkQ= 73954 -KGV4Yw== 73955 -IG1pc3VuZGVyc3Rvb2Q= 73956 -IENVVA== 73957 -IOS4rQ== 73958 -CXRp 73959 -X2luc2lkZQ== 73960 -IGJpY3ljbGVz 73961 -IGRlYW4= 73962 -ZGlyZWN0aXZl 73963 -LnBlZXI= 73964 -aWNpbmE= 73965 -X2l0ZXJz 73966 -IGltcGx5aW5n 73967 -Lm9idGFpbg== 73968 -IHBzeWNoaWF0cmlzdA== 73969 -dXNlclNlcnZpY2U= 73970 -ZWxpdmVyeQ== 73971 -CXBhcnQ= 73972 -IGh1cnJpZWQ= 73973 -IGJ1bQ== 73974 -IGhlcGF0aXRpcw== 73975 -amlk 73976 -J10+Owo= 73977 -IHVuY29udmVudGlvbmFs 73978 -IGZhc2Npc3Q= 73979 -IFBleQ== 73980 -6K+t 73981 -Jyl9PC8= 73982 -LkNsdXN0ZXI= 73983 -IEJpdENvbnZlcnRlcg== 73984 -ZWRhdGE= 73985 -zr/PhQ== 73986 -4pSC 73987 -QXBwQnVuZGxl 73988 -Lmh0dHBDbGllbnQ= 73989 -IGFwbw== 73990 -QUlOUw== 73991 -IFZG 73992 -X2dpZA== 73993 -IG9kZQ== 73994 -RVJSWQ== 73995 -IFJlY2VpcHQ= 73996 -IENhbmRsZQ== 73997 -IG1pc3Npb25hcnk= 73998 -IENyYW5l 73999 -IFNUQVRFUw== 74000 -Ym91dA== 74001 -YXlhcmFu 74002 -Li4uIiwK 74003 -IGl0aW5lcmFyeQ== 74004 -KGxhdGl0dWRl 74005 -IENPTlM= 74006 -L3NpZGViYXI= 74007 -U3BpZGVy 74008 -R1JJRA== 74009 -LmRlYnVnTGluZQ== 74010 -IGAn 74011 -LXllbGxvdw== 74012 -IHJlZmluZW1lbnQ= 74013 -IE1ha2V1cA== 74014 -IERhbm4= 74015 -KCk7DQoNCg0K 74016 -IG92ZXJjb21pbmc= 74017 -IEJhdHRlcg== 74018 -L3BhY2thZ2Vz 74019 -INCy0LjQtA== 74020 -IGFyeQ== 74021 -4oCdPw== 74022 -cmVsbGFz 74023 -IGdydXBvcw== 74024 -IFR5cGljYWw= 74025 -IE1vbnNhbnRv 74026 -SW50ZXJzZWN0aW9u 74027 -IHR5cmU= 74028 -PT09PT09Cg== 74029 -zq4= 74030 -OzsKCg== 74031 -IHRyaXZpYQ== 74032 -X3Rha2Vu 74033 -IHNtdWdnbGluZw== 74034 -IG5hcnJvd2Vk 74035 -4bqpbQ== 74036 -IHBhbGFicmE= 74037 -Y2Vh 74038 -cGFydGljdWxhcmx5 74039 -QWNjZXNzVHlwZQ== 74040 -IGNvbGU= 74041 -VG9GaXQ= 74042 -IHZlcmU= 74043 -IENPUw== 74044 -L3ZpZGVvcw== 74045 -ICgkKCIj 74046 -IGNyYW5l 74047 -Lmhhc01vcmU= 74048 -JHBhdGg= 74049 -aXZpc20= 74050 -IHN1cGVydmlzb3Jz 74051 -IEZsb3Jlcw== 74052 -cHJvZ3JhbXM= 74053 -LlppcA== 74054 -IGltcGFjdGluZw== 74055 -IG1vdG8= 74056 -IFRK 74057 -cGVnYXdhaQ== 74058 -X0tJTkQ= 74059 -X2ludGVyZmFjZXM= 74060 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 74061 -IExlYXZpbmc= 74062 -VGV4dFN0eWxl 74063 -YmVpdGVy 74064 -IFdpbm5pbmc= 74065 -LXBhcmFt 74066 -R2FyeQ== 74067 -IFN1bnM= 74068 -YWzEscWf 74069 -ZHVjaw== 74070 -IHRocmVhZElkeA== 74071 -IHBvZXRz 74072 -IHBsZWFkaW5n 74073 -IENvcmludGhpYW5z 74074 -ZmNj 74075 -YXdhaXRlcg== 74076 -Ki0= 74077 -IHBlcnNldmVy 74078 -IGFjdGl2aWRhZGVz 74079 -X291dGxpbmU= 74080 -LXBsYW4= 74081 -LnNjcm9sbFZpZXc= 74082 -cXVhdA== 74083 -IHNhbXN1bmc= 74084 -IGxldmVsaW5n 74085 -IHNwbGl0dGVy 74086 -X2dlb20= 74087 -IHByb21pbmVudGx5 74088 -IFNlZWRz 74089 -5Zyf 74090 -dWFpcw== 74091 -ZWZ1bGx5 74092 -SUVudW1lcmFibGU= 74093 -YWRkcw== 74094 -dmVyc2F0aW9ucw== 74095 -IGRpc2FibGVz 74096 -QU5EUk9JRA== 74097 -IFdlaXRlcg== 74098 -X0Zvcm1hdA== 74099 -X3NwbGl0cw== 74100 -IEFjdGl2ZVN1cHBvcnQ= 74101 -KGNzcw== 74102 -X21pY3Jv 74103 -c3RyaWtl 74104 -IENhdXNlcw== 74105 -IHZpc2libHk= 74106 -Q2FuY2VsYWJsZQ== 74107 -IFlvc2g= 74108 -IGRyYWluaW5n 74109 -IGNvbGk= 74110 -YXNsZXk= 74111 -IFJlc3BvbnNpYmlsaXRpZXM= 74112 -IFN1dHRvbg== 74113 -KnRoaXM= 74114 -U2hhcmVz 74115 -LWdyYXBo 74116 -IGVubGFyZ2Vk 74117 -Um91dGluZQ== 74118 -IGZyYW1lYnVmZmVy 74119 -IGFpcmZsb3c= 74120 -IHRyeA== 74121 -IExlaWdo 74122 -IEtlbnM= 74123 -KGhlYXA= 74124 -IHNwaWxsZWQ= 74125 -U0NBTEw= 74126 -IFZlbHZldA== 74127 -YWN0dWFsbHk= 74128 -X0VOQ09ESU5H 74129 -IFdvcm0= 74130 -KSl9Cg== 74131 -IERhbmdlcm91cw== 74132 -IHN1cGVyaW50ZW5kZW50 74133 -Lmxvb2s= 74134 -IHNoZWw= 74135 -L2Zz 74136 -U2FmZXR5 74137 -5a6L 74138 -LkRFRklORQ== 74139 -X2ZhY3RvcnM= 74140 -IHBhcnRpZG8= 74141 -IG9wdGltaXppbmc= 74142 -RG91YmxlQ2xpY2s= 74143 -LWNvbW1lcmNpYWw= 74144 -IGxvZ2ljYWxseQ== 74145 -Y3ljaA== 74146 -dXJ2ZQ== 74147 -wrU= 74148 -QUlMWQ== 74149 -IHJlYWN0aW5n 74150 -X0VYUFI= 74151 -a8O2 74152 -LmxvY2FsaXplZERlc2NyaXB0aW9u 74153 -IGFzdG91bmRpbmc= 74154 -IHBhc3RyeQ== 74155 -IGdsb3NzeQ== 74156 -IGJlaGF2ZXM= 74157 -L2Vj 74158 -IGNsaXBwZWQ= 74159 -IHByb3dlc3M= 74160 -IFVC 74161 -LyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 74162 -CWFscGhh 74163 -IGV4dHJhdmFn 74164 -IGZpbm5z 74165 -KFNvY2tldA== 74166 -IFVuc2FmZQ== 74167 -IHF1aWVyZQ== 74168 -X2VuY29kZWQ= 74169 -b2x1bWJpYQ== 74170 -IHphYg== 74171 -c3RyaWN0ZWQ= 74172 -IG1uaWU= 74173 -IE1PUw== 74174 -IGF0aGxldGljcw== 74175 -IEtlbmRhbGw= 74176 -IOyYpA== 74177 -QVZBSUxBQkxF 74178 -aW5veA== 74179 -X09QQ09ERQ== 74180 -IEl0ZW1UeXBl 74181 -IGNlbnRyaWY= 74182 -IGludGVyc3RhdGU= 74183 -X2Jvb2tz 74184 -LmRlbGl2ZXJ5 74185 -IExpc3Rl 74186 -b3JzaQ== 74187 -X3NlY3VyZQ== 74188 -Z3Jvd3Ro 74189 -IHZlbnRl 74190 -IHBzeWNob2xvZ2lzdHM= 74191 -IENDUw== 74192 -dWRlbmNl 74193 -IGNyYXdsZXI= 74194 -L21hbnVhbA== 74195 -IHRleHRTdHlsZQ== 74196 -IHBhbGluZHJvbWU= 74197 -IGNvbmR1Y3Rz 74198 -dGFibA== 74199 -V2l0aFVSTA== 74200 -L3JpZ2h0 74201 -IERyYQ== 74202 -Lk1haWw= 74203 -KHNlYw== 74204 -b2Z0d2FyZQ== 74205 -IHNldWw= 74206 -IHdyaW5rbGVz 74207 -X0ZX 74208 -QXk= 74209 -IEVybnN0 74210 -dW5iaW5k 74211 -IGNvbW1lbmQ= 74212 -X2hvb2tz 74213 -IE1vbmV0YXJ5 74214 -IFFR 74215 -dW5pdE9mV29yaw== 74216 -IEVudGl0eVR5cGU= 74217 -IGhvcm1vbmFs 74218 -LkZBSUw= 74219 -QFNsZg== 74220 -L2NoYW5uZWw= 74221 -c29ubw== 74222 -RGFucw== 74223 -X1JlZ2lzdGVy 74224 -SGFu 74225 -T1JC 74226 -SktMTU5PUA== 74227 -dmVudGVk 74228 -IGxvbmdzdGFuZGluZw== 74229 -IGJnQ29sb3I= 74230 -IDsp 74231 -IFJvYmJpZQ== 74232 -KCIuIg== 74233 -IGFqdXN0 74234 -LmhhbmRsZUNsaWNr 74235 -cmF0aW5ncw== 74236 -cHRlcg== 74237 -IGVyb3RpY28= 74238 -IEplbGx5 74239 -KioqKioqDQo= 74240 -LkRvZXNOb3RFeGlzdA== 74241 -CWJl 74242 -JHRlbXA= 74243 -Ij4mIw== 74244 -55u0 74245 -CVB1YmxpYw== 74246 -neyytA== 74247 -IEJ1aWxkaW5ncw== 74248 -LWFsb25l 74249 -LCdc 74250 -IHN3YXBz 74251 -IHBlcnBsZXg= 74252 -X3Byb2Nlc3NvcnM= 74253 -INC00LI= 74254 -IE5ZUEQ= 74255 -UENS 74256 -5q+P 74257 -IGhvamU= 74258 -RWRpdE1vZGU= 74259 -IHZ1bGdhcg== 74260 -IHZlcmRl 74261 -ICgpPT57Cg== 74262 -L2Zyb250ZW5k 74263 -IHRlbGVmb25l 74264 -IGxhbnRlcm4= 74265 -LnBhZ2VY 74266 -IER1ZA== 74267 -bGltaXRhdGlvbnM= 74268 -IG5vdGlmaWVy 74269 -IE1lc3NhZ2luZw== 74270 -IWltcG9ydGFudA== 74271 -IHN1cmdlb25z 74272 -KT0o 74273 -Rml4ZWRTaXpl 74274 -Llpvb20= 74275 -aW5hbg== 74276 -IGNyZWRz 74277 -IEJVRg== 74278 -LlN0YWNrVHJhY2U= 74279 -IHdhcnJhbnRlZA== 74280 -IHNvdXJjaW5n 74281 -IGNvbm5h 74282 -X0ZSRQ== 74283 -IHdvbGw= 74284 -IHJlZmluaW5n 74285 -X0FMTE9XRUQ= 74286 -X212 74287 -IFdvcmNl 74288 -IFNpbmNsYWly 74289 -Q2hlY2tzdW0= 74290 -IHVubG9ja3M= 74291 -IE1hcmtkb3du 74292 -IGZpc2hlcm1lbg== 74293 -RHVi 74294 -IEJvbm5pZQ== 74295 -ICAgICAgICAJCg== 74296 -IHZlcno= 74297 -Piw8Lw== 74298 -PjwhWw== 74299 -Wyc8ew== 74300 -amVj 74301 -IEVyZw== 74302 -cmF0aGVy 74303 -IHBhbGFicmFz 74304 -IFBBQ0tFVA== 74305 -bWlzZQ== 74306 -ZGFx 74307 -IE9rdG9iZXI= 74308 -KEdMRlc= 74309 -IEhlbnJp 74310 -IEZvdA== 74311 -IER1bw== 74312 -IE5FUw== 74313 -IHNhbHNh 74314 -IHVuYmlhc2Vk 74315 -QFNwcmluZ0Jvb3RUZXN0 74316 -IG9mZnM= 74317 -5YWs5Y+4 74318 -IGFtb3VudGVk 74319 -RnVsbFBhdGg= 74320 -IHF1YXQ= 74321 -IG1haWRlbg== 74322 -IFN1YnNldA== 74323 -IEFwcGxpY2F0aW9uRGJDb250ZXh0 74324 -bWlycm9y 74325 -bmV4 74326 -LnN0cmVldA== 74327 -c2V0UXVlcnk= 74328 -JHJlc3VsdHM= 74329 -YWRlcm8= 74330 -Z3Jlc3Nvcg== 74331 -X2J1Zw== 74332 -aXNzZXI= 74333 -IFNlYXJz 74334 -IGZpbGxDb2xvcg== 74335 -Lm1hc2tz 74336 -IERpYWJsbw== 74337 -X0FORFJPSUQ= 74338 -0J7QsQ== 74339 -IGZyZWFraW5n 74340 -IHJpbnNl 74341 -KHBrdA== 74342 -IGJvb2tsZXQ= 74343 -IHNhbmN0aW9uZWQ= 74344 -IHN0cmVhbWVk 74345 -dGFicGFuZWw= 74346 -IFJldHVybmluZw== 74347 -UGxhaW5UZXh0 74348 -TE9ZRUU= 74349 -YWxlc2Nl 74350 -0L7QutCw 74351 -IEZpeHR1cmU= 74352 -YXNzYWRvcnM= 74353 -IGRpc2JlbGllZg== 74354 -IEx1c3Q= 74355 -IHJhZGljYWxz 74356 -LkZlYXR1cmVz 74357 -X2luY2hlcw== 74358 -KHByaW1hcnk= 74359 -IEpNZW51SXRlbQ== 74360 -X3Rha2U= 74361 -IENva2U= 74362 -VW5pdE9mV29yaw== 74363 -IFdDSEFS 74364 -IGNvbnNjaWVudA== 74365 -b25lbnVtYmVy 74366 -UElORw== 74367 -YWJham8= 74368 -XSgi 74369 -LnNhbGVz 74370 -X2hlcmU= 74371 -IG9mZnNldFg= 74372 -dGFnTmFtZQ== 74373 -INmK 74374 -X1JpZ2h0 74375 -aWxpZw== 74376 -dGhlVmFsdWU= 74377 -b2NhcmQ= 74378 -IGNvbnN1bHRhbmN5 74379 -IGJsaWo= 74380 -Z29ybQ== 74381 -TmF2aWdhdGU= 74382 -xLFj 74383 -SWxsZWdhbEFyZ3VtZW50RXhjZXB0aW9u 74384 -X3Zl 74385 -LkNPTlRFTlQ= 74386 -dXJvcGVhbg== 74387 -LnJhZGlv 74388 -IGVudmlzaW9uZWQ= 74389 -IFNPTQ== 74390 -LnNk 74391 -QU5USVRZ 74392 -IENBTExCQUNL 74393 -IGhn 74394 -ZGVjcnlwdA== 74395 -566x 74396 -XFF1ZXVl 74397 -IE1JTEY= 74398 -IHJlY3Vyc2U= 74399 -IERhbnRl 74400 -LmdhbW1h 74401 -b3Jrcw== 74402 -KCIiKSkK 74403 -IEdyaW0= 74404 -Lm9wZW5n 74405 -IE1pY2hlbGU= 74406 -QW5hbHk= 74407 -IFBydQ== 74408 -X3JlZGlyZWN0ZWQ= 74409 -X3BhbA== 74410 -ZmFsbGJhY2s= 74411 -IOWtlw== 74412 -IGRpbm5lcnM= 74413 -R2VuZXJhdGluZw== 74414 -JCIs 74415 -aGlzdG9yaWM= 74416 -Z2V0U2ltcGxlTmFtZQ== 74417 -IE1pbGxpb25z 74418 -LWdsb2JhbA== 74419 -cm91dGluZw== 74420 -IGNvbnNvbGlkYXRl 74421 -IHJlY29pbA== 74422 -T2JqZWN0T2ZUeXBl 74423 -IGRlc3BlcmF0aW9u 74424 -QW55d2hlcmU= 74425 -IGdldE1vZGVs 74426 -X2tpbGw= 74427 -b2Jvb2s= 74428 -L2Rpc3BsYXk= 74429 -Ii8+Cgo= 74430 -IG1heW8= 74431 -INGB0L/QuNGB0L7Qug== 74432 -IGdvYWxpZQ== 74433 -eERG 74434 -IFByZXBhcmF0aW9u 74435 -IGRlcGVuZGFibGU= 74436 -LklOVkFMSUQ= 74437 -Li4uJw== 74438 -bmF0YWw= 74439 -bW9kdWxlTmFtZQ== 74440 -Y2FyYm9u 74441 -UEFM 74442 -IG1lZQ== 74443 -IGNhc2luZw== 74444 -6aG555uu 74445 -bmljYXM= 74446 -IEhhbW0= 74447 -IEJhYmU= 74448 -b3dhbmU= 74449 -IHN5bm9ueW0= 74450 -IFFpbg== 74451 -aW9j 74452 -ZW1vdGlvbg== 74453 -IGZlcm1lbnRhdGlvbg== 74454 -IGN1bXBs 74455 -IEVsZWN0cmljaXR5 74456 -KFJPT1Q= 74457 -dGVzdGVy 74458 -IEh1c2JhbmQ= 74459 -IEJhdQ== 74460 -X01BQ1JP 74461 -YWtlbmluZw== 74462 -ICAgICAgICAKICAgICAgICAKICAgICAgICAK 74463 -LmZpbg== 74464 -IENvbmZpZGVudGlhbA== 74465 -aWV6 74466 -TUJFUg== 74467 -IHNwZXJtYQ== 74468 -IEhQVg== 74469 -dHhu 74470 -Q09OVEFDVA== 74471 -LlRocm93 74472 -IG11cmFs 74473 -IFR3aXN0 74474 -KCZfX18= 74475 -IGpk 74476 -IGVtcG93ZXJtZW50 74477 -IGRpc3RpbnQ= 74478 -IGJvbWJpbmdz 74479 -T3V0Y29tZQ== 74480 -IHNob3J0ZW4= 74481 -5b6M 74482 -QUNDT1VOVA== 74483 -X2NvdmVyYWdl 74484 -ZW5jbw== 74485 -X3JlZmVy 74486 -c2V0TWVzc2FnZQ== 74487 -IHJlcGVyYw== 74488 -cHRpZGVz 74489 -IGRlaXR5 74490 -dWNoc2lh 74491 -KGh0 74492 -LnN1YnNjcmlwdGlvbg== 74493 -IHJlZGlzdHJpYnV0ZWQ= 74494 -IER5bmFzdHk= 74495 -X3Zj 74496 -LWZyYW1ld29yaw== 74497 -cnlmYWxs 74498 -IGdhdGluZw== 74499 -IExvcmVuem8= 74500 -b29kb28= 74501 -IGRpZ2VzdGlvbg== 74502 -IGZvb3Rpbmc= 74503 -CUhhc2hNYXA= 74504 -cmVhbERvbmFsZFRydW1w 74505 -IGFwYWNoZQ== 74506 -KHZhbG9y 74507 -IHBvaXNvbm91cw== 74508 -LlBlcm1pc3Npb24= 74509 -IHBhcmFtb3VudA== 74510 -d2VpdA== 74511 -bGxhbmQ= 74512 -IGh5cG90aGVzZXM= 74513 -IFByeQ== 74514 -IGhvbWVt 74515 -KERldmljZQ== 74516 -aW5kaWNl 74517 -ZXZh 74518 -cHJlc2VuY2U= 74519 -IEJlbnRsZXk= 74520 -IEVuZGluZw== 74521 -IGRvbWVzdA== 74522 -CXRw 74523 -CWVycm9ycw== 74524 -Y29ybmVy 74525 -bGRh 74526 -CgkJCQkK 74527 -X1BFUlNPTg== 74528 -IFNlcmdleQ== 74529 -IFBhcnNlcw== 74530 -LWZpY3Rpb24= 74531 -LkJhY2tncm91bmRDb2xvcg== 74532 -IHNvbW1lcw== 74533 -IGNvb2xlc3Q= 74534 -IHJ1YmJsZQ== 74535 -LmpvYnM= 74536 -IGRyb3duaW5n 74537 -YWRvcmFz 74538 -IHdpbmdlcg== 74539 -IEluY3JlYXNpbmc= 74540 -2YrYqQ== 74541 -QkJCQg== 74542 -KFJvbGU= 74543 -IG9kZGx5 74544 -RGV2RXhwcmVzcw== 74545 -LXV0aWw= 74546 -IFNoZW1hbGU= 74547 -cHJpbWl0aXZl 74548 -IGFmZmlybWVk 74549 -LnJldHVyblZhbHVl 74550 -LWxpdmU= 74551 -IEFjdGlvbkNvbnRyb2xsZXI= 74552 -w6ts 74553 -ZXJjdWxvc2lz 74554 -IHByYWt0 74555 -IGdlb3BvbA== 74556 -cGljcw== 74557 -Q0RD 74558 -LkZs 74559 -LnNpZA== 74560 -cmllYmVu 74561 -KHZhcnM= 74562 -K3NlbGY= 74563 -IGludGVyaW9ycw== 74564 -IEF1Z3VzdGluZQ== 74565 -IjpAIg== 74566 -IFN0ZWFsdGg= 74567 -IGdldENvbG9y 74568 -IEdlbnRsZQ== 74569 -fiI6Ig== 74570 -IHdoaW0= 74571 -KCc8Lw== 74572 -IFNTRQ== 74573 -IFZpb2xldA== 74574 -X2NyZWQ= 74575 -IGF0YQ== 74576 -IEF6ZXJiYWlqYW4= 74577 -ID8/Pz8/ 74578 -LmV2ZXJ5 74579 -KGNvbm5lY3Q= 74580 -IERyb25l 74581 -IHRvbGVyYW50 74582 -c3VidG90YWw= 74583 -X3NodWZmbGU= 74584 -dXN0YWluYWJpbGl0eQ== 74585 -cHJlZmVycmVk 74586 -IFNFWA== 74587 -IGNvbmdyZXNzbWFu 74588 -IG5hbW9ybw== 74589 -IGhvbm9yYWJsZQ== 74590 -IGFmdGVyRWFjaA== 74591 -IMW8eWM= 74592 -SEFN 74593 -LnRvbQ== 74594 -IGVsb25n 74595 -IFNlcmlvdXM= 74596 -LVNlbWl0aWM= 74597 -0KHRgg== 74598 -IGZsYW0= 74599 -dGVuZXI= 74600 -LlRFU1Q= 74601 -IFRSQUNL 74602 -IFBoaWxpcHM= 74603 -IEFyZW4= 74604 -IEhpY2tz 74605 -b2luZWQ= 74606 -IEZhaA== 74607 -aXNzZXVy 74608 -IGNpcmN1bWNpc2lvbg== 74609 -KHR3ZWV0 74610 -IHBvaWw= 74611 -IFNlZW4= 74612 -X01BUFBJTkc= 74613 -IGludmFyaWFibHk= 74614 -IEZ1c2U= 74615 -ICc/Jw== 74616 -PXBhc3N3b3Jk 74617 -IOuCmA== 74618 -IElIdHRw 74619 -c3R5cGU= 74620 -Zml0bmVzcw== 74621 -LlRhZ3M= 74622 -IOqwnA== 74623 -KERXT1JE 74624 -IHF1YQ== 74625 -IE1hcnZpbg== 74626 -Ik0= 74627 -LmlzQXV0aGVudGljYXRlZA== 74628 -Lmd1YXJk 74629 -KT8KCg== 74630 -CQkJCQkJCQkJCQkJCQkJCQkJCQ== 74631 -IFNoaXBz 74632 -IHNlbnNpdA== 74633 -fTsNCg0KDQo= 74634 -YWhhaGE= 74635 -IGxpZXV0ZW5hbnQ= 74636 -IEphZ3Vhcg== 74637 -IC8vLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 74638 -VUNF 74639 -SW5zcA== 74640 -YWludGVy 74641 -X3BvbHlnb24= 74642 -LkRvd24= 74643 -IHRleHR1cmVk 74644 -LnNldEFjdGlvbg== 74645 -b2dy 74646 -IHNjaWVudGlmaWNhbGx5 74647 -IHNocmluZQ== 74648 -IGNsb3VkeQ== 74649 -LkhvdXI= 74650 -UG9zdEJhY2s= 74651 -QVpZ 74652 -X2NhbmRpZGF0ZXM= 74653 -KFNlYXJjaA== 74654 -IGNvbW1pc3Npb25lcnM= 74655 -IEJpZW4= 74656 -IGRvY3RvcmFs 74657 -IEZlZWxpbmc= 74658 -X1ZFUlRJQ0FM 74659 -IEJk 74660 -bmdpbng= 74661 -IOWcqA== 74662 -X2FyZ3Y= 74663 -UlNB 74664 -IGVsZGVzdA== 74665 -LWhlYXZ5 74666 -Q09OTg== 74667 -IEh0dHBOb3RGb3VuZA== 74668 -LWNvbHVtbnM= 74669 -IE5QQ3M= 74670 -IGNhZmVz 74671 -IGfDqQ== 74672 -IHN0YWxscw== 74673 -IGZvcmtz 74674 -IHBvYmw= 74675 -U3RyZWFtcw== 74676 -IGJhc3RhcmQ= 74677 -IFJhcHRvcnM= 74678 -IEdyYW1teQ== 74679 -IEdlaA== 74680 -X1RpY2s= 74681 -KHByZWc= 74682 -IGxpcHN0aWNr 74683 -X3J1 74684 -PEg= 74685 -IMSRaQ== 74686 -LkNhcg== 74687 -IHNwYXJlZA== 74688 -bW9uaWM= 74689 -aW5jdGlvbnM= 74690 -QWZyaWNh 74691 -KGRpY3Rpb25hcnk= 74692 -ICoqKSY= 74693 -YGBg 74694 -X3ByZXNzdXJl 74695 -bWll 74696 -IFJvbWFuaWFu 74697 -L21hcms= 74698 -IG1haW50ZW5hbnQ= 74699 -IHRyZW4= 74700 -IFBvc3RncmVTUUw= 74701 -UkVMRUFTRQ== 74702 -SlBFRw== 74703 -IGRlZGljYXRl 74704 -TWFrZVJhbmdl 74705 -IHJvYm90aWNz 74706 -YWt0aXY= 74707 -JSUl 74708 -YWFy 74709 -dmlld01vZGVs 74710 -KG1hYw== 74711 -dWNoZXI= 74712 -IGRlYmVu 74713 -TG9jYWxpemF0aW9u 74714 -0L7Qt9Cy0YDQsNGJ0LDQtdGC 74715 -LnNldFRvb2xUaXA= 74716 -LmZhc3Rqc29u 74717 -IHBlcmVubmlhbA== 74718 -LWNoaWVm 74719 -a2lzaA== 74720 -IGF0dGlj 74721 -U3VidGl0bGU= 74722 -IFNsYW0= 74723 -IExpdGVyYXJ5 74724 -ZXJuZXM= 74725 -INGC0L7Qu9GM0LrQvg== 74726 -IHN0YXJ0QWN0aXZpdHlGb3JSZXN1bHQ= 74727 -LkVycm9yTWVzc2FnZQ== 74728 -YmluYXRpb25z 74729 -Ikw= 74730 -IGZvcmJpZA== 74731 -IGxvZGdlZA== 74732 -Lkxpc3RCb3g= 74733 -IFBTRA== 74734 -IGN1bHR1cmE= 74735 -VU5DVA== 74736 -Ik9uZQ== 74737 -IEd1aWxs 74738 -IEJhdHRhbGlvbg== 74739 -IGNhcmVnaXZlcnM= 74740 -IEtsbw== 74741 -QmVoaW5k 74742 -IHNlYXJjaGFibGU= 74743 -X0JPVU5E 74744 -Uk9D 74745 -IHN0ZXJlb3R5cGU= 74746 -IHByZXBlbmQ= 74747 -aW50ZXJzZWN0aW9u 74748 -QmFza2V0 74749 -KGxv 74750 -IGZpbGVJbmZv 74751 -IFVJU2Nyb2xsVmlldw== 74752 -ZWNlc3NhcmlseQ== 74753 -IENoZXM= 74754 -LWluc3RhbmNl 74755 -IGFwcGFydA== 74756 -IEFtYXI= 74757 -IHJvd0RhdGE= 74758 -IGF5dWRh 74759 -IGNhcmF2YW4= 74760 -X3BpY2tsZQ== 74761 -IGNoYWluaW5n 74762 -KV07Cgo= 74763 -IGJveGVk 74764 -YWVwZXI= 74765 -IEVWRVI= 74766 -eW50aGVzaXM= 74767 -LWZhc3Q= 74768 -IOuwsA== 74769 -5Y+v5Lul 74770 -IHZvbHVudGVlcmVk 74771 -IGV4aWc= 74772 -U0lERQ== 74773 -IFBob25lTnVtYmVy 74774 -dWxhaXJl 74775 -IEthZA== 74776 -IGRhcm4= 74777 -IHlhaw== 74778 -IEJsaW5r 74779 -LnNwaW5uZXI= 74780 -IG9yZGVhbA== 74781 -X2VuZW15 74782 -IGdldFM= 74783 -IEJvbw== 74784 -TGluZU51bWJlcg== 74785 -X0xPT0s= 74786 -RUxDT01F 74787 -IHNlYW1z 74788 -IHNhZ2Vu 74789 -aXNjbG9zZWQ= 74790 -KHJheQ== 74791 -W2dyb3Vw 74792 -UFRT 74793 -Lk5hdmlnYXRl 74794 -IE93bA== 74795 -IGRidXM= 74796 -IGltcGF0aWVudA== 74797 -IEd1cHRh 74798 -KG9iamVjdHM= 74799 -IGFwcmls 74800 -LXF1 74801 -IG91dHJhcw== 74802 -IFRIRU0= 74803 -IEVNQw== 74804 -RW1wbGVhZG8= 74805 -IGdydWI= 74806 -SUFN 74807 -IHZlbm9t 74808 -IHRyYW5zY2VuZA== 74809 -IHZpY3RvcmlvdXM= 74810 -IE1heWVy 74811 -INGC0L7QstCw0YA= 74812 -IEtlbGxleQ== 74813 -SW5wdXRHcm91cA== 74814 -IHJlZmlsbA== 74815 -V2l0aFR5cGU= 74816 -IGNoYXVmZg== 74817 -b2xkZW0= 74818 -X3RpZA== 74819 -IGZsdXNoZWQ= 74820 -XHN5c3RlbQ== 74821 -LnJhbmRyYW5nZQ== 74822 -IFBPU0lUSU9O 74823 -IFRlbmFudA== 74824 -Y29udmVyc2lvbg== 74825 -Y2FsbGluZw== 74826 -KCkpKSwK 74827 -0L7QvdCw 74828 -IHNpZGV3YXlz 74829 -IGxheA== 74830 -CXJlcA== 74831 -YWVwZXJuaWNr 74832 -IG5lZ2Vy 74833 -IEZseWVycw== 74834 -ICJALw== 74835 -dXBha2Fu 74836 -X2VsYXBzZWQ= 74837 -dHViZQ== 74838 -UG9zWA== 74839 -LnNleA== 74840 -IGzDpHNzdA== 74841 -IEdyYXZl 74842 -5Y+C 74843 -KGVtcA== 74844 -KHN0cnRvbG93ZXI= 74845 -Y29udmVydGVy 74846 -IFNwb25zb3JlZA== 74847 -KHdvcmtlcg== 74848 -IG1hdHJpbW9u 74849 -Q29tbWlzc2lvbg== 74850 -KGh3 74851 -X1NJR05BVFVSRQ== 74852 -bWVr 74853 -IGFsZ3VuYXM= 74854 -X0VU 74855 -aXN0cmluZw== 74856 -THY= 74857 -U2xpZGVz 74858 -IHdlYWtTZWxm 74859 -IHdr 74860 -IFppZw== 74861 -IHB1YnM= 74862 -IEJSQQ== 74863 -IGZsdW9yZXNjZW50 74864 -Y2Fycnk= 74865 -LmVyYg== 74866 -IEluaQ== 74867 -LkRyYXdTdHJpbmc= 74868 -IFNFUA== 74869 -dXR0ZXJz 74870 -2ZE= 74871 -Um95YWw= 74872 -IGNhYmJhZ2U= 74873 -IFN1aw== 74874 -XT49 74875 -IEVkaXNvbg== 74876 -IHNwZWN1bGF0ZWQ= 74877 -LmRvd25jYXNl 74878 -IHRwaA== 74879 -IMOD 74880 -IGd1bnNob3Q= 74881 -cnBt 74882 -IGZsdXR0ZXI= 74883 -IGFueA== 74884 -YXplcw== 74885 -UU9iamVjdA== 74886 -IEZhdm9y 74887 -IG1vZHVsZU5hbWU= 74888 -JnM= 74889 -bGVo 74890 -LldlaWdodA== 74891 -IFdBTA== 74892 -X1ZBUlM= 74893 -IFdhc3Nlcg== 74894 -IG91dGJvdW5k 74895 -IGVyZm9sZ3Jl 74896 -LnZhbG9y 74897 -KGxpZ2h0 74898 -IE1hZ251cw== 74899 -IHpvZWs= 74900 -eWg= 74901 -IHN0eWxlc2hlZXQ= 74902 -Pm0= 74903 -V2hpdGVzcGFjZQ== 74904 -IFsnLw== 74905 -CVJlcXVlc3Q= 74906 -X2luY3JlYXNl 74907 -LWRpc3RhbmNl 74908 -aWNvbG9y 74909 -aGNp 74910 -IEtJTkc= 74911 -UFg= 74912 -b2ls 74913 -ZW1pbmc= 74914 -bmFtZW50cw== 74915 -RGVmaW5lcw== 74916 -IFstLQ== 74917 -IHZhcmlvcw== 74918 -IFBSRVNT 74919 -LGF4aXM= 74920 -IENvbGxpZGVy 74921 -KX0KCg== 74922 -IGZvcmNpYmx5 74923 -IHN0YWF0 74924 -X1NUQU5EQVJE 74925 -IG9jY3VsdA== 74926 -IGJhcHRpc20= 74927 -IEN1bm5pbmdoYW0= 74928 -X2J1aWx0aW4= 74929 -Q1BG 74930 -W21heG4= 74931 -IFJIUw== 74932 -IE9uZXM= 74933 -KF86 74934 -IGluc2VjdXJpdHk= 74935 -LnJlZ2lzdHJhdGlvbg== 74936 -aW1wbGlmaWVk 74937 -IFN5bXBvc2l1bQ== 74938 -aHJlYWQ= 74939 -IHF1ZWxsZQ== 74940 -IGZyZW56eQ== 74941 -Q2FsaWJyaQ== 74942 -IFNQRUVE 74943 -b3Vp 74944 -KCldLAo= 74945 -YWNjb3JkaW5n 74946 -IG1jYw== 74947 -IGFzaWF0 74948 -IGFkamFjZW5jeQ== 74949 -IEFibGU= 74950 -IHNhbGRv 74951 -bm9zdGk= 74952 -IGRpbWU= 74953 -ZXRyYXRpb24= 74954 -IE1vZGlmaWNhdGlvbg== 74955 -IEhlcmI= 74956 -IHBsYWF0cw== 74957 -IGludGVycGVyc29uYWw= 74958 -IO2ZleyduA== 74959 -YXJtZQ== 74960 -IGNvbWVyY2lhbA== 74961 -IEJhdGVz 74962 -KGNhcmRz 74963 -LmdldENsaWVudA== 74964 -Lk5PUk1BTA== 74965 -CVRlc3Q= 74966 -ICAgICAgICANCiAgICAgICAgDQo= 74967 -IFJhem9y 74968 -d2Vpcw== 74969 -SVRIVUI= 74970 -IEVOVElUWQ== 74971 -YWdpdA== 74972 -IG1pbmVjcmFmdA== 74973 -cHJvcG9zYWw= 74974 -IHNhbHR5 74975 -YW5kcg== 74976 -IENvbmNsdXNpb24= 74977 -IHBydWRlbnQ= 74978 -IFtA 74979 -IFB1cHBldA== 74980 -aWdvbg== 74981 -IEdvdGhhbQ== 74982 -IGNoZWVycw== 74983 -IFNoYXk= 74984 -IGpp 74985 -IEdESw== 74986 -ZXhwZXJ0 74987 -IGZ1bmt5 74988 -IFphbQ== 74989 -W05VTQ== 74990 -RGVxdWU= 74991 -X1RXTw== 74992 -XHZpZXdz 74993 -IHByb2pla3Q= 74994 -IGRyb3duZWQ= 74995 -a2lkcw== 74996 -LnNoZWV0 74997 -IG5vbmQ= 74998 -IGNvdXJ0ZQ== 74999 -IC4uLgoKCgo= 75000 -IHBpY3R1cmVzcXVl 75001 -IHR1YmluZw== 75002 -KCkuIg== 75003 -amV0cw== 75004 -X1B1YmxpYw== 75005 -IEZhcnI= 75006 -IEFyZA== 75007 -T1VSU0U= 75008 -IGthZGFy 75009 -IFByb2dyYW1t 75010 -LmtleXdvcmQ= 75011 -CSAgICAgICAgICAgICAgICA= 75012 -aWVkYWRlcw== 75013 -YXRvbG9neQ== 75014 -IER1bmQ= 75015 -PWNvdW50 75016 -IHNsb3dkb3du 75017 -LSIs 75018 -LkZvcmVncm91bmRDb2xvcg== 75019 -UnVucw== 75020 -LlR5cGVPZg== 75021 -JGN1cnJlbnQ= 75022 -IHVwc2NhbGU= 75023 -CXVuaW9u 75024 -KGNoaXA= 75025 -dW1pZGl0eQ== 75026 -PVtdDQo= 75027 -IGhhcnQ= 75028 -ICRfWw== 75029 -eW5lYw== 75030 -LlVzdWFyaW8= 75031 -IG9jdGF2ZQ== 75032 -IHBvcnRyYXlhbA== 75033 -INC90L7QvNC10YA= 75034 -IE9jY3VweQ== 75035 -X25hbg== 75036 -IFNtYXJ0cGhvbmU= 75037 -aGluZA== 75038 -IHdpbmRzaGllbGQ= 75039 -IGxvbmVsaW5lc3M= 75040 -L2NoYXJ0 75041 -IGFjdGl2YXRlcw== 75042 -LnJpYmJvbg== 75043 -IGxhZ2k= 75044 -IHBhcmFjaA== 75045 -SHlwZXI= 75046 -c2NhbGVk 75047 -VGVz 75048 -IEJlZXQ= 75049 -IGRpc3NlY3Q= 75050 -IENpYw== 75051 -IH0sCgoK 75052 -PigpCgo= 75053 -LnN0dWR5 75054 -IGNvbnRyYXN0aW5n 75055 -WkVSTw== 75056 -IHR1bmE= 75057 -IENob3c= 75058 -X3Zh 75059 -ZmF2b3I= 75060 -W0luZGV4 75061 -IFBvd2VyU2hlbGw= 75062 -KHByb3Rv 75063 -JykpOgo= 75064 -X2Zvcm1hdHRlcg== 75065 -Q2hyaXN0b3BoZXI= 75066 -T3JOdWxs 75067 -Q0lTSU9O 75068 -X2NvbnN1bWVy 75069 -UGFzdGU= 75070 -KG5vbWU= 75071 -ZW50b24= 75072 -IHVucmF2ZWw= 75073 -X2Rvbg== 75074 -IHBhcmVudGhlc2Vz 75075 -IE5VSVQ= 75076 -L10= 75077 -IOKIpw== 75078 -c3RhY2xlcw== 75079 -L2NvbW1lbnQ= 75080 -dXR0aW5n 75081 -IHNsb3BweQ== 75082 -KFt7 75083 -LnNhdg== 75084 -dG9Kc29u 75085 -IOu5hA== 75086 -IFByYXR0 75087 -Lm1vZGlmeQ== 75088 -LklzQ2hlY2tlZA== 75089 -IHZlbmV6 75090 -IFNFVFRJTkdT 75091 -amF3 75092 -IGZpcmVzdG9yZQ== 75093 -IGNvbnNvcnRpdW0= 75094 -IGthYg== 75095 -IFN1cHBvcnRpbmc= 75096 -IFRoZXNpcw== 75097 -IG5vbmxpbmVhcg== 75098 -IHRleHRib3g= 75099 -LiIiIg== 75100 -IEVuZXJn 75101 -LkpPcHRpb25QYW5l 75102 -IGludGVycnVwdGlvbg== 75103 -w6h0cmVz 75104 -IHNoYWxl 75105 -IFBsYXllZA== 75106 -IHNvY2lhbGU= 75107 -WUdPTg== 75108 -X0JBVENI 75109 -IHRyaW1lc3Q= 75110 -IFByb2NlZHVyZXM= 75111 -IGF0dGVuZHM= 75112 -IiR7 75113 -ZXZhbHVhdGlvbg== 75114 -LlByb2dyZXNzQmFy 75115 -IEFsZXhhbmRyYQ== 75116 -Y2jDqQ== 75117 -X1NFUVVFTkNF 75118 -IGNyb2NoZXQ= 75119 -Um9z 75120 -IGlobmVu 75121 -ICIqKio= 75122 -IGFyb3Vz 75123 -IG1vZHVsdXM= 75124 -X0xJTlVY 75125 -U3RhY2tTaXpl 75126 -aWF0aW9uRXhjZXB0aW9u 75127 -Lk11dGFibGU= 75128 -IClb 75129 -IHBpaQ== 75130 -Zmlmbw== 75131 -X1BJQ0s= 75132 -UHVycG9zZQ== 75133 -KFN0dWRlbnQ= 75134 -IE5pY28= 75135 -ZXN6 75136 -L3Nt 75137 -IFBQUA== 75138 -W2lucHV0 75139 -5Y+Y 75140 -IGJsYXN0cw== 75141 -IE11dHVhbA== 75142 -cm9sbGV5 75143 -IHV0aWxpc2Vy 75144 -OlRoZQ== 75145 -5Z+6 75146 -LmRlY29kZXI= 75147 -IG9iamV0b3M= 75148 -IGF3YWtlbmluZw== 75149 -IEVubGlnaHQ= 75150 -CWFsaWdu 75151 -X3Jld3JpdGU= 75152 -L2N1cnJlbnQ= 75153 -IGRhcmF1Zg== 75154 -Q2FudGlkYWQ= 75155 -LG5w 75156 -IHZlbG9jaXRpZXM= 75157 -Q0xS 75158 -IG1pc2luZm9ybWF0aW9u 75159 -IHN0cmVhbWxpbmVk 75160 -IGdyb29taW5n 75161 -IGF6aQ== 75162 -b2xn 75163 -IGNvbnN0aXR1ZW50 75164 -IHdlZQ== 75165 -0YXQvtC00LjQvA== 75166 -IEFsb25zbw== 75167 -aWV0Zg== 75168 -Y3Rlcg== 75169 -IHRoZXJtb3N0YXQ= 75170 -KEND 75171 -IHN0YWNraW5n 75172 -X2NvbnZlcnRlcg== 75173 -IERpc25leWxhbmQ= 75174 -CWZpbGVz 75175 -SUNJ 75176 -X1RPUElD 75177 -CUVsZW1lbnQ= 75178 -YXJnYXM= 75179 -IFxA 75180 -YW5jb2Nr 75181 -IEJhc2VFbnRpdHk= 75182 -KCItLS0= 75183 -cmJyYWtr 75184 -IG5lZ2F0aXZlcw== 75185 -IHZ3 75186 -PWZvcGVu 75187 -Y2hlbWlzdA== 75188 -QXJjaGl2bw== 75189 -IGAu 75190 -IEZPVVI= 75191 -KGFp 75192 -VGFibGVXaWRnZXRJdGVt 75193 -PD8+Pg== 75194 -LnByZWQ= 75195 -VHJhaWw= 75196 -LWZhY3Rvcg== 75197 -IEltYWdlQnV0dG9u 75198 -cGVyaWE= 75199 -IENlbGVicmF0aW9u 75200 -LlJlc3BvbnNlQm9keQ== 75201 -dXJjaGFzZXM= 75202 -IGdldEtleQ== 75203 -IENyYWI= 75204 -IHFp 75205 -IFdpY2s= 75206 -IGNoYXN0 75207 -IC4uLi4uLg== 75208 -IGNvbWVueg== 75209 -IHNoYXJkcw== 75210 -IGTDqWNvcg== 75211 -IGhhbHZlcw== 75212 -UVVFTkNZ 75213 -IHBvd2VyaG91c2U= 75214 -TElORw== 75215 -Q2xhc3NMb2FkZXI= 75216 -Y2VudHJl 75217 -LXNlbmQ= 75218 -bWFo 75219 -IHNocmVkZGVk 75220 -IFRJRkY= 75221 -aW5rYQ== 75222 -LgoKCgoK 75223 -IGRlc2lnbmF0ZQ== 75224 -IE5pZ2h0bWFyZQ== 75225 -IEdlbmV0aWM= 75226 -X2NoYW5jZQ== 75227 -KGFuaW1hdGlvbg== 75228 -cXVpbGE= 75229 -X3NwZWNpZXM= 75230 -TkVZ 75231 -b3lzdGljaw== 75232 -cmVsbG8= 75233 -zqw= 75234 -IGRpdmlzaXZl 75235 -IFJFQw== 75236 -IHN0dW1ibGU= 75237 -KGZha2U= 75238 -IExhY2U= 75239 -YW50YWdlZA== 75240 -YWtlc3Q= 75241 -cHJvbW90aW9u 75242 -IEZvd2xlcg== 75243 -PWNlbnRlcg== 75244 -IENpdWRhZA== 75245 -UmFkaQ== 75246 -IFNsZWVwaW5n 75247 -dXRyb24= 75248 -IHF1b2k= 75249 -IFJBRA== 75250 -IGV4cG9uZW50aWFsbHk= 75251 -IEJyZWVk 75252 -IG1vbm9wb2w= 75253 -aGlnaGVzdA== 75254 -eG1sbnM= 75255 -SW50UHRy 75256 -IHR1dHRl 75257 -IFJlZnJpZ2Vy 75258 -IOmhtemdog== 75259 -IHpvbmRlcg== 75260 -bGJyYWtr 75261 -O2VsZW1lbnQ= 75262 -IEhlZA== 75263 -UmVsYXRpb25z 75264 -64U= 75265 -Q29ycmVv 75266 -5aC0 75267 -IE1pZ2h0eQ== 75268 -QU5HTw== 75269 -X2NvbXBpbGU= 75270 -LmdldENtcA== 75271 -IGludmFkZQ== 75272 -LnNwcmluZ2Jvb3Q= 75273 -IFR1bmU= 75274 -X3NuYXA= 75275 -X0ZFRUQ= 75276 -IGRlY2lwaGVy 75277 -PXNpemU= 75278 -X2ZyZQ== 75279 -IFRpbGxlcnNvbg== 75280 -0LjQutCw 75281 -dGlnaHQ= 75282 -IGN1bHByaXQ= 75283 -UlRM 75284 -IFBhcmU= 75285 -KHB1Yg== 75286 -ZWdvdg== 75287 -IHBvbnRv 75288 -IGNvbnN1bA== 75289 -SlNJbXBvcnQ= 75290 -IHZlcndlbmRldA== 75291 -IEJvb3N0ZXI= 75292 -5b6F 75293 -IGNhcnJvdA== 75294 -dmVyaWdl 75295 -KExQ 75296 -IHd4VA== 75297 -IGltcHJvcGVybHk= 75298 -Iik6DQo= 75299 -IHN1Y2U= 75300 -L21vZGFs 75301 -IElDVA== 75302 -LikuCgo= 75303 -X21hcmtz 75304 -IENhY2hlZA== 75305 -IEN1cnJpY3VsdW0= 75306 -QnM= 75307 -CUpPcHRpb25QYW5l 75308 -m4Q= 75309 -IGNvZ25pdGlvbg== 75310 -IE5lZ290 75311 -PXJlc3VsdA== 75312 -X0ZvbnQ= 75313 -YXJpbmU= 75314 -IGNvbnNwaWM= 75315 -IENhbGN1bGF0aW9u 75316 -IENFT3M= 75317 -LXRyYW5zcGFyZW50 75318 -IEJlcmVpY2g= 75319 -56iL5bqP 75320 -Lmh5 75321 -LkFsaWdu 75322 -IGhvcGVsZXNz 75323 -IGNvbG9tYg== 75324 -dXJiZWQ= 75325 -IFNBWA== 75326 -IGVpbno= 75327 -KHpvbmU= 75328 -IG11enpsZQ== 75329 -IHRyZXNwYXNz 75330 -IEFicmFtcw== 75331 -IGNvbXDDqXQ= 75332 -IFNhbmN0dWFyeQ== 75333 -IE5TVGV4dEFsaWdubWVudA== 75334 -IHN0YXY= 75335 -IHByYWdtYXRpYw== 75336 -c3RyZW5ndGg= 75337 -V2l0aE9wdGlvbnM= 75338 -LmJhbmQ= 75339 -YXBoYWVs 75340 -QXVzdHJhbGlhbg== 75341 -IE9TRXJyb3I= 75342 -TWFuY2hlc3Rlcg== 75343 -SWRl 75344 -XFJlc291cmNl 75345 -0L7QtNC10YDQtg== 75346 -IHppZQ== 75347 -SGFybmVzcw== 75348 -LlR3ZWVu 75349 -Y2Ftcw== 75350 -4pyU 75351 -LXNjYWxhYmxl 75352 -LW9r 75353 -IGpsb25n 75354 -IE9sc29u 75355 -IE9ha3M= 75356 -LnNsaW0= 75357 -IHPFgg== 75358 -IG5ld09iag== 75359 -LkludmVudG9yeQ== 75360 -IGtlbm4= 75361 -IG5pZ2h0bWFyZXM= 75362 -aXJjbGVz 75363 -Lm50 75364 -Z3Jlbg== 75365 -IFRFTg== 75366 -IFNjb3Rz 75367 -IERpc2FiaWxpdHk= 75368 -X21hbmlmZXN0 75369 -LnNpZGViYXI= 75370 -IHNodWZmbGVk 75371 -IGh1bWlsaXR5 75372 -LnRhcA== 75373 -IEdyYWlu 75374 -bm90aWNlZA== 75375 -77yJ44CC 75376 -X2hwcA== 75377 -IGRpbGF0aW9u 75378 -IGhhbmRpY2Fw 75379 -Z2V0RGF0ZQ== 75380 -IGR6aWHFgg== 75381 -JykuJzwv 75382 -cmVjb3Zlcg== 75383 -eXNp 75384 -KGdyYXk= 75385 -YWhrYW4= 75386 -IGludGVyZmVyaW5n 75387 -X1RPVUNI 75388 -X3JlZHVjdGlvbg== 75389 -QWx0ZXI= 75390 -IGN1Yw== 75391 -RXhwZXJ0 75392 -IEx1bXA= 75393 -Wzpd 75394 -IHJlbG9j 75395 -IGNvbmR1Yw== 75396 -Q2hhcnNldHM= 75397 -Lmxpc3RlbmVycw== 75398 -LWludmVyc2U= 75399 -IHN1bW1vbnM= 75400 -IMO6bmljbw== 75401 -IE9W 75402 -IFNpY2hlcg== 75403 -IEpGYWN0b3J5 75404 -LmdldEJvdW5kaW5nQ2xpZW50UmVjdA== 75405 -amg= 75406 -IHNrZWxldG9ucw== 75407 -IEFzaWFucw== 75408 -IEFNQw== 75409 -aXNlbGVjdA== 75410 -LmNsaWVudEhlaWdodA== 75411 -KGZy 75412 -SGFzRm9yZWlnbktleQ== 75413 -LnJlbGF0aXZl 75414 -INiu 75415 -IG11bHRpY3VsdHVyYWw= 75416 -X0NPTEw= 75417 -IG1pY3JvYmlhbA== 75418 -IGltcG9ydGFudGVz 75419 -U3BhaW4= 75420 -IGN5bGluZGVycw== 75421 -aWVuaWU= 75422 -X09XTkVS 75423 -KERJUw== 75424 -IGZhbmRvbQ== 75425 -KG54 75426 -IGFwbGljYWNpw7Nu 75427 -b2NhdG9y 75428 -ZXNzaWFu 75429 -IENsYXVkZQ== 75430 -IGludG9sZXJhbmNl 75431 -xYJlbQ== 75432 -IFNlbWFudGlj 75433 -Lk1pZGRsZVJpZ2h0 75434 -QVJFU1Q= 75435 -IHNpZXZl 75436 -xLHEn8Sx 75437 -aWNhYmxl 75438 -ZXJnaWM= 75439 -IGJhdHRsZWQ= 75440 -b3JiaXQ= 75441 -KXx8KA== 75442 -dWVsZQ== 75443 -IGZhc2NpbmF0aW9u 75444 -IGTDpQ== 75445 -IFRpZ2h0 75446 -X0lOQ1JFRg== 75447 -LklzU3VjY2Vzcw== 75448 -LE8= 75449 -IHN0w7hy 75450 -IHByZXNzdXJlZA== 75451 -LlRSVUU= 75452 -IFRob3VzYW5k 75453 -IGdlbWVpbnM= 75454 -IHpi 75455 -IHNwaXJpdHVhbGl0eQ== 75456 -IFpldXM= 75457 -IFBvd2VyZnVs 75458 -YmF0dGVyeQ== 75459 -aXN0ZXM= 75460 -IO2D 75461 -LnNoaXJv 75462 -IEhpcHA= 75463 -ZGVjbHR5cGU= 75464 -LmpmYWNl 75465 -LnRlbXBlcmF0dXJl 75466 -IG1hcnF1ZQ== 75467 -X2JhZw== 75468 -QXR1YWw= 75469 -cHJpY2luZw== 75470 -Q2xlYXJseQ== 75471 -X0Fic3RyYWN0 75472 -w6lr 75473 -YWhydW5nZW4= 75474 -SW5zdHI= 75475 -CQoKCg== 75476 -IGNoZXdpbmc= 75477 -IENvYWNoaW5n 75478 -JExBTkc= 75479 -bWFsbG93 75480 -IHNlcmlvdXNuZXNz 75481 -X2N1dG9mZg== 75482 -IFF1YXJ0ZXJseQ== 75483 -fScpCgo= 75484 -IikpKTsKCg== 75485 -6KeE 75486 -LlBvc2l0aXZl 75487 -LXBv 75488 -eGl0bw== 75489 -LlJhZA== 75490 -IGJyaXNr 75491 -IExpZmVjeWNsZQ== 75492 -5pWw5o2u5bqT 75493 -ZmF0YWw= 75494 -IHhwb3M= 75495 -LkRldGFpbA== 75496 -ZW5hbA== 75497 -TUFUQ0g= 75498 -IGhlZWQ= 75499 -IGFmcmljYW4= 75500 -RGFkb3M= 75501 -YmVyYXBh 75502 -IGhlbGY= 75503 -JywnJyw= 75504 -IGVudHJlcHJlbmV1cnNoaXA= 75505 -IGNlcnRz 75506 -ZWNl 75507 -PnI= 75508 -X2ZpeHR1cmU= 75509 -IHBvb2xpbmc= 75510 -IG1vZ2VsaWpr 75511 -IHNldERhdGU= 75512 -5pS/ 75513 -LWNvbXBsZXRl 75514 -X1JBRElP 75515 -IGt1bA== 75516 -IGdvYg== 75517 -X1NMQVZF 75518 -IGZ1cnJ5 75519 -IE5VSVRLQQ== 75520 -SUxJVElFUw== 75521 -IG5vY2hl 75522 -IGN1ZmY= 75523 -IGNvbnRlc3RhbnRz 75524 -IFdW 75525 -IHBhc3Nwb3J0cw== 75526 -IMWC 75527 -IE5haWw= 75528 -X2RlY2ltYWw= 75529 -YXN0bGU= 75530 -IFNvbGRpZXJz 75531 -UmVjaXBpZW50 75532 -IGNvdXJzZXdvcms= 75533 -IGltZQ== 75534 -IFNlYXRz 75535 -X0RM 75536 -IGNvbnN1bHRhdGlvbnM= 75537 -X0FEVg== 75538 -IElrZWE= 75539 -IG9maWNpYWw= 75540 -IHJlZ2ltZW50 75541 -IEJhdGhz 75542 -LXBpbg== 75543 -X0JVQ0tFVA== 75544 -QUJDREVGR0hJSktMTU5PUA== 75545 -Il0pKTsK 75546 -PE1lc2g= 75547 -Iix7 75548 -IGRlcml2ZXM= 75549 -4oCcRm9y 75550 -IFl1Z29zbA== 75551 -aXNFbmFibGVk 75552 -IHNvbGx0ZW4= 75553 -IHBldGl0aW9ucw== 75554 -b3ZlcmFsbA== 75555 -IGdldFRvdGFs 75556 -X0hJTlQ= 75557 -TWludXM= 75558 -IGFub21hbGllcw== 75559 -IFBpY2t1cA== 75560 -PT09Jw== 75561 -bGVpdHVuZw== 75562 -IERlaw== 75563 -WVNJUw== 75564 -LnNlc3Npb25z 75565 -IGNhcmM= 75566 -X0l0ZW1z 75567 -IGludGVybWl0dGVudA== 75568 -Lkpzb25Qcm9wZXJ0eQ== 75569 -IG1NYXA= 75570 -IEthaw== 75571 -YWluY29udHJp 75572 -X3NlZWs= 75573 -IHVuYW1l 75574 -X3B1dHN0cg== 75575 -RmQ= 75576 -TGltaXRlZA== 75577 -c25vdw== 75578 -IFBhdmlsaW9u 75579 -IEV4YWN0 75580 -IHBvc3Rpbmdz 75581 -CWRpc3Q= 75582 -PHN0ZGxpYg== 75583 -TGlnaHRz 75584 -IGZpbHRybw== 75585 -V29ya2Vycw== 75586 -IHN5c2xvZw== 75587 -R2lybHM= 75588 -IEd1bQ== 75589 -X3llYXJz 75590 -J319Cg== 75591 -IGjDpHQ= 75592 -Z2F5 75593 -KHByb2I= 75594 -ZWxsYXM= 75595 -IHdpbHQ= 75596 -Lm9wdGltaXpl 75597 -X0RVTVA= 75598 -KFhNTA== 75599 -IERYR0k= 75600 -IG3DqXRo 75601 -SVRJWkU= 75602 -ZWxlY3Ryb24= 75603 -LmN6 75604 -IHN1YnNldHM= 75605 -IHJlc3Bvc3Rh 75606 -IGJlYWQ= 75607 -wrsu 75608 -IE9TQw== 75609 -JnBhZ2U= 75610 -Z3Bz 75611 -YW5pYW4= 75612 -UHVycGxl 75613 -IGFjcm9ueW0= 75614 -Uk9XTg== 75615 -QXVkaXQ= 75616 -IGNvdXJpZXI= 75617 -YWxpZQ== 75618 -IFdhc3M= 75619 -IGF1ZGl0cw== 75620 -IFBPVg== 75621 -IEZhY2lhbA== 75622 -X3N0cmNtcA== 75623 -ICsl 75624 -ICAgICAKCg== 75625 -YCk7Cgo= 75626 -RUhJQ0xF 75627 -WyJA 75628 -LW5hdGlvbmFs 75629 -6ZuF6buR 75630 -6L2v6ZuF6buR 75631 -X2NvZGlnbw== 75632 -IHVucXVlc3Rpb24= 75633 -aWxtaW5ndG9u 75634 -cmVxdWVzdENvZGU= 75635 -IElX 75636 -LnN0cmF0ZWd5 75637 -IFNZTUJPTA== 75638 -IGdyw7bDnw== 75639 -X2JlaGF2aW9y 75640 -IHJlZnJlc2hUb2tlbg== 75641 -IG1vbmc= 75642 -aW1lbnRhcnk= 75643 -IFNob3Bz 75644 -KCc/ 75645 -X2hpZ2hsaWdodA== 75646 -X2xleA== 75647 -IGlsbHVtaW5hdGVk 75648 -IHBhbHA= 75649 -LWluc2VydA== 75650 -IHN0cml2ZXM= 75651 -IGZvcnRz 75652 -IGVtYm9kaW1lbnRz 75653 -bXBqZXM= 75654 -X1RPTw== 75655 -IGRyYWdnYWJsZQ== 75656 -IGltbWVyc2lvbg== 75657 -cGlucw== 75658 -IFJlZ2lzdHI= 75659 -IEZyZWVCU0Q= 75660 -X3hsaW0= 75661 -IFR1bHNh 75662 -U25hY2tiYXI= 75663 -L2RhdGU= 75664 -IGRhdm9u 75665 -IGF1dG9yZWxlYXNl 75666 -IHZhY2F0aW9ucw== 75667 -CQkgCQ== 75668 -aWNlcHM= 75669 -IFJhbXA= 75670 -IEN5bnRoaWE= 75671 -X3BvcHVsYXRpb24= 75672 -JCQk 75673 -IFRBUg== 75674 -ZW5nYQ== 75675 -IHB1cw== 75676 -IOW5 75677 -IHRpbWVzdGVw 75678 -TGlmZXRpbWU= 75679 -IGZpbG1lcg== 75680 -WVNU 75681 -IEdhemV0dGU= 75682 -IG91dHNpZGVy 75683 -IEVYUE9SVA== 75684 -R09SSVRITQ== 75685 -LmZsZXg= 75686 -IFJvb3Rz 75687 -KHBpeGVs 75688 -emN6ZQ== 75689 -YWlyaWU= 75690 -IG92ZXJsb2FkZWQ= 75691 -U1RSQUNU 75692 -IENvdXJpZXI= 75693 -44GW 75694 -Y29udGluZW50 75695 -RnJlZA== 75696 -IHNlbXA= 75697 -IFN0ZWxsYQ== 75698 -IGRvdWJ0ZnVs 75699 -YWRtaW5z 75700 -IG9wdGluZw== 75701 -TE9UUw== 75702 -IG1hbmlmZXN0bw== 75703 -LWZvbGRlcg== 75704 -X2Ryb3BvdXQ= 75705 -dXR1cmVz 75706 -w612ZWlz 75707 -YWNoaWV2ZW1lbnQ= 75708 -IGNveQ== 75709 -ZmFpdGg= 75710 -X0hBTEY= 75711 -aXJlY3RlZA== 75712 -IGNvbnRhdG8= 75713 -U2VtYXBob3Jl 75714 -UHNp 75715 -IHZpdGFsaXR5 75716 -IEZsYXRCdXR0b24= 75717 -SXRlbVR5cGU= 75718 -IGltcGVjYw== 75719 -IGJ1b3k= 75720 -dWlu 75721 -IHNreXJvY2tldA== 75722 -IFNsYXllcg== 75723 -IFJDTVA= 75724 -IFNldmVudGg= 75725 -X0ludGVyZmFjZQ== 75726 -IGZpZXJj 75727 -c3RhdGlvbnM= 75728 -IEdyYWY= 75729 -bGljZWQ= 75730 -IGVudW1lcmF0b3I= 75731 -Q29udGFpbmVycw== 75732 -IG9p 75733 -w4fDg08= 75734 -LXRvbg== 75735 -UkVQ 75736 -KGZsb3c= 75737 -LmNvb3Jk 75738 -R2Fi 75739 -IE1vcnBo 75740 -IFpvZQ== 75741 -IGhhcmJvdXI= 75742 -Lm1lc3NhZ2luZw== 75743 -X29wdGlvbmFs 75744 -IEJhc2VBY3Rpdml0eQ== 75745 -cmVzZW50ZXI= 75746 -IG5ieXRlcw== 75747 -IGNvdXJhZ2VvdXM= 75748 -PSE= 75749 -J0l0 75750 -IGZvcnM= 75751 -IGNvcnJpZG9ycw== 75752 -IEJFRU4= 75753 -IGZ1c2Vk 75754 -PWltYWdl 75755 -LkdyaWRWaWV3 75756 -IHNlbWVu 75757 -aWdyb3Vw 75758 -dXB0aW1l 75759 -IFhC 75760 -5o6S5bqP 75761 -IGludGVncmF0ZXM= 75762 -X09D 75763 -IGJhaWxvdXQ= 75764 -IHRlc3Rl 75765 -IG9jdXA= 75766 -YXVsZWQ= 75767 -X29kZA== 75768 -cGdh 75769 -IEFTVVM= 75770 -IFRTUg== 75771 -IG9jY3VwYW50cw== 75772 -U2V0VGl0bGU= 75773 -U2NoZWR1bGVycw== 75774 -IGJla29tbWVu 75775 -QnJpZ2h0 75776 -IE1haW5Gb3Jt 75777 -Xygn 75778 -RnJvbUFycmF5 75779 -IGluZGljYQ== 75780 -SEFORA== 75781 -T3JkZW4= 75782 -IFRlbXBlcg== 75783 -LnN0YXR1c1RleHQ= 75784 -cG9saXRpY2Fs 75785 -IFBlcmN5 75786 -44CCCgoKCgoK 75787 -LnNldFg= 75788 -Z2V0TGlzdA== 75789 -aG9sZXM= 75790 -UGl4 75791 -IG91dHNvdXJjaW5n 75792 -IG1lc3NhZ2VJZA== 75793 -IGdldFNlc3Npb24= 75794 -IFZJUg== 75795 -T2ZGaWxl 75796 -IFNwYXRpYWw= 75797 -LkZsb2F0RmllbGQ= 75798 -KShfXw== 75799 -IFN3aW1taW5n 75800 -QUNMRQ== 75801 -IHNlbnRpcg== 75802 -IHBsdW5nZWQ= 75803 -IGF1am91cmQ= 75804 -Z3VuYWthbg== 75805 -KHZvbHVtZQ== 75806 -IGNyYXRlcg== 75807 -Lnhscw== 75808 -woDCmQ== 75809 -UmVuZGVyV2luZG93 75810 -LnVzZXJtb2RlbA== 75811 -IGZ1bmN0b3I= 75812 -RG9tYWlucw== 75813 -aW50ZXJwcmU= 75814 -IGFibm9ybWFsaXRpZXM= 75815 -YXJnaW5n 75816 -RGVtb2NyYXRz 75817 -IHBhbG1z 75818 -4qCA 75819 -w7hk 75820 -KkE= 75821 -RnJvbURhdGU= 75822 -fFs= 75823 -IEFsdGVybmF0ZQ== 75824 -IHB1ZG8= 75825 -IGNvbmRlbnNlZA== 75826 -KHBsYW4= 75827 -ZGVsaXZlcg== 75828 -IGJ1bGxldGlu 75829 -J11dLA== 75830 -IGNyw6llcg== 75831 -LWlw 75832 -V3M= 75833 -IiIiLAo= 75834 -IGlrZWE= 75835 -IHZpc2l0ZQ== 75836 -IG11bHRpcw== 75837 -UmVzdWx0YWRv 75838 -IFBob3RvZ3JhcGhlcg== 75839 -Li4uJywK 75840 -IG1pZ2xpb3Jp 75841 -IFRocmVhZHM= 75842 -Z2V0U3R5bGU= 75843 -ZXJhw6fDo28= 75844 -PFRTb3VyY2U= 75845 -IEdpbmc= 75846 -J10iLA== 75847 -IHNpZ25hbGVk 75848 -U3VwcHJlc3NMaW50 75849 -IGR3b3Jk 75850 -IEh1bnRpbmd0b24= 75851 -IEFBUA== 75852 -QU5HTEVT 75853 -LmNyZWRlbnRpYWxz 75854 -c3dhZ2dlcg== 75855 -LWNvbnNvbGU= 75856 -Ii0t 75857 -LlRleHRJbnB1dA== 75858 -IE5PUlRI 75859 -IG5pZ2h0bHk= 75860 -LkZPTlQ= 75861 -IHF1b3RpZW50 75862 -5Lmf 75863 -IHNjaMO2bg== 75864 -IFBsYW5uZXI= 75865 -IHJlYWRsaW5l 75866 -IGNvbmZyb250aW5n 75867 -YH0= 75868 -SXRlbUNvdW50 75869 -CWFjdGl2ZQ== 75870 -IHLDqXBvbmQ= 75871 -ZWxtZXQ= 75872 -IGdpbW0= 75873 -LG5vbmF0b21pYw== 75874 -IEFDVElWRQ== 75875 -aGV1cmU= 75876 -L1ByaXZhdGU= 75877 -IG1lYw== 75878 -LlNlY3JldA== 75879 -IENJUw== 75880 -xYJ1Zw== 75881 -KHBlcmlvZA== 75882 -IGxsZWdhcg== 75883 -dXJpYQ== 75884 -RGVzY3JpYmU= 75885 -IHBhcmVqYQ== 75886 -IFZlZA== 75887 -LWVmZmVjdHM= 75888 -IFBhcnNpbmc= 75889 -LXJlc291cmNl 75890 -IGFiYQ== 75891 -ICosCg== 75892 -IGFuYXRvbQ== 75893 -ICgqKSg= 75894 -LXJlYWw= 75895 -IFZlbnR1cmVz 75896 -IFNoaWVsZHM= 75897 -IFVuaXZlcnNpdGllcw== 75898 -UFJFU0VOVA== 75899 -IFFMYXRpbg== 75900 -xaU= 75901 -IFdpbGV5 75902 -QWFyb24= 75903 -IHJhY2lhbGx5 75904 -IE5hZHU= 75905 -IGh0dHBSZXNwb25zZQ== 75906 -w610aWNh 75907 -IOuwqQ== 75908 -IGdyw6F0aXM= 75909 -5LuL 75910 -b21hcA== 75911 -IGFub24= 75912 -CXBvcA== 75913 -YXZhdGFycw== 75914 -IHN1YnBhcmFncmFwaA== 75915 -ZHpp 75916 -UHJvamVjdGlsZQ== 75917 -RFRW 75918 -bGlzdGVuaW5n 75919 -X3JlZ2VuZXJhdGlvbg== 75920 -IFNoZWx0ZXI= 75921 -PFZlcnRleA== 75922 -L21k 75923 -KGxl 75924 -IHZhaw== 75925 -c2VsZWN0ZWRJbmRleA== 75926 -X10= 75927 -IFN5bnRoZXRpYw== 75928 -YXBwSWQ= 75929 -IEZpcmVk 75930 -IHBhbXBo 75931 -X2xhdGVuY3k= 75932 -aW5maWxl 75933 -KGNyaXRlcmlh 75934 -c2VyaWFsaXphdGlvbg== 75935 -UkNU 75936 -CWV2 75937 -IFNDSA== 75938 -IE9wdGljYWw= 75939 -IHN0aXJyZWQ= 75940 -IFBvdGlvbg== 75941 -ZXRoaWNhbA== 75942 -Ojp7Cg== 75943 -IFBlbmd1aW5z 75944 -UEhZ 75945 -RGVjaXNpb24= 75946 -a2FydA== 75947 -IGV4cG9ydGVycw== 75948 -IFBvbHllc3Rlcg== 75949 -Y29udHJlcw== 75950 -IExhd3Nvbg== 75951 -IEVtcGxveWVy 75952 -IHNhc3M= 75953 -IGRvd250aW1l 75954 -IGJyb2tlcmFnZQ== 75955 -IFJvdGFyeQ== 75956 -IFdhaGw= 75957 -V0FSTg== 75958 -IHNldEFjdGl2ZQ== 75959 -dGVtcGw= 75960 -Q2hlZXJz 75961 -LXNoZWxs 75962 -Rml0bmVzcw== 75963 -IHF1aWw= 75964 -IGNsZWFuZXJz 75965 -IOeb 75966 -IE1pbGFubw== 75967 -LWFzc29jaWF0ZWQ= 75968 -fX19LAo= 75969 -UEZO 75970 -IG9uUGFnZQ== 75971 -X3N0cmVhbXM= 75972 -IHNjdWxwdHVyZXM= 75973 -IG5haWxlZA== 75974 -PXNj 75975 -6aaW6aG1 75976 -0LjQvNCy 75977 -Y29ubmV4aW9u 75978 -Sk9C 75979 -IEthcm1h 75980 -IFN3aWZ0VUk= 75981 -IERleg== 75982 -L1VJ 75983 -IOyZ 75984 -Z2V0Q2xpZW50T3JpZ2luYWw= 75985 -IHB1bmlzaGluZw== 75986 -IG9kZW5zZQ== 75987 -LHJpZ2h0 75988 -ZW5lcmF0aXZl 75989 -IFByb2JsZQ== 75990 -IEFwcFN0YXRl 75991 -IGRpc2Nsb3N1cmVz 75992 -IENhbnRlcg== 75993 -Y29tcG9zZXI= 75994 -dXBhdGVu 75995 -IHN1Y2Nlc3NvcnM= 75996 -Ij4nCg== 75997 -IHByZXNlcnZlcw== 75998 -Lm9wZW5k 75999 -X05vcm1hbA== 76000 -L2hy 76001 -UmFuZ2Vz 76002 -LGxvbmc= 76003 -CQkJCSAgICAgICAgICAg 76004 -cHJvZHVjdG9z 76005 -IGZseWVy 76006 -IEdydXBv 76007 -Tmlja25hbWU= 76008 -SGllcg== 76009 -IERFQQ== 76010 -U3ByaXRlcw== 76011 -CW1hc2s= 76012 -X3Jlc2VydmVk 76013 -LXNob3A= 76014 -Lm5vdGlmaWNhdGlvbnM= 76015 -IGRpdmlzaWJsZQ== 76016 -aW9zaw== 76017 -a2VyamE= 76018 -aW5ndA== 76019 -IEZpZnR5 76020 -IGFjY291bnRhbnQ= 76021 -IEV4cGxvcmF0aW9u 76022 -X2Jyb2FkY2FzdA== 76023 -IGV4dHJhb3JkaW5hcmlseQ== 76024 -IGtvdA== 76025 -IGNpcmN1bWZlcmVuY2U= 76026 -cm91Y2g= 76027 -W0Jvb2xlYW4= 76028 -Y3Jhd2xlcg== 76029 -L3JlbW92ZQ== 76030 -YXJlbGxh 76031 -IHNleGVz 76032 -SGludHM= 76033 -IGdhbWI= 76034 -IGRhcmVk 76035 -dGVzdGVk 76036 -X0tFRVA= 76037 -IGZpbHRyYXRpb24= 76038 -aWNrZXk= 76039 -IEluZmx1ZW5jZQ== 76040 -IHNwZWNpZmljaXR5 76041 -X0lEUw== 76042 -IFJvZG5leQ== 76043 -X0lSUUhhbmRsZXI= 76044 -T25FcnJvcg== 76045 -IHByZXZTdGF0ZQ== 76046 -aWVnZWw= 76047 -IExFU1M= 76048 -IGF3YWtlRnJvbU5pYg== 76049 -IExV 76050 -dW1hYmx5 76051 -b3J0YWxpdHk= 76052 -IG1hbmRhdGVz 76053 -CXZlcnNpb24= 76054 -IHBhcmVudE5vZGU= 76055 -IHBlc3Rz 76056 -IGNhc2M= 76057 -Y2VwdGFy 76058 -IFdvb2R5 76059 -ZXJlZQ== 76060 -X3Bm 76061 -LlBPUw== 76062 -aXN0cmE= 76063 -bGV3 76064 -WWFuZw== 76065 -IHN5c3RlbWQ= 76066 -IHJvYW0= 76067 -LkdyYXk= 76068 -IGNvbmR1 76069 -4oCUaW5jbHVkaW5n 76070 -VmlvbGF0aW9u 76071 -TWFob24= 76072 -IE1VU0lD 76073 -IFNpcmk= 76074 -IEVudGVyZWQ= 76075 -IGNlcnRhaW5z 76076 -ZWxhaA== 76077 -CU1haW4= 76078 -LkRhdGVGaWVsZA== 76079 -LkhlYWx0aA== 76080 -IEthc2ljaA== 76081 -IGNhbmluZQ== 76082 -PXJvb3Q= 76083 -dWRkbGU= 76084 -XGNvbW1vbg== 76085 -IFN1bHRhbg== 76086 -ZmluYW5jaWFs 76087 -IFFTcWw= 76088 -IGFzY2VudA== 76089 -IHBydWViYQ== 76090 -emllaHVuZw== 76091 -LmdldEVycm9y 76092 -IEdsb3JpYQ== 76093 -RWNobw== 76094 -X0NIT0lDRVM= 76095 -X2Vwcw== 76096 -L3Byb3ZpZGVy 76097 -UEhPTkU= 76098 -5YWz6Zet 76099 -IGNvbXByb21pc2luZw== 76100 -X0FQUFJP 76101 -UHJvY2Vzc0V2ZW50 76102 -IGJ5dGVBcnJheQ== 76103 -IENydWM= 76104 -wqg= 76105 -IGljaW5n 76106 -IFBDTQ== 76107 -dmVjdA== 76108 -QW15 76109 -IFZhY3V1bQ== 76110 -aW5jaWRlbnQ= 76111 -IHVzZXJu 76112 -emJlaw== 76113 -XSspLw== 76114 -IH19Ij48 76115 -IEdldERhdGE= 76116 -Y250bA== 76117 -IHNhZ3Q= 76118 -X1BSSU1BUlk= 76119 -IGxlcg== 76120 -IEZVQ0s= 76121 -IFN0YXJy 76122 -SUg= 76123 -w7ZycGVy 76124 -eW1z 76125 -XSldCg== 76126 -L3Rvb2w= 76127 -Y29tYmluYXRpb24= 76128 -IHRhbXA= 76129 -IEJlaXQ= 76130 -IE5JR0hU 76131 -IGFubsOpZQ== 76132 -KGFt 76133 -XFRyYWl0cw== 76134 -Olwi 76135 -IGNhcmdh 76136 -LmlkZQ== 76137 -IGRpa2tl 76138 -Q29tcGV0 76139 -IHNjb290ZXI= 76140 -IHhQb3M= 76141 -KGludGVycA== 76142 -IGhhc2ls 76143 -Y2xpZA== 76144 -IGhldXJlcw== 76145 -Z2xvbWVy 76146 -c2hhcmVz 76147 -77yMCgo= 76148 -cG9uZGU= 76149 -4bqjaQ== 76150 -X2R1cGxpY2F0ZXM= 76151 -c29uZ3M= 76152 -fV07Cg== 76153 -IFNuaXBlcg== 76154 -IFRodXI= 76155 -cm9wcA== 76156 -IGdydWVz 76157 -IG9yZXM= 76158 -dXNoaW1h 76159 -IHVzYWJpbGl0eQ== 76160 -6ZKf 76161 -L21lbWJlcg== 76162 -b2xkZW1vcnQ= 76163 -SXNBY3RpdmU= 76164 -R2V0RW51bWVyYXRvcg== 76165 -bXV4 76166 -V0lORE9XUw== 76167 -TmVnYXRpdmVCdXR0b24= 76168 -4Liz 76169 -LW1ha2Vycw== 76170 -44Kk44Oz 76171 -IEJlcm0= 76172 -QnlFeGFtcGxl 76173 -IFLDvGNr 76174 -U2hvd3M= 76175 -Z2hp 76176 -IElocmVy 76177 -IENydWQ= 76178 -Y2hlZg== 76179 -X2F1Yw== 76180 -IGFww7Nz 76181 -YW5rYW4= 76182 -IEtERQ== 76183 -SUxMUw== 76184 -IGFuZ2xhaXM= 76185 -LXJlZnJlc2g= 76186 -CXJhbmdl 76187 -eG1t 76188 -KGVkZ2Vz 76189 -IGFwcGVs 76190 -Ijt9 76191 -IGVkaQ== 76192 -IHN3b2xsZW4= 76193 -IGJ1dGNoZXI= 76194 -aWNpZGVz 76195 -aG91bmQ= 76196 -IF4o 76197 -IEV2YWx1 76198 -IGtleWJvYXJkVHlwZQ== 76199 -U1NJRA== 76200 -cm9iYXQ= 76201 -IG5paw== 76202 -IHN0cmF3YmVycmllcw== 76203 -XCJd 76204 -bm9zaXM= 76205 -TUVE 76206 -54g= 76207 -5LqU 76208 -aW1heA== 76209 -XEFubm90YXRpb24= 76210 -IG51cnU= 76211 -IE1pbmltYWw= 76212 -IHdvcmRwcmVzcw== 76213 -IGNvbGRlcg== 76214 -CXBhcnNl 76215 -L3N0cmV0Y2g= 76216 -5omn6KGM 76217 -cm9tb3NvbWU= 76218 -RElN 76219 -IHRlbnRhdGl2ZQ== 76220 -Ok5TVVRG 76221 -LGltZw== 76222 -IE1BVEVSSUFM 76223 -IEpldEJyYWlucw== 76224 -TGVnZW5kYXJ5 76225 -CXN0cm5jcHk= 76226 -IGRlZnM= 76227 -TnVtYmVyRm9ybWF0RXhjZXB0aW9u 76228 -IGJ5dGVjb2Rl 76229 -IHdpc3Nlbg== 76230 -X01PUkU= 76231 -oO2DnQ== 76232 -IENvZmY= 76233 -LkNvbmRpdGlvbg== 76234 -IGTDqXBhcnQ= 76235 -ZHNu 76236 -IHBhcmFtZXRybw== 76237 -XEw= 76238 -Lm5hbm9UaW1l 76239 -Qk9UVE9N 76240 -LldoYXQ= 76241 -64Q= 76242 -IERpeA== 76243 -X0RB 76244 -KENvbnRhaW5lcg== 76245 -YXlhcg== 76246 -RmxleGlibGU= 76247 -LlJheWNhc3Q= 76248 -IEVkd2lu 76249 -W3VybA== 76250 -wpI= 76251 -LnN0cm9rZVN0eWxl 76252 -IFBvbHlub21pYWw= 76253 -aWxpdGF0aW5n 76254 -IFFWQm94TGF5b3V0 76255 -KHJlcA== 76256 -LnZu 76257 -LWFzc2V0cw== 76258 -Q0hBU0U= 76259 -IEVzc2VudGlhbHM= 76260 -anlsbGFuZA== 76261 -IGF4cw== 76262 -IFRyZW0= 76263 -Lm1haW5sb29w 76264 -IFdJTkRPV1M= 76265 -LlJFUVVFU1Q= 76266 -IHJlaW50 76267 -IExpYnJl 76268 -Y2hlb24= 76269 -IGd1ZXJy 76270 -CU5kckZjU2hvcnQ= 76271 -LnNvZnRtYXg= 76272 -IEFzdXM= 76273 -LXNjb3Jl 76274 -IEpPSE4= 76275 -PlN0YXR1cw== 76276 -PkVkaXQ= 76277 -IENhbWU= 76278 -IEFzaGU= 76279 -X3VzaW5n 76280 -IExvbmU= 76281 -IGxlc2Vu 76282 -IHJldmVyc2luZw== 76283 -bmdyeA== 76284 -LnNpZ25hdHVyZQ== 76285 -LUFzc2Fk 76286 -L25hdGl2ZQ== 76287 -X3JhdGluZ3M= 76288 -IG55YQ== 76289 -IGFkaWRhcw== 76290 -KG9wdGlvbmFs 76291 -Il0o 76292 -IHJlY3VycmVuY2U= 76293 -IEJNUA== 76294 -z4w= 76295 -X2dw 76296 -Ij5c 76297 -X3dyb25n 76298 -eXBz 76299 -LlByb3h5 76300 -X1VEUA== 76301 -UXRDb3Jl 76302 -TGlua2VkSW4= 76303 -IGNhdmVybg== 76304 -IHNww6ljaWFs 76305 -X3dpcmU= 76306 -IG5hbm9w 76307 -LmJhbGw= 76308 -IHJlZHVjZXJz 76309 -IG1haWxlZA== 76310 -ZG9uZw== 76311 -IG9wcG9zZXM= 76312 -IEhhbnNvbg== 76313 -IFNhdHVyZGF5cw== 76314 -YWNvbW1lbnQ= 76315 -X01ldGFEYXRh 76316 -IEdhbGFjdGlj 76317 -KCIvIik= 76318 -IENsZWFuZXI= 76319 -X1RFUk0= 76320 -IGNsYXJv 76321 -Lk9VVA== 76322 -5a6h 76323 -IHNsaWs= 76324 -IGplZG5haw== 76325 -SGFuZGxlckNvbnRleHQ= 76326 -IGlycmFkaQ== 76327 -ICAgICAgICAgICAgICAgICAgICAgICAgIAo= 76328 -LnRpZ2h0 76329 -QnJlYWRjcnVtYg== 76330 -ZnJleQ== 76331 -IOqwneyytA== 76332 -bGJyYWNl 76333 -TEVHQUw= 76334 -LWd1bg== 76335 -IEJsb2dz 76336 -IFNoaXJsZXk= 76337 -IFB1bmU= 76338 -dXJzaW9ucw== 76339 -IHN1YnRyYWN0aW9u 76340 -ICoqKgo= 76341 -YXJtYWN5 76342 -IHNhbXQ= 76343 -PSIpLg== 76344 -IHBlcm1pc3NpYmxl 76345 -KHJk 76346 -IFdBVEVS 76347 -IHByb2Zlc2lvbmFs 76348 -IGhhbmRib29r 76349 -IG1vdXJuaW5n 76350 -YXJlZmE= 76351 -IGFzbg== 76352 -aXNleA== 76353 -IGNvbnRlbnU= 76354 -IFVOQw== 76355 -LmdldFByaWNl 76356 -IFB1bXBraW4= 76357 -LwoKCg== 76358 -IGNvc2luZQ== 76359 -IG5pZWQ= 76360 -IEJyYWtl 76361 -RGF0YVVSTA== 76362 -IERhdGFHcmlkVmlld0NlbGxTdHlsZQ== 76363 -IFJldHVybmVk 76364 -ZXdvb2Q= 76365 -aXF1w6k= 76366 -IGJsZWFr 76367 -IHdlYmhvb2s= 76368 -LlRoZXk= 76369 -YXJi 76370 -TEFOR0FETQ== 76371 -X29yZGVyZWQ= 76372 -IHByYW5r 76373 -Lk5ld1JlcXVlc3Q= 76374 -IGxpdGVyYWxz 76375 -J30+Cg== 76376 -c2VyaWFsaXplZA== 76377 -a3Rvcg== 76378 -KHJ4 76379 -IGdldFk= 76380 -CVN0cmluZ0J1ZmZlcg== 76381 -KHNsaWNl 76382 -cmJyYWNl 76383 -ZW1lbnRv 76384 -IGxhbmM= 76385 -RGVwbG95bWVudA== 76386 -IGNvbmNlbnRyYXRpbmc= 76387 -U2tldGNo 76388 -IGJyaWdodGx5 76389 -QmVnaW5uaW5n 76390 -IERhaA== 76391 -VGs= 76392 -SW5zZW5zaXRpdmU= 76393 -IHNhYmU= 76394 -KE1vZHVsZQ== 76395 -IGNlZGFy 76396 -X2NvbnRpbnVl 76397 -IHdpdGhPYmplY3Q= 76398 -IGNvbHVtbmE= 76399 -IENhbGRlcg== 76400 -INC/0L7QvA== 76401 -X3NvZnRj 76402 -c2hhbGVk 76403 -ZXJ0YXRpb24= 76404 -CSAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 76405 -OkAiIg== 76406 -IGZhw6dvbg== 76407 -dXN0dW0= 76408 -c3Rr 76409 -X0NSQw== 76410 -b2R6aQ== 76411 -IGFzY2VuZA== 76412 -Zmdhbmc= 76413 -IHByZWZhYg== 76414 -IGZpbmRldA== 76415 -Oicr 76416 -5Y2V5L2N 76417 -dW1ibGVkb3Jl 76418 -LmludmFsaWRhdGU= 76419 -IHRvaQ== 76420 -YW5nZXBpY2tlcg== 76421 -X0FJ 76422 -aGls 76423 -U2VhdA== 76424 -IHBpc3Rvbg== 76425 -Zmli 76426 -X2JsdWVwcmludA== 76427 -44K4 76428 -X1JlY29yZA== 76429 -cmV0cw== 76430 -RnJhbg== 76431 -IENhaXQ= 76432 -IHBlbGlj 76433 -IGRuYQ== 76434 -IHVwZGF0ZVRpbWU= 76435 -IC9eWw== 76436 -IHJhbGxpZWQ= 76437 -IEhpbWFs 76438 -U1NJ 76439 -X3BsYW5lcw== 76440 -IE91dHN0YW5kaW5n 76441 -QXBwbGljYXRpb25CdWlsZGVy 76442 -c3R1ZA== 76443 -X2xvY2F0b3I= 76444 -IGFib2xpdGlvbg== 76445 -ICgkKQ== 76446 -amVybmU= 76447 -IEFBQw== 76448 -L3dpbmRvd3M= 76449 -LUNhbA== 76450 -X1NFQ09ORFM= 76451 -ICcnfQo= 76452 -w6FueQ== 76453 -IHl1bW15 76454 -5omL5py65Y+3 76455 -IFZHQQ== 76456 -aWxhdGU= 76457 -IFN1cnZlaWxsYW5jZQ== 76458 -CUd0aw== 76459 -8J+Y 76460 -IHNoaW1tZXI= 76461 -YWx0ZXJuYXRl 76462 -Rm9yU2VndWU= 76463 -dWVzdHJh 76464 -LWNvdmVy 76465 -YXNs 76466 -IEluc2V0cw== 76467 -bGlqYWg= 76468 -OlM= 76469 -CWNhdGVnb3J5 76470 -IGZq 76471 -w61saWE= 76472 -IE1BRA== 76473 -QGpz 76474 -5p8= 76475 -IHBvb2xlZA== 76476 -IHRyZWF0aWVz 76477 -IEJpaw== 76478 -IEhhemVs 76479 -QWxsb2NhdGU= 76480 -IGFpcnBsYW5lcw== 76481 -IHNlcm1vbg== 76482 -IFBvc2l0aW9ucw== 76483 -IE1BSUw= 76484 -U3RvcHBpbmc= 76485 -YXZvcmVk 76486 -KFRlbXA= 76487 -IGNoZWF0cw== 76488 -LnVzZXJJRA== 76489 -IHB1dGE= 76490 -LXl5eXk= 76491 -VWlUaHJlYWQ= 76492 -IG9mc3RyZWFt 76493 -XFNlZWRlcg== 76494 -IENvdHRhZ2U= 76495 -IF4K 76496 -IEFMVEVS 76497 -IHF1YW50aWZ5 76498 -cmVpYnVuZw== 76499 -IG5lY2Vzc2l0aWVz 76500 -LkxvY2FsRGF0ZQ== 76501 -IOaXpQ== 76502 -cGljdHVyZXM= 76503 -IGNydWQ= 76504 -5pyo 76505 -IGRvd250dXJu 76506 -YWN0b3Jpbmc= 76507 -IERlcm0= 76508 -IGVzdHJ1Y3Q= 76509 -IE11c2lr 76510 -IG1seA== 76511 -Lm1ham9y 76512 -Lkh0dHBTZXNzaW9u 76513 -Pzw= 76514 -eWVhaA== 76515 -IG1vam8= 76516 -IFVuaXR5RWRpdG9y 76517 -IHJha2U= 76518 -X3R3ZWV0 76519 -IHJhZGlvQnV0dG9u 76520 -IERvbWluaW9u 76521 -YXNTdHJpbmc= 76522 -b3p5 76523 -IHZvZGth 76524 -b2dsb2I= 76525 -IEFsdW1uaQ== 76526 -YmFsYW5jZXM= 76527 -X21hbnVhbA== 76528 -LmxvYWR0eHQ= 76529 -X2ZyaWVuZHM= 76530 -IFhtbERvY3VtZW50 76531 -W2ZpcnN0 76532 -S2V5Q29kZQ== 76533 -IHBvZXRpYw== 76534 -bWluYQ== 76535 -IG9wY2lvbmVz 76536 -5omT 76537 -X3N1cHBsaWVy 76538 -LkZyb21SZXN1bHQ= 76539 -X2Rpc3RyaWN0 76540 -IEdhbGE= 76541 -LnF0 76542 -IGNvbnRyYWN0dWFs 76543 -YWNvbnM= 76544 -LWFuY2hvcg== 76545 -IHl1cA== 76546 -IHVuYW5zd2VyZWQ= 76547 -IG1heGxlbg== 76548 -RXJyTXNn 76549 -LXNu 76550 -IGh5cG5vdA== 76551 -X1dN 76552 -KCldWw== 76553 -IGRlc2VydmluZw== 76554 -b3dtZW50 76555 -KFJhbmRvbQ== 76556 -IHZldG9y 76557 -IElTVA== 76558 -0LDQvdC0 76559 -LWxhbmc= 76560 -IHNpaw== 76561 -Y3JlYXNpbmc= 76562 -IHBvcnRhbHM= 76563 -IEJ1bGxkb2dz 76564 -cHJvbW8= 76565 -IHByb3Zva2Vk 76566 -XX07Cg== 76567 -IEliaWQ= 76568 -ZXJnbGFzcw== 76569 -X1dJRkk= 76570 -YXBwcm9wcmk= 76571 -IHJlZGVzaWduZWQ= 76572 -IC8vLS0tLS0tLS0tLS0tLS0tLQ== 76573 -emlr 76574 -JG8= 76575 -dWx0b24= 76576 -IFJlbGF0aXZlcw== 76577 -IG1ldHJvcw== 76578 -IG1lbnRvcmluZw== 76579 -YXTEgw== 76580 -dXNobWFu 76581 -IGluaGVyaXRz 76582 -IFJ0 76583 -L3ByZWZlcmVuY2Vz 76584 -aW1lZA== 76585 -Sk9JTg== 76586 -KGludGVyZmFjZQ== 76587 -IGFkZXB0 76588 -IE9mZmVuc2l2ZQ== 76589 -IEFHUkU= 76590 -b25pYW4= 76591 -LnBhcnNlcnM= 76592 -IHBhc3NwaHJhc2U= 76593 -IHVuc2VyaWFsaXpl 76594 -VmlzaXRlZA== 76595 -IGdldFByb3BlcnR5 76596 -IG5vYw== 76597 -ZWRhZA== 76598 -ICMtfQoK 76599 -dmlkYQ== 76600 -c29sdmVy 76601 -IE1vcmFsZXM= 76602 -IGt2aW5uZQ== 76603 -IEFjY2lkZW50 76604 -IHZldXQ= 76605 -IG1pc2d1aWRlZA== 76606 -IFJldmVsYXRpb24= 76607 -IHJhcGlkZQ== 76608 -cHVuaw== 76609 -Iy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 76610 -T2JqZWN0SWQ= 76611 -YWJpbmV0 76612 -ZXh0cmFjb21tZW50 76613 -IGJ1bm55 76614 -IERlZmVycmVk 76615 -dXR0YQ== 76616 -dWFl 76617 -YnVzdGVycw== 76618 -IFNvaWw= 76619 -R1NU 76620 -LkN1cnJlbnRSb3c= 76621 -44GR 76622 -IGdyYXR1aXRz 76623 -IGNydWlzZXI= 76624 -15E= 76625 -IFRlbm4= 76626 -anNj 76627 -IO2VhA== 76628 -ZGlzcG9zZWQ= 76629 -QUJPVVQ= 76630 -fQ0NCg== 76631 -ZXhwaXJlZA== 76632 -IFhtbE5vZGU= 76633 -IFRhdHRvbw== 76634 -Vm90ZXM= 76635 -Rm9sZA== 76636 -RWxpemFiZXRo 76637 -X0ZJTEVOTw== 76638 -IGNvbmNv 76639 -IEdkaw== 76640 -b3BpZXM= 76641 -fX19 76642 -UVVPVEU= 76643 -LUlJ 76644 -c3BhbQ== 76645 -LWxp 76646 -IGNhcnRh 76647 -LmxheW91dHM= 76648 -IGJlc3Bva2U= 76649 -IGFtYXRldXJz 76650 -IGNvdWxldXI= 76651 -aXRhbWlu 76652 -IGlycmVzcGVjdGl2ZQ== 76653 -IGJsYWNrQ29sb3I= 76654 -LnlhaG9v 76655 -IHdlYXJ5 76656 -IHN3ZWV0cw== 76657 -PyI7Cg== 76658 -PVwiJQ== 76659 -X3dvcmtzcGFjZQ== 76660 -IERpYW1ldGVy 76661 -IGFtZA== 76662 -IE5ldWU= 76663 -IGRiTmFtZQ== 76664 -SmVyZW15 76665 -bG9nZmlsZQ== 76666 -YXRyaWI= 76667 -IEh0dHBTZXNzaW9u 76668 -CUNyZWF0ZQ== 76669 -aWRkeQ== 76670 -LlBBUkFN 76671 -IGZpYW4= 76672 -IHN6Y3o= 76673 -IHFyZWFs 76674 -X0VTQ0FQRQ== 76675 -dXNhaGFhbg== 76676 -LmRpZ2VzdA== 76677 -IGdldFBhcmVudA== 76678 -LkRyb3BEb3duTGlzdA== 76679 -IHRow6k= 76680 -IG1vbnN0cm91cw== 76681 -IGJlcmhhc2ls 76682 -IiIiDQoNCg== 76683 -U3VwcG9ydGVkQ29udGVudA== 76684 -IEdhdGhlcmluZw== 76685 -aW5jeQ== 76686 -LktleUNvZGU= 76687 -IGZldHVz 76688 -LmNlbnQ= 76689 -IGJlc29uZGVycw== 76690 -bmlsYWk= 76691 -TFRSQg== 76692 -IGhpbmdl 76693 -UFJPUA== 76694 -LmZvdW5kYXRpb24= 76695 -bnVtZXI= 76696 -LXJhbmtlZA== 76697 -6I0= 76698 -IHBhaW5mdWxseQ== 76699 -ICg7Oyk= 76700 -Zm9ybWU= 76701 -TGFkeQ== 76702 -L2FwcGxl 76703 -IENvbnN0aXQ= 76704 -IHN0b2NraW5ncw== 76705 -5rS7 76706 -IG1lbnRvcnM= 76707 -PkNyZWF0ZQ== 76708 -IEludGVybmFsRW51bWVyYXRvcg== 76709 -IHRlbGV2aXNlZA== 76710 -VG9rZW5UeXBl 76711 -IGJyaWI= 76712 -Y3JlYXRlVmlldw== 76713 -L0RURA== 76714 -R2l0SHVi 76715 -KGJpZw== 76716 -IG3DoXhpbW8= 76717 -5b6u6L2v6ZuF6buR 76718 -LmNm 76719 -IMKgIMKgIMKgIMKg 76720 -PHR5cGVvZg== 76721 -IHByb2dyZXNzaW5n 76722 -LnNldFdpZHRo 76723 -KHR2 76724 -IHVuZmFpcmx5 76725 -IEFuaXRh 76726 -YXJ5YXdhbg== 76727 -RGFs 76728 -VVJZ 76729 -b2dlbmVpdHk= 76730 -ZWZh 76731 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 76732 -IGRlamE= 76733 -T1NF 76734 -cmFpbA== 76735 -cm9vZg== 76736 -X3F1b3Rlcw== 76737 -PGo= 76738 -44Ko 76739 -KHNldHRpbmc= 76740 -bGV2ZWxuYW1l 76741 -X2hhbmRsaW5n 76742 -w6lyYQ== 76743 -JGo= 76744 -IGRhcmxpbmc= 76745 -LlBhdGhWYXJpYWJsZQ== 76746 -W3NvdXJjZQ== 76747 -TWV0aG9kTmFtZQ== 76748 -IE91dGxldA== 76749 -5pKt 76750 -IENvY29h 76751 -VWJ1bnR1 76752 -IG1vb2ll 76753 -IGZsb3JpZGE= 76754 -IHJldGhpbms= 76755 -IGdldFg= 76756 -Z2V0RWxlbWVudA== 76757 -IHJhZGl4 76758 -IEdhbWVy 76759 -ZGVhbGxvYw== 76760 -bGVmdEpvaW4= 76761 -X1NZTg== 76762 -R3JpZExheW91dA== 76763 -Imdv 76764 -KGVhY2g= 76765 -CXNjZW5l 76766 -IFB5RXJy 76767 -SG93YXJk 76768 -LlNpZ25hbA== 76769 -IFRFTQ== 76770 -IOen 76771 -VkVOVE9SWQ== 76772 -IHNpbXVs 76773 -IDw8LQ== 76774 -IHR1cmJpbmVz 76775 -IHN1cnRvdXQ= 76776 -YWx0bw== 76777 -IHVuYXJ5 76778 -YA0K 76779 -IFNjcmk= 76780 -IE1vbms= 76781 -IHVuZm9sZGVk 76782 -Q29tcG9zaXRpb24= 76783 -UFBFUg== 76784 -IHNpZGluZw== 76785 -Jyx7Jw== 76786 -IHRyZWZm 76787 -X1VOSUNPREU= 76788 -IGRlcmVjaG8= 76789 -IHBvbGFyaXR5 76790 -IG9yYw== 76791 -PERvY3VtZW50 76792 -KHRvZGF5 76793 -LikKCgoK 76794 -IHNlZW1pbmc= 76795 -XFY= 76796 -PklE 76797 -IGZpYm9uYWNjaQ== 76798 -KG1hdGVyaWFs 76799 -RkxBU0g= 76800 -ZGlyZWN0b3JpZXM= 76801 -ZXN0ZXJz 76802 -VEVDVElPTg== 76803 -d3JhcHBlZA== 76804 -LXNlbGVjdGlvbg== 76805 -LXJlbGF0aXZl 76806 -KGNocg== 76807 -IHBvcnRmb2xpb3M= 76808 -IHNob3dEaWFsb2c= 76809 -aW5nbGV0b24= 76810 -IFRJQ0s= 76811 -IEludmVzdG9y 76812 -IGJyYXY= 76813 -IFNWTg== 76814 -IGhhdGVmdWw= 76815 -cmlwcw== 76816 -ZXhwaXJ5 76817 -X2NvaW4= 76818 -PgoKCgoK 76819 -IG1hcmdpbmFsaXplZA== 76820 -IGV4Y2VlZGluZ2x5 76821 -bmF2YmFyU3VwcG9ydGVkQ29udGVudA== 76822 -KGV4dGVuc2lvbg== 76823 -IGFkdmFudGFnZW91cw== 76824 -Lk1pY3Jvc29mdA== 76825 -IGVuc3VpdGU= 76826 -LXZpb2w= 76827 -X2R1ZQ== 76828 -S0g= 76829 -IFJvbWFudGlj 76830 -aW5hbmQ= 76831 -ZWNp 76832 -cmVwb3J0ZWQ= 76833 -IENvcnB1cw== 76834 -IHNwYW5raW5n 76835 -IENyb3NieQ== 76836 -LkZvdW5kYXRpb24= 76837 -XF8= 76838 -IGFubm9uY2Vz 76839 -QXR0YWNobWVudHM= 76840 -4Liy4Lij 76841 -IFdheA== 76842 -77yB77yBCgo= 76843 -IHNhaWxlZA== 76844 -LkV1bGVy 76845 -CXNjcm9sbA== 76846 -IHBlYXNhbnRz 76847 -IEJ1aWxkZXJz 76848 -LkdlbmVyYWw= 76849 -QVJFQQ== 76850 -IG1lc3Npbmc= 76851 -dmVybg== 76852 -IGRpYXBlcg== 76853 -IG9jY3VwaWVz 76854 -CWxvZ2lu 76855 -LkxPQw== 76856 -aWdhbnM= 76857 -77yB4oCd 76858 -X2Zvb3Q= 76859 -X3RhdQ== 76860 -LXBhY2thZ2Vz 76861 -cmVjdXI= 76862 -QWx0ZXJuYXRpdmU= 76863 -77yB44CN 76864 -YXJvbw== 76865 -IHRydXN0ZWU= 76866 -LDpd 76867 -5pa55byP 76868 -Pz4+ 76869 -Lk1pbnV0ZQ== 76870 -IGFsY2Fu 76871 -IENvbmNlcHRz 76872 -Y2hpbGROb2Rlcw== 76873 -Q291cnQ= 76874 -IGNlbGxhcg== 76875 -bGVr 76876 -YWtpcw== 76877 -QnViYmxl 76878 -IG9iamVjdGVk 76879 -IO+7vw== 76880 -Ol06Cg== 76881 -LnBhcnNlRmxvYXQ= 76882 -IHNwYXJrcw== 76883 -LWZpbmQ= 76884 -dmFyaWF0aW9u 76885 -SGFjaw== 76886 -RmFucw== 76887 -X3BhcnNlZA== 76888 -RW50aXR5VHlwZQ== 76889 -YXVjZQ== 76890 -X3RyZWVz 76891 -IEVnZ3M= 76892 -VUlCYXJCdXR0b25JdGVt 76893 -X3RheG9ub215 76894 -IFNIT1A= 76895 -VHdlbnR5 76896 -X2NoZWNrcw== 76897 -IExY 76898 -dXRzY2hlaW4= 76899 -KHBsYXRmb3Jt 76900 -IGF1dG9wc3k= 76901 -UmVxdWlyZW1lbnQ= 76902 -IFJFQ1Q= 76903 -dG9Db250YWlu 76904 -JywnJQ== 76905 -L2VkaXRvcg== 76906 -IHFi 76907 -IEVFRw== 76908 -aHRh 76909 -X1RJTEU= 76910 -LXN1bQ== 76911 -IEFsYnVxdWVycXVl 76912 -IHNob3J0Y29kZQ== 76913 -IHNpbnVz 76914 -IGRlc2tz 76915 -IHBvb3A= 76916 -Lm9wZW5zb3VyY2U= 76917 -IENvbGxhcHNl 76918 -LmRlcg== 76919 -IGhhd2s= 76920 -IFZhbmd1YXJk 76921 -IE1hcnJpb3R0 76922 -X1RhcmdldA== 76923 -IEJhbmFuYQ== 76924 -X2F0dGVudGlvbg== 76925 -IEFyaWVs 76926 -X3Rlbg== 76927 -IGJha2Vy 76928 -4oCUaGU= 76929 -xIXFvA== 76930 -dmVsb3BtZW50 76931 -RWxm 76932 -X2djaGFuZGxl 76933 -UmVwdWJsaWNhbnM= 76934 -IGl0ZW1CdWlsZGVy 76935 -V29u 76936 -X2FjY3Vt 76937 -IG5ld1Bhc3N3b3Jk 76938 -IGRldm9pZA== 76939 -IE1hcmt1cw== 76940 -ZGFlbW9u 76941 -Lkh0dHBDb250ZXh0 76942 -S3Jpc3Q= 76943 -IGFhbGJvcmc= 76944 -X3RyaWFscw== 76945 -KGFzc2VydA== 76946 -44Gj44Gm 76947 -YmVsdA== 76948 -IG1pbGRseQ== 76949 -ZXJ2b2ly 76950 -IGRlc2NlbmRhbnQ= 76951 -IEdpb3Zhbm5p 76952 -IGRlY2x0eXBl 76953 -LVNoaXJ0 76954 -IGFwcm8= 76955 -QXBwbGllZA== 76956 -LmdldFBhcmFt 76957 -aG9m 76958 -dXJhcg== 76959 -IE9CUw== 76960 -X3Nlcg== 76961 -KHNlY3JldA== 76962 -W2xheWVy 76963 -IHVzZWZ1bG5lc3M= 76964 -IEtvdQ== 76965 -X3N1Ym1pc3Npb24= 76966 -X0hPUklaT05UQUw= 76967 -LHRtcA== 76968 -Ly4K 76969 -IGxlc3Nlbg== 76970 -X3dj 76971 -X0ZJTkFM 76972 -0L3QvtC/ 76973 -LnRvZG9z 76974 -LlhQYXRo 76975 -IElEYXRh 76976 -IGRvb3JzdGVw 76977 -IGNvbXBvc2luZw== 76978 -IGh1dA== 76979 -IFZMQU4= 76980 -IG91dGY= 76981 -6K+l 76982 -KGJldGE= 76983 -KioqLwoK 76984 -IEluZG8= 76985 -IGtsYQ== 76986 -X2NvbmZpZ3VyZQ== 76987 -Lk1hcms= 76988 -b3NlY29uZHM= 76989 -KFZlcnRleA== 76990 -b3JnYW5pc21z 76991 -IGZmbQ== 76992 -IGRlbW9saXNoZWQ= 76993 -ICItLS0= 76994 -bGVzaQ== 76995 -IFNpZG5leQ== 76996 -LmdldEluZGV4 76997 -Lk1vbmFk 76998 -U2VsZWN0ZWRJdGVt 76999 -IE5hdlBhcmFtcw== 77000 -YXpvbGU= 77001 -QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo= 77002 -X3NlbnRlbmNlcw== 77003 -IGluY2xpbmF0aW9u 77004 -IEZhdGhlcnM= 77005 -YWNjb3VudElk 77006 -aGFyaQ== 77007 -KT4K 77008 -L3Jhdw== 77009 -ICcnKTsKCg== 77010 -K2w= 77011 -KGNk 77012 -IHVuemlw 77013 -IGdsYW1vcm91cw== 77014 -IyIs 77015 -IG5hdw== 77016 -IG1pbmli 77017 -IEJyYW4= 77018 -TmFjaA== 77019 -X3R3ZWV0cw== 77020 -IENDUA== 77021 -JSI+PA== 77022 -IFN0ZXBoZW5z 77023 -bWFzxLE= 77024 -J2Vz 77025 -IHJlcGFy 77026 -X2RvY3VtZW50cw== 77027 -LmNsb3NlZA== 77028 -LXJpbmc= 77029 -L2NhdGVnb3JpZXM= 77030 -IERlZXBDb3B5 77031 -U1VQ 77032 -Lm5ld2F4aXM= 77033 -IGdkeQ== 77034 -aG9l 77035 -IFJlZWY= 77036 -IHBvbGl0aWM= 77037 -IFJlcXVpcmVtZW50 77038 -IHNoZWRz 77039 -c2VhbGVk 77040 -IHBhdGhvbG9neQ== 77041 -Ii8+PA== 77042 -bW9kbw== 77043 -IHN0ZW1taW5n 77044 -IHRhYm9v 77045 -IFNhdmlvcg== 77046 -IH0NCg0KDQoNCg== 77047 -LmN2 77048 -IGpvdWV1cg== 77049 -IENvcm53YWxs 77050 -IFJlY2VwdGlvbg== 77051 -IGlsbHVtaW5hdGlvbg== 77052 -IGdkYg== 77053 -VkVD 77054 -b2R1 77055 -Q29udGVudEFsaWdubWVudA== 77056 -c3RhbnRpYWw= 77057 -YmFzZWxpbmU= 77058 -X2J1c3k= 77059 -LwoKCgo= 77060 -IHBsYXllcklk 77061 -5qM= 77062 -X3BldA== 77063 -IE1pcmFjbGU= 77064 -dXJlbnQ= 77065 -IE1lcmxpbg== 77066 -dWJlbg== 77067 -IHNldENvbG9y 77068 -IGRhcmtlc3Q= 77069 -c3Rlcnk= 77070 -IGNhcmlj 77071 -IHJldGFyZA== 77072 -IEhvdXNlaG9sZA== 77073 -IGphbA== 77074 -IHlw 77075 -IiwiIik7Cg== 77076 -IEFjZXI= 77077 -W1c= 77078 -b2xraWVu 77079 -YXlv 77080 -UHJpdmF0ZUtleQ== 77081 -IFNUQVRT 77082 -INC90YPQtg== 77083 -OicuJA== 77084 -IHRoYW5rZnVsbHk= 77085 -IGRpc3RydXN0 77086 -Z2V0RGVmYXVsdA== 77087 -L2ZhY2Vib29r 77088 -IENvbnJhZA== 77089 -IHV0aWxpemFuZG8= 77090 -IEthZw== 77091 -L25hbWU= 77092 -IGJhbWI= 77093 -LkZyb21TZWNvbmRz 77094 -IG11dGls 77095 -IExhZ29z 77096 -IEJsZXNzZWQ= 77097 -aWxsZWdhbA== 77098 -aWVp 77099 -X1RQ 77100 -IG1hdGxhYg== 77101 -IGN5Y2xpYw== 77102 -IHdpdGhoZWxk 77103 -IGhvcnJpYmx5 77104 -LWhvdXJz 77105 -LUhlYWRlcnM= 77106 -IG92ZXJsYXBz 77107 -IGN1YXRybw== 77108 -IGVxdWl0YWJsZQ== 77109 -IGNvbG9ybWFw 77110 -IHNoaW4= 77111 -IFN1aXRlcw== 77112 -X2x1YQ== 77113 -KHZv 77114 -X1JFU1VMVFM= 77115 -IFZpa3Rvcg== 77116 -RG93bmxvYWRpbmc= 77117 -bm9jaA== 77118 -TW9vbg== 77119 -IGRlY2lkZWRseQ== 77120 -44GU44GW 77121 -X1JQQw== 77122 -SW50ZXJwb2xhdG9y 77123 -IHZhbnM= 77124 -e1Q= 77125 -X3NwYXdu 77126 -IEV4eG9u 77127 -X0NhbGw= 77128 -IENsYXNzcm9vbQ== 77129 -IHNlcm90b25pbg== 77130 -IERpcGxvbWE= 77131 -YmVkdGxz 77132 -IFByb3RvdHlwZQ== 77133 -LmV4ZWN1dGlvbg== 77134 -IGRhdGluZ3NpZGU= 77135 -IEdva3U= 77136 -X3Jvb21z 77137 -4oCZYW0= 77138 -Z3JhZg== 77139 -YWNlb3Vz 77140 -IGFjY29tbW9kYXRpbmc= 77141 -fSwn 77142 -LmRpbWVuc2lvbg== 77143 -ZXJyb3JNc2c= 77144 -CW1lc2g= 77145 -RmlsbGVk 77146 -LnByZWZlcmVuY2U= 77147 -IHNtYXJ0eQ== 77148 -X2NvdXBvbg== 77149 -IMO2dmVy 77150 -IGNvbmNlaXZl 77151 -b2Rvbg== 77152 -ZGljZQ== 77153 -VG9EYXRl 77154 -YWRhbWVudGU= 77155 -LW1hc2s= 77156 -IGVzY2FsYXRpbmc= 77157 -4oCmKQoK 77158 -SW5SYW5nZQ== 77159 -X0Vt 77160 -IHV0aWxpemE= 77161 -IGxldnk= 77162 -PCFb 77163 -IEplbm5lcg== 77164 -IFJFU09VUkNF 77165 -X1NUQVJURUQ= 77166 -IHZvbGxleWJhbGw= 77167 -IG1nYQ== 77168 -IFJvc3Np 77169 -Q2hhbmNl 77170 -IEVuZGVk 77171 -LnVudGls 77172 -IGtub2Nrb3V0 77173 -X2V4ZQ== 77174 -IFByZXNjcmlwdGlvbg== 77175 -IENPVU5UWQ== 77176 -Lmhy 77177 -aWVyc2hpcA== 77178 -RVJWRQ== 77179 -6ak= 77180 -44Gn44Gv 77181 -IHBlcsOt 77182 -IGltZ1VybA== 77183 -ZWN4 77184 -IFd5bg== 77185 -CVJldHVybnM= 77186 -X2V5ZQ== 77187 -IEFnaW5n 77188 -cXVldWVz 77189 -IOWIneWni+WMlg== 77190 -LlNlcmlhbGl6ZWROYW1l 77191 -LmhvdXJz 77192 -IGlzZQ== 77193 -LkFjdG9y 77194 -5p2h5Lu2 77195 -YXBwbA== 77196 -VGFu 77197 -L2NhdGFsb2c= 77198 -L1Jlc291cmNlcw== 77199 -ZWxhbg== 77200 -KCd7ew== 77201 -IGluc24= 77202 -IG5vZGVOYW1l 77203 -IGNvb2tib29r 77204 -JywnPScsJw== 77205 -Uk9NRQ== 77206 -LnRlbXBsYXRlcw== 77207 -ZWN1cmU= 77208 -LWtleXM= 77209 -IGdsVW5pZm9ybQ== 77210 -IGdlw6c= 77211 -IFJlY292ZXI= 77212 -SURY 77213 -IEtyaXN0ZW4= 77214 -IHBvbnRvcw== 77215 -YD0nJA== 77216 -YXJnZW50 77217 -IGFycmFuZ2luZw== 77218 -6KiY5LqL 77219 -IGVybGU= 77220 -ZW5lZG9y 77221 -KCkpKTs= 77222 -w6Zra2U= 77223 -IEdpbGxlcw== 77224 -In0+Cg== 77225 -Lm1vdmllcw== 77226 -LXNlbGVjdG9y 77227 -LmxlYXJu 77228 -IHBvdGVuY3k= 77229 -IGZpbm8= 77230 -CWJn 77231 -IGxlaGV0 77232 -IGzDtg== 77233 -IGVybQ== 77234 -IGFzYmVzdG9z 77235 -IGRlc3Rl 77236 -IGJsb2NrYWRl 77237 -IFJPVU5E 77238 -IGxuYW1l 77239 -IFNlcGFyYXRl 77240 -w6RuZ2U= 77241 -IGZ1eno= 77242 -CVVO 77243 -X25vbWU= 77244 -X2xpbmtlZA== 77245 -IFNoYXJlUG9pbnQ= 77246 -aGF1c2Vu 77247 -IGxvYWY= 77248 -LWVjb25vbWlj 77249 -IGRpZEZpbmlzaA== 77250 -eWVu 77251 -IGJsYXN0aW5n 77252 -IFdlaXJk 77253 -SUNMRVM= 77254 -IEdGWA== 77255 -IHN1ZmZpY2U= 77256 -ZWJpbg== 77257 -IGFwcHJvdmluZw== 77258 -IFJleWVz 77259 -IFJUQUw= 77260 -aWdsaQ== 77261 -X3Rvaw== 77262 -b3Jkb3Zh 77263 -Q2FybA== 77264 -IFBsYXlz 77265 -bG9zc2Vu 77266 -cGFpcmVk 77267 -QUdNQQ== 77268 -d2nEhXo= 77269 -bGlua2VkaW4= 77270 -IGVnYWw= 77271 -KHByZWRpY2F0ZQ== 77272 -IFJFU1BPTlNF 77273 -IG1pblg= 77274 -IGNoYW5jZWxsb3I= 77275 -IFJFQ0VJVkVS 77276 -IGFzY2VydGFpbg== 77277 -IHplcg== 77278 -IFdvcmtzaGVldHM= 77279 -Tks= 77280 -IHZvd2Vs 77281 -dmFudA== 77282 -VVBT 77283 -4oCcLg== 77284 -IEhheWRlbg== 77285 -IFNwYXJ0YW4= 77286 -cmlnaHRz 77287 -LmdldElu 77288 -IGlubGFuZA== 77289 -IE5pbGU= 77290 -IFRyYW5zbGF0b3I= 77291 -IHJlY3RhbmdsZXM= 77292 -QnV0dG9uVHlwZQ== 77293 -IFNvbGlj 77294 -IHJhZ2F6emE= 77295 -L3RhZw== 77296 -IGlycmVzaXN0 77297 -I0VuZA== 77298 -KioqKioqKg0K 77299 -IHJlc3RyYWluZWQ= 77300 -IGNoaXJvcHI= 77301 -L1No 77302 -LWZsaWdodA== 77303 -Y29udmVydGVk 77304 -IHNraXJ0cw== 77305 -KGNoYXJz 77306 -JHZpZXc= 77307 -IGlucHV0RmlsZQ== 77308 -Z21haWw= 77309 -X0RJQUc= 77310 -IG51bWVs 77311 -IEdpbmE= 77312 -ZWxsdW5nZW4= 77313 -IHRheGE= 77314 -IGRyaXBwaW5n 77315 -PSIiLz4K 77316 -IGJvcmRlcmVk 77317 -IHRvdWdobmVzcw== 77318 -bGVuZXNz 77319 -IEJpZWJlcg== 77320 -X1dBS0U= 77321 -KGV0 77322 -IHNhbnTDqQ== 77323 -IFRFWA== 77324 -X0RJU0NPTk5FQ1Q= 77325 -IHBpZW4= 77326 -IEZvbnRTdHlsZQ== 77327 -X1VM 77328 -LXRvdGFs 77329 -d29sZg== 77330 -IE1hcml0aW1l 77331 -IE9QVElPTkFM 77332 -LXJlc3Q= 77333 -IG1lbWJ1YXQ= 77334 -IEJTT04= 77335 -X3NpbWlsYXJpdHk= 77336 -Lm92ZXJsYXk= 77337 -IHBhbGF0ZQ== 77338 -IEJyaWRnZXM= 77339 -QW5kUGFzc3dvcmQ= 77340 -IENoYXZleg== 77341 -aGV0dG8= 77342 -Lm9mZnNldEhlaWdodA== 77343 -IHVuZGVzaXJhYmxl 77344 -IGFwbGlr 77345 -IC8+XA== 77346 -LHRv 77347 -IHJlbW92ZXI= 77348 -IE1vZGVsaW5n 77349 -IHB1cmNoYXNlcg== 77350 -IENob29zaW5n 77351 -b3BsZWZ0 77352 -IG11dGFibGVMaXN0T2Y= 77353 -IFNpc3RlbWE= 77354 -IElQTA== 77355 -aWNrZXJWaWV3 77356 -SGFzQ29sdW1uVHlwZQ== 77357 -IHNvYmll 77358 -dWJlcm4= 77359 -IGFsdW5v 77360 -IGltYWdpbmF0aXZl 77361 -IEludGVyZXN0ZWQ= 77362 -KCl9PC8= 77363 -IGRpdmVyc2lvbg== 77364 -X3Rvb2x0aXA= 77365 -LlNhbXBsZQ== 77366 -IEZ1dHVyZXM= 77367 -Y29udGVuaWRv 77368 -IEVJTlZBTA== 77369 -KGVuY29kZWQ= 77370 -IFNoYXVu 77371 -CXBheWxvYWQ= 77372 -ZGVr 77373 -PllvdXI= 77374 -SXNv 77375 -VHJhdmVyc2Fs 77376 -aWNpZQ== 77377 -LmNyb3A= 77378 -IEpC 77379 -SU5HRVI= 77380 -IGV4ZW1wbGFyeQ== 77381 -X3JlbHU= 77382 -YW5uaXM= 77383 -0LXQt9GD0LvRjNGC0LDRgg== 77384 -Y2x1YnM= 77385 -4oaR 77386 -IHNjcmFtYmxl 77387 -IFVuYmxvY2s= 77388 -IGRvcnM= 77389 -IHNoYWNr 77390 -IG1pbmltaXppbmc= 77391 -IFBhc3Npbmc= 77392 -YWRkRWxlbWVudA== 77393 -4bud 77394 -IHJvb2Zz 77395 -IGpjbGFzcw== 77396 -Y29yZG92YQ== 77397 -UG9zWQ== 77398 -KENhbnZhcw== 77399 -KGZpbg== 77400 -LWxvc3M= 77401 -LmJ0bkNsb3Nl 77402 -ZG9jdW1lbnRhdGlvbg== 77403 -IFJK 77404 -YW1vbmc= 77405 -TW9z 77406 -bGluZ2Vu 77407 -IEFndQ== 77408 -b2x5bm9taWFs 77409 -XTw9 77410 -IGRpZmZpY2lsZQ== 77411 -IFdpbm5lcnM= 77412 -5bGV 77413 -U3RyYQ== 77414 -IGNvbmdyZWc= 77415 -IEVuYWJsZXM= 77416 -IFN5bXB0b21z 77417 -X3Nn 77418 -IFJpZGluZw== 77419 -X2hlYWRz 77420 -IENvc21ldGlj 77421 -w650 77422 -LlNpbmdsZXRvbg== 77423 -IE5pY2FyYWd1YQ== 77424 -IAoKCgoK 77425 -IG3DrQ== 77426 -J30sDQo= 77427 -IEJvc25pYQ== 77428 -Plg= 77429 -Ly8qWw== 77430 -IHBpbGVk 77431 -Y2FzdGluZw== 77432 -IGdyw6JjZQ== 77433 -IEhlbHNpbmtp 77434 -R3Jv 77435 -I2Fm 77436 -7Iud 77437 -IHNvdWhh 77438 -IEluZGll 77439 -X25lYXI= 77440 -IGltbW9iaWw= 77441 -LkV4Y2Vs 77442 -IHJhZGlhbnQ= 77443 -X01C 77444 -IEtldG8= 77445 -dmVudGFyaW8= 77446 -X2FnZW50cw== 77447 -VGFibGVWaWV3Q2VsbA== 77448 -IFRoZW9kb3Jl 77449 -PT09PT09PT0K 77450 -LGxpc3Q= 77451 -KHNp 77452 -aWNpcGF0aW9u 77453 -QVJUSA== 77454 -c2V0RGlzcGxheQ== 77455 -LkZ1dHVyZQ== 77456 -IFNUQU5EQVJE 77457 -IE9JRA== 77458 -IGZyb3duZWQ= 77459 -IE1hcmlseW4= 77460 -b2xhcmU= 77461 -UHU= 77462 -IHPDqWN1cml0w6k= 77463 -UmVkdXg= 77464 -U0NP 77465 -CQkJCQkgICAgICA= 77466 -cml2 77467 -cGVydA== 77468 -IHNvZnRtYXg= 77469 -IHNlbmF0ZQ== 77470 -PWVtYWls 77471 -IGVzdGltYXRpbmc= 77472 -CXRk 77473 -RnVjaw== 77474 -IFdhdGVybG9v 77475 -IG1leGljbw== 77476 -TmV3dG9u 77477 -U2Fi 77478 -LOKApgoK 77479 -IGNlbGVzdGlhbA== 77480 -IFFOYW1l 77481 -IGdldEFwcA== 77482 -Tmll 77483 -X3BjaQ== 77484 -IFFQb2ludEY= 77485 -X2xpc3Rh 77486 -Lk5WYXJDaGFy 77487 -IENvYw== 77488 -S2Fy 77489 -IGJ1c3RlZA== 77490 -aXphdGlvbmFs 77491 -b3VyZA== 77492 -X2Nvbm5lY3Rvcg== 77493 -IFNla3M= 77494 -0L3Rg9GO 77495 -0II= 77496 -L0xpc3Q= 77497 -L2lj 77498 -XEZyYW1ld29ya0J1bmRsZQ== 77499 -dXh0 77500 -IGhlYWRwaG9uZQ== 77501 -RVhURVJO 77502 -LXJlc2V0 77503 -IEdlaWxl 77504 -IHRyaWFuZw== 77505 -IEFOTg== 77506 -IHTDrQ== 77507 -IFNQQQ== 77508 -IE1hY2Vkb25pYQ== 77509 -IGNyaWFy 77510 -IGNsaW1icw== 77511 -IFNPTg== 77512 -IENyaXRpY3M= 77513 -IGTDsw== 77514 -X1NQTElU 77515 -IEJvdW5kYXJ5 77516 -X0luc2VydA== 77517 -Q29sZA== 77518 -LmNyZWF0ZUNlbGw= 77519 -X3NhaWRh 77520 -LkJMVUU= 77521 -QmlnRGVjaW1hbA== 77522 -KEJ5dGVz 77523 -CVN0YXRl 77524 -LS0tQA== 77525 -Vmlld1NldA== 77526 -YWthaA== 77527 -X1JlcG9ydA== 77528 -LWNyb3Nz 77529 -LmdldEN1cnJlbnRVc2Vy 77530 -dWx0dXI= 77531 -KEZs 77532 -IEltYWc= 77533 -Q1Rlc3Q= 77534 -7IOd 77535 -IHN0YWc= 77536 -IG96b25l 77537 -IGvDqQ== 77538 -cmVwYWly 77539 -KSIpOw0K 77540 -IHZvd3M= 77541 -LkFsdGVy 77542 -IEFsZ2VicmE= 77543 -IEFoZWFk 77544 -Z2V0dA== 77545 -LklubmVyVGV4dA== 77546 -IFpoZW5n 77547 -LnJlYWxwYXRo 77548 -IGRpc3RyYWN0aW9ucw== 77549 -LGV2ZW50 77550 -IElOQ0xVREVE 77551 -Lk1hdGNoZXI= 77552 -LnNwb3RpZnk= 77553 -IGNvbnNpZA== 77554 -Lk1hcHBpbmc= 77555 -IEZvYW0= 77556 -IE5BTkQ= 77557 -IGRldmFudA== 77558 -XSIpXQo= 77559 -TGF1cmE= 77560 -IHNhY2tlZA== 77561 -X3hvcg== 77562 -IHJlYWxtcw== 77563 -IFJvYm90aWNz 77564 -LlNlZWs= 77565 -LiQk 77566 -IFJpYmJvbg== 77567 -CUhSRVNVTFQ= 77568 -IENyZXNjZW50 77569 -RUZS 77570 -IE1lZGl0YXRpb24= 77571 -LmdldFo= 77572 -INC60L7QvNC/ 77573 -anNvbndlYnRva2Vu 77574 -Oj8= 77575 -ZmFm 77576 -VklPVVM= 77577 -YWxsYWg= 77578 -IHBpcGluZw== 77579 -IG1vZGVybmU= 77580 -cG9zdGFsY29kZQ== 77581 -IGxldmVyYWdpbmc= 77582 -IENISVA= 77583 -cGNt 77584 -bWFp 77585 -IGlQ 77586 -QUtFUg== 77587 -ZGF0YUdyaWRWaWV3 77588 -X2RlcHM= 77589 -LWRyaXZlcg== 77590 -TGll 77591 -ZGlzY2FyZA== 77592 -eW50YXhFeGNlcHRpb24= 77593 -IGVjdA== 77594 -IEV4aGliaXQ= 77595 -ICgqKg== 77596 -IOuU 77597 -Q2hhbmdlRXZlbnQ= 77598 -IHN1cGVybWFya2V0cw== 77599 -IHNobQ== 77600 -cHJvZml0cw== 77601 -cGlsbGFy 77602 -cmFpc29u 77603 -V2F0 77604 -IHBoYXJtYWNpZXM= 77605 -IG5ydw== 77606 -Ly89PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0= 77607 -CXdvcmxk 77608 -U3RyZWFtaW5n 77609 -RGlhbW9uZA== 77610 -IEVudW1lcmF0b3I= 77611 -IGVucXVpcnk= 77612 -LmxhbWJkYQ== 77613 -YmVr 77614 -Uk9UTw== 77615 -IFBkZlA= 77616 -IGhpc3Rv 77617 -IGdldENoaWxk 77618 -L3N0cmV0Y2hy 77619 -IEFNQVo= 77620 -IEFyZ3VtZW50T3V0T2ZSYW5nZUV4Y2VwdGlvbg== 77621 -InVzZXI= 77622 -IHNhbml0YXRpb24= 77623 -IENsb3RoZXM= 77624 -Lm51bXB5 77625 -ZmVj 77626 -ICMjIyMjIyMjIyMjIw== 77627 -0LXQudGB0YLQsg== 77628 -X2xw 77629 -IGF6dXJl 77630 -WFBhdGg= 77631 -VmVudA== 77632 -TGFib3I= 77633 -IG1pc3Rha2VubHk= 77634 -IGNvbmR1aXQ= 77635 -IEZhaXJmYXg= 77636 -Z2V0U3RhdHVzQ29kZQ== 77637 -IE1veQ== 77638 -TGlzdEFkYXB0ZXI= 77639 -ICg/KQ== 77640 -R2VuZXJhbGx5 77641 -LmlzQ29ubmVjdGVk 77642 -dmlkbw== 77643 -TW91c2VCdXR0b24= 77644 -R2VuZXJhdGlvblN0cmF0ZWd5 77645 -X2Rlcml2 77646 -IGxla2tlcg== 77647 -TWVhc3VyZW1lbnQ= 77648 -X0NPT0tJRQ== 77649 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 77650 -IGNvbXBldGl0aXZlbmVzcw== 77651 -IGdhbWxl 77652 -IHJldHJvc3BlY3Q= 77653 -IEVkdWFyZG8= 77654 -IERhdGFTZXJ2aWNl 77655 -IGVzY29ydGVk 77656 -IFF0eQ== 77657 -SG9saWRheQ== 77658 -CXJhdw== 77659 -bGV1cnM= 77660 -QmlydGhkYXk= 77661 -IGhlYXRz 77662 -LmludmVyc2U= 77663 -IF8NCg== 77664 -aWxsdW0= 77665 -b2thYmxlQ2FsbA== 77666 -X21s 77667 -TGlrZWQ= 77668 -ZW51bWVyYXRl 77669 -RmluaXRl 77670 -LXByb3A= 77671 -QXJlYVZpZXc= 77672 -IG1lZGlhdGlvbg== 77673 -IGNoYW50aW5n 77674 -X05U 77675 -X3VuYw== 77676 -c21vdXRo 77677 -IHBpZ21lbnQ= 77678 -UGFzc3dvcmRFbmNvZGVy 77679 -IHbDqXI= 77680 -IHdhc3Rld2F0ZXI= 77681 -LVBhY2s= 77682 -IGpvdmVu 77683 -YWVz 77684 -S1k= 77685 -UGludGVyZXN0 77686 -IG11c2ljYQ== 77687 -bGFjZXM= 77688 -IFdpY2g= 77689 -KHJvdA== 77690 -KGly 77691 -IOyCreygnA== 77692 -44Gd44KM 77693 -X1RIRQ== 77694 -Z2V0RmlsZQ== 77695 -W3Byb3BlcnR5 77696 -IGVuZGluZ3M= 77697 -aXp6YXJl 77698 -PXRyYWlu 77699 -LWxvdmluZw== 77700 -IG5vdXZl 77701 -IGNvbW1hcw== 77702 -IGNhbWJp 77703 -IFp1c2FtbWVu 77704 -CUV4dA== 77705 -KG9ic2VydmVy 77706 -Zm9ybWlr 77707 -IHF1aW5kaQ== 77708 -IEl2b3J5 77709 -IEJvbGl2aWE= 77710 -YXNhZA== 77711 -X2xlZ2VuZA== 77712 -Q2l0aWVz 77713 -X0ZJUkU= 77714 -YXNkZg== 77715 -LkRlcHRo 77716 -VmFsdWVHZW5lcmF0aW9uU3RyYXRlZ3k= 77717 -dXBk 77718 -LkdldFJlc3BvbnNl 77719 -IHVyZ2VudGx5 77720 -SW52YXJpYW50 77721 -R2V0WA== 77722 -IHN0YXR1cmU= 77723 -IGltYWdpbmluZw== 77724 -YXRlYXU= 77725 -TU9WRUQ= 77726 -KFRyYW5zYWN0aW9u 77727 -X3Bvcg== 77728 -UmVmUHRy 77729 -Lmdsb2JhbERhdGE= 77730 -Z3JhdmU= 77731 -aW1lc3RlcHM= 77732 -Zm91bmRsYW5k 77733 -U2FsaXI= 77734 -YXJ0aXN0cw== 77735 -IGNyZWF0ZUFjdGlvbg== 77736 -IFNhbnRv 77737 -INC90LXRgg== 77738 -CQkJICAgICAgICAgICAgICAg 77739 -LXNvbmc= 77740 -IG51aXNhbmNl 77741 -IGltcG92ZXI= 77742 -XykNCg== 77743 -IGNyb3dkZnVuZGluZw== 77744 -IHRpbXA= 77745 -UGljdHVyZXM= 77746 -IGxvZGdpbmc= 77747 -6ZKu 77748 -YXRhc2V0cw== 77749 -44Ot44Kw 77750 -cGVyc29ucw== 77751 -Y29uZHVjdA== 77752 -IGV2YWRl 77753 -IGhhdW50aW5n 77754 -ICEhfQ== 77755 -IExBUkdF 77756 -IGtpdHRlbg== 77757 -IHVwaGlsbA== 77758 -KG1pbnV0ZXM= 77759 -IEVtYW51ZWw= 77760 -J0M= 77761 -IFNreXdhbGtlcg== 77762 -cHVycG9zZQ== 77763 -X21hcHBlcg== 77764 -IGFkYXB0YXRpb25z 77765 -LmZpbGxUZXh0 77766 -cnVr 77767 -IHJlcGVydG9pcmU= 77768 -KHByaW9yaXR5 77769 -KG1hcHBlZA== 77770 -Um9iaW4= 77771 -IGVycm9uZW91cw== 77772 -IGluaGFs 77773 -Qk9WRQ== 77774 -KCIsIikK 77775 -dWVsbGVtZW50 77776 -IGZpbmdlcnByaW50cw== 77777 -IFBZVEhPTg== 77778 -LWRlbQ== 77779 -bGVhbm9y 77780 -esSFZA== 77781 -IlBlb3BsZQ== 77782 -YXNpZXI= 77783 -IHBhdHJpb3RpYw== 77784 -LmZyZWV6ZQ== 77785 -SUo= 77786 -IEJhbmNv 77787 -IGlzU3VjY2Vzcw== 77788 -KHZlaGljbGU= 77789 -KExheW91dA== 77790 -IGNhcnZpbmc= 77791 -X2NpcGhlcg== 77792 -IHZlemVz 77793 -KCdfJyw= 77794 -IEZpcnN0bHk= 77795 -IGZ1bGxlc3Q= 77796 -IExpc3RlbmluZw== 77797 -X3NpZ25hbHM= 77798 -ZXdvbGY= 77799 -IFNDUg== 77800 -IE1lcnJ5 77801 -L3Rlc3RpZnk= 77802 -X1NBTklUSVpF 77803 -aW9jdGw= 77804 -SUVFRQ== 77805 -PU1hdGg= 77806 -IGVucXU= 77807 -CWF1eA== 77808 -4pml 77809 -IGRpc3BlcnNlZA== 77810 -aGFyZQ== 77811 -YmVybg== 77812 -IEFtZW5k 77813 -IGluc2lkZXJz 77814 -IEFsdmFyZXo= 77815 -IFp1Zw== 77816 -L2NhbGVuZGFy 77817 -IGhldXJl 77818 -LXBhcGVy 77819 -IHNvZm9ydA== 77820 -IHNtaXRo 77821 -IHBvYg== 77822 -KHJhdGU= 77823 -IHNvY2nDqXTDqQ== 77824 -IHdvZXM= 77825 -IGJydXNoaW5n 77826 -cWQ= 77827 -b2xvZ3Vl 77828 -c29ja2V0cw== 77829 -X1lFUw== 77830 -LmFkZENvbHVtbg== 77831 -IGV2YXNpb24= 77832 -U09GVFdBUkU= 77833 -YWJveA== 77834 -LnlsaW0= 77835 -IGVuZ3VsZg== 77836 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLwo= 77837 -IG5nT25EZXN0cm95 77838 -IG5vc3Nh 77839 -LmxzdA== 77840 -KCl9Pgo= 77841 -Lmt3YXJncw== 77842 -IGNvbnRleHRv 77843 -IFBVQg== 77844 -RnU= 77845 -IGJpZ290cnk= 77846 -IGJyaWQ= 77847 -IHN0ZXJvaWQ= 77848 -IHZpZ29yb3VzbHk= 77849 -IGJ1cnN0aW5n 77850 -IHZlbmU= 77851 -IHNhbGFkcw== 77852 -IFZBUklBQkxFUw== 77853 -IE9uYw== 77854 -IGZpcmVFdmVudA== 77855 -c2FuZGJveA== 77856 -IHRvdWNoc2NyZWVu 77857 -c2Fucw== 77858 -L0luc3RydWN0aW9u 77859 -IGVvZg== 77860 -bGVjdHVyZQ== 77861 -Py0= 77862 -LmxvY2FsaXphdGlvbg== 77863 -VkVT 77864 -X3ZvaWNl 77865 -aXR1cmE= 77866 -LnJlcG9ydGluZw== 77867 -IF0pOw== 77868 -Tm92YQ== 77869 -X0NPTVBBVA== 77870 -IG91dGJyZWFrcw== 77871 -LmNsaWVudFdpZHRo 77872 -aWZsb3dlcg== 77873 -X0dSQQ== 77874 -SW5pdGlhbGl6aW5n 77875 -X3BlcmY= 77876 -KCl9LA== 77877 -PVA= 77878 -X0lNRVRIT0Q= 77879 -IHRpZ2h0ZW5pbmc= 77880 -IHRhYkJhcg== 77881 -IEJL 77882 -CURvdWJsZQ== 77883 -L2hhc2g= 77884 -IG1leg== 77885 -VG9VcHBlcg== 77886 -VEc= 77887 -KGluZGVudA== 77888 -IHNpbGljYQ== 77889 -IC8vLy8vLw== 77890 -w7Zr 77891 -IGVsdmVz 77892 -ZW1wbGF0ZXM= 77893 -LkNvbXBhcmVUbw== 77894 -IGd1bmZpcmU= 77895 -YW5pbWFscw== 77896 -IGtlcGFkYQ== 77897 -IENQUg== 77898 -X0xTQg== 77899 -CXZlcnRleA== 77900 -INC/0LXRgNCy 77901 -LCE= 77902 -IGR1bHk= 77903 -X1BBVENI 77904 -RU5B 77905 -CUND 77906 -Y29tcG9zaXRpb24= 77907 -X3N2 77908 -TGJs 77909 -amVq 77910 -0YHRgtGA0L7QuQ== 77911 -LkVkaXRWYWx1ZQ== 77912 -5YW3 77913 -YW50YXM= 77914 -IGJyZWFkY3J1bWI= 77915 -IFRlc3Rlcg== 77916 -IE1lYXN1cmVtZW50cw== 77917 -L0lucHV0 77918 -IFJheg== 77919 -X1BPTEw= 77920 -SW5kZXBlbmRlbnQ= 77921 -Lmx1Y2VuZQ== 77922 -IE1lY2hhbmljcw== 77923 -Y29sb24= 77924 -LnN1cmZhY2U= 77925 -IHVuYXM= 77926 -cmFkbw== 77927 -UExJQ0FURQ== 77928 -Q1JU 77929 -LnNldERlZmF1bHQ= 77930 -JUg= 77931 -IHJlc3BvbnNhYmxl 77932 -IHBlcnBlbmRpY3VsYXI= 77933 -IFJlc3Bpcg== 77934 -IFR1bmlzaWE= 77935 -XEFycmF5 77936 -6Lev5b6E 77937 -IHBhdw== 77938 -IGRlYm91bmNl 77939 -KE1QSQ== 77940 -INiv2LE= 77941 -IGVsaw== 77942 -IFJlbGF5Q29tbWFuZA== 77943 -L2xpZ2h0 77944 -LnNlcmlhbGl6YXRpb24= 77945 -QlNJVEU= 77946 -KSgoKCg= 77947 -IEJpb3M= 77948 -X3N2Zw== 77949 -KHN1cmZhY2U= 77950 -RHVwbGljYXRlcw== 77951 -ICg+ 77952 -X0FTVA== 77953 -Lm5pY2s= 77954 -IldoeQ== 77955 -IEludGVsbGVjdHVhbA== 77956 -YWJicmV2aWF0aW9u 77957 -ZWFyYWJsZQ== 77958 -IGNvbnNlZ3Vpcg== 77959 -KEJl 77960 -X1BvZHM= 77961 -PEFuaW1hdG9y 77962 -X1VOREVGSU5FRA== 77963 -QVJSWQ== 77964 -IC8vfg== 77965 -cGVyYXRvcg== 77966 -LndyaXRlRmlsZVN5bmM= 77967 -QWxz 77968 -bGRlcg== 77969 -IG1pZWpz 77970 -IGZ1bmNz 77971 -aW5jaWJsZQ== 77972 -IGR1c3R5 77973 -IERyaWxs 77974 -IGNvbnRpbnVhbA== 77975 -IEVsZWN0cm9u 77976 -LmVuZW15 77977 -KHBi 77978 -IHJldW5pdGVk 77979 -U21va2U= 77980 -LWZhY2Vk 77981 -SW50ZW5zaXR5 77982 -IFRyZWVNYXA= 77983 -IEFyZ3VtZW50RXJyb3I= 77984 -LndyaXRlSGVhZA== 77985 -IFRSRQ== 77986 -U3BsaXRPcHRpb25z 77987 -LyoqKioqKi8K 77988 -IFw8Xg== 77989 -IEludmVzdG1lbnRz 77990 -U1VNRVI= 77991 -IGRhYw== 77992 -QU5J 77993 -Llllc05v 77994 -KG9mU2l6ZQ== 77995 -eXRo 77996 -ZWxvYWQ= 77997 -IGltcHJlcw== 77998 -IGJsb2Jz 77999 -LnJldHJpZXZl 78000 -IHR5cmFubnk= 78001 -IGNhbmNlbEJ1dHRvblRpdGxl 78002 -IGhhY2k= 78003 -IENhc2lub3M= 78004 -IGRoZQ== 78005 -UmV0YWls 78006 -IFBvcm5odWI= 78007 -IENyaW1lcw== 78008 -T2ls 78009 -KElTZXJ2aWNl 78010 -UmVzaXphYmxl 78011 -CVNv 78012 -T2Z0ZW4= 78013 -IGNvbW1vbnBsYWNl 78014 -X0dD 78015 -YWxkaQ== 78016 -YXRobG9u 78017 -KFZpZXdHcm91cA== 78018 -KEVtcGxveWVl 78019 -IHNhZmVndWFyZHM= 78020 -6YCA5Ye6 78021 -X0FVUkE= 78022 -IHVubm90aWNlZA== 78023 -IFRob3Ju 78024 -bW9kZWxl 78025 -IGFjb3Jkbw== 78026 -IFdlbmdlcg== 78027 -aW11cw== 78028 -ZW5zYnVyZw== 78029 -b21iYQ== 78030 -Y2nDs24= 78031 -Imh0dHA= 78032 -X01hdHJpeA== 78033 -fHx8fA== 78034 -b3JuZWNlZG9y 78035 -CUJ1ZmZlcmVkUmVhZGVy 78036 -cmVnaXN0ZXJz 78037 -cmVsZWFzZWQ= 78038 -IGFkZE9ic2VydmVy 78039 -IFZhbGVudA== 78040 -KEN1bHR1cmVJbmZv 78041 -IG1hbm5lbg== 78042 -IGJ1cmdsYXJ5 78043 -X21pbnV0ZQ== 78044 -IGludGVyY2VwdG9y 78045 -b2NyYXRlcw== 78046 -YXR0cm8= 78047 -IFlF 78048 -ZXNzbGVy 78049 -bGlzdGVuZXJz 78050 -L3Byb20= 78051 -IOek 78052 -dG91Y2hlcw== 78053 -RXNw 78054 -IEFib3J0 78055 -IGZmaQ== 78056 -IGNsdW1z 78057 -TklM 78058 -X1ZJUlRVQUw= 78059 -IGxvaW4= 78060 -eW5vbWlhbHM= 78061 -INec 78062 -IGd6 78063 -IE5lb24= 78064 -SVNJUw== 78065 -YW1lcmF0ZQ== 78066 -X2F2YWls 78067 -IG1heGk= 78068 -IGlzQXJyYXk= 78069 -Q29sdW1uSW5mbw== 78070 -aXppbg== 78071 -IHBlcnNv 78072 -IG91ZA== 78073 -aWFsaXplZA== 78074 -eW1p 78075 -IGNvbmZpZGVudGx5 78076 -PSIvIj4K 78077 -LmRhdGFzb3VyY2U= 78078 -IHBheWNoZWNr 78079 -IEJhdg== 78080 -L0JyYW5jaA== 78081 -IFRlYXI= 78082 -IG1lcnVwYWthbg== 78083 -IEJyYWg= 78084 -INC60L7QvdGC 78085 -74I= 78086 -LHBhdGg= 78087 -IGRhenpsaW5n 78088 -IFVDSEFS 78089 -IHByb3Zpc2lvbmFs 78090 -0L/Qvw== 78091 -IGxlZ2FsaXplZA== 78092 -X2FsZ28= 78093 -X1JTQQ== 78094 -YWx0ZXJuYXRpdmU= 78095 -IERFVEFJTFM= 78096 -VG9Ebw== 78097 -cmVmbGVjdGlvbg== 78098 -X1dFRUs= 78099 -IENMRUFO 78100 -IHNsb2dhbnM= 78101 -IOuTsQ== 78102 -IFZldGVyaW5hcnk= 78103 -aWRm 78104 -LmRhdGVUaW1lUGlja2Vy 78105 -aWNvbnRyb2w= 78106 -KHBsYXk= 78107 -IHVsbGFt 78108 -ICcpDQo= 78109 -IGNoZXF1ZQ== 78110 -5a6L5L2T 78111 -IHVuc2VyZW0= 78112 -IEFyY2hpdGVjdHM= 78113 -YW1lbnRhbHM= 78114 -IHZtYXg= 78115 -IGplbWFuZA== 78116 -Q0VFRA== 78117 -IE9saXZpZXI= 78118 -c2V2ZXJpdHk= 78119 -Uks= 78120 -RGlzY29ubmVjdGVk 78121 -IHdlYXBvbnJ5 78122 -dWnDp8Ojbw== 78123 -IGJpbmdv 78124 -ZG9udA== 78125 -X0NIQU5ORUxT 78126 -IERhZw== 78127 -IGTDpHI= 78128 -w6lyaXF1ZQ== 78129 -Z3JhZGFibGU= 78130 -IENPTVBMRVRF 78131 -IHNwYW5pc2g= 78132 -IGluc3RydW1lbnRhdGlvbg== 78133 -dmFzaXZl 78134 -RFJBVw== 78135 -IGZwdXRz 78136 -IFNwZW5k 78137 -IFJlc3BlY3Q= 78138 -Q291cnRlc3k= 78139 -IHNjaG8= 78140 -IHBvc3RhZ2U= 78141 -IE1lYWRvd3M= 78142 -IHR1dG9yaW5n 78143 -ZXJ2bw== 78144 -QWJzb2x1dGVseQ== 78145 -w6FuZGV6 78146 -vZTrk5w= 78147 -IFNIUg== 78148 -cGhvb24= 78149 -IERlcG9z 78150 -PScnCg== 78151 -IHBoeXNpb2xvZ3k= 78152 -KnRpbWU= 78153 -IFRvdWdo 78154 -ZG9jaw== 78155 -L2hl 78156 -KEhhdmU= 78157 -IE1vaW5lcw== 78158 -U1RZUEU= 78159 -IEJyaWRl 78160 -IHN0cm9u 78161 -IHdvcmxkdmlldw== 78162 -IGdyYXR1aXRv 78163 -IGFlcm9zcGFjZQ== 78164 -IElocmVt 78165 -IHFj 78166 -IG1hbmlmZXN0YXRpb25z 78167 -c2xhdWdodA== 78168 -PEFjY291bnQ= 78169 -IEluZm9z 78170 -YW1iaWw= 78171 -X0ZpbmFs 78172 -IGFkbWluaXN0cmF0aW9ucw== 78173 -IGNvbGxhYm9yYXRlZA== 78174 -LmpkZXNrdG9w 78175 -b2x1Y2nDs24= 78176 -YXNjdGltZQ== 78177 -X2FsbG9jYXRl 78178 -YXJyaXZhbA== 78179 -Sk9S 78180 -IHNoYWR5 78181 -IHBpbmVhcHBsZQ== 78182 -44KP 78183 -IHNhdGlu 78184 -YnJlcm8= 78185 -IExpZXM= 78186 -IHRlbnNvcnM= 78187 -IEludGVsbGlnZW50 78188 -LlNlbGVjdGVkSW5kZXhDaGFuZ2Vk 78189 -IHJhZGlhdG9y 78190 -YXNzaXN0YW50 78191 -JGZpZWxkcw== 78192 -CXN0ZXA= 78193 -IE1pdGdsaQ== 78194 -IEV2ZXJldHQ= 78195 -IFNjaGVkdWxlZA== 78196 -SG9yYQ== 78197 -Il0tPg== 78198 -IG1vdHM= 78199 -IERTVA== 78200 -Zm9udE5hbWU= 78201 -IFdhcndpY2s= 78202 -X1Rhc2s= 78203 -KkM= 78204 -44On 78205 -b2JlbA== 78206 -X0RFVA== 78207 -IHNvY2lvbG9neQ== 78208 -IEthdHo= 78209 -aWNpb25z 78210 -b3RsYW5k 78211 -YWRvbw== 78212 -X3BhcnM= 78213 -IHJpcHBpbmc= 78214 -aWNobw== 78215 -IG51dHJpdGlvdXM= 78216 -CWRhbWFnZQ== 78217 -S3k= 78218 -IGFuY2hvcmVk 78219 -IGFydGlmaWNpYWxseQ== 78220 -IEp1dmVudHVz 78221 -L3Blcmw= 78222 -IGV4cHJlc3NpdmU= 78223 -eEVF 78224 -IEVudW1lcmF0aW9u 78225 -Lk1FU1NBR0U= 78226 -KGRlZw== 78227 -5b+X 78228 -IyMjIyMj 78229 -ICIiKSw= 78230 -a2zDpHI= 78231 -XE1haWw= 78232 -RGVzaWduZWQ= 78233 -IHN0YWZmZXI= 78234 -IHNhbHRz 78235 -KioqKioNCg== 78236 -IOKB 78237 -IHNldFRpdGxlQ29sb3I= 78238 -RFZE 78239 -LldyaXRlQWxs 78240 -ZWxsYW50 78241 -IGNvZXJjaW9u 78242 -IFNvcnRpbmc= 78243 -6KiA 78244 -IHN0YXJ2YXRpb24= 78245 -Ly97ew== 78246 -LmhlYXA= 78247 -IE1lZGlldmFs 78248 -ICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 78249 -77yR77yQ 78250 -IHdhcmRz 78251 -IEhlcmM= 78252 -IEhvZ3dhcnRz 78253 -LWNvbW1lbnRz 78254 -IExhdWRlcmRhbGU= 78255 -5rw= 78256 -IHJpZnQ= 78257 -IHplaXQ= 78258 -IHByb29mcw== 78259 -LnZpZXdwb3J0 78260 -JHN0YXJ0 78261 -IEJvdWdodA== 78262 -LnJpY2hUZXh0Qm94 78263 -IGNsaW5n 78264 -ICcqKg== 78265 -T3duZXJzaGlw 78266 -IEJvZWhuZXI= 78267 -KGR5bmFtaWM= 78268 -IG1lZGljYWxseQ== 78269 -IFdURg== 78270 -IE1haW5NZW51 78271 -6LSt 78272 -IGRpZmVyZW50ZQ== 78273 -L3Jlc3VsdHM= 78274 -ZW50aGFs 78275 -IFdpZGdldHM= 78276 -cnVzaA== 78277 -IFJNUw== 78278 -IFZvbGxleQ== 78279 -IHJlbW92ZUZyb21TdXBlcnZpZXc= 78280 -IExhZmF5ZXR0ZQ== 78281 -IEZldGNoVHlwZQ== 78282 -YWNhcw== 78283 -IHBhdGhvZ2Vucw== 78284 -IE1NTw== 78285 -LkN1cnJlbmN5 78286 -b2Npb3Vz 78287 -IHNwcml0ZUJhdGNo 78288 -ZG9sbA== 78289 -IHZhbXBpcmVz 78290 -bGF1bmNoZXI= 78291 -IHBlYWtlZA== 78292 -IGRlYnVuaw== 78293 -IEFTRA== 78294 -IHVuZXF1YWw= 78295 -IHNxdWFkcw== 78296 -fS4kew== 78297 -bWFuaQ== 78298 -IkU= 78299 -IEZhaHI= 78300 -IElTSQ== 78301 -IHVuYXZvaWQ= 78302 -b3Bob25l 78303 -WzpdCg== 78304 -IERpcmVjdGVk 78305 -IGJ1c2hlcw== 78306 -LmZhaWx1cmU= 78307 -IGltbWVyc2Vk 78308 -ZXhv 78309 -SGlzdG9ncmFt 78310 -IEthbm4= 78311 -IHBpcmFjeQ== 78312 -IENydW5jaA== 78313 -IGzDpg== 78314 -Ly8i 78315 -IG1vbm90 78316 -IFNhdW5kZXJz 78317 -IFNldmVudA== 78318 -KEFic3RyYWN0 78319 -IHNtb2tlcg== 78320 -cm9uZQ== 78321 -LmNsaWVudFk= 78322 -ICItIiw= 78323 -IEZvdW50YWlu 78324 -IGlubmU= 78325 -7IOJ 78326 -Q3Ry 78327 -JGlucHV0 78328 -UFJPRklMRQ== 78329 -IERvbmF0aW9u 78330 -V2l0aEVtYWls 78331 -IGZyYWN0dXJlcw== 78332 -S2VlcGVy 78333 -IG1laXNqZXM= 78334 -IGFyY2hpdGVjdHVyZXM= 78335 -IEx1bmc= 78336 -J2ltYWdl 78337 -aGFybWE= 78338 -IGFiYW5kb25pbmc= 78339 -QUxMRUQ= 78340 -c3VidHlwZQ== 78341 -cmVpcmE= 78342 -IG1vc3M= 78343 -IFBhcnNvbnM= 78344 -YWtlZG93bg== 78345 -PW9iag== 78346 -IHN1Y2Vzcw== 78347 -IHdlYXJhYmxl 78348 -44Kn 78349 -IGFkdWx0aQ== 78350 -LnVt 78351 -IHZpYnJhdGlvbnM= 78352 -IHN3ZWxs 78353 -IERpc2Nsb3N1cmU= 78354 -IFJERA== 78355 -cGFpcnM= 78356 -YW5nZ2Fu 78357 -IG1haW5CdW5kbGU= 78358 -IERJTg== 78359 -IHJvY2tlZA== 78360 -c2hvdWxkQmU= 78361 -Lmdi 78362 -IElNRA== 78363 -IFdO 78364 -LGFyZw== 78365 -4oCm4oCm4oCm4oCm4oCm4oCm4oCm4oCm 78366 -W109JA== 78367 -LlNN 78368 -IGFsZ3Vucw== 78369 -YWRkb25z 78370 -X0NvbW1vbg== 78371 -X1JFRlJFU0g= 78372 -INmB2Yo= 78373 -IFRZUE8= 78374 -IEVjb2xvZ3k= 78375 -IGdsdQ== 78376 -LkRhdGFUeXBl 78377 -IFByb2Jl 78378 -THV4 78379 -b3dlZ28= 78380 -IHJlaw== 78381 -IFBsYWludGlmZg== 78382 -YWNoYWJsZQ== 78383 -Lm5hbWE= 78384 -Km91dA== 78385 -fX17ew== 78386 -IENBUElUQUw= 78387 -5L2G 78388 -SW1wb3J0ZXI= 78389 -LmNyZWF0ZVNlcnZlcg== 78390 -X3Jlc29sdmU= 78391 -X0VQUw== 78392 -c3RlbGxhcg== 78393 -X1Byb2ZpbGU= 78394 -CXN3 78395 -LW1vbg== 78396 -dWRldg== 78397 -XFBsdWdpbg== 78398 -X01JWA== 78399 -IERpc2NyaW0= 78400 -LmZyb21MVFJC 78401 -IFN0cmFuZA== 78402 -QW55dGhpbmc= 78403 -cG93ZXJz 78404 -XV0NCg== 78405 -LlRJTQ== 78406 -IGFkZHNsYXNoZXM= 78407 -IGVzaQ== 78408 -QEJlZm9yZQ== 78409 -IHNhaw== 78410 -ICcvJzsK 78411 -Y29j 78412 -xZ/EsQ== 78413 -ICkpOw0K 78414 -X2Fib3Zl 78415 -IEVDQw== 78416 -L2NwdQ== 78417 -IGNhZGU= 78418 -LlN0ZGVycg== 78419 -IHBlbGxldHM= 78420 -IFBhbGlu 78421 -IGfDqW4= 78422 -X2phdmE= 78423 -IHNhbGFo 78424 -IGJlcmdlbg== 78425 -X1NXQVA= 78426 -IGdpYg== 78427 -acOjbw== 78428 -X2Rpc3RhbmNlcw== 78429 -IENpbmRlcg== 78430 -IGFuYXJjaGlzdA== 78431 -aW1hdA== 78432 -CW1vY2s= 78433 -44GX44G+44GZ 78434 -T21lZ2E= 78435 -IGJhaHdh 78436 -X1BhcnNl 78437 -LnBhcGVy 78438 -CUludGVudA== 78439 -cmVucw== 78440 -L2dyaWQ= 78441 -IGZpbHRoeQ== 78442 -LmV2 78443 -IyMjIyMK 78444 -IHNhcmU= 78445 -IHNvYWtpbmc= 78446 -IFJlZ2lvbnM= 78447 -X1VTRUQ= 78448 -IFNpaw== 78449 -aWZpa2FzaQ== 78450 -CUVkaXRvcg== 78451 -THVjaw== 78452 -IOyXsA== 78453 -xINt 78454 -LiI7 78455 -IFppZWw= 78456 -IGdyYXlzY2FsZQ== 78457 -KEZ1bmM= 78458 -44OB 78459 -LkRlbnNl 78460 -LWxlYW5pbmc= 78461 -IGdyYWNlZnVs 78462 -R3JhcGhOb2Rl 78463 -X0NPTU1JVA== 78464 -IENWUw== 78465 -IHBsYWlucw== 78466 -IHJlag== 78467 -cGNpb25lcw== 78468 -IHVuZGVybWluaW5n 78469 -X2NhdHM= 78470 -ZmVi 78471 -Q29sbGVjdGlvblZpZXc= 78472 -U0VNQg== 78473 -IHRodQ== 78474 -dGV4dGJveA== 78475 -KEFuZHJvaWQ= 78476 -IHJpZ29y 78477 -IFlpZWxk 78478 -LmlzUGxheWluZw== 78479 -OnZpZXc= 78480 -cmVtYWluZGVy 78481 -IFBpcA== 78482 -KWluZGV4 78483 -IEJlY2tlcg== 78484 -dG9Mb2NhbGU= 78485 -YXV0b3JlbGVhc2U= 78486 -IFJvbWVybw== 78487 -LkhhbmRsZWQ= 78488 -IENhYmluZXRz 78489 -KVY= 78490 -IHJ0ZQ== 78491 -IEh1bHU= 78492 -aWNpZWw= 78493 -L2FuaW1hdGlvbnM= 78494 -IHByZXN1bWU= 78495 -LnRyYW5zcGFyZW50 78496 -IHN1Ym1lbnU= 78497 -cW0= 78498 -aWVydGVu 78499 -IHRleHRTaXpl 78500 -IHN0YXJ2aW5n 78501 -L2pvYg== 78502 -QXBhY2hl 78503 -IHlpZWxkaW5n 78504 -LWFydGljbGU= 78505 -Jz0+JF8= 78506 -IOih 78507 -PFNwcml0ZVJlbmRlcmVy 78508 -IFNoaWE= 78509 -KToo 78510 -IHB1Ymxp 78511 -emllag== 78512 -IHRlbGVzYw== 78513 -IHRlaWw= 78514 -TGVnYWN5 78515 -IFBsYWNlbWVudA== 78516 -KCkpew== 78517 -IHRyb3VibGVzb21l 78518 -5pif 78519 -IHBlcnPDtm4= 78520 -X0FzcE5ldA== 78521 -PX0= 78522 -KHVzZXJJRA== 78523 -U3Vz 78524 -44K6 78525 -LWF2ZXJhZ2U= 78526 -IFFJbWFnZQ== 78527 -LlN0cmljdA== 78528 -dGVib3Jn 78529 -LWZ1bmN0aW9ucw== 78530 -UkVHSU9O 78531 -Pk5ldw== 78532 -X2Nob29zZQ== 78533 -KGNp 78534 -IHVubGVhc2g= 78535 -IFJJR0hUUw== 78536 -IFNwZWFy 78537 -CW1ha2U= 78538 -IHR5cw== 78539 -YW5lbGE= 78540 -IFdY 78541 -X01BS0U= 78542 -L3NldHVw 78543 -IG9uU2F2ZQ== 78544 -IGNsaW5pY2lhbnM= 78545 -CWJhY2s= 78546 -LkxpbmtlZA== 78547 -IGNvbnNlcnZl 78548 -IGJpdHRlbg== 78549 -X3ZhcmlhbmNl 78550 -IGxpcmU= 78551 -IGluZXJ0aWE= 78552 -dWZmbGVz 78553 -X01QSQ== 78554 -aWRkbGVz 78555 -W2Fycg== 78556 -LnZvY2Fi 78557 -IHNoaXR0eQ== 78558 -IG5lc3Rl 78559 -c3NpemU= 78560 -IEtU 78561 -Ymxlcg== 78562 -X2xpbnV4 78563 -IG1vbmdvZGI= 78564 -IElURU1T 78565 -S29u 78566 -IEJ1cnN0 78567 -X3Bob3Rvcw== 78568 -Q29sb3JhZG8= 78569 -IGFja25vd2xlZGdtZW50 78570 -IG9pbHk= 78571 -IG5mcw== 78572 -IFppb25pc3Q= 78573 -IGFkZGljdHM= 78574 -IGFkZFVzZXI= 78575 -IE1pc2g= 78576 -IGtX 78577 -IFdhbnRz 78578 -KHJlY29yZHM= 78579 -b2N1cnJlbmN5 78580 -SlNHbG9iYWw= 78581 -LmVsYXBzZWQ= 78582 -IE5i 78583 -IHBwdA== 78584 -XERlcGVuZGVuY3k= 78585 -Um9s 78586 -IMOnYWzEscWf 78587 -IGV4cGFuc2lvbnM= 78588 -YnViYmxl 78589 -IG1pZHRlcm0= 78590 -ICcjew== 78591 -Y3R4dA== 78592 -SVN5bnRheEV4Y2VwdGlvbg== 78593 -IFZhbGxl 78594 -IENhZGlsbGFj 78595 -ICIifSwK 78596 -IHNlbXVh 78597 -cmljaFRleHQ= 78598 -c29mdG1heA== 78599 -b2JqUEhQRXhjZWw= 78600 -LmhzdGFjaw== 78601 -X2NyaXRpY2Fs 78602 -KDw/ 78603 -ZGo= 78604 -IGNvbnNvbg== 78605 -IHJvb21JZA== 78606 -RE9NQ29udGVudExvYWRlZA== 78607 -cGFybXM= 78608 -IHplaWd0 78609 -VFBM 78610 -LW5vdGNo 78611 -IG9wcHJlc3NpdmU= 78612 -Q29kaW5n 78613 -IExlYXZlcw== 78614 -KERpc3BsYXk= 78615 -LnNpZ25Jbg== 78616 -Ly8tLQ== 78617 -IE9wcg== 78618 -Y3Rh 78619 -IG1ldGF2 78620 -U2VyaWFsaXplZA== 78621 -IHVuYWZmZWN0ZWQ= 78622 -IEFUTA== 78623 -IEtQ 78624 -QXRsYW50aWM= 78625 -LHVybA== 78626 -LHN0YXRl 78627 -IGJpc3Q= 78628 -ZW5lZw== 78629 -IHNpbXBsaXN0aWM= 78630 -IGJpZGRlcg== 78631 -IHBlcmNlcHQ= 78632 -IGNlbGli 78633 -IFRIUk9X 78634 -KC9b 78635 -VGNw 78636 -IGZ1cnRoZXJtb3Jl 78637 -LkFjYw== 78638 -b3BwYWJsZQ== 78639 -5Lik 78640 -IFRhcnQ= 78641 -IEJlbno= 78642 -IGVtYm9kaWVk 78643 -KENvbnN0 78644 -ICst 78645 -UGFydGljaXBhbnRz 78646 -IGh0dHBSZXF1ZXN0 78647 -YWNjZW50 78648 -IFPDvA== 78649 -IGhvcnJpZnlpbmc= 78650 -IC8+LA== 78651 -IGVuYWN0bWVudA== 78652 -IFVOSU9O 78653 -L2xvZ3M= 78654 -IHNjcmVlbkhlaWdodA== 78655 -IGV0d2E= 78656 -5L6L5aaC 78657 -IGHDum4= 78658 -5bem 78659 -X3RpbWVsaW5l 78660 -ICIiKSkK 78661 -JzonJw== 78662 -Qlc= 78663 -IHJlbm92YXRpb25z 78664 -IDwK 78665 -UGFsZQ== 78666 -Pjo8Lw== 78667 -U2tlbGV0b24= 78668 -IGdldFVzZXJz 78669 -X2RhdGFmcmFtZQ== 78670 -YWJy 78671 -bWF0ZXJpYWxz 78672 -JmVhY3V0ZQ== 78673 -LkRpc3BsYXlOYW1l 78674 -IGh2aXM= 78675 -X2xhbmd1YWdlcw== 78676 -LnN5 78677 -dG93ZXI= 78678 -SUZJQ0FUSU9OUw== 78679 -IGJhcnJpYw== 78680 -IFBsdXRv 78681 -YDs= 78682 -44OL 78683 -Y2VudGU= 78684 -I2Fi 78685 -IGxleGljYWw= 78686 -IEJSTw== 78687 -IHJ1bGluZ3M= 78688 -SEVZ 78689 -LmlPUw== 78690 -cmV0dXJuZWQ= 78691 -LmJvb2tz 78692 -IEh1YmI= 78693 -ZW9m 78694 -Pj46Og== 78695 -IOyG 78696 -IGdvVG8= 78697 -6ICD 78698 -44Go44GG 78699 -PEZvcm0= 78700 -Y29waWVz 78701 -LnF1YW50 78702 -IFBvdGF0bw== 78703 -IENvdXNpbnM= 78704 -IHPDuw== 78705 -R292ZXJu 78706 -IGdhbGVy 78707 -IEZJUg== 78708 -X1dpZHRo 78709 -IFNoZWxkb24= 78710 -LkRldg== 78711 -IFJlc3BvbnNpYmlsaXR5 78712 -c29uaWFu 78713 -IHN1cGVyY2xhc3M= 78714 -Yml0c2V0 78715 -ZWRkYXI= 78716 -IExhYm9yYXRvcmllcw== 78717 -IGNvaW5lZA== 78718 -IFRlY2huaXF1ZQ== 78719 -KENvcmU= 78720 -IHNwcmF5ZWQ= 78721 -IHBvbmc= 78722 -KE5ldHdvcms= 78723 -IHJvYXI= 78724 -IEVBU1Q= 78725 -c3RyYWlu 78726 -IG1lbnN0cnVhbA== 78727 -b21iYXQ= 78728 -IGNhbG1pbmc= 78729 -CURpbQ== 78730 -X21vdmllcw== 78731 -IFJBSUQ= 78732 -LWRpc21pc3NpYmxl 78733 -IGZyZXVuZA== 78734 -LWNoYW4= 78735 -IHJlc2lzdG9y 78736 -X0NvcHk= 78737 -b2NyaW5l 78738 -IGVzcGlvbmFnZQ== 78739 -Z2Fkbw== 78740 -TkRBUg== 78741 -IHBvcmNlbGFpbg== 78742 -dGhhbG0= 78743 -IGBb 78744 -IGdyYWRv 78745 -0LjRgA== 78746 -RE9VQkxF 78747 -IGFjY2Vzc2Vz 78748 -LkZsb29y 78749 -IOKGlA== 78750 -IHRva2VuaXpl 78751 -YW5hbHl0aWNz 78752 -LkNyZWF0ZUluc3RhbmNl 78753 -IHN1Y2hl 78754 -CWVudA== 78755 -aWduZXI= 78756 -INC/0LXRgNC10LQ= 78757 -IGNvbmRpY2lvbmVz 78758 -LmxpYnM= 78759 -Iic7 78760 -UERPRXhjZXB0aW9u 78761 -IG9uRGF0YQ== 78762 -IEF1dGlzbQ== 78763 -LWhlbHBlcg== 78764 -IHJld2luZA== 78765 -IGNvZmZpbg== 78766 -44O844K4 78767 -IHRyYW5zbWl0dGluZw== 78768 -LnNldEFsaWdubWVudA== 78769 -IGRlYWxsb2M= 78770 -IGFuY2VzdHJhbA== 78771 -b2dpZQ== 78772 -LkNPTVA= 78773 -OmZyYW1l 78774 -bW1v 78775 -Jzoi 78776 -IFJlZ2VudHM= 78777 -IGNoZWF0ZWQ= 78778 -Lmdn 78779 -IHBhY2Vk 78780 -IGVzdGFk 78781 -b2NlbmU= 78782 -bHNh 78783 -KGZj 78784 -L2dyb3Vwcw== 78785 -L21pc2M= 78786 -IFNodXR0bGU= 78787 -VVBJ 78788 -w6Fv 78789 -LWN5Y2xl 78790 -CXByb3Bz 78791 -IHJvdHRlbg== 78792 -UmVqZWN0ZWQ= 78793 -I2Fj 78794 -LnVh 78795 -IEFtbmVzdHk= 78796 -IHBlbm5lZA== 78797 -SU5DUkVNRU5U 78798 -PGRpbQ== 78799 -LnNldFVw 78800 -IFR3ZWV0cw== 78801 -IE1hZHVybw== 78802 -INmC 78803 -IENBY3RpdmU= 78804 -CUJZVEU= 78805 -KHNlcGFyYXRvcg== 78806 -LlJlc2l6ZQ== 78807 -dWZmbWFu 78808 -c3VwcG9ydHM= 78809 -IHVyYg== 78810 -IEZvdW5kZWQ= 78811 -X2hhcmQ= 78812 -IGVjbGVjdGlj 78813 -LkZpbHRlcnM= 78814 -IFJvdW5kZWRSZWN0YW5nbGU= 78815 -X3NhbXBsaW5n 78816 -IEpldHp0 78817 -YW1lcmljYW4= 78818 -Lmludm9rZUxhdGVy 78819 -IEJ1dHRlcmZseQ== 78820 -KGNvbm5lY3Rpb25TdHJpbmc= 78821 -IE5hb21p 78822 -IEphaW1l 78823 -cnRz 78824 -IG1hZ2ljYWxseQ== 78825 -Lm1hY2hpbmU= 78826 -IEFwcGFsYWNo 78827 -Iisi 78828 -dmFsZQ== 78829 -LW1vdW50ZWQ= 78830 -IGFjaGU= 78831 -TUo= 78832 -IFVJSW1hZ2VQaWNrZXJDb250cm9sbGVy 78833 -LUp1bg== 78834 -TWFuYQ== 78835 -a3JhaW5l 78836 -RENG 78837 -L1Byb2R1Y3Q= 78838 -IFJFU0VSVkVE 78839 -IEZIQQ== 78840 -OkAiJUAiLA== 78841 -IFByb2pla3Q= 78842 -IE5pcg== 78843 -IENhcm5pdmFs 78844 -ICom 78845 -IFFT 78846 -V0hP 78847 -IHdlbHQ= 78848 -IG1hcnJ5aW5n 78849 -QWxleGFuZGVy 78850 -IFJldmlld2Vk 78851 -YWN0ZXJpYQ== 78852 -IHdhbg== 78853 -KHJvYm90 78854 -IFdpbmRvd01hbmFnZXI= 78855 -IG1vbnVtZW50YWw= 78856 -IERvbWluZw== 78857 -L3dlYXRoZXI= 78858 -X3NlY29uZGFyeQ== 78859 -T3BlcmF0b3Jz 78860 -X1NJREU= 78861 -S2F0 78862 -LXpvbmU= 78863 -IHNpZ25pZmllcw== 78864 -IEh0dHBNZXRob2Q= 78865 -L2NvbnRleHQ= 78866 -Ig0KDQoNCg== 78867 -IFJvZHJpZ28= 78868 -IGJ1Yg== 78869 -L211c2lj 78870 -IHNlcm9udA== 78871 -IG1STkE= 78872 -X2VtYWlscw== 78873 -ICc+Jw== 78874 -IEdlbWU= 78875 -INGA0LDRgQ== 78876 -IH5+ 78877 -IGR1Y2tz 78878 -IEZyZXVuZA== 78879 -RXhwZXJpbWVudA== 78880 -IHJlb3BlbmVk 78881 -IFwiew== 78882 -IGVsbGlwdA== 78883 -IGNvbmNhdGVuYXRl 78884 -IHBvbG8= 78885 -VGltZVpvbmU= 78886 -ICAKICAgIAo= 78887 -IGNhcHRpb25z 78888 -cmlja3M= 78889 -LmZyZXE= 78890 -Lm1lbW8= 78891 -IHNtYg== 78892 -RHJ1Zw== 78893 -XVsv 78894 -X0JBQ0tFTkQ= 78895 -IEVsbGE= 78896 -IFBvcnRpb25z 78897 -IGZldGNoRGF0YQ== 78898 -IGNvcm91dGluZQ== 78899 -IGVzdGF2YQ== 78900 -IEdlbml1cw== 78901 -OmB+ 78902 -IFN3YW5zZWE= 78903 -KHBheW1lbnQ= 78904 -Vm90cmU= 78905 -IFBydWl0dA== 78906 -Lm9mZnNldFdpZHRo 78907 -YXJ5bA== 78908 -IHVuaWZvcm1seQ== 78909 -IFdhcnA= 78910 -IFNFQQ== 78911 -IGRlZHVjdGlibGU= 78912 -IGJ1bGxpZWQ= 78913 -IEJlc2No 78914 -IFByb3NwZWN0 78915 -T1NQ 78916 -IlllYWg= 78917 -IEFuZ3J5 78918 -LlZhbA== 78919 -IGdpZ3M= 78920 -IGJ1bGt5 78921 -ZXRlcmlh 78922 -LmdldFN0YXJ0 78923 -IE1FVEg= 78924 -IGNvaGVyZW5jZQ== 78925 -IG1lZGlhdGVk 78926 -0LXQs9C40YHRgg== 78927 -Li4uLgo= 78928 -IHN0cm9rZUxpbmU= 78929 -bWo= 78930 -IFVuc3VyZQ== 78931 -YXRocm9vbQ== 78932 -KEJpbmFyeQ== 78933 -X0tleVByZXNz 78934 -5p6E 78935 -aW5oZXJpdHM= 78936 -IHJlcHJlaA== 78937 -CVNjaGVtYQ== 78938 -IHVucmVzdHJpY3RlZA== 78939 -LmRlZmluaXRpb24= 78940 -XT8u 78941 -IGl0aA== 78942 -5aCx 78943 -IHNsaW1l 78944 -bXNncw== 78945 -X0pT 78946 -CVZlcnNpb24= 78947 -X1NFQ1VSRQ== 78948 -IGNvc3Rv 78949 -LlJlc3Ry 78950 -Y3Ny 78951 -X1RPT0xUSVA= 78952 -cGNs 78953 -IOKGkw== 78954 -U2VsZlBlcm1pc3Npb24= 78955 -LnJhdmVs 78956 -IG1lbWJyZXM= 78957 -QXNzZW1ibGVy 78958 -cm9taXVt 78959 -c3VyZg== 78960 -IFVQREFURUQ= 78961 -KGJyYW5jaA== 78962 -KGluY2x1ZGU= 78963 -IElkb2w= 78964 -XE9iamVjdA== 78965 -IGNsb25pbmc= 78966 -IGlzTmFO 78967 -IGFueg== 78968 -xrDhu51uZw== 78969 -IG9uYw== 78970 -X0NMVVNURVI= 78971 -IHt9KSwK 78972 -aW1pbmFyeQ== 78973 -CWNvbnRlbnRQYW5l 78974 -dHJhaWw= 78975 -IG5pbmV0eQ== 78976 -IE5pYWdhcmE= 78977 -IEFuZHI= 78978 -w6lzeg== 78979 -IGRpZmlj 78980 -dXRyYQ== 78981 -J319Pg== 78982 -44Kk44OI 78983 -c3Bhcg== 78984 -ICJcIiw= 78985 -IG15ZmlsZQ== 78986 -ZmZj 78987 -IG5vdGljZWFibHk= 78988 -ZXlh 78989 -IFB1dHRpbmc= 78990 -SlY= 78991 -LmRpbWVuc2lvbnM= 78992 -ZXJjYQ== 78993 -Z2VuZXNpcw== 78994 -ZWZmZWN0aXZl 78995 -IHBlcmRlcg== 78996 -Lk9S 78997 -X0NPTVBBUkU= 78998 -Omxlbg== 78999 -L3JlZA== 79000 -IEFyaXN0b3RsZQ== 79001 -IHF1ZXJpZWQ= 79002 -IGZvcmVzZWVhYmxl 79003 -IFVJQ29udHJvbA== 79004 -cmVtaW5kZXI= 79005 -IGNlbmE= 79006 -IGhpYw== 79007 -ICIiOw0KDQo= 79008 -L2Jhc2lj 79009 -IGFmZm9yZGFiaWxpdHk= 79010 -LGVycg== 79011 -INGB0LjQvNCy 79012 -IElTUg== 79013 -bGljZW5zZXM= 79014 -Vk9JQ0U= 79015 -Lkxhbmc= 79016 -LnJlbGF0aW9uc2hpcA== 79017 -IGxlbmRz 79018 -IG51dHplbg== 79019 -IGVzcGVjw61m 79020 -aWVuZGE= 79021 -PFBhaXI= 79022 -VHY= 79023 -X1JFVFJZ 79024 -IGhvbm9yaW5n 79025 -X2RlY2xhcmF0aW9u 79026 -KE5P 79027 -IEhpY2s= 79028 -IG1pbmxlbmd0aA== 79029 -IEdlc2NoaWNodGU= 79030 -YXBlc2g= 79031 -QVRPTQ== 79032 -JykiKTsK 79033 -ZW50ZXJwcmlzZQ== 79034 -Pn08Lw== 79035 -IHBvbGl0aXF1ZQ== 79036 -ZWRpdGlvbg== 79037 -X0RlYnVn 79038 -QW5uZQ== 79039 -LlNjb3Bl 79040 -Y3Rw 79041 -Y2Fub25pY2Fs 79042 -Pj47Cg== 79043 -TWVudXM= 79044 -IGZpZXJjZWx5 79045 -Lk9uY2U= 79046 -IEJvcnJvdw== 79047 -IHNvc3Q= 79048 -IHNlcnZpbmdz 79049 -LWZsYWc= 79050 -IHZlc3RlZA== 79051 -IGZyb24= 79052 -7ZWo 79053 -IGZhbWluZQ== 79054 -Il0pKXsK 79055 -ZXJlw6dv 79056 -IGtpamtlbg== 79057 -IEZsb29yaW5n 79058 -55CD 79059 -b2JzZXJ2YXRpb24= 79060 -IHVzZXJEYW8= 79061 -PSIiPg0K 79062 -Q09WSUQ= 79063 -YmFieQ== 79064 -IHRyb3VnaA== 79065 -IFNlYW0= 79066 -IEZpZ2h0ZXJz 79067 -b21pdA== 79068 -IENoYXJnZXM= 79069 -UnVzcw== 79070 -IHF1ZWxxdWU= 79071 -R2V0UG9zaXRpb24= 79072 -IE1pbmlzdGVycw== 79073 -X3JlY2VpcHQ= 79074 -IHJvb3ROb2Rl 79075 -bXVsdGlw 79076 -JHNlYXJjaA== 79077 -IikpKSkK 79078 -dGFrZXM= 79079 -ICghIQ== 79080 -IEJBVA== 79081 -Y2hhbmc= 79082 -xJM= 79083 -Lm9j 79084 -IHNraWxsZXQ= 79085 -IFNLVQ== 79086 -IEdhbGxhZ2hlcg== 79087 -IGNyZXNj 79088 -d2Vla2RheQ== 79089 -ZXJ2aXNlZA== 79090 -Q2FyZENvbnRlbnQ= 79091 -LmFjY2Vs 79092 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK 79093 -VGFp 79094 -IENvbXBhdGliaWxpdHk= 79095 -eENG 79096 -X3Jld2FyZHM= 79097 -cmRm 79098 -QVBQTEU= 79099 -LWZlZA== 79100 -IGRlcGVuZGVk 79101 -LWdlbmVyYXRvcg== 79102 -KFByb2Nlc3M= 79103 -0LzQvtC2 79104 -IGRpc2NyZXBhbmN5 79105 -IHBob3NwaGF0ZQ== 79106 -TmV0d29ya2luZw== 79107 -6K6+6K6h5Zmo 79108 -KHJv 79109 -IGNvbmN1cnJlbmN5 79110 -CWF1dGg= 79111 -UGx1Zw== 79112 -QVRBTE9H 79113 -c3Viag== 79114 -L3RlYW0= 79115 -KGF2Zw== 79116 -b2tpbg== 79117 -IHBsZWRnZXM= 79118 -IGNvbGxhYm9yYXRvcnM= 79119 -IGVtYmFya2Vk 79120 -IERvY2g= 79121 -IERhaXJ5 79122 -Y29tcGV0aXRpb24= 79123 -IE11dGFibGVMaXN0 79124 -LXNldmVu 79125 -IGNvbmN1cnJlbnRseQ== 79126 -IFZpag== 79127 -IHJlc2V0dGluZw== 79128 -ZHBp 79129 -IHNsaXQ= 79130 -IFBPSU5URVI= 79131 -IENBUlQ= 79132 -LmRleA== 79133 -Y3Vsb3M= 79134 -X3BlcnNvbmFs 79135 -IGFuYWx5dGlj 79136 -I2NyZWF0ZQ== 79137 -X21lbWNweQ== 79138 -KExpc3ROb2Rl 79139 -X1RhZw== 79140 -IElycg== 79141 -Ij4nOw0K 79142 -U2hvcnRseQ== 79143 -LnRpcA== 79144 -XFs= 79145 -IFJlcHJlc2VudGF0aW9u 79146 -X0xJVEVSQUw= 79147 -LmNibw== 79148 -IEthcm5hdGFrYQ== 79149 -IENvbXBldGl0aXZl 79150 -IFJ1ZQ== 79151 -IHJ1bm9mZg== 79152 -IFNwZWxscw== 79153 -ZmNsb3Nl 79154 -Y2lz 79155 -RnJh 79156 -IHJlbW9yc2U= 79157 -IENvbG9nbmU= 79158 -IHJhbmdlcg== 79159 -IE1vcmc= 79160 -ZmlnaHRlcnM= 79161 -LlJlcXVlc3RQYXJhbQ== 79162 -Q29ycw== 79163 -IGRlbm90ZQ== 79164 -IGNob3Nlcw== 79165 -w6JuZA== 79166 -LnJlY3ljbGU= 79167 -IExvZ2lzdGlj 79168 -IERFQUQ= 79169 -LWxvYWRlZA== 79170 -IENsZWFycw== 79171 -IGtlbGw= 79172 -cmFwaGlj 79173 -IE1hbmU= 79174 -RU1CRVI= 79175 -IG1hc2tpbmc= 79176 -CWVkaXRvcg== 79177 -SGFsbG8= 79178 -Omxpc3Q= 79179 -IGV0aG4= 79180 -LXNlYXQ= 79181 -ICopWw== 79182 -IEdseQ== 79183 -IEFDUw== 79184 -CXN0YXQ= 79185 -L0NvbW1vbg== 79186 -IGRpc2d1aXNlZA== 79187 -RmluYW5jZQ== 79188 -IEVsZXBoYW50 79189 -dGVtcG9yYXJ5 79190 -IENhcmx5 79191 -IGNvY29z 79192 -IEp1ZGl0aA== 79193 -IHdyYXBwZXJz 79194 -IEx1bmFy 79195 -IHLDqWN1cA== 79196 -LXNldHVw 79197 -IHNpemFibGU= 79198 -ICAJIA== 79199 -Y2xhc3NpZmllcg== 79200 -IGZpZ3NpemU= 79201 -IG1hc3R1cg== 79202 -IOabtOaWsA== 79203 -IFJ3YW5kYQ== 79204 -KXQ= 79205 -IEN1cHM= 79206 -QXp1cmU= 79207 -KCl9LAo= 79208 -U1BBUkVOVA== 79209 -KGRpYw== 79210 -IFRleHRGb3JtRmllbGQ= 79211 -IGRlZm9ybQ== 79212 -IGRpcmVjY2nDs24= 79213 -IHlheg== 79214 -IGdsdWVk 79215 -IGF0cmF2w6lz 79216 -Y29mZmVl 79217 -IFVwZGF0aW5n 79218 -IENvbGxlZ2Vz 79219 -w6RsbHQ= 79220 -YW5kZWxpZXI= 79221 -IHNhbGly 79222 -IFNDQUxF 79223 -cWU= 79224 -6rO1 79225 -KHJlY2VpdmVy 79226 -bWRi 79227 -Im1hdGg= 79228 -aXNuYW4= 79229 -dGVsZWZvbmU= 79230 -UkVQT1JU 79231 -LmFkZE1vdXNlTGlzdGVuZXI= 79232 -ZHVlZA== 79233 -e31d 79234 -KCkpOg== 79235 -IHdvcmtpbmdz 79236 -fSk7CgoKCg== 79237 -IGNvbXBvbmVudFdpbGxNb3VudA== 79238 -U2VydmVycw== 79239 -X0NMT1NFRA== 79240 -SVpFUg== 79241 -IGJvb2I= 79242 -IENPTkNBVA== 79243 -IEhhcHBpbmVzcw== 79244 -IGNvbW11bmU= 79245 -eEFC 79246 -b3duZXJzaGlw 79247 -X05FQVI= 79248 -X0hBUkQ= 79249 -IFlB 79250 -bGlvbg== 79251 -IHNwaWVs 79252 -IHRhZ2dpbmc= 79253 -IGltbW9yYWw= 79254 -LWdyb3VuZA== 79255 -IHRodW5r 79256 -IGxvY3Vz 79257 -IExhdHZpYQ== 79258 -aXppb25p 79259 -Y2xhcnNpbXA= 79260 -IHBhdGllbnRseQ== 79261 -XEhhcw== 79262 -IHN1Ym9yZGluYXRl 79263 -IFdISUNI 79264 -ZW50aW9uUG9saWN5 79265 -IGRlcGxldGVk 79266 -RlNJWkU= 79267 -IFss 79268 -IEJpb2dyYXBoeQ== 79269 -IFNhbmRz 79270 -U0hBUkU= 79271 -Q2hhcnNldA== 79272 -LndyaXQ= 79273 -X1NVUw== 79274 -IE1vcmVubw== 79275 -IGJyb2Njb2xp 79276 -IFZY 79277 -YW1pY3M= 79278 -LkdldFVzZXI= 79279 -IENvbW1vZA== 79280 -LnNjaGVtZQ== 79281 -KHZz 79282 -IGFuYWxvZ291cw== 79283 -UHN5 79284 -PWxpbmU= 79285 -LnB1Ymxpc2hlcg== 79286 -IG9ud2FyZA== 79287 -0LXQutGB 79288 -IERlYWxlcnM= 79289 -IHRvQXJyYXk= 79290 -IENob2ljZXM= 79291 -0JTQvtCx0LDQsg== 79292 -IGRlZmF1bHRNZXNzYWdl 79293 -IGFncmVn 79294 -IENvbmNhdA== 79295 -SFY= 79296 -IENpcmN1bGFyUHJvZ3Jlc3M= 79297 -X3N2Yw== 79298 -VEFC 79299 -X2ZpbA== 79300 -Lk1hcFBhdGg= 79301 -emJ1cmc= 79302 -IGdldFByb2R1Y3Q= 79303 -IFZFUklGWQ== 79304 -Lk1vbmdv 79305 -IHB1bmRpdHM= 79306 -cHVsc2U= 79307 -bGljdGluZw== 79308 -Z2lhdGFu 79309 -IC4uLiI= 79310 -IGZpeg== 79311 -IGFudGlt 79312 -IENoYXR0 79313 -X1RZUEVERUY= 79314 -R3V5 79315 -CXRlc3Rz 79316 -IFNsb3Zlbmlh 79317 -IENvbW1hbmRMaW5l 79318 -IGJlbmVmaWNpYXRpb24= 79319 -IGJpbmRBY3Rpb25DcmVhdG9ycw== 79320 -TlRBWA== 79321 -LUNz 79322 -IGNoYXJpc21hdGlj 79323 -LmFsbG9j 79324 -X25m 79325 -IGFzc2F1bHRpbmc= 79326 -INGC0LDQsdC70LjRhg== 79327 -IGPDoWM= 79328 -IFNjcm9sbHM= 79329 -SEFT 79330 -eXl5eU1NZGQ= 79331 -IEdhbGU= 79332 -IFByb3plbnQ= 79333 -IFRob3JudG9u 79334 -ZGVhbGVy 79335 -IGV2aWN0aW9u 79336 -IGFuYWxl 79337 -4oCO 79338 -PSIo 79339 -IGVhZw== 79340 -KCcnKTsKCg== 79341 -IGNvbnRlbXBsYXRpbmc= 79342 -aHlw 79343 -YmVsdW0= 79344 -IEZpdHM= 79345 -IEV4YW1pbmVy 79346 -IEJ1Y2M= 79347 -IG1lbWJyYW5lcw== 79348 -IGJyaWxsaWFudGx5 79349 -IENlcmFtaWM= 79350 -w6h2ZQ== 79351 -IFBvdW5k 79352 -IHRyZWFzdXJ5 79353 -LicpOw0K 79354 -CXRj 79355 -ZWNha2U= 79356 -Q3VycmVudFVzZXI= 79357 -LmhhYmJv 79358 -IHRyZWFzb24= 79359 -IEZUQw== 79360 -TVVY 79361 -IG51bWJlcmluZw== 79362 -UklB 79363 -LS0pDQo= 79364 -IGJlaWdl 79365 -IEFydGVt 79366 -YmFzZXM= 79367 -X0JBTkQ= 79368 -IFBhdmVs 79369 -0YHRgtGA0YPQug== 79370 -dGhlZA== 79371 -X25icg== 79372 -INCx0LDQtw== 79373 -c2xpZGVVcA== 79374 -IFRheGk= 79375 -IGFxdWVs 79376 -IE1pc2NlbGxhbmVvdXM= 79377 -ZWx1 79378 -IGluc3VsYXRlZA== 79379 -IGFzc2V6 79380 -LkNvbmZpZ3VyZQ== 79381 -IHF1ZWxsYQ== 79382 -IHBhcmFzaXRlcw== 79383 -QXdheQ== 79384 -ZHVjaWJsZQ== 79385 -KCc9Jw== 79386 -IHZlcm8= 79387 -IFdhdGtpbnM= 79388 -IFNlcGFyYXRvcg== 79389 -YXBzZXM= 79390 -ZW52aXJvbm1lbnRz 79391 -IGFwcHJhaXNhbA== 79392 -cGF1c2Vk 79393 -X2RlYXRo 79394 -IHNpdHVhY2nDs24= 79395 -IGZyYXRlcm5pdHk= 79396 -IGluc2lzdGVuY2U= 79397 -X2NyeXB0bw== 79398 -QXR0cmliUG9pbnRlcg== 79399 -Il1dLAo= 79400 -IG94aWRhdGl2ZQ== 79401 -IG5ldXJvbmFs 79402 -IFFHcmFwaGljcw== 79403 -Ij4nLA== 79404 -IFNtaWxl 79405 -T2JqZWN0aXZl 79406 -IFNha3VyYQ== 79407 -Wk8= 79408 -YW1pZW50b3M= 79409 -LkxvY2FsRGF0ZVRpbWU= 79410 -L3VuaXQ= 79411 -LWZyZXF1ZW5jeQ== 79412 -LUNT 79413 -In07Cgo= 79414 -IHJlbGV2 79415 -QWxsb2NhdGlvbg== 79416 -JU0= 79417 -IER1c3Rpbg== 79418 -IHN3aXBlcg== 79419 -IE5hcmM= 79420 -dGF0dXM= 79421 -IGxvbmdpbmc= 79422 -IHRodWlzb250dmFuZ3N0 79423 -IGNvbW1vZG8= 79424 -IEFEQQ== 79425 -aW11 79426 -X2ZvcnVt 79427 -YW5naQ== 79428 -CUFwcGxpY2F0aW9u 79429 -W2Zyb20= 79430 -IEJldGhlc2Rh 79431 -b3Ryb3BpYw== 79432 -IE1VQ0g= 79433 -IHByZWRpYw== 79434 -ZmlsbWU= 79435 -KGdyYW1tYXI= 79436 -KEFQUA== 79437 -IEN1cmw= 79438 -IHNob3J0aGFuZA== 79439 -YWZmaWxpYXRl 79440 -XSoq 79441 -X250aA== 79442 -aWFiaWxpdHk= 79443 -Ym9tYg== 79444 -WVQ= 79445 -KCItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 79446 -IEJpY3ljbGU= 79447 -aW1hdGluZw== 79448 -Lm5paQ== 79449 -IEthcmE= 79450 -YXNrYW4= 79451 -cmVhY3RzdHJhcA== 79452 -IHdsYW4= 79453 -b2dyYXBoZXJz 79454 -CSANCg== 79455 -cGFnaW5hdG9y 79456 -aWhhbm5h 79457 -IG1hdGNodXBz 79458 -X1BBRERJTkc= 79459 -X3JlZ2lzdGVycw== 79460 -eXRl 79461 -IHByaWNleQ== 79462 -IGZvb3Ro 79463 -IEh1Y2s= 79464 -UEFSVE1FTlQ= 79465 -IHByb2hpYml0aW5n 79466 -LmlzRGVidWdFbmFibGVk 79467 -4KS4 79468 -bGVpbg== 79469 -PXJlcw== 79470 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 79471 -ZGRs 79472 -bXBy 79473 -IOqwmQ== 79474 -IFdBTEw= 79475 -IHJldm9sdmVz 79476 -IFBFUkY= 79477 -KTt9 79478 -IFRvYnk= 79479 -Ly4uLw== 79480 -IGthbw== 79481 -IGZvcmVjYXN0aW5n 79482 -X0NvbnRlbnQ= 79483 -IH0pKSwK 79484 -cG9ybm8= 79485 -bGVhZGVycw== 79486 -LWhvb2tz 79487 -aXN0cmlidXRvcg== 79488 -L3N0b3J5 79489 -CWxpbmVz 79490 -LXJlcGx5 79491 -IGFkcmVuYWxpbmU= 79492 -Rmxvd0xheW91dA== 79493 -LnJvdXRpbmc= 79494 -CXRpbWVvdXQ= 79495 -IHJhaWRlZA== 79496 -CURE 79497 -IGRpc2RhaW4= 79498 -Y29uc2lzdGVudA== 79499 -Z2Vpc3Q= 79500 -KCI6Lw== 79501 -KHN0YXRlcw== 79502 -IEhJVA== 79503 -LVJheQ== 79504 -LWhlYWx0aA== 79505 -IC8vLQ== 79506 -dGVtZW50 79507 -Lm5hdmlnYXRlVG8= 79508 -IGJlbmNoZXM= 79509 -ZXdpbmc= 79510 -ZW56aGVu 79511 -LXNwbGl0 79512 -UmVqZWN0 79513 -IHB5bGFi 79514 -IGZsYXNobGlnaHQ= 79515 -IGluaXRpYXRpbmc= 79516 -IE9FQ0Q= 79517 -IGVudHJlZ2E= 79518 -TmF0dXJl 79519 -Lm9yYW5nZQ== 79520 -IMO6bHRpbW9z 79521 -IGVjcw== 79522 -LmhvdmVy 79523 -IGRlbHV4ZQ== 79524 -Um9nZXI= 79525 -IFRpYw== 79526 -IixfXw== 79527 -IHBsYWNlaG9sZGVycw== 79528 -IHNwYXduaW5n 79529 -IG51cnR1cmU= 79530 -IGV4Y2hhbmdpbmc= 79531 -Q3JlYXRlRGF0ZQ== 79532 -IGxhbWlu 79533 -IFNlbWljb25kdWN0b3I= 79534 -ICovCgoKCg== 79535 -IGbDuHJzdGU= 79536 -IGluaXRpYWxz 79537 -IHByb3ZlcmI= 79538 -IEFjdHJlc3M= 79539 -Q29uY2F0 79540 -IE5pY29sYQ== 79541 -LXNob3BwaW5n 79542 -aXZpdMOg 79543 -aXRpYW4= 79544 -IFdlcnQ= 79545 -LkFkZFNjb3BlZA== 79546 -IHNhbGVzbWFu 79547 -Ym9z 79548 -IEZlcnJ5 79549 -Q0VOVEVS 79550 -bW9kZWxv 79551 -IFJvZQ== 79552 -IElzbGFuZGVycw== 79553 -dXBlcnRpbm8= 79554 -RGVjbGFyZQ== 79555 -IHZvd2Vscw== 79556 -IGJveGVy 79557 -KHRvb2xiYXI= 79558 -IGhhbGZ0aW1l 79559 -bmlu 79560 -IEJyb29rZQ== 79561 -IFZlcw== 79562 -0LvQsNGC 79563 -IG1vdGl2bw== 79564 -cHJvdGVpbg== 79565 -a3Vz 79566 -YnVzeQ== 79567 -IHN0cmluZ1ZhbHVl 79568 -CU15 79569 -TnV0 79570 -dXp6aQ== 79571 -IHNleg== 79572 -IG9sZHM= 79573 -IG1ldGh5bA== 79574 -IGLDvA== 79575 -aGliYQ== 79576 -IEluc3BpcmF0aW9u 79577 -IGF3YWl0ZWQ= 79578 -QnJ1Y2U= 79579 -QkFMTA== 79580 -IFRSWQ== 79581 -LWxpdGU= 79582 -IHVuZGVyZXN0aW1hdGU= 79583 -CXJ2 79584 -Lm1vdg== 79585 -IGhpc3TDsw== 79586 -IEVyaWU= 79587 -Y25hbWU= 79588 -L2Nvbm5lY3Q= 79589 -Y29uZmVyZW5jZQ== 79590 -X3RyYWl0 79591 -IGt2aW5kZQ== 79592 -IEludm9jYXRpb24= 79593 -IERhdGVUaW1lT2Zmc2V0 79594 -d2VjaGF0 79595 -Q0VP 79596 -IExpYnlhbg== 79597 -LmNhcGl0YWxpemU= 79598 -IGdyYWNlZnVsbHk= 79599 -IHJlZWxz 79600 -aW5jcmVhc2U= 79601 -Lm1heGNkbg== 79602 -ZmF2b3JpdGVz 79603 -SVRFRA== 79604 -PFNjYWxhcg== 79605 -LkZldGNo 79606 -IHN1c3BpY2lvbnM= 79607 -W01BWE4= 79608 -X1RSQU5TQUNUSU9O 79609 -IGN5bGluZHJpY2Fs 79610 -Lm5leHRFbGVtZW50 79611 -IG1vcnBob2xvZ3k= 79612 -IENlZA== 79613 -IGNuYW1l 79614 -KHJhd1ZhbHVl 79615 -V2Fsa2luZw== 79616 -TG9hZHM= 79617 -X0FMSUdOTUVOVA== 79618 -X1JPVU5E 79619 -IFJPQ0s= 79620 -Y2x1c3RlcnM= 79621 -Img= 79622 -dWV1cg== 79623 -cGxhbnM= 79624 -IGF0aGVpc3Rz 79625 -IHZhdA== 79626 -PSJfXw== 79627 -YXdhaA== 79628 -ZXJ2YXRpdmVz 79629 -IGZpbmRPbmU= 79630 -IG5vdGVib29rcw== 79631 -IFRUTA== 79632 -LkdldEFzeW5j 79633 -IG3DvG5jaGVu 79634 -bUFo 79635 -YnJ0Yw== 79636 -X1BZ 79637 -QnVpbGRlckludGVyZmFjZQ== 79638 -CWdiYw== 79639 -IGJsYW5rcw== 79640 -IGTDqW0= 79641 -UmVjdXJzaXZl 79642 -Lk1hbnlUb01hbnlGaWVsZA== 79643 -X1BBUlNFUg== 79644 -IGVuZGVhdm9ycw== 79645 -IGRyaWI= 79646 -X3BocA== 79647 -IGF1dG9tb2JpbGVz 79648 -bG9pdA== 79649 -IE9ydGl6 79650 -IFVE 79651 -KGRBdEE= 79652 -IE1pdHN1YmlzaGk= 79653 -QXR0cmlidXRlVmFsdWU= 79654 -IHBvYXRl 79655 -55u45YWz 79656 -IGNhdmFscnk= 79657 -Lk1hdGNoZXJz 79658 -IGluZ3Jlc3M= 79659 -IEplaG92YWg= 79660 -CXNlcQ== 79661 -X3N0cmVldA== 79662 -IFNvZmlh 79663 -IHNjcm9sbHM= 79664 -dmluY2Vz 79665 -ZWxlY3Ryb25pY3M= 79666 -XHBhcmFt 79667 -IHplbmQ= 79668 -IHNraW0= 79669 -LnBpeA== 79670 -ZW5r 79671 -X2FyZWFz 79672 -IEJvaXNl 79673 -LXZhbGlkYXRvcg== 79674 -IHVuZWFydGg= 79675 -b2ZpbG0= 79676 -IEJDRQ== 79677 -b3Zza3k= 79678 -IExldmVy 79679 -IHBvbGljZW1hbg== 79680 -IG1pZXM= 79681 -IFBvcnRyYWl0 79682 -IHBvdGlvbnM= 79683 -X21vdA== 79684 -bWFzc2FnZQ== 79685 -0LXQvdGL 79686 -IGN1ZA== 79687 -IG1hbnVzY3JpcHRz 79688 -Y29udGludW91cw== 79689 -LnRj 79690 -w7x6 79691 -IEZyZWV6ZQ== 79692 -Xzoq 79693 -Lmht 79694 -IENTUkY= 79695 -IE3DpGRjaGVu 79696 -LXBlZXI= 79697 -IHB1dFN0ckxu 79698 -IGltc2hvdw== 79699 -IEB7JA== 79700 -IEJhdWVy 79701 -KHRvbHVh 79702 -IHdyb3VnaHQ= 79703 -IEdpYW4= 79704 -IMO2bg== 79705 -ZnVuZw== 79706 -QnV0dG9uVGl0bGVz 79707 -fSkiLA== 79708 -IE11cmRvY2g= 79709 -S1c= 79710 -IFJlcG9ydGVk 79711 -c2ll 79712 -IG1laWxsZXVycw== 79713 -IEthZXBlcm5pY2s= 79714 -IGRzcA== 79715 -IEV2ZXJ5ZGF5 79716 -cmVuZHM= 79717 -IENvbmNl 79718 -IGluY29udHI= 79719 -LnJlbW92ZUF0dHJpYnV0ZQ== 79720 -44G+44GX44Gf 79721 -IHJldw== 79722 -IFByZXNlbmNl 79723 -L2dpbg== 79724 -LkNsYWltcw== 79725 -CXNs 79726 -RHJhZ2dpbmc= 79727 -IHNwcmVl 79728 -IGFjdHVhbGl6YXI= 79729 -IG5vc3M= 79730 -IGxpZmVzdHlsZXM= 79731 -O2M= 79732 -VURHRQ== 79733 -SW5NaWxsaXM= 79734 -IGl0aw== 79735 -YWJieQ== 79736 -KHBh 79737 -aXNzZW50 79738 -IFByZXNpZGVudHM= 79739 -IEhleGF0cmlnZXNpbWFs 79740 -ZWNpZGVk 79741 -KHRleA== 79742 -IGNyb3duZWQ= 79743 -UGhpbGlw 79744 -IFNhcms= 79745 -IEFkZGl0aW9u 79746 -IENvbGJlcnQ= 79747 -IEdMRVM= 79748 -IFFMaW5lRWRpdA== 79749 -IGRyYWlucw== 79750 -IHNvcnRPcmRlcg== 79751 -ZXNjb3J0 79752 -VGVk 79753 -IG1hbmlmZXN0ZWQ= 79754 -LnZhcmlhbnQ= 79755 -IFJFRkVSRU5DRVM= 79756 -KGdj 79757 -L3sk 79758 -b2N5dGU= 79759 -IG9ybmFtZW50 79760 -IGJvb2tzdG9yZQ== 79761 -SG9s 79762 -IFZhbGw= 79763 -Lycp 79764 -YWNhaw== 79765 -IE5hdkJhcg== 79766 -IG55ZQ== 79767 -X0RlYw== 79768 -b2x2aW1lbnRv 79769 -TVJJ 79770 -IGhvb3A= 79771 -ICAgCiAgICAK 79772 -IFBvc3Rpbmc= 79773 -IG91dGxpbmluZw== 79774 -YWdhc2Nhcg== 79775 -LmJyZWFrcG9pbnRz 79776 -Y2F0aWQ= 79777 -X3RyaWdnZXJlZA== 79778 -IHJ1bm5hYmxl 79779 -L3RydW5r 79780 -LWNoYWly 79781 -IGJhaXNlcg== 79782 -ZmFjaWxpdHk= 79783 -IHBvbGxlbg== 79784 -6Z+z 79785 -IFtbIg== 79786 -IENHU2l6ZU1ha2U= 79787 -IGFzc2FpbA== 79788 -IEF0aGVuYQ== 79789 -IEFkZGljdGlvbg== 79790 -aWxhbmQ= 79791 -O2Jy 79792 -LktleWJvYXJk 79793 -X2Zt 79794 -QWNl 79795 -IFJFUQ== 79796 -IE5ld2VzdA== 79797 -Oy4= 79798 -IE1BREU= 79799 -c2V0VGltZW91dA== 79800 -U2VydmxldENvbnRleHQ= 79801 -CQkJCQkgICAgICAg 79802 -IEx1cA== 79803 -LXJldmlld2Vk 79804 -IEFuYWx5emVy 79805 -Lk5hTg== 79806 -dXR1cmE= 79807 -R2VvbQ== 79808 -eW1lcw== 79809 -X3Npbg== 79810 -IHRydXN0ZWVz 79811 -Ly89PT0= 79812 -IGFkbWl0dGVkbHk= 79813 -IGFrbw== 79814 -IFVFRkE= 79815 -X2hlcm8= 79816 -R2l0aHVi 79817 -X2VzdGltYXRl 79818 -IGNvcnJvYm9y 79819 -ZW50aWZ1bA== 79820 -IFN0ZWVyaW5n 79821 -IE1pdGFy 79822 -IFBpcGVz 79823 -IGvDpQ== 79824 -X3NlYXNvbg== 79825 -IEJDSFA= 79826 -L3NvZnR3YXJl 79827 -bmV0dGU= 79828 -KiIs 79829 -dW5kcmE= 79830 -IGdldFJlcXVlc3Q= 79831 -LkJ1ZmZlcmVk 79832 -ZmVybg== 79833 -TWFyaW8= 79834 -IGRpc3BlcnM= 79835 -X2NhdGVnb3JpYQ== 79836 -IGVuZGxlc3NseQ== 79837 -Z3VhcmRz 79838 -CWF0b21pYw== 79839 -c2NvcGVk 79840 -IHVuZG9uZQ== 79841 -U0hPUA== 79842 -IFRvcmNo 79843 -IEhhc3Rpbmdz 79844 -IEZJTEVT 79845 -X1NhdmU= 79846 -V2l0aE1hbnk= 79847 -V2lz 79848 -IGludGVuc2lmaWVk 79849 -LmFyZ3VtZW50 79850 -IEFwaVNlcnZpY2U= 79851 -IEpTSW1wb3J0 79852 -ZWtp 79853 -SW5zdXJhbmNl 79854 -c3R5 79855 -LmRzbA== 79856 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo= 79857 -bHRyZQ== 79858 -U0VH 79859 -RFJBTQ== 79860 -LWJsb2NraW5n 79861 -0L3QtQ== 79862 -cGlyaW5n 79863 -IFBSRVM= 79864 -IEZhY2g= 79865 -IHNhcmM= 79866 -IFNNRQ== 79867 -IEVsZW0= 79868 -IENhbGlmb3Ju 79869 -VW5zYWZl 79870 -IENvbXBvc2Vy 79871 -KGRlcA== 79872 -IEF0dGVuZA== 79873 -ICopKCg= 79874 -IHRlYXNlZA== 79875 -IEFUSQ== 79876 -KHBt 79877 -ICIoXDw= 79878 -J10r 79879 -IHNlY3Rhcmlhbg== 79880 -IFBoYXJtYQ== 79881 -RUk= 79882 -CVRva2VuTmFtZUlkZW50aWZpZXI= 79883 -w6d1 79884 -IGF1Z21lbnRhdGlvbg== 79885 -IHNhamE= 79886 -IGNvbG9yZQ== 79887 -ZGVhZGxpbmU= 79888 -LklURU0= 79889 -IFJpeQ== 79890 -bWFhbA== 79891 -CWNsaWNr 79892 -UGVybWFuZW50 79893 -SG91c3Rvbg== 79894 -UmVzcG9uc2l2ZQ== 79895 -IEVyZ2Vibg== 79896 -ICIlIg== 79897 -LnRvT2JqZWN0 79898 -CXBpZA== 79899 -LlN1Ykl0ZW1z 79900 -IFsr 79901 -IGZ1bmd1cw== 79902 -IGJyb2NodXJl 79903 -IEFwcHJveGltYXRlbHk= 79904 -IG1paw== 79905 -dmVsb3Blcg== 79906 -IHBhZ2FtZW50bw== 79907 -5Yqo55Sf5oiQ 79908 -IGN5dA== 79909 -IFRlbXBs 79910 -ZW5pYWJsZQ== 79911 -IENvbmFu 79912 -IHNldGJhY2s= 79913 -b2JsaW5z 79914 -IE5UTg== 79915 -b3NzYWw= 79916 -VkVSQk9TRQ== 79917 -LmJpbw== 79918 -IMWe 79919 -4buf 79920 -IEdyaXA= 79921 -PCo= 79922 -VFJJRVM= 79923 -LmNob29zZQ== 79924 -UGhvZW5peA== 79925 -IHByb3ZpbmNpYQ== 79926 -TUZMT0FU 79927 -Q2Fycw== 79928 -IHJldHJvc3BlY3RpdmU= 79929 -IGFnb255 79930 -IGxsZW4= 79931 -IGJ1bXBlZA== 79932 -eWxhdGlvbg== 79933 -IHdhcnRv 79934 -IHRvZGRsZXJz 79935 -bGF2 79936 -KHBhdGllbnQ= 79937 -ICgpLT4= 79938 -Y2xj 79939 -IG9uQWN0aXZpdHlSZXN1bHQ= 79940 -IGVtdWxhdGlvbg== 79941 -IGJ1bGxk 79942 -X0FVVEhPUg== 79943 -Pk8= 79944 -L3F1 79945 -IMK2 79946 -CWhy 79947 -c3RkQ2xhc3M= 79948 -IHNwYWNlcg== 79949 -VHJhbnNsYXRlZg== 79950 -LmFkag== 79951 -Oml0ZW0= 79952 -IGV4aGF1c3Rpbmc= 79953 -cGx4 79954 -IHJldml0YWw= 79955 -xZtuaWU= 79956 -IGNhbGlmb3JuaWE= 79957 -c2V0U3RhdGU= 79958 -L3RhYg== 79959 -aW5kc2lnaHQ= 79960 -X0xldmVs 79961 -aW1pbGFy 79962 -Lm5hdmlnYXRvcg== 79963 -IHRlbXBlcmFtZW50 79964 -IGRpZsOtYw== 79965 -IGluZXhwZXJpZW5jZWQ= 79966 -IGltcHJpbnQ= 79967 -IFJlc2lzdA== 79968 -X0ZPTExPVw== 79969 -IFJldHJ5 79970 -IGVuZ2FnZW1lbnRz 79971 -Q2FuQmVDb252ZXJ0ZWQ= 79972 -IHNpbmdsZWQ= 79973 -Lmljb25z 79974 -IGNvbmRvbXM= 79975 -IEZlYXRoZXI= 79976 -bGVybmVu 79977 -KWI= 79978 -IE5wZ3NxbA== 79979 -IENvbnNvbGlk 79980 -cGVrdA== 79981 -56uv 79982 -c3RyaW5nVmFsdWU= 79983 -R2Ft 79984 -IFNpbmFp 79985 -IE9iamVjdFR5cGU= 79986 -X2lucA== 79987 -IHBhcnRp 79988 -IFdhdGVycHJvb2Y= 79989 -IGNvbGxpZGVk 79990 -IGFpcnM= 79991 -L3dvcmxk 79992 -L1NlYXJjaA== 79993 -X3N5bnRheA== 79994 -xZ9p 79995 -X2Fubm90YXRpb25z 79996 -IFRhY28= 79997 -TEFU 79998 -IE9wY29kZQ== 79999 -44CC4oCdCgo= 80000 -IGxlYXNo 80001 -IEFsaWNpYQ== 80002 -77yM6buY6K6k 80003 -IFRTQQ== 80004 -IGhvdHRlcg== 80005 -X0hhbmRsZVR5cGVEZWY= 80006 -Z2luYXM= 80007 -IGluZGlmZmVyZW50 80008 -Q3VzdG9tTGFiZWw= 80009 -kZA= 80010 -b2R5bmFtaWNz 80011 -T25VaVRocmVhZA== 80012 -IENhcmE= 80013 -LmRldmljZXM= 80014 -IEZvcmVpZ25LZXk= 80015 -PicpOw0K 80016 -LmJ1dA== 80017 -LnRpZg== 80018 -IOaWsA== 80019 -IE9rSHR0cENsaWVudA== 80020 -KFRleHR1cmU= 80021 -LlNPQ0s= 80022 -KGluc3Ry 80023 -bWlzdA== 80024 -VW5uYW1lZA== 80025 -U3I= 80026 -Km51bQ== 80027 -KE5VTQ== 80028 -KioqKioKCg== 80029 -L2hlbHA= 80030 -YmVlbGQ= 80031 -LmFkanVzdA== 80032 -X1Bhcm1z 80033 -X0FOR0xF 80034 -VFJFRQ== 80035 -IGVzdHVkaW8= 80036 -d29ya3NoZWV0 80037 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCg== 80038 -QWR2aWNl 80039 -w7bDn2U= 80040 -bkVudGVy 80041 -YcSH 80042 -IGFnZWluZw== 80043 -IEt1cmRpc3Rhbg== 80044 -X1JUQw== 80045 -YmFua3M= 80046 -LlVS 80047 -IGluY2FybmF0aW9u 80048 -IGdsYW1vdXI= 80049 -IOOCuQ== 80050 -IGltcGVyaWFsaXNt 80051 -7J6F64uI64uk 80052 -IHNpZGVsaW5l 80053 -LkFycmF5QWRhcHRlcg== 80054 -IyMjIyMjCg== 80055 -IFN5cmlhbnM= 80056 -IEF0dGVuZGFuY2U= 80057 -LWVzcXVl 80058 -IGdyZW5hZGVz 80059 -X3Fvcw== 80060 -T1ND 80061 -X2Rvb3I= 80062 -LkNhcA== 80063 -REFM 80064 -IGFtYnVzaA== 80065 -CWVz 80066 -VG9Kc29u 80067 -TWFudWZhY3Q= 80068 -RW1lcmdlbmN5 80069 -IFFGaWxl 80070 -IOWV 80071 -CUxQ 80072 -5pCc57Si 80073 -IEdhcmxhbmQ= 80074 -LmNvbm5lY3Rpb25z 80075 -LlJlYWRGaWxl 80076 -IEh3eQ== 80077 -4oCUZXZlbg== 80078 -eERF 80079 -IG5vdXZlbGxlcw== 80080 -IEh1c3M= 80081 -RGVwb3NpdA== 80082 -X2ZvcmVpZ24= 80083 -YWJhag== 80084 -IFBveg== 80085 -ZGJ1cw== 80086 -IGlvZA== 80087 -w5cKCg== 80088 -IENoZWVycw== 80089 -SmVzc2ljYQ== 80090 -IHNhaXNvbg== 80091 -IFB0eQ== 80092 -Ij48IS0t 80093 -aW5vYQ== 80094 -ZXhjbHVkaW5n 80095 -IGJpdHRlcm5lc3M= 80096 -dWVsaW5n 80097 -UHJvdGVjdGlvbg== 80098 -IEJlcmdlbg== 80099 -CQkJIAo= 80100 -QkVM 80101 -IFRvYmlhcw== 80102 -IHVwZA== 80103 -67KE 80104 -IGZvbGlhZ2U= 80105 -X1BVUg== 80106 -IEFkdm9jYXRl 80107 -IG9uUmVxdWVzdA== 80108 -LnBhcnRpdGlvbg== 80109 -IERldmVsb3BlZA== 80110 -IGNyaWI= 80111 -0YHQutC4 80112 -dm91Y2hlcg== 80113 -IEludGVyc2VjdGlvbg== 80114 -IG5pZWNl 80115 -IGxr 80116 -IENhdWN1cw== 80117 -KFsNCg== 80118 -IERldGVjdG9y 80119 -L2xn 80120 -IEhlZGdl 80121 -IHNsdWdn 80122 -YW5nc3Ryb20= 80123 -IENvbnRyb2xsZXJCYXNl 80124 -CXl5 80125 -LnBw 80126 -IEtsaW5n 80127 -IExUUw== 80128 -4oaT 80129 -YXJyYQ== 80130 -Z2V0SlNPTg== 80131 -X3dlYnNpdGU= 80132 -IGlkaW90cw== 80133 -IE1lZ2hhbg== 80134 -QnV0dG9uTW9kdWxl 80135 -ICU+ 80136 -IHByb2plY3RpbGVz 80137 -c3dvcmQ= 80138 -ICAgIAkJCQkJ 80139 -IGFzc2Vz 80140 -IFN1Y2hl 80141 -IGtlZA== 80142 -csOhZg== 80143 -IHNhcsOg 80144 -TEVuY29kZXI= 80145 -UkFORA== 80146 -IFNvbWVob3c= 80147 -IFNhbGE= 80148 -IG11bHRpbQ== 80149 -IG51bVJvd3M= 80150 -IFJvY2tpZXM= 80151 -IHhk 80152 -IGRpc3Byb3BvcnRpb25hdGU= 80153 -CVJUTEk= 80154 -CVVSTA== 80155 -YWdsaQ== 80156 -IFN1YkxPYmplY3Q= 80157 -IEdyYXZlcw== 80158 -X3JlZ3VsYXJpemVy 80159 -X2NoYXJhY3RlcnM= 80160 -LmFuYWx5dGljcw== 80161 -Lm1vZHM= 80162 -IGltcHJvdmlz 80163 -IEJsb2NrUG9z 80164 -X2luc3RhbGxlZA== 80165 -X0NPTlRJTlVF 80166 -L2Rvd24= 80167 -U09D 80168 -LmFwaVVybA== 80169 -LlVzZXJTZXJ2aWNl 80170 -VHJlZXM= 80171 -5oqV 80172 -X292ZXJmbG93 80173 -YXVzYWw= 80174 -Ym94ZWQ= 80175 -Jgo= 80176 -IEphY3F1 80177 -X3Vzcg== 80178 -SU5UUg== 80179 -IHNpZ25hZ2U= 80180 -IGNvY2g= 80181 -Tm9ybWFsaXplZA== 80182 -CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo= 80183 -IHN1c3RhaW5pbmc= 80184 -IFNjcmFw 80185 -cHJhYWs= 80186 -LWF2YXRhcg== 80187 -LndlYnNpdGU= 80188 -KGd1aQ== 80189 -PXJlc3BvbnNl 80190 -KG9wZXJhdG9y 80191 -IGVmZm9ydGxlc3M= 80192 -IEFjdGlvbkJhcg== 80193 -RkZF 80194 -56uL 80195 -CVJlZ2lzdGVy 80196 -QVJTRQ== 80197 -KW4= 80198 -IE1PU1Q= 80199 -X1NQUg== 80200 -X0NISVA= 80201 -YXNk 80202 -IHRvcExlZnQ= 80203 -IFR4dA== 80204 -0LDQttC0 80205 -LlZvbHVtZQ== 80206 -IGlubGV0 80207 -IGZyYWN0dXJlZA== 80208 -IExvbmdpdHVkZQ== 80209 -IERyYW0= 80210 -LkNvbm5lY3Rpb25TdHJpbmdz 80211 -YWJlZQ== 80212 -cGVyYXRl 80213 -am5p 80214 -YHQ= 80215 -ZmluZ2Vy 80216 -IEplc3NpZQ== 80217 -LGxs 80218 -IFJ1ZHk= 80219 -IGdlbmVyb3VzbHk= 80220 -X0NPTlZFUlQ= 80221 -IGVpdXNtb2Q= 80222 -IERhaQ== 80223 -aW1hZ2lu 80224 -IEdPYmplY3Q= 80225 -IMSRw6M= 80226 -aWRpb3Vz 80227 -cmlkZ2Vk 80228 -IHNvcHI= 80229 -0LvQsNC0 80230 -IHN0aXRjaGluZw== 80231 -IGtyYg== 80232 -CiAgICAgICAgCiAgICAgICAgCg== 80233 -IGxhdmlzaA== 80234 -IENpdg== 80235 -U3RhcnRFbGVtZW50 80236 -IExvbA== 80237 -CXV0aWw= 80238 -J11dLg== 80239 -IE1hbGF5 80240 -IC4NCg== 80241 -548= 80242 -X0ludm9rZQ== 80243 -aXZpc3Q= 80244 -RGVwZW5kaW5n 80245 -KSI7DQo= 80246 -IHRvZnU= 80247 -IE1DUA== 80248 -IHN0b2NraW5n 80249 -IGNhdGhlZHJhbA== 80250 -IHF1YWRyYXRpYw== 80251 -YWxlemE= 80252 -Lm1vdmVUb0ZpcnN0 80253 -Q29sb3JCcnVzaA== 80254 -IEVyZWN0 80255 -IFJDUw== 80256 -OmJlZm9yZQ== 80257 -PW5vZGU= 80258 -IHByb2Jsw6htZQ== 80259 -X3Jobw== 80260 -IHN2ZW5zaw== 80261 -Um95 80262 -YmFzZVBhdGg= 80263 -IGtvbmQ= 80264 -INC10YHRgtGM 80265 -Z2V0U2luZ2xldG9u 80266 -IERTTQ== 80267 -SWFu 80268 -IGh1bnRlZA== 80269 -IFRlcnJhY2U= 80270 -IGNoaWxkY2FyZQ== 80271 -IGNvZWZmcw== 80272 -IGdyYWRlZA== 80273 -IEx1Y2lh 80274 -IGpzb25PYmo= 80275 -YWJsZU9iamVjdA== 80276 -VmF1bHQ= 80277 -w61zdGljYQ== 80278 -X3BhZ28= 80279 -X1BG 80280 -YW5kcmU= 80281 -IEFuYXRvbXk= 80282 -LkpDb21ib0JveA== 80283 -b3VyZQ== 80284 -IGdlbm90eXBl 80285 -YmVuY2htYXJr 80286 -IGJhaWs= 80287 -IFF1w6liZWM= 80288 -KCkpDQoNCg== 80289 -IGt1bm5l 80290 -IFBvc3NpYmx5 80291 -IEJlaXNwaWVs 80292 -IGNvbmRvbGVuY2Vz 80293 -PXF1ZXJ5 80294 -IHbDtQ== 80295 -IG51ZXZhcw== 80296 -IEFwb2NhbHlwc2U= 80297 -dmVjdGlvbg== 80298 -CXNwcml0ZQ== 80299 -bGV2YXRvcg== 80300 -LiJdCg== 80301 -Z2V0TmV4dA== 80302 -KFJlZ2lzdGVy 80303 -IHVuc3Vi 80304 -dHJlZXZpZXc= 80305 -Tm9kZUlk 80306 -IOyK 80307 -JikK 80308 -Zmx0 80309 -IGhvdHNwb3Q= 80310 -IGdhc3Ryb2ludGVzdGluYWw= 80311 -ZmlnY2FwdGlvbg== 80312 -b3dlcmVk 80313 -IENzcw== 80314 -X3Jvcw== 80315 -X3NjYWxpbmc= 80316 -IGVkaXRhcg== 80317 -J11dKTsK 80318 -Lm5lZw== 80319 -IGZ1dHVyaXN0aWM= 80320 -IHN0YXRh 80321 -dWN0b3I= 80322 -VUxBVEU= 80323 -IHfFgg== 80324 -LWNoYXJhY3Rlcg== 80325 -ICAKCgo= 80326 -IEJlYXU= 80327 -IHBlcm1hbGluaw== 80328 -Qnl0ZUJ1ZmZlcg== 80329 -IGRpY3RhdGVz 80330 -IE1MQQ== 80331 -X0xvZ2lu 80332 -Q29uZGl0aW9uYWw= 80333 -U1lN 80334 -QXJyYW5nZQ== 80335 -IFN0b2Nrcw== 80336 -IG1lYXNsZXM= 80337 -4KSk 80338 -RW5jcnlwdGlvbg== 80339 -IEVudGlyZQ== 80340 -IG1pbk9jY3Vycw== 80341 -IGh1Z3M= 80342 -L3dpbmRvdw== 80343 -CXByb3A= 80344 -PSQoKA== 80345 -IFVDUw== 80346 -IEZpcg== 80347 -LkNsb2Nr 80348 -LWRlc2t0b3A= 80349 -IG1hbGZvcm1lZA== 80350 -IEFiZXJkZWVu 80351 -IMOF 80352 -IFJvYWRz 80353 -IEJlaGF2aW91cg== 80354 -KCkn 80355 -5bGe5oCn 80356 -LkNvbXBhcmF0b3I= 80357 -X21v 80358 -X0lPUw== 80359 -IE9yaW9sZXM= 80360 -Lkxvb2t1cA== 80361 -IGZzZWVr 80362 -X0lC 80363 -L3N0YXI= 80364 -Kzwv 80365 -X0Rlc3Ryb3k= 80366 -LXRyYQ== 80367 -KCcuJyk= 80368 -IEZvckNhbkJlQ29udmVydGVk 80369 -IEZvckNhbkJlQ29udmVydGVkVG9G 80370 -IEZvckNhbkJlQ29udmVydGVkVG9Gb3JlYWNo 80371 -IEFhZA== 80372 -IGFpcnN0cmlrZXM= 80373 -aXNPaw== 80374 -IGZlZGVyYXRpb24= 80375 -IExhYnJhZG9y 80376 -X2xhdW5jaGVy 80377 -YWxvZ3k= 80378 -Pj4oKTsKCg== 80379 -IEp1Yg== 80380 -dXRy 80381 -aXN0aW5ndWlzaGVk 80382 -YWJhbnQ= 80383 -UmVnaW9ucw== 80384 -L2hlbHBlcg== 80385 -X2xpc3Rlbg== 80386 -CVRvYXN0 80387 -IEZpbGVNYW5hZ2Vy 80388 -aXRvcmlz 80389 -IGVsZWN0cm9kZXM= 80390 -R1JBREU= 80391 -IGJlZ2dlZA== 80392 -IFBsYXRlcw== 80393 -YWZvbmU= 80394 -ISEhCg== 80395 -IGVieA== 80396 -IGRlZmF1bHRQcm9wcw== 80397 -IGNvbXBhcmVUbw== 80398 -IFNDQw== 80399 -LmV4dGVudA== 80400 -YXV0b3M= 80401 -IOyW 80402 -IFRvbGtpZW4= 80403 -OjoqOwoK 80404 -Kics 80405 -LmRvY3VtZW50cw== 80406 -c2luZw== 80407 -PUJpdENvbnZlcnRlcg== 80408 -IEtyaXNobmE= 80409 -IHBsYWlzaXI= 80410 -IGJ1Z2d5 80411 -IHJlZ3VsYXRlcw== 80412 -IGZyaWRheQ== 80413 -IGNvbXBsZXRlbmVzcw== 80414 -IGF1ZGlibGU= 80415 -IFJlY29nbml0aW9uRXhjZXB0aW9u 80416 -IHNoZWRkaW5n 80417 -W10pewo= 80418 -KGJhbGw= 80419 -IENoYXRDb2xvcg== 80420 -KENvZGU= 80421 -KCksCgo= 80422 -IHRlcnRpYXJ5 80423 -IFNJREU= 80424 -KEpTT05PYmplY3Q= 80425 -pOaWrQ== 80426 -UmVtYXJrcw== 80427 -IGxpc3RCb3g= 80428 -LmltYWdlVXJs 80429 -IGRlbGF5aW5n 80430 -IHNvY2lvZWNvbm9taWM= 80431 -Lmxw 80432 -PE15 80433 -Lm9uU3RhcnQ= 80434 -IFNjb3I= 80435 -Ynl0ZXJpYW4= 80436 -LXJvY2s= 80437 -X21ldGVy 80438 -IHJlcG1hdA== 80439 -IHByZWd1bnRh 80440 -IE1FVEE= 80441 -KGd0 80442 -IEZSSUVORA== 80443 -IHNvcnRl 80444 -IGhlcA== 80445 -b25vbWllcw== 80446 -IGF1dG9tw6F0 80447 -IEZvcm1hdHM= 80448 -c3RhdGVQcm92aWRlcg== 80449 -LWZsb29y 80450 -X01VWA== 80451 -KENvbnRlbnQ= 80452 -IElOU1RBTEw= 80453 -IFRpdGFuaXVt 80454 -cnVj 80455 -LkRhdGFzZXQ= 80456 -YXNjbw== 80457 -Lk1BVENI 80458 -IGZlc3Rpdml0aWVz 80459 -TVNO 80460 -Lm90 80461 -IEdldExhc3RFcnJvcg== 80462 -aWVucw== 80463 -IF9fX19fX19fX19fX19fX19fXwoK 80464 -X0dG 80465 -X3BsYXRl 80466 -IEZvcm1hbA== 80467 -LWxldHRlcg== 80468 -S2F0ZQ== 80469 -YXBpYQ== 80470 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi8K 80471 -L2dlbmVyYXRlZA== 80472 -IERpbmc= 80473 -IEZyaWVkcmljaA== 80474 -ICcpJw== 80475 -VUJMSVNI 80476 -IEFiaWxpdGllcw== 80477 -IHVubG9ja2luZw== 80478 -Lnl5 80479 -IEludGVycg== 80480 -bm90aHJvdw== 80481 -aXBvcA== 80482 -IENPUlBPUg== 80483 -W2FycmF5 80484 -PFdlYkVsZW1lbnQ= 80485 -X1NJRA== 80486 -LnF1YWw= 80487 -RGlhZ25vc3RpYw== 80488 -OiIiLAo= 80489 -KG1vbWVudA== 80490 -anVyZWQ= 80491 -IHRlcnJlc3RyaWFs 80492 -ZXJ1bGU= 80493 -ICYpOwo= 80494 -IGJ1cmVhdWNyYXRpYw== 80495 -b3BwaW5z 80496 -IGphcG9u 80497 -bGVvbg== 80498 -X3JlbmFtZQ== 80499 -X0RFU1RST1k= 80500 -LkVuZHNXaXRo 80501 -IGVydXB0aW9u 80502 -KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi8K 80503 -UEVU 80504 -X3JlbG9hZA== 80505 -IHN1cHBsZW1lbnRhcnk= 80506 -IHppZW4= 80507 -Q0xMb2NhdGlvbg== 80508 -IGtsZWlu 80509 -X2Vm 80510 -Ont9 80511 -IGNvbWVudGFyaW9z 80512 -KHZhbGlkYXRpb24= 80513 -Lnh0ZXh0 80514 -X0lNQUdFUw== 80515 -LnNldElucHV0 80516 -IERlY29tcGlsZWQ= 80517 -X1RCTA== 80518 -Y29tcGxleFR5cGU= 80519 -X2ZlYXR1cmVk 80520 -ID8+PD8= 80521 -LnZvdGU= 80522 -IEZyaWRheXM= 80523 -LmNvbnN1bWU= 80524 -Lk1FRElB 80525 -IHN5bmVyZw== 80526 -jpjsnbTsp4A= 80527 -X0hFQURFUlM= 80528 -eEFD 80529 -X252 80530 -zq0= 80531 -IFNpbW9uZQ== 80532 -Q2VycmFy 80533 -YWRkb2Nr 80534 -LnNlcmlhbGl6ZXI= 80535 -IENsYXNzaWZpZWQ= 80536 -Lkl0ZW1zU291cmNl 80537 -IHByZWNvbmRpdGlvbg== 80538 -44Gd44GX44Gm 80539 -RElTVA== 80540 -SW1hZ2VVcmw= 80541 -L3JhbmRvbQ== 80542 -IGVyw7N0 80543 -W3Jvb3Q= 80544 -QUxMRVJZ 80545 -Y2o= 80546 -eEFE 80547 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwo= 80548 -IGl0YWxpYW5p 80549 -fCM= 80550 -IHJlZ2VuZXJhdGU= 80551 -IHN0cnI= 80552 -KHx8 80553 -IEVtZXJzb24= 80554 -IFBJRQ== 80555 -Y2xpZmZl 80556 -CWFu 80557 -PlBhc3N3b3Jk 80558 -dG9EYXRl 80559 -Q2lwaGVy 80560 -IGNvbnZveQ== 80561 -IFhDVEFzc2VydFRydWU= 80562 -L19f 80563 -LWZvY3Vz 80564 -IFJoaW5v 80565 -IGdvbw== 80566 -IGJvdG9u 80567 -Lk5vU3VjaA== 80568 -IFJlZHVjZWQ= 80569 -TUlTUw== 80570 -IFdpbmNoZXN0ZXI= 80571 -dXJsZW5jb2Rl 80572 -IG11ZGR5 80573 -aXlh 80574 -IE1icHM= 80575 -IHN0YWw= 80576 -b2RhZm9uZQ== 80577 -5Lus 80578 -IHBo4bqpbQ== 80579 -ICIvIjsK 80580 -IEFtbW8= 80581 -TmV3UHJvcA== 80582 -ID0KCg== 80583 -INCf0YA= 80584 -IHBheg== 80585 -IGxpYmVybw== 80586 -CVJlc291cmNl 80587 -bmVpZ2hib3Jz 80588 -LHJlc3BvbnNl 80589 -X2F0dGVtcHRz 80590 -IG5r 80591 -IG1pbGl0aWFz 80592 -X1BBWUxPQUQ= 80593 -LkJ5dGVTdHJpbmc= 80594 -INGB0L7QtNC10YDQtg== 80595 -YXJ0b24= 80596 -PkhlbGxv 80597 -bGlnaHRseQ== 80598 -b3dlbGw= 80599 -IGd1YXJkaW5n 80600 -IFRPSw== 80601 -IHdoZXJlYWJvdXRz 80602 -X2R3 80603 -IFJvdWxldHRl 80604 -IGd5cg== 80605 -IEZlZG9yYQ== 80606 -LkJ1dHRvbnM= 80607 -IGV4Y2xhaW1lZA== 80608 -IFNvbW1lcg== 80609 -QXV0aEd1YXJk 80610 -LXJhdGluZw== 80611 -TWV0aG9kQmVhdA== 80612 -LnBvc2l0aW9ucw== 80613 -TWVkaWFu 80614 -LuKApgoK 80615 -IGdsYWM= 80616 -IHVuZGVybWluZWQ= 80617 -JSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJQ== 80618 -X3RoaXJk 80619 -LmtlZXA= 80620 -IGhheWE= 80621 -IHRvSlNPTg== 80622 -IExhdXJpZQ== 80623 -IAkgICA= 80624 -IEFjY3Vt 80625 -IHBydW5l 80626 -dXJ2ZWQ= 80627 -IE5TRg== 80628 -IEdyYXBl 80629 -RkxJQ1Q= 80630 -6LI= 80631 -IHByZWRpcw== 80632 -X3B0cnM= 80633 -IG11bHRpY2FzdA== 80634 -KEdyb3Vw 80635 -IGhlacOf 80636 -IGZlZGVyYWxseQ== 80637 -X1BBVVNF 80638 -IG1hbGF5c2lh 80639 -IFJlY2FsbA== 80640 -IHJvZHo= 80641 -IFNlbnRlbmNl 80642 -aW50ZWw= 80643 -X2RydmRhdGE= 80644 -LXNjZW5lcw== 80645 -PHk= 80646 -IGZvb2xlZA== 80647 -IExvdWQ= 80648 -IGFudGl2aXJ1cw== 80649 -LnBsaXN0 80650 -IHZlcndlbmRlbg== 80651 -IFdvbGZl 80652 -KWl0ZW0= 80653 -IHR3aXN0aW5n 80654 -IGVzcGFu 80655 -YXRlcm5v 80656 -IEFjY29yZA== 80657 -KCldLA== 80658 -UkVNT1ZF 80659 -ZGVoeQ== 80660 -X1ByZQ== 80661 -IG1pc2Nhcg== 80662 -dmxh 80663 -IHNlbWJs 80664 -IHRldGhlcg== 80665 -IEJpag== 80666 -LycKCg== 80667 -IENvcGllcw== 80668 -LXBhdHRlcm4= 80669 -Lm9uVmlldw== 80670 -LXRha2luZw== 80671 -X3NpbXBz 80672 -44GX44GL44GX 80673 -IERBQ0E= 80674 -b3JuaW5n 80675 -IFBlc3NvYQ== 80676 -b3JueQ== 80677 -X3Bhcw== 80678 -IGVpZ2h0eQ== 80679 -VGFj 80680 -X1NUT0NL 80681 -LmxvY2F0aW9ucw== 80682 -Iil9LAo= 80683 -IHTDoQ== 80684 -LWZpZWxkcw== 80685 -b2thbmU= 80686 -L2t1YmVybmV0ZXM= 80687 -IGNoaWNh 80688 -IGFydMOtY3Vsbw== 80689 -7II= 80690 -Q1JFQVNF 80691 -QVNB 80692 -IExvbmQ= 80693 -IGV4ZW1wbG8= 80694 -QWxsb3dz 80695 -aHRtbHNwZWNpYWxjaGFycw== 80696 -KHZpcw== 80697 -IGpy 80698 -54Gr 80699 -IEVDTQ== 80700 -IGVtYmFy 80701 -X0FEQVBURVI= 80702 -IGRpbHV0ZWQ= 80703 -X29mZmljZQ== 80704 -IHNraW5jYXJl 80705 -QUdJTkc= 80706 -IMO+ 80707 -IFNNQVJU 80708 -L1RhYmxl 80709 -IGJhc2Fs 80710 -Q29uY3VycmVuY3k= 80711 -IFZveA== 80712 -IFVJQ29sbGVjdGlvblZpZXdDZWxs 80713 -IHdvbA== 80714 -IFNPVVRI 80715 -IGZyb21EYXRl 80716 -IGNvcmRz 80717 -RU1T 80718 -LndlaXhpbg== 80719 -J2VsbGU= 80720 -IOWx 80721 -IGdvYWx0 80722 -dWli 80723 -IE5lcHR1bmU= 80724 -KG9yZA== 80725 -xLFuxLFu 80726 -IG1pY3JvYmVz 80727 -V2VhcG9ucw== 80728 -LURlYw== 80729 -IFJvb25leQ== 80730 -IFN3YWdnZXI= 80731 -66qF 80732 -X2xh 80733 -IGdlbmVyYWRv 80734 -IEhpcg== 80735 -Q29taWM= 80736 -IGNhcnZl 80737 -X3Jx 80738 -aWN0ZXI= 80739 -IGNhcnRlbA== 80740 -YW5jaWFz 80741 -IFBhbmFzb25pYw== 80742 -IHJvYWRzaWRl 80743 -IGZyZXNod2F0ZXI= 80744 -IGRiYw== 80745 -X3RleHRz 80746 -X3NrdQ== 80747 -IFN1bW1lcnM= 80748 -IFBpY3R1cmVCb3g= 80749 -Lmdyb3VwQ29udHJvbA== 80750 -VkFSQ0hBUg== 80751 -UmVMVQ== 80752 -IHNhYm90YWdl 80753 -DQogICAgICAgICAgICANCg== 80754 -IHNjcm9sbGJhcg== 80755 -IGJhdHRlcmVk 80756 -Y2lw 80757 -LXBpY3R1cmU= 80758 -CXN0YXRz 80759 -LmNyZWF0b3I= 80760 -X0NMRUFO 80761 -Lk1PRA== 80762 -IGJpZ2ludA== 80763 -IFRlcnJvcmlzbQ== 80764 -X1Nob3c= 80765 -IFNwaWNlcg== 80766 -X0VUSA== 80767 -IMSR4buD 80768 -IHN1bW1lcnM= 80769 -IFVyYW4= 80770 -L21lbW9yeQ== 80771 -UmV2aWV3ZWQ= 80772 -IGR1ZXM= 80773 -c2V0U2NhbGU= 80774 -IFJheXM= 80775 -IENTQw== 80776 -aW5jb21pbmc= 80777 -LWJ1eQ== 80778 -IHByb2N1cmU= 80779 -ZW50YXI= 80780 -IGJ1bGxz 80781 -IAkJCQkJCQ== 80782 -IEZpYm9uYWNjaQ== 80783 -LXNjaGVtYQ== 80784 -bWFrZXM= 80785 -RWY= 80786 -X0Rlc2NyaXB0aW9u 80787 -L2FsZXJ0 80788 -IGpzb25TdHJpbmc= 80789 -dWZmbGluZw== 80790 -IEtFUk5FTA== 80791 -IEhveQ== 80792 -IGdyYW50UmVzdWx0cw== 80793 -b25hbGQ= 80794 -IFByb3ZpbmNpYWw= 80795 -c2VuZGluZw== 80796 -cHRvbQ== 80797 -INCe0LE= 80798 -IGNvbnN0cmFpbg== 80799 -IMWhdG8= 80800 -IFJhaXNlZEJ1dHRvbg== 80801 -VVRET1dO 80802 -IEdMc2l6ZWk= 80803 -IOekug== 80804 -44OR 80805 -IEdvbg== 80806 -UExJRVI= 80807 -J119PC8= 80808 -Y2xhc3NpYw== 80809 -IGVuZ3JhdmVk 80810 -IG1hc2N1bGluaXR5 80811 -TWFyc2g= 80812 -c3NxbA== 80813 -KEdyYXZpdHk= 80814 -IGxvYnN0ZXI= 80815 -67aE 80816 -X0ludGVy 80817 -XGJhc2U= 80818 -JzpbJw== 80819 -IGRldGFsbGU= 80820 -dHdlZXRz 80821 -IGplYWxvdXN5 80822 -YWdlbmRh 80823 -LGl0 80824 -c3dpcmU= 80825 -K0I= 80826 -IHRyb3V0 80827 -X2FsdGVybg== 80828 -OiIj 80829 -IER3YXJm 80830 -IFNoYXBpcm8= 80831 -ZXJvb24= 80832 -IG5vaw== 80833 -X2xvbmdpdHVkZQ== 80834 -IFdlcm5lcg== 80835 -IHZpb2xldA== 80836 -dXJzaXZlbHk= 80837 -LWF3YWl0 80838 -IH0KCgoKCgo= 80839 -IExlbm5vbg== 80840 -IEFudGFyY3RpYw== 80841 -IGLDpWRl 80842 -X3Nsb3Bl 80843 -bWFuZG8= 80844 -b3VuY2Vy 80845 -LWlvbg== 80846 -IERlc3RydWN0aW9u 80847 -aXNzZW5zY2hhZnQ= 80848 -UGl6emE= 80849 -IEdlb2xvZ2ljYWw= 80850 -Qk9VTkQ= 80851 -IGNpbmU= 80852 -RGVtb24= 80853 -LnBlb3BsZQ== 80854 -X1RPR0dMRQ== 80855 -CW5vZGVz 80856 -YnVzY2Fy 80857 -LnByb2Nlc3Nvcg== 80858 -Tmg= 80859 -L3Nkaw== 80860 -IG15Y2tldA== 80861 -YXVjdGlvbg== 80862 -TWVn 80863 -R01FTQ== 80864 -IGlyb25pY2FsbHk= 80865 -5riF 80866 -IGNvbnZlcmdl 80867 -IFVJVGFibGVWaWV3RGF0YVNvdXJjZQ== 80868 -QXJkdWlubw== 80869 -PmU= 80870 -Sm95 80871 -IFNob3VsZGVy 80872 -IER1Yw== 80873 -UFJJTUFSWQ== 80874 -Lioo 80875 -LXByZXM= 80876 -IGRpYWxvZ1JlZg== 80877 -aW1hZ2VOYW1l 80878 -X2ludm9rZQ== 80879 -XFRlbXBsYXRl 80880 -T0k= 80881 -IHZyaWVuZA== 80882 -IEd1ZXJy 80883 -IHByZXJlcXVpc2l0ZQ== 80884 -IFBHQQ== 80885 -IFJlc3A= 80886 -KSIsIg== 80887 -bGxlbg== 80888 -IHNuYXBwaW5n 80889 -X0ZpcnN0 80890 -S0lU 80891 -LnNldEZvY3Vz 80892 -IEN5cHJlc3M= 80893 -Y3JhZnRlZA== 80894 -LzsK 80895 -d2VpZ2h0ZWQ= 80896 -dm95 80897 -X3RG 80898 -X2luc24= 80899 -IEluc3RhbGxpbmc= 80900 -IEdhbGx1cA== 80901 -QURPUg== 80902 -IEFMT0c= 80903 -Q29udGV4dEhvbGRlcg== 80904 -IFRvdXQ= 80905 -IEZvbGV5 80906 -IGNvbnRlbXBsYXRl 80907 -IENvaW5iYXNl 80908 -WMOj 80909 -d2FuZA== 80910 -LkNyZWF0ZUNvbW1hbmQ= 80911 -U29jaw== 80912 -IHVud3JhcA== 80913 -Y2xhc3NwYXRo 80914 -PFJlc291cmNl 80915 -X0VTVA== 80916 -PXJhbmRvbQ== 80917 -IFNoYWRl 80918 -IGRpY2k= 80919 -2K/Zig== 80920 -IGtpdHR5 80921 -0LDRgtC10LM= 80922 -4buNbg== 80923 -LkNvbXBsZXRlZA== 80924 -cGxvcmVy 80925 -IGJhYmVs 80926 -Lk9uSXRlbUNsaWNrTGlzdGVuZXI= 80927 -IE1jTWFob24= 80928 -IHJlc3RUZW1wbGF0ZQ== 80929 -IHRlc3M= 80930 -U2V0VXA= 80931 -L29jdGV0 80932 -IGNhbGFt 80933 -IGhpbmdlcw== 80934 -IGFydGVyaWFs 80935 -IFRydW1hbg== 80936 -IENoZXJ5bA== 80937 -X0REUg== 80938 -IHRtcGw= 80939 -IExlcg== 80940 -W2hhc2g= 80941 -S0VS 80942 -IHByb3BvcmNpb24= 80943 -IGNvYXN0bGluZQ== 80944 -YWNpb3M= 80945 -Ij4tLX19Cg== 80946 -IGRpc2FkdmFudGFnZWQ= 80947 -VG91Y2hMaXN0ZW5lcg== 80948 -IFNlZ2E= 80949 -Y29lcw== 80950 -SWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbg== 80951 -PEJveA== 80952 -IEluY3JlZGlibGU= 80953 -VXBkYXRlcg== 80954 -RkxU 80955 -aW5hbWU= 80956 -IEludGVyZmFjZXM= 80957 -Kylc 80958 -ZW5kaW1lbnRv 80959 -IHBhbmNha2Vz 80960 -IGluY29uc2lzdA== 80961 -LnBldA== 80962 -IGtleW9m 80963 -SW5uZXJUZXh0 80964 -Picp 80965 -RGVhbg== 80966 -IFDDqQ== 80967 -KENvbnRyb2w= 80968 -IHNwYXI= 80969 -bGluaWs= 80970 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA== 80971 -IERhbmU= 80972 -X1BBR0VT 80973 -IHNldEJhY2tncm91bmRDb2xvcg== 80974 -c3ViY2F0ZWdvcnk= 80975 -IFN0cmluZ1NwbGl0T3B0aW9ucw== 80976 -QWxsZW4= 80977 -ISgie30iLA== 80978 -hOyerA== 80979 -IGJhYw== 80980 -X1BST0RVQ1RT 80981 -dXBwZXJjYXNl 80982 -PSQoIiM= 80983 -xJlr 80984 -IFVJVGFwR2VzdHVyZVJlY29nbml6ZXI= 80985 -TUVUQQ== 80986 -IHNjYXJjZWx5 80987 -6aA= 80988 -X21hbmFnZWQ= 80989 -IGNvbnN1bW8= 80990 -TW91c2VNb3Zl 80991 -IFNwZWNz 80992 -IFNlYXJjaGluZw== 80993 -SGVhZGVyVmlldw== 80994 -Oicp 80995 -IG1pY3Jvc29mdA== 80996 -IEtvc292bw== 80997 -ZW1hbm4= 80998 -LmZmdA== 80999 -IEh1YmJhcmQ= 81000 -IGRleA== 81001 -X1RFUk1JTg== 81002 -X0ZD 81003 -IHBoaWxpcHBpbmVz 81004 -XENvbGxlY3Rpb25z 81005 -IHRlaA== 81006 -IHF1YWxpZmllcw== 81007 -IGlucHV0VmFsdWU= 81008 -IEdPVA== 81009 -KHNh 81010 -SUxMRUQ= 81011 -IHNsYW5n 81012 -IGtlaW5lbg== 81013 -IGZlbG9u 81014 -IEVyaWNr 81015 -YWJpbGlkYWRl 81016 -LnNlcg== 81017 -IHJ1bmVz 81018 -IFVucmVhbA== 81019 -KG9y 81020 -IOusuOyekA== 81021 -IGJpZGk= 81022 -IGlyYw== 81023 -CWl0ZXI= 81024 -Im5pbA== 81025 -L3VidW50dQ== 81026 -IG11cmRlcmluZw== 81027 -ID8u 81028 -dW5rZXI= 81029 -UmVjdFRyYW5zZm9ybQ== 81030 -JykpCgoK 81031 -IGFyaXR5 81032 -IEZyZWVs 81033 -Lm1vdW50 81034 -Q09NTUVOVA== 81035 -ICIqIiw= 81036 -ZW5jcnlwdGlvbg== 81037 -W21vZGVs 81038 -In19Pgo= 81039 -LlRvdWNo 81040 -L3RodW1i 81041 -IHByZXo= 81042 -L2NvbXBhbnk= 81043 -IHLDs8W8 81044 -IHNvZnRlbg== 81045 -IHBvc3NpYmlsZQ== 81046 -IEVDQg== 81047 -X0Jvb2w= 81048 -IC0tLS0tCg== 81049 -IGludGVydHc= 81050 -X3N0YQ== 81051 -X0JBTA== 81052 -Lm5hdmlnYXRpb25CYXI= 81053 -IFJHQkE= 81054 -Z3JpbHk= 81055 -c3RvZmY= 81056 -YWNreQ== 81057 -UUI= 81058 -QEFwaQ== 81059 -cGVjaWE= 81060 -IFJwYw== 81061 -IGFtcHM= 81062 -IEZlbmNl 81063 -IGdlbm9taWM= 81064 -KGFsaWFz 81065 -Vmllbg== 81066 -U3BpbkJveA== 81067 -LmdldFNlY29uZHM= 81068 -IGdsb2JhbGl6YXRpb24= 81069 -IGN1cw== 81070 -a3ViZWN0bA== 81071 -IHRocm90dA== 81072 -IGluZXJ0 81073 -IFNjcmF0Y2g= 81074 -w5c8Lw== 81075 -Lmlzc3Vl 81076 -ZXNzYXk= 81077 -LUlzbA== 81078 -IG3DoXI= 81079 -CWJpdA== 81080 -IGFib2xpc2hlZA== 81081 -LmluZmluaXR5 81082 -bGluZW5v 81083 -LmFsZ29yaXRobQ== 81084 -b3JzY2g= 81085 -RW1haWxBZGRyZXNz 81086 -IERBRw== 81087 -YnJpbmdpbmc= 81088 -Lm15YXBwbGljYXRpb24= 81089 -LlN1cHBvcnQ= 81090 -X2xlYWRlcg== 81091 -IERldmlu 81092 -IFtdDQoNCg== 81093 -IHJtcw== 81094 -IGJ1Y2tsZQ== 81095 -aWdsaWE= 81096 -L3Byb2JsZW0= 81097 -IGhhdXRl 81098 -IGluc3RpdHV0ZWQ= 81099 -SVU= 81100 -bGFtYQ== 81101 -RVhQRUNURUQ= 81102 -IEJlY2toYW0= 81103 -IEh5ZHJhdWxpYw== 81104 -U3RhdGljcw== 81105 -X25vcm1hbGl6ZWQ= 81106 -LmAsCg== 81107 -IG1pbWV0eXBl 81108 -IHNoYXZpbmc= 81109 -T3ZlcnJpZGVz 81110 -IE1lcmNlcg== 81111 -dHJmcw== 81112 -LXN0YXRz 81113 -b3NwYWNl 81114 -IGFudGlveGlkYW50cw== 81115 -aW5maW5pdHk= 81116 -Um9ja2V0 81117 -IEV1bGVy 81118 -LXZhbHU= 81119 -IGzDuA== 81120 -LUlO 81121 -SG1t 81122 -LXJldHVybg== 81123 -IFBBTkVM 81124 -IHRlcm1pbmF0b3I= 81125 -IHRla24= 81126 -IHByZWRpY2F0ZXM= 81127 -U3RhbXBlZA== 81128 -IHN2ZQ== 81129 -YW50ZXI= 81130 -IGN5Y2xpc3Q= 81131 -IEVwc3RlaW4= 81132 -IGhpdHRlcnM= 81133 -ZG9ncw== 81134 -LkFkZExpc3RlbmVy 81135 -X2V4Y2VwdGlvbnM= 81136 -IEZPT1Q= 81137 -aWNhcmU= 81138 -W3RhZw== 81139 -LWZldGNo 81140 -VVBMT0FE 81141 -LmRyb3Bkb3du 81142 -IGNlbnRyb2lkcw== 81143 -IGFyYmU= 81144 -IGhpam8= 81145 -IERhdGFiYXNlUmVmZXJlbmNl 81146 -UG9saXRpY2Fs 81147 -IEJBU0lD 81148 -LWZvcmNl 81149 -fCQ= 81150 -IFJFVklFVw== 81151 -LmRlY29yYXRl 81152 -IEFzcGVjdA== 81153 -IGNvbW1lbW9y 81154 -IGNsZWFuc2U= 81155 -IENsYXVkaWE= 81156 -Z2VuZXJhdGlvbg== 81157 -SExU 81158 -dHlwZW9ybQ== 81159 -cHJlZmVy 81160 -b3ZlcmxhcA== 81161 -YmlvbG9neQ== 81162 -U3RyZWFtZXI= 81163 -Y29tbWlzc2lvbg== 81164 -IHRodW1ibmFpbHM= 81165 -LkN1cnJlbnRDdWx0dXJl 81166 -IHVybHBhcnNl 81167 -IGdpb3Jubw== 81168 -IGRldnM= 81169 -X2FzcGVjdA== 81170 -IGNoZXJpc2hlZA== 81171 -IE5hY2hyaWNodA== 81172 -IHJpZ2dlZA== 81173 -L2xvZ2dpbmc= 81174 -aHVudA== 81175 -VHlwZUVycm9y 81176 -PFNlbGVjdA== 81177 -KHByb2c= 81178 -IEdyaWRMYXlvdXQ= 81179 -6JA= 81180 -IEVYUEVS 81181 -CUtFWQ== 81182 -LmRt 81183 -CWNhcmQ= 81184 -IFRhdQ== 81185 -IG5vdGFtbWVudA== 81186 -IGhlcm9pbmU= 81187 -IGJhdGh0dWI= 81188 -YXRyb24= 81189 -IOaU 81190 -77yS77yQ 81191 -Y29ub21pY3M= 81192 -IHJldmVyc2libGU= 81193 -6YeR6aKd 81194 -IGpzeA== 81195 -IFNwZWFrZXJz 81196 -RGVzZXJpYWxpemVy 81197 -LnRvRmxvYXQ= 81198 -INC/0LXRgNC10LzQtdC9 81199 -IFByb3ZpZGluZw== 81200 -6LSm 81201 -W2VsZW1lbnQ= 81202 -Kjo= 81203 -PlJldHVybnM= 81204 -IHRpdHVsYXI= 81205 -IGhlYXJ0YnJlYWtpbmc= 81206 -X05C 81207 -LkFyZ3VtZW50cw== 81208 -IG9wdGlj 81209 -YXR0YWNrcw== 81210 -IFZ1bG5lcg== 81211 -CWtleXM= 81212 -IGNvbnRyb2xl 81213 -LlJHQg== 81214 -IHN1Ymdyb3Vw 81215 -bWFuZGF0b3J5 81216 -IENBQg== 81217 -CWVuZ2luZQ== 81218 -44Gw 81219 -TUVESUE= 81220 -L3RyYW5z 81221 -IGRhbms= 81222 -IHNlcnZpY2Vk 81223 -IGluY2FyY2VyYXRlZA== 81224 -IEZyZWFr 81225 -IHVwdG8= 81226 -ZHJhd2Vy 81227 -WyIr 81228 -IGVudHdpY2s= 81229 -Z0w= 81230 -TW9kZWxFcnJvcg== 81231 -IHJlYWRkaXI= 81232 -aXN0cmlidXRl 81233 -IGdsYXJl 81234 -aXF1ZW1lbnQ= 81235 -Y2hpbmE= 81236 -IEthcGxhbg== 81237 -IFN0YWJpbGl0eQ== 81238 -cG9zaXRlcw== 81239 -IEpBWEJFbGVtZW50 81240 -IHRvdGFsbWVudGU= 81241 -KGNvbW0= 81242 -X3Byb2Nlc3Nlcw== 81243 -VGhvdXNhbmRz 81244 -IElscw== 81245 -ZXJ0YWludHk= 81246 -IFNoYWRlcw== 81247 -YWN0YWw= 81248 -bG9nZ2VkSW4= 81249 -IE5pY2hvbHM= 81250 -IE1pZGxhbmRz 81251 -ZGV2aWw= 81252 -IHN0clNRTA== 81253 -In0p 81254 -IEpvcmQ= 81255 -KGZm 81256 -IEp1bmk= 81257 -5bCx 81258 -YXJ0aXNhbmxpYg== 81259 -IG1vb25z 81260 -IHVucmVzb2x2ZWQ= 81261 -IHdpdGNoZXM= 81262 -IEfDvA== 81263 -IEdvYmxpbg== 81264 -YW5zc29u 81265 -fCU= 81266 -IGJ6 81267 -IGR1cGxleA== 81268 -ICIpKQ== 81269 -Lmxpa2Vz 81270 -KHZlcnRpY2Fs 81271 -IGNvd2JveQ== 81272 -U2VsZWNjaW9uZQ== 81273 -ICcqJyw= 81274 -IFNhcA== 81275 -IFNhYmJhdGg= 81276 -U09SVA== 81277 -4Ka/4KY= 81278 -X2NlbnRlcnM= 81279 -XFBvc3Q= 81280 -KFRyZWU= 81281 -IHBhcnRlcw== 81282 -X3lhdw== 81283 -YXJlbW9z 81284 -c2V2ZW4= 81285 -IGhpYXR1cw== 81286 -X2ludGVuc2l0eQ== 81287 -LW1hbnk= 81288 -IERvbGxhcnM= 81289 -LXVuc3R5bGVk 81290 -IGdyaXBwaW5n 81291 -IG1hcnZlbG91cw== 81292 -IHJlY2VwdGlvbnM= 81293 -IG92ZXJjbG9jaw== 81294 -YmVybWFu 81295 -IGhlYWRxdWFydGVyZWQ= 81296 -eEJC 81297 -Y2xhc3NDYWxsQ2hlY2s= 81298 -IG9ic2VydmVz 81299 -U3VibWl0dGluZw== 81300 -0LjRh9C10YE= 81301 -IEh0dHBTdGF0dXNDb2RlUmVzdWx0 81302 -IGhpZXJvbnRh 81303 -cm9wcGluZw== 81304 -Rk9SQ0U= 81305 -CXV0aWxz 81306 -IHZlbnRz 81307 -YWRkZXJz 81308 -IE1JWA== 81309 -IEVsZWdhbnQ= 81310 -IGFjb3M= 81311 -KG1hY2hpbmU= 81312 -IG1lZGRsaW5n 81313 -IHZpbGU= 81314 -LWNvbXBhdGlibGU= 81315 -IGNyZWFtcw== 81316 -IFRhYmxlUm93 81317 -IFJlaGFiaWxpdGF0aW9u 81318 -QWJi 81319 -KHVzZXJJbmZv 81320 -X2V4cGlyZWQ= 81321 -Lk9iamVjdE1ldGE= 81322 -IGdvZHQ= 81323 -dXN1YWw= 81324 -LmJpbmRpbmdOYXZpZ2F0b3JNb3Zl 81325 -IFJlZ2lzdHJhcg== 81326 -bWlncmF0aW9u 81327 -YXB0dXJlZA== 81328 -LHBhcmFtcw== 81329 -IGNlbnRlclk= 81330 -b3dhbg== 81331 -bG9jYWxlcw== 81332 -SW5wdXRNb2R1bGU= 81333 -IHZpZ2lsYW50 81334 -IG5jb2xz 81335 -IGluZ3I= 81336 -IGPDtHTDqQ== 81337 -dmVydGltZQ== 81338 -IHdpZGVzdA== 81339 -IEhERg== 81340 -IEFsZ2VyaWE= 81341 -IGNoYXR0 81342 -JHNlbGVjdA== 81343 -Il0pDQo= 81344 -IG11bHRlcg== 81345 -IENoZW5leQ== 81346 -ZnVzY2F0ZWQ= 81347 -PSciLiRf 81348 -IERlbmlzZQ== 81349 -IHJpZmY= 81350 -QWJzZW50 81351 -IHRhbWHDsW8= 81352 -IGplc3pjemU= 81353 -LlByb2dyYW0= 81354 -CWJy 81355 -ZXJhaXM= 81356 -IHNhbmRhbHM= 81357 -ICws 81358 -IGRpc3NvbHV0aW9u 81359 -IHVudGVyc2NoaWVk 81360 -UHJvdg== 81361 -LnRyYW5zYWN0aW9ucw== 81362 -IFRyb3VibGU= 81363 -Lm1pZGRsZQ== 81364 -LmdldERlY2xhcmVk 81365 -IHN3ZWF0aW5n 81366 -IEhhbmNvY2s= 81367 -6LS5 81368 -IHBvZw== 81369 -IEtpYQ== 81370 -IG1vZG5l 81371 -IEFjY2Vzc2liaWxpdHk= 81372 -IGxlYWthZ2U= 81373 -IGRlY2VwdGl2ZQ== 81374 -IFdPTQ== 81375 -INC+0YE= 81376 -IGNzYWs= 81377 -YWNvY2s= 81378 -LlN5bnRheA== 81379 -ICxb 81380 -LicpLAo= 81381 -IGZvcmVjbG9zdXJl 81382 -IHVuZmF2b3I= 81383 -IGV4Y2w= 81384 -Q1VEQQ== 81385 -ZGVuc2U= 81386 -PFVuaXQ= 81387 -IHZhcGluZw== 81388 -IG1hamVzdGlj 81389 -aWF0b3Jz 81390 -IGF1dGlzdGlj 81391 -LmdhdGV3YXk= 81392 -VXJsUGFyc2Vy 81393 -SGVsbA== 81394 -IENvc3Rjbw== 81395 -IEhJUA== 81396 -T2JzZXJ2ZXJz 81397 -IFBlb3BsZXM= 81398 -IFNwb3RsaWdodA== 81399 -IFRhdmVybg== 81400 -IFRPVVI= 81401 -cGxpbmdz 81402 -LldSQVA= 81403 -IGFsZA== 81404 -TkFM 81405 -KCIqKio= 81406 -c2V0UHJvcGVydHk= 81407 -X1N0b3A= 81408 -YW5ub3VuY2VtZW50 81409 -IEltbWVkaWF0ZQ== 81410 -IEhTVg== 81411 -X1RFU1RT 81412 -IGNyYXZl 81413 -X1VD 81414 -LmRlY3J5cHQ= 81415 -KFJvbGVz 81416 -IHN1Ymo= 81417 -X0ludGVnZXI= 81418 -Lm5vdE51bGw= 81419 -IEdzdA== 81420 -IEJ5cm5l 81421 -IEFxdWFyaXVt 81422 -IENhbmM= 81423 -X0NIQU4= 81424 -IERUTw== 81425 -Lmhs 81426 -IG1lbmdndW5ha2Fu 81427 -RnJhbmM= 81428 -RGlhbG9nQ29udGVudA== 81429 -Li4uJwo= 81430 -IEt1bnN0 81431 -IEFsbG9jYXRvcg== 81432 -VVNBR0U= 81433 -S25vd2xlZGdl 81434 -CWNwdQ== 81435 -IG1vcmFscw== 81436 -cGF0aWVudHM= 81437 -IGlsaw== 81438 -IGNyaXRlcg== 81439 -IFZldA== 81440 -IE1lc3NpYWg= 81441 -X186 81442 -YXZlbm91cw== 81443 -X3ZpZXdlcg== 81444 -KERpY3Rpb25hcnk= 81445 -IEJvZGllcw== 81446 -aGFzT25l 81447 -0LjQvNC10YA= 81448 -IHppcGNvZGU= 81449 -U3Rlcg== 81450 -IGLDoXM= 81451 -X0Rpc3BsYXk= 81452 -IGZpcm1h 81453 -IFJhaWRlcg== 81454 -IEtI 81455 -V2l0aERhdGE= 81456 -KEFSRw== 81457 -IHByb3Ry 81458 -IG1zZWM= 81459 -IGxhdmVuZGVy 81460 -KFV0aWw= 81461 -INC/0YDQvtCz0YDQsNC8 81462 -X211eA== 81463 -X2xhdGl0dWRl 81464 -UG9ydHJhaXQ= 81465 -IHNpdGNvbQ== 81466 -IGFkaWNpb24= 81467 -KGNvbnN0YW50cw== 81468 -IEFueGlldHk= 81469 -IFJvc2Vz 81470 -IHN0aW11bGF0ZWQ= 81471 -IGNocm9ubw== 81472 -IGZvc3NpbHM= 81473 -IEFpcmJ1cw== 81474 -bGVmdHJpZ2h0 81475 -IE3DqXRvZG8= 81476 -Inc= 81477 -IGtsZWluZW4= 81478 -IGNsaXF1ZQ== 81479 -b21pbmF0aW9u 81480 -IG1vdGVs 81481 -L3ZlY3Rvcg== 81482 -ZGVjbGFyYXRpb24= 81483 -IG5ld1k= 81484 -W0g= 81485 -LnNjYWxhcg== 81486 -b21ibw== 81487 -aHVk 81488 -O3NldA== 81489 -ZnR5cGU= 81490 -KCcnKS4= 81491 -b3JkZXM= 81492 -eW5vcw== 81493 -J10sCgo= 81494 -X0ZMVVNI 81495 -aWRlbnRpZnk= 81496 -L2RldmljZXM= 81497 -IGRpY3RhdGVk 81498 -IGRlamFy 81499 -IEVtaW4= 81500 -IFBlbmRhbnQ= 81501 -IG9uVXBkYXRl 81502 -XSkpKQ== 81503 -IEJhcmtlcg== 81504 -T3Jt 81505 -6K+36YCJ5oup 81506 -X2d1aWRl 81507 -w6FiYWRv 81508 -b3BoZQ== 81509 -ICIuCg== 81510 -IEJyZXdlcnM= 81511 -IGJyaWRhbA== 81512 -IENFUw== 81513 -X0NhdGVnb3J5 81514 -IEJUTg== 81515 -IERhcnRo 81516 -I2Zvcg== 81517 -ZXRobmlj 81518 -YXJjaGl0ZWN0dXJl 81519 -IENvdXBl 81520 -aWRvcmVz 81521 -IGZhc2Npc20= 81522 -IGNvbnRyYWRpY3Rpb25z 81523 -ZWZmZWN0cw== 81524 -SW5pdGlhbFN0YXRl 81525 -IOekuuS+iw== 81526 -bWF0cGxvdGxpYg== 81527 -LmRlc2t0b3A= 81528 -INCt 81529 -IFFQaXhtYXA= 81530 -CWJlZ2lu 81531 -IHduZA== 81532 -IGNvbnRpZW5l 81533 -KGhlbHBlcg== 81534 -Lk5vdGlmeQ== 81535 -KEJvb2s= 81536 -IEd1YXJhbnRlZWQ= 81537 -cGxs 81538 -aW9sYQ== 81539 -IGZ1bmdp 81540 -aXZlbnQ= 81541 -IE9B 81542 -5rKh5pyJ 81543 -IHdpxJljZWo= 81544 -CQoJCgkKCQo= 81545 -77yaIis= 81546 -IFRhbGtz 81547 -LnN0YXJ0ZWQ= 81548 -b2NpdGllcw== 81549 -IGVzcG9ydHM= 81550 -PElucHV0 81551 -IEVYQ0VQVElPTg== 81552 -IGFjdHU= 81553 -LmltcA== 81554 -ICIvIgo= 81555 -T3RoZXJ3aXNl 81556 -IFBlbnNpb24= 81557 -IFdhdmVz 81558 -xrDGoQ== 81559 -aWFyZHM= 81560 -ICo8Lw== 81561 -dXJnZW9u 81562 -IFNDSQ== 81563 -IExhdXJlbA== 81564 -ZXRhZw== 81565 -TmV0ZmxpeA== 81566 -IFJlc3BvbnNlcw== 81567 -IG5lb2xpYmVyYWw= 81568 -aXNDb250YWluZWQ= 81569 -PW15 81570 -IHJlcHJpbnQ= 81571 -b25lc3RseQ== 81572 -IGRlcGFydGluZw== 81573 -UFdN 81574 -ZXdoYXQ= 81575 -PSI8PA== 81576 -Lnlhbmc= 81577 -IFRyYWRpdGlvbg== 81578 -KyI6 81579 -ZGVwZW5kaW5n 81580 -X1VuaXQ= 81581 -IENvZGFibGU= 81582 -IHdoaXNreQ== 81583 -IGNvcnJlbGF0ZQ== 81584 -IGRpcmV0 81585 -TGFzdGx5 81586 -CU91dHB1dA== 81587 -KGlub2Rl 81588 -XExvZw== 81589 -IERlcGVuZGVuY2llcw== 81590 -V2lsbERpc2FwcGVhcg== 81591 -IFBhbmVscw== 81592 -IOKUnOKUgOKUgA== 81593 -IG9zdGVuc2libHk= 81594 -fC0t 81595 -QW5udWFs 81596 -IGF1dG9sb2Fk 81597 -VmFsdWVIYW5kbGluZw== 81598 -LmNvaW4= 81599 -ZWR1Y3Q= 81600 -Wlk= 81601 -IENhbnVja3M= 81602 -IHNtZWFy 81603 -IHJlYWxpZGFk 81604 -IHt7Cg== 81605 -aXZvbA== 81606 -ZXRTb2NrZXRBZGRyZXNz 81607 -IEtlbXA= 81608 -L0ZyYW1ld29yaw== 81609 -IHF1aWNrZXN0 81610 -XyIuJA== 81611 -IHdpdGhob2xkaW5n 81612 -IGludHJpZ3Vl 81613 -IEFERFI= 81614 -RGllc2U= 81615 -V2Vla2x5 81616 -X19fX18= 81617 -IEludmFsaWRBcmd1bWVudEV4Y2VwdGlvbg== 81618 -b2xhdGVk 81619 -UnVuTG9vcA== 81620 -IHBhc3PDqQ== 81621 -LmZpcmViYXNlaW8= 81622 -LmV1bGVyQW5nbGVz 81623 -aXN0ZW5jZQ== 81624 -IGZlYXJpbmc= 81625 -IEVsZW1lbnRUeXBl 81626 -L1Rlc3Q= 81627 -IOafpeivog== 81628 -IGZvbmRv 81629 -IFBhcnI= 81630 -IHplc3Q= 81631 -IFRyYW5zZm9ybWVycw== 81632 -TGluZVN0eWxl 81633 -IGV0aGVybmV0 81634 -YWZmbGVz 81635 -IG5hbWVkdHVwbGU= 81636 -IFNjYWxhcnM= 81637 -TlNVUkxTZXNzaW9u 81638 -LWV4dGVuc2lvbg== 81639 -KE1lc3NhZ2Vz 81640 -IGF0ZW5jacOzbg== 81641 -IEplcnNleXM= 81642 -YmVkUGFuZQ== 81643 -IFN0dW5kZW4= 81644 -IHZvaXR1cmU= 81645 -IOm7mOiupA== 81646 -Lm9wZW5nbA== 81647 -ICJ9 81648 -IFJldmVuZ2U= 81649 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0K 81650 -SW5zdGFudGlhdGU= 81651 -IGVucg== 81652 -VmFsaWRhdGlvbkVycm9y 81653 -X0FMUkVBRFk= 81654 -TG90cw== 81655 -b2Nl 81656 -IHNjcmlt 81657 -IGVtYm9keQ== 81658 -0YDQsNGC 81659 -IGNvbmNlZGU= 81660 -YXNzZWw= 81661 -IEJSRQ== 81662 -UExFQVNF 81663 -CWRpZmY= 81664 -57uT5p2f 81665 -LmZw 81666 -YmFt 81667 -TWVhbA== 81668 -IE1hZG9ubmE= 81669 -IHB1bmlzaGFibGU= 81670 -aWZmaWVz 81671 -X3VuaXg= 81672 -7JmA 81673 -IEdhZ2E= 81674 -InN0cnVjdA== 81675 -VG9TZW5k 81676 -IE9DUg== 81677 -IHByYWlzaW5n 81678 -Z2V0U3RvcmU= 81679 -IGV1dGg= 81680 -IGFycmVnbG8= 81681 -IGZlcm0= 81682 -ZmRm 81683 -Q29vbGRvd24= 81684 -IFJlY3ljbGluZw== 81685 -QW5h 81686 -aW5kcg== 81687 -X0hQ 81688 -IEdvdmVybmFuY2U= 81689 -IGJhcnJhZ2U= 81690 -L2Nh 81691 -ICwo 81692 -RsO8cg== 81693 -IElTUHM= 81694 -IG1lbmFjZQ== 81695 -VmlyZ2luaWE= 81696 -IGZhbmM= 81697 -IG5vbWJyZXM= 81698 -Lmluc3RydWN0aW9ucw== 81699 -IGVzY2FsYXRlZA== 81700 -YWdpbmE= 81701 -IExldmluZQ== 81702 -CWZpbmQ= 81703 -X2Vy 81704 -IGRlanRpbmdzYWo= 81705 -c3Zw 81706 -YWdvcw== 81707 -KHNvbA== 81708 -IExpZA== 81709 -UFJJVkFURQ== 81710 -IElNUExFTUVOVA== 81711 -ZWZlbGxlcg== 81712 -KFRhcmdldA== 81713 -4LmJ4Lit4Lih 81714 -aG91c2luZw== 81715 -LnNldEN1cnNvcg== 81716 -IG5laG1lbg== 81717 -LnJlY2VpdmVy 81718 -IFR1dG9y 81719 -IG1hdHRlcmVk 81720 -bWRhdA== 81721 -cmVndWxhdGVk 81722 -IGdldEFkZHJlc3M= 81723 -IE1pbnV0ZW4= 81724 -IElV 81725 -0LvQsNCy 81726 -IHR1cm5vdmVycw== 81727 -IHN1aXRhYmlsaXR5 81728 -CWVzYw== 81729 -Y2FsY3Vs 81730 -X1N0cmVhbQ== 81731 -X2ZpbGVuYW1lcw== 81732 -LXZhcnM= 81733 -Li4uLi4KCg== 81734 -RGlh 81735 -IHN3aW1z 81736 -T3B0aW1pemVy 81737 -PGJvb3N0 81738 -IFBlcm1pdA== 81739 -J10pKXs= 81740 -XE9wdGlvbnNSZXNvbHZlcg== 81741 -5qGI 81742 -IGhlY3RhcmVz 81743 -KHVz 81744 -IERldmVsb3Bpbmc= 81745 -X3hz 81746 -IG5vdmVsaXN0 81747 -IENvbnZlbmllbmNl 81748 -d2Fsa2luZw== 81749 -IGNoYXJtcw== 81750 -IExlYXNl 81751 -CUhBTA== 81752 -KFsm 81753 -IHJlc3RhcnRlZA== 81754 -TWFnZQ== 81755 -SXB2 81756 -INGN0Lo= 81757 -UkxG 81758 -IGFzc2VtYmxpbmc= 81759 -IEVjYw== 81760 -dmluZm9z 81761 -cGVkaWRv 81762 -IHN5bm9wc2lz 81763 -IFN0YW50b24= 81764 -c3RhcnR1cA== 81765 -LmdldHZhbHVl 81766 -IEtpdHQ= 81767 -cHJvcGVy 81768 -IHByZXRyYWluZWQ= 81769 -IFBFTg== 81770 -LlRlcm0= 81771 -IHBlcXU= 81772 -ZXBoaXI= 81773 -IEFsbGllcw== 81774 -IG1vZGVsQW5kVmlldw== 81775 -IGJ1dHRlcmZsaWVz 81776 -IEtpcnN0 81777 -IENoZWNrZXI= 81778 -IGN1bm5pbmc= 81779 -LnNldFk= 81780 -X01hc3Rlcg== 81781 -SW5jcmVhc2luZw== 81782 -IGh1cmRsZQ== 81783 -IGZpc3Rz 81784 -IFNsb3Zha2lh 81785 -IG5vbWJyZXV4 81786 -IDo6Cg== 81787 -dGFza0lk 81788 -IGZvbGx5 81789 -PFRyZWVOb2Rl 81790 -IFZvbGRlbW9ydA== 81791 -IGJsaXN0ZXI= 81792 -xYJl 81793 -LkVudGl0eU1hbmFnZXI= 81794 -LkRPV04= 81795 -IEdyZWdn 81796 -LWNvb3JkaW5hdGU= 81797 -KHZj 81798 -w6FiYg== 81799 -LlRvZ2dsZQ== 81800 -IExpc2Jvbg== 81801 -56I= 81802 -INC/0L7Rgg== 81803 -cGFyZW50Tm9kZQ== 81804 -LnNldFNjYWxl 81805 -X01JU1NJTkc= 81806 -IG91dHJh 81807 -IGt1cA== 81808 -YF0= 81809 -X3ZpYQ== 81810 -ZWRpY3M= 81811 -IEJvcmRlcnM= 81812 -IGlwYWQ= 81813 -IGVkdA== 81814 -IENhcnRlc2lhbg== 81815 -L21hYw== 81816 -IGJhcmxleQ== 81817 -IFNjYXJsZXQ= 81818 -ICAgIAogICAgCiAgICAKICAgIAo= 81819 -cXVlcnlQYXJhbXM= 81820 -IHJoeXRobXM= 81821 -IGdlYXJpbmc= 81822 -Wlg= 81823 -aHlkcmF0aW9u 81824 -U1RT 81825 -IHBsZW50aWZ1bA== 81826 -Y29ycA== 81827 -fUA= 81828 -aW50ZWdy 81829 -L2F0 81830 -LmRlYg== 81831 -IHVuZGVuaWFibGU= 81832 -IG9wZW5zc2w= 81833 -LmRlYWQ= 81834 -IFBpbGxvdw== 81835 -IEJlYW5z 81836 -LmFudA== 81837 -X3Fz 81838 -LWluZm9ybWF0aW9u 81839 -IOuzgOyImA== 81840 -JSIpLAo= 81841 -INC00YDRg9Cz 81842 -IFNwb25nZQ== 81843 -IHNpZnQ= 81844 -dGVzdGltb25pYWw= 81845 -IHVubmF0dXJhbA== 81846 -VUlTY3JvbGxWaWV3 81847 -dmVyZ2VuY2U= 81848 -KHRleHRCb3g= 81849 -LXBhZ2luYXRpb24= 81850 -IERpc3F1cw== 81851 -X3Byb2R1aw== 81852 -YWduYXI= 81853 -S2V5VXA= 81854 -CQkJICAgICAgICA= 81855 -0LXQu9C1 81856 -PHNvdXJjZQ== 81857 -Lmls 81858 -LmF0b20= 81859 -X0NvbXBvbmVudA== 81860 -IHlu 81861 -WydfXw== 81862 -IHdlYWtlc3Q= 81863 -X2RlY3J5cHQ= 81864 -L21zZw== 81865 -Y2Jj 81866 -IHBvbGl0ZWx5 81867 -b21hdA== 81868 -IGVubGlnaHRlbm1lbnQ= 81869 -IGNyZWE= 81870 -IGJydWs= 81871 -X2FscmVhZHk= 81872 -IHNvY2tmZA== 81873 -dW5wYWNr 81874 -b3JnZXM= 81875 -IFVORVNDTw== 81876 -aW5hbGl0eQ== 81877 -IHNlbnRpbmVs 81878 -IGFmZmx1ZW50 81879 -IHRocm93RXJyb3I= 81880 -aWV0cw== 81881 -QU5KSQ== 81882 -IFN1ZmZvbGs= 81883 -YmVybw== 81884 -a2V0w7h5 81885 -RW5kcG9pbnRz 81886 -ZXhlY3V0b3I= 81887 -R2E= 81888 -LkxB 81889 -X3BvcnRmb2xpbw== 81890 -dW5zY2g= 81891 -ZWxhZ2U= 81892 -IGdvYmllcm5v 81893 -IEJpb2w= 81894 -TW9kaWZpY2F0aW9u 81895 -IERlY2ltYWxGb3JtYXQ= 81896 -IFZvY8Oq 81897 -IG1ldGhvZG9sb2dpZXM= 81898 -W10u 81899 -IEdW 81900 -IHJlcGxpY2Fz 81901 -4oCUd2l0aA== 81902 -KTspOwo= 81903 -cG9zaXg= 81904 -U3VjY2Vzc0xpc3RlbmVy 81905 -cGhl 81906 -X25vcm1hbGl6ZQ== 81907 -IExhcmdlcg== 81908 -IHJlcGVyY3Vzc2lvbnM= 81909 -X1ZlcnQ= 81910 -IGhvc3RlbA== 81911 -IGluY29tcGV0ZW50 81912 -aGV2 81913 -X0RFTFRB 81914 -IHB1ZWRv 81915 -aW5zdGFsbGF0aW9u 81916 -X2ZyYWc= 81917 -KHJy 81918 -IE1BVg== 81919 -IExvY2FsaXphdGlvbg== 81920 -KCIiKS4= 81921 -IC0tLS0tLS0tLQ== 81922 -DQoK 81923 -IFB5VHVwbGU= 81924 -IEp1bGlv 81925 -CUdMdWludA== 81926 -bWFya3Vw 81927 -X0ZBTUlMWQ== 81928 -UFJPR1JBTQ== 81929 -IEZpcm13YXJl 81930 -KnNpemU= 81931 -V2lmaQ== 81932 -IHZpc2l0YQ== 81933 -IEVybA== 81934 -RmluZE9iamVjdA== 81935 -LlVOUkVMQVRFRA== 81936 -cGh0aGFsbQ== 81937 -IHBlcnNvbmFsaXpl 81938 -IGNyw6lhdGlvbg== 81939 -ICAgIAkg 81940 -LnByZWNpc2lvbg== 81941 -IHNldHRlcnM= 81942 -IG5ld1NpemU= 81943 -IENhdGFsYW4= 81944 -CW9wdGlvbg== 81945 -IHBpZWw= 81946 -IGNhZ2Vz 81947 -IFN0ZW0= 81948 -ZHJhd2luZw== 81949 -ZXhwbGFpbmVk 81950 -IOaOpw== 81951 -IGRyZWFkZnVs 81952 -ZXJydXB0ZWQ= 81953 -LmdldFZhbHVlQXQ= 81954 -IGVsYXBzZWRUaW1l 81955 -IGluZGVmaW5pdGU= 81956 -IFRIQU5L 81957 -X3N0YXJ0dXA= 81958 -U1VSRQ== 81959 -IGtpZG5leXM= 81960 -IEN1aXNpbmU= 81961 -fGFycmF5 81962 -U2VuZE1lc3NhZ2U= 81963 -ZmF2 81964 -IEFlcm9zcGFjZQ== 81965 -X21lYW5z 81966 -IG5lYg== 81967 -IE9UUA== 81968 -IGNodXJu 81969 -L2Zy 81970 -IFJlaWdu 81971 -X2NsYXNzaWZpY2F0aW9u 81972 -IE1hY0RvbmFsZA== 81973 -Ii4KCgoK 81974 -IGNoaWxseQ== 81975 -IOivt+axgg== 81976 -aWhhdA== 81977 -U1RB 81978 -J2F1dHJlcw== 81979 -IGxhc2M= 81980 -Lm1peA== 81981 -IGJsb3Q= 81982 -IElERA== 81983 -ZGF0YXRhYmxl 81984 -c3BpZWw= 81985 -IMOpeGl0bw== 81986 -YXJ0aWM= 81987 -LkF4aXM= 81988 -LmFkdmFuY2U= 81989 -IG1vdXNlWA== 81990 -J8Og 81991 -IHJlY2lldmVk 81992 -IHBvc2k= 81993 -IGZvdXJu 81994 -IE1hZmlh 81995 -IHBjYQ== 81996 -YmVsb25ncw== 81997 -YWJseXR5cGVk 81998 -QVVUSE9SSVpFRA== 81999 -LnNjYWxhYmx5dHlwZWQ= 82000 -7JyE 82001 -LWRvdA== 82002 -IGVtcGhhc2l6aW5n 82003 -TWVtYmVyc2hpcA== 82004 -KnBvdw== 82005 -LXNwaW4= 82006 -cnV0YQ== 82007 -aGV2aWs= 82008 -X0FTWU5D 82009 -X2NvbXBpbGVy 82010 -LkZsYWc= 82011 -IGVsYm93cw== 82012 -LkNSRUFURQ== 82013 -TWV0cm8= 82014 -LmxvZ3M= 82015 -em1hbg== 82016 -cG9uZQ== 82017 -xJnFvA== 82018 -IGludGVycw== 82019 -IHdlYnM= 82020 -X0hJRERFTg== 82021 -CW5vdw== 82022 -Q29tbXVuaWM= 82023 -JHRwbA== 82024 -c2NvcGVz 82025 -IFppa2E= 82026 -IHN0cmluZ3N0cmVhbQ== 82027 -IFVuY2F0ZWdvcml6ZWQ= 82028 -Rlk= 82029 -L3N3YWdnZXI= 82030 -UGVubg== 82031 -aW1lSW50ZXJ2YWw= 82032 -IGNvbnRlbmRz 82033 -eGllcw== 82034 -IFNhbGVzZm9yY2U= 82035 -IHV0ZW5z 82036 -IHVuZGlz 82037 -Q3J5c3RhbA== 82038 -Lm5kaW0= 82039 -IGZvcm11bA== 82040 -IEZhdg== 82041 -5bm/ 82042 -cmlzaw== 82043 -bmFk 82044 -L3Rvcw== 82045 -IFBFUkZPUk1BTkNF 82046 -IHdyaXRlbG4= 82047 -IGNvbGxv 82048 -YW50aWNhbGx5 82049 -VURFTlQ= 82050 -Umdi 82051 -IG9mZXJl 82052 -IG1lcmdlcw== 82053 -ZmlkZg== 82054 -IGt6 82055 -VmljdG9yaWE= 82056 -IC9eXA== 82057 -IGt1YmU= 82058 -IEFwb3N0bGU= 82059 -IGRlZmVuZHM= 82060 -PD0o 82061 -IE1FTU9SWQ== 82062 -XElk 82063 -IEFjdGl2ZUZvcm0= 82064 -IE9uZVBsdXM= 82065 -SHR0cFNlcnZsZXRSZXF1ZXN0 82066 -IFRlbXBEYXRh 82067 -7KCB 82068 -LkFTQ0lJ 82069 -2YTYpw== 82070 -S0k= 82071 -IGZyYXQ= 82072 -X0NJUEhFUg== 82073 -LlN1cmZhY2U= 82074 -IHBpdGZhbGxz 82075 -LW1lZGlhdGVk 82076 -eXBp 82077 -LWFsaXN0 82078 -eEJD 82079 -dGVhY2hlcnM= 82080 -IEN5Yw== 82081 -IHBzeWNoZWRlbGlj 82082 -IER1bWJsZWRvcmU= 82083 -IikuCgo= 82084 -IFRoYXRjaGVy 82085 -IFByaW5jaXBsZQ== 82086 -VG9nZXRoZXI= 82087 -IGZsb3Jh 82088 -d2Vla3M= 82089 -X2NyaXRlcmlh 82090 -Ym9uZXM= 82091 -LmludGVybmV0 82092 -IGJsb2NrRGlt 82093 -LlNpbmdsZU9yRGVmYXVsdA== 82094 -RGljZQ== 82095 -IEV2ZWw= 82096 -IFRMYWJlbA== 82097 -IElnb3I= 82098 -IENvcHA= 82099 -IGluYXVndXI= 82100 -L3ByaXZhdGU= 82101 -IGFiZXJy 82102 -bmRz 82103 -O2lm 82104 -LXJhbmdpbmc= 82105 -YWNodHM= 82106 -X21hcnNoYWxs 82107 -IF9fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18= 82108 -LmVuZFRpbWU= 82109 -IE1vZGVsUmVuZGVyZXI= 82110 -KGZvb2Q= 82111 -KCJ+ 82112 -IHN1cHBs 82113 -KCJcKA== 82114 -U3E= 82115 -VHJhbnNsYXRlZA== 82116 -IENvbnRpbnVpbmc= 82117 -IHBvc3Nvbm8= 82118 -RklYTUU= 82119 -IEFuZ2Vib3Q= 82120 -aWV2ZXI= 82121 -IEt5b3Rv 82122 -Y2ls 82123 -TmV3VXJsUGFyc2Vy 82124 -LkRp 82125 -IGh1bWFuZQ== 82126 -RGVtYW5k 82127 -IE1hcnRpYW4= 82128 -d29vZHM= 82129 -IEhlYWw= 82130 -IFl1ZQ== 82131 -IGNvdXJ0aG91c2U= 82132 -IHZvbnQ= 82133 -IGJvbnM= 82134 -aW50ZWdyYWw= 82135 -ICQoJyMn 82136 -ZXRlcm1pbmF0aW9u 82137 -Lm1vZGlmaWVk 82138 -IHByaW5jaXBhbHM= 82139 -IGFsYXJtZWQ= 82140 -LmNyZWF0ZU9iamVjdA== 82141 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo= 82142 -L2NvdW50 82143 -IGVudHJlbmNoZWQ= 82144 -XGE= 82145 -IGludHJ1c2lvbg== 82146 -IE54 82147 -CQkKCQkKCQkK 82148 -Y2hlbWF0aWM= 82149 -IHNsaWRlcnM= 82150 -IHNlbGVjdGFibGU= 82151 -X25s 82152 -aWVzZQ== 82153 -X2VzdGltYXRvcnM= 82154 -IFN2Zw== 82155 -IGRlbGV0ZVVzZXI= 82156 -KG1hcHBpbmc= 82157 -IOyymOumrA== 82158 -IGFudGFnb25pc3Q= 82159 -IGtpbmFzZQ== 82160 -IHdlbGRlZA== 82161 -IExlbmE= 82162 -ZWRpdGg= 82163 -aWFsaQ== 82164 -KHBpYw== 82165 -IGJyZWFjaGVk 82166 -UElD 82167 -IGNvYXN0ZXI= 82168 -RkRB 82169 -IGtyZQ== 82170 -cGVyZmls 82171 -IEdlbXM= 82172 -X2ZlbmNl 82173 -VVJMUmVxdWVzdA== 82174 -4oCZYXBw 82175 -UkVGRVJFTkNF 82176 -LkV4cG9ydA== 82177 -IG1pbmltaXplZA== 82178 -aXBlbA== 82179 -aWRhdGE= 82180 -KWRlYWxsb2M= 82181 -ZXNjYWw= 82182 -X2Z3ZA== 82183 -bWVtY3B5 82184 -IExvcmk= 82185 -X1JlZg== 82186 -IGJhcmE= 82187 -IFNlbGxlcnM= 82188 -IGRldGVyaW9yYXRpb24= 82189 -ZnJhY3Rpb24= 82190 -KV07 82191 -L3BsYXk= 82192 -wqU= 82193 -LXRlc3Rz 82194 -T2Zmc2V0cw== 82195 -T2k= 82196 -IEtsYXVz 82197 -IHF1ZXJ5aW5n 82198 -d2lzaA== 82199 -YXBlbA== 82200 -X3dvcmtpbmc= 82201 -bXlNb2RhbExhYmVs 82202 -IHRvRGF0ZQ== 82203 -cGVybWFsaW5r 82204 -IGZyZWM= 82205 -b2xlY3VsZXM= 82206 -IEdvb3Nl 82207 -LXdpZGdldHM= 82208 -dHVydGxl 82209 -SW1wcm92ZWQ= 82210 -IHJvYWR3YXk= 82211 -a2Vocg== 82212 -IGFzdHJvbm9teQ== 82213 -Q29tYmluZQ== 82214 -IGNpZ2Fycw== 82215 -X0dBVEU= 82216 -L21hbmFnZQ== 82217 -IEdlcmFyZA== 82218 -IFByb3RlY3Rvcg== 82219 -U3Vic3lzdGVt 82220 -L2ZpbmQ= 82221 -L1lZWVk= 82222 -IHRvdGFsaW5n 82223 -0LzQvtGC 82224 -IE9tYW4= 82225 -IGluZmluaXQ= 82226 -LW9mZmljZQ== 82227 -IGluc3RhbnRpYXRpb24= 82228 -LsKn 82229 -Y2V1 82230 -KGF0b20= 82231 -IERyb3BvdXQ= 82232 -7YGs 82233 -IGNvbmRlbW5pbmc= 82234 -X2Jhc2VuYW1l 82235 -XX08Lw== 82236 -RGF0YUNvbnRleHQ= 82237 -IFdhc2hpbmc= 82238 -Lk9O 82239 -IG1vbW15 82240 -KCl9Owo= 82241 -IDspCgo= 82242 -L2V4dA== 82243 -Zm9yZWdyb3VuZENvbG9y 82244 -dW5zdXBwb3J0ZWQ= 82245 -IHNvbGxlbg== 82246 -IGNvbWXDpw== 82247 -RElTQUJMRQ== 82248 -IG9uUGF1c2U= 82249 -INGH0YLQvtCx0Ys= 82250 -IEFpbg== 82251 -R3M= 82252 -CVRhc2s= 82253 -aGF3aw== 82254 -Ik5vdA== 82255 -QUdS 82256 -LmdldFRhYmxl 82257 -IGRpdmVyZ2VuY2U= 82258 -IG5lZ29jaQ== 82259 -UmVwbGFjaW5n 82260 -XX0pCg== 82261 -aWxsdXNpb24= 82262 -IM6U 82263 -X0tFWUJPQVJE 82264 -S3I= 82265 -CW9y 82266 -56Gu6K6k 82267 -CXByaW50bG4= 82268 -IFNlYXJjaGVz 82269 -IEZyZXNubw== 82270 -IHZlcmRhZA== 82271 -XE1pZGRsZXdhcmU= 82272 -IOy1nA== 82273 -fSkoKTs= 82274 -dGV4dEFsaWdu 82275 -aW5rZWw= 82276 -LlR4dA== 82277 -IG9wdGltaXphdGlvbnM= 82278 -eW91bmc= 82279 -IGxlYXNlZA== 82280 -SlQ= 82281 -IElvbmljTW9kdWxl 82282 -ZXR0aW5ncw== 82283 -ZXNlaGVu 82284 -IGZhdm91cmFibGU= 82285 -YW5leQ== 82286 -IG90aGVyQnV0dG9uVGl0bGVz 82287 -IFRoYW1lcw== 82288 -CXVuaXQ= 82289 -Q09MVU1O 82290 -IGxvaQ== 82291 -LHByb3Rv 82292 -X1BSSQ== 82293 -IHdhbmRlcmVk 82294 -IHNhcGk= 82295 -YmFja3dhcmQ= 82296 -YXJhb2g= 82297 -IEZI 82298 -IEFsZw== 82299 -CWFj 82300 -YXJybw== 82301 -5Y6G 82302 -IFNPUw== 82303 -IERyZWFk 82304 -VmVjdG9yWGQ= 82305 -LnJtdHJlZQ== 82306 -X2V4ZWN1dG9y 82307 -IHByZWduYW5jaWVz 82308 -IHByYWN5 82309 -IFd3dw== 82310 -IEFyY2hiaXNob3A= 82311 -IG1laW5lbg== 82312 -RlU= 82313 -LkVudg== 82314 -IGVubGlnaHRlbmVk 82315 -IG9yaWdpbmF0ZQ== 82316 -5Y+K 82317 -IHpsaWI= 82318 -X1NB 82319 -IHdhc3Rlcw== 82320 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 82321 -cHJhcw== 82322 -IGhvcnJpZmllZA== 82323 -IENhbGR3ZWxs 82324 -dG95 82325 -X3Nob3Q= 82326 -IGxlc2Jp 82327 -IE1hZ25ldA== 82328 -b3hpYw== 82329 -U3VybmFtZQ== 82330 -IHNob3dUb2FzdA== 82331 -CURlc3Ryb3k= 82332 -LmdldEV4dGVybmFs 82333 -SUxJ 82334 -IE5ldmlsbGU= 82335 -dHNreQ== 82336 -IG1lbGFrdWthbg== 82337 -ICImIw== 82338 -IGZsb3dlcmluZw== 82339 -IHZldGVyaW5hcmlhbg== 82340 -IGhhcm1vbmlj 82341 -IENhc3NhbmRyYQ== 82342 -KENyZWF0ZQ== 82343 -cGVyc2U= 82344 -UGVybQ== 82345 -KU5TU3RyaW5n 82346 -IGlzSW4= 82347 -IEZsb2F0aW5nQWN0aW9uQnV0dG9u 82348 -L05ldw== 82349 -IPCd 82350 -Y2FwYWJpbGl0eQ== 82351 -IGN1Y2tvbGQ= 82352 -IEJhaW4= 82353 -KCl7DQoNCg== 82354 -UEVBUg== 82355 -IGphd3M= 82356 -IGdvZGU= 82357 -IGNhc3NldHRl 82358 -LmZyZXF1ZW5jeQ== 82359 -U0NPUkU= 82360 -LmludGVudA== 82361 -Olsi 82362 -IOWmguaenA== 82363 -77yf4oCd 82364 -L0ltYWdl 82365 -IHNpZW5kbw== 82366 -X2FsbG9jYXRpb24= 82367 -OkI= 82368 -L1JlZ2lzdGVy 82369 -X2thdGVnb3Jp 82370 -dW55YQ== 82371 -Lmluc3RhbmNlcw== 82372 -IFVOSVZFUlNJVFk= 82373 -IHBsZWFzYW50bHk= 82374 -IGdsYW5kcw== 82375 -IFlFTExPVw== 82376 -IFRoaWNr 82377 -QW10 82378 -IHByeQ== 82379 -IGx1aw== 82380 -KHByb2JsZW0= 82381 -IHByb2plY3Rpbmc= 82382 -W25vdw== 82383 -IGVzdG95 82384 -KCgpPT4= 82385 -IHdheXBvaW50cw== 82386 -IEJsaWNr 82387 -LlJlcXVpcmU= 82388 -TGFrZQ== 82389 -IElHTk9SRQ== 82390 -IFFIQm94TGF5b3V0 82391 -X3Jlc3BvbnNlcw== 82392 -Lndy 82393 -JmFjdGlvbg== 82394 -LmNoYXJhY3RlcnM= 82395 -SVc= 82396 -cGFnZU51bQ== 82397 -IGRpc3RyYWN0aW5n 82398 -XS0n 82399 -cGVlcw== 82400 -b3VuY3k= 82401 -IHNlZ3U= 82402 -LmdldFNlbGVjdGlvbk1vZGVs 82403 -SW5saW5pbmc= 82404 -J2FmZg== 82405 -IFByZXNlcnZl 82406 -IGFjcXVhaW50YW5jZQ== 82407 -IGFudXM= 82408 -aW5zdGl0dXRpb24= 82409 -IC8vKg== 82410 -IFNpY2s= 82411 -IEtvZGk= 82412 -IEFWUg== 82413 -IGJldHI= 82414 -IEJlcm5zdGVpbg== 82415 -LGN2 82416 -Y2Ni 82417 -Q0FG 82418 -CXNpZ25hbA== 82419 -6KiI 82420 -UmVzdWx0c0NvbnRyb2xsZXI= 82421 -IHNhbG9wZXM= 82422 -IHBoZW5vdHlwZQ== 82423 -dWJhaA== 82424 -X2RhdGFzZXRz 82425 -IGdyYWNpb3Vz 82426 -IENsaXBib2FyZA== 82427 -IGdlbmRlcnM= 82428 -ZG93bmxvYWRz 82429 -RXhwZXJpbWVudGFs 82430 -IGJla2FubnQ= 82431 -IG5pdmU= 82432 -LkVk 82433 -ZGlzbWlzcw== 82434 -XFR3aWc= 82435 -LkF2 82436 -L3Rhc2tz 82437 -LnBpY2tsZQ== 82438 -KkI= 82439 -Y2VzdG9y 82440 -Y2FwaXRhbGl6ZQ== 82441 -LkdldFNlcnZpY2U= 82442 -S2V5SWQ= 82443 -LnBpdGNo 82444 -IENvbnRyb2xsZWQ= 82445 -LnNhdmVk 82446 -IHphag== 82447 -IENhdGh5 82448 -KENhbmNlbGxhdGlvblRva2Vu 82449 -LWFuaW1hdGU= 82450 -XFxc 82451 -IEphc21pbmU= 82452 -LkxJTkU= 82453 -IGJvdGhlcnM= 82454 -IGJ1ZmZhbG8= 82455 -IEZPUkVJR04= 82456 -IHRhY2tsZWQ= 82457 -X0hFQVA= 82458 -IHNlcnZpYw== 82459 -Pj4s 82460 -IEFjdG9ycw== 82461 -LlR4 82462 -ZWJ4 82463 -X3Zpc2l0b3I= 82464 -X21hcnNoYWxlZA== 82465 -LG1hcA== 82466 -IGhlYXRlcnM= 82467 -IHVMb2NhbA== 82468 -IEthcG9vcg== 82469 -IG1pbnV0 82470 -LnJlYWRBcw== 82471 -IC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4u 82472 -X1ZPTFQ= 82473 -LmJ6 82474 -IGNvcnJlY3Rpbmc= 82475 -U0VQ 82476 -YnJpbmc= 82477 -SHU= 82478 -IEd1cw== 82479 -QUFE 82480 -aWVyYW4= 82481 -ZnJhcmVk 82482 -X3JvbQ== 82483 -IHNjYXJjaXR5 82484 -IGFwb2xvZ2lzZQ== 82485 -IHNvbGlkcw== 82486 -IEZvcm1hdHRlcg== 82487 -ICclJA== 82488 -LXZpcw== 82489 -IiwiIiw= 82490 -VU5ERVI= 82491 -ISEhIQoK 82492 -IEVsZXZlbg== 82493 -KSld 82494 -IHNhdGlyZQ== 82495 -XHVC 82496 -IHNldmVudGVlbg== 82497 -TEFOR1VBR0U= 82498 -IGFkdmVyc2FyeQ== 82499 -IHN0cmZ0aW1l 82500 -IG5leHVz 82501 -dWJpdHM= 82502 -ICclIg== 82503 -IFNLSVA= 82504 -S0hS 82505 -LmJhdA== 82506 -IEplYW5z 82507 -Lj8= 82508 -IGltcG9zdA== 82509 -LnF0eQ== 82510 -Q29tcHJlc3Npb24= 82511 -IHByaW5jaXBhbGVz 82512 -b25pbw== 82513 -IGJhcmNlbG9uYQ== 82514 -IENoaWxp 82515 -X21vc3Q= 82516 -LnVm 82517 -IGNvbnRlbnRWYWx1ZXM= 82518 -IEZpc3Q= 82519 -dWdhZG9y 82520 -VGV4dFdyaXRlcg== 82521 -QkFDS0dST1VORA== 82522 -IGxpdnJv 82523 -IERlc2lyZQ== 82524 -bWVhc3VyZW1lbnQ= 82525 -UHJvYmU= 82526 -IHB1ZGRpbmc= 82527 -LnNob3dFcnJvcg== 82528 -IHVudGVyc3TDvHQ= 82529 -44CB44CB 82530 -IMSHZQ== 82531 -IHB1bml0aXZl 82532 -5q2i 82533 -TGlzdEdyb3Vw 82534 -LkFyZWE= 82535 -IPCfmIkKCg== 82536 -b29yZA== 82537 -IHNjcmFwaW5n 82538 -KHRpY2tldA== 82539 -IFdvY2hl 82540 -IGV4cGVjdGVkUmVzdWx0 82541 -IEtvc3Rlbmxvcw== 82542 -Y29uZmlndXJlZA== 82543 -X3N0cmVycm9y 82544 -LmFkZEhhbmRsZXI= 82545 -bW91c2VsZWF2ZQ== 82546 -IEZlbGlwZQ== 82547 -IENoaW0= 82548 -X0NTUg== 82549 -UENB 82550 -aWZpY2HDp8Ojbw== 82551 -KysKCg== 82552 -eWFz 82553 -IOaWueazlQ== 82554 -IElETQ== 82555 -IGFuaW1hdGVXaXRoRHVyYXRpb24= 82556 -IHNhbWVu 82557 -LnN1YnRpdGxl 82558 -X0tleURvd24= 82559 -IFRyZXk= 82560 -IHRlbXBvcmFkYQ== 82561 -IHNwZA== 82562 -IFJj 82563 -IE1hc3NpdmU= 82564 -IGJvd3M= 82565 -SG9zcGl0YWw= 82566 -IGdyb290 82567 -IHBhdmluZw== 82568 -IGNob3Jlcw== 82569 -IEFsbHk= 82570 -IGNlcnRpZmljYXRpb25z 82571 -IHhib3g= 82572 -c2VsZWN0QWxs 82573 -R2FtZU92ZXI= 82574 -IGNvcm5lcnN0b25l 82575 -UmVjb3ZlcmVk 82576 -IGRlZW0= 82577 -VWx0cmE= 82578 -IGdldExhc3Q= 82579 -IGFsbWE= 82580 -LnRleHRGaWVsZA== 82581 -IHdhaXZlZA== 82582 -Pih7Cg== 82583 -IEVzdHI= 82584 -aXNhYmxl 82585 -IHByb3Rvbg== 82586 -X2ZhY2Vib29r 82587 -X1RSQUlO 82588 -IGNvb3BlcmF0aW5n 82589 -dW5naQ== 82590 -QXJpem9uYQ== 82591 -I2VjaG8= 82592 -LWV4cHJlc3Npb24= 82593 -Lm1pbnV0ZXM= 82594 -IHByZWZpeGVk 82595 -IGZpc2hlcmllcw== 82596 -LmNvcnJlY3Q= 82597 -IG7Dpg== 82598 -KFNwcml0ZQ== 82599 -TW9kcw== 82600 -IFZpZGU= 82601 -IGdldEJ5SWQ= 82602 -IEtleW5lcw== 82603 -IEVneXB0aWFucw== 82604 -X0NPRA== 82605 -Qmllbg== 82606 -cmVvcGVu 82607 -aWdoZXQ= 82608 -UkVERU5USUFM 82609 -IHVud2luZA== 82610 -JA0K 82611 -IHJhY2tldA== 82612 -IGZsb2F0VmFsdWU= 82613 -IFNwZWNpYWx0eQ== 82614 -b2NhdGU= 82615 -bW91bnRlZA== 82616 -QXR0ZW1wdHM= 82617 -T2ZmaWNlcnM= 82618 -SGFzaFRhYmxl 82619 -IGTDqXZlbG9wcGVtZW50 82620 -IGRhcA== 82621 -IG10eA== 82622 -TmFycmF0ZWQ= 82623 -a0I= 82624 -X1NUQQ== 82625 -LUNsYXNz 82626 -IGR1bA== 82627 -IExlYWRz 82628 -IHRyw6pz 82629 -ZnJpZW5kbHk= 82630 -IEZpbHRlcmluZw== 82631 -LXByb3ZpZGVy 82632 -INGD0YHQvw== 82633 -IEtvbGthdGE= 82634 -bWFza2Vk 82635 -SURhdGE= 82636 -IFt8 82637 -wqQ= 82638 -IFJlZXNl 82639 -IEhvbm9sdWx1 82640 -VG9PYmplY3Q= 82641 -IHRocmlmdA== 82642 -YXNzaQ== 82643 -IGNvbmdyYXR1bGF0aW9ucw== 82644 -U0tJ 82645 -ZW50YXJpb3M= 82646 -IEZST05U 82647 -dWZpZw== 82648 -aG9u 82649 -CWdldGxpbmU= 82650 -IGhlYXJ0eQ== 82651 -Y2FsaW5n 82652 -IMOpY29ub20= 82653 -ICoqKi8K 82654 -X0hFUkU= 82655 -YCg= 82656 -TWljaGlnYW4= 82657 -QmVhbnM= 82658 -LXJvdXRl 82659 -IHByaW5j 82660 -IEd1aWRhbmNl 82661 -CWVtaXQ= 82662 -Lk9Q 82663 -dGhpYw== 82664 -ZWxvcGU= 82665 -IElSZXF1ZXN0 82666 -IGhhbmRsZUNsb3Nl 82667 -ZGF0YUFycmF5 82668 -LkV4ZWN1dGVTY2FsYXI= 82669 -RVBISVI= 82670 -IENvbnZlcnNlbHk= 82671 -KEZvbnQ= 82672 -IG1ldHJl 82673 -IFNwaWVsZXI= 82674 -RWxsaXBzZQ== 82675 -IFBWT0lE 82676 -IERhdGFDb250ZXh0 82677 -Y29uc3RydWN0ZWQ= 82678 -QU5ESU5H 82679 -LS0tLS0tLS0tLS0qLwo= 82680 -Qm9uam91cg== 82681 -X1BIUA== 82682 -cHJvZ3Jlc3NiYXI= 82683 -Tm90U3VwcG9ydGVkRXhjZXB0aW9u 82684 -IHZlcmRhZGU= 82685 -L2NoYW5nZQ== 82686 -b3Jzaw== 82687 -IGFyb21hdGlj 82688 -cmVzcG9ucw== 82689 -cmVhbGxvYw== 82690 -YXRpc2No 82691 -LGV2 82692 -IFNpb3V4 82693 -dGVh 82694 -IFBvZQ== 82695 -5LmI 82696 -X2Ntb3M= 82697 -IGFsYg== 82698 -KGxy 82699 -IEFwcGFyZWw= 82700 -IGRlbGxv 82701 -INGC0L7Rhw== 82702 -IHN0cmVhbWxpbmU= 82703 -d2NoYXI= 82704 -QWRvYmU= 82705 -LG1vZHVsZQ== 82706 -IHVuaW5zdXJlZA== 82707 -fSIpDQo= 82708 -KCIvLypbQA== 82709 -LXBoYXNl 82710 -IGZldQ== 82711 -X3RB 82712 -em9law== 82713 -IGZvbGxpYw== 82714 -IHR1Zw== 82715 -IGJlZmluZA== 82716 -IHRhbGxlc3Q= 82717 -KG10 82718 -aWVkeQ== 82719 -X0xlbmd0aA== 82720 -IHN0YXVuY2g= 82721 -IHJlbW92ZU9iamVjdA== 82722 -IGZsYWtlcw== 82723 -Z3Jlc3Fs 82724 -IGlua2w= 82725 -IFNDU0k= 82726 -IEtlZXBlcg== 82727 -O2w= 82728 -IEhpbmR1cw== 82729 -X1BFRA== 82730 -X0NPTkQ= 82731 -IExhdW5kcnk= 82732 -KytdPQ== 82733 -X0FVWA== 82734 -IGJ5xYI= 82735 -IGF1bWVudG8= 82736 -bWFyZ2luTGVmdA== 82737 -ZXF1YWxpdHk= 82738 -IEx1eg== 82739 -IEVjaw== 82740 -X21hcw== 82741 -X2xlbnM= 82742 -IHN0ZXJpbGU= 82743 -Y2xpZW50ZXM= 82744 -J30pCgo= 82745 -IGdvb2R3aWxs 82746 -IEVsbGlzb24= 82747 -U3BhY2VJdGVt 82748 -IHNob3dNZXNzYWdl 82749 -66Gc6re4 82750 -IGNvbnRyYXRv 82751 -UG9zdGluZw== 82752 -LmludGVycG9sYXRl 82753 -KGZpbGw= 82754 -IGJ1bGxwZW4= 82755 -LmdlbmVy 82756 -IGh1ZXM= 82757 -IG1lbW9yYW5kdW0= 82758 -dG9Qcm9taXNl 82759 -IEJ5eg== 82760 -KHB4 82761 -KFByb2dyYW0= 82762 -UkVTU0lPTg== 82763 -YmZk 82764 -IHBsYW50YQ== 82765 -Lm1vdXNlUG9zaXRpb24= 82766 -IFNwYW0= 82767 -6LSn 82768 -dGVsZWdyYW0= 82769 -YWd5 82770 -IGdlZnVuZGVu 82771 -LkRvbQ== 82772 -IGxpbmVtYW4= 82773 -LmJ0bkRlbGV0ZQ== 82774 -IHNlbGVjdGl2ZWx5 82775 -65Og 82776 -SUZT 82777 -IEdldEhhc2hDb2Rl 82778 -IHJldGly 82779 -IHJlcXVpc2l0ZQ== 82780 -QlRUYWc= 82781 -cGxpYg== 82782 -IGZpcmVmb3g= 82783 -LnRyYWRl 82784 -ICMk 82785 -LmNvbXByZXNz 82786 -IGxhZGVu 82787 -IERpcmVjdG9yeUluZm8= 82788 -IE1vZGVz 82789 -IGtvbmU= 82790 -IGRpdnVs 82791 -CWhz 82792 -Y3JvZnQ= 82793 -IFdIWQ== 82794 -eENF 82795 -L0dyaWQ= 82796 -X0FVRA== 82797 -IFNjcmU= 82798 -IGVycm9yVGhyb3du 82799 -U2FkbHk= 82800 -YXRpdGlz 82801 -IG5lZ2xpZ2libGU= 82802 -LlJlZ2lzdGVyVHlwZQ== 82803 -IE1vaXN0 82804 -5rWL6K+V 82805 -IEJNQw== 82806 -bGVhZmxldA== 82807 -eW5l 82808 -cm9rZW4= 82809 -IHZpbmM= 82810 -dHR5 82811 -IGJldXJldHRl 82812 -IEFscGluZQ== 82813 -IE1jTQ== 82814 -U3BvaWxlcg== 82815 -ZGlzdHJpYnV0aW9u 82816 -LXJheXM= 82817 -IOuwlA== 82818 -X3BhcmVudHM= 82819 -IGNyYXRlcw== 82820 -IGNvbW11dGVycw== 82821 -IEFyZ2VudGluZQ== 82822 -77u/LyoK 82823 -L2ZyYW1ld29yaw== 82824 -IGNoYW5uZWxJZA== 82825 -Z3JlZW5z 82826 -LnNldFN0eWxlU2hlZXQ= 82827 -IGluYWNjZXNzaWJsZQ== 82828 -aXRhdGVz 82829 -IHdhcm1lZA== 82830 -RmFicmlj 82831 -Z2V0YXR0cg== 82832 -ZGlzcGxheVRleHQ= 82833 -X01PTklUT1I= 82834 -IHNpZGV3YWxrcw== 82835 -SW50aWFsaXplZA== 82836 -IGtvbWVu 82837 -IGRpc2NyaW1pbmF0b3I= 82838 -IE5hdmlnYXRl 82839 -KERpcmVjdGlvbg== 82840 -IFNwaXQ= 82841 -X2FkZGl0aW9uYWw= 82842 -IGh0b24= 82843 -IGVzcGVyYQ== 82844 -IGRlbHZl 82845 -IGNvbXBhcnRpcg== 82846 -IHByZWVtcHQ= 82847 -cHJvY2Vzc29ycw== 82848 -LWdpdA== 82849 -YmVlbg== 82850 -LlNVQg== 82851 -IFJlZXZlcw== 82852 -L2dlbg== 82853 -O3RvcA== 82854 -CU1QSQ== 82855 -Wlc= 82856 -R0VTVA== 82857 -YWJpbGly 82858 -IHByb2dyZXNzaXZlcw== 82859 -aGFmdA== 82860 -QXVm 82861 -IEFjdGlvblR5cGU= 82862 -bGVv 82863 -IHV0YW4= 82864 -SW5pY2lhbA== 82865 -PlVzZXI= 82866 -IH0pOwoKCgo= 82867 -INio2Yc= 82868 -IENoYWlucw== 82869 -aXNzcGFjZQ== 82870 -L3JlbQ== 82871 -U1FMaXRl 82872 -IGNlYXNlZmlyZQ== 82873 -JGFy 82874 -VFJT 82875 -Oi8vew== 82876 -IFNwaXJpdHM= 82877 -2Lo= 82878 -KFNpemU= 82879 -IG51Zw== 82880 -IE9sc2Vu 82881 -IGNobG9yaWRl 82882 -IERpc3BsYXlOYW1l 82883 -IFBlcnQ= 82884 -IGdldE1heA== 82885 -IEVkaXRvcnM= 82886 -IFBhaXM= 82887 -YXNtdXM= 82888 -VmFj 82889 -IFRhYmxlTmFtZQ== 82890 -IG51YW5jZWQ= 82891 -Rm9yTWVtYmVy 82892 -IHNsZWVweQ== 82893 -YWR2aXNvcg== 82894 -IHN0YWxraW5n 82895 -Lm1lZGlhbg== 82896 -X0F0dA== 82897 -IGdldE5vZGU= 82898 -IEZhbmN5 82899 -5pWw6YeP 82900 -LkF0dHJpYnV0ZVNldA== 82901 -KGluc3RydWN0aW9u 82902 -eEJE 82903 -IGtvcA== 82904 -QWZmZWN0ZWQ= 82905 -L25hdmJhcg== 82906 -IGFpbG1lbnRz 82907 -IFJhbWFkYW4= 82908 -IEFjY2VudA== 82909 -IFBhcmFtb3VudA== 82910 -IEdBTQ== 82911 -5L2N572u 82912 -PSov 82913 -LklOUFVU 82914 -PFByb2plY3Q= 82915 -TGVhc3Q= 82916 -IEdlbm9tZQ== 82917 -QWNjZXNzb3JUeXBl 82918 -bGVmdHJpZ2h0YXJyb3c= 82919 -dmVudGluZw== 82920 -L3BheW1lbnQ= 82921 -X1B0cg== 82922 -IHRhbWU= 82923 -IE1FTUJFUg== 82924 -IEJpdGNvaW5z 82925 -LmVwYW0= 82926 -LlBsZWFzZQ== 82927 -IHNjaHdhcg== 82928 -Q3BwTWV0aG9kSW50aWFsaXplZA== 82929 -IHVuaWNvcm4= 82930 -IGJlZGV1dA== 82931 -X0hT 82932 -IGF1dG9nZW5lcmF0ZWQ= 82933 -IExpbGx5 82934 -IEFzc2Vzcw== 82935 -IEhlaWRp 82936 -LnNvdXJjZXM= 82937 -LnRlbGw= 82938 -YXJnaW5z 82939 -KCInIiw= 82940 -0LvQvtC2 82941 -IEVyb3RpYw== 82942 -IGp1c3Rv 82943 -IGVzYWM= 82944 -Y29tYQ== 82945 -IENvbG9ueQ== 82946 -IHBjdA== 82947 -CWVu 82948 -IGVtcGV6 82949 -IERlbGV0aW5n 82950 -TkVM 82951 -IGVuYW0= 82952 -UHJlc3NFdmVudA== 82953 -IFJlc29sdmVy 82954 -IFJURQ== 82955 -Rng= 82956 -IEluY29ycmVjdA== 82957 -IHlj 82958 -X3JlYWRpbmc= 82959 -O2Jhc2U= 82960 -IGhhc2h0YWdz 82961 -IE1hcmluZXJz 82962 -LlNldEZsb2F0 82963 -IHJlYXNzdXJpbmc= 82964 -aXJzY2g= 82965 -KHVzZXJpZA== 82966 -ID09PT0= 82967 -XSkpKTsK 82968 -a2Y= 82969 -IHRpbGVk 82970 -ZWd1YXJk 82971 -Q2xpZW50ZXM= 82972 -5pmC6ZaT 82973 -ZHNs 82974 -UmlnaHRz 82975 -IFBzYWxt 82976 -ZHVyaW5n 82977 -Q2xlYXJDb2xvcg== 82978 -dXN0YQ== 82979 -PENvbW1lbnQ= 82980 -IG5venpsZQ== 82981 -IFBMQUNF 82982 -L2hpc3Rvcnk= 82983 -aWh1 82984 -aVZhcg== 82985 -IGdlcm0= 82986 -IHRyaW1taW5n 82987 -IEh1bnRlcnM= 82988 -IFJTVlA= 82989 -SW50ZXJlc3RpbmdseQ== 82990 -amlhbg== 82991 -KSl7Cgo= 82992 -LkV4cGVjdA== 82993 -IFRvaWxldA== 82994 -IHdhbGxwYXBlcnM= 82995 -LldlYlNlcnZsZXQ= 82996 -YXJwYQ== 82997 -L21haW53aW5kb3c= 82998 -aHE= 82999 -IHV5 83000 -IGluZGlnbg== 83001 -Q2hlY2tlZENoYW5nZUxpc3RlbmVy 83002 -IGNhbGxlcnM= 83003 -IE1vdXNlRXZlbnRBcmdz 83004 -IEpTY3JvbGxQYW5l 83005 -IHfFgmE= 83006 -cmVwb3NpdG9yaWVz 83007 -IMWbdw== 83008 -IHJlZmVyZW5jaWE= 83009 -IGlvdGE= 83010 -IGNhcmdhcg== 83011 -X29ic2VydmVy 83012 -SENJ 83013 -c2lsdmVy 83014 -IGRldmFzdGF0aW9u 83015 -LXNlbWlib2xk 83016 -IEV4cGxhaW4= 83017 -IEJsb2NrbHk= 83018 -Llhy 83019 -ZXN0dXJlUmVjb2duaXplcg== 83020 -Q2FuY2VsQnV0dG9u 83021 -IExvY2tl 83022 -VHJpYWw= 83023 -X1BMQUNF 83024 -anVhbGFu 83025 -IFJ1Ymlu 83026 -U3RyaXBl 83027 -IG1ldGFEYXRh 83028 -Y29uZmlkZW5jZQ== 83029 -X2JhdHRlcnk= 83030 -IGlzbA== 83031 -IGJvYQ== 83032 -LnRhcmdldHM= 83033 -bGlqa2U= 83034 -IGFkb2xlc2NlbnRl 83035 -YmV3 83036 -LEZhbHNl 83037 -IHlPZmZzZXQ= 83038 -UHJldmlvdXNseQ== 83039 -PXBhdGg= 83040 -X0FB 83041 -iOadgw== 83042 -IGJha2VrYQ== 83043 -IGxlZQ== 83044 -IEJsb2NraW5n 83045 -L3RpdGxl 83046 -IOW8gA== 83047 -IFN0ZXZlbnNvbg== 83048 -KW9iamVjdA== 83049 -aXN0cm9z 83050 -LmdldFNlcnZlcg== 83051 -IHBsYW50YXRpb24= 83052 -X0JveA== 83053 -ICc7Jw== 83054 -dGljYQ== 83055 -KSldOwo= 83056 -IGRpc3Bhcml0aWVz 83057 -xrDhu5s= 83058 -aWNyb2JpYWw= 83059 -IHNwYXM= 83060 -L0RE 83061 -KHBvaW50ZXI= 83062 -IG1pZHBvaW50 83063 -LmdldENsYXNzTmFtZQ== 83064 -IFRvdGFsbHk= 83065 -IGNvbmdlbg== 83066 -IHTDqnRl 83067 -LnhsaW0= 83068 -Q09NUExFVEU= 83069 -KGZp 83070 -b3dhcmQ= 83071 -0LzRjw== 83072 -LmFzYw== 83073 -IHBhZ2luYXRl 83074 -IGx1cmtpbmc= 83075 -LnNpZ251cA== 83076 -U1RZTEU= 83077 -IHdvcnNo 83078 -aHY= 83079 -IGRlZmVuc2l2ZWx5 83080 -IEx1dGhlcmFu 83081 -LmZ1bg== 83082 -INC40L3RhNC+0YDQvA== 83083 -cHNj 83084 -IGFkbW9u 83085 -IEVzdGltYXRlZA== 83086 -IE15U3FsQ29ubmVjdGlvbg== 83087 -LnN0YXR1c1N0cmlw 83088 -IGFudGlnZW4= 83089 -IGhlcnJhbWllbnQ= 83090 -IENvbnN1bWVycw== 83091 -IFlU 83092 -Lm1hc2tzVG9Cb3VuZHM= 83093 -Lnh0aWNrcw== 83094 -OnJlcXVlc3Q= 83095 -IE1vbw== 83096 -LWF1 83097 -IHRvUmV0dXJu 83098 -IFNhcHBoaXJl 83099 -Y294 83100 -ZXhhbXBsZUlucHV0RW1haWw= 83101 -IGNvcmF6 83102 -KHBpZWNl 83103 -IHJlY29uc3RydWN0ZWQ= 83104 -X3NpZ251cA== 83105 -J10pPw== 83106 -QmlsbGluZw== 83107 -IENyb3dsZXk= 83108 -c3Rvcm1z 83109 -Zm9yY2Vy 83110 -IHN1cHJlbWFjaXN0 83111 -X3doZWVs 83112 -CXBj 83113 -LmdldERvY3VtZW50 83114 -LnVuc3F1ZWV6ZQ== 83115 -LmdyYWRl 83116 -ZWxsdW5n 83117 -LnNob3BwaW5n 83118 -Y3VzdG9tZXJJZA== 83119 -IG1lZGlkYXM= 83120 -IE1vbWVudHM= 83121 -ZW51b3Vz 83122 -SUZJQ0FURQ== 83123 -IyMjIyMjIwo= 83124 -5paH56ug 83125 -4buNYw== 83126 -b3Jtc2c= 83127 -YWxvbQ== 83128 -LXRyYWRl 83129 -CWJ0 83130 -L3N0dWRlbnQ= 83131 -YnJpZw== 83132 -YW5uZXNz 83133 -KHJh 83134 -IHJpY2VyY2E= 83135 -U3BlYWtlcg== 83136 -csOz 83137 -Z3Rlc3Q= 83138 -R2x5cGg= 83139 -w7xnZW4= 83140 -QEpzb24= 83141 -KHN1bW1hcnk= 83142 -S29t 83143 -YmV0aA== 83144 -L2VuZ2luZQ== 83145 -Q2xpbWF0ZQ== 83146 -c3VibWl0QnV0dG9u 83147 -ZXZl 83148 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cg== 83149 -cGVkaWE= 83150 -IHVzZXJuYW1lcw== 83151 -IEpN 83152 -IG1zZQ== 83153 -aW5zcGVjdA== 83154 -IFNuYXBkcmFnb24= 83155 -IGRlZmVuc2VtYW4= 83156 -IFVJVGFibGVWaWV3RGVsZWdhdGU= 83157 -aW5kaG92ZW4= 83158 -IEJveWxl 83159 -IEFsdGE= 83160 -YXJkdQ== 83161 -IHdyZXN0bGVy 83162 -IFN0cmFpdA== 83163 -IGVncmVn 83164 -X2Jhc2VsaW5l 83165 -RW52aXJvbm1lbnRhbA== 83166 -IGludml0 83167 -IEJUUw== 83168 -IElTSUw= 83169 -IGNvb3A= 83170 -aG9yZXM= 83171 -I0A= 83172 -IGNvbXBlbA== 83173 -KHNraXA= 83174 -6Ziz 83175 -X0RFUFJFQ0FURUQ= 83176 -aXBoZXJz 83177 -ZG91YmxlVmFsdWU= 83178 -IEFSUg== 83179 -LlNjb3Jl 83180 -IGNocm9tb3NvbWVz 83181 -Y2xhdXNl 83182 -IEx1aWdp 83183 -IHN1bnNjcmVlbg== 83184 -IGN5dG9r 83185 -LnRvSlNPTlN0cmluZw== 83186 -IHByb3ByZQ== 83187 -cG9vbnM= 83188 -bWl0dGVycw== 83189 -IGtpdHRlbnM= 83190 -IGNhdGhvbGlj 83191 -Lmx0 83192 -wqw= 83193 -X3F1aWNr 83194 -IHZyYWk= 83195 -IElSZWFkT25seQ== 83196 -IEhpZ2dpbnM= 83197 -IHNob3ZlZA== 83198 -IGxpYWlzb24= 83199 -X293bg== 83200 -IG1vc3F1aXRvZXM= 83201 -X25n 83202 -LlNldEtleU5hbWU= 83203 -X1JlbmRlcmVy 83204 -X09zYw== 83205 -LnVucmVnaXN0ZXI= 83206 -TWVzc2FnZVR5cGU= 83207 -LWZvdW5kZWQ= 83208 -IHNvdXRoZWFzdGVybg== 83209 -IGhhc2h0YWJsZQ== 83210 -LmluZGVudA== 83211 -IGpveWZ1bA== 83212 -X3NleA== 83213 -c2Fk 83214 -LmRlYmlhbg== 83215 -X2dhcw== 83216 -IHBlcmlzaA== 83217 -IGhldGU= 83218 -X3NpbmdsZXRvbg== 83219 -KGdyYWQ= 83220 -IGt0w7NyYQ== 83221 -IGR3aW5k 83222 -aXR0YWw= 83223 -U2VlaW5n 83224 -IFJvb2tpZQ== 83225 -CUxhYmVs 83226 -c2hhbg== 83227 -PDw8PDw8PDw= 83228 -IHLDqA== 83229 -aWVzZWw= 83230 -YXJyZXJh 83231 -Y2hyaXN0 83232 -IGN1cnZhdHVyZQ== 83233 -IGVwaGVt 83234 -Rm9ybWF0dGluZw== 83235 -LmRpY3Rpb25hcnk= 83236 -LlNldHRlcg== 83237 -IEhpc3RvZ3JhbQ== 83238 -IFN0dXR0Z2FydA== 83239 -IHBhY2luZw== 83240 -dXRhdGlvbnM= 83241 -IE5TSw== 83242 -IFBhbWVsYQ== 83243 -IEJhaWw= 83244 -IHBvbGFyaXphdGlvbg== 83245 -IEfDtg== 83246 -IEVsYWluZQ== 83247 -IGtpY2tvZmY= 83248 -IGNoYXBlbA== 83249 -PXBvc3Q= 83250 -IG1pZHdheQ== 83251 -ZXdpcw== 83252 -X01S 83253 -aWVlZQ== 83254 -LXRlc3Rpbmc= 83255 -bWV6 83256 -Pi0t 83257 -IGRvY3RyaW5lcw== 83258 -IG1pbGlldQ== 83259 -IFJBRElP 83260 -dGFrZW4= 83261 -UmVzcG9ucw== 83262 -IGhhbmRzZXQ= 83263 -IGNvbnRybw== 83264 -IEFwcGxpZXM= 83265 -6Zif 83266 -LkJpbmRpbmdTb3VyY2U= 83267 -INis 83268 -IGh1bWlsaQ== 83269 -IE1lbGFuaWE= 83270 -T3ZlcmxhcA== 83271 -KFBhcmNlbA== 83272 -IHdhcmVob3VzZXM= 83273 -LkdldEJ5SWQ= 83274 -IGZyYW5rZnVydA== 83275 -IFdpdHQ= 83276 -LnByb2o= 83277 -IFNhc2hh 83278 -IFJldmVy 83279 -IGFydGljdWxhdGVk 83280 -YW5jaGVz 83281 -IFNlbWluYXI= 83282 -IERhZ2dlcg== 83283 -IEFnaWxl 83284 -T1dM 83285 -IEJz 83286 -b2tseW4= 83287 -RXRh 83288 -IGFnb3N0bw== 83289 -7ZWY7Jes 83290 -IG9wdGFyZw== 83291 -CW9uQ2hhbmdl 83292 -IFJPQUQ= 83293 -R0JL 83294 -IGVudGZlcg== 83295 -LkF1dG9Db21wbGV0ZQ== 83296 -IGhlbGZlbg== 83297 -Q2hlYXA= 83298 -IGFwcHJlbnRpY2U= 83299 -aW90aWNz 83300 -5oqA 83301 -T2ZZZWFy 83302 -aW5kZXJlZA== 83303 -Lk1TRw== 83304 -IE1hcsOtYQ== 83305 -KGlucGxhY2U= 83306 -IGZpbmRl 83307 -KERF 83308 -LlNlcmlhbGl6ZXI= 83309 -JHRpbWU= 83310 -dW5uYWJsZQ== 83311 -TWFpblRocmVhZA== 83312 -ZGVwbG95bWVudA== 83313 -IG1wZnI= 83314 -cmljaFRleHRQYW5lbA== 83315 -KTsKCgoKCg== 83316 -IGRhbnljaA== 83317 -X0JFRk9SRQ== 83318 -X2FyeQ== 83319 -IEJhdW0= 83320 -IHR1cmJ1bGVudA== 83321 -IE11bHRpbWVkaWE= 83322 -IHBoeXNpY2lzdA== 83323 -5Zy6 83324 -QW5pbWF0ZQ== 83325 -PUY= 83326 -UGFnbw== 83327 -L3R3aXR0ZXI= 83328 -b3R0aWU= 83329 -dWN1cnNhbA== 83330 -X3BhZ2luYXRpb24= 83331 -LmFyY2hpdmU= 83332 -LWRvY3VtZW50 83333 -aW5pbmU= 83334 -U2VsbGVy 83335 -YWRyZXNz 83336 -6ZO+5o6l 83337 -0LDRgtC10LPQvtGA 83338 -X2ZybQ== 83339 -bm9EQg== 83340 -aWdhdGVk 83341 -IE9zYW1h 83342 -cGV0dG8= 83343 -Pnk= 83344 -LVVu 83345 -IGNvcHBpYQ== 83346 -QWxtb3N0RXF1YWw= 83347 -LmxleA== 83348 -IGxldmVsZWQ= 83349 -IFNDSVA= 83350 -X0hPT0s= 83351 -SUxvZ2dlcg== 83352 -bmVhdQ== 83353 -77ye 83354 -24zZhg== 83355 -aWtoYWls 83356 -IHVwbG9hZGVy 83357 -IENhcm9seW4= 83358 -LmFkZFZhbHVl 83359 -dGhpbmtpbmc= 83360 -cHJpbnRTdGF0cw== 83361 -IGNhbWJpb3M= 83362 -cG9p 83363 -IEJFRA== 83364 -IHhibWM= 83365 -Lu+/vQ== 83366 -IHNhcmNhc3Q= 83367 -IE5FQw== 83368 -JGJvZHk= 83369 -QWxsV2luZG93cw== 83370 -IHlvdW5nc3Rlcg== 83371 -IHVuZWFzeQ== 83372 -KEFU 83373 -IG5vc3RhbGdpYw== 83374 -UFJJQ0U= 83375 -IFNlaXRlbg== 83376 -IG1ha2E= 83377 -IGxpbXA= 83378 -IGNvbnRyYXN0cw== 83379 -Q29mZmVl 83380 -CWdlbg== 83381 -IHBlcm1z 83382 -IE5lZWRsZXNz 83383 -b3V2ZQ== 83384 -YXJjaGluZw== 83385 -X3BlbmFsdHk= 83386 -cm93YWQ= 83387 -b25nYW4= 83388 -X2R1cg== 83389 -IGlmbmRlZg== 83390 -aWF1eA== 83391 -IGNhcGFjaWRhZA== 83392 -IE5vcnRl 83393 -IC0qLQ0K 83394 -aWZlcw== 83395 -IE1hbnNpb24= 83396 -I1JlZ2lvbg== 83397 -Q2FuY2VsbGF0aW9u 83398 -IG5lYXJpbmc= 83399 -IGxhbmd1 83400 -ZXJlcXVpc2l0ZXM= 83401 -X2V4cGVyaW1lbnQ= 83402 -b25kaGVpbQ== 83403 -XSwm 83404 -IENvb2xpbmc= 83405 -IHNhZmFyaQ== 83406 -IHBpb25lZXJz 83407 -IGZhcm1ob3VzZQ== 83408 -IGRpc3RhbmNpYQ== 83409 -IGRlc2VydGVk 83410 -IE5hcnJvdw== 83411 -LnNn 83412 -IGVudHJhcg== 83413 -LnJh 83414 -IHJlZnVyYmlzaGVk 83415 -IGludGVyY29ubmVjdGVk 83416 -IHN1cnZpdmVz 83417 -IHF1YWxpZmllcnM= 83418 -X0NIQVJT 83419 -LWFqYXg= 83420 -IFJvcnk= 83421 -IGtvbGVq 83422 -L0dM 83423 -X2xlZ2Fs 83424 -IFRZUEVT 83425 -IFZvaWNlcw== 83426 -IEZlcmQ= 83427 -dWplbXk= 83428 -IHNjb3JlYm9hcmQ= 83429 -IEJPVA== 83430 -eERE 83431 -IEl2YW5rYQ== 83432 -IGhzdg== 83433 -bm9kaXNjYXJk 83434 -IFRIRVNF 83435 -bW9qb20= 83436 -IHRpY2tpbmc= 83437 -cGVx 83438 -IOa3u+WKoA== 83439 -IE5pY29s 83440 -CWFuZ2xl 83441 -X2FsbG9jYXRlZA== 83442 -IHN0cnV0 83443 -eERC 83444 -RXZhbHVhdGU= 83445 -IFZBUklBTlQ= 83446 -IHJlZmVyZW5jZWRDb2x1bW5OYW1l 83447 -bG9o 83448 -IFJlcXVlc3RPcHRpb25z 83449 -IGNvY28= 83450 -IGJsZWFjaA== 83451 -X29yZ2FuaXphdGlvbg== 83452 -IENITw== 83453 -SFRUUFM= 83454 -X2JhcnJpZXI= 83455 -LnZpc2l0TWV0aG9kSW5zbg== 83456 -IHZpdGU= 83457 -IC0k 83458 -W2NlbGw= 83459 -IGNlc3NhdGlvbg== 83460 -CgoKCgoKCgoKCgo= 83461 -INGB0LDQuQ== 83462 -RXZhbHVhdGlvbg== 83463 -IENJTQ== 83464 -cXVhbGl0aWVz 83465 -WG1sQXR0cmlidXRl 83466 -IEVtb2pp 83467 -ICIoJw== 83468 -IFRVUk4= 83469 -eHNk 83470 -IEdJUw== 83471 -IGNyZWF0ZVNlbGVjdG9y 83472 -cmlwcGxl 83473 -IHVubmVjZXNzYXJpbHk= 83474 -IG5ld1Bvcw== 83475 -IHN5bWJvbGlzbQ== 83476 -b2J1dHRvbg== 83477 -IHNhbW8= 83478 -ICgqKCg= 83479 -LnJld2FyZA== 83480 -S0VSTkVM 83481 -KGpTY3JvbGxQYW5l 83482 -IGJ5c3RhbmQ= 83483 -X2ljYWxs 83484 -IGR1bmdlb25z 83485 -IGNvbnN0ZWxsYXRpb24= 83486 -IGVtYnJhY2Vz 83487 -IEluZmFudA== 83488 -QXVzdGlu 83489 -LmFic3RyYWN0 83490 -IGNvbXBhZ24= 83491 -IENvbmRpdGlvbmluZw== 83492 -TWFpcw== 83493 -VmVyaWZpZXI= 83494 -IFB5cmFtaWQ= 83495 -IG1MaXN0ZW5lcg== 83496 -X2J1aWxkaW5n 83497 -LlJlZGlz 83498 -IFRvb3Ro 83499 -TE9HR0VS 83500 -LkFzeW5jVGFzaw== 83501 -X3ByaW5jaXBhbA== 83502 -ZXhhbXBsZU1vZGFsTGFiZWw= 83503 -CUxvY2Fs 83504 -TWFya2Vycw== 83505 -IGRvbHBoaW5z 83506 -LlRleHRFZGl0 83507 -J2Fs 83508 -IG92ZXJzdA== 83509 -LWRyaXZl 83510 -IGluc29tbmlh 83511 -IGFkYg== 83512 -X3F1ZXVlcw== 83513 -RWI= 83514 -IERhbW4= 83515 -aXN0cmluZ3N0cmVhbQ== 83516 -CUR1ZWw= 83517 -aWJibGU= 83518 -IGltcmVhZA== 83519 -LmZpbmlzaGVk 83520 -IG1pc3JlcHJlc2VudGVk 83521 -xYRzdA== 83522 -aW9uYWxlcw== 83523 -Ik5vdw== 83524 -LlNlbGVjdFNpbmdsZU5vZGU= 83525 -IHdlYWtlbmluZw== 83526 -X2luc3RydWN0aW9ucw== 83527 -LW9z 83528 -IHN0YXJ0UG9pbnQ= 83529 -IE1pbWU= 83530 -IEhlbGQ= 83531 -fHwo 83532 -dW1taW5ncw== 83533 -b2tpbm8= 83534 -IHJlZmw= 83535 -cmlkb3I= 83536 -SW50ZWdyYXRlZA== 83537 -RU9iamVjdA== 83538 -cGVhdHM= 83539 -Q2lyY3VsYXI= 83540 -IFNvZGl1bQ== 83541 -IHBvZHLDrWE= 83542 -bWVkaWNpbmU= 83543 -IHBhcmFub2lh 83544 -L2JhY2tncm91bmQ= 83545 -KGJvcmRlcg== 83546 -X3Nsb3c= 83547 -IHByZXNlbnRWaWV3Q29udHJvbGxlcg== 83548 -IGNvbnRpbmdlbmN5 83549 -IFBhc2FkZW5h 83550 -bG9vcHM= 83551 -IE9j 83552 -YXBwbGljYXRpb25z 83553 -IG1wZw== 83554 -IEFR 83555 -LldpbkNvbnRyb2xz 83556 -bGVkb24= 83557 -IFJlcQ== 83558 -IEFjcmVz 83559 -aWJpcg== 83560 -IGdldFdpbmRvdw== 83561 -IFlhaA== 83562 -IG5lZWR5 83563 -4pa6 83564 -IFRPTQ== 83565 -KFsuLi4= 83566 -IGZx 83567 -IENhbWRlbg== 83568 -b3JkaW5hdGVk 83569 -CWNoaWxkcmVu 83570 -dmVnZXQ= 83571 -CWRpcmVjdGlvbg== 83572 -PEZpZWxk 83573 -X2NvcnJlY3Rpb24= 83574 -KEVORA== 83575 -SEVFVA== 83576 -RmFsc3k= 83577 -LmR5bGli 83578 -X1JFUE8= 83579 -IGJyaWxsaWFuY2U= 83580 -b2dyw6Fm 83581 -bG9k 83582 -IHBvd2RlcmVk 83583 -KEFydA== 83584 -IE1JTEw= 83585 -0LXQtNCw0Lo= 83586 -X3NpbXVsYXRpb24= 83587 -IHNtYXNoaW5n 83588 -IHVybFN0cmluZw== 83589 -IGRyZWFkZWQ= 83590 -cmllZw== 83591 -L25z 83592 -IEludGVycHJldGVy 83593 -Om1heA== 83594 -ZGVyaXY= 83595 -IFBldHQ= 83596 -IG1vZMOobGU= 83597 -IGFtcGxpZmllZA== 83598 -IFNpZ25hbHM= 83599 -Lm5hdkN0cmw= 83600 -5ZY= 83601 -IHNlcGFyYXRvcnM= 83602 -IFNISUZU 83603 -IGZpZGVsaXR5 83604 -LnNvbg== 83605 -KGNh 83606 -IFBMVUdJTg== 83607 -IGxpZ2h0ZW4= 83608 -UEJT 83609 -ZmxvYXRpbmc= 83610 -KGxvYWRlcg== 83611 -IHBlZWxlZA== 83612 -aGlj 83613 -IHRhcGVk 83614 -IG5vdmVtYnJl 83615 -IHN0dWZmaW5n 83616 -IEZpcmVhcm1z 83617 -LkRyYXdhYmxl 83618 -IGNvcnRpY2Fs 83619 -IEdVSUNvbnRlbnQ= 83620 -IFZlcm9uaWNh 83621 -X3JzYQ== 83622 -IGNvbW1lbW9yYXRl 83623 -LlNZU1RFTQ== 83624 -IGRhbXM= 83625 -LmlzVHJ1ZQ== 83626 -IFByZWduYW5jeQ== 83627 -7Iug 83628 -IGF1ZGl0b3J5 83629 -KENlbGw= 83630 -IGludmFkaW5n 83631 -IGZvckVhY2g= 83632 -CURyYXc= 83633 -TWFyY3Vz 83634 -UHJvY2Vzc2Vk 83635 -IHNwcmF5aW5n 83636 -IE91dGxpbmVJbnB1dEJvcmRlcg== 83637 -ZXNzZXJhY3Q= 83638 -IOacgA== 83639 -UGc= 83640 -LXF1YXJ0ZXJz 83641 -IHNrbA== 83642 -L3Byb3ZpZGVycw== 83643 -dG9IYXZlQmVlbkNhbGxlZFRpbWVz 83644 -IGNvc21vcw== 83645 -IGZpbmFsaXN0cw== 83646 -IHNsZWVwZXI= 83647 -IE1hdGVyaWFsQXBw 83648 -ZGFj 83649 -IGJ1c2luZXNzbWVu 83650 -xJ9lcg== 83651 -Qmlhcw== 83652 -ZGF0YWw= 83653 -VXBFZGl0 83654 -IFRpcg== 83655 -SVNUSUM= 83656 -IEhlcmE= 83657 -X2ludGVyc2VjdGlvbg== 83658 -IExhbWE= 83659 -CWFwcGVuZA== 83660 -IHBvbGx1dGFudHM= 83661 -IFNpa2g= 83662 -IGNvbGxhYm9yYXRpb25z 83663 -bnV0cml0aW9u 83664 -IGhhbW0= 83665 -IERpbGxvbg== 83666 -X0RPVA== 83667 -IGZpcnN0aGFuZA== 83668 -U09BUA== 83669 -PXo= 83670 -LnByaXY= 83671 -TWlzbWF0Y2g= 83672 -LnNlbmRSZWRpcmVjdA== 83673 -LmxpbmtMYWJlbA== 83674 -IHdyZWFr 83675 -TWFydmVs 83676 -L3Ns 83677 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 83678 -IG1vdmFibGU= 83679 -0YPQuQ== 83680 -IERyaW5raW5n 83681 -YWNlYQ== 83682 -IHRyb3ZhcmU= 83683 -LkNTUw== 83684 -IGtlcm4= 83685 -dmZz 83686 -5pWw5a2X 83687 -IHN0ZXNzbw== 83688 -IEZPUkNF 83689 -IGxpZWY= 83690 -IGFjaGlldmVz 83691 -IEVsaWphaA== 83692 -R2V0UHJvcGVydHk= 83693 -LypA 83694 -IEh1bWFuaXR5 83695 -KFRoZQ== 83696 -d2FybQ== 83697 -PiIp 83698 -IGNvbXB1dGF0aW9ucw== 83699 -LnRpbnRDb2xvcg== 83700 -IHVzbGVlcA== 83701 -IEdQTHY= 83702 -bmRhdGE= 83703 -L2NsaQ== 83704 -TW9o 83705 -PiINCg== 83706 -LmJyaWRnZQ== 83707 -IGVuY3ljbG9wZWRpYQ== 83708 -IEJJTg== 83709 -IFN1cHBvc2U= 83710 -INio2Kc= 83711 -cmlldmVk 83712 -cGFnZW4= 83713 -aXJzZQ== 83714 -UGFjaWZpYw== 83715 -LmZ1bGxOYW1l 83716 -IGFsbGVnZQ== 83717 -aWxsdXN0cg== 83718 -IOqysA== 83719 -IGRldGVycmVudA== 83720 -IE5hcGxlcw== 83721 -aW5jbHVkZWQ= 83722 -UmF0ZXM= 83723 -IGhhc05leHQ= 83724 -IEplcmVtaWFo 83725 -IEZlcm5hbmRleg== 83726 -IGdldE9yZGVy 83727 -LlN1YnNjcmliZQ== 83728 -UG9zcw== 83729 -OikK 83730 -IFdvcmtzaGVldA== 83731 -YmxlbmQ= 83732 -IHdpdHR5 83733 -IGNvdW50ZXJmZWl0 83734 -X2R5 83735 -L1J1bnRpbWU= 83736 -IHNvZG9t 83737 -L2Rv 83738 -IDx8 83739 -IFJlY3J1 83740 -5aOw5piO 83741 -IG1vZGVsb3M= 83742 -IGJpdHJhdGU= 83743 -LmNybQ== 83744 -bHVz 83745 -IGZpbGVUeXBl 83746 -5bCR 83747 -IG1hcnJvdw== 83748 -IFZlbmV6dWVsYW4= 83749 -IHNjYXY= 83750 -IFNUT0NL 83751 -IEltcG9zc2libGU= 83752 -bmF2aWdhdGlvbkJhcg== 83753 -IHNpZ2h0aW5ncw== 83754 -IGNlbGxGb3JSb3dBdA== 83755 -IHJlY3Rz 83756 -IGFpcmw= 83757 -IExlc3Rlcg== 83758 -IG5vZHM= 83759 -QHJlZ2lzdGVy 83760 -eENE 83761 -cG5hbWU= 83762 -IHBvdHRlcnk= 83763 -IHp3YXI= 83764 -IFN1bmRlcmxhbmQ= 83765 -4oCmYnV0 83766 -L2NvbnRyb2w= 83767 -IGNhbGN1bHVz 83768 -KGlzb2xhdGU= 83769 -cGxhY2Vob2xkZXJz 83770 -Kilf 83771 -IH19DQo= 83772 -IEtvaGFuYQ== 83773 -Y29kaWxl 83774 -b3Rlcmlj 83775 -IHByZXBhaWQ= 83776 -IGdyYW5kbWE= 83777 -IHN1bHBo 83778 -IEdhaW5lcw== 83779 -XE1vZHVsZQ== 83780 -IGNvdW5zZWxsaW5n 83781 -LWdlbmVyaWM= 83782 -IFR1ZXM= 83783 -LkdyYWRpZW50 83784 -IFRodXJz 83785 -IGVudHJh 83786 -IGFkdmFuY2VtZW50cw== 83787 -U1dFUA== 83788 -X01BUktFUg== 83789 -IGtsdWI= 83790 -IG3DqWc= 83791 -ZmZmZmZmZg== 83792 -Il0pewo= 83793 -L2NvbXBpbGVy 83794 -YWRpZW5z 83795 -U3RyaW5nVmFsdWU= 83796 -IFNjdWxwdA== 83797 -cGFuZWxz 83798 -5b2i 83799 -5Lqn5ZOB 83800 -YXLDrWE= 83801 -IGRlcmFpbA== 83802 -IExvY2g= 83803 -IHBlcHA= 83804 -bXB6 83805 -IOKe 83806 -S1Y= 83807 -IERpZXRhcnk= 83808 -QVJSSUVS 83809 -IHBvbw== 83810 -IFJBTkRPTQ== 83811 -6LM= 83812 -IEhvbWV3b3Jr 83813 -LlZhbGlkYXRpb25FcnJvcg== 83814 -IE1hcnhpc20= 83815 -0YPRgtGM 83816 -IGNvbWVudGFyaW8= 83817 -X0JPVEg= 83818 -IHBybQ== 83819 -Y2FzdEhpdA== 83820 -aXBsaW5h 83821 -IFZvdGVycw== 83822 -LmFzc2lnbm1lbnQ= 83823 -bmV0dA== 83824 -U0FNUExF 83825 -amlz 83826 -InRpdGxl 83827 -LnZhbGlkYXRvcnM= 83828 -ICI/Ig== 83829 -dW5pZGFk 83830 -X2ZpZ3VyZQ== 83831 -IGFjY3J1 83832 -IFJlbWFyaw== 83833 -Rm91bmRlcg== 83834 -LmluaXRpYWxpemVBcHA= 83835 -IFByZXNlbnRz 83836 -IE1VTFRJ 83837 -dmVzdGVy 83838 -LnZpc2l0SW5zbg== 83839 -IGdldFBhdGg= 83840 -X2RpZmZlcmVudA== 83841 -IGxvb3Nlbg== 83842 -IGFycm9nYW5jZQ== 83843 -IGp1bmk= 83844 -IFphaGw= 83845 -IEdDQk8= 83846 -IG1vZGVyYXRvcnM= 83847 -TGluZUNvbG9y 83848 -IE5vZGVUeXBl 83849 -X2JlbG93 83850 -b3JndA== 83851 -IEhhcmxlbQ== 83852 -IE9yd2VsbA== 83853 -X1VOSVg= 83854 -LnJlc3RhcnQ= 83855 -aXRoZQ== 83856 -IGdlbmll 83857 -IGNsYWQ= 83858 -Jzp7Jw== 83859 -IHNob3djYXNlZA== 83860 -IGxhcnZhZQ== 83861 -TWljaGVsbGU= 83862 -IExI 83863 -LmdldExvZw== 83864 -Q29uc3RydWN0ZWQ= 83865 -IGh2YQ== 83866 -X3N1YnM= 83867 -IGRhYg== 83868 -LmRvY3VtZW50YXRpb24= 83869 -IG5pZw== 83870 -IE1hbmRhcmlu 83871 -4oCUYXJl 83872 -LXBpYw== 83873 -X2Nvcm5lcnM= 83874 -LkJvdA== 83875 -XVso 83876 -X18nOg0K 83877 -LkVkaXRvckJ1dHRvbg== 83878 -LXN5bnRheA== 83879 -U2FuZGVycw== 83880 -IFRhbmtz 83881 -ZGVzaXJlZA== 83882 -c3RhbnRpYXRlVmlld0NvbnRyb2xsZXI= 83883 -R2Vhcg== 83884 -IHVzZXJNb2RlbA== 83885 -CWNvbnRyb2w= 83886 -RGF0YUJhc2U= 83887 -IERlYmF0ZQ== 83888 -aW5lc2lz 83889 -IHhl 83890 -Lm1hZ25pdHVkZQ== 83891 -IHlhbg== 83892 -IEFwaUV4Y2VwdGlvbg== 83893 -KHdoaWNo 83894 -YXRoZXJpbmc= 83895 -Q29uc2lkZXJpbmc= 83896 -IEFMUEhB 83897 -568= 83898 -IFJhbmtpbmdz 83899 -LmxpZmU= 83900 -6rCS 83901 -T0ZGU0VU 83902 -LnRlbGVncmFt 83903 -IGZhdmljb24= 83904 -X3NzaA== 83905 -IEVER0U= 83906 -UmVmcw== 83907 -YW5kYW4= 83908 -IGFkb2xlc2NlbmNl 83909 -IFNoYW5r 83910 -IFN3YW1w 83911 -X3BlcmM= 83912 -IGNvbnRyYXJpbw== 83913 -Lm55 83914 -LiIpLA== 83915 -IHVudGVu 83916 -X0VOU1VSRQ== 83917 -L29yZGVycw== 83918 -KGNm 83919 -IHVudHJlYXRlZA== 83920 -YXplbg== 83921 -KElucHV0U3RyZWFt 83922 -IGFwcHJvdmFscw== 83923 -IGdlcm1hbnk= 83924 -IGF2ZXJl 83925 -VHJpcGxl 83926 -LWJhcnM= 83927 -IHNldFBhZ2U= 83928 -SmFj 83929 -IEZpcmVz 83930 -IERBWVM= 83931 -56i/ 83932 -IHNjcmF0Y2hlZA== 83933 -IEJFTg== 83934 -LXdpZmU= 83935 -IGludGVsbGVjdHVhbHM= 83936 -IHBvdWNv 83937 -IHN0YWJpbGl6YXRpb24= 83938 -IHBlbG9z 83939 -IFNUT1JZ 83940 -PGZpZWxkc2V0 83941 -IE1haWRlbg== 83942 -LkNpcmNsZQ== 83943 -IHNtw6U= 83944 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLw== 83945 -L2VuZA== 83946 -6Iux 83947 -KG51bXB5 83948 -LnBhbmVsQ29udHJvbA== 83949 -Y2hyaWZ0 83950 -Y29udGluZW50YWw= 83951 -X3BlbA== 83952 -RFNM 83953 -PFwv 83954 -IE9QUw== 83955 -IE5vb24= 83956 -IHVuZGlzY2xvc2Vk 83957 -IFlpbg== 83958 -c3Bv 83959 -CWRlc2NyaWJl 83960 -dG9ncm91cA== 83961 -IGRpYXBlcnM= 83962 -IG1IYW5kbGVy 83963 -CUNsb3Nl 83964 -IHJlbmRpdGlvbg== 83965 -PXsoew== 83966 -RW50ZXJpbmc= 83967 -KERJUg== 83968 -X09MRA== 83969 -IFN0aW5n 83970 -IFBhd24= 83971 -dXNzZXM= 83972 -IGdldENvZGU= 83973 -SXRlbUxpc3Q= 83974 -IGluZGlz 83975 -ID4iLA== 83976 -IGNvbmZs 83977 -IGRvbWluYXRlcw== 83978 -dGhlc2l6ZWQ= 83979 -c3RlcmVk 83980 -IGNhYw== 83981 -IEdlbnVpbmU= 83982 -PFBhdGg= 83983 -IEhvZGc= 83984 -LWZseQ== 83985 -LmNpZA== 83986 -IG9iamVjdElk 83987 -KCMp 83988 -Lm1vdmVUb05leHQ= 83989 -RGlhbG9ndWU= 83990 -PHBjbA== 83991 -dGVhckRvd24= 83992 -Jyl9fQo= 83993 -5ri4 83994 -TGl2ZXI= 83995 -TWF0cml4WGQ= 83996 -IGNyYXBweQ== 83997 -X0RFQUQ= 83998 -LnBhcnRpYWw= 83999 -LkRyb3BEb3duU3R5bGU= 84000 -ZnVy 84001 -LkNvbGxhcHNlZA== 84002 -LXRvd24= 84003 -SUNJQUw= 84004 -RGlyZWNjaW9u 84005 -IHNldFJlc3VsdA== 84006 -L3Jlc3VsdA== 84007 -IFNoZWVw 84008 -eXNjYWxl 84009 -Y29udGk= 84010 -IHJlY29ub2M= 84011 -6b4= 84012 -W2Jsb2Nr 84013 -Y2xheno= 84014 -IGJlbmVmaXRpbmc= 84015 -QUFQ 84016 -LnJlcXVpcmVz 84017 -LkNvb2tpZQ== 84018 -IGNhcHRpdml0eQ== 84019 -LlNlY3Rpb24= 84020 -XSkpOw== 84021 -LWNhcmV0 84022 -KHZh 84023 -IHbDpGw= 84024 -IEhpZ2hsYW5kcw== 84025 -Tm90YQ== 84026 -IEZNTA== 84027 -d2ludGVy 84028 -IGFnZW5kYXM= 84029 -X18sX18= 84030 -ZGVtYW5k 84031 -IHR1dG9ycw== 84032 -X1NZTQ== 84033 -KENI 84034 -IHVuZXF1aXY= 84035 -LnRyYW5zaXRpb25z 84036 -IENhbG9yaWVz 84037 -IEVjb25vbWlzdA== 84038 -LlBpbg== 84039 -IGRlZmxlY3Q= 84040 -RXhwb3NlZA== 84041 -IGdlcA== 84042 -LkxheW91dENvbnRyb2xJdGVt 84043 -IHJhaw== 84044 -ZmliZXI= 84045 -IGFwb3B0 84046 -IEVudW1z 84047 -aXRldXI= 84048 -IG1vZGlmaWVz 84049 -IHJlbHVjdGFuY2U= 84050 -IHNwaWxscw== 84051 -QXNjZW5kaW5n 84052 -IHRlbXBlcmF0dXJh 84053 -LWludGVyZmFjZQ== 84054 -IGNvd29ya2Vycw== 84055 -IDpc 84056 -IFJvdW5kZWRSZWN0YW5nbGVCb3JkZXI= 84057 -PEtleVZhbHVlUGFpcg== 84058 -UGFyc2Vk 84059 -IHdpdGhkcmF3aW5n 84060 -KGhpc3Q= 84061 -IHRoZW9yaXN0cw== 84062 -LW5n 84063 -IGNoaWZm 84064 -66W4 84065 -UEFJUg== 84066 -IEJyZXdlcg== 84067 -S2E= 84068 -IEJvd2xpbmc= 84069 -X3Rs 84070 -J30pLg== 84071 -IHByb2Jpbmc= 84072 -QXJz 84073 -LnJlYWxt 84074 -IGVzdGF0ZXM= 84075 -dmFyeQ== 84076 -IEtlcw== 84077 -ICIsIiw= 84078 -fSwNCg0K 84079 -UGxhbm5pbmc= 84080 -IFJlY29u 84081 -IGNvbmNsdXM= 84082 -dmF1bHQ= 84083 -IGluY2VudGl2 84084 -IGJpbm5lbg== 84085 -IFBoaWxsaWVz 84086 -LkxvYWRlcg== 84087 -IEZhbGxlbg== 84088 -X1R3bw== 84089 -IEJpYXM= 84090 -Um9sZUlk 84091 -IFBhcmNlbGFibGU= 84092 -IERvZGQ= 84093 -ICQoIiMi 84094 -5Lq/5YWD 84095 -LW1lYW4= 84096 -KE91dHB1dA== 84097 -QVRUUklCVVRF 84098 -IHNlY3JldGl2ZQ== 84099 -IFBlcmlwaGVyYWw= 84100 -IEZpbGVk 84101 -IOW3 84102 -X21lZGlhbg== 84103 -LklD 84104 -IEFycmF5QnVmZmVy 84105 -KFRBQkxF 84106 -IF0KCgo= 84107 -IGFudGhvbG9neQ== 84108 -IG9ic2NlbmU= 84109 -b3BhdXNl 84110 -IEVTVg== 84111 -w6F2ZWlz 84112 -b3NlbWl0ZQ== 84113 -R3J1cG8= 84114 -IE1PQ0s= 84115 -IHVuYXZvaWRhYmxl 84116 -IGNvdmlk 84117 -aG93ZXI= 84118 -Lk5ldmVy 84119 -U2V0QWN0aXZl 84120 -e3RleHQ= 84121 -X3Byb2Jh 84122 -XENvbmZpZ3VyYXRpb24= 84123 -IEJyeWNl 84124 -IGNvZXJjZQ== 84125 -IFZhbmRlcmJpbHQ= 84126 -Z2VtZW50cw== 84127 -bGVnZw== 84128 -IHJlYnV0 84129 -IFZJTg== 84130 -5YiG6ZKf 84131 -IG9ic2Vzc2l2ZQ== 84132 -L2NtZA== 84133 -IGtvbW1lbnQ= 84134 -IExhdWdo 84135 -64uI 84136 -IHNlbHZlcw== 84137 -b3JyYQ== 84138 -LnJvb21z 84139 -IGNvbXBsZXhpdGllcw== 84140 -CW9wZXJhdG9y 84141 -QWx0ZXJuYXRl 84142 -IHNvcnRpZQ== 84143 -Z2V0TnVt 84144 -IHJlYWxpemFkbw== 84145 -RG9pbmc= 84146 -X0dyaWQ= 84147 -IHNldFN1cHBvcnRBY3Rpb25CYXI= 84148 -w6RobHQ= 84149 -5ZQ= 84150 -OnsNCg== 84151 -SW50ZXJlc3RlZA== 84152 -IGRpbWluaXNoaW5n 84153 -IExvb3Q= 84154 -QWRhcHRlckZhY3Rvcnk= 84155 -LXJ1bm5lcg== 84156 -c2F2aW5n 84157 -KHNlbQ== 84158 -ZmFk 84159 -RURVUkU= 84160 -X2RvY3VtZW50bw== 84161 -IENhbGVi 84162 -IGd1aXNl 84163 -IE1jR3U= 84164 -KHVuaXRz 84165 -IGJlemllcg== 84166 -IHBhdHQ= 84167 -IHBlbHZpYw== 84168 -IGNvbm9zYw== 84169 -YWN0aXZv 84170 -IE1hbG9uZQ== 84171 -LlRha2U= 84172 -KHNxcnQ= 84173 -c3Rhc2hvcA== 84174 -LWVuZGVk 84175 -IE1pZGk= 84176 -IEJhbmM= 84177 -IFBlcHNp 84178 -X01BWQ== 84179 -IHBsbA== 84180 -L2luZXQ= 84181 -LWVuaA== 84182 -IEl0YWw= 84183 -bW91cg== 84184 -IHJlbHVjdGFudGx5 84185 -LnJjUGFyYW1z 84186 -IHBhbHM= 84187 -LnBrZw== 84188 -IGZvcm1hcw== 84189 -bGllw59saWNo 84190 -LWJvb2tz 84191 -b21hbHk= 84192 -IHJlY29tbWFuZA== 84193 -UExJQ0lU 84194 -acSN 84195 -LmNnQ29sb3I= 84196 -KEJvYXJk 84197 -0LXQvdC40Lg= 84198 -IExFTg== 84199 -Xy1f 84200 -IFVubw== 84201 -IE5PVElGWQ== 84202 -aGFuYQ== 84203 -W3Nsb3Q= 84204 -XGFkbWlu 84205 -SW5JbnNwZWN0b3I= 84206 -KWNvbnN0 84207 -IGZsYXR0ZXJpbmc= 84208 -aWdyYW1z 84209 -Y2Fj 84210 -IGhlYXJ0ZmVsdA== 84211 -SW5kdXN0cmlhbA== 84212 -QWlycG9ydA== 84213 -WEk= 84214 -IHZhbGlkYXI= 84215 -cmVwcmVzZW50YXRpb24= 84216 -IFJlbnRhbHM= 84217 -IG9taXNzaW9u 84218 -IG15dGhpY2Fs 84219 -IEVudHJhbmNl 84220 -IHNlcmdlYW50 84221 -IHdyaXRlVG8= 84222 -IE5vcndpY2g= 84223 -IExpb25lbA== 84224 -LWJhbA== 84225 -IFp3ZQ== 84226 -X3JlbnQ= 84227 -IHJlbWFy 84228 -IEJhaGFtYXM= 84229 -IEJhbGU= 84230 -OiIiLA== 84231 -U3RhdGVNYW5hZ2Vy 84232 -IGLDqW7DqQ== 84233 -ICEqKio= 84234 -IGJsb2NrZXJz 84235 -LnNlbA== 84236 -KExFRA== 84237 -IGZzbQ== 84238 -IHdpcGluZw== 84239 -IHphbWFu 84240 -IFJlaQ== 84241 -YWd1YXk= 84242 -Li4n 84243 -IGxvdW5n 84244 -ZXRjb2Rl 84245 -IGxhbno= 84246 -Y2l0YXRpb24= 84247 -W2A= 84248 -LWVs 84249 -YXNib3VyZw== 84250 -IFNPTEQ= 84251 -IE9yY2hhcmQ= 84252 -Q0hhbmRsZQ== 84253 -IExvZnQ= 84254 -LmRpdmlkZQ== 84255 -LVdpdGg= 84256 -L2Rlc2lnbg== 84257 -LlNlcnZpY2VNb2RlbA== 84258 -TWlz 84259 -IHJhd0RhdGE= 84260 -IGludGVyYWN0cw== 84261 -IEVyb3Rpaw== 84262 -IG9uUG9zdEV4ZWN1dGU= 84263 -6Jk= 84264 -IHZleA== 84265 -IHN0cmluZ2lmeQ== 84266 -eW5lcw== 84267 -X0VtYWls 84268 -X09N 84269 -cXVpdGU= 84270 -X2VmZmVjdHM= 84271 -QURY 84272 -IGFkb3JuZWQ= 84273 -c3Nm 84274 -ZWRpdGFy 84275 -IE1hZGFtZQ== 84276 -IHJlZnV0ZQ== 84277 -IEx1Y2E= 84278 -IFdvbHZlcmluZQ== 84279 -c2V4bw== 84280 -QW5kcmU= 84281 -PFJvdXRl 84282 -IFNjZW5lcw== 84283 -IHJlb3JkZXI= 84284 -X214 84285 -Y3JlYXRlVGltZQ== 84286 -IHN5bnQ= 84287 -LG1vZGVs 84288 -aWNyb3Vz 84289 -IE1PVVNF 84290 -6rk= 84291 -Y29tcHJlc3Npb24= 84292 -IHByaW5jZXM= 84293 -IHNoYW1lZnVs 84294 -IHBhdQ== 84295 -IFRFRA== 84296 -KGNvZWZmcw== 84297 -4K+B 84298 -L3VtZA== 84299 -IGNhbnlvbg== 84300 -L3JlbmRlcg== 84301 -LnVzZWQ= 84302 -IEFncmVl 84303 -IEpld2Vs 84304 -L2NvbW1hbmQ= 84305 -QmFyY29kZQ== 84306 -KGRlYWQ= 84307 -d2Vic29ja2V0 84308 -dW11 84309 -R0xPU1M= 84310 -IGZvcnRu 84311 -IGJvYXN0ZWQ= 84312 -ICJcIj4= 84313 -aXN0dW5n 84314 -LW1hY2hpbmU= 84315 -IGluY2lkZW50YWw= 84316 -IG1N 84317 -LXJlYWRhYmxl 84318 -LmZ4 84319 -IFBPTElU 84320 -IHN5bWxpbms= 84321 -KHVzaW5n 84322 -eEVE 84323 -ICIiIi4= 84324 -LlN0ZG91dA== 84325 -IOiL 84326 -IGFsbWFjZW4= 84327 -CXRyaWdnZXI= 84328 -LXRpcA== 84329 -IENPTU1JVA== 84330 -LmluZ3JlZGllbnRz 84331 -IG1hbmlmZXN0cw== 84332 -IE9TUw== 84333 -IEhhdXQ= 84334 -L2xvYWRpbmc= 84335 -LlR5cGVTdHJpbmc= 84336 -KGNsZWFu 84337 -IExJQw== 84338 -IEJhcmJpZQ== 84339 -T09TRQ== 84340 -LuKApg== 84341 -IEludml0YXRpb24= 84342 -IHJlZGVlbWVk 84343 -KS4nPC8= 84344 -IGltZGI= 84345 -IGJlbGFuZw== 84346 -IHNjcmFwcGVk 84347 -LW5pbA== 84348 -IFByb3Vk 84349 -0LDRgdGC 84350 -LlNJWkU= 84351 -IHNldFZpc2libGU= 84352 -IHJhaW5pbmc= 84353 -IGxlbmdodA== 84354 -IGFuYWs= 84355 -X0NNUA== 84356 -IHBhbm9yYW1pYw== 84357 -IGdpbQ== 84358 -c2FpZA== 84359 -IHByb2dlbg== 84360 -IEdCUA== 84361 -4oCg 84362 -IGludmVzdGlnYXRlcw== 84363 -IHByw6hz 84364 -L25hdmlnYXRpb24= 84365 -Lm1vdGlvbg== 84366 -IExpZ2h0d2VpZ2h0 84367 -CQkgICAgICAgICAgICA= 84368 -IG9udG9sb2d5 84369 -IE5JSA== 84370 -KHNpbXA= 84371 -LnB1bGw= 84372 -IHByb3Bvc2l0aW9ucw== 84373 -QFdlYlNlcnZsZXQ= 84374 -IHJlZGVmaW5l 84375 -IEVORVJHWQ== 84376 -7KC4 84377 -T1JJWkFUSU9O 84378 -IFZlcmbDvGc= 84379 -fX1dLAo= 84380 -IHdlZ2Vu 84381 -4LmH 84382 -Jm9hY3V0ZQ== 84383 -LkJvYXJk 84384 -IGN1bHBh 84385 -IEdlbmV0aWNz 84386 -IH0+ 84387 -IGFkYW1hbnQ= 84388 -44GV44KM 84389 -CWF1ZGlv 84390 -6riA 84391 -IG51bWVyYWw= 84392 -IHJlc3RyYWluaW5n 84393 -LklOVEVSTkFM 84394 -IE1vbXM= 84395 -IElQQWRkcmVzcw== 84396 -aW1lbnRp 84397 -IGFscGhhYmV0aWNhbA== 84398 -IEpGSw== 84399 -IEF0dGVtcHRz 84400 -ZnJhZ2U= 84401 -IGRhcm0= 84402 -IGJhc2VtYW4= 84403 -PWxvZw== 84404 -LGVycm9y 84405 -IERJU0NMQUlNUw== 84406 -CXRleHR1cmU= 84407 -LWNvdmVyZWQ= 84408 -IFBsdW0= 84409 -IOWVhg== 84410 -IHDDqXJp 84411 -KHJldmlldw== 84412 -IEZvcmNlZA== 84413 -Rkg= 84414 -IOy0iA== 84415 -IGV5ZWJyb3c= 84416 -X1JFR1M= 84417 -IGNoZXN0cw== 84418 -IExhcmdlc3Q= 84419 -XV06Cg== 84420 -VVRPUg== 84421 -IGVucXVpcmllcw== 84422 -IGNva2U= 84423 -LWNhdGNoaW5n 84424 -IEdlb2dyYXBoeQ== 84425 -YXRlbA== 84426 -KHByb2Q= 84427 -b3JXaGVyZQ== 84428 -TmluZQ== 84429 -IFBpZWQ= 84430 -IGFkanVzdHM= 84431 -KHByb20= 84432 -X21lbnVz 84433 -X2V4YW0= 84434 -IE5vdGlmaWNhdGlvbkNlbnRlcg== 84435 -CWRz 84436 -TElL 84437 -X3R3aXR0ZXI= 84438 -Q1JD 84439 -IGV1eA== 84440 -IFN0YWJsZQ== 84441 -aXlvcg== 84442 -IGNhcmJvbmF0ZQ== 84443 -LnNhbA== 84444 -TWFwcGVk 84445 -aWV2aW5n 84446 -KXk= 84447 -eW5hbW9kYg== 84448 -LkNvbXBhcmVUYWc= 84449 -IHNldmVyZWQ= 84450 -J2VtYWls 84451 -IGZvcnNr 84452 -bGV4cG9ydA== 84453 -SU1JVEVS 84454 -IEFwZXg= 84455 -IGhtYWM= 84456 -IE9kZHM= 84457 -b3ZlcnJpZGVz 84458 -OiI7DQo= 84459 -IG9waW9pZHM= 84460 -IG1lc21lcg== 84461 -IEdBTA== 84462 -LWxpbmVz 84463 -IGFwcGx5TWlkZGxld2FyZQ== 84464 -IHNlcmlh 84465 -RVNJUw== 84466 -IG5pbGFp 84467 -IG1hbGxz 84468 -IFBhb2xv 84469 -IExlbnQ= 84470 -LmJ1aWxkZXJz 84471 -LyY= 84472 -IENsaXBz 84473 -IEp1cmFzc2lj 84474 -4pWd 84475 -LWNvbmQ= 84476 -44O844OI 84477 -fHd4 84478 -LmhvdXNl 84479 -IGhlcmF1cw== 84480 -IGhr 84481 -IENvY28= 84482 -IlwK 84483 -IGFjY3JlZGl0YXRpb24= 84484 -IFJhY2g= 84485 -ZXJ0ZXN0 84486 -c2hvcnRjb2Rl 84487 -IHZhbGlkYXRpb25z 84488 -VUxTRQ== 84489 -IGV4Y2VycHRz 84490 -U2Vla0Jhcg== 84491 -IGdldExvY2F0aW9u 84492 -IGZlbmNlZA== 84493 -KGdz 84494 -IGx5cw== 84495 -IGhhcm1z 84496 -IEhvbW8= 84497 -4oCcU2hl 84498 -IOKAuw== 84499 -PXNlc3Npb24= 84500 -X0NPTVBJTEU= 84501 -TWVhbnM= 84502 -IHBldGl0aW9uZXI= 84503 -SU1P 84504 -Il09Pg== 84505 -ZGJl 84506 -X2dwcw== 84507 -IG1q 84508 -X2V4cGlyZQ== 84509 -IERBTg== 84510 -IHh2 84511 -IGZ1bmNpb25lcw== 84512 -IHNoYWt5 84513 -U3VnYXI= 84514 -IGdldFJlc3VsdA== 84515 -PFRva2Vu 84516 -aHR0cENsaWVudA== 84517 -Lm9uUGF1c2U= 84518 -c3Rp 84519 -U25ha2U= 84520 -TWFwcGluZ3M= 84521 -IFJlYXBlcg== 84522 -IGZyZWk= 84523 -IENvc21vcw== 84524 -dWVycw== 84525 -IEhhag== 84526 -IEJsYXpl 84527 -b2ppcw== 84528 -Q3JMZg== 84529 -LnByb2M= 84530 -IG90cA== 84531 -IERyYXdz 84532 -CVJFRw== 84533 -KCcnJw== 84534 -IGdlbmVyYQ== 84535 -IEF0dGFjaGVk 84536 -UkVN 84537 -JTsiPg== 84538 -dXJuaXNoZWQ= 84539 -X3Jw 84540 -IHpvYWxz 84541 -IGFzc29ydGVk 84542 -aXRpemVk 84543 -IGNhbWlubw== 84544 -IGFiZHVjdGVk 84545 -LnRvQmU= 84546 -J10pOg== 84547 -IE1vb3I= 84548 -SW5jbHVkaW5n 84549 -IGdyYXppbmc= 84550 -c2V0U3RhdHVz 84551 -YWlyb2Jp 84552 -X0V4ZWN1dGU= 84553 -aWZpYW50 84554 -ZWxkbw== 84555 -YXV0b21hdGlj 84556 -KCQp 84557 -IGxlYXBz 84558 -b25lZERhdGVUaW1l 84559 -KGxheWVycw== 84560 -LXByb2R1Y2Vk 84561 -IFdvcmtib29r 84562 -IGVub3Jtb3VzbHk= 84563 -IGRlcHJlc3NpdmU= 84564 -IGFhYQ== 84565 -RW1iZWRkZWQ= 84566 -QlVN 84567 -IGVsbGVz 84568 -IGJvYXJkZWQ= 84569 -xZtteQ== 84570 -IG1hc2lo 84571 -X2dlbmVz 84572 -CVRleHR1cmU= 84573 -aXN0YXI= 84574 -IEF1Z3VzdGE= 84575 -IEFwcE1ldGhvZEJlYXQ= 84576 -IGtvZGU= 84577 -YWJleg== 84578 -X3BpZWNlcw== 84579 -Q3Vycg== 84580 -IGxpYmVyYWxpc20= 84581 -RGljaw== 84582 -QWxl 84583 -IHF1YWxl 84584 -fSc7Cg== 84585 -LmFuc3dlcnM= 84586 -IEpBTg== 84587 -IFBVUkU= 84588 -IGNhbm9l 84589 -IFNBTUU= 84590 -UXVhbGlmaWVy 84591 -IGRibmFtZQ== 84592 -IElubm9j 84593 -CVRSQUNF 84594 -aXZyZQ== 84595 -IG1lY2g= 84596 -YXNlbA== 84597 -Iixb 84598 -IGFzaWE= 84599 -IENhbnRlcmJ1cnk= 84600 -LkRhdGFCaW5kaW5ncw== 84601 -a2Fo 84602 -KCkpKSk= 84603 -IGR6aWV3 84604 -cmV0ZQ== 84605 -IHNjcmVlbmluZ3M= 84606 -Lk1PVVNF 84607 -IGJ1c2llc3Q= 84608 -CXJlbmRlcmVy 84609 -IHRlc3RpbW9uaWFscw== 84610 -IGFzcGlyZQ== 84611 -Zm9ydHVuZQ== 84612 -IE1TQw== 84613 -IGRhbXBpbmc= 84614 -XCIsCg== 84615 -V2Vs 84616 -V2lr 84617 -IOyXrA== 84618 -KHRpZA== 84619 -IENhbm5lcw== 84620 -b2NvcA== 84621 -PiIrCg== 84622 -ZmFjZXQ= 84623 -IHNsYXNoZWQ= 84624 -IExpYmVyaWE= 84625 -U21vb3Ro 84626 -X2NoZQ== 84627 -TGFib3Vy 84628 -IGVtaW5lbnQ= 84629 -Olg= 84630 -XEJhY2tlbmQ= 84631 -ICsrKQo= 84632 -IHRlYW13b3Jr 84633 -X2FnZw== 84634 -LlNlcnZl 84635 -IFNORA== 84636 -IFBJQ0s= 84637 -IHdpcGVz 84638 -L1R5cG9ncmFwaHk= 84639 -IEFQQQ== 84640 -aWtraQ== 84641 -IGNvZGVy 84642 -Z2FiZW4= 84643 -IHVua25vdw== 84644 -LkRlcGFydG1lbnQ= 84645 -4Lix4Lia 84646 -IHBsYXllck5hbWU= 84647 -KmU= 84648 -PEJsb2Nr 84649 -X3VwZA== 84650 -IEdpYmJz 84651 -bGVhc2luZw== 84652 -IENvbG9tYmlhbg== 84653 -KFBIUA== 84654 -ICoqKiEK 84655 -IOydvA== 84656 -IEN1cnRhaW4= 84657 -L2F5 84658 -2YTZiQ== 84659 -c3BvcnRz 84660 -IGRlc2Vh 84661 -aXLDoQ== 84662 -IHVuY29uZGl0aW9uYWw= 84663 -IHRocm9t 84664 -IENIUklTVA== 84665 -IEhPUg== 84666 -b3Njb3BpYw== 84667 -IHlhxZ8= 84668 -IG5vc3Rybw== 84669 -Li4uIik7DQo= 84670 -IHNsdXI= 84671 -IGhhdHRlbg== 84672 -IHBlc3RpY2lkZQ== 84673 -IGZyZWV3YXk= 84674 -IENvaA== 84675 -IHdhbm5vbmNl 84676 -IG1laWRlbg== 84677 -X3N1YnN0cg== 84678 -X0NTUw== 84679 -IFN5bWJvbHM= 84680 -4Li34Lit 84681 -REVU 84682 -IE1hZGRlbg== 84683 -IHJlcXVlc3Rlcg== 84684 -LnZpcnR1YWw= 84685 -IHd4RGVmYXVsdA== 84686 -IGF1dG9tw6F0aWNhbWVudGU= 84687 -YnJpZHM= 84688 -aVQ= 84689 -LlByaW9yaXR5 84690 -Jyk7PC8= 84691 -YnVuZw== 84692 -RGVhZGxpbmU= 84693 -Q29uY3JldGU= 84694 -IG5leHRQYWdl 84695 -IOuwmw== 84696 -IFN0b2tl 84697 -a29w 84698 -INCx0L7Qu9GM 84699 -IFByb2R1aw== 84700 -LW1ha2Vy 84701 -IFByb2plY3RpbGU= 84702 -YW5jZWxsYWJsZQ== 84703 -IFRIRUlS 84704 -VG9SZW1vdmU= 84705 -RU1V 84706 -Y29tbWVyY2lhbA== 84707 -QVZFRA== 84708 -IHdlYXZpbmc= 84709 -IGJpb21l 84710 -QFNldHRlcg== 84711 -cW1s 84712 -IGJyb2FkZW4= 84713 -INGB0L8= 84714 -SVNS 84715 -IGRlYWN0aXZhdGVk 84716 -IHNlbGVjdGVkSW5kZXg= 84717 -cmlvdXM= 84718 -ZWxwcw== 84719 -LkVzY2FwZQ== 84720 -IHBvbGxlZA== 84721 -cXVpYQ== 84722 -X3JlZmw= 84723 -X21pbWU= 84724 -PEF1ZGlvU291cmNl 84725 -KFRyYW5zZm9ybQ== 84726 -ZXZlbm9kZA== 84727 -CXJhbmRvbQ== 84728 -bG9jcw== 84729 -IGRldXQ= 84730 -cmVwbGFjZW1lbnQ= 84731 -IGV4YW1pbmVy 84732 -SGFzS2V5 84733 -IOumrOyKpO2KuA== 84734 -IENsb3Ro 84735 -IOCkqg== 84736 -IFJlZ2lzdHJv 84737 -IEVzdGhlcg== 84738 -IFNoYXJlZE1vZHVsZQ== 84739 -LmJvcnJvdw== 84740 -IG9zY2lsbGF0b3I= 84741 -IGZvb2xz 84742 -uqs= 84743 -IGJvYXN0aW5n 84744 -X3B1bHNl 84745 -c2hhcmluZw== 84746 -IHBpc3RvbHM= 84747 -X1BMQU4= 84748 -IHNlcHRlbWJlcg== 84749 -IG11c3Rlcg== 84750 -IG1hcmNow6k= 84751 -Q0hFTVk= 84752 -IHN1aQ== 84753 -IGdlYnJ1aWs= 84754 -Lj0n 84755 -ZXJyYXRlZA== 84756 -IExpYQ== 84757 -IGhhdW50 84758 -IEN1c2g= 84759 -cm91dGVQcm92aWRlcg== 84760 -Inw= 84761 -ZW5kcGhw 84762 -Il1dCg== 84763 -IGF2YQ== 84764 -77yBIiw= 84765 -7Ke4 84766 -IGNvbGE= 84767 -X1NQRUxM 84768 -IGFsw6lt 84769 -KExhbmd1YWdl 84770 -KGR1bW15 84771 -IGJ1bmtlcg== 84772 -IEVtcHJlc2E= 84773 -IGNyZWF0ZUNvbnRleHQ= 84774 -Om1pbg== 84775 -IEJPT1Q= 84776 -IE1lcmVkaXRo 84777 -Wmg= 84778 -IERvd25pbmc= 84779 -d2pnbA== 84780 -LmRj 84781 -c2RhbGU= 84782 -IGluY29udmVuaWVudA== 84783 -IHJlYWRtZQ== 84784 -TmF2aWdhdGlvblZpZXc= 84785 -Q09ORElUSU9O 84786 -LmRlcA== 84787 -IHLDqXVzcw== 84788 -IG9wY2nDs24= 84789 -IEFjY291bnRhYmlsaXR5 84790 -Lk1hcg== 84791 -LWd1aWQ= 84792 -RURHRQ== 84793 -RXZlbnRNYW5hZ2Vy 84794 -IGRpc2NpcGxl 84795 -dWNrbGVz 84796 -fX0+ 84797 -aW50ZXJlc3RlZA== 84798 -RmlsdGVyV2hlcmU= 84799 -IHB1c3M= 84800 -LXByb3h5 84801 -X3N0YXR1c2Vz 84802 -IFsj 84803 -dW5mb2xk 84804 -IFJvbm5pZQ== 84805 -JiYh 84806 -IGFjZXNzbw== 84807 -dW9z 84808 -X3lpZWxk 84809 -KGNhbGVuZGFy 84810 -KHNvdW5k 84811 -IGRhdGFBcnJheQ== 84812 -IFlhdGVz 84813 -IHByb2Nlc3Npb24= 84814 -RUZBVUxU 84815 -IEdIQw== 84816 -YW11cmE= 84817 -IHN0cmljdGVy 84818 -LkJPVFRPTQ== 84819 -IGhhYml0dWFs 84820 -eEFG 84821 -QVZJTkc= 84822 -IHNldHVwcw== 84823 -ID17Cg== 84824 -Kioo 84825 -IHNvaw== 84826 -IHJldGluYQ== 84827 -IEZpcmVwbGFjZQ== 84828 -aW52ZXJ0 84829 -IEZvcnJlc3Q= 84830 -PGRhdGE= 84831 -XEFjdGlvbg== 84832 -T1VHSA== 84833 -IGNhcmVsZXNz 84834 -LmdldEFjdGl2ZQ== 84835 -ZXNlcw== 84836 -IHpkasSZ 84837 -KSkqKA== 84838 -U0VN 84839 -IFBhbmlj 84840 -VG91Y2hlcw== 84841 -IHByZWNv 84842 -L2FjY291bnRz 84843 -5L6b 84844 -UG9zdGFsQ29kZXM= 84845 -LXBsdWdpbnM= 84846 -PG1lc3NhZ2U= 84847 -KHBvd2Vy 84848 -IHBlcmN1c3Npb24= 84849 -IGPDqWw= 84850 -5o6o 84851 -IGRhbmNlZA== 84852 -X1NDQU5DT0RF 84853 -IFNpdHRpbmc= 84854 -IExva2k= 84855 -U2hhcmluZw== 84856 -LkRpcg== 84857 -IHNjaHdlcg== 84858 -X0xB 84859 -Lk1lbnVTdHJpcA== 84860 -X3plcm9z 84861 -IGZpeGF0aW9u 84862 -IEFtaXQ= 84863 -IGNvbXBsaWVk 84864 -LnNwYWNlQmV0d2Vlbg== 84865 -IGFycmVzdGluZw== 84866 -IFN1Zw== 84867 -IHBlcmZvcg== 84868 -IGtvbXBsZQ== 84869 -IEVzc2VuY2U= 84870 -IHBsZWlu 84871 -c2ltdWxhdGlvbg== 84872 -IGNyZWF0ZWRCeQ== 84873 -IEV4cGVkaXRpb24= 84874 -77yBCgoKCg== 84875 -dHJhaW5lcg== 84876 -Il09JA== 84877 -IHN1Y3Rpb24= 84878 -bVBpZA== 84879 -bm90aW4= 84880 -IHByZWNpb3M= 84881 -IEFzc3VyYW5jZQ== 84882 -IExhbA== 84883 -LiIm 84884 -IG1pbkxlbmd0aA== 84885 -IE1pbmVyYWxz 84886 -dHJhamVjdG9yeQ== 84887 -U0FGRQ== 84888 -IG51YW5jZXM= 84889 -KGV4dHJh 84890 -X3ZpZGVvcw== 84891 -W109ew== 84892 -IGhvbmV5bW9vbg== 84893 -X3ByZXA= 84894 -CQkJCQkJCQkJCSA= 84895 -IHB1cnBvcw== 84896 -IGFuemVpZ2Vu 84897 -LnN0cnV0cw== 84898 -IHBhZ2Fy 84899 -LkF1dG9TaXplTW9kZQ== 84900 -IHdlbmlnZXI= 84901 -IHBhZ2Fu 84902 -IGFjaWRpYw== 84903 -Z01hcHM= 84904 -IGJld2FyZQ== 84905 -X2lwYw== 84906 -IG1lZHM= 84907 -IGRpc2XDsW8= 84908 -KSkpCgoK 84909 -Q2h1cmNo 84910 -IG51cnR1cmluZw== 84911 -X21waQ== 84912 -IHJlc3VsdGFudA== 84913 -IFBpc3RvbA== 84914 -c1BpZA== 84915 -TXNw 84916 -TW9tZW50 84917 -IFVQTE9BRA== 84918 -TmFubw== 84919 -YmxpY2s= 84920 -IG1lc3VyZQ== 84921 -IExheWVycw== 84922 -X3RyYWo= 84923 -IGJ1dHRvbldpdGhUeXBl 84924 -CWNvbW1vbg== 84925 -IE15Q2xhc3M= 84926 -2KjYsQ== 84927 -eG9vcHM= 84928 -X0hlaWdodA== 84929 -X1dBUk5JTkdT 84930 -U2V0VGV4dA== 84931 -IEhpc3Bhbmljcw== 84932 -TnVsbFBvaW50ZXJFeGNlcHRpb24= 84933 -LmZhY3Rvcg== 84934 -IHZpZWxsZWljaHQ= 84935 -IHNob3V0cw== 84936 -dHJ1c3RlZA== 84937 -IG5ld1Jvdw== 84938 -IEZyYW7Dpw== 84939 -W2pq 84940 -4oCUd2hv 84941 -IFFEaXI= 84942 -X2FkdmFuY2Vk 84943 -KEhhdmVPY2N1cnJlZA== 84944 -IHVucGw= 84945 -L3Jvcw== 84946 -LmVhc3k= 84947 -IEJBTEw= 84948 -550= 84949 -L2xncGw= 84950 -IHN1YmNvbnNjaW91cw== 84951 -ICctJzsK 84952 -ICcpOw== 84953 -INGW 84954 -IHNjYW50 84955 -X3Nlc3M= 84956 -X3BsYXlpbmc= 84957 -X0lTTw== 84958 -IHNldFNpemU= 84959 -X2RlY2s= 84960 -X0xBUkdF 84961 -IE1leQ== 84962 -Q2hpY2tlbg== 84963 -aWZmaW4= 84964 -ZGlzcG9zZQ== 84965 -SEVTVA== 84966 -TGF1Z2g= 84967 -IExDUw== 84968 -IG9uc2l0ZQ== 84969 -LmlzTG9nZ2VkSW4= 84970 -IGlycml0YXRlZA== 84971 -IGJyaWdhZGU= 84972 -IGRlcXVldWU= 84973 -Y2xhc3NOYW1lcw== 84974 -IE3DoXM= 84975 -IEF0YXJp 84976 -KElPRXhjZXB0aW9u 84977 -UmFjaGVs 84978 -LXNhbXBsZQ== 84979 -IGVpZ2VudGxpY2g= 84980 -SUZERUY= 84981 -Lm5laWdoYm9ycw== 84982 -IHNlcGVyYXRl 84983 -IExpc3Rpbmdz 84984 -LmZm 84985 -KGltcG9ydA== 84986 -TW9kZWxBdHRyaWJ1dGU= 84987 -IHNwZW5kZXI= 84988 -IG1vdGlmcw== 84989 -c3N1ZQ== 84990 -IEFwcHJlbnRpY2U= 84991 -LWNhdA== 84992 -clBpZA== 84993 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8K 84994 -b2N6 84995 -aW5pb25z 84996 -L2NvbnRhaW5lcg== 84997 -IHBsYWdpYXJpc20= 84998 -V3JpdGFibGVEYXRhYmFzZQ== 84999 -Ly4KCg== 85000 -IEZldmVy 85001 -LVZlcnNpb24= 85002 -YWNpamE= 85003 -IHdlaQ== 85004 -LWluZw== 85005 -IHRlbWFz 85006 -IHN1cmdlZA== 85007 -IGNyaWE= 85008 -IGFyZA== 85009 -Yml0Y29pbg== 85010 -LnRpbWV6b25l 85011 -IG9iamVjdE1hcHBlcg== 85012 -IAogICAgICAgICAgICAK 85013 -IHlsaW0= 85014 -IElDVQ== 85015 -IERlcHJlY2F0ZWQ= 85016 -KSgpOwo= 85017 -QVJHRVI= 85018 -dW5nYWxvdw== 85019 -VGVzdERhdGE= 85020 -KHB0cw== 85021 -RklMRU5BTUU= 85022 -dXBwbHk= 85023 -IHBhY2llbnRlcw== 85024 -LGxlZnQ= 85025 -IFdyaXRlTGluZQ== 85026 -IHBhcmNlbHM= 85027 -X2ZvbGRlcnM= 85028 -IERpcms= 85029 -LmFzc2VydElzSW5zdGFuY2U= 85030 -TWND 85031 -X1ZhcmlhYmxl 85032 -KGFh 85033 -IFBvcms= 85034 -LlB1Ymxpc2g= 85035 -LWdheQ== 85036 -IFBldHJh 85037 -IENvbm5lY3Rpbmc= 85038 -VGFiQ29udHJvbA== 85039 -aXZlcmluZw== 85040 -KFNjcmVlbg== 85041 -IGNoaWxsZWQ= 85042 -IGFpbw== 85043 -VG91Y2hFdmVudA== 85044 -IGFjY2Vzc2lvbg== 85045 -IExvaXM= 85046 -L21vbWVudA== 85047 -IGFudsOkbmQ= 85048 -IHN1aWNpZGVz 85049 -KGhlbHA= 85050 -YW5kZXJz 85051 -IFZJRA== 85052 -QmVp 85053 -ZXZlbnRv 85054 -IEFuZ3Vz 85055 -VmVycw== 85056 -IEJvcmRlYXV4 85057 -LnN0cmVhbWluZw== 85058 -IHJvdWdl 85059 -IGNyYWZ0c21hbnNoaXA= 85060 -b3NzaWw= 85061 -X0ZBTEw= 85062 -QG1lZGlh 85063 -aWxlYWtz 85064 -RGF0YVNlcnZpY2U= 85065 -IFRyaXBBZHZpc29y 85066 -IE1hYXI= 85067 -Q3Vyc28= 85068 -UG9zdGFsQ29kZXNOTA== 85069 -KCk7Kys= 85070 -JFBvc3RhbENvZGVzTkw= 85071 -IG9jb3I= 85072 -IHRhaW50ZWQ= 85073 -IGxlbQ== 85074 -LW91dHM= 85075 -IHh4eHg= 85076 -IGlycml0YXRpbmc= 85077 -b3hpZA== 85078 -b2ludGVk 85079 -IFRvcm8= 85080 -X292 85081 -LmJpcnRo 85082 -KyU= 85083 -IENoYXJhY3RlcmlzdGljcw== 85084 -IEJldHRpbmc= 85085 -IG9mZmVuZA== 85086 -IFBIWVM= 85087 -IElDTVA= 85088 -eERD 85089 -IENk 85090 -LmdldE1hcA== 85091 -YXRjaGV0 85092 -LmN1cnJlbnRJbmRleA== 85093 -RVJBTA== 85094 -IGthcHBh 85095 -aWRlbmNlcw== 85096 -UGFyZW4= 85097 -IFNlcmdlaQ== 85098 -LWZpbg== 85099 -J10sWyc= 85100 -w6FtYXJh 85101 -R3Jvd2luZw== 85102 -R2xhc3M= 85103 -CW1ldGE= 85104 -dmVyYmF0aW0= 85105 -L0dQTA== 85106 -IEthaA== 85107 -KHN2Zw== 85108 -Y2xpc3Q= 85109 -IEJsb3dqb2I= 85110 -b2NjYW4= 85111 -LmFib3J0 85112 -b2RlbGlzdA== 85113 -IGRpZmbDqXJlbnRz 85114 -X09QVFM= 85115 -PXJlcQ== 85116 -IGludG94 85117 -IGRpYWdvbg== 85118 -IFsoIg== 85119 -JlI= 85120 -IG9iamVjdGl2ZWx5 85121 -IGJsaW5raW5n 85122 -IExvdmVz 85123 -cmluZ2U= 85124 -Kik7Cgo= 85125 -IEJvbmRz 85126 -IExvdmVk 85127 -ZWx0cw== 85128 -IGRpc3BhcmF0ZQ== 85129 -IEVucmlxdWU= 85130 -IldpdGg= 85131 -cmVtaXVt 85132 -YWphcmFu 85133 -dHJ5aW5n 85134 -LVJ1c3NpYW4= 85135 -bmV3SW5zdGFuY2U= 85136 -LlRSQU4= 85137 -IG9yYW5nZXM= 85138 -L2xvY2FsZQ== 85139 -IERJU1A= 85140 -CW5z 85141 -IFNodXR0ZXJzdG9jaw== 85142 -IENMT0NL 85143 -KHJhZA== 85144 -IGFzc3VyYW5jZXM= 85145 -IHJhc3A= 85146 -VWJlcmdyYXBo 85147 -RW1pbHk= 85148 -IGludmVudGlvbnM= 85149 -cmlvdA== 85150 -IHRvc3Npbmc= 85151 -IG1ha2VvdmVy 85152 -IHVuaXRPZldvcms= 85153 -YnV0dG9uU2hhcGU= 85154 -5Yid5aeL5YyW 85155 -IHBhcnRlZA== 85156 -4paR 85157 -LnNpZ21vaWQ= 85158 -IHJlZGlyZWN0aW9u 85159 -IGRpc3R1cmJhbmNlcw== 85160 -IGludGltaWRhdGVk 85161 -CUNyZWF0ZWQ= 85162 -YWdldA== 85163 -IGNvcnJlcw== 85164 -IE5FRw== 85165 -aXRvbmU= 85166 -L2Zyb250 85167 -IFZlcnNl 85168 -Z2FtYmFy 85169 -IHByZW1pZXJlZA== 85170 -IElNTw== 85171 -IEdvYmllcm5v 85172 -IGlmcw== 85173 -YXlhaA== 85174 -LkNPTA== 85175 -IGZyZWRlcg== 85176 -IHN1Ym1lcmdlZA== 85177 -IE5lcm8= 85178 -bW9kaWZpYWJsZQ== 85179 -L0Zvb3Rlcg== 85180 -LWNlbnRyYWw= 85181 -IGdvdXZlcg== 85182 -IFRyaWVk 85183 -IGRpenp5 85184 -UXVlcnlQYXJhbQ== 85185 -Ij4nKwo= 85186 -X3ByaW1pdGl2ZQ== 85187 -56iO 85188 -LmdwdQ== 85189 -IHZveg== 85190 -ZW56ZQ== 85191 -IFdpbGRlcm5lc3M= 85192 -IHByb2JhYmls 85193 -L3JlYw== 85194 -IGFjY2Vz 85195 -IFRydXN0ZWVz 85196 -R2I= 85197 -IHBhZGRpbmdIb3Jpem9udGFs 85198 -U2hpZWxk 85199 -IE5hbWVu 85200 -dWRkbGVk 85201 -IFByaW9yaXR5UXVldWU= 85202 -UG9vcg== 85203 -IFNBRg== 85204 -LS1bWw== 85205 -IGNobG9yaW5l 85206 -IHZlcmJhbGx5 85207 -IGFpcmU= 85208 -PjsNCg== 85209 -aWxoYQ== 85210 -W2NvbG9y 85211 -YW5kYWxvbmU= 85212 -LmFkZFJvdw== 85213 -IFNvaw== 85214 -IENvbm9y 85215 -IG1lam9yYXI= 85216 -J2lscw== 85217 -ZGV0YWxsZQ== 85218 -ICIpLAo= 85219 -JUA= 85220 -Lmxhenk= 85221 -Lmp1bXA= 85222 -b3N0ZQ== 85223 -K0Y= 85224 -IGluZnVyaQ== 85225 -IHNvbnJh 85226 -aXRlbWlk 85227 -JGxvZw== 85228 -IG11cmRlcm91cw== 85229 -TEVD 85230 -CW5pbA== 85231 -IE3DpHI= 85232 -KHBn 85233 -aWxlbw== 85234 -QXNjaWk= 85235 -IExvY2toZWVk 85236 -IFRoZW8= 85237 -QmVsbA== 85238 -YWNpb25hbGVz 85239 -LmNyZWF0ZU5ldw== 85240 -IOW+ 85241 -LWZvb3RiYWxs 85242 -IGVjb21tZXJjZQ== 85243 -CVNpbXBsZQ== 85244 -Y2x5 85245 -LklubmVyRXhjZXB0aW9u 85246 -IHBlc29z 85247 -IHRyb3Bl 85248 -IEFSR1M= 85249 -TWlhbWk= 85250 -IFBhbG8= 85251 -IFN1emFubmU= 85252 -X21hcHBpbmdz 85253 -I3tA 85254 -IE9jY3VwYXRpb25hbA== 85255 -X2J1Y2tldHM= 85256 -Z29hbHM= 85257 -X1J1bg== 85258 -LXByZXBlbmQ= 85259 -c3Nz 85260 -bWFyc2hhbGw= 85261 -IGVxdWl2YWxlbmNl 85262 -IFdlbGNo 85263 -KE9wQ29kZXM= 85264 -CWNsb2Nr 85265 -IE1lZGluYQ== 85266 -VEVSUw== 85267 -b3Jhbmc= 85268 -VGhvdWdodA== 85269 -IG9hdHM= 85270 -X1RFWA== 85271 -UklDUw== 85272 -IGluZGlmZmVyZW5jZQ== 85273 -IGFsbG90 85274 -LlVzZVRleHQ= 85275 -IFRyaWNrcw== 85276 -YXdl 85277 -LkZJTEw= 85278 -LXBocA== 85279 -LnZvaWNl 85280 -IFBhdGhmaW5kZXI= 85281 -X1RBR1M= 85282 -IFRyaXQ= 85283 -5oyJ6ZKu 85284 -YmJj 85285 -IGFkZGl0aXZlcw== 85286 -IHNjaGxl 85287 -IEtleWJvYXJkSW50ZXJydXB0 85288 -IHVzZVBhcmFtcw== 85289 -IEJ1Y2hhbmFu 85290 -cmlhbmdsZQ== 85291 -IG11bHRpcGx5aW5n 85292 -IHNlbGJlcg== 85293 -IFllcA== 85294 -Q2hhaXI= 85295 -LXJlcG9ydGVk 85296 -X1NESw== 85297 -LG5v 85298 -IEZhbGxpbmc= 85299 -5rk= 85300 -ICgpLAo= 85301 -cGRi 85302 -IEJvcm91Z2g= 85303 -LnJlbW92ZUZyb20= 85304 -IG92ZXJzaGFkb3c= 85305 -aWdhaWw= 85306 -IHR1bmc= 85307 -IG1tYw== 85308 -W3BhcmVudA== 85309 -RXh0ZXJu 85310 -YXZpb2xldA== 85311 -JykiCg== 85312 -IGNvdW50ZXJ0b3Bz 85313 -IHVidW50dQ== 85314 -5rc= 85315 -IM6T 85316 -IHVucHVibGlzaGVk 85317 -IEluZGllcw== 85318 -VU5FVA== 85319 -IG9mZXJ0YQ== 85320 -IGRhbWVz 85321 -IGFzdGVyb2lkcw== 85322 -IG5vdmVtYmVy 85323 -Y29udHJhc3Q= 85324 -LkFkZE1vZGVsRXJyb3I= 85325 -K1NhbnM= 85326 -IHNjcmFtYmxpbmc= 85327 -dGV4dFZpZXc= 85328 -L2NyeXB0bw== 85329 -VXNlUHJvZ3JhbQ== 85330 -QHVwZGF0ZQ== 85331 -RGVzZGU= 85332 -U0FU 85333 -IGRpc3BsZQ== 85334 -YW5uw6ll 85335 -XERlcGVuZGVuY3lJbmplY3Rpb24= 85336 -IGl0bQ== 85337 -IOe8 85338 -IGV0aG9z 85339 -QVBP 85340 -IEdhcmPDrWE= 85341 -aWRpcw== 85342 -IFN0ZWFr 85343 -cmliYQ== 85344 -X3ZlcmlmaWNhdGlvbg== 85345 -IEZL 85346 -IEVpbnNhdHo= 85347 -IHBlcnNvbmFsaXNlZA== 85348 -LW1vdGlvbg== 85349 -IE1lbGFuaWU= 85350 -w7Zo 85351 -X1ZD 85352 -IGRyaWZ0aW5n 85353 -LmNvbnN0cnVjdA== 85354 -IO2UhA== 85355 -IGJhdGNoaW5n 85356 -Li4vLi4vLi4vLi4v 85357 -RVJQ 85358 -X3V0Yw== 85359 -IG11bHRpdA== 85360 -IG1yYg== 85361 -Y2Nhaw== 85362 -Y2h1bmtz 85363 -IHRyYW5zbHVjZW50 85364 -IHBheW9mZg== 85365 -4oCUYW4= 85366 -IHNpbGw= 85367 -IG9ybmFtZW50cw== 85368 -Z3Vh 85369 -VUJZ 85370 -KHN0ZXBz 85371 -IEJPUkRFUg== 85372 -IFNPVU5E 85373 -YGAK 85374 -ZW5hcmllcw== 85375 -IEJpdHRl 85376 -IGdseXBocw== 85377 -IG92ZXJydW4= 85378 -IGJsb2NrSWR4 85379 -IE1TVA== 85380 -IGdlbm9tZXM= 85381 -dGVuc29yZmxvdw== 85382 -RGlyZWN0b3J5TmFtZQ== 85383 -X2xocw== 85384 -IGZpbnQ= 85385 -YWRkdG9ncm91cA== 85386 -IHN0ZWFkZmFzdA== 85387 -IGNsb3Zlcw== 85388 -IFNvdmlldHM= 85389 -IElTQQ== 85390 -wqNv 85391 -dXJnZXJ5 85392 -c292 85393 -INCy0YvQstC+0LQ= 85394 -IHB1ZA== 85395 -LXdhdGNo 85396 -IEhvc3BpdGFscw== 85397 -fXdoaWxl 85398 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMj 85399 -4buj 85400 -IGFrdHVhbA== 85401 -IGtpbG9ncmFtcw== 85402 -IEZBQw== 85403 -b3BoeXM= 85404 -cHJz 85405 -KkA= 85406 -eWI= 85407 -c2VjdXJlZA== 85408 -IGFsZ8O6bg== 85409 -IOCkuQ== 85410 -cGhhbnM= 85411 -QWRkb24= 85412 -IGNlbnRyYWxseQ== 85413 -X1NVSVRF 85414 -SW50ZXJlc3Rpbmc= 85415 -dWx0aW1v 85416 -QWdhaW5zdA== 85417 -IEV6cmE= 85418 -IEhlYg== 85419 -dWlkYQ== 85420 -IHNreXM= 85421 -T0xWRQ== 85422 -QmVuZWZpdHM= 85423 -IHByaXNl 85424 -Lio/KQ== 85425 -LmlzRGVmaW5lZA== 85426 -IHN0YW5kb2Zm 85427 -IHBsYW5v 85428 -LmxhdGVzdA== 85429 -ICgkLg== 85430 -IEdvdWxk 85431 -IGNhdXRpb25lZA== 85432 -J10o 85433 -IG51aXQ= 85434 -IEhDSQ== 85435 -Zm9vdGJhbGw= 85436 -IHdpbGxlbg== 85437 -UHJvY2VlZA== 85438 -IGludGVuZGluZw== 85439 -dGlm 85440 -IHNwb25zb3Jpbmc= 85441 -b2hhbmE= 85442 -RG9z 85443 -TW9ybmluZw== 85444 -ICEiKTsK 85445 -LnNoZWxs 85446 -IFJFTEFURUQ= 85447 -IHBpbXA= 85448 -L2NvdXJzZQ== 85449 -IHJhbWlmaWNhdGlvbnM= 85450 -IHBpeG1hcA== 85451 -IHBvd2VybGVzcw== 85452 -IGRvdWNoZQ== 85453 -Y3JpbWU= 85454 -Y29udHJpYnV0b3Jz 85455 -KHByb3RvY29s 85456 -IGdldFBvc2l0aW9u 85457 -U0VUVElOR1M= 85458 -IHZpZXQ= 85459 -aXNzZXM= 85460 -V2l0aEVtYWlsQW5kUGFzc3dvcmQ= 85461 -UmV0dXJuVHlwZQ== 85462 -QXBwZQ== 85463 -IElLRQ== 85464 -LkNvb2tpZXM= 85465 -Lm1lZGl1bQ== 85466 -LmdldEpTT05BcnJheQ== 85467 -X0Zvcg== 85468 -L3Rpbnlvcw== 85469 -IFRhYmxlQ2VsbA== 85470 -IFJFUExBQ0U= 85471 -Lk5ldHdvcmtpbmc= 85472 -IGJvd2Vk 85473 -CW1k 85474 -PSJ7ISE= 85475 -IGhvbmRh 85476 -IEV1cg== 85477 -IGluZG9uZXNpYQ== 85478 -IGhlbmQ= 85479 -LnZpZXdtb2RlbA== 85480 -CWN0cmw= 85481 -IFRhYmxldHM= 85482 -LW9yYW5nZQ== 85483 -ZXJyYXM= 85484 -X2dyYXBoaWNz 85485 -e3M= 85486 -IFRpdGxlcw== 85487 -IGRpYWdub3Nlcw== 85488 -b3VwbGU= 85489 -X0RvdWJsZQ== 85490 -W3Jlc3VsdA== 85491 -IGppdHRlcg== 85492 -X05VTUVSSUM= 85493 -PmY= 85494 -X01Z 85495 -0LjRgdGC0LXQvA== 85496 -c3RvcmVJZA== 85497 -IHJlbGlucXU= 85498 -ZW9z 85499 -IHdpZGVuaW5n 85500 -IHRhY29z 85501 -LllFUw== 85502 -XSsn 85503 -IEluZGV4ZWQ= 85504 -IHByb2Zlc3Npb25uZWw= 85505 -IFN0cmFw 85506 -QnVmZmVyRGF0YQ== 85507 -ZWVh 85508 -ZXJpbg== 85509 -QU5DRVM= 85510 -X1RYVA== 85511 -IHt9Lg== 85512 -KGNvbnRyYWN0 85513 -eXc= 85514 -IGJsaW5kbmVzcw== 85515 -Q0hBTg== 85516 -CWdsQ29sb3I= 85517 -IGN1cnJlbnRQb3NpdGlvbg== 85518 -IENhdWNhc2lhbg== 85519 -JGltZw== 85520 -I2Fh 85521 -IHNlYW4= 85522 -TWVzcw== 85523 -Kj0qPQ== 85524 -IGNhcGFjaXRvcg== 85525 -YWxmYQ== 85526 -LlJlbW92ZUFsbA== 85527 -IFdQQVJBTQ== 85528 -dWxhZG8= 85529 -bmljb3M= 85530 -IG9yZ3k= 85531 -R1g= 85532 -X0RFVklDRVM= 85533 -b3Vya2U= 85534 -IGtC 85535 -IHNvcGhpc3RpY2F0aW9u 85536 -X2F1ZGl0 85537 -L0lQ 85538 -IEx5ZnQ= 85539 -L1N0 85540 -CWNhbmNlbA== 85541 -IG92YXJpYW4= 85542 -bWFyaW5l 85543 -a8SZ 85544 -IFlN 85545 -IE1pbG8= 85546 -IE1hdFRhYmxl 85547 -IEFiYnk= 85548 -bnpl 85549 -IEx1ZHdpZw== 85550 -X2FybW9y 85551 -IHNjYWZmb2xk 85552 -4buXaQ== 85553 -YXV0aG9yaXR5 85554 -4bqleQ== 85555 -LmdldFByb2R1Y3Q= 85556 -IE9yYml0 85557 -X1BhcmFtZXRlcg== 85558 -LmRhdGVGb3JtYXQ= 85559 -L3RhZ3M= 85560 -LlNwZWVk 85561 -KExpbmU= 85562 -IHBvbGlzaGluZw== 85563 -IGtvbWI= 85564 -IHJ0cmlt 85565 -J2ljb24= 85566 -cmllcmU= 85567 -IFByZWZlcg== 85568 -c3RydG9sb3dlcg== 85569 -UmVncw== 85570 -Q0JE 85571 -LT4K 85572 -IHBhcmFzaXRl 85573 -ZW5kc1dpdGg= 85574 -IENvYnJh 85575 -OnRlc3Q= 85576 -IE51Z2dldHM= 85577 -xaF0 85578 -Q29yZUFwcGxpY2F0aW9u 85579 -L2JpbmQ= 85580 -IE1jSW50 85581 -aXR1bmVz 85582 -Wy0t 85583 -IFN1cnByaXNl 85584 -X0lORw== 85585 -IEZhc3Rlcg== 85586 -0J3QsA== 85587 -OkU= 85588 -IGRpbnQ= 85589 -bmdl 85590 -LiInLCciLiQ= 85591 -IGFkamVjdGl2ZQ== 85592 -LmJj 85593 -Y29uc3VtZQ== 85594 -Qk9S 85595 -KGFuY2hvcg== 85596 -IGVzdGVlbQ== 85597 -IGJyZWFrdXA= 85598 -ZGVjYXk= 85599 -ICQKCg== 85600 -RWR3YXJk 85601 -QVNJ 85602 -IGF0dGFjaGVz 85603 -X0RJU0s= 85604 -IFdpbG1pbmd0b24= 85605 -IEt1bA== 85606 -IFtbXQ== 85607 -IERlcGFydG1lbnRz 85608 -IHJldHVyblR5cGU= 85609 -IFVOSVRFRA== 85610 -b2JqZWN0aXZl 85611 -IGdpcmxmcmllbmRz 85612 -X0dV 85613 -QHN0b3Jl 85614 -LU91dA== 85615 -Lm1vdmVz 85616 -KHN0YXJ0RGF0ZQ== 85617 -CUpCdXR0b24= 85618 -IFBhY2U= 85619 -IEJlYXRz 85620 -IGxpY3o= 85621 -IGV0aGVyZXVt 85622 -IGNoZWVyZWQ= 85623 -IGF1Y3Vu 85624 -UmVnYXJkaW5n 85625 -IG1pZ3JhdGluZw== 85626 -IGZ1dGlsZQ== 85627 -IFRhY29tYQ== 85628 -X0NoYXJhY3Rlcg== 85629 -IHZn 85630 -IENvcGE= 85631 -2Ks= 85632 -IG5hbA== 85633 -IGxhbmRmaWxs 85634 -IHRhbWls 85635 -IHBlcnBldHJhdG9y 85636 -IFBhY2Vycw== 85637 -LmdldE9yZGVy 85638 -fA0K 85639 -R2V0T2JqZWN0 85640 -IGJsYQ== 85641 -IEhhcmFt 85642 -cG9ydGxldA== 85643 -IGxva2Fs 85644 -TWVyY2hhbnQ= 85645 -UGFzc3dvcmRz 85646 -b25lbnQ= 85647 -IGFydGVyaWVz 85648 -IEludGVsbGk= 85649 -XFN5c3RlbQ== 85650 -PWxvY2FsaG9zdA== 85651 -LmF2aQ== 85652 -IFZlbmQ= 85653 -KHRibA== 85654 -Q29ycmVjdGlvbg== 85655 -IHV0ZXJ1cw== 85656 -IHNhbGl2YQ== 85657 -Kys7DQoNCg== 85658 -KCcqJyw= 85659 -IHNuYXRjaA== 85660 -IFNUUkVFVA== 85661 -KVs6 85662 -54Sh44GX44E= 85663 -U2VudGVuY2U= 85664 -KCkuJy8= 85665 -OnJlbGF0aXZl 85666 -leOCkw== 85667 -X3VzZXJpZA== 85668 -b2xpbmc= 85669 -IENsYXNo 85670 -CXNldHVw 85671 -KG1p 85672 -IGppdA== 85673 -IFNjYW5kaW5hdmlhbg== 85674 -IFBob25lcw== 85675 -Iic7Cg== 85676 -IHR1bXVsdA== 85677 -IEludGw= 85678 -IFNpbm4= 85679 -KG5ld3M= 85680 -IGRicw== 85681 -IFJlbWFya3M= 85682 -S2l0Y2hlbg== 85683 -IGFkbWlyYWJsZQ== 85684 -X2Rhc2g= 85685 -IERPTUFJTg== 85686 -YWRkTGlzdGVuZXI= 85687 -Il0uKA== 85688 -CU1ldGhvZA== 85689 -bWFya3Q= 85690 -LGV4cG9ydHM= 85691 -IG91dG51bWJlcg== 85692 -X0FTQw== 85693 -cHJlbWl1bQ== 85694 -KU5VTEw= 85695 -IEJvd21hbg== 85696 -LnNldE9uSXRlbUNsaWNrTGlzdGVuZXI= 85697 -IFJlZ2V4T3B0aW9ucw== 85698 -S2Vs 85699 -L21hdA== 85700 -44GT44KM 85701 -IHdlYXJlcg== 85702 -aW5pcw== 85703 -W2RpbQ== 85704 -IE51dHp1bmc= 85705 -aXNidXJ5 85706 -5Yid 85707 -IHJvb3RSZWR1Y2Vy 85708 -ZXlK 85709 -SW5jbHVkZWQ= 85710 -LUxlYWd1ZQ== 85711 -YW5heA== 85712 -KGluZmxhdGVy 85713 -IEZpZWxkVHlwZQ== 85714 -IHNob3Zl 85715 -IGZ1bGxmaWxl 85716 -RGF0YU1hbmFnZXI= 85717 -LmdldExlZnQ= 85718 -IEZz 85719 -ZHJvcG91dA== 85720 -IOuyiA== 85721 -IG1hbmnDqHJl 85722 -IGZsYW1pbmc= 85723 -IGNvbXBsZXRhbWVudGU= 85724 -4oCw 85725 -fC4= 85726 -RW5lbWllcw== 85727 -b3NjaQ== 85728 -IFNBWQ== 85729 -IG1hcnk= 85730 -KFJ1bnRpbWVPYmplY3Q= 85731 -IH4+ 85732 -IFNpbXBzb25z 85733 -J10uJA== 85734 -X21lbWJlcnNoaXA= 85735 -KSI6 85736 -IGxheW91dE1hbmFnZXI= 85737 -IFJvY2tlZmVsbGVy 85738 -ICd8Jw== 85739 -SVBI 85740 -RE9O 85741 -YWNodGU= 85742 -UGVhY2U= 85743 -aHRhcg== 85744 -QCIK 85745 -IHRyZWFkbWlsbA== 85746 -IHNwdXJyZWQ= 85747 -IEtW 85748 -bWlkZA== 85749 -IGZsb3dlZA== 85750 -w6Nlc3Rl 85751 -R2VuZXNpcw== 85752 -PT0+ 85753 -IFZlbnR1cmE= 85754 -X2VsaW0= 85755 -INC40LzRjw== 85756 -IHNvbmd3cml0ZXI= 85757 -Y3JlYXRlRm9ybQ== 85758 -SUdITA== 85759 -IG1vbGRlZA== 85760 -IHJldmVyZWQ= 85761 -VW5kZXJUZXN0 85762 -aW1ibGVkb24= 85763 -X1Nlc3Npb24= 85764 -IG1hc2NvdA== 85765 -IGFsZg== 85766 -66mU 85767 -PldlbGNvbWU= 85768 -IGtub2Nrcw== 85769 -IEVxdWF0aW9u 85770 -LnRvdWNoZXM= 85771 -X0xhc3Q= 85772 -IHVwYmVhdA== 85773 -YmlnaW50 85774 -IGVudmlz 85775 -L2Jhbm5lcg== 85776 -44GC44KK44GM 85777 -IERvd25z 85778 -X1NG 85779 -IHJ1bkFwcA== 85780 -IHF1ZXN0aQ== 85781 -VHJhZGl0aW9uYWw= 85782 -X3dhaXRpbmc= 85783 -cGlja3Vw 85784 -KCdALw== 85785 -CXNl 85786 -IEtlcm4= 85787 -IERlbGljaW91cw== 85788 -IHNhdHVybg== 85789 -IEpTT05FeGNlcHRpb24= 85790 -44KN 85791 -SlI= 85792 -fSgpKTsK 85793 -IFNvbWFsaQ== 85794 -dWFp 85795 -aW1hZ2Vt 85796 -YW5kRmlsdGVyV2hlcmU= 85797 -w6hsZXM= 85798 -aW5ib3g= 85799 -IHlhcMSx 85800 -IG1laXN0ZW4= 85801 -YF0o 85802 -U1dH 85803 -LGNsYXNz 85804 -4LWN4LQ= 85805 -dGFpZW50 85806 -IEZyYW7Dp29pcw== 85807 -QXV0aFRva2Vu 85808 -IHB1ZXN0bw== 85809 -IGps 85810 -IGdhdGVk 85811 -IERlYXRocw== 85812 -IFNpZGQ= 85813 -IHByZXZhaWxlZA== 85814 -LcOqdHJl 85815 -KGFsYnVt 85816 -IHFpbnQ= 85817 -bWFyY2E= 85818 -IE5BRlRB 85819 -IHRpZ2h0ZW5lZA== 85820 -X0dBUA== 85821 -RU5TSU9OUw== 85822 -IExpYmVydGFyaWFu 85823 -X3N0eWxlc2hlZXQ= 85824 -LlNldEludA== 85825 -X3B1Ymxpc2hlcg== 85826 -cGFnZU51bWJlcg== 85827 -enNjaGU= 85828 -IFNRTEFsY2hlbXk= 85829 -IGhvb2Y= 85830 -Z2V0VG9rZW4= 85831 -IG5lYmVu 85832 -bHVuZA== 85833 -Lm1pdA== 85834 -ZXJycw== 85835 -LnNldE1pbmltdW0= 85836 -LXByaWNlZA== 85837 -KHBv 85838 -ZW5nYWdl 85839 -X0ZU 85840 -Ly8KCgo= 85841 -IHRvbWU= 85842 -ICI+PC8= 85843 -VmVjdG9ycw== 85844 -IFRlc3RVdGlscw== 85845 -ZmlsdHI= 85846 -VXN1 85847 -IGRpY3Rpb25hcnlXaXRo 85848 -IG9icmFz 85849 -IEJEU00= 85850 -LmdldFRhcmdldA== 85851 -IGFsbG93YWJsZQ== 85852 -IEluc2VydHM= 85853 -CU5vbmU= 85854 -IGxpYmVyYXRlZA== 85855 -S2VudA== 85856 -IFdpc2hsaXN0 85857 -IExhZ2Vy 85858 -IGp1aW4= 85859 -IG51ZXM= 85860 -IG1vbmFzdGVyeQ== 85861 -IG1pY3Jvc2Vjb25kcw== 85862 -IEhhbm5h 85863 -0L7RgdGC0Lg= 85864 -d2VhcG9ucw== 85865 -X3Nwb3Q= 85866 -b2RvbQ== 85867 -Lk1vZGVsRm9ybQ== 85868 -IG9yZGVybHk= 85869 -RklOSVRF 85870 -IHJlc2lkZW5jZXM= 85871 -X3RD 85872 -Q0dDb2xvcg== 85873 -IMW+ZQ== 85874 -IHNjcmVlbnBsYXk= 85875 -IHB5bW9uZ28= 85876 -IGTDqXQ= 85877 -IGRlc3Rh 85878 -IE5ldXJvc2NpZW5jZQ== 85879 -bmllc3Q= 85880 -QEdlbmVyYXRlZFZhbHVl 85881 -RUxTRQ== 85882 -PGw= 85883 -IGRpc2pvaW50 85884 -LnB1Ymxpc2hlZA== 85885 -ZWxsYW4= 85886 -IFN0cmluZ1dyaXRlcg== 85887 -LkJyb2FkY2FzdA== 85888 -IEZlaW5zdGVpbg== 85889 -YW1waGV0YW1pbmU= 85890 -S2V5U3BlYw== 85891 -IEdyaW1t 85892 -ZXR0ZWw= 85893 -4Lic 85894 -T3Q= 85895 -aWJyYWx0YXI= 85896 -Y2Vi 85897 -IHRpbWluZ3M= 85898 -aW5lZQ== 85899 -IEFuZHLDqQ== 85900 -RXNzYXk= 85901 -Lmpk 85902 -IEJ1bmRlc2xpZ2E= 85903 -UmV0dXJuZWQ= 85904 -IGFwcGFsbGluZw== 85905 -LkJpZ0ludGVnZXI= 85906 -IFNFTg== 85907 -IEhvbWVtYWRl 85908 -LmNoYXB0ZXI= 85909 -LXZhbGlk 85910 -IEFUVFJJQlVURQ== 85911 -dXN0cmlh 85912 -IGVudMOjbw== 85913 -UmV0dXJuaW5n 85914 -dmVydGlzZXI= 85915 -LlBhY2thZ2VNYW5hZ2Vy 85916 -Q2xhcms= 85917 -IHF1b3Rhcw== 85918 -IHNjYWxlRmFjdG9y 85919 -IGNveg== 85920 -X21pbmk= 85921 -IG11dGF0ZWQ= 85922 -LmFjdGl2YXRpb24= 85923 -Km1hdGg= 85924 -LnZlcnR4 85925 -PGFydGljbGU= 85926 -IGVtYnJvaWRlcnk= 85927 -L2J1c2luZXNz 85928 -Y2tldHQ= 85929 -c2NpZW50aWZpYw== 85930 -IEdpbGVz 85931 -IHJhY2Vy 85932 -X3BlcmZvcm1hbmNl 85933 -IGxhbWluYXRl 85934 -IFBISQ== 85935 -UsOp 85936 -IEF0aGU= 85937 -Y29sZXM= 85938 -IHNhxJ8= 85939 -IElua1dlbGw= 85940 -CXNpZw== 85941 -IHNwYWNlc2hpcA== 85942 -IGluc29s 85943 -IFVDbGFzcw== 85944 -LmxlYWRpbmdBbmNob3I= 85945 -dG90YWxz 85946 -IHNwcmlua2xl 85947 -IE1vZHVsYXI= 85948 -ICdcIg== 85949 -b3Jvbg== 85950 -LlJlYWRBbGxUZXh0 85951 -ICAgIAkNCg== 85952 -L2lvbg== 85953 -REVQVEg= 85954 -X21pbmltdW0= 85955 -XENhY2hl 85956 -IGRpdmVyc2lmaWVk 85957 -aWduZXQ= 85958 -IGRvam8= 85959 -IFVJQWxlcnRWaWV3 85960 -L3R0eQ== 85961 -IFNhc3M= 85962 -IC9cLig= 85963 -IElNQUdFUw== 85964 -IGRhdGluZ3NpZGVy 85965 -IEV4cGxvcw== 85966 -LmdlbnJl 85967 -XEV2ZW50cw== 85968 -IGVudW1lcmF0ZWQ= 85969 -Y3VycmVudFN0YXRl 85970 -aXRydXN0 85971 -Q2FsbGFibGVXcmFwcGVy 85972 -Rm91bmRlZA== 85973 -IHJveWFsdGllcw== 85974 -KFByb3BlcnRpZXM= 85975 -IFVTUFM= 85976 -LS0tLS0tLS0tLS0NCg== 85977 -LlJlYWRUb0VuZA== 85978 -IGNvc3k= 85979 -IGFwZQ== 85980 -X2RlZmluaXRpb25z 85981 -IHBhZ2VObw== 85982 -IGR6aWVjaQ== 85983 -c3RhbmRlbg== 85984 -IGJlc2Fy 85985 -aXRpbg== 85986 -IGNvbnNlcXVhdA== 85987 -IHBydg== 85988 -IHNwbGl0dGVk 85989 -IGVzcG9zYQ== 85990 -PWZpbmRWaWV3QnlJZA== 85991 -V2Fsa2Vy 85992 -IEhlYXJ0aA== 85993 -aWJyYXRvcg== 85994 -b3RvbXk= 85995 -YWdnYWJsZQ== 85996 -IOW9kw== 85997 -77yBJyk7Cg== 85998 -aW9uYXRl 85999 -L3llYXI= 86000 -IHNldEM= 86001 -IE1lZGlhVGVr 86002 -LWJveQ== 86003 -LnRvb2xTdHJpcE1lbnVJdGVt 86004 -Q29uZmlncw== 86005 -YXR0ZW5kZWQ= 86006 -IGVtb2M= 86007 -IEJhaQ== 86008 -b3BvbGl0YW4= 86009 -IGludHJ1c2l2ZQ== 86010 -IHp1Zw== 86011 -IGZmbXBlZw== 86012 -X2Jvb3N0 86013 -IG1vemlsbGE= 86014 -IHNsaWNpbmc= 86015 -V0c= 86016 -cGFnZXNpemU= 86017 -UHJvcGVydHlEZXNjcmlwdG9y 86018 -IEFsZWphbmRybw== 86019 -VVNFUw== 86020 -SG9zdGluZw== 86021 -IHJpc2tpbmc= 86022 -IEludml0ZQ== 86023 -IEphemVlcmE= 86024 -IHJlZ2FpbmVk 86025 -IEhhZ3Vl 86026 -IGd1ZXJyYQ== 86027 -IGVuY2xvc2luZw== 86028 -J10iKQo= 86029 -PFRyYW5zZm9ybQ== 86030 -Lk5PUlRI 86031 -IGNyaW0= 86032 -SU5V 86033 -IGNsZW4= 86034 -IE1vdGhlcnM= 86035 -IE93bmVyc2hpcA== 86036 -RHJpbms= 86037 -IGJlYmVyYXBh 86038 -Lm9uZXJyb3I= 86039 -KSsK 86040 -IHRhYkluZGV4 86041 -IERpbw== 86042 -IEZvcnR5 86043 -KExpbms= 86044 -IHNlZ21lbnRlZA== 86045 -IGphbWVz 86046 -IFRhcmdldHM= 86047 -IFJUUw== 86048 -INC60L3QvtC/ 86049 -IHZhcmlhcw== 86050 -IHTDrXR1bG8= 86051 -IGTDvHI= 86052 -L0dhbWU= 86053 -cmFuc2l0aW9u 86054 -IGRpc3Rpbmd1aXNoaW5n 86055 -dWt0dXI= 86056 -YW5qZQ== 86057 -IE1jQ2FiZQ== 86058 -cGFp 86059 -KHRr 86060 -RGVzdHJ1Y3Rvcg== 86061 -R2FtZU9iamVjdFdpdGhUYWc= 86062 -JGg= 86063 -IGFmcg== 86064 -LnNldEVtYWls 86065 -IHJlcGV0aXRpb25z 86066 -bGFuZGVycw== 86067 -IFNoZWE= 86068 -X2NsYWlt 86069 -IGFjZXNz 86070 -QmVuY2htYXJr 86071 -LkVzdA== 86072 -LlBP 86073 -IE7DpA== 86074 -IGl0Y2hpbmc= 86075 -IGNvbmRvbWluaXVt 86076 -X0ZXRA== 86077 -IHJlYWx0aW1l 86078 -IGNpdmlsaXplZA== 86079 -X3BoeXNpY2Fs 86080 -UmFs 86081 -IHdpbnRlcnM= 86082 -IFlhZA== 86083 -IGZvcmE= 86084 -IGNhbGlicmF0ZWQ= 86085 -UGV0cw== 86086 -IHN0b3JtZWQ= 86087 -IGplbA== 86088 -IFNTUA== 86089 -ZGF0YWdyaWQ= 86090 -IExhdQ== 86091 -dW5hcg== 86092 -dWxmaWxsZWQ= 86093 -RVJJTkc= 86094 -IFRyaW8= 86095 -2LHZiA== 86096 -Rm9yZWdyb3VuZENvbG9y 86097 -PW91dA== 86098 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi8K 86099 -IHZpZW50 86100 -IEFETQ== 86101 -X0Nvbm5lY3Rpb24= 86102 -LWNhbmNlbA== 86103 -KCcuJyk7Cg== 86104 -IHNhaWxz 86105 -IGVxdWl2YWxlbnRz 86106 -TmI= 86107 -IGZseWVycw== 86108 -IEdJUg== 86109 -a2VsaWc= 86110 -LXdhbGw= 86111 -LlJlcXVpcmVz 86112 -IGNvc2U= 86113 -IEFOQw== 86114 -IGphZGU= 86115 -IEFsZWM= 86116 -IGVuZHJlZ2lvbg== 86117 -IEVYVEk= 86118 -ZWRlcmU= 86119 -VGVycmFpbg== 86120 -U3BlY2lmaWNhdGlvbnM= 86121 -IFN3ZWVw 86122 -c2V0SXRlbQ== 86123 -IHNtaXJr 86124 -IHNjcmlwdGVk 86125 -W1N5c3RlbQ== 86126 -56eB 86127 -IHN5bmNlZA== 86128 -IHNxcg== 86129 -Z2V3YXRlcg== 86130 -IGpld2Vscw== 86131 -IGhkYw== 86132 -4KWN4KSw 86133 -z4Y= 86134 -w7xzc2VsZG9yZg== 86135 -bGllbg== 86136 -Qm9yZGVycw== 86137 -IEF0b21pY0ludGVnZXI= 86138 -IHBhcmFseXNpcw== 86139 -Q2xhc3NpZmljYXRpb24= 86140 -IGdsaWRl 86141 -IHVtcA== 86142 -IC8+fQ== 86143 -IHZlbmRpbmc= 86144 -4Li04LiZ 86145 -bm90aWY= 86146 -Jl8= 86147 -IEVtZXJnaW5n 86148 -YXRpY29u 86149 -IHByb3BhZ2F0ZWQ= 86150 -LW9yZGVycw== 86151 -YWdhcw== 86152 -dXJnZW50 86153 -KFRpbWVTcGFu 86154 -QUxDSEVNWQ== 86155 -L2Jvd2Vy 86156 -7IKw 86157 -LmJvb3N0 86158 -LmRlcGVuZGVuY2llcw== 86159 -LlN3aW5nQ29uc3RhbnRz 86160 -dW50bGV0 86161 -LmNoYXJz 86162 -LWNpZ2FyZXR0ZXM= 86163 -IE1vZHM= 86164 -ICAgICAJ 86165 -IGJyYXZlcnk= 86166 -IGNvdW50ZXJlZA== 86167 -cmVsdWRl 86168 -X21vYg== 86169 -QUlORUQ= 86170 -bmdvaW5n 86171 -IHVuZGVyZ3JhZA== 86172 -R2V0TWV0aG9k 86173 -RHVhbA== 86174 -X2pvdXJuYWw= 86175 -LE5v 86176 -IHNpZGVs 86177 -IExhcnNvbg== 86178 -KyIsIis= 86179 -IG5hcnJhdGlvbg== 86180 -IFN1YndheQ== 86181 -IExleGVy 86182 -IE5pbmc= 86183 -aW5kaWM= 86184 -dGhhbmU= 86185 -LlNJRw== 86186 -LWVhcnRo 86187 -IGJlcnJ5 86188 -IFRldWNob3M= 86189 -CUVudGl0eQ== 86190 -ZXJzcGVjdGl2ZQ== 86191 -Tm9z 86192 -IE93bmVk 86193 -QlVS 86194 -IGxpbmVubw== 86195 -IEZpamk= 86196 -R2V0SW50 86197 -U3RyaW5nUmVm 86198 -ICcmJw== 86199 -dWFkYQ== 86200 -LmNhcHRpb24= 86201 -YXBwTmFtZQ== 86202 -KG9mZg== 86203 -IHZlcnN0 86204 -IHR5cG8= 86205 -6ZyA6KaB 86206 -YXRlcmFuZ2VwaWNrZXI= 86207 -IHFlbXU= 86208 -IEdFTw== 86209 -X0Ns 86210 -LklU 86211 -IE51bmVz 86212 -W1o= 86213 -IENvbXBsZXRlbHk= 86214 -LkxpdmU= 86215 -IEphcw== 86216 -IHdlaXQ= 86217 -Y29zaXR5 86218 -IHBvbGljZW1lbg== 86219 -KHRhcmdldHM= 86220 -aXRsZWRCb3JkZXI= 86221 -IOinow== 86222 -LkdsaWRl 86223 -IGRlbW9uaWM= 86224 -SW50ZXJpb3I= 86225 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 86226 -IERvdGE= 86227 -IG9yYml0cw== 86228 -QU1Z 86229 -IFRyaW5pZGFk 86230 -aWN1bQ== 86231 -Lnph 86232 -IGdldEludA== 86233 -QXRsYW50YQ== 86234 -IGFtbmVzdHk= 86235 -IFJhaHVs 86236 -IF98 86237 -aGlybw== 86238 -IFRBS0U= 86239 -IGp1bWxhaA== 86240 -IEF1dG9tb2JpbGU= 86241 -4buP 86242 -d2hvc2U= 86243 -X1NBTVBM 86244 -UGF0aWVudHM= 86245 -INGC0LXQutGD0Yk= 86246 -LnN1YnNjcmlwdGlvbnM= 86247 -IE1lbnRpb24= 86248 -VG9Xb3JsZA== 86249 -aXBh 86250 -CU1lc3NhZ2VCb3g= 86251 -PEFwcGxpY2F0aW9uVXNlcg== 86252 -INil 86253 -ZmFicmlj 86254 -a2VsZXRhbA== 86255 -QmFyQnV0dG9u 86256 -IGFyY2hldHlwZQ== 86257 -aW5zdGFudA== 86258 -IGludGVybmFjaW9uYWw= 86259 -IFZveWFnZXI= 86260 -KHRvdWNo 86261 -IFZhbGs= 86262 -L01JVA== 86263 -IGNhdWw= 86264 -J0Nvbm5vcg== 86265 -KCIh 86266 -KE9Q 86267 -ZmFjdWx0eQ== 86268 -IEJhdG9u 86269 -IFZvbHVudGVlcnM= 86270 -dGFuaw== 86271 -X0JJTkRJTkc= 86272 -O2xpbmU= 86273 -IFZlcnNpb25z 86274 -WUxFUw== 86275 -IGplZXA= 86276 -KEVuY29kaW5n 86277 -IGdlb2xvZ2ljYWw= 86278 -TmljaA== 86279 -KHBkZg== 86280 -IGFuYWx5emVz 86281 -IGNhcHRpdmF0aW5n 86282 -IGhpem8= 86283 -Lm1kbA== 86284 -IGphcA== 86285 -IGZsaXBz 86286 -CWRm 86287 -IFBpZXQ= 86288 -IG5yb3dz 86289 -IGthbXU= 86290 -INCy0L7Qtw== 86291 -IHBydW5pbmc= 86292 -YWN1bGE= 86293 -IHRyYXZlbGxlcg== 86294 -U2hvb3Q= 86295 -LmVwc2lsb24= 86296 -IEZsZW1pbmc= 86297 -aWJ1cg== 86298 -b3BlcmF0ZQ== 86299 -aWdodGVy 86300 -IGJlZ3M= 86301 -IFdhbG51dA== 86302 -KFBhcnNlcg== 86303 -IHdpdGhkcmF3YWxz 86304 -aXNjb3BhbA== 86305 -IGJpbGxib2FyZA== 86306 -a2Vr 86307 -LW9wZW5pbmc= 86308 -IER1ZGU= 86309 -Y29uaQ== 86310 -eEVC 86311 -IGNhbG9y 86312 -YW1haGE= 86313 -LlRYVA== 86314 -RHJ5 86315 -IG1pc3Npb25hcmllcw== 86316 -X1ZlcnNpb24= 86317 -IG11bHRpbGluZQ== 86318 -4oCUd2U= 86319 -IGNvbXBvbmVudERpZFVwZGF0ZQ== 86320 -RmF2b3JpdGVz 86321 -aWdoYW0= 86322 -IGpvdXJuw6ll 86323 -IGFtdXNlZA== 86324 -IE9tbmk= 86325 -dGd0 86326 -IHdhaA== 86327 -ZXRpbmU= 86328 -IHBoYXNlZA== 86329 -IG9uU3RvcA== 86330 -Y3JlYXRpdmVjb21tb25z 86331 -U29waA== 86332 -IHVuYm9ybg== 86333 -PUU= 86334 -IEZlZEV4 86335 -bm9ybWFsbHk= 86336 -IGx5cg== 86337 -TWF0cml4TW9kZQ== 86338 -IHplaWdlbg== 86339 -QXRo 86340 -IEt1bQ== 86341 -w6RobGVu 86342 -LyI7Cgo= 86343 -IGRhbGxl 86344 -IGxhbmNl 86345 -IFN1aXRhYmxl 86346 -IGNvdW5zZWxvcnM= 86347 -5YWo6YOo 86348 -IGZhc3Rh 86349 -IGJsYXppbmc= 86350 -7KeE 86351 -L3R1dG9yaWFs 86352 -LnRjcA== 86353 -5pmv 86354 -TWFuYWdlckludGVyZmFjZQ== 86355 -IFNhbWFy 86356 -CWdsVW5pZm9ybQ== 86357 -IHByZXJlcXVpc2l0ZXM= 86358 -IGFudGljaXBhdGluZw== 86359 -cmFxdW8= 86360 -a3Nlbg== 86361 -TWFnbml0dWRl 86362 -dXRvbWF0aW9u 86363 -SGllcmFyY2h5 86364 -IGRldmlhdGlvbnM= 86365 -aW1ldA== 86366 -Q0NJ 86367 -PSgK 86368 -IGFudGxy 86369 -CWluaXRpYWw= 86370 -IFJlc29ydHM= 86371 -aG9tZXM= 86372 -CXBvb2w= 86373 -IG1hdMOp 86374 -P29wdGlvbg== 86375 -Om15c3Fs 86376 -KHV0Zg== 86377 -LlRhYkNvbnRyb2w= 86378 -PlRpdGxl 86379 -IEFkb3B0 86380 -LklzTWF0Y2g= 86381 -IGVudHJ1c3RlZA== 86382 -U3VzYW4= 86383 -c3dpbmc= 86384 -aW1hZ2VuZXM= 86385 -IHNlbGVjaW9u 86386 -IGFpZGluZw== 86387 -KFtdKg== 86388 -IHNldEZyYW1l 86389 -c3Bpcml0 86390 -L3Jzcw== 86391 -SXRhbGlj 86392 -IFByb3BlbEV4Y2VwdGlvbg== 86393 -IFRvbGw= 86394 -LkZpbmRHYW1lT2JqZWN0V2l0aFRhZw== 86395 -aW5hbnQ= 86396 -IHNlbGZpZXM= 86397 -XXxb 86398 -IGFwcGxpY2F0aW9uQ29udGV4dA== 86399 -aXhl 86400 -Y2Ri 86401 -ZWJi 86402 -IE92ZXJzZQ== 86403 -IHNxbENvbW1hbmQ= 86404 -SG9zdE5hbWU= 86405 -LWxhdW5jaA== 86406 -Umlzaw== 86407 -O3I= 86408 -LlNwYW4= 86409 -X0NJVFk= 86410 -X01B 86411 -LyIKCg== 86412 -UGF3bg== 86413 -IFllbHA= 86414 -QnVuZGxlT3JOaWw= 86415 -IG1heW9yw61h 86416 -U3RhY2tOYXZpZ2F0b3I= 86417 -ITsK 86418 -IHRodWdz 86419 -IEJhcm5ldHQ= 86420 -44O744O744O7Cgo= 86421 -IOqygA== 86422 -X0NPTlY= 86423 -IGJ1enppbmc= 86424 -a2V0ZXJhbmdhbg== 86425 -TWlsaXRhcnk= 86426 -d2VlZA== 86427 -IGRlbGltaXRlZA== 86428 -6LWE5rqQ 86429 -INCw0Lo= 86430 -X0hFTFBFUg== 86431 -IFJFQURZ 86432 -TG9vcGVy 86433 -KioqKi8K 86434 -IFRydWNrcw== 86435 -5Y67 86436 -X3BvZA== 86437 -T01BVElD 86438 -LWphdmE= 86439 -IHVuaWZ5 86440 -L0FyZWE= 86441 -ICcvJyk7Cg== 86442 -IEdhbWJsaW5n 86443 -LkhpdA== 86444 -IEZhcnJlbGw= 86445 -X2ZpdG5lc3M= 86446 -cmVjb21tZW5kZWQ= 86447 -emVuZA== 86448 -b2RpZQ== 86449 -X2JlYW0= 86450 -IHBsYWdl 86451 -bmRvbg== 86452 -LmFzc2VydGo= 86453 -IGdyYXRl 86454 -TWVhc3VyZWQ= 86455 -LmNlbnRyYWw= 86456 -Z2VzdHVyZQ== 86457 -IEdsb2JhbEtleQ== 86458 -cHl4 86459 -IE5lY2tsYWNl 86460 -5Y2O 86461 -LkFkZENvbHVtbg== 86462 -IFJ1ZGQ= 86463 -IFByZXNieXRlcmlhbg== 86464 -dW5kbGVy 86465 -IyFb 86466 -X2xhaGly 86467 -KCk9PSI= 86468 -QWNjZXNzaWJpbGl0eQ== 86469 -LXRyYWluaW5n 86470 -IFRob3U= 86471 -X1BJWA== 86472 -X1RSWQ== 86473 -PEo= 86474 -xrDGoW5n 86475 -bHVjaw== 86476 -X01BWElNVU0= 86477 -IHRoYXc= 86478 -VW5pZmllZA== 86479 -PkNvbnRhY3Q= 86480 -LVByZXNpZGVudA== 86481 -LXBhcnNl 86482 -IFBpY2tlcg== 86483 -TWFyY28= 86484 -dHJz 86485 -zrQ= 86486 -LiQu 86487 -X01FU0g= 86488 -IHNhZ3Rl 86489 -Kz0n 86490 -0K8= 86491 -KHBhcmNlbA== 86492 -aXZvcnM= 86493 -IGRpdmVydGVk 86494 -QUdBSU4= 86495 -IG5lc3M= 86496 -IHZhbGxleXM= 86497 -IC4uLig= 86498 -IEVRVUk= 86499 -IE91dHM= 86500 -IERlbW9uc3Ry 86501 -RGV0YWxsZQ== 86502 -IOu2gA== 86503 -UG9pbnRYWVo= 86504 -LmVwcw== 86505 -IHN5bm9ueW1z 86506 -ID09KA== 86507 -4oCcWWVz 86508 -J3V0aWxpc2F0ZXVy 86509 -TmFtaW5n 86510 -TEVW 86511 -cHJvdG9jb2xz 86512 -IOyb 86513 -IGdldFVzZXJuYW1l 86514 -LXZhcg== 86515 -X210eA== 86516 -IHNwZWN1bGFy 86517 -IG5vdGFz 86518 -SG9yaXpvbnRhbEFsaWdubWVudA== 86519 -IEJheWVy 86520 -c3Vz 86521 -ICAgIAkJCg== 86522 -IFNoYWNr 86523 -cmVzaGVy 86524 -IGltbWF0dXJl 86525 -YnJhY2h0 86526 -SVNDTw== 86527 -LmNyZWRpdA== 86528 -IHZpbmVz 86529 -X0xQ 86530 -RUVERUQ= 86531 -IFNjYXJib3JvdWdo 86532 -w6FudA== 86533 -KT09Jw== 86534 -CWRlbHRh 86535 -X0NPTE9SUw== 86536 -LkN1c3RvbUJ1dHRvbg== 86537 -IGFmaXJt 86538 -IEppbmc= 86539 -UGFybXM= 86540 -Y2VudGVycw== 86541 -LT5fX18= 86542 -IExETA== 86543 -LWNvbnRyaWI= 86544 -IERyZXNkZW4= 86545 -IFBpeGVscw== 86546 -ICIiIiIsCg== 86547 -TEVUVEU= 86548 -eEJF 86549 -IEh1c3Q= 86550 -IEV4ZWN1dGlvbkNvbnRleHQ= 86551 -IEJ1ZmZldHQ= 86552 -Y2xhbXA= 86553 -LkFydGljbGU= 86554 -IFJhdGg= 86555 -IFBleXRvbg== 86556 -IExPV0VS 86557 -b29rZQ== 86558 -IHRpZGFs 86559 -IHVuaGVhcmQ= 86560 -IFNoYWxs 86561 -IGJvbWJhcmQ= 86562 -YW5vdmE= 86563 -W21hc2s= 86564 -KGNyZWRlbnRpYWxz 86565 -IEV1cm9z 86566 -IGJyYW5jaGluZw== 86567 -IHN0cm9uZ2hvbGQ= 86568 -IGNpdmlsaXphdGlvbnM= 86569 -LWNvbm5lY3Q= 86570 -IExTVE0= 86571 -LW1vdmluZw== 86572 -IHV0ZW4= 86573 -Y3Jhc3Q= 86574 -X0RJU1A= 86575 -IENvbnRyb2xsZXJz 86576 -dXBl 86577 -LnBlbg== 86578 -IGRlc3Nh 86579 -IGRpZsOtY2ls 86580 -dWl0YWJsZQ== 86581 -b2ZpcmU= 86582 -W2NoaWxk 86583 -UkVGRVJFTkNFUw== 86584 -IGRlY2VpdA== 86585 -IFVyZw== 86586 -PEVkZ2U= 86587 -IGRlc2k= 86588 -IEJPVEg= 86589 -ICcpJzsK 86590 -dHlwZU5hbWU= 86591 -Q29tbWFuZEV2ZW50 86592 -d2hlcmVJbg== 86593 -KG9wdGltaXplcg== 86594 -IHLDqWFsaXM= 86595 -IG9taW5vdXM= 86596 -IEJyYWNrZXQ= 86597 -IGRhdGVTdHJpbmc= 86598 -IHNpbmdseQ== 86599 -KEpGcmFtZQ== 86600 -4oCZVA== 86601 -ZXNsaW50 86602 -KGhlcm8= 86603 -IE1hcmE= 86604 -IGNhdGNoeQ== 86605 -LGNhbGxiYWNr 86606 -IGN0eXBl 86607 -cHJlc2V0 86608 -CWdsZnc= 86609 -0LXRiQ== 86610 -aGs= 86611 -IHRpdGFu 86612 -QWNlcHRhcg== 86613 -44Gh44Gv 86614 -X2Fzc2lnbmVk 86615 -X2VyYXNl 86616 -IGluZmFuY3k= 86617 -UmV2aWV3ZXI= 86618 -IFJlY29yZGVy 86619 -IHNjbQ== 86620 -IEJpZ2dlc3Q= 86621 -IEdvYQ== 86622 -CVND 86623 -X0xvY2F0aW9u 86624 -X29yaQ== 86625 -a2ls 86626 -cmVuZGU= 86627 -IG1hcnpv 86628 -U3RyaW5nVXRpbA== 86629 -0YPRidC10YHRgtCy 86630 -IEhvd2U= 86631 -xrDhu51p 86632 -Zm9pcw== 86633 -WE1MRWxlbWVudA== 86634 -IGRlcmVjaG9z 86635 -IGR1bmc= 86636 -IFdhaw== 86637 -IEdhdw== 86638 -fVxc 86639 -ISIpOw== 86640 -IEpvaGFubmVzYnVyZw== 86641 -IHN1Ym1hcmluZXM= 86642 -IGFjY29s 86643 -IGZvc3RlcmluZw== 86644 -LgoKCgoKCgoKCgoKCg== 86645 -Lk9wZXJhdG9y 86646 -IG51b3Zh 86647 -IHRyYWplY3Rvcmllcw== 86648 -LnNjaGVkdWxlcnM= 86649 -IEZvbGxvd2Vycw== 86650 -IEFuZGVyc2Vu 86651 -IFBlZ2d5 86652 -LmZyZQ== 86653 -xLFjxLE= 86654 -IGt2cA== 86655 -Y29i 86656 -LWxlbg== 86657 -IG1haWxz 86658 -IGFjY3I= 86659 -IEpBVkE= 86660 -IGFkbWluaXN0ZXJpbmc= 86661 -RGVmYXVsdENlbGxTdHlsZQ== 86662 -IGNsaWNrYWJsZQ== 86663 -IEphY2tldHM= 86664 -O2Rpc3BsYXk= 86665 -IGJyZWFkY3J1bWJz 86666 -Y2hhbA== 86667 -Oic7Cg== 86668 -IEhvdmVy 86669 -dWNjaGluaQ== 86670 -IHRlYw== 86671 -IHN0b3B3YXRjaA== 86672 -X1JlbGVhc2U= 86673 -TWF5b3I= 86674 -4Z62 86675 -IFlhbmtlZQ== 86676 -Y2huZXI= 86677 -QXJ0aWZhY3Q= 86678 -LmJhbm5lcg== 86679 -IGtm 86680 -X3N0dWR5 86681 -Zm92 86682 -IE1lZXRpbmdz 86683 -w7Zt 86684 -IGluanVyaW5n 86685 -L2RvY3VtZW50YXRpb24= 86686 -QkNN 86687 -c3R5bA== 86688 -CXJi 86689 -IG9yaWdpbmFscw== 86690 -IGZsZXJl 86691 -IFRlcnJhcmlh 86692 -dG9rZW5pemVy 86693 -LWxpdGVy 86694 -Jyk7Ig== 86695 -IHBldGl0cw== 86696 -IEJidw== 86697 -IFRoaWVm 86698 -VUlMVElO 86699 -Uk9VVA== 86700 -IHNudWc= 86701 -Pj4p 86702 -LW5pbmU= 86703 -IH1dOwoK 86704 -IEJlbGxldg== 86705 -IGVsw6k= 86706 -IHl5bg== 86707 -eW5hbW8= 86708 -Z2xlcw== 86709 -IHNwZWQ= 86710 -LkJVVFRPTg== 86711 -IGRpc3BlcnNpb24= 86712 -b3VibGVz 86713 -IG5vdmVsbGVy 86714 -Il0uIg== 86715 -IHByaWVzdGhvb2Q= 86716 -ICIiKQoK 86717 -CWd1aQ== 86718 -LWluYw== 86719 -WG1sTm9kZQ== 86720 -IHN0dWRz 86721 -LklzQWN0aXZl 86722 -IHRyw6Q= 86723 -IG9yZGFpbmVk 86724 -IEJ5dGVBcnJheUlucHV0U3RyZWFt 86725 -IHJlcXVlc3RCb2R5 86726 -IFJUUA== 86727 -UkVTVUxUUw== 86728 -KGNvbGw= 86729 -IHJlbG9hZGluZw== 86730 -Lk5hdmlnYXRvcg== 86731 -X2NvdW50ZXJz 86732 -IGJ1ZGRpbmc= 86733 -IGxpY2Vuc2Vl 86734 -b2xvZ2k= 86735 -IHPhuqNu 86736 -IEtpcw== 86737 -IEZsYXR0ZW4= 86738 -X3ByaQ== 86739 -IGFwcHJvcHJpYXRpb24= 86740 -6K+E6K66 86741 -X1JTUA== 86742 -Y29tYmF0 86743 -X1BH 86744 -IGhpc3RvZ3JhbXM= 86745 -ZHE= 86746 -RW50ZXJwcmlzZQ== 86747 -IE5PQUE= 86748 -IFNwZWVkd2F5 86749 -IGJhZ2k= 86750 -IEJld2VydA== 86751 -RmxvYXRpbmc= 86752 -IEtpbWJlcmx5 86753 -UHJvc2Vj 86754 -SmltbXk= 86755 -IEVsaWFz 86756 -IGFyYml0cmFyaWx5 86757 -IOS9v+eUqA== 86758 -IENvdW50cw== 86759 -dXN0ZQ== 86760 -Rmlyc3RDaGlsZA== 86761 -IENsZWFucw== 86762 -LnB1cmNoYXNl 86763 -IGludGVycG9sYXRlZA== 86764 -IGJ1aWxkdXA= 86765 -X1NURU5DSUw= 86766 -RWd5cHQ= 86767 -IGF1cmU= 86768 -LnRydXRo 86769 -ZmVvZg== 86770 -IEdpbQ== 86771 -b2NhY2hl 86772 -IFV0dGFy 86773 -X0NPTVBMRVRFRA== 86774 -U2Vlbg== 86775 -IE5hcG9saQ== 86776 -KGRt 86777 -IGdyaXR0eQ== 86778 -LmVudGVycHJpc2U= 86779 -Y29uZXhhbw== 86780 -IGdhdGhlcnM= 86781 -IHNldFNlYXJjaA== 86782 -IENsaWZmb3Jk 86783 -IFNuYXBl 86784 -IFNhbHZhdGlvbg== 86785 -TG9naW5Gb3Jt 86786 -Q3JpdGljYWxTZWN0aW9u 86787 -LnVzZXJkZXRhaWxz 86788 -IHJlcGFpbnQ= 86789 -44GC44KK44GM44Go44GG 86790 -SHVudGVy 86791 -WmVu 86792 -VGlueQ== 86793 -bWxhbmQ= 86794 -ZXJ0aWw= 86795 -CWJ1ZmY= 86796 -X09mZnNldA== 86797 -IHNtZWxsZWQ= 86798 -Uml2ZXI= 86799 -LXRvcGlj 86800 -IGFjb21w 86801 -IFJvdXRlU2VydmljZVByb3ZpZGVy 86802 -IDwr 86803 -b21icw== 86804 -IENvb3BlcmF0aXZl 86805 -IHNldWxl 86806 -IGFpbWU= 86807 -c2hvdWxkUmVjZWl2ZQ== 86808 -SG9uZw== 86809 -IG9hc2lz 86810 -IEdlbWluaQ== 86811 -cmFwaWQ= 86812 -RHVw 86813 -KFF0R3Vp 86814 -b2RvbnQ= 86815 -LWdudQ== 86816 -IFNlbGVuaXVt 86817 -Jyk/Pjwv 86818 -IE5vcGU= 86819 -R3JlYXRlclRoYW4= 86820 -Lk9ic2VydmVy 86821 -IEFwcHJvcHJp 86822 -IExvbmVseQ== 86823 -IGhhaXJjdXQ= 86824 -IGFsbGVyZGluZ3M= 86825 -w7NwZXo= 86826 -esWR 86827 -IHNsdW1w 86828 -IEdpbnM= 86829 -IGdpb3JuaQ== 86830 -IHBhcGVyYmFjaw== 86831 -LkZpbGVSZWFkZXI= 86832 -ZGFm 86833 -Y3JlZHM= 86834 -dHlwaW5ncw== 86835 -ZGVoeWRl 86836 -Y29pbA== 86837 -U291dGhlcm4= 86838 -IG1vdXNlQ2xpY2tlZA== 86839 -emVpY2huZXQ= 86840 -dXNlclJlcG9zaXRvcnk= 86841 -RGVzdHJveWVk 86842 -aW50ZXJuZXQ= 86843 -IEVpZA== 86844 -IGxpbmtlcg== 86845 -4oCZQg== 86846 -IHNsYXVnaHRlcmVk 86847 -IFBlcnI= 86848 -CVJ1bnRpbWVPYmplY3Q= 86849 -c2FpZGE= 86850 -IHBhZ2VDb3VudA== 86851 -IFJhbmRvbHBo 86852 -IEpOSUVudg== 86853 -X3N1cGVydXNlcg== 86854 -LWRpcmVjdGVk 86855 -IElEYg== 86856 -IEJlcm5hcmRpbm8= 86857 -IE5pbnRo 86858 -IEFsZ29yaXRobXM= 86859 -YmRi 86860 -QHRlc3RhYmxl 86861 -LmFybQ== 86862 -YmVsbGlvbg== 86863 -KHNpZA== 86864 -IGJyaWVmZWQ= 86865 -4pWX 86866 -6YWN572u 86867 -IFVtYQ== 86868 -IEluZGljZXM= 86869 -IEJ1Y2NhbmU= 86870 -IGF5YW50 86871 -RnJlZWRvbQ== 86872 -IFl1cmk= 86873 -ZXRzaw== 86874 -X1Bo 86875 -IGl0YWxpYQ== 86876 -Y2xvc2luZw== 86877 -IHdyaXN0cw== 86878 -ICp9 86879 -c2VjdXRpdmU= 86880 -RW52aWFy 86881 -cmFpdGg= 86882 -IEhhd3Ro 86883 -15M= 86884 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgo= 86885 -cGFnZVRpdGxl 86886 -IGRoY3A= 86887 -IOyLpO2WiQ== 86888 -d2lzaGxpc3Q= 86889 -IGJsYW1lcw== 86890 -IHNpZGw= 86891 -dWRkZWQ= 86892 -IGNvbnRyb3ZlcnNpZXM= 86893 -6I8= 86894 -KHVzZXJEYXRh 86895 -IGxpbnNwYWNl 86896 -IERpZmZlcmVuY2Vz 86897 -X2RlcG9zaXQ= 86898 -REVUQUlM 86899 -LmRlY2s= 86900 -IGNvbnRpbnV1bQ== 86901 -IHNhY3JhbQ== 86902 -b21pdGU= 86903 -IG5mbA== 86904 -Q3Vt 86905 -IHNvZg== 86906 -IGV2aWxz 86907 -IGVudGlkYWQ= 86908 -CXNvY2s= 86909 -IExlbW1h 86910 -LlNoaXA= 86911 -IHppZw== 86912 -VGVsZWZvbmU= 86913 -SURFUw== 86914 -IE51bWVyb3Vz 86915 -Lm1ldHJpYw== 86916 -aW5zbg== 86917 -IGNvcHlyaWdodHM= 86918 -IGNvbXBsaWNhdGlvbg== 86919 -IFVSTFNlc3Npb24= 86920 -IGRpcHBpbmc= 86921 -IGNx 86922 -IEJ1c3R5 86923 -cmVsYXRpb25zaGlwcw== 86924 -IENvcnZldHRl 86925 -U3VtbW9u 86926 -ZXZlbnROYW1l 86927 -SXNzdWVz 86928 -IGlycmVzaXN0aWJsZQ== 86929 -IGdyaXM= 86930 -Q0FTQ0FERQ== 86931 -IHBhdXNlcw== 86932 -IGxlZGdl 86933 -X0dQ 86934 -LkltcA== 86935 -IG9yZGVyYnk= 86936 -IE9yZ2FuaXplcg== 86937 -IEdyZWVud2ljaA== 86938 -T2Fr 86939 -LW1lbWJlcnM= 86940 -IFdlYkdM 86941 -IGdhbW0= 86942 -bW9kdWxlSWQ= 86943 -IGZ1bGxQYXRo 86944 -bG9nZW4= 86945 -KGV2ZW50TmFtZQ== 86946 -KCIuIik7Cg== 86947 -IGtyaXN0 86948 -IGNsaWZmcw== 86949 -IFBlcmNlcHRpb24= 86950 -RVRJTkc= 86951 -IGzhuqFp 86952 -IGludGVydg== 86953 -IG9wcG9ydHVu 86954 -IEp1ZGdlcw== 86955 -IENvbWJpbmF0aW9u 86956 -Y29udGludWVk 86957 -Y29ubw== 86958 -LmRyYXdSZWN0 86959 -LkNvbXBvc2U= 86960 -IHNpZ3VpZW50ZXM= 86961 -IER1ZmZ5 86962 -KGVuY29kaW5n 86963 -IFZ1bGthbg== 86964 -IEdlcnI= 86965 -IHBhcmZhaXQ= 86966 -KHl5 86967 -X1RIQU4= 86968 -IGdldFNlcnZpY2U= 86969 -X09SRA== 86970 -LGVw 86971 -Z3JhcGhpYw== 86972 -IFF1ZXJpZXM= 86973 -IHBhcnRpY3VsYXJz 86974 -IEhhdmFuYQ== 86975 -PW8= 86976 -ZmFucw== 86977 -IHVuaWxhdGVyYWw= 86978 -IFJGSUQ= 86979 -Q29tcGF0aWJpbGl0eQ== 86980 -c3RyYW5k 86981 -IHdha3R1 86982 -IHF1YWxpZGFkZQ== 86983 -UHJvcGVydHlQYXJhbXM= 86984 -cmV0ZW4= 86985 -KGhvc3RuYW1l 86986 -X0NBUg== 86987 -IHdpZGVuZWQ= 86988 -IFhwZXJpYQ== 86989 -cG9sbG8= 86990 -QWJvcnQ= 86991 -ISEpCg== 86992 -IFdhZw== 86993 -LS0r 86994 -INGC0YA= 86995 -IFJlY3Vyc2l2ZQ== 86996 -IGFubmU= 86997 -IEdhbWVwbGF5 86998 -PENsaWVudA== 86999 -LlVzYWdl 87000 -IElTU1VF 87001 -IGpkYmM= 87002 -aXNvcnk= 87003 -X21hY3Jvcw== 87004 -cGlja2xl 87005 -LmdhbWVzZXJ2ZXI= 87006 -IHR2Yg== 87007 -0YLRiw== 87008 -Lk9QRU4= 87009 -IHByZWRldGVybWluZWQ= 87010 -IHNpcmU= 87011 -CQkJDQoJCQkNCg== 87012 -aXNjcmltaW5hdGlvbg== 87013 -IHJlcGVhbGVk 87014 -IGNvbmplY3Q= 87015 -IFByZWNvbmRpdGlvbnM= 87016 -IHRpbHRlZA== 87017 -IGlub2M= 87018 -IGV1cm9wZWFu 87019 -YWJk 87020 -X0RFTEVURUQ= 87021 -IC0s 87022 -4oCTYW5k 87023 -QEZYTUw= 87024 -ICldCg== 87025 -UklORw== 87026 -IGFsaXF1YQ== 87027 -IGdydWVzb21l 87028 -IEluY2hlcw== 87029 -UGxheWVk 87030 -KGNvbmZpcm0= 87031 -IE5WSUM= 87032 -X1RvdGFs 87033 -aXNhcw== 87034 -IE9uaW9u 87035 -IHNlY29uZG8= 87036 -IEdldFVzZXI= 87037 -XFVybA== 87038 -X2Fic3RyYWN0 87039 -IGRldmV6 87040 -IGN1cGJvYXJk 87041 -dGV4dHM= 87042 -IElzbGVz 87043 -X01BVEg= 87044 -U2tpcHBpbmc= 87045 -X2Nvc3Rz 87046 -PW91dHB1dA== 87047 -aWJpbGk= 87048 -IGtudWxs 87049 -X2NvZWZmcw== 87050 -X2F0dGVtcHQ= 87051 -CVJ1bg== 87052 -Z2VuZGVu 87053 -cnVwdGVk 87054 -IHNvYXJlZA== 87055 -X2hz 87056 -IGFkb3B0cw== 87057 -X01PRElGSUVE 87058 -XEZhY3Rvcmllcw== 87059 -IFN3ZWF0 87060 -IGRva3VtZW50 87061 -IFRlbGVzY29wZQ== 87062 -IEZpeGVz 87063 -b3JxdWU= 87064 -LkNoYXJ0aW5n 87065 -X0RBQw== 87066 -IHNlY3JldGlvbg== 87067 -IHJoZXRvcmljYWw= 87068 -UGVyZmls 87069 -IG3DtmNodGVu 87070 -LCcs 87071 -IHZpZXdQYWdlcg== 87072 -QlVZ 87073 -IG9uRm9jdXM= 87074 -b3NhbHM= 87075 -IGJpc2N1aXRz 87076 -IHZib3g= 87077 -IGZvcmNlZnVsbHk= 87078 -TmludGVuZG8= 87079 -IHbDoWw= 87080 -IGNsYW5z 87081 -ZnJvZw== 87082 -IGJvcmRlclRvcA== 87083 -QnJpZWY= 87084 -LkJvcmRlckZhY3Rvcnk= 87085 -LXNlcnZpbmc= 87086 -IHF1b3RhdGlvbnM= 87087 -IEdhcm5lcg== 87088 -IEFsbGV5 87089 -Ij8+Cg== 87090 -KHNjYW5uZXI= 87091 -IGVudGFpbA== 87092 -IC8vPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ== 87093 -KGA8 87094 -LmRlc2NyaXBjaW9u 87095 -X0J5 87096 -IOyalA== 87097 -IHBha2lzdGFu 87098 -ZWxobw== 87099 -RW5naW5lZXJpbmc= 87100 -IGJvb24= 87101 -IExvb3Nl 87102 -aWVyZ2U= 87103 -U2VuYXRl 87104 -IExZ 87105 -cmVzcG9uc2VPYmplY3Q= 87106 -aW9yZQ== 87107 -w6FnZW5lcw== 87108 -IOS4jQ== 87109 -IGFkZEFjdGlvbg== 87110 -IE1BQ0hJTkU= 87111 -YW5na2Fu 87112 -X21p 87113 -X0FSUg== 87114 -TGl0ZXI= 87115 -T0xG 87116 -IHN1cHBlcg== 87117 -IHBhdGhNYXRjaA== 87118 -IE9ycg== 87119 -w61k 87120 -KGZpbHRlcmVk 87121 -IGF1dGhUb2tlbg== 87122 -IOKEnQ== 87123 -LTwv 87124 -KHRlbnNvcg== 87125 -IHJldm9sdmluZw== 87126 -IGluaWNpYXI= 87127 -IFNjaHdhcno= 87128 -ZGVmZ3JvdXA= 87129 -Y29sdW1uTmFtZQ== 87130 -X3RyYWplY3Rvcnk= 87131 -4LmE4Lih 87132 -ZWdhc3Vz 87133 -IOydtOumhA== 87134 -IGVhdGVy 87135 -IHVuZGVyZXN0aW1hdGVk 87136 -IGJ0Yw== 87137 -IOyEoO2DnQ== 87138 -ZW5hZGU= 87139 -IFNFWFA= 87140 -ZW1vdXRo 87141 -T01FVFJZ 87142 -ZW50ZXJlZA== 87143 -LnBob25lTnVtYmVy 87144 -IFZvYw== 87145 -IGV4Y2Vzc2l2ZWx5 87146 -IENBVEVHT1JZ 87147 -X1VQREFURUQ= 87148 -IG1vbmFyY2h5 87149 -YXJjaHM= 87150 -IGNhdmVhdA== 87151 -d2lucw== 87152 -IHBsYXlib29r 87153 -c2hhZGU= 87154 -IHNldFVzZXJuYW1l 87155 -IGFjY3VzZXM= 87156 -IG1vxbxsaQ== 87157 -IGxvcnNxdWU= 87158 -IGFqdWQ= 87159 -aGVhcg== 87160 -IHBzeWNvcGc= 87161 -KEVD 87162 -IG1lbGFuY2g= 87163 -dGhyb2F0 87164 -bmlo 87165 -V09PRA== 87166 -IHZvbHRz 87167 -X05FRUQ= 87168 -X3doaWxl 87169 -IFJpZGVycw== 87170 -16I= 87171 -IC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4= 87172 -TmV0TWVzc2FnZQ== 87173 -TW9kaWZpY2Fy 87174 -LnNlc3M= 87175 -KCIiKSw= 87176 -6Kmx 87177 -IHByYWlzZXM= 87178 -IGxjbQ== 87179 -IG1ha2VzaGlmdA== 87180 -IE5PVEhJTkc= 87181 -IEFydGlmYWN0 87182 -d2lq 87183 -dHlwaWNhbGx5 87184 -KCde 87185 -PGs= 87186 -xJlraQ== 87187 -INC+0YLQv9GA0LDQsg== 87188 -IOE= 87189 -IGRlZlN0eWxlQXR0cg== 87190 -aW5jZXJlbHk= 87191 -w6lzdA== 87192 -SW5UaGU= 87193 -c3RpbWU= 87194 -IGZyYWdtZW50ZWQ= 87195 -IGZyeWluZw== 87196 -Z3JpbQ== 87197 -ZmllbGRuYW1l 87198 -IGNyb3NzaW5ncw== 87199 -IGFtbw== 87200 -X09wdGlvbnM= 87201 -IGhhaXJlZA== 87202 -L3dhaXQ= 87203 -IHBhcmNobWVudA== 87204 -IGNyZWF0ZUVsZW1lbnQ= 87205 -SHR0cFN0YXR1cw== 87206 -IGVya2zDpA== 87207 -aXp6YXppb25l 87208 -dGh1bWJuYWlscw== 87209 -bG92YWs= 87210 -IGJhbmdpbmc= 87211 -IHVuaW1hZ2lu 87212 -IE92ZW4= 87213 -KEF1ZGlv 87214 -YXBzdWxhdGlvbg== 87215 -IHJhbXBz 87216 -55Wq 87217 -IFdvb2R3YXJk 87218 -6Zeu6aKY 87219 -cm9ncmFt 87220 -0YDRg9C/0L8= 87221 -IFdvcnNoaXA= 87222 -IHN0YWQ= 87223 -IG5lZg== 87224 -IEphdW5l 87225 -YnV6eg== 87226 -YWx1cw== 87227 -T05ET04= 87228 -LXN1 87229 -IG91dHBhdGllbnQ= 87230 -amFj 87231 -RVNQTg== 87232 -w6ZsbGFuZA== 87233 -bXlw 87234 -IHNob3dyb29t 87235 -TW9udHNlcnJhdA== 87236 -LmdldERyYXdhYmxl 87237 -w6l0aWNv 87238 -IHbDoG8= 87239 -SUJD 87240 -RXhwZXJ0cw== 87241 -TWJwcw== 87242 -Ij4j 87243 -IG5vcnRoZWFzdGVybg== 87244 -IE1lag== 87245 -KG1pbGxpc2Vjb25kcw== 87246 -4oCUYWxs 87247 -LXJlYWNoaW5n 87248 -CXJlcGx5 87249 -P3R5cGU= 87250 -IGNydXo= 87251 -ID48Pw== 87252 -LkZpbmRBc3luYw== 87253 -KGNpcmNsZQ== 87254 -IFNoaW5l 87255 -IE1hdmVyaWNrcw== 87256 -IHNhZmV6b25l 87257 -IExhemFy 87258 -IGRpc3RpbmN0aW9ucw== 87259 -LWZlZWQ= 87260 -LnNldENvZGU= 87261 -4KSq 87262 -IHTDqWM= 87263 -IHNlcmFpdA== 87264 -IE1JQ1JP 87265 -IENvbnN1bXB0aW9u 87266 -Xm4= 87267 -LmZyb21GdW5jdGlvbg== 87268 -IFJ1cGVydA== 87269 -IGhhcmFzc2luZw== 87270 -LUNv 87271 -IHRpaw== 87272 -IFN2ZW5z 87273 -LkltYWdlQWxpZ24= 87274 -X3doaXRlc3BhY2U= 87275 -IGtpY2tlcg== 87276 -IGNhZGFzdHI= 87277 -Q2V0dGU= 87278 -X25vdGlmaWVy 87279 -IEZBRw== 87280 -IHByaW1hbA== 87281 -IGhvbW9nZW5lb3Vz 87282 -IGFzdHJvbm9taWNhbA== 87283 -IEJ1cnI= 87284 -LkNvcHlUbw== 87285 -Z3JhcGhz 87286 -aXR0bw== 87287 -T1NI 87288 -IHNob3dBbGVydA== 87289 -YW50cm8= 87290 -ImRlZmF1bHQ= 87291 -ZW1waGFzaXM= 87292 -V2Vp 87293 -b3V0Y29tZQ== 87294 -IGFrdQ== 87295 -IGNhbXBhaWduZWQ= 87296 -KSI7Cgo= 87297 -IHJlY2lwcm9jYWw= 87298 -IFJveWFsZQ== 87299 -ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM= 87300 -LlRJTUU= 87301 -IDwq 87302 -T2Zmc2V0VGFibGU= 87303 -Y29tcG91bmQ= 87304 -d2FpdEZvcg== 87305 -dWVnb3M= 87306 -LnN0cmluZ1ZhbHVl 87307 -X1NDSEVE 87308 -IGZhdHQ= 87309 -wqDCoMKgwqDCoMKgwqA= 87310 -LmRpc2s= 87311 -IHdhcnBlZA== 87312 -IGNyaXRpcXVlcw== 87313 -PycKCg== 87314 -KHNraWxs 87315 -IG1vZGVyYXRlZA== 87316 -X2VsZW1z 87317 -S2V5TGlzdGVuZXI= 87318 -IHNlYXNvbmluZw== 87319 -IHBvdXJxdW9p 87320 -X0ZE 87321 -cHJk 87322 -aHlh 87323 -Ij7Dlzwv 87324 -IG5vdXZlYXV4 87325 -IGdpdmVhd2F5cw== 87326 -5oql6YGT 87327 -TWFpbk1lbnU= 87328 -Oy8q 87329 -IEdyb24= 87330 -cXVpdm9z 87331 -Ow0KDQoNCg0K 87332 -IGluZmx1ZW5jZXJz 87333 -KFRJTQ== 87334 -U2hhcmVkUHRy 87335 -IGRpYWxvZ3M= 87336 -KioqKiovCg== 87337 -LkF0b21pYw== 87338 -IE1vcnNl 87339 -IHBjYg== 87340 -IEFQQw== 87341 -LkltbXV0YWJsZQ== 87342 -IHJlc2l6aW5n 87343 -IEx1bXB1cg== 87344 -IEh1bWFuaXRpZXM= 87345 -X3NvbHZl 87346 -X2h1bWFu 87347 -ZXR5bA== 87348 -IEh1cnQ= 87349 -IEVzdGFibGlzaGVk 87350 -Y2xhcmVk 87351 -IGNvbXBhcnRtZW50cw== 87352 -QmVhbQ== 87353 -X1JN 87354 -LmZhbHNl 87355 -KEdyaWQ= 87356 -IFFTaXpl 87357 -X2ZsZw== 87358 -aXN0aWNh 87359 -PkxvZ2lu 87360 -OlVJQnV0dG9uVHlwZQ== 87361 -IEV4aXRpbmc= 87362 -Y2xhcw== 87363 -IGFyc2Vu 87364 -KG1ldHJpYw== 87365 -cm93c2luZw== 87366 -cXVlcnlTZWxlY3Rvcg== 87367 -X0ZSSUVORA== 87368 -LWlv 87369 -IGNvbmZpc2NhdGVk 87370 -IGRlZmlhbnQ= 87371 -IE1PVE9S 87372 -cmVndW50YQ== 87373 -IE1vcnJvdw== 87374 -IEJlcnM= 87375 -Q3JhaWc= 87376 -IENQQQ== 87377 -IHNleGtvbnRha3Rl 87378 -IHNhbW1lbg== 87379 -L0F1dGg= 87380 -LkxpYg== 87381 -Y3JhcGVy 87382 -aWNlbWFpbA== 87383 -Y3JhdGNo 87384 -IFdpcmVk 87385 -IGFkdmVydGlzZXI= 87386 -IGdldENsaWVudA== 87387 -IHJlc3BvbnNpYmx5 87388 -CVVPYmplY3Q= 87389 -LnNldFJvdGF0aW9u 87390 -LkNvdW50ZXI= 87391 -X0hPVVI= 87392 -VGVzdENhdGVnb3J5 87393 -IGhpbmRzaWdodA== 87394 -XGNvbnRyb2xsZXJz 87395 -d2FsbHM= 87396 -LnNldE1heGltdW0= 87397 -IHB1YmVydHk= 87398 -X3RlYW1z 87399 -X01PREFM 87400 -LkNP 87401 -IGJhZGFzcw== 87402 -KSddLAo= 87403 -w7pzcXVlZGE= 87404 -aXJ1dA== 87405 -Q2hlbHNlYQ== 87406 -LnRyYW5zZm9ybXM= 87407 -IGNhcGl0YWxpc3Rz 87408 -TWFyY2E= 87409 -IEFyeQ== 87410 -LWNvZGVk 87411 -546v 87412 -VVJFRA== 87413 -PFRyYW5zYWN0aW9u 87414 -IFBhcmxpYW1lbnRhcnk= 87415 -KSRf 87416 -IHN1YnRseQ== 87417 -IHNpbGt5 87418 -IERpcnQ= 87419 -IHB1enpsZWQ= 87420 -fScpOwo= 87421 -cXVlc3Rz 87422 -Rm9vdGJhbGw= 87423 -IENvbmZpZGVuY2U= 87424 -dXp1 87425 -YnVsYW4= 87426 -IGh1bW1pbmc= 87427 -bW91c2VlbnRlcg== 87428 -UmV0ZW50aW9u 87429 -IHNkbA== 87430 -b2tlZGV4 87431 -JywnPScsJA== 87432 -IEt1YWxh 87433 -U0FN 87434 -IHRyYW5zZm9ybWF0aXZl 87435 -UEtH 87436 -aWxsdXM= 87437 -IHJvb3Rpbmc= 87438 -IFdpdG5lc3Nlcw== 87439 -IFJhamFzdGhhbg== 87440 -5byg 87441 -LWFkZGVk 87442 -IFRlcnJpdG9yaWVz 87443 -KHNxdWFyZQ== 87444 -cmFiYml0 87445 -X1Jlc291cmNl 87446 -6ZaL 87447 -4LiT 87448 -IHdpbm5pbmdz 87449 -IHNwbGU= 87450 -IGTDqHM= 87451 -IE1EQg== 87452 -w6lydA== 87453 -IE1hdHRpcw== 87454 -YWlsbGVz 87455 -X3dlYWs= 87456 -L2phdg== 87457 -IGNvbGxhcHNlcw== 87458 -ICAgICAgCQk= 87459 -IHN3aXJs 87460 -IE5TU3RyaW5nRnJvbUNsYXNz 87461 -IHZvbHZlcg== 87462 -LlJlY2VpdmU= 87463 -IERleHRlcg== 87464 -IHRhYmxlbmFtZQ== 87465 -cmVhdGl2ZQ== 87466 -LkdldEZpbGVz 87467 -dm9vcg== 87468 -IEhvZQ== 87469 -VkVSTg== 87470 -IE9QQw== 87471 -7YOc 87472 -cmFtaWRz 87473 -54Sh44GX44GV44KT 87474 -U3Bpcml0 87475 -IE5PUA== 87476 -IE1haW50YWlu 87477 -KHNpZ21h 87478 -b3Ry 87479 -TW91c2VDbGlja2Vk 87480 -cXVpZXJkYQ== 87481 -X3dm 87482 -0L7QutCw0Lc= 87483 -YXBwYWJsZQ== 87484 -IEhvbGRlbg== 87485 -IENvdW50ZG93bg== 87486 -LnNpZ21h 87487 -Y2hhbGs= 87488 -YmlsZGVy 87489 -IHZpc2lvbmFyeQ== 87490 -CU9u 87491 -JHVwZGF0ZQ== 87492 -IEdpbmdyaWNo 87493 -cm9vbUlk 87494 -Pk5hbWE= 87495 -IHl5dHlwZQ== 87496 -LkRlY2ltYWxGaWVsZA== 87497 -bWFjcm9z 87498 -LnNldExheW91dFBhcmFtcw== 87499 -IHJubg== 87500 -IElNRGI= 87501 -56eN 87502 -ZW1hbGVz 87503 -IGluY2lkaWR1bnQ= 87504 -UmVzdHJpY3RlZA== 87505 -IHBlZGFscw== 87506 -IEpvZw== 87507 -IEFkYXB0aXZl 87508 -IGZhZGVz 87509 -LkV2ZW50U3lzdGVtcw== 87510 -IFBhaWdl 87511 -IHNlaXM= 87512 -IGFwcHJvcHJpYXRlZA== 87513 -RkZU 87514 -Z29yaXQ= 87515 -IGNvaGVzaXZl 87516 -IE5pY2h0 87517 -X3dvcmtmbG93 87518 -bGl1cw== 87519 -IEZvcnRuaXRl 87520 -X0lX 87521 -QXRQYXRo 87522 -IGludG94aWNhdGVk 87523 -bm9zdGlj 87524 -QmluQ29udGVudA== 87525 -LnJlZHVjZXI= 87526 -KT8K 87527 -J10q 87528 -IE9ic2VydmF0aW9u 87529 -X3ByZWZz 87530 -LnJlc29sdXRpb24= 87531 -LlBheWxvYWQ= 87532 -TWl4ZWQ= 87533 -IFJhaQ== 87534 -KHBkZXY= 87535 -KEAo 87536 -aWNvdA== 87537 -JGlz 87538 -IGNyZWU= 87539 -Pz0uKg== 87540 -LlFMYWJlbA== 87541 -IEdlb3JnaWFu 87542 -eENB 87543 -IGRlZmljaWVudA== 87544 -dGhyb3du 87545 -IHJhcGluZw== 87546 -dXBvcw== 87547 -CWNsaQ== 87548 -Z2V0Vmlldw== 87549 -SGlnaGxpZ2h0ZWQ= 87550 -Q3BwR3VpZA== 87551 -IHJlbGVnYXRlZA== 87552 -IGxlYWRlcmJvYXJk 87553 -UmVjZWl2ZVByb3Bz 87554 -Lmhhcg== 87555 -IGNvbmRp 87556 -SU1JVElWRQ== 87557 -IE1jQ2FydA== 87558 -KXRocm93cw== 87559 -YnVpZQ== 87560 -YnVhaA== 87561 -LmNvZWZm 87562 -IEF1c3NpZQ== 87563 -IFNhYmhh 87564 -KGZhYnM= 87565 -cmVsYW5k 87566 -IEbDtnI= 87567 -YmFyYW5n 87568 -LHRvcA== 87569 -CWVsc2lm 87570 -U3RlcFRocm91Z2g= 87571 -IHNrZXdlZA== 87572 -IFVudXNlZA== 87573 -Jyl9Pgo= 87574 -WWU= 87575 -Y2FsbGVl 87576 -SGliZXJuYXRl 87577 -IEV2ZXJlc3Q= 87578 -aW1wb3J0RGVmYXVsdA== 87579 -IHRhcm4= 87580 -IE5vd2FkYXlz 87581 -WUE= 87582 -IENoYWxsZW5nZXI= 87583 -X2xvZ2ljYWw= 87584 -IGNyZWF0ZURhdGU= 87585 -IEdsb3VjZQ== 87586 -IGN1YW50bw== 87587 -IEhBUg== 87588 -IENoaWxs 87589 -Il4= 87590 -IGN1cnNvcw== 87591 -LkVPRg== 87592 -IG5pamU= 87593 -IGFuZ2VyZWQ= 87594 -b2N1c2luZw== 87595 -PENvbnRhY3Q= 87596 -IEF0bW9zcGhlcmlj 87597 -IFdvbGZnYW5n 87598 -IEJK 87599 -Y2hpbGRz 87600 -IEJ1Z3M= 87601 -X0hFWA== 87602 -KFNQ 87603 -w6Vs 87604 -X2V2YWx1YXRpb24= 87605 -IFJBTkdF 87606 -IFNPUA== 87607 -X3Rva2VuaXpl 87608 -bXNnaWQ= 87609 -IHJleA== 87610 -CXBt 87611 -Q29weWluZw== 87612 -Kkw= 87613 -RGFsbGFz 87614 -LVN0YXRl 87615 -dWxmaWxs 87616 -IGJ5xYJv 87617 -IENvbnRyYWN0b3I= 87618 -RGlkbg== 87619 -QVNURQ== 87620 -IFBJTw== 87621 -LlRlbGU= 87622 -LndhdGVy 87623 -ZGV6 87624 -IGFuZ3JpbHk= 87625 -IHV0aWxpc2F0ZXVy 87626 -IHZvcnRleA== 87627 -Q29ycG9yYXRl 87628 -YXR1cmFz 87629 -IHByaXplZA== 87630 -J3VybA== 87631 -dWdsaWZ5 87632 -IGltcHVsc2Vz 87633 -IGNocm9ub2xvZ2ljYWw= 87634 -cGxlbg== 87635 -X25hbWE= 87636 -L29u 87637 -IE9mZmljZXM= 87638 -IENQSQ== 87639 -IEFmdGVyd2FyZHM= 87640 -44GT44KT44Gr 87641 -X0JMT0NLUw== 87642 -R3JhY2U= 87643 -LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg== 87644 -IEthYnVs 87645 -IOaIkA== 87646 -IExlaXB6aWc= 87647 -4Kao 87648 -U2hvY2s= 87649 -QXVz 87650 -IG11cm0= 87651 -X3N0YXJ0cw== 87652 -IGLDpA== 87653 -IFp5 87654 -IkY= 87655 -LXJpZ2h0cw== 87656 -IGJlaGF2aW5n 87657 -KCc+ 87658 -IG1vc3F1ZXM= 87659 -KndpZHRo 87660 -Ii8+Ljwv 87661 -LnVuc3BsYXNo 87662 -LmdldEFjdGl2aXR5 87663 -VVU= 87664 -IFNoYWs= 87665 -X3Jn 87666 -X0VxdWFscw== 87667 -J2h0dHBz 87668 -IE94eWdlbg== 87669 -IFBvcnRzbW91dGg= 87670 -4oCUb25l 87671 -IHdhdGNoZXJz 87672 -IENob2k= 87673 -IHNpZGVy 87674 -cGVjdHJhbA== 87675 -bXF0dA== 87676 -LmNyZWF0ZVVzZXI= 87677 -amVjdGl2ZXM= 87678 -dXJtYQ== 87679 -UmVnaXN0cg== 87680 -UGVyc29uYWxseQ== 87681 -PWtleQ== 87682 -IE5FTw== 87683 -IEZBUXM= 87684 -aWJpbGlkYWRl 87685 -Y2tzw6U= 87686 -IENvbGxhYm9yYXRpb24= 87687 -CWxibA== 87688 -LlNFUlZFUg== 87689 -IGFib3VuZA== 87690 -IEJlbmU= 87691 -d2FudGVk 87692 -LWhvbGU= 87693 -IG11dHRlcmVk 87694 -IHBlcA== 87695 -bmVzYw== 87696 -LlVwbG9hZA== 87697 -c2VtaQ== 87698 -eEVD 87699 -Jz4iKw== 87700 -IGVtYnJ5bw== 87701 -IEZpeGVkVXBkYXRl 87702 -Q2FzdGxl 87703 -Lm1vZGVsbw== 87704 -IHBscw== 87705 -IGVudmVsb3Blcw== 87706 -X3JlbWFpbg== 87707 -UXVhcnRlcg== 87708 -YWxlcnRWaWV3 87709 -X2Zvcm1hdHRlZA== 87710 -IGxhc2hlcw== 87711 -emVsZg== 87712 -aG9tbWU= 87713 -LmZsb3dMYXlvdXRQYW5lbA== 87714 -YWlycG9ydA== 87715 -IE1lbW9yaWVz 87716 -IEhFUk8= 87717 -IEFzaHRvbg== 87718 -IGV4aGliaXRpbmc= 87719 -KFNFTEVDVA== 87720 -U3VibWlzc2lvbg== 87721 -U3R1ZmY= 87722 -X3N1bg== 87723 -IHBlcsOtb2Rv 87724 -IGRlc3ByZQ== 87725 -CWVkaXQ= 87726 -IER0eXBl 87727 -Y2Vzc2l2ZQ== 87728 -YWFk 87729 -IGRlc2Nvbg== 87730 -bmVsbHk= 87731 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ== 87732 -IHNjcmlwdHVyZXM= 87733 -IG9uVmlld0NyZWF0ZWQ= 87734 -IEVWRQ== 87735 -IEJhbGxldA== 87736 -O307Cg== 87737 -VURP 87738 -IFByb2JhYmlsaXR5 87739 -cXVpcnJlbA== 87740 -Q29udGFpbmluZw== 87741 -IFBsYXQ= 87742 -6KI= 87743 -L2JpdA== 87744 -IEpRdWVyeQ== 87745 -IHRpZW5lcg== 87746 -L2RyaXZlcnM= 87747 -IFByZXNpZGVuY3k= 87748 -XHVE 87749 -IEl2ZQ== 87750 -aWVuYQ== 87751 -IGh5cGVycw== 87752 -IFNwZW5kaW5n 87753 -PFc= 87754 -IFRIRU1F 87755 -IHVzZXJQcm9maWxl 87756 -IGFubnVt 87757 -cmV0d2VldGVk 87758 -IFwnJw== 87759 -YnVuZGxlcw== 87760 -KCk8Lw== 87761 -IEN5bGluZGVy 87762 -IG91dGxpZXJz 87763 -IGRpc3NlbWluYXRpb24= 87764 -L2FwdA== 87765 -IE5hdGFzaGE= 87766 -IHJlbmRlckl0ZW0= 87767 -IENoaXBz 87768 -IHJvdW5kdXA= 87769 -IGltcHJvdg== 87770 -IGNvbW11bmljYXRvcg== 87771 -IHNreXBl 87772 -TU1N 87773 -cmlqaw== 87774 -LlBsYWNl 87775 -IHBhc2E= 87776 -IFNZTkM= 87777 -ZW5zaXM= 87778 -IEF4ZWw= 87779 -ZW7Dp2E= 87780 -Z2V0U3RyaW5nRXh0cmE= 87781 -YWJpbGl0w6k= 87782 -IGVtYWNz 87783 -LmdyYXZpdHk= 87784 -IGNoZXJpc2g= 87785 -IElTU04= 87786 -CUpzb24= 87787 -dXlv 87788 -IHVwdGltZQ== 87789 -IHJhbmRvbW5lc3M= 87790 -IGxvZnR5 87791 -Qm93 87792 -Q3JlYXI= 87793 -IHRvd2VyaW5n 87794 -Y2F0ZWdvcmll 87795 -L3Bvd2Vy 87796 -L3dlbGNvbWU= 87797 -fFI= 87798 -IGJhcnJpbmc= 87799 -aWRpYQ== 87800 -cXVhbQ== 87801 -w7pkbw== 87802 -ZXhwZXJpbWVudGFs 87803 -IGNsYQ== 87804 -IGN1cmF0b3I= 87805 -cmVhbWJsZQ== 87806 -aW5keA== 87807 -TExM 87808 -IH0pOg== 87809 -IGhpc3RvaXJl 87810 -c2ltdWxhdGU= 87811 -PEFueQ== 87812 -IEdsYW0= 87813 -IEJhcmc= 87814 -VmFsdWVDb2xsZWN0aW9u 87815 -IEluc3RpdHV0bw== 87816 -QXNTdHJpbmdBc3luYw== 87817 -IGFkZWM= 87818 -IGZlbGxvd3M= 87819 -cGlwZXM= 87820 -IFBsYWNlaG9sZGVy 87821 -IEtn 87822 -IEFsYnVtcw== 87823 -ICooKg== 87824 -X0dPT0Q= 87825 -KSIsDQo= 87826 -LlFSZWN0 87827 -w6Jt 87828 -IH0NDQo= 87829 -TWFyc2hhbEFz 87830 -QmFjaGVsb3I= 87831 -IEJhcmNvZGU= 87832 -IFRyYXZlcnNl 87833 -IG9kaW8= 87834 -LnNldFBhcmVudA== 87835 -IHNlbWljb25kdWN0b3I= 87836 -QUxMRUw= 87837 -IGJhbnF1ZXQ= 87838 -IE5ld3NwYXBlcg== 87839 -RE9NTm9kZQ== 87840 -IE5hdWdodHk= 87841 -Rm9ybWF0dGVkTWVzc2FnZQ== 87842 -IGRpc3J1cHRpbmc= 87843 -5piT 87844 -IGxvb2thaGVhZA== 87845 -IGdyYXR1aXRlcw== 87846 -IGNoZWVzeQ== 87847 -IFNQRg== 87848 -blA= 87849 -IGFyc29u 87850 -IGFudGVubmFz 87851 -X01JRERMRQ== 87852 -X01BTExPQw== 87853 -LmdvQmFjaw== 87854 -IFByb3Bvc2l0aW9u 87855 -IE1pY2hhZWxz 87856 -X3Byb29m 87857 -INC90LDQudC0 87858 -w6R0emxpY2g= 87859 -LXJvbGw= 87860 -RURB 87861 -w6Fuw60= 87862 -Z292ZXJubWVudA== 87863 -w7Z0dA== 87864 -IEVzdGFibGlzaG1lbnQ= 87865 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 87866 -X0hJVA== 87867 -IEFJTQ== 87868 -YWRvbA== 87869 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCg== 87870 -X1JFRkVSRVI= 87871 -IGZvcm1hdERhdGU= 87872 -dWN0b3Nl 87873 -IGRvd25sb2FkZXI= 87874 -VGV4dEVkaXQ= 87875 -IGRpc2FybQ== 87876 -IEhBUFA= 87877 -0L7QtNCw 87878 -ISkuCgo= 87879 -L3Byb2Nlc3M= 87880 -IGJyYWluc3Rvcm0= 87881 -IE9SSUdJTkFM 87882 -LlRhYmxlTmFtZQ== 87883 -IEtvc3Rlbmxvc2U= 87884 -IGTDqXA= 87885 -IElzYWJlbA== 87886 -IGFzdHJvbm9tZXJz 87887 -UVVJUkVT 87888 -OiIt 87889 -dXBsb2FkZXI= 87890 -Oi8vJQ== 87891 -IGFtaXM= 87892 -RmlsZVZlcnNpb24= 87893 -ICwk 87894 -Y29vaw== 87895 -LFNJR05BTA== 87896 -JywvLw== 87897 -IFN1cHByZXNz 87898 -IExhdGlub3M= 87899 -IHdpdGhob2xk 87900 -IG1uZW1vbmlj 87901 -X0NZQ0xF 87902 -IGhvZA== 87903 -IFdvcnNl 87904 -ZXJkZQ== 87905 -IHR5cGVpZA== 87906 -CWV4cG9ydHM= 87907 -IGFjaHRlcg== 87908 -b3Nhcw== 87909 -IGZvb3Rub3Rl 87910 -aGFuaQ== 87911 -KFBhcmFtZXRlcg== 87912 -CVJlbmRlcg== 87913 -IFlZU1RBQ0s= 87914 -IFhJSQ== 87915 -IHNpZGVu 87916 -IGFyb3VzYWw= 87917 -IE9P 87918 -Qml0dGU= 87919 -IG5lYXJlcg== 87920 -IENpcmN1cw== 87921 -IENPTE9SUw== 87922 -IHdpZWxkaW5n 87923 -LkZpbGVTeXN0ZW0= 87924 -IGdyaWxsZQ== 87925 -IERvdmVy 87926 -CiAgICAgCg== 87927 -KGdlb21ldHJ5 87928 -IHN0YXBsZXM= 87929 -IEFubm91bmNlbWVudA== 87930 -IOuyhA== 87931 -IGZvcnR1bmF0ZWx5 87932 -LlNvbWU= 87933 -IG1hbmdhbmVzZQ== 87934 -IGludGVydmlld2Vy 87935 -WVJP 87936 -IGNyeXB0b2dyYXBoeQ== 87937 -IGNoYW1icmU= 87938 -LnJldHJ5 87939 -IGltaXRhdGlvbg== 87940 -JGZkYXRh 87941 -IGxvdGlvbg== 87942 -KGlkZW50aXR5 87943 -LnBn 87944 -IHByZXN1bXB0aW9u 87945 -X1NVUEVS 87946 -dm9jYWI= 87947 -IFNlbWVzdGVy 87948 -IEFiZWw= 87949 -X2FwcHJvdmVk 87950 -LmNvbXBhdA== 87951 -IHdhcnRpbWU= 87952 -XV07Cgo= 87953 -bHV0 87954 -X0FjY291bnQ= 87955 -Pygn 87956 -Y29vcA== 87957 -L3JlZw== 87958 -LnNldFRv 87959 -aXRlc3Nl 87960 -IEh5ZHJh 87961 -Qmlucw== 87962 -Y2FkZW5h 87963 -Pi8nLA== 87964 -Llwi 87965 -CWFjY291bnQ= 87966 -IERhaGw= 87967 -IGRyb3du 87968 -IGdhdXNz 87969 -IHRyYW5zZm9ybWVycw== 87970 -IE1ldGFsbGlj 87971 -IEhlcmJhbA== 87972 -YWNocw== 87973 -X2J1dA== 87974 -IGl0ZXJhdGl2ZQ== 87975 -IEZyZWVk 87976 -anVy 87977 -fE0= 87978 -O2JyZWFr 87979 -X0ZG 87980 -KGRvd25sb2Fk 87981 -4buDbg== 87982 -LmNoZWNrU2VsZlBlcm1pc3Npb24= 87983 -TkVUV09SSw== 87984 -OmZsZXg= 87985 -IENUTA== 87986 -IEFyYg== 87987 -IFByb2R1Y2U= 87988 -CXN5bmNocm9uaXplZA== 87989 -4oCcT2g= 87990 -LmRhdGF0YWJsZXM= 87991 -IGNvbmVz 87992 -RMOp 87993 -0YbQsA== 87994 -QWxn 87995 -IGZ1bmNpb25h 87996 -IFViaXNvZnQ= 87997 -IGdlb3BvbGl0aWNhbA== 87998 -IHNpZWh0 87999 -IGh5ZHJhdGlvbg== 88000 -c3Rocm91Z2g= 88001 -IER1ZGxleQ== 88002 -YXrEgw== 88003 -IHRheGluZw== 88004 -INC30LDQutCw0Lc= 88005 -X0FTTQ== 88006 -TmV1dHJhbA== 88007 -dHJhZGl0aW9uYWw= 88008 -UGxheWFibGU= 88009 -IHNwYWdoZXR0aQ== 88010 -IGlDbG91ZA== 88011 -IERheXRvbmE= 88012 -IHdlcmRl 88013 -IEFOVA== 88014 -IFByb24= 88015 -IFN0YXRpb25z 88016 -IGF0dGVzdA== 88017 -IGZ1bGxlcg== 88018 -IG5vdmFtZW50ZQ== 88019 -XVxc 88020 -Y2Nl 88021 -KGRlY2s= 88022 -L2F5dXNobWFu 88023 -aWdzYXc= 88024 -IGFkdWx0ZXM= 88025 -IHRlcnJl 88026 -Lk9yZGVycw== 88027 -CXByb3BlcnRpZXM= 88028 -RElH 88029 -IFRJTUVT 88030 -ImluZGljZXM= 88031 -ITw= 88032 -TW9uYWQ= 88033 -IG5vbmV4aXN0ZW50 88034 -IEF0bGFudGlz 88035 -IGdyaWV2YW5jZXM= 88036 -dXJlbmNl 88037 -IElQUFJPVE8= 88038 -4pmA4pmA4pmA4pmA 88039 -IGVtcGxlYWRv 88040 -INmD 88041 -Lk1vdmVOZXh0 88042 -IElzbw== 88043 -YmVhdXRpZnVs 88044 -IHNvbHVibGU= 88045 -IHNsdWdnaXNo 88046 -IGRpZmZz 88047 -X09CUw== 88048 -eG1pbg== 88049 -IHR1bWJsZQ== 88050 -IFVuYXJ5 88051 -IHppcGZpbGU= 88052 -IHN2ZW5za2E= 88053 -ZXJsYW5k 88054 -L2N1cGVydGlubw== 88055 -CXNjcmlwdA== 88056 -aXNjaGVz 88057 -TW9kaWZpZWREYXRl 88058 -IHZleWE= 88059 -IGRldGVybWluYW50 88060 -IEdvcmdlb3Vz 88061 -Z2Jvb2xlYW4= 88062 -IExPRA== 88063 -ZGNj 88064 -c2NlbmVz 88065 -IFRTUk1MUw== 88066 -KFR5cGVFcnJvcg== 88067 -IGNhbW91ZmxhZ2U= 88068 -IGJ1cmdl 88069 -VGhlbQ== 88070 -LkFzc2lnbg== 88071 -IGxhc3RJbmRleA== 88072 -X3NwaGVyZQ== 88073 -X0FCSQ== 88074 -w4Q= 88075 -aWxhZ2U= 88076 -XHhmZg== 88077 -IGtheWFr 88078 -IGZpeno= 88079 -dWl0ZW4= 88080 -LlNob3VsZEJl 88081 -IGh0b25s 88082 -IFBldGl0ZQ== 88083 -IGhlYWxz 88084 -IE9zYWth 88085 -Tko= 88086 -SW5QYXJhbWV0ZXI= 88087 -IEJpcmNo 88088 -IGNvbW1lbnRhaXJl 88089 -IFNpZWdl 88090 -IGtleWNvZGU= 88091 -LWludGVuc2l2ZQ== 88092 -cHJvcFR5cGVz 88093 -RXhwb3J0cw== 88094 -IGJ1dHRvblRleHQ= 88095 -IEdvZHppbGxh 88096 -LkV4Y2hhbmdl 88097 -IHVuZGVyc3RhbmRhYmx5 88098 -IGFjY29yZGlvbg== 88099 -IHLDqWdpb24= 88100 -IG1hcmtlZGx5 88101 -YW5vb2dh 88102 -IGNvbnRyYXQ= 88103 -X2xpZnQ= 88104 -W2RhdGU= 88105 -IHNjb3Ju 88106 -IERhdGFNYW5hZ2Vy 88107 -4oCm4oCmCgo= 88108 -X0NPTVBJTEVS 88109 -IENsYXc= 88110 -b2RhdGU= 88111 -IHVuZGVyYWdl 88112 -IEltcGxlbWVudGVk 88113 -Q2xp 88114 -S2Fs 88115 -UHJvZHVjdG9z 88116 -IGVuZmVybWVk 88117 -w6lpcw== 88118 -IGRpc2NyZWRpdA== 88119 -IFNhbW9h 88120 -IFByZXNlbnRlZA== 88121 -IGNpbmVtYXQ= 88122 -XEFjdGl2ZUZvcm0= 88123 -IGZlcm4= 88124 -IFByaW1lcg== 88125 -5oKo 88126 -Z2VyZQ== 88127 -IGlsbHVzaW9ucw== 88128 -bm90YXRlZA== 88129 -IHBvag== 88130 -IG1vZGVsTmFtZQ== 88131 -IFBNQw== 88132 -IGRlY2Fk 88133 -IGZvcmVzdHJ5 88134 -dm9pZQ== 88135 -Li4uCgoKCgoK 88136 -IH19Owo= 88137 -IHRva2VuSWQ= 88138 -YW1tdQ== 88139 -IFBlcnNvbmVu 88140 -IFZFUkJPU0U= 88141 -IHBhdHJvbHM= 88142 -IGFudGlj 88143 -X2RlZXA= 88144 -ZWdlbmQ= 88145 -IFNldFByb3BlcnR5 88146 -IEdhcmV0aA== 88147 -IE1BUw== 88148 -LnJlc3RhdXJhbnQ= 88149 -IEhlYXZlbmx5 88150 -aWVkbw== 88151 -X2xlYWQ= 88152 -IEZ1amk= 88153 -UU4= 88154 -TWFzc2FnZQ== 88155 -IHBhcmFtTWFw 88156 -IGNpdGE= 88157 -X1NwZWVk 88158 -KGJib3g= 88159 -IEpVTA== 88160 -4oCZYW4= 88161 -IG1lbnRl 88162 -IFNob3djYXNl 88163 -IENTSQ== 88164 -PlR5cGU= 88165 -LlNu 88166 -b3R5cGljYWw= 88167 -IEZhbGxvbg== 88168 -LlVUQw== 88169 -IHByZWRhdG9yeQ== 88170 -IG9yZ2FuaXNpbmc= 88171 -Y29sZA== 88172 -IHBhcnNlcnM= 88173 -dWllbg== 88174 -IGNvbXBpbGVycw== 88175 -IFs9 88176 -IEV1cmFz 88177 -TU9TVA== 88178 -CiAgICAKCg== 88179 -UkFS 88180 -LlNjaGVkdWxl 88181 -Lm9wZXJhdGlvbnM= 88182 -dWZz 88183 -w7FhbmE= 88184 -IHByZW9jdXA= 88185 -LXRyZWF0ZWQ= 88186 -LmdldFdvcmxk 88187 -Lic6 88188 -IEFUSA== 88189 -OnN0YXJ0 88190 -IGF1dG9pbW11bmU= 88191 -IEJsYWNramFjaw== 88192 -X0ZJTklTSA== 88193 -KGZsb29y 88194 -IHdyZWNrYWdl 88195 -VVJU 88196 -LkJyYW5k 88197 -cGFpcw== 88198 -Y2ltYWw= 88199 -Y2nDsw== 88200 -TkZM 88201 -LWVxdWlwcGVk 88202 -LmNvbnRlbnRPZmZzZXQ= 88203 -IG92ZXJjcm93 88204 -IFRa 88205 -IG9kb20= 88206 -IENlbGx1bGFy 88207 -CXdyaXRlbA== 88208 -KGlucHV0U3RyZWFt 88209 -KHByZWY= 88210 -LXN0b2Nr 88211 -IERlbmllZA== 88212 -LXN1cHBvcnRlZA== 88213 -ICcoKA== 88214 -YW5jb2Rl 88215 -LmZpbHRlcmVk 88216 -RGltcw== 88217 -IGpi 88218 -CXByaWNl 88219 -IEBACg== 88220 -bm9jaw== 88221 -Lm9wZW5Db25uZWN0aW9u 88222 -IGFudGljcw== 88223 -cmVzdWx0Q29kZQ== 88224 -UGxheWJhY2s= 88225 -IGNlbHVsYXI= 88226 -IEZPT0Q= 88227 -IFBvZGVzdGE= 88228 -PW1lc3NhZ2U= 88229 -LnBlcmZvcm1hbmNl 88230 -IERtaXRyeQ== 88231 -YWx0aW1vcmU= 88232 -IHBsYXRlZA== 88233 -IHR1YmVyY3Vsb3Npcw== 88234 -X2dlbQ== 88235 -KEVkaXRvcg== 88236 -VHBs 88237 -IGNyaWFu 88238 -IGJ1ZmZlcmluZw== 88239 -6KeG6aKR 88240 -ICcpCgo= 88241 -VnU= 88242 -TWF0aGY= 88243 -IHRpbWVsaW5lcw== 88244 -IFRhdGE= 88245 -L3Bw 88246 -IHBsYXN0 88247 -IFRydWx5 88248 -IFN1YnN0aXR1dGU= 88249 -a2llbQ== 88250 -a2Fhcg== 88251 -IFZpc2g= 88252 -J2h1aQ== 88253 -IE1hZ2ljaw== 88254 -L0xheW91dA== 88255 -dXJhbsOnYQ== 88256 -X3R0bA== 88257 -SGlkZUluSW5zcGVjdG9y 88258 -LmtleXdvcmRz 88259 -TGlzdE1vZGVs 88260 -X1N1Y2Nlc3M= 88261 -aWxpaGFu 88262 -IGJsYWNrbWFpbA== 88263 -IFNlcmJpYW4= 88264 -cXVlbGxl 88265 -IER5c2Z1bmN0aW9u 88266 -IFByZXBhcmVk 88267 -IGpNZW51SXRlbQ== 88268 -IGxvZ2luVXNlcg== 88269 -c2V0YXR0cg== 88270 -LkNS 88271 -X2xjZA== 88272 -IGJ5dGVzUmVhZA== 88273 -IGNkZWNs 88274 -IHRvd25zaGlw 88275 -cGVr 88276 -aWprc3RyYQ== 88277 -IG1heGltaXppbmc= 88278 -LnByb3ZpZGVycw== 88279 -SW52ZXN0aWdhdG9ycw== 88280 -IHNob290b3V0 88281 -IGFpcnNwYWNl 88282 -dG9vbGJveA== 88283 -UVdpZGdldA== 88284 -PXBr 88285 -IHBvcnRlcg== 88286 -IFByZWRhdG9y 88287 -IFN1bnJpc2U= 88288 -IGRldm91cg== 88289 -CVVJbnQ= 88290 -aXR0YW5jZQ== 88291 -U1BB 88292 -X2VuZGlhbg== 88293 -IE5hZ2Fy 88294 -dmVuaWRh 88295 -L29wdA== 88296 -QnlFbWFpbA== 88297 -IFBoeXNpY2lhbg== 88298 -XEQ= 88299 -INC80Ys= 88300 -WUVBUg== 88301 -SUND 88302 -L3BvcnRmb2xpbw== 88303 -LmV4ZWN1dG9y 88304 -dWRlbQ== 88305 -RmFsbGJhY2s= 88306 -dWR1 88307 -U2xpbQ== 88308 -w7Nsbg== 88309 -Xnst 88310 -YW5za2U= 88311 -IGh1c3RsZQ== 88312 -IElyZW5l 88313 -IGFieXNz 88314 -IFJvYmJpbnM= 88315 -IGluZGV4ZXI= 88316 -U2F1ZGk= 88317 -IHdob2xlc29tZQ== 88318 -LXNsb3Q= 88319 -IFRlY24= 88320 -IHBhZ2VUaXRsZQ== 88321 -IGNvbnRlc3RhbnQ= 88322 -aWNvcHRlcg== 88323 -IGNvdXJzZUlk 88324 -Q2hy 88325 -IEFYSVM= 88326 -Zm9yZGVy 88327 -X1RVTg== 88328 -VHJhZmZpYw== 88329 -IHR5cGVhbGlhcw== 88330 -IGRhcmY= 88331 -LXVyaQ== 88332 -dHN4 88333 -LmRlc3Ryb3lBbGxXaW5kb3dz 88334 -IGl0ZXJhdGluZw== 88335 -UmVhY3Rpb24= 88336 -CUFN 88337 -IGN1ZW50 88338 -LWNvb2tpZQ== 88339 -IGZsYXZvcmVk 88340 -c3RvaQ== 88341 -IGZsaXJ0aW5n 88342 -44CL77yM 88343 -4KSu 88344 -X0NSWVBUTw== 88345 -W3Rva2Vu 88346 -IHByb2xldGFyaWF0 88347 -LuKAmeKAnQoK 88348 -CWRj 88349 -LlN0cmluZ1Zhcg== 88350 -IGxlZ2l0aW1hdGVseQ== 88351 -X2RlY29yYXRvcg== 88352 -TG9ja2Vy 88353 -IEplbm5h 88354 -VVJJTkc= 88355 -5YaN 88356 -X1ByaW50Zg== 88357 -QVRPUlk= 88358 -LWRpc3Q= 88359 -ICIuIik7Cg== 88360 -LnF1aXo= 88361 -IGlyZ2VuZA== 88362 -LWxlYWd1ZQ== 88363 -Z2llbg== 88364 -IFByb2R1Y2Vk 88365 -SGVsbWV0 88366 -5Y+v6IO9 88367 -UGxhdGZvcm1z 88368 -IFJlc291cmNlTWFuYWdlcg== 88369 -IEh1bmRyZWQ= 88370 -cm9tZXRlcg== 88371 -ZW5na2Fw 88372 -SG9w 88373 -IHBvc3N1aQ== 88374 -QmVmb3JlRWFjaA== 88375 -IENISw== 88376 -IElNUw== 88377 -VGlja2Vy 88378 -IGdyaW5uZWQ= 88379 -LmdldEFz 88380 -IGltcG9zZXM= 88381 -XSIp 88382 -Rm9yZ2V0 88383 -L2ltcG9ydA== 88384 -IGluamVjdGluZw== 88385 -TG92 88386 -IGFicmls 88387 -X3NsaWNlcw== 88388 -LWNvbW0= 88389 -IFBST0RVQ1RT 88390 -IE9hc2lz 88391 -IMO4bnM= 88392 -IFJlamVjdA== 88393 -IHJlZ3VsYXJpemF0aW9u 88394 -aW1wbGljaXRseQ== 88395 -bmF6 88396 -U3BlY2lmaWVy 88397 -IGltcG92ZXJpc2hlZA== 88398 -5po= 88399 -IG5vbWluYXRl 88400 -IE9WRVJSSURF 88401 -IEJhbmRz 88402 -ZXRoeXN0 88403 -IEppYW4= 88404 -IG5ld2NvbWVy 88405 -IE5hYg== 88406 -IGVicA== 88407 -IFBhZ2Vy 88408 -IEh1bWI= 88409 -L2Nj 88410 -IGV4cMOpcmllbmNl 88411 -dWRnaW5n 88412 -TWI= 88413 -ZGJ1Zg== 88414 -Jy8+ 88415 -IG9ja3PDpQ== 88416 -IGpkYmNUZW1wbGF0ZQ== 88417 -IFNISVBQSU5H 88418 -IGludGVyZGlzY2lwbGluYXJ5 88419 -IENFVA== 88420 -YXV0b3A= 88421 -LXN5bWJvbA== 88422 -YXZlYw== 88423 -IGNvbXBvdW5kZWQ= 88424 -IENodW5n 88425 -X1NNUw== 88426 -LWll 88427 -IFByb3NlY3V0b3I= 88428 -IExlaWE= 88429 -IE1hbmRlbGE= 88430 -U2luZ2xlT3JEZWZhdWx0 88431 -CVJFUVVJUkU= 88432 -YXRvd24= 88433 -dXJyZXRz 88434 -5paH5a2X 88435 -IENPTlRFWFQ= 88436 -RU5TSVRZ 88437 -IGluc3VyZ2VudHM= 88438 -IERpYXM= 88439 -LnN0YXRpb24= 88440 -IEtsYW4= 88441 -X21lYXN1cmVtZW50 88442 -X1FNQVJL 88443 -IHN0b2k= 88444 -TU9PVEg= 88445 -PicpOwoK 88446 -IGluZ2VzdGlvbg== 88447 -IEdsb3c= 88448 -dXRjaGVz 88449 -YmVhcmluZw== 88450 -LnRvYXN0cg== 88451 -IGZyYWdtZW50YXRpb24= 88452 -aXBwbw== 88453 -X1NFR01FTlQ= 88454 -IHN0dW1ibGluZw== 88455 -aW1hcg== 88456 -c3Rpbmlhbg== 88457 -XygpCg== 88458 -IG1vdGl2YXRpb25hbA== 88459 -TGlzdEl0ZW1UZXh0 88460 -IHdvbWVucw== 88461 -T3BlbkhlbHBlcg== 88462 -aWJhbmQ= 88463 -IGJ0blNhdmU= 88464 -IGluY29ycG9yYXRpb24= 88465 -IGRvY3VtZW50YXJpZXM= 88466 -aWNs 88467 -IE5k 88468 -IEFyYQ== 88469 -IHF1YWtl 88470 -IEN1bW1pbmdz 88471 -aHRt 88472 -YXN0ZXJlZA== 88473 -LmR0cA== 88474 -IGNvbmRvcw== 88475 -IEd1bmRhbQ== 88476 -L2Rpc2FibGU= 88477 -aHlkcmF0ZQ== 88478 -IEVwb2No 88479 -IG5hdGlvbmFsaXN0cw== 88480 -IGRldmVy 88481 -LHJlcXVlc3Q= 88482 -LmdldFZlcnNpb24= 88483 -Q0VMRVI= 88484 -IFNhbGFo 88485 -IG1vdGU= 88486 -IE1lbGxvbg== 88487 -c3BvdGlmeQ== 88488 -IG9yaWdlbg== 88489 -IG5hbGU= 88490 -IGFkdmVyc2FyaWVz 88491 -LkpUYWJsZQ== 88492 -Zm9yY2VtZW50cw== 88493 -IFJldHJlYXQ= 88494 -IGFyY2hpdm9z 88495 -IHNsYXNoZXM= 88496 -Lk1vdXNlRG93bg== 88497 -PDo6 88498 -X3Rocm91Z2g= 88499 -QWxhbWF0 88500 -LmJsdXI= 88501 -X2ZpbmRlcg== 88502 -IGFsbHVyZQ== 88503 -UGVyaXBoZXJhbA== 88504 -X3Bhc3NlZA== 88505 -X2NoYWxsZW5nZQ== 88506 -IFBhbGVv 88507 -SU5J 88508 -RGlyZQ== 88509 -c3BoZXJl 88510 -KENPTE9S 88511 -YWNrZXJz 88512 -IEdseXBo 88513 -KGludGVnZXI= 88514 -INC60L4= 88515 -IFJlbGV2YW50 88516 -INm+ 88517 -IGF0YXM= 88518 -X3ByaW0= 88519 -IE1VVA== 88520 -bmluZ2Vy 88521 -YXV0b3JlbGVhc2Vwb29s 88522 -PV9f 88523 -IFNpZ25pbmc= 88524 -7ZWY7KeA 88525 -IHVjeg== 88526 -RWRpdGluZ1N0eWxl 88527 -IEhlYXRlcg== 88528 -IEZhaXJmaWVsZA== 88529 -IEJlYXJk 88530 -LGVu 88531 -dXNhdA== 88532 -KCcuJw== 88533 -L3N0cmVhbQ== 88534 -IGdldFN1cHBvcnRGcmFnbWVudE1hbmFnZXI= 88535 -IG1DdXJyZW50 88536 -X1NUQVRFUw== 88537 -X3dpbmQ= 88538 -Q0hBUFRFUg== 88539 -cHJvYmFiaWxpdHk= 88540 -KGFubm90YXRpb24= 88541 -ICovDQoNCg0K 88542 -LlVuaXF1ZQ== 88543 -LkFkZEZpZWxk 88544 -SGlnaGVy 88545 -LmRpZ2l0YWw= 88546 -LmV4cGVyaW1lbnRhbA== 88547 -YXds 88548 -IHdoZW5jZQ== 88549 -ZXJub3Rl 88550 -U0FNRQ== 88551 -Lmlwdg== 88552 -dG9CZUZhbHN5 88553 -YnJhbmU= 88554 -X2NhdGVnb3JpY2Fs 88555 -QXVyYQ== 88556 -IFR5cGVTY3JpcHQ= 88557 -IHNwb250YW5lb3VzbHk= 88558 -bG9uZ2xlZnRyaWdodGFycm93 88559 -aWthbA== 88560 -X1RPRE8= 88561 -IFd5YXR0 88562 -IGZsdXJyeQ== 88563 -ZGlm 88564 -IHJlY2tvbg== 88565 -IENvcm91dGluZQ== 88566 -CWZmbHVzaA== 88567 -IHdvcmtmbG93cw== 88568 -IEZBTUlMWQ== 88569 -c3ByaXRlcw== 88570 -X1dvcms= 88571 -LkdldFNpemU= 88572 -IENvbnN0cmFpbnRz 88573 -QmlnSW50 88574 -aXRpYQ== 88575 -Z2V0Um93 88576 -IGR1aw== 88577 -IGlzTmV3 88578 -IFByb2R1a3Rl 88579 -eENC 88580 -aXNpZXJ0 88581 -ZnVuY3M= 88582 -IEFkZW3DoXM= 88583 -QmluZGluZ1V0aWw= 88584 -b21waWxlcg== 88585 -LWludg== 88586 -IGNoYW50cw== 88587 -IGVudHNwcmVjaA== 88588 -KHRp 88589 -X0lB 88590 -0L7RgNC00LjQvQ== 88591 -IEZBTEw= 88592 -aW1k 88593 -IGxvY2FsdGltZQ== 88594 -PExpbms= 88595 -0L3QuNC60LA= 88596 -IHByb2ZpbGVy 88597 -IGdldFVzZXJJZA== 88598 -IFBoeXNpY2lhbnM= 88599 -UkFE 88600 -IGhtbQ== 88601 -IE5lc3M= 88602 -IFRlbXBv 88603 -IEpU 88604 -IHJlY29ubmFpc3NhbmNl 88605 -PHRyYW5zbGF0aW9u 88606 -IGVudGljaW5n 88607 -IHF1YWludA== 88608 -IGNvdXBl 88609 -X18nLA== 88610 -TkFTREFR 88611 -INC30L3QsNGH0LXQvdC40Y8= 88612 -UEVSQVRVUkU= 88613 -IFBhaQ== 88614 -IHRldGFz 88615 -Q0FT 88616 -SVJST1I= 88617 -IGtj 88618 -IHRvdGU= 88619 -IGRyYXdiYWNr 88620 -IHBhcnNsZXk= 88621 -CUZ1bmN0aW9u 88622 -aXN0eQ== 88623 -IERVUA== 88624 -X0NJRA== 88625 -X1VU 88626 -IGtzaQ== 88627 -IGrDpA== 88628 -PXZhbA== 88629 -LnRvSGV4U3RyaW5n 88630 -5p2/ 88631 -LmNsaXBz 88632 -IG9mZmVu 88633 -IFRFQ0hOTw== 88634 -IFNoYW1l 88635 -IHN1c2NlcHRpYmlsaXR5 88636 -IHN0dXBpZGl0eQ== 88637 -IFRyb3V0 88638 -IENoYW1wYWduZQ== 88639 -ZXRoeWxlbmU= 88640 -IGJlZ3I= 88641 -X3JlZGlz 88642 -WWVw 88643 -IGhhbnM= 88644 -IERlZmVuZGFudA== 88645 -IGRhc2hlcw== 88646 -IHVzZXJUeXBl 88647 -X2RhdG9z 88648 -IHVuaWM= 88649 -a3JpdA== 88650 -IHJlY2VwdGl2ZQ== 88651 -IEdyZXQ= 88652 -KG1i 88653 -IEluZmx1 88654 -w6tu 88655 -fS8+ 88656 -aW50ZXJlc3Rpbmc= 88657 -VVRVUkU= 88658 -IGltYWdlU2l6ZQ== 88659 -IGdyZA== 88660 -IGFic29s 88661 -L2Zh 88662 -LmdyYWRpZW50 88663 -IHd5c3Q= 88664 -XX0+Cg== 88665 -bGVnYXRpb24= 88666 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCg== 88667 -IEJsZW5kZXI= 88668 -X18pOw== 88669 -IHVzZXJFbWFpbA== 88670 -IFBoYXI= 88671 -bGVoZW0= 88672 -KSk/ 88673 -KFJldHVybg== 88674 -ZWdyYQ== 88675 -dXRpdm8= 88676 -IGFwcGVuZGl4 88677 -IFJUVkY= 88678 -IFNFQUw= 88679 -IGd5cHN1bQ== 88680 -X0FyZw== 88681 -IGlsbHVtaW5hdGU= 88682 -IFNjaGlmZg== 88683 -cXVpbA== 88684 -LkNvbWJvQm94U3R5bGU= 88685 -J10pKQoK 88686 -IGFsdGVycw== 88687 -IHByYWN0aXNl 88688 -IHVzdA== 88689 -IERpbWl0 88690 -LVJlZ3VsYXI= 88691 -IGNyZWVwaW5n 88692 -IENhbmFkaWVucw== 88693 -IHJldG9ybg== 88694 -LWNvcm5lcg== 88695 -ICJdIg== 88696 -KHJuZw== 88697 -IGNhbmFkaWFu 88698 -IHBvc3Rv 88699 -LmFzc2VydEFsbW9zdEVxdWFs 88700 -IEJlY2t5 88701 -L3Nz 88702 -IGhvc3RhZ2Vz 88703 -IGJpb2xvZ2lzdA== 88704 -IEhvc3BpdGFsaXR5 88705 -IEVsaw== 88706 -IEJhcmFuZw== 88707 -66qp 88708 -YmJiYg== 88709 -LnRlYWNoZXI= 88710 -IHRlcm1pbmF0ZXM= 88711 -IGlzRXJyb3I= 88712 -IEtlbmRyaWNr 88713 -ZW5kYXJz 88714 -IFN1Z2dlc3Rpb25z 88715 -Q2Vs 88716 -IFNlcnZpY2VQcm92aWRlcg== 88717 -IFdpY2hpdGE= 88718 -XSkpLAo= 88719 -IGhlYWRsaWdodHM= 88720 -X3ZlbnRh 88721 -QU5USQ== 88722 -IHByb3BpZWRhZA== 88723 -IGVubGlzdA== 88724 -CW9yZw== 88725 -TWVzc2VuZ2Vy 88726 -LmxhbmQ= 88727 -IicK 88728 -YXNwZXJz 88729 -IHRlcnM= 88730 -ZmlsdA== 88731 -IEZ1bmN0b3I= 88732 -IHNsaW5n 88733 -X0JMSw== 88734 -LUV1cm9wZWFu 88735 -IEFjaGlsbGVz 88736 -XEVudGl0aWVz 88737 -LkRpc3BsYXlNZW1iZXI= 88738 -IHJlZGV2ZWxvcG1lbnQ= 88739 -CWhlbHA= 88740 -IFsnLQ== 88741 -IEp1bGllbg== 88742 -PUludGVnZXI= 88743 -LmlzTnVsbE9yRW1wdHk= 88744 -IFdvVw== 88745 -UGF5bWVudHM= 88746 -KGhkcg== 88747 -IGJhamE= 88748 -IEpDb21ib0JveA== 88749 -RmlyZWZveA== 88750 -IGNvbmdsb21lcg== 88751 -X2N1c3Q= 88752 -JCIpCg== 88753 -IG11dGFudHM= 88754 -TWFnbg== 88755 -IE1QSA== 88756 -e18= 88757 -X3dhcm5pbmdz 88758 -IGdhc3Q= 88759 -THQ= 88760 -IHRyYWluYWJsZQ== 88761 -VHJhZGVtYXJr 88762 -QkFTSA== 88763 -IEVDUw== 88764 -UmV0cmlldmU= 88765 -J08= 88766 -IGluaXRpYWxpc2Vk 88767 -IGNoZW1pbg== 88768 -LlRyYW5zcG9ydA== 88769 -IFlpbmc= 88770 -YXNpb25z 88771 -IG1vYw== 88772 -X0xPR0dFUg== 88773 -R0VOQ1k= 88774 -IEJsb2dnZXI= 88775 -ICIpIgo= 88776 -UEVuZA== 88777 -IGFjY29tcGFnbg== 88778 -LkNPREU= 88779 -IG1MaXN0 88780 -LWVkdWNhdGVk 88781 -LC8= 88782 -IE1lcnJpbGw= 88783 -L3Blb3BsZQ== 88784 -LicnJwo= 88785 -X3RvZG8= 88786 -IGfDvG4= 88787 -X0ZVTExTQ1JFRU4= 88788 -LmNsZWFudXA= 88789 -VW5tYXJzaGFsbGVy 88790 -LlN1cHByZXNzTGludA== 88791 -IG9uc2xhdWdodA== 88792 -IE1hcnNlaWxsZQ== 88793 -ZWRpYXRvcg== 88794 -X0VOVFJJRVM= 88795 -LGRlZmF1bHQ= 88796 -bWVsZHVuZw== 88797 -ZWxmdGg= 88798 -IEdvdmVybm1lbnRz 88799 -IHBsZWFz 88800 -b3R0cw== 88801 -IHBsdW5kZXI= 88802 -cmVhZE9ubHk= 88803 -IGR5c2Z1bmN0aW9uYWw= 88804 -J05laWxs 88805 -IHVubG9hZGVk 88806 -IHNxdWVlemluZw== 88807 -IGRvb2Q= 88808 -LmFkZERhdGE= 88809 -IEFzaQ== 88810 -TUVT 88811 -KHNjaGVkdWxl 88812 -IGFkdmVudHVyZXJz 88813 -ZXhwZWN0RXhjZXB0aW9u 88814 -IH19Pns= 88815 -Q0xT 88816 -IHJlY2hlcg== 88817 -IGRlcm5pw6hyZQ== 88818 -LkRldGFpbHM= 88819 -IHJhbmRvbU51bWJlcg== 88820 -IGlhcg== 88821 -IExhbmdl 88822 -ZXdl 88823 -IEVtaWw= 88824 -IGFkdmVydHM= 88825 -IGRyYW1hcw== 88826 -IEtvbW0= 88827 -ICAJCQkJ 88828 -X1Rlc3RDYXNl 88829 -IENsYXJlbmNl 88830 -0LXQvdGC0LA= 88831 -dG91cHBlcg== 88832 -Lm9uU3VibWl0 88833 -Y2Fh 88834 -X0FMQVJN 88835 -KikKCg== 88836 -IOuzgOqyvQ== 88837 -LlByaXZhdGU= 88838 -IHNreWxpbmU= 88839 -UkFJTg== 88840 -KGN1cmw= 88841 -b3NpdGU= 88842 -SWdub3Jpbmc= 88843 -IHZ6 88844 -IHZlZGVyZQ== 88845 -IE9TWA== 88846 -YmFuYW5h 88847 -IG1ldGFt 88848 -IHRyYW5zbGF0ZVk= 88849 -IE1jR3I= 88850 -4oCZYWNj 88851 -5Lul5LiL 88852 -IHNwaXJpdHVhbGx5 88853 -KGVuYWJsZWQ= 88854 -IHJlc3RvcmVz 88855 -IGJ0bkNhbmNlbA== 88856 -dmFuaXNoZWQ= 88857 -IE51ZXZv 88858 -U2FsdmFy 88859 -Y2FmZmU= 88860 -IG1hc3RlcmluZw== 88861 -aWRkbGVk 88862 -LmlzZGlnaXQ= 88863 -IGdyYXZ5 88864 -YWdlZExpc3Q= 88865 -XFJlc291cmNlcw== 88866 -IGRvd25mYWxs 88867 -LlBhc3M= 88868 -IGFsdGlqZA== 88869 -IHBpenphcw== 88870 -IH0pKQ== 88871 -cGVybXM= 88872 -aWdodG9u 88873 -IHJlcGVsbA== 88874 -ICcnKSw= 88875 -Lm5vcm1hbGl6ZWQ= 88876 -IG1hcmNoZXM= 88877 -CXJlc29sdmU= 88878 -Q2hpbGRTY3JvbGxWaWV3 88879 -IEluc3RpdHV0aW9ucw== 88880 -QXR0ZW5kYW5jZQ== 88881 -bHNl 88882 -ZXJkZW0= 88883 -LmdldElucHV0 88884 -SGFzQmVlbg== 88885 -YXBldXRpY3M= 88886 -ICpc 88887 -IFJpdHVhbA== 88888 -X0xT 88889 -IHNwb3RpZnk= 88890 -IHNww6R0ZXI= 88891 -IFRodW1ibmFpbA== 88892 -KGNlcnQ= 88893 -IGdldFJlc291cmNl 88894 -X3Bsb3Rz 88895 -IHN0YWluaW5n 88896 -YWRqdXN0ZWQ= 88897 -INep 88898 -RGl2RWxlbWVudA== 88899 -IFRUQw== 88900 -IGFwcm92ZQ== 88901 -LnZpZXdlcg== 88902 -fD0= 88903 -Z2V0U291cmNl 88904 -55S16K+d 88905 -X1RC 88906 -X2JpbGxpbmc= 88907 -LUxpZmU= 88908 -IHBzeWNoZQ== 88909 -IHRhYlBhZ2U= 88910 -IEluZmVjdA== 88911 -eGZmZg== 88912 -X2hpZA== 88913 -IGFwb2NhbHlwc2U= 88914 -IE5GUw== 88915 -IElURVI= 88916 -V2luZG93U2l6ZQ== 88917 -aGVpdHM= 88918 -IGluY3JlbWVudGVk 88919 -IEJyYXk= 88920 -ZW5lZ3Jv 88921 -IGFsbW9uZHM= 88922 -WVBSRQ== 88923 -Tm9ybWFsaXpl 88924 -4oCcV2VsbA== 88925 -IEFwaUNvbnRyb2xsZXI= 88926 -W1VuaXQ= 88927 -R2VucmVz 88928 -IE5leA== 88929 -IExORw== 88930 -IGZvcmVnb2luZw== 88931 -IHRlbmRvbg== 88932 -IEhw 88933 -Q291bmNpbA== 88934 -IFNhdWRpcw== 88935 -IERlemU= 88936 -IHNjcmFwZWQ= 88937 -IGJvdHRsZW5lY2s= 88938 -IE9ybg== 88939 -IHVubWFubmVk 88940 -IGludm9raW5nU3RhdGU= 88941 -IEV4b2R1cw== 88942 -X0FUT01JQw== 88943 -U3ViTWVudQ== 88944 -X2NvbXByZXNz 88945 -Iy4= 88946 -RHJ2 88947 -LnB1c2hCdXR0b24= 88948 -IHN1aXRjYXNl 88949 -b3NzZWQ= 88950 -Yml0cmFyeQ== 88951 -U25pcHBldA== 88952 -IEVwaWRlbWk= 88953 -RGlzYWxsb3c= 88954 -X0NISw== 88955 -IHZlcmlmaWVz 88956 -IENhdGFseXN0 88957 -4oCUZnJvbQ== 88958 -IGNvbnRhbWluYW50cw== 88959 -Sm9obm55 88960 -KGZpbA== 88961 -IGRlcmVu 88962 -IG91dGNyeQ== 88963 -IEpvaGFubg== 88964 -PFRhZw== 88965 -X3Nhbg== 88966 -IHN0ZGRldg== 88967 -IHBhcmFseXplZA== 88968 -IExleHVz 88969 -b3NhdGU= 88970 -IENoYXJzZXQ= 88971 -IFJlYWx0 88972 -PT8iLA== 88973 -KERlZmF1bHQ= 88974 -IFRyZWFzdXJlcg== 88975 -RWluZQ== 88976 -IHVudHJ1ZQ== 88977 -IGZpbmFuemk= 88978 -IGJlaGF2aW91cmFs 88979 -IG5pcHBsZQ== 88980 -IFJhZGljYWw= 88981 -IFBheg== 88982 -IE1haXNvbg== 88983 -LWVtcGxveWVk 88984 -IHdlcmVsZA== 88985 -IGpvcw== 88986 -IERpZWQ= 88987 -ZW50cmVwcmlzZQ== 88988 -JHJvd3M= 88989 -IHNwb29m 88990 -IMK7Lg== 88991 -IGtleXBvaW50cw== 88992 -IGN1cGNha2Vz 88993 -IHt9KTsKCg== 88994 -Y2hpbmU= 88995 -4oCL4oCL 88996 -LExPQ0FUSU9O 88997 -IHBseXdvb2Q= 88998 -IG1hZ2c= 88999 -IFJhbw== 89000 -IERQUg== 89001 -IGVib29rcw== 89002 -KXNpemU= 89003 -IHNwZWNpYWxpc2Vk 89004 -I2Fl 89005 -IG1pY2hhZWw= 89006 -IFNURE9VVA== 89007 -IFBlbGw= 89008 -QU1FUkE= 89009 -YW5nZWxv 89010 -IGluZ2lu 89011 -IG1BdXRo 89012 -IGxlZ2FsaXpl 89013 -IEN1YW5kbw== 89014 -IGNlcnRv 89015 -IGxpdHJlcw== 89016 -IEV4dHJhcw== 89017 -U0hPUlQ= 89018 -IHByZW1hdHVyZWx5 89019 -IFNlbWFwaG9yZQ== 89020 -SEVO 89021 -IGFtcGhpYg== 89022 -IGjDqQ== 89023 -RXhpdGluZw== 89024 -ZXVpbGxleg== 89025 -IFRNUHJv 89026 -LnByZWZlcmVuY2Vz 89027 -LmdldEluZm8= 89028 -w6l0aWNh 89029 -IiIiLg== 89030 -Lm5ld0FycmF5TGlzdA== 89031 -IGtyb24= 89032 -IEJMTA== 89033 -Y2xpbmU= 89034 -X2di 89035 -IFRvbWFz 89036 -cHJvYmFudGU= 89037 -SVRJT05BTA== 89038 -4buRaQ== 89039 -IExvZA== 89040 -SXNu 89041 -LHsK 89042 -IGtvbW11bg== 89043 -d2R4 89044 -Z2Vub21l 89045 -6YCj 89046 -dG9IYXZlTGVuZ3Ro 89047 -J0U= 89048 -IHDDumJsaWNh 89049 -IERldGVjdGVk 89050 -IF8KCg== 89051 -0YzRjg== 89052 -K1M= 89053 -Y2xvdGg= 89054 -Um90b3I= 89055 -Lm51bWVybw== 89056 -X3N0YW5k 89057 -R0ND 89058 -6rU= 89059 -X3Zw 89060 -X0ZBUg== 89061 -QWhlYWQ= 89062 -e31c 89063 -KGNvcnJlY3Q= 89064 -ImNyeXB0bw== 89065 -bW9kdWxv 89066 -X1VUSUxT 89067 -LlZhcg== 89068 -LW1lbg== 89069 -IHZlbmlhbQ== 89070 -IE1jQ29ybQ== 89071 -Z2V0TG9jYXRpb24= 89072 -W2NvZGU= 89073 -JWY= 89074 -IGRpZmZlcmVk 89075 -SVBBZGRyZXNz 89076 -IFN0cmF3YmVycnk= 89077 -IFNhaGFyYQ== 89078 -Y3JlYXRlQ2xhc3M= 89079 -IS8= 89080 -IG1lbWJlcnNoaXBz 89081 -IHByb25vdW5jZQ== 89082 -LkNvbnN0cmFpbnQ= 89083 -IEVucm9sbG1lbnQ= 89084 -IHJlbmV3YWJsZXM= 89085 -Lmd0 89086 -aXp6aWU= 89087 -cnp5 89088 -ZXJzZW4= 89089 -PD0k 89090 -REVMQVk= 89091 -IHNpZ25pbg== 89092 -IFBTVQ== 89093 -QXBwTmFtZQ== 89094 -fVwuWw== 89095 -RUdB 89096 -IGNpZW50 89097 -IFN5bm9wc2lz 89098 -IGxldHRlclNwYWNpbmc= 89099 -IGNoaWxkcw== 89100 -IFNjYWxpbmc= 89101 -KXByZXBhcmU= 89102 -IGNvbW11dGVy 89103 -U2xhc2g= 89104 -b3VzZXI= 89105 -IHdhdGVybWFyaw== 89106 -IFVJU2NyZWVu 89107 -b2xpYW4= 89108 -CXZlcnRpY2Vz 89109 -PkFjdGlvbg== 89110 -IGFwaA== 89111 -aGFuZHM= 89112 -IE9DQw== 89113 -SFU= 89114 -IHNlY2x1ZGVk 89115 -IHZpc2NlcmFs 89116 -IHZpZGVvZw== 89117 -IFNhbXVyYWk= 89118 -IFp1aw== 89119 -IFdpZG93 89120 -YWNjaW5l 89121 -IGxpbGxl 89122 -IFJ5ZGVy 89123 -IFByb2dyYW1tZXI= 89124 -RXhwb3J0ZXI= 89125 -IG1vdmltaWVudG8= 89126 -YXBhcw== 89127 -IGxlaWRlcg== 89128 -dWxhcmVz 89129 -aWVtZQ== 89130 -LWRlbnNpdHk= 89131 -ZGVzY2VuZGluZw== 89132 -KElU 89133 -IHNjcmFwZXI= 89134 -IGljZWJlcmc= 89135 -X0NSSVRJQ0FM 89136 -IGF1dGU= 89137 -X1N0eWxl 89138 -IE1BTA== 89139 -IEhlY3Rvcg== 89140 -LUNocmlzdGlhbg== 89141 -IGRpZmZlcmVudGlhdGVk 89142 -IEJpc29u 89143 -ICAgICAgIAk= 89144 -LnBvcHVsYXRpb24= 89145 -Umlv 89146 -LVRy 89147 -PVZhbHVl 89148 -IEx1ZnQ= 89149 -IEdpdWxpYW5p 89150 -55yf 89151 -Q291cG9u 89152 -IGhhY2llbmRv 89153 -44Od 89154 -cG9uY2U= 89155 -X3Jlc2lkdWFs 89156 -IGxp4buHdQ== 89157 -XHVmZg== 89158 -0L7QsdGF0L7QtNC40Lw= 89159 -IHJlc3BlY3Rv 89160 -IERlc2lyZWQ= 89161 -RGF0YVN0cmVhbQ== 89162 -LnNheA== 89163 -IG1vcA== 89164 -IEhhY2tlcg== 89165 -QU5UQQ== 89166 -QW5j 89167 -VmVudGE= 89168 -IFdvcmRwcmVzcw== 89169 -CWVmZmVjdA== 89170 -YWRhcHQ= 89171 -IEludGVydmlld3M= 89172 -IGRyYXdiYWNrcw== 89173 -QUxMRU5H 89174 -IGfDqW7DqXJhbA== 89175 -LWJhZGdl 89176 -UmVzaXN0YW5jZQ== 89177 -IE9TSQ== 89178 -dG91cm5hbWVudA== 89179 -IFJlcHV0YXRpb24= 89180 -IEVpc2VuaG93ZXI= 89181 -RmlsZWQ= 89182 -IGhlYnQ= 89183 -I1w= 89184 -Y3JlYXRlUXVlcnlCdWlsZGVy 89185 -5pyJ5pWI 89186 -dmFuY2Vk 89187 -Lkhhc0tleQ== 89188 -ZGRl 89189 -KHN0YXJ0VGltZQ== 89190 -IEluc3RhbGxlcg== 89191 -IEltcGw= 89192 -Y29hY2g= 89193 -IHByZWFjaGVk 89194 -IGJyZXdlZA== 89195 -SW5zdGFsbGVy 89196 -b2x2YWJsZQ== 89197 -IGFsYXM= 89198 -KHNwZWxs 89199 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 89200 -IGRlZmFtYXRpb24= 89201 -KEFyZw== 89202 -IHVzZXJEZXRhaWxz 89203 -IGxpY2Vuc29ycw== 89204 -IEludmVzdGlnYXRpb25z 89205 -IGRpbmVy 89206 -IGZpY3Q= 89207 -U3RpY2s= 89208 -TmVpZ2hib3I= 89209 -dG9UaHJvdw== 89210 -LXNlY3Rvcg== 89211 -IHJpc3VsdA== 89212 -4oCZOg== 89213 -Sk5JRW52 89214 -eXBpY2Fs 89215 -ZGVzaWduYXRpb24= 89216 -KHdw 89217 -IGNvbmZpcm1QYXNzd29yZA== 89218 -LWlvcw== 89219 -ICItIjsK 89220 -CWFzc2VydE5vdE51bGw= 89221 -YWRkRXJyb3I= 89222 -YXZyYXM= 89223 -Vm0= 89224 -KGpRdWVyeQ== 89225 -IFZpY3RpbXM= 89226 -IHJlbGlhbnQ= 89227 -IEJsaXR6 89228 -IG91dGFnZQ== 89229 -IGZsdW9yaWRl 89230 -IFROVA== 89231 -LkRpc2NsYWltZXI= 89232 -IFNOTVA= 89233 -dmFibHk= 89234 -IHBob3RvbnM= 89235 -LlJlYWRBc1N0cmluZ0FzeW5j 89236 -U2NoZWR1bGVk 89237 -IGpld2lzaA== 89238 -IEdlb2ZmcmV5 89239 -IEdyYW5ueQ== 89240 -fgo= 89241 -LW1lc3NhZ2Vz 89242 -KGdvYWw= 89243 -IGFyZ2VudA== 89244 -IFBlc3Q= 89245 -IGNvbmdyYXR1bGF0ZQ== 89246 -aW5vc2F1cg== 89247 -IHdoaXNwZXJz 89248 -IHNpc3RlbWFz 89249 -IEbDqQ== 89250 -L0luZGV4 89251 -Lk1JTExJU0VDT05EUw== 89252 -IGFjaGlldmFibGU= 89253 -IEJyaXR0YW55 89254 -KysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKys= 89255 -IFJldHVyblR5cGU= 89256 -IGluZml4 89257 -LmlzU3VjY2Vzcw== 89258 -LkNhdGVnb3JpZXM= 89259 -IG91dGxpZXI= 89260 -LkFzc2V0 89261 -b3RlYw== 89262 -IHdpemFyZHM= 89263 -IGJvb3Rsb2FkZXI= 89264 -X2Jlcg== 89265 -IHJlaGFiaWxpdA== 89266 -YW50b3I= 89267 -IFZpdm8= 89268 -IEdhcm1pbg== 89269 -b2JqZWN0SWQ= 89270 -QFBhdGg= 89271 -IMO6bmljYQ== 89272 -IFlvcmtlcnM= 89273 -R3VpZElk 89274 -JGVycm9ycw== 89275 -ICs9Cg== 89276 -IGF4aW9t 89277 -IFBTSQ== 89278 -IFN1Y2M= 89279 -IFNwb2thbmU= 89280 -ICciLiRf 89281 -IExO 89282 -Lm5ld0xpbmU= 89283 -IGludGVyc2VjdHM= 89284 -bGljaGtlaXQ= 89285 -IElBTQ== 89286 -LkRyb3BEb3duSXRlbXM= 89287 -IGNvdXJ0ZW91cw== 89288 -IFNtaXRoc29uaWFu 89289 -IEhtbQ== 89290 -UURlYnVn 89291 -c3RyYWlnaHQ= 89292 -X3NvbGQ= 89293 -QnVsaw== 89294 -VHJpU3RhdGU= 89295 -IGFkZEJ1dHRvbg== 89296 -IEhpcmluZw== 89297 -VHJhbnNwb3Nl 89298 -IFVJVGV4dFZpZXc= 89299 -aXN0ZW5jaWE= 89300 -L2NwcA== 89301 -INC/0L7Qu9GP 89302 -IENvb2tib29r 89303 -L0FwcGxpY2F0aW9u 89304 -Z2VuaWM= 89305 -IFdvb0NvbW1lcmNl 89306 -LHZlY3Rvcg== 89307 -IEJpdGU= 89308 -Lmh3 89309 -IGRvY2tpbmc= 89310 -IFRhbnRyYQ== 89311 -IFNWQw== 89312 -IE1hdXJpdA== 89313 -aWFsaWFz 89314 -IEF1cmU= 89315 -IGJvbHM= 89316 -TE9DSVRZ 89317 -IFdlc3Ricm9vaw== 89318 -IEJQTQ== 89319 -IEZleQ== 89320 -IFNvdmVyZQ== 89321 -IHBhbmRh 89322 -IHF1aXp6ZXM= 89323 -IGNyZW8= 89324 -c3BlZWNo 89325 -L2Rpcg== 89326 -INC40YHQv9C+0LvRjNC30L7Qsg== 89327 -IGZvdW5kYXRpb25hbA== 89328 -LWFwcGVuZA== 89329 -blRoZQ== 89330 -IGFwaVVybA== 89331 -LlhQQVRI 89332 -IExpbmd1 89333 -IEV4aGF1c3Q= 89334 -UGFraXN0YW4= 89335 -IG9tYXA= 89336 -IGZvbnRTdHlsZQ== 89337 -0LXRgdGC0Lg= 89338 -IG1hbnNsYXVnaHRlcg== 89339 -X0xvbmc= 89340 -IGNhcnBldHM= 89341 -Q2hlc3M= 89342 -ZWxpZ2h0 89343 -RHJhd2VyVG9nZ2xl 89344 -IFBhdHR5 89345 -X2Nyb3NzZW50cm9weQ== 89346 -IHR3ZWFraW5n 89347 -0YLRgw== 89348 -IENBTEM= 89349 -c2lw 89350 -IEpNUA== 89351 -X19fX19fX19fX19fX19fX18KCg== 89352 -VHJlZVZpZXc= 89353 -LXdhdmU= 89354 -IHBhc3R1cmU= 89355 -ZWxpbWluYXI= 89356 -IGVyeQ== 89357 -IHJlc3RsZXNz 89358 -6rWs 89359 -IG1hcmlhZ2U= 89360 -IEVsbGll 89361 -Xz0n 89362 -IHZtaW4= 89363 -S2ljaw== 89364 -LnRvb2xib3g= 89365 -IE1hcmlubw== 89366 -eXBzeQ== 89367 -c3RkYXJn 89368 -cHRyZGlmZg== 89369 -IFBlYWtz 89370 -X1ZhbA== 89371 -IGluZ2VzdA== 89372 -IGNvbXBz 89373 -RGViZQ== 89374 -IERlY2xhcmF0aW9ucw== 89375 -aXJjb24= 89376 -PWFsbA== 89377 -LkRlYnVnZg== 89378 -UHJlZGljdGlvbg== 89379 -IGRhdQ== 89380 -KE1lbWJlcg== 89381 -IGNoaWVmbHk= 89382 -L2FuaW1hdGU= 89383 -LkF0dGFjaA== 89384 -IGdhc3RyaWM= 89385 -IFVzZXJEZXRhaWxz 89386 -w7ZyZW4= 89387 -a29h 89388 -LWJvb3Q= 89389 -IHNwbGljZQ== 89390 -bGVh 89391 -b3Rp 89392 -W29w 89393 -U3F1YXJlZA== 89394 -IHNjcm9sbFRv 89395 -IE5ld2ZvdW5kbGFuZA== 89396 -CUVSUk9S 89397 -V2Fs 89398 -RU1BTEU= 89399 -R2V0WQ== 89400 -IGNhYmlucw== 89401 -IGFic2w= 89402 -Lm1peGVy 89403 -IGNkcg== 89404 -Y29uY2VydA== 89405 -IFN5bHZpYQ== 89406 -Qks= 89407 -5LuK5bm0 89408 -X0NMQU1Q 89409 -0YHRgtGA0YPQutGC0L7RgA== 89410 -L2dhbWVz 89411 -xZN1cg== 89412 -PGxvY2F0aW9u 89413 -IGNsb3NlQnV0dG9u 89414 -IEhhaXJzdA== 89415 -4bqhbw== 89416 -IGNydW1ibGluZw== 89417 -IHN1bGZhdGU= 89418 -IGFsZ3VpZW4= 89419 -IEpEQkM= 89420 -IEt2 89421 -UElQ 89422 -X3N1cmY= 89423 -IHXFvHl0aw== 89424 -IG1hbm5lZA== 89425 -IE9jY2FzaW9uYWxseQ== 89426 -b2Jqcw== 89427 -TWluaW1hbA== 89428 -LWRlc3M= 89429 -IFdBVg== 89430 -IEVycm9ySGFuZGxlcg== 89431 -IHNldExvY2F0aW9u 89432 -IGlldHM= 89433 -IHN1YnJvdXRpbmU= 89434 -IHRvbmd1ZXM= 89435 -X3F1aXo= 89436 -TWlsbGVy 89437 -IEJhc2VUeXBl 89438 -IFZ1ZXg= 89439 -aXJhdGU= 89440 -U2VyaW91c2x5 89441 -dHlwZWlk 89442 -IGt1dGpl 89443 -IHByZXNjcmliaW5n 89444 -X3N1cnZleQ== 89445 -LkN0 89446 -IGJsaW5kbHk= 89447 -LmdldExhYmVs 89448 -LCIpOwo= 89449 -IHBvdHJ6ZQ== 89450 -IFN3b3Jkcw== 89451 -U29ydGFibGU= 89452 -IEJsYWNrYnVybg== 89453 -IE1hdGE= 89454 -IHBvbmRz 89455 -IHByb3Rlc3RvcnM= 89456 -IEVuc2VtYmxl 89457 -OmZvY3Vz 89458 -IGl0YWxpYW5h 89459 -IGRvcm1hbnQ= 89460 -IE5lbA== 89461 -SU5DTFVERQ== 89462 -KENvbnY= 89463 -IGJ1Zmxlbg== 89464 -IENETg== 89465 -LnhodG1s 89466 -SGRy 89467 -IGNhcmNpbm9tYQ== 89468 -IFdvcmNlc3Rlcg== 89469 -bmRs 89470 -dXNlUmFs 89471 -dXNlUmFsYXRpdmU= 89472 -dXNlUmFsYXRpdmVJbWFnZVBhdGg= 89473 -IHRha2Vhd2F5 89474 -ZWxlbWVudEd1aWRJZA== 89475 -LmxhYmVsWA== 89476 -W0lE 89477 -QUxFUg== 89478 -CXV2 89479 -PigpLT4= 89480 -L2xp 89481 -K2xlbg== 89482 -IHByb3BlbA== 89483 -IGNhYm8= 89484 -XCIiKTsK 89485 -IHZvY2F0aW9uYWw= 89486 -LXBpbGw= 89487 -Lm5sbQ== 89488 -IGVyb3RpY2E= 89489 -b3BvdA== 89490 -bGFuZHNjYXBl 89491 -aW5zaw== 89492 -IHBsYWNlbWVudHM= 89493 -LnNldEF1dG8= 89494 -IGhvbWljaWRlcw== 89495 -X0ZpZWxkT2Zmc2V0VGFibGU= 89496 -Omw= 89497 -IGFubm90YXRl 89498 -LXJpc2U= 89499 -LGFscGhh 89500 -IGludGVydmVuaW5n 89501 -YW1iaQ== 89502 -Lj0nPA== 89503 -IHBhcmxlcg== 89504 -772l772l 89505 -IGNvbXBseWluZw== 89506 -LWhhbmRsZQ== 89507 -IGludGVycnVwdGlvbnM= 89508 -cGxlcnM= 89509 -cm91cHM= 89510 -X0RlZg== 89511 -IHBpY2tlclZpZXc= 89512 -IHBpZXJjZWQ= 89513 -IGVyYWRpY2F0ZQ== 89514 -bW9ieA== 89515 -W3RyYWlu 89516 -RGVmZXJyZWQ= 89517 -IHRvdGFsZWQ= 89518 -Q2hpbGRJbmRleA== 89519 -IFJlY29tbWVuZGF0aW9ucw== 89520 -X1dPUkRT 89521 -IHNpZ25pZnk= 89522 -IEFlcm8= 89523 -X2Jvb3RzdHJhcA== 89524 -X1Vw 89525 -cHJvZHVjdE5hbWU= 89526 -LWFueQ== 89527 -IHBwbA== 89528 -X1BVVA== 89529 -IGx5b24= 89530 -X0lMaXN0 89531 -IMOpY3JpdA== 89532 -KGd1aWQ= 89533 -IGNvbnRhZ2lvdXM= 89534 -X1NlbGVjdGlvbg== 89535 -L2xhbmd1YWdl 89536 -cXVhbg== 89537 -IGFjdXB1bmN0dXJl 89538 -IG9mcmVjZQ== 89539 -CVJURQ== 89540 -Lkd1bmE= 89541 -IHNlbnNlZA== 89542 -IEtyYWs= 89543 -IHVubHVja3k= 89544 -YXZpYw== 89545 -dGl0bGVMYWJlbA== 89546 -IGhheXN0YWNr 89547 -LmJpdG1hcA== 89548 -IENvdW5zZWxpbmc= 89549 -UExBVEZPUk0= 89550 -X1Rvb2w= 89551 -VGFt 89552 -V2VyZQ== 89553 -0YDQsNC3 89554 -X1NQRQ== 89555 -IG9uQW5pbWF0aW9u 89556 -PTw/PSQ= 89557 -IFNsZQ== 89558 -IEd1aW5uZXNz 89559 -IHR3ZWFrZWQ= 89560 -LXByZXNzdXJl 89561 -X21vbnRocw== 89562 -KW8= 89563 -UHJvYmFiaWxpdHk= 89564 -IENhbXBvcw== 89565 -LkNPTkZJRw== 89566 -VmludGFnZQ== 89567 -PndpbmRvdw== 89568 -IEZhY3RvcnlCb3Q= 89569 -cG9zdGdyZXNxbA== 89570 -IHRhYmxldG9w 89571 -IENhdGE= 89572 -aG9j 89573 -X2FzYw== 89574 -4oKs4oCc 89575 -QmFja1N0YWNr 89576 -w6lv 89577 -IFNvdXM= 89578 -c2V0dGVy 89579 -JyldKQo= 89580 -dmVsbGU= 89581 -IEFsdW1pbml1bQ== 89582 -eEJB 89583 -Lm1vbmdv 89584 -IFZhcmlhdGlvbg== 89585 -eXR1dA== 89586 -bmVobWVy 89587 -4buDbQ== 89588 -IGVmZmVjdGVk 89589 -ICoqLw0K 89590 -IHJlY291bnRlZA== 89591 -UHJhY3RpY2U= 89592 -Q0FOQ0VM 89593 -Y3puaWU= 89594 -TGFycnk= 89595 -IHFh 89596 -IEh1ZmZtYW4= 89597 -Z2V0RHJhd2FibGU= 89598 -IGVuZnJlbnQ= 89599 -IG9uQ2FuY2VsbGVk 89600 -IGxlbw== 89601 -IFhTUw== 89602 -IEh1cnJpY2FuZXM= 89603 -IGpvbg== 89604 -IFRlc3RlZA== 89605 -IE1vcmFs 89606 -IGJlZHRpbWU= 89607 -IEpBRFg= 89608 -IGVjaGFuZw== 89609 -IG51ZXN0cmFz 89610 -UENN 89611 -KS4u 89612 -IOyImOyglQ== 89613 -IGJvcmRlcmxpbmU= 89614 -IGFzc2lzdGly 89615 -IEhlbHBz 89616 -IERpdmU= 89617 -X3NuZA== 89618 -d2l0 89619 -X2JsZW5k 89620 -IGlzRmlyc3Q= 89621 -IGhlYXBx 89622 -KCc9 89623 -IGFzc2VtYmxlcg== 89624 -IE15c3RpYw== 89625 -b3JnaA== 89626 -IGhpam9z 89627 -X0tIUg== 89628 -KGRlY29kZWQ= 89629 -IFFVSQ== 89630 -INeR 89631 -IGNvbnRyb2xJZA== 89632 -U3BhY2Vy 89633 -LmFnZ3JlZ2F0ZQ== 89634 -IHNoYWx0 89635 -X3RyYXA= 89636 -IEZhbWlsaWU= 89637 -zrg= 89638 -b3J0YQ== 89639 -LlBvc3RNYXBwaW5n 89640 -7LA= 89641 -ICcuLics 89642 -esOh 89643 -L2FybQ== 89644 -LmdhbGxlcnk= 89645 -IGltcGVjY2FibGU= 89646 -IHdpbmRvd0hlaWdodA== 89647 -c2xhY2s= 89648 -ZmZi 89649 -X3Fw 89650 -bGFkZW4= 89651 -IFRFUk0= 89652 -c2V0TGFiZWw= 89653 -IFNpbmdsZUNoaWxkU2Nyb2xsVmlldw== 89654 -ecO8aw== 89655 -IHB1bHVtaQ== 89656 -LWdhcA== 89657 -dW5pYWNpZA== 89658 -CWhvbGRlcg== 89659 -LmFkZEZpZWxk 89660 -IHRyaXBsZXM= 89661 -IEp1ZGdtZW50 89662 -IENlbmE= 89663 -cGFyc2Vycw== 89664 -LmRyYXdUZXh0 89665 -INC60LDQttC0 89666 -IGFjY3Q= 89667 -aGl2ZQ== 89668 -IG11c2lxdWU= 89669 -IFlheg== 89670 -LXBvc3Rz 89671 -IGZpbHM= 89672 -IC8vew0K 89673 -X3B1dHM= 89674 -IFN0YXR1ZQ== 89675 -ZGlhbW9uZA== 89676 -U3RvcmFnZVN5bmM= 89677 -IHNodXRz 89678 -IGdldHRpbWVvZmRheQ== 89679 -IEFBQkI= 89680 -aWNoZXJu 89681 -Z2V0TG9jYWxl 89682 -aW50cmVl 89683 -IGZydWl0ZnVs 89684 -QmVhcg== 89685 -IHBsdW1iZXI= 89686 -cWlk 89687 -Q0hJUA== 89688 -IG1vdGl2YXRpbmc= 89689 -IGVzY2FsYXRl 89690 -LmJ1bGs= 89691 -IFBsYXlncm91bmQ= 89692 -X21pcnJvcg== 89693 -IFBlZWw= 89694 -IGRhbmU= 89695 -aW52b2ljZXM= 89696 -SGFzQmVlblNldA== 89697 -LXZlcnRpY2Fs 89698 -IEZyYW5jZXNjbw== 89699 -IEFTQQ== 89700 -INC60L7Qu9C40YfQtdGB0YLQstC+ 89701 -w6Bu 89702 -Rm91cnRo 89703 -IENyZWF0ZVRhYmxl 89704 -Y2N0b3I= 89705 -IGZyYW50aWM= 89706 -YWFi 89707 -IEthcmFjaGk= 89708 -X2ltYWc= 89709 -IG5hdHV1cg== 89710 -RWF0 89711 -IHN0dW1w 89712 -IHJvbGxlcnM= 89713 -IHRyYWl0ZW1lbnQ= 89714 -INC/0YDQvtC0 89715 -IHJlYWxpc3RpY2FsbHk= 89716 -IGVQdWI= 89717 -IFphZw== 89718 -ZGFtbg== 89719 -IEFubmV4 89720 -cGVjaWVz 89721 -KGV4aXQ= 89722 -IHNwZWN0YXRvcg== 89723 -IEJ1bGdhcmlhbg== 89724 -IG1lZ2V0 89725 -IG1hdHVyZXM= 89726 -IGRldGVjdGlvbnM= 89727 -IHphaGw= 89728 -ZW5lZml0 89729 -YWtvdg== 89730 -IGFkdWx0b3M= 89731 -bWlkZGxld2FyZXM= 89732 -aXNPYmplY3Q= 89733 -S2Vubg== 89734 -IHVuZXRoaWNhbA== 89735 -c3VibmV0 89736 -R3JhcGhRTA== 89737 -IEdhZWw= 89738 -LkRyb3BvdXQ= 89739 -IGJ1cmVhdWNyYXRz 89740 -IFJlZGVtcHRpb24= 89741 -LkR0bw== 89742 -LkV2YWx1YXRl 89743 -IG9nZ2k= 89744 -IHRyYXRhbWllbnRv 89745 -IHJlY2FsbGluZw== 89746 -aXN0aW5ndWlzaA== 89747 -L3JlbGVhc2U= 89748 -X1dST05MWQ== 89749 -CW1rZGly 89750 -VHlwZUVudW0= 89751 -IERBUks= 89752 -5rWB 89753 -IFZhcG9y 89754 -IGF0b2w= 89755 -CWluc3Q= 89756 -LmApOwo= 89757 -L2Vs 89758 -IHJlY2xhaW1lZA== 89759 -w59lcmRlbQ== 89760 -X2xvc3Q= 89761 -IEFsYQ== 89762 -INC+0YjQuNCx 89763 -IEJhcnRo 89764 -Q29sb24= 89765 -b3Bvcg== 89766 -X3Bhc3N3ZA== 89767 -X2V4Y2x1ZGU= 89768 -QVBB 89769 -Zmxvd2Vycw== 89770 -IEVib29r 89771 -IFNUQQ== 89772 -VU5T 89773 -X0RJU1BBVENI 89774 -QUNJw5NO 89775 -dGVybWluYXRpb24= 89776 -IG5lc3RsZWQ= 89777 -YWRyYXRpYw== 89778 -Um93QW5pbWF0aW9u 89779 -X2tt 89780 -IHJvbmQ= 89781 -XV0+PC8= 89782 -5L2Z 89783 -IGNvc3BsYXk= 89784 -IG1pbGxlbm5pdW0= 89785 -X3NlcmlhbGl6ZQ== 89786 -IHZlcnNjaGllZGVuZW4= 89787 -YW50dA== 89788 -IEFtaWQ= 89789 -Y3JldGlvbg== 89790 -KT8k 89791 -IHRvd2luZw== 89792 -LmZpbA== 89793 -LkZpbGVXcml0ZXI= 89794 -IGFpcw== 89795 -IGVTcG9ydHM= 89796 -cHJ0 89797 -SVBB 89798 -LkZBTFNF 89799 -IHByaWNr 89800 -RW5kaW5n 89801 -IHByw6lzaWRlbnQ= 89802 -X2dseXBo 89803 -IHN1cHBsZW1lbnRlZA== 89804 -IGNvbnRhcg== 89805 -Ii4kXw== 89806 -IEJ1eWVycw== 89807 -dWph 89808 -IFRpbWVab25l 89809 -ZW5uZW50 89810 -SW5Qcm9ncmVzcw== 89811 -IFN1c3RhaW5hYmlsaXR5 89812 -IFByb3NwZXI= 89813 -Q29udG91cnM= 89814 -IHN0YXJ0bGVk 89815 -X2xlYXN0 89816 -IENvdmVudA== 89817 -Y2huaXR0 89818 -IE1pbGt5 89819 -ICItPg== 89820 -ZXRhaw== 89821 -IHR1c3Nlbg== 89822 -LXBheWluZw== 89823 -X2FjY2Vzc2libGU= 89824 -QmF0bWFu 89825 -KGl0cg== 89826 -SUFMSVpFRA== 89827 -IFRleHRBcmVh 89828 -YW5rZQ== 89829 -X0pVTVA= 89830 -IGJlaGF2ZWQ= 89831 -LG9wdGlvbnM= 89832 -eGl2 89833 -LlBMTA== 89834 -cXg= 89835 -Lm9uTmV4dA== 89836 -IHZlcmlmaWVy 89837 -IGR1xbw= 89838 -IEZ1a3VzaGltYQ== 89839 -IENPUlBPUkFUSU9O 89840 -X3RE 89841 -IE1lYWRvdw== 89842 -IHByb3llY3Rvcw== 89843 -ICgnXA== 89844 -IEJhcmNsYXlz 89845 -IGxlZ2FsaXR5 89846 -IGhhbWJ1cmdlcg== 89847 -IGVpbnM= 89848 -SW5kaWFuYQ== 89849 -IFRLZXk= 89850 -Y2xvYWs= 89851 -PGFsZ29yaXRobQ== 89852 -IHByZWFjaGVy 89853 -e2xuZw== 89854 -LmFydGljbGVz 89855 -c2V0SW1hZ2U= 89856 -UmVuYW1l 89857 -IGJsb3Nzb20= 89858 -IEJsb3Nz 89859 -IHV1cg== 89860 -IGRhZHM= 89861 -IFRpdGFuaWM= 89862 -ICAgICAgICANCg0K 89863 -IG9yZGluYW5jZXM= 89864 -IG3DpG5u 89865 -IGVyaw== 89866 -IGRpc3RpbGxlZA== 89867 -IMOkbA== 89868 -IHJ1cHR1cmU= 89869 -IENhbWVyYXM= 89870 -w7luZw== 89871 -IGhhaXJzdHlsZXM= 89872 -IGVtYnJ5b3M= 89873 -4oCdCg== 89874 -Lk5hdg== 89875 -IHN0cm0= 89876 -CXVzYWdl 89877 -LkFJ 89878 -IFRPVUNI 89879 -IElsbGVnYWxBY2Nlc3NFeGNlcHRpb24= 89880 -6rKw 89881 -a29uZWtzaQ== 89882 -ISIp 89883 -IGVzY2Fw 89884 -dWRpb3M= 89885 -c3RhcnR0aW1l 89886 -IG1laW5lbQ== 89887 -IFNwaXJhbA== 89888 -IEVyZWN0aWxl 89889 -aXZhbGVuY2U= 89890 -IGl0ZW1UeXBl 89891 -IGFiYWl4bw== 89892 -VmVydHM= 89893 -dGFraW5n 89894 -cHN0 89895 -IE9zY2Fycw== 89896 -IER4 89897 -ZXR0eQ== 89898 -TUFM 89899 -IE5lZWRsZQ== 89900 -IENPTVBVVEVS 89901 -5Lu75Yqh 89902 -IG5ld1g= 89903 -ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAK 89904 -cGxldmVs 89905 -QUNFTUVOVA== 89906 -IEpvaGFu 89907 -UG9pbnRG 89908 -IHJlc3Ryb29t 89909 -dmVybw== 89910 -IGVsxZE= 89911 -cHJvZHVr 89912 -IFlFQVJT 89913 -CWFjdHVhbA== 89914 -VVBMRQ== 89915 -Q29udmVydGlibGU= 89916 -IHBvcnJm 89917 -SW5qZWN0ZWQ= 89918 -X2JvdGg= 89919 -L0dhdGU= 89920 -Y2FsY3VsYXRvcg== 89921 -ZW1haWxlcg== 89922 -LlBvZA== 89923 -IFpvdA== 89924 -X3NtYXJ0 89925 -YmFzaXM= 89926 -PENvbG9y 89927 -IGNyYXZpbmdz 89928 -RHJpdmVycw== 89929 -KGNvcw== 89930 -ZGF0YWJsZQ== 89931 -LW1ldGFs 89932 -IFBj 89933 -LmNvcHlPZg== 89934 -IG9yaWVudGF0aW9ucw== 89935 -CWFzdA== 89936 -IFpvbWJpZXM= 89937 -IGJvbWJlZA== 89938 -SG9zdG5hbWU= 89939 -X3JhaXNlcw== 89940 -bWVuc2FnZW0= 89941 -IGNvcnRpc29s 89942 -IEZpb25h 89943 -bGljb3M= 89944 -aGVhdnk= 89945 -IOqwgOyguA== 89946 -b21lbmNs 89947 -IGN1bHR1cmVk 89948 -IGFydGlrZWw= 89949 -xaHDrQ== 89950 -amRr 89951 -IHZhbmRhbGlzbQ== 89952 -IH1dKTsK 89953 -U3RyYWlnaHQ= 89954 -IHJlaGVhcnNhbA== 89955 -RWRpdGlvbg== 89956 -IEluc3Bpcg== 89957 -CXdj 89958 -IGZvcm11bGF0ZQ== 89959 -YW56ZWlnZW4= 89960 -IHBhdGhvbG9naWNhbA== 89961 -IGtlbm5lbmxlcm5lbg== 89962 -Pnsi 89963 -IGRpY2Vk 89964 -IGJyYWNlbGV0cw== 89965 -CQkgICAgCg== 89966 -Kj4q 89967 -L3RhcmdldA== 89968 -LkFnZW50 89969 -Lm1hZ2lj 89970 -IGlkZW9sb2dpZXM= 89971 -VFJBQ0s= 89972 -X2luZGl2aWR1YWw= 89973 -PGRlY2x0eXBl 89974 -IFJFQ0VJVkU= 89975 -L2Jvb3Q= 89976 -OkB7 89977 -UU0= 89978 -IE1hbmRhbA== 89979 -TkFNRVNQQUNF 89980 -IHRlcmNlcg== 89981 -IFJlZ2dpZQ== 89982 -IE5pY2hvbHNvbg== 89983 -IEZ1bHRvbg== 89984 -c3Rha2luZw== 89985 -IHJlc29uYXRl 89986 -bHBhcnI= 89987 -IGNvbnZlcnRlcnM= 89988 -ICgiLw== 89989 -IE1hcmxpbnM= 89990 -SW5mb3JtZQ== 89991 -Jz0+Wyc= 89992 -IHJvYmVydA== 89993 -IEhJTQ== 89994 -d2Vicw== 89995 -LnRyYWlsaW5nQW5jaG9y 89996 -LmFzY2lp 89997 -IE1hc2M= 89998 -IHRlY2hubw== 89999 -ZXR4dA== 90000 -CSAgICAgICAgCg== 90001 -zrHOuQ== 90002 -KFNlcQ== 90003 -ID8+Ojwv 90004 -IFBlYg== 90005 -W3NlbGVjdGVk 90006 -SkVDVEVE 90007 -Q2FzdEV4Y2VwdGlvbg== 90008 -P2Y= 90009 -IGV5ZXdpdG5lc3M= 90010 -IG1lbm8= 90011 -IERhbWllbg== 90012 -X0lFbnVtZXJhdG9y 90013 -IC4uLi4uLi4uLi4uLi4uLi4= 90014 -LlNFTEVDVA== 90015 -IGNyYXk= 90016 -X3BhcGVy 90017 -LlJvbGxiYWNr 90018 -SURFT1M= 90019 -cnBhcnI= 90020 -aW5lYXI= 90021 -X1JlbA== 90022 -IFdpbGRl 90023 -IFdvbmRlcmxhbmQ= 90024 -IFNodWZmbGU= 90025 -IHN0cmlrZW91dHM= 90026 -c2lnbW9pZA== 90027 -ISgiew== 90028 -ZXBhbQ== 90029 -IHJpY2huZXNz 90030 -IGVuZGVhdm91cg== 90031 -bWVudUl0ZW0= 90032 -INCf0L7Qu9GD0Yc= 90033 -IGZydXN0cmF0aW9ucw== 90034 -X3N1YnNjcmliZQ== 90035 -IGJvb3pl 90036 -IExpY2h0 90037 -IHBlYXNhbnQ= 90038 -IHdlaWdodGluZw== 90039 -IOW/ 90040 -QWN0aW9uQ29kZQ== 90041 -LnRyYWNrcw== 90042 -IMOY 90043 -IG1pbGxpb25haXJl 90044 -KHVy 90045 -J10pCgoK 90046 -ICIuJF8= 90047 -X0VERUZBVUxU 90048 -IGN1cmxz 90049 -X0NvbUNhbGxhYmxlV3JhcHBlcg== 90050 -LnNldFZpZXdwb3J0 90051 -IGRlbmQ= 90052 -IGF1dG91cg== 90053 -IEZvdXJpZXI= 90054 -IGJvaWxz 90055 -IEpQRw== 90056 -IGRpZ3M= 90057 -IGNvbXBsYWlucw== 90058 -LWxpbmVk 90059 -IEJsYWRlcw== 90060 -X2RpY3Rz 90061 -IElwcw== 90062 -cmVmZXJlcg== 90063 -IGFueWhvdw== 90064 -YW50YXI= 90065 -LXNoZWV0 90066 -CXBsYXk= 90067 -aWVyY2U= 90068 -Lk1lc3NhZ2luZw== 90069 -6KeB 90070 -CXByb2dyZXNz 90071 -LkRhdGFWaXN1YWxpemF0aW9u 90072 -IFN0b3Bz 90073 -SW50ZXJ2YWxTaW5jZQ== 90074 -QGJyaWVm 90075 -LndpbmQ= 90076 -IGdldElucHV0 90077 -IEtB 90078 -IFJFU1BPTlM= 90079 -IHRhcmc= 90080 -dmlzdWFsaXphdGlvbg== 90081 -IEVzcGHDsQ== 90082 -bmllcg== 90083 -IERvdmU= 90084 -X2lzcg== 90085 -IEFQUExZ 90086 -YmVkbw== 90087 -W117Cg== 90088 -IGV2YWN1YXRl 90089 -IG1pY3Jvc2NvcGlj 90090 -5q2j56Gu 90091 -ZXJvdA== 90092 -LW9wZXJhdGl2ZQ== 90093 -aWt1dA== 90094 -IGRibA== 90095 -IGFqb3V0 90096 -Lml4 90097 -ICAgICAgICAKICAgIAo= 90098 -dGVzdGU= 90099 -bml2ZWw= 90100 -LnNuYXA= 90101 -dXR6dA== 90102 -LmlzQWRtaW4= 90103 -KElD 90104 -IG9iZW4= 90105 -IEVmZmljaWVudA== 90106 -RERldmljZQ== 90107 -IGluZGVtbg== 90108 -IGZyb3pl 90109 -LHJw 90110 -IGRlY2VtYmVy 90111 -57uZ 90112 -IG1lbG9kaWVz 90113 -IEVUQQ== 90114 -44GT44KT44Gr44Gh44Gv 90115 -IHF1YWxjaGU= 90116 -IHNldERlZmF1bHRDbG9zZU9wZXJhdGlvbg== 90117 -T1JJQQ== 90118 -IHphZw== 90119 -IGFsbG93YW5jZXM= 90120 -L3Bo 90121 -LVRva2Vu 90122 -IFBvdQ== 90123 -IG1pbmlzdHJpZXM= 90124 -LkxPR0lO 90125 -IHNlYXJjaFRlcm0= 90126 -IGh1cnJpY2FuZXM= 90127 -IEZsb3Vy 90128 -IFNVUw== 90129 -VGhlbWVz 90130 -cmVlY2U= 90131 -IGVudHJldg== 90132 -RFhWRUNUT1I= 90133 -IEJyZW5kYQ== 90134 -RXJyb3JNc2c= 90135 -OildOwo= 90136 -IGRvbWluYQ== 90137 -IEludmlzaWJsZQ== 90138 -PD4oIg== 90139 -cHV0Yw== 90140 -SEFWRQ== 90141 -RXZhbHVhdG9y 90142 -bWF0Y2hpbmc= 90143 -LW5hbWVz 90144 -IGxhaA== 90145 -X1lVVg== 90146 -5pyN5Yqh5Zmo 90147 -LldSSVRF 90148 -KTpc 90149 -LWRlZmluaXRpb24= 90150 -IGNoaW1uZXk= 90151 -LmNscw== 90152 -a25vd2xlZGdl 90153 -IEFsZXhhbmRyZQ== 90154 -IGNvbGVn 90155 -b8WbY2k= 90156 -LkNobw== 90157 -IHNvZnRlbmVk 90158 -IHJvdGF0ZXM= 90159 -LXN0YXRlcw== 90160 -6rc= 90161 -dmlvbGVudA== 90162 -IDopCg== 90163 -IGFjY2nDs24= 90164 -bmlrYQ== 90165 -IExhdHRlcg== 90166 -X0Zsb2F0 90167 -IGVncmVnaW91cw== 90168 -b2RpYWw= 90169 -U3lub3BzaXM= 90170 -KHhp 90171 -IH0sew== 90172 -Y3h4 90173 -RW1tYQ== 90174 -IENvbmN1cnJlbnRIYXNoTWFw 90175 -X0NhbWVyYQ== 90176 -IHBlYW51dHM= 90177 -44Kz44Oh44Oz44OI 90178 -X2JlZA== 90179 -IGVycm9yQ2FsbGJhY2s= 90180 -IFBhcHVh 90181 -LFRydWU= 90182 -tpo= 90183 -IHN0YWRpdW1z 90184 -IGtub2Jz 90185 -aWZpY2FjaW9uZXM= 90186 -IHB1cnBvc2VseQ== 90187 -IFB1cmVDb21wb25lbnQ= 90188 -INC60LvQuA== 90189 -LlRyYWNr 90190 -c3Nj 90191 -KEpvYg== 90192 -KEh0dHBDb250ZXh0 90193 -IGNob2lzaXI= 90194 -IOy7 90195 -IGF1c3A= 90196 -dXBwZW4= 90197 -QWR2ZW50dXJl 90198 -IEZMQUM= 90199 -IGFwcGVsbGFudA== 90200 -ICgoIg== 90201 -z4c= 90202 -IHRyaWY= 90203 -IGR1cmF0aW9ucw== 90204 -IE5HWA== 90205 -LmJw 90206 -YWN0aW9uRGF0ZQ== 90207 -Lmluc3RhbnQ= 90208 -LVJlcXVlc3RlZA== 90209 -JyYm 90210 -INGH0LXRgA== 90211 -PWJvb2w= 90212 -IGxvcmRz 90213 -bGljaW5n 90214 -IG1hcmlu 90215 -IGJsaW5kZWQ= 90216 -L2xheW91dHM= 90217 -ZmVpdG8= 90218 -aXp6bGluZw== 90219 -RXZ0 90220 -IGJ1bGxpc2g= 90221 -ZXhjbHVzaXZl 90222 -4oCZZXM= 90223 -LmdldE93blByb3BlcnR5RGVzY3JpcHRvcg== 90224 -IGJhcHRpemVk 90225 -INGB0LvRg9GH 90226 -IENlY2ls 90227 -LmVmZmVjdHM= 90228 -IGNyeXB0b2dyYXBoaWM= 90229 -IFZpbGxl 90230 -dWZ0 90231 -IEFudGhlbQ== 90232 -IHNlZWtlcg== 90233 -IG5pY2tuYW1lZA== 90234 -IGNhbXBncm91bmQ= 90235 -IGFjdGlvbkJhcg== 90236 -IEVwaXNvZGVz 90237 -IC0tLS0tLS0tCg== 90238 -QnVpbGRlckZhY3Rvcnk= 90239 -X1VOU1VQUE9SVEVE 90240 -VklMTEU= 90241 -LlJlZ2lzdHJ5 90242 -VG9uaWdodA== 90243 -IG1ha3M= 90244 -IGFkZG9ucw== 90245 -IERlY3J5cHQ= 90246 -LnNraWxscw== 90247 -KGZo 90248 -IGp1Z2c= 90249 -IENvdXBsZXM= 90250 -IEFtaXI= 90251 -ID09PT09PT09PT0= 90252 -IGVuZGVyZWNv 90253 -LlN0cmluZ3M= 90254 -IGhhcm1pbmc= 90255 -IGJ1c3RsaW5n 90256 -KGZpcnN0TmFtZQ== 90257 -LnNwYXJzZQ== 90258 -SVRP 90259 -ICAgICAgICAgICAgICANCg== 90260 -5p2l5rqQ 90261 -b2RlZ2E= 90262 -YW5hZ2Fu 90263 -LkhhbmRsZXJGdW5j 90264 -IHRpbmRlcg== 90265 -ICMo 90266 -IGltYWdpbmFibGU= 90267 -IGF1bg== 90268 -UHJlc2VuY2U= 90269 -UGFja2FnZU1hbmFnZXI= 90270 -IGx1ZGljcm91cw== 90271 -acOobWU= 90272 -IGdldE9iamVjdA== 90273 -Ym94aW5n 90274 -IHNxdWlk 90275 -w6p0ZXM= 90276 -RGFlbW9u 90277 -X2xpa2Vz 90278 -hrU= 90279 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 90280 -Lnd3dw== 90281 -c3NlbA== 90282 -ZXRlY3Rpb25z 90283 -ZGFl 90284 -L2Rvd25sb2Fkcw== 90285 -IENsYXNzaWZpZXI= 90286 -X1NVQkpFQ1Q= 90287 -emVnbw== 90288 -X0dST1VQUw== 90289 -YWN0aWNlcw== 90290 -X2xpdGU= 90291 -IGRhbm1hcms= 90292 -L2Js 90293 -YXB5cnVz 90294 -VElNRVI= 90295 -IFNjcmlwdHVyZXM= 90296 -0Y/Rgg== 90297 -c3Bh 90298 -Ikc= 90299 -IHBlbmV0cmF0aW5n 90300 -IGNvbmZvcm1pdHk= 90301 -bmV3bGluZQ== 90302 -IGx5bg== 90303 -IE1NUA== 90304 -IElOVEVSRkFDRQ== 90305 -IEFjdGlvblR5cGVz 90306 -LmNyaXRlcmlh 90307 -4buRbmc= 90308 -IHJlc3RpdHV0aW9u 90309 -CUZPUg== 90310 -PHBhdGg= 90311 -PT8iOwo= 90312 -KHBlcmNlbnQ= 90313 -bmRv 90314 -IEFDTQ== 90315 -CWN0 90316 -QGE= 90317 -IHTDug== 90318 -IHNwb3R0aW5n 90319 -w7xybg== 90320 -IEdFUg== 90321 -LndyaXRlVmFsdWU= 90322 -X2Jsb2NrZWQ= 90323 -WW1k 90324 -IGluZWZm 90325 -IFJhZGlhdGlvbg== 90326 -IE9pbGVycw== 90327 -QmVlcg== 90328 -cm90cw== 90329 -IFRyb3Q= 90330 -cm5h 90331 -cG9ydGVy 90332 -ZW5lcnk= 90333 -IHBvcm5vZmlsbQ== 90334 -65SU 90335 -X2Nr 90336 -LkNvbXB1dGU= 90337 -IFtdCgoK 90338 -Z2l1bQ== 90339 -IFRFTEU= 90340 -IEluc3RhbmNlcw== 90341 -Kkk= 90342 -IHdpcmVUeXBl 90343 -b25pdW0= 90344 -ZXNoaXJl 90345 -IHB1dGNoYXI= 90346 -IGF3YWtlbmVk 90347 -LmRlZ3JlZQ== 90348 -aGVpdGVu 90349 -LWF3YWl0ZWQ= 90350 -IG5ldXJvdHJhbnM= 90351 -LXRlc3RpZA== 90352 -CgogICAgCg== 90353 -IOe7kw== 90354 -IGtpbm8= 90355 -X0RBWVM= 90356 -IFZhbGVyaWU= 90357 -bnRpdHk= 90358 -QEJlYW4= 90359 -ZXRDb2Rl 90360 -PFJlbmRlcmVy 90361 -IiIK 90362 -IGJlcm4= 90363 -IHRvdGFsaXRhcmlhbg== 90364 -Y2xpbmlj 90365 -IE3DvG5jaGVu 90366 -bm9pbnNwZWN0aW9u 90367 -aXNjZQ== 90368 -X3R1cGxlcw== 90369 -LlBvaW50cw== 90370 -IHBhc3RvcmFs 90371 -SmFr 90372 -a2VuaW5n 90373 -L2NvbHVtbg== 90374 -LXByb2R1Y2luZw== 90375 -IGFib2xpc2g= 90376 -ZmVhcw== 90377 -cmVzcG9uc2VEYXRh 90378 -cmVkaXJlY3RUb1JvdXRl 90379 -IG9ic2VydmF0aW9uYWw= 90380 -cE5leHQ= 90381 -enRl 90382 -Q2hvaWNlcw== 90383 -CUxDRA== 90384 -JlM= 90385 -IGJpbGxpb25haXJlcw== 90386 -X0VPRg== 90387 -IGNvaG9ydHM= 90388 -YW5rZW4= 90389 -LmNvbWJpbmU= 90390 -KE9wdGlvbmFs 90391 -X0NPTlNPTEU= 90392 -QWN0aXZpdHlJbmRpY2F0b3JWaWV3 90393 -IHBoYXJtYWNpc3Q= 90394 -IERvdWdo 90395 -IE9wZXJhdGlvbmFs 90396 -57I= 90397 -IGphbXM= 90398 -U29sbw== 90399 -CWR1cmF0aW9u 90400 -LnJt 90401 -IFRvbmk= 90402 -LmxlYXZl 90403 -IHB1ZWRh 90404 -IEZheQ== 90405 -RGV0YWNo 90406 -Lk1heGltaXplQm94 90407 -IG1hcnR5cg== 90408 -IGhhemU= 90409 -L25l 90410 -IG1hbW1h 90411 -c2VsZWN0b3JNZXRob2Q= 90412 -IHBpbGdyaW1hZ2U= 90413 -IEFzcGhhbHQ= 90414 -IHZhbGlkbw== 90415 -RW5kRWxlbWVudA== 90416 -IGxhcHNl 90417 -ID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0K 90418 -aWxvcw== 90419 -ZXJuYWxz 90420 -Q29ubmVjdGlvbkZhY3Rvcnk= 90421 -IExvdmluZw== 90422 -LkNvbXBpbGU= 90423 -IGNvcms= 90424 -IEJ5ZQ== 90425 -aWJOYW1lT3JOaWw= 90426 -ZXN0YXI= 90427 -XEdlbmVyYXRlZFZhbHVl 90428 -KExM 90429 -IFJhaXNlUHJvcGVydHlDaGFuZ2Vk 90430 -IElyYW5pYW5z 90431 -IGdldFByaWNl 90432 -bWFyaWVz 90433 -anVtYm90cm9u 90434 -IFJlYmVscw== 90435 -RElGRg== 90436 -IE1vag== 90437 -b3J0aWM= 90438 -CWNvbnN0ZXhwcg== 90439 -bnRw 90440 -IG1hZ2ljaWFu 90441 -IHBhdHJpb3Rpc20= 90442 -LmNl 90443 -LlNpbXBsZUJ1dHRvbg== 90444 -IFBSSVY= 90445 -aGlzdG9pcmU= 90446 -aGlnaGVy 90447 -cmVmaXhlcg== 90448 -Q0pL 90449 -IE9zd2FsZA== 90450 -LnNwcml0ZXM= 90451 -Lkls 90452 -IGFyY2FuZQ== 90453 -IENodW4= 90454 -X09m 90455 -IGV2ZXJ5dGltZQ== 90456 -0Y7RiQ== 90457 -IGxldHJhcw== 90458 -aWxhbg== 90459 -YmFydQ== 90460 -LWJvdA== 90461 -IFNpZ25pZmljYW50 90462 -iOyKteuLiOuLpA== 90463 -4oCM 90464 -LWlzc3Vl 90465 -IGluc2FuZWx5 90466 -YXRlZ2lj 90467 -X1ZF 90468 -OkNHUG9pbnQ= 90469 -TWFya3M= 90470 -LnByb2JsZW0= 90471 -J10uJy8= 90472 -IHJlZHVuZGFuY3k= 90473 -IGRlY3J5cHRpb24= 90474 -SHVuZw== 90475 -LXZhbGlkYXRl 90476 -IEFuZ2Vsbw== 90477 -Sk0= 90478 -IHBvcG92ZXI= 90479 -ZGViaXQ= 90480 -Q29tcHV0ZWRTdHlsZQ== 90481 -KV9f 90482 -KHNpbg== 90483 -ICcpLA== 90484 -KGRlZnZhcg== 90485 -w7R0ZQ== 90486 -VGhhbk9yRXF1YWxUbw== 90487 -Lnpo 90488 -KE5vdGU= 90489 -aWJCdW5kbGVPck5pbA== 90490 -IFNvbmlh 90491 -eW1vdXM= 90492 -44CCPA== 90493 -IGZpbG15 90494 -IGVhcnRobHk= 90495 -IExlYXJuZWQ= 90496 -W3NlY3Rpb24= 90497 -Lmpzb3Vw 90498 -c3RydXA= 90499 -IFBhdHJvbg== 90500 -ICkq 90501 -c2V0Rm9udA== 90502 -IGhlZw== 90503 -IGRlbHRhWQ== 90504 -X1NDUg== 90505 -LmN1dA== 90506 -IHZiQ3JMZg== 90507 -Lk9iamVjdE1hcHBlcg== 90508 -IHLDqXBvbnNl 90509 -WXU= 90510 -KCl7fQoK 90511 -LXBhcmFtZXRlcg== 90512 -xLFzxLE= 90513 -aWF6emE= 90514 -SVpFUw== 90515 -X1NVUFBMWQ== 90516 -a2l0cw== 90517 -IHJlaW5z 90518 -KGRvY3M= 90519 -JSE= 90520 -IHN5c3RlbWN0bA== 90521 -IFBzcg== 90522 -IFdlcms= 90523 -UGhpbGFkZWxwaGlh 90524 -QlJFQUs= 90525 -LmFwcGVuZFRv 90526 -KGxvbg== 90527 -QWJy 90528 -L3JlbmRlcmVy 90529 -IEVsZWFub3I= 90530 -Q0VSVA== 90531 -UGFyYW1ldGVyVmFsdWU= 90532 -JGdldA== 90533 -IOCy 90534 -IEpM 90535 -IGlnbml0ZQ== 90536 -IGLhuqFu 90537 -IENhdWw= 90538 -IGhhc3Rl 90539 -IGRvbWluZ28= 90540 -VGVzbGE= 90541 -L2NvbmZpZ3VyYXRpb24= 90542 -KGV4cGVjdA== 90543 -dXNyYQ== 90544 -IHByZWZlY3Q= 90545 -IGZyb2dz 90546 -IGFzc2lnbmFibGU= 90547 -IGludGVydmVuZWQ= 90548 -LmNob2ljZXM= 90549 -VUlTdG9yeWJvYXJkU2VndWU= 90550 -IGLDqQ== 90551 -IEzDtnM= 90552 -YWxwaGFiZXQ= 90553 -IHByZWFtYmxl 90554 -ZGJh 90555 -IGVtaXR0aW5n 90556 -Lm1vcmU= 90557 -IEJhc2Vs 90558 -KGRhdGVUaW1l 90559 -KCl9KTsK 90560 -IG5vZGVMaXN0 90561 -IEZQR0E= 90562 -d2Vs 90563 -IGxvZGFzaA== 90564 -X2F1dGhlbnRpY2F0aW9u 90565 -w7NyaW8= 90566 -KHJ1bnRpbWU= 90567 -X1NDRU5F 90568 -IGN1ZmZz 90569 -IEFkcmVzc2U= 90570 -Ojw/ 90571 -X2NtZHM= 90572 -VMOqbg== 90573 -IGVqZWN0 90574 -CUVSUg== 90575 -PE8= 90576 -IEtyYW1lcg== 90577 -4oCmCg== 90578 -c29tZW9uZQ== 90579 -IENQTA== 90580 -77yN 90581 -bG9ja2luZw== 90582 -LkZvb3Rlcg== 90583 -IGFsbQ== 90584 -IEFkb2xm 90585 -KS4v 90586 -IE1hdHRoaWFz 90587 -ICIsIgo= 90588 -ZW51aXR5 90589 -IExvdmVy 90590 -IGFsaW1lbnRvcw== 90591 -cGxldHM= 90592 -w6R0emU= 90593 -KHJlY3Y= 90594 -dXJhYQ== 90595 -U1RET1VU 90596 -YW50eg== 90597 -LkZsb2F0VGVuc29y 90598 -IFJhZQ== 90599 -cGln 90600 -IHRlcnVn 90601 -IHRoZW9sb2c= 90602 -IHRheGlz 90603 -Y29tcG9zaXRl 90604 -c2hlcg== 90605 -bGVEYg== 90606 -IFJhaG1lbg== 90607 -IDst 90608 -SW5kZW50ZWQ= 90609 -IHRyb2xsaW5n 90610 -RVJJQ0FO 90611 -Z2V0RW1haWw= 90612 -X0VOQ09ERQ== 90613 -Z2V0Q2VsbA== 90614 -IFdyYXRo 90615 -KHN1aXRl 90616 -bm90RW1wdHk= 90617 -LmdldFJpZ2h0 90618 -IGJyZWF0aGFibGU= 90619 -44Gf44Gg 90620 -IHNldFRpbWU= 90621 -J29wdGlvbnM= 90622 -IHBheWxvYWRz 90623 -YXVnYQ== 90624 -ZWRt 90625 -KHdlYXRoZXI= 90626 -CXNlbQ== 90627 -KGZyb250 90628 -IHBheW91dHM= 90629 -LnNldFRleHR1cmU= 90630 -LFtdLA== 90631 -IFBhY2tz 90632 -IGNhenpv 90633 -V2l0aFBhdGg= 90634 -UHJvZw== 90635 -bW1hcw== 90636 -IGtvaw== 90637 -LkNzcw== 90638 -IGRlbGE= 90639 -QXdhcmQ= 90640 -w7xsdA== 90641 -c291cA== 90642 -KFsoJw== 90643 -b2xsaXBvcA== 90644 -LFNMT1Q= 90645 -Y2hpYQ== 90646 -IGJsYW5jbw== 90647 -T0xVVEU= 90648 -LXBsYW5l 90649 -LExpc3Q= 90650 -eGluZw== 90651 -SU1BVEU= 90652 -LW1vcnQ= 90653 -IGdyYXZpZA== 90654 -IEhhbmdpbmc= 90655 -IHNjb2Zm 90656 -Lml0ZW1JZA== 90657 -VEhFTg== 90658 -aW5mZXI= 90659 -IG1pc3BsYWNlZA== 90660 -CU1vbm8= 90661 -d2F5bmU= 90662 -IGVkZ2Vk 90663 -X25pY2s= 90664 -IE1BUlQ= 90665 -CXN0YXRlbWVudA== 90666 -IEV2ZW50QnVz 90667 -PkFib3V0 90668 -IGJ1cmdlb25pbmc= 90669 -IGNpY2xv 90670 -TE9PUA== 90671 -IGRlZnk= 90672 -IGVsZW1lbnRUeXBl 90673 -IGNvbnNlcnZhdGlzbQ== 90674 -V2ViSG9zdA== 90675 -LkRpc2FibGVk 90676 -IGNsYXA= 90677 -IEFsZWtz 90678 -cm9yaW5n 90679 -aXNzaW9uYWw= 90680 -LUJvbGQ= 90681 -SVJUSA== 90682 -Lml0ZW1WaWV3 90683 -cWluZw== 90684 -P2tleQ== 90685 -IFZlbm9t 90686 -IGFudGlk 90687 -IEZvcm1hdHRpbmc= 90688 -UVB1c2hCdXR0b24= 90689 -IEFzc2VtYmx5VGl0bGU= 90690 -X3Jlc2VydmU= 90691 -LkRpcmVjdA== 90692 -QW5pbWU= 90693 -IG1hdGVyaWFsbHk= 90694 -IGFkanVuY3Q= 90695 -LnNldFRvb2xUaXBUZXh0 90696 -bGFzc2lhbg== 90697 -KG5y 90698 -IG5pbmfDum4= 90699 -IG1pc3VuZGVyc3RhbmQ= 90700 -IEFwcGx5aW5n 90701 -X2NvbXBhdA== 90702 -IG1peGlu 90703 -IGplb3BhcmR5 90704 -0YvQstCw0LXQvA== 90705 -IGNvY2luYQ== 90706 -X1dST05H 90707 -QVRBUg== 90708 -S0Q= 90709 -IGNhdGVnb3J5TmFtZQ== 90710 -SHR0cENvbnRleHQ= 90711 -IGJ1YmI= 90712 -IGFua2xlcw== 90713 -b3dlcmluZw== 90714 -RnJhbWV3b3Jrcw== 90715 -IHNlZ3VuZG9z 90716 -LkFzc2VtYmx5 90717 -X0VudGl0eQ== 90718 -SFE= 90719 -IGZvdXJz 90720 -IGZvcmZlaXR1cmU= 90721 -dmxhbg== 90722 -LWRvbWluYXRlZA== 90723 -LWF3YXk= 90724 -SUNJRU5U 90725 -LlJlYWRCeXRl 90726 -YW1heA== 90727 -Lj0iPA== 90728 -X3Nwcml0ZXM= 90729 -IFJlbWFpbmluZw== 90730 -TE9PRA== 90731 -X3JlcXVpcmVtZW50cw== 90732 -J2FydGljbGU= 90733 -IFBvbXBlbw== 90734 -IHTDqXI= 90735 -IERyb3Bz 90736 -SG9tZUFz 90737 -SG9tZUFzVXA= 90738 -w7ph 90739 -Lm5hc2E= 90740 -X2Jpbw== 90741 -IFlvc2hp 90742 -RWxlY3Ryb25pYw== 90743 -IGpvc2U= 90744 -IGludGVsaWc= 90745 -ID8+Pjw/ 90746 -PnshIQ== 90747 -X3Byb3Y= 90748 -PURC 90749 -PCEtLQo= 90750 -LWZsb2F0aW5n 90751 -eXVt 90752 -LkpNZW51SXRlbQ== 90753 -IE5hdGlvbndpZGU= 90754 -SW1wb3NzaWJsZQ== 90755 -6K+m5oOF 90756 -SmVycnk= 90757 -IGRlc2Nhcmdhcg== 90758 -7JW8 90759 -RGVjcnlwdA== 90760 -IHRlbXBlcmVk 90761 -IGVrcw== 90762 -w61jaWE= 90763 -Lmxhcmdl 90764 -IHVuZm9sZHM= 90765 -IGh2ZXI= 90766 -IEFWTA== 90767 -LnR0 90768 -4oKA 90769 -PSUu 90770 -IHRvcHBpbmdz 90771 -IHN0b3V0 90772 -IHNlbWluYWw= 90773 -eGVz 90774 -IE9VVEVS 90775 -YWRybw== 90776 -IHlvaw== 90777 -IERlcmU= 90778 -CWZyZW9wZW4= 90779 -X2xuZw== 90780 -Q2h1bmtz 90781 -LmdldE9yRWxzZQ== 90782 -KGVsbQ== 90783 -ICgpKTsKCg== 90784 -Q2VsZWJy 90785 -X2NhcGFiaWxpdHk= 90786 -IHNvY2llZGFk 90787 -IGludGltaWRhdGU= 90788 -IEJsYXplcnM= 90789 -aWd0aA== 90790 -ZW5kY29kZQ== 90791 -VUlMREVS 90792 -IEhhbm5pdHk= 90793 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0K 90794 -INC40YHQv9C+0LvRjNC3 90795 -IFRvb2s= 90796 -IE1vdmVk 90797 -IHByb250bw== 90798 -IE1hcnRpbnM= 90799 -RGF0YUV4Y2hhbmdl 90800 -LlBvb2w= 90801 -ZXVz 90802 -IGpvYklk 90803 -IEF4ZXM= 90804 -IGhhbXN0cmluZw== 90805 -LnJtaQ== 90806 -RGF0YVRhc2s= 90807 -IE1hZ2ljTW9jaw== 90808 -IEdBUw== 90809 -IE5hdw== 90810 -IHNuZWw= 90811 -X3NjZW5hcmlv 90812 -IGVtYWlsQWRkcmVzcw== 90813 -IE11c3M= 90814 -IHBob2VuaXg= 90815 -IGRlbnNpdGllcw== 90816 -IE1hY09T 90817 -cmVtYQ== 90818 -IHRlc3RlcnM= 90819 -KT87Cgo= 90820 -IHB1cHM= 90821 -bGFwcw== 90822 -ZGRi 90823 -L1BlYWs= 90824 -IGJhY2tzdGFnZQ== 90825 -IGJhY2tCdXR0b24= 90826 -KG5hdg== 90827 -eEFF 90828 -c3RyY3B5 90829 -aWNodGV0 90830 -IFJpZg== 90831 -4LiB4Lij 90832 -IGhvbm91cmVk 90833 -IGdyYXBwbGluZw== 90834 -VmVydGV4QnVmZmVy 90835 -LmdldEFjY291bnQ= 90836 -LU5ldw== 90837 -IG9wcHJlc3M= 90838 -IHV0dGVyZWQ= 90839 -IFVTQUdF 90840 -X0xFQVZF 90841 -X2NvbGxlY3Rpb25z 90842 -X1V0aWw= 90843 -KCIiKSk7Cg== 90844 -IHF1aWV0ZXI= 90845 -YCksCg== 90846 -IHR5cGVJZA== 90847 -IHNlcmlm 90848 -c3RhbGs= 90849 -IHByaW1hcnlTdGFnZQ== 90850 -eEVB 90851 -Ok5TTGF5b3V0 90852 -X1JC 90853 -X0FQUFM= 90854 -U0tV 90855 -KnNjYWxl 90856 -IENvdWdhcg== 90857 -CVJFVFVSTg== 90858 -aWZpw6k= 90859 -dGltaW5n 90860 -IGlkb2xz 90861 -656Y7Iqk 90862 -4oCUaWY= 90863 -KGZvcm1hdHRlcg== 90864 -IGFtYWxn 90865 -c2V0V2lkdGg= 90866 -LG1pZA== 90867 -b3JlYWw= 90868 -LlJvbGVz 90869 -IGRldmVs 90870 -IGdldEluZGV4 90871 -IHN0b29scw== 90872 -IHNub3d5 90873 -IGdyYW5kaQ== 90874 -0Y/QtdC8 90875 -aWd1aWVudGU= 90876 -0LrQvtCy 90877 -IEN1dHRlcg== 90878 -cm9zY29wZQ== 90879 -YWlyYQ== 90880 -0YPRgNGB 90881 -IHRhYmVs 90882 -IGRlZmlhbmNl 90883 -LlRvQm9vbGVhbg== 90884 -IHBlcmc= 90885 -LWNvbW11bml0eQ== 90886 -IHB1cnN1aXRz 90887 -KG1ldHJpY3M= 90888 -TXVzbGlt 90889 -IFJpeWFkaA== 90890 -IOKCuQ== 90891 -LldlYkVsZW1lbnQ= 90892 -IEhhcmRlbg== 90893 -IENvcnJ1cHRpb24= 90894 -IEFl 90895 -IFRhbm5lcg== 90896 -IGluZGVi 90897 -IENoYXJnaW5n 90898 -X1BST0Q= 90899 -IOKTmA== 90900 -IGNlbnRlclg= 90901 -dHlwaW5n 90902 -IHV4 90903 -IFRvZQ== 90904 -CWxvb3A= 90905 -Zmxv 90906 -UmVnaW9uYWw= 90907 -X2Fh 90908 -IHZpZXdwb2ludHM= 90909 -PnRoaXM= 90910 -LXJlc291cmNlcw== 90911 -IEltYW0= 90912 -IFNoaXY= 90913 -IGFuZHJh 90914 -UkVRVUlSRUQ= 90915 -IHNlZWRlZA== 90916 -dW1vbnQ= 90917 -IHRvYXN0ZXI= 90918 -IGhvbWVzY2hvb2w= 90919 -24zYsQ== 90920 -X2V4dHJhY3Rvcg== 90921 -bW9kZXM= 90922 -IE11bmRv 90923 -X2ZpcmVzdG9yZQ== 90924 -IHB1bmlzaG1lbnRz 90925 -IGJvcmVkb20= 90926 -anVyaWVz 90927 -LlNhZmU= 90928 -YW1iaXF1ZQ== 90929 -IGFkdmVyc2l0eQ== 90930 -VUxFUg== 90931 -IGFuYWxzZXg= 90932 -bW9ycGg= 90933 -IE9tbg== 90934 -KCkiPgo= 90935 -IEdJVkVO 90936 -U3o= 90937 -IG5vdW5z 90938 -IHF1YW0= 90939 -IFdpa2ltZWRpYQ== 90940 -IGR6aWV3Y3o= 90941 -LmNvbW11bmlj 90942 -Q291cmllcg== 90943 -Qm9uZA== 90944 -LmNvbW11bmljYXRpb24= 90945 -LlByZWZlcmVuY2U= 90946 -c2xpZGVEb3du 90947 -L2djYw== 90948 -IHZpYmVz 90949 -QVBJVmlldw== 90950 -IE92ZXJzaWdodA== 90951 -X3Zr 90952 -IGVtcHJlcw== 90953 -IGFyaXNlbg== 90954 -ICovKQ== 90955 -KCcoJw== 90956 -IGJ0dw== 90957 -IGNvbmV4acOzbg== 90958 -IFV6YmVr 90959 -IOyEnA== 90960 -IGltYWdlVVJM 90961 -44Kq 90962 -c3RvcHBlZA== 90963 -IFdvdWxkbg== 90964 -IENoZXc= 90965 -Z3LDqQ== 90966 -IHRydXRoZnVs 90967 -IFRyYW5zcGFyZW50 90968 -KHNlcnY= 90969 -IE1jS2F5 90970 -PXJlYWQ= 90971 -IFNhbw== 90972 -CUdyaWQ= 90973 -IGluZHVjZXM= 90974 -Lmxpc3RGaWxlcw== 90975 -IGNhcnJlcmE= 90976 -IGljb25OYW1l 90977 -IENhcmx0b24= 90978 -LkV2ZW50VHlwZQ== 90979 -IGRyYXBlZA== 90980 -X1NBTVBMRVM= 90981 -KGVzdA== 90982 -IFJ1aXo= 90983 -IGNhcHRhaW5z 90984 -IG1hZmlh 90985 -IFJhcGhhZWw= 90986 -IEdBUA== 90987 -aW1wYW4= 90988 -Y29taWM= 90989 -IG1hbnRlbg== 90990 -JEw= 90991 -IGFmdGVybWFya2V0 90992 -15c= 90993 -IENm 90994 -CXRpbGU= 90995 -QXBwU3RhdGU= 90996 -IHdob2xlc2FsZXJz 90997 -bG93ZXN0 90998 -RGVtb2NyYXRpYw== 90999 -IHBvd2VyaW5n 91000 -YXBvdA== 91001 -IENvcnRleA== 91002 -KHNpbmdsZQ== 91003 -b3BoeXNpY2Fs 91004 -LnV0Zg== 91005 -77yf44CN 91006 -IHRhcmVh 91007 -RXF1aXA= 91008 -IGtsaWs= 91009 -IHJ1YQ== 91010 -IGFWYWx1ZQ== 91011 -IE1pbmVy 91012 -IFZlZw== 91013 -YW55bA== 91014 -Q293 91015 -QGM= 91016 -X0xPQURFRA== 91017 -IEFITA== 91018 -d2FrZQ== 91019 -LkxvZ0luZm9ybWF0aW9u 91020 -KGNhdGVnb3JpZXM= 91021 -IFFVRVNUSU9O 91022 -LnVtbA== 91023 -IENyZWF0ZU1hcA== 91024 -bWVlcg== 91025 -IHJlbmNvbnRyZXI= 91026 -X3N1 91027 -IGF0bGVhc3Q= 91028 -KFByb3BlcnR5TmFtZQ== 91029 -IFlhbw== 91030 -IEhhdXB0 91031 -QmxvY2tTaXpl 91032 -IFNBQw== 91033 -IExlZ3M= 91034 -Yml0ZQ== 91035 -IGxvZ2FyaXRo 91036 -IElNZXNzYWdl 91037 -QmFja2Ryb3A= 91038 -IGdkaw== 91039 -7Jy866m0 91040 -LmV4Y2x1ZGU= 91041 -QURPUw== 91042 -LXNoaWZ0 91043 -YXRobGV0ZQ== 91044 -X2NvbWJpbmVk 91045 -IHJlYmF0ZQ== 91046 -IHBhcmQ= 91047 -IGltcGVkYW5jZQ== 91048 -cmVhdQ== 91049 -Xw0KDQo= 91050 -IGRhZ2Vu 91051 -a2VsYXM= 91052 -IGluZ3Jlc2Fy 91053 -IEJSQU5E 91054 -Lm1rZGlycw== 91055 -IHJlaWduaW5n 91056 -VGFsa2luZw== 91057 -LyoqCgo= 91058 -X1JFU09VUkNFUw== 91059 -IFBST0dNRU0= 91060 -IGRhdGFTaXpl 91061 -44Og 91062 -ZGVueQ== 91063 -SVJT 91064 -IHRlbGV2aXM= 91065 -PV8oJw== 91066 -ZWdpcw== 91067 -PD8s 91068 -IHVwc2V0dGluZw== 91069 -IHNhdWNlcw== 91070 -IHB1ZXJ0bw== 91071 -IFZvZ3Vl 91072 -aWRpbmU= 91073 -IEdyZWVud29vZA== 91074 -emlvbg== 91075 -L3F0 91076 -5bGA 91077 -Lmxhbmd1YWdlcw== 91078 -IFBsYXlib3k= 91079 -b25uZW1lbnQ= 91080 -IFBvc2l0aW9uZWQ= 91081 -IOS4uw== 91082 -IEZyaXR6 91083 -SW5pdGlhbGx5 91084 -bm9kZVZhbHVl 91085 -X1RSSUFOR0xFUw== 91086 -LWJhY2tlbmQ= 91087 -dG9JU09TdHJpbmc= 91088 -IEdvdmVybm9ycw== 91089 -WUxPTg== 91090 -Lk9SREVS 91091 -RE9J 91092 -IENoZXZyb24= 91093 -IGRlY2tpbmc= 91094 -IFNoYXJpYQ== 91095 -b3RoZXJtYWw= 91096 -RW1wdHlFbnRyaWVz 91097 -KEluaXRpYWxpemVk 91098 -ZG9yZg== 91099 -Lmx1 91100 -KFJvb20= 91101 -LlllbGxvdw== 91102 -IEFicmFt 91103 -X2xt 91104 -INC90LDQvw== 91105 -IFRIQU4= 91106 -fi1+LX4tfi0= 91107 -Lk92ZXJyaWRl 91108 -IFNWTQ== 91109 -IFN1c3BlbnNpb24= 91110 -IGFic29yYnM= 91111 -X3RyYWZmaWM= 91112 -ICI+Ig== 91113 -LmZpdHM= 91114 -IHJlaW5mb3JjaW5n 91115 -IG1veWVu 91116 -ZXJlcg== 91117 -IFJvc2Vuc3RlaW4= 91118 -IFdlc3Rvbg== 91119 -IGNvbmZpbmVz 91120 -T0xB 91121 -b3JyYWluZQ== 91122 -X0dSUA== 91123 -IHN0cmFwcGVk 91124 -IG1pbmdsZQ== 91125 -CVZr 91126 -IG5vc3RyYQ== 91127 -IGFjdHJlc3Nlcw== 91128 -IFNhbW15 91129 -bGlnbmU= 91130 -SUdITElHSFQ= 91131 -IHN0dXA= 91132 -aWN0b3J5 91133 -IGNvbnZpY3Q= 91134 -IHN1cHA= 91135 -cGVvbg== 91136 -dnJpZXI= 91137 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM= 91138 -IHRyb3R6 91139 -IG1lbHRkb3du 91140 -YXJrZXJz 91141 -LlNlbGVjdENvbW1hbmQ= 91142 -IExpYWJpbGl0eQ== 91143 -IEJlY2FtZQ== 91144 -IGx1Y2tpbHk= 91145 -INC/0L7RgA== 91146 -IHJlYXNzdXJl 91147 -IENvbnRyYXN0 91148 -IEF1ZHJleQ== 91149 -IENvbnN1bHRhbnRz 91150 -IFF1ZW50aW4= 91151 -LU93bmVk 91152 -b2NyaW4= 91153 -X1NUUklQ 91154 -IHJldGFsaQ== 91155 -IHJhbGx5aW5n 91156 -IFJlcXVlc3RDb250ZXh0 91157 -IG1hc3NhYw== 91158 -CWdy 91159 -TEVF 91160 -IGNhxYI= 91161 -IEpvYW5uYQ== 91162 -4butYQ== 91163 -aGho 91164 -IHNxbFNlc3Npb24= 91165 -xLFrbA== 91166 -Q29tcG9zZXI= 91167 -IGN1cnJlbnRQbGF5ZXI= 91168 -YWdpbmk= 91169 -IEJhcmJhcg== 91170 -IEhlbGxvV29ybGQ= 91171 -bG9vbWJlcmc= 91172 -LkhlcmU= 91173 -IGRpc2d1c3RlZA== 91174 -CQkJCQkJICAgIA== 91175 -b2t1cw== 91176 -VmV0ZXI= 91177 -IGNob3Bz 91178 -IEZPUldBUkQ= 91179 -IEVpZw== 91180 -IFBhcnRpYWxWaWV3 91181 -IGltcG9zcw== 91182 -IGNvbnNlcXVlbnRpYWw= 91183 -IFsnIw== 91184 -CWxvZ2dpbmc= 91185 -IEVsaXM= 91186 -cHJvY3M= 91187 -LDwv 91188 -X3BpbnM= 91189 -XERvY3RyaW5l 91190 -VXZz 91191 -IEdJVA== 91192 -IHRhaA== 91193 -KHJ1bGVz 91194 -Y3JlYXRlRnJvbQ== 91195 -ICctJykK 91196 -aGFuZGxpbmc= 91197 -ZXh0ZXJuYWxBY3Rpb25Db2Rl 91198 -Uk9EVUNUSU9O 91199 -Rm9yUmVzb3VyY2U= 91200 -c2J1cmc= 91201 -PFRleHRWaWV3 91202 -dGhpbmthYmxl 91203 -YW5nbGluZw== 91204 -ICJ9XA== 91205 -UFJT 91206 -QXBwcm92YWw= 91207 -IGtsaWVudA== 91208 -bm91bg== 91209 -IERpYW1vbmRz 91210 -SEc= 91211 -IFRyaWJhbA== 91212 -LnB4 91213 -IHByb3BOYW1l 91214 -IGhlbHk= 91215 -0LvQuNGH 91216 -IEJvdXRpcXVl 91217 -Iik7fQo= 91218 -L2hvc3Q= 91219 -IHN0YXR1c0Jhcg== 91220 -PkRhdGE= 91221 -IGRpc2NvbnRlbnQ= 91222 -IGZyYWls 91223 -LmVsZW1lbnRBdA== 91224 -IGVtYW5j 91225 -CWZ1bg== 91226 -YXR0bGVz 91227 -IHByb3B1bHNpb24= 91228 -IGludGVyY2hhbmdlYWJsZQ== 91229 -IFRhbWJpw6lu 91230 -IHZlbmVy 91231 -X0xPV0VS 91232 -IHBkbw== 91233 -IGRldGVyZ2VudA== 91234 -IHRhdmVybg== 91235 -VmVudWU= 91236 -Lmphc3Blcg== 91237 -eXR0 91238 -IEppaGFk 91239 -4oCZw6A= 91240 -IG1lZGlhUGxheWVy 91241 -P3A= 91242 -cGNm 91243 -YW5kb25lZA== 91244 -IHJlY2ViZXI= 91245 -T1RQ 91246 -KGlPUw== 91247 -KCckew== 91248 -UHRz 91249 -IG1hbmFnZXJpYWw= 91250 -IFR1ZA== 91251 -IFdFTEw= 91252 -b3pl 91253 -IEFudG9pbmU= 91254 -IFxcCg== 91255 -IFZlY3Q= 91256 -IFdpbWJsZWRvbg== 91257 -aXNtZXQ= 91258 -IGJvdGhlcmluZw== 91259 -aW9zaXM= 91260 -Z2V0TWV0aG9k 91261 -IGlucHV0RGF0YQ== 91262 -IEJpbmRlcg== 91263 -IGRjdA== 91264 -w6Fsbg== 91265 -X0JPTEQ= 91266 -IEp1Z2VuZA== 91267 -IEJlZ2lubmVycw== 91268 -aW9tcw== 91269 -IHJlbGVudGxlc3NseQ== 91270 -IE1vbmRheXM= 91271 -5LyY 91272 -VG9tb3Jyb3c= 91273 -IFNhbXA= 91274 -XFBlcnNpc3RlbmNl 91275 -TUFTVEVS 91276 -KHByZWRpY3Rpb25z 91277 -KG51bWVybw== 91278 -LnR3aXRjaA== 91279 -LlJlc3RyaWN0 91280 -IFpa 91281 -IE1MTQ== 91282 -LlNtYWxs 91283 -XWJ5dGU= 91284 -IFZpZXdQYWdlcg== 91285 -IEFnZW5jaWVz 91286 -IHBhcnRpY2lwYXRlcw== 91287 -IGluaXRXaXRoU3R5bGU= 91288 -JVg= 91289 -IGAs 91290 -Lk9iag== 91291 -ID8iKTsK 91292 -Q2FyZWVy 91293 -IDwlPQ== 91294 -a3Vs 91295 -Q3BwSQ== 91296 -IE11c2hyb29t 91297 -dXJhdA== 91298 -bWlh 91299 -Q2Q= 91300 -YXJkdWlubw== 91301 -IGNvdW50cnlDb2Rl 91302 -X3BsYWNlbWVudA== 91303 -KCI9PT09PT09PT09PT09PT09 91304 -LWJlbA== 91305 -QXNzZXJ0aW9ucw== 91306 -IHByw7N4aW1h 91307 -KCkiKQo= 91308 -X2Vn 91309 -U1NJUA== 91310 -dXpl 91311 -cGxhY2Vy 91312 -YW1iaWd1b3Vz 91313 -X0lOSVRJQUxJWkVS 91314 -IEhhdHM= 91315 -IEdPT0dMRQ== 91316 -IGFnaXRhdGlvbg== 91317 -KG11dGV4 91318 -SElHSA== 91319 -OiIp 91320 -IGludmFkZXJz 91321 -ICl9Cgo= 91322 -Lm1hbnVhbA== 91323 -IFNpZW1lbnM= 91324 -CUpQYW5lbA== 91325 -YmluZHVuZw== 91326 -ZWNlcmE= 91327 -L21ldA== 91328 -IMOpYw== 91329 -KHN0YXRpb24= 91330 -IHBvc2ljacOzbg== 91331 -X2lzc3Vlcw== 91332 -X2FsaWFzZXM= 91333 -X3RvcG9sb2d5 91334 -IEF1dG9kZXNr 91335 -QWNrbm93bGVk 91336 -ISpcCg== 91337 -IEZyZWlnaHQ= 91338 -IEZYTUxMb2FkZXI= 91339 -aWNoZWw= 91340 -KENoYXRDb2xvcg== 91341 -IGRpc3NvY2k= 91342 -IGFuYWxvZ3Vl 91343 -PHVzaXpl 91344 -LWV2 91345 -IHRlbmRy 91346 -PkFsbA== 91347 -IFVTRVJT 91348 -LnJlc3A= 91349 -X2ludGVncmF0aW9u 91350 -RGlzcGxheVN0eWxl 91351 -RkFJTFVSRQ== 91352 -0YfQuNGC 91353 -aWxkZWQ= 91354 -X3NlbWFwaG9yZQ== 91355 -YWNhZGVtaWM= 91356 -IHNjbGVyb3Npcw== 91357 -RmFs 91358 -LHN0 91359 -YD0= 91360 -aWZ0b24= 91361 -IHN1YnN0aXR1dGVz 91362 -IFN1cHBvcnRlcnM= 91363 -YXBwbGljYW50 91364 -KGt2 91365 -IEJlcm11ZGE= 91366 -IGRpc2NyZXBhbmNpZXM= 91367 -LlNvbGlk 91368 -d2VlbmV5 91369 -IGd1bA== 91370 -IGZpbGV0eXBl 91371 -IHJlc3VsdGF0 91372 -U2VuZGVySWQ= 91373 -IGdlem9jaHQ= 91374 -IEJlcmtzaGlyZQ== 91375 -ICgiPA== 91376 -KG1s 91377 -KHNoaWZ0 91378 -X1JFRElSRUNU 91379 -T0xPTg== 91380 -L2Jyb3dzZQ== 91381 -Ok5TTWFrZVJhbmdl 91382 -IHdhaXZl 91383 -IGV4Y2U= 91384 -IGNhdGFsb2dz 91385 -5Lmm 91386 -aWxsaW9ucw== 91387 -LkdldEN1cnJlbnRNZXRob2Q= 91388 -IGJpbGluZ3VhbA== 91389 -IENhc2NhZGVUeXBl 91390 -CVRyYW5zZm9ybQ== 91391 -X0NVU1RPTUVS 91392 -aXNpZnk= 91393 -INCx0Ls= 91394 -IFdob2V2ZXI= 91395 -IEVBUg== 91396 -IFs9Ww== 91397 -INC80L7QttC90L4= 91398 -IGphcmRpbg== 91399 -QHNob3c= 91400 -IGhlaXJz 91401 -IGFiYW5kb25tZW50 91402 -IFRyYW5zY3JpcHQ= 91403 -XV4= 91404 -OlNldFBvaW50 91405 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo= 91406 -IEZhY3Rpb24= 91407 -KGVudGl0aWVz 91408 -ZmFjdGlvbg== 91409 -bXR4 91410 -X3JlY2FsbA== 91411 -Lk5VTEw= 91412 -Lm9wdGlvbmFs 91413 -KHByZWRpY3Rpb24= 91414 -QUdFTlQ= 91415 -IPCfmIA= 91416 -4oCZeQ== 91417 -4oCZdXRpbA== 91418 -IGFuZ3N0 91419 -LkV4cGVyaW1lbnRhbA== 91420 -aG9vdA== 91421 -YXN5YXJhaw== 91422 -YXV0b3BsYXk= 91423 -IFNwbGFzaFNjcmVlbg== 91424 -IGhlY3RpYw== 91425 -IG1ldGljdWxvdXNseQ== 91426 -IGNvbWVy 91427 -S2VpdGg= 91428 -IGZyYXNl 91429 -X1VOSVFVRQ== 91430 -Lk1hZ2VudGE= 91431 -KE1heA== 91432 -IHNjYWxlWQ== 91433 -IHB1dHQ= 91434 -KElG 91435 -IEFQUExF 91436 -UG9ybm8= 91437 -LmFkZENlbGw= 91438 -IG1vbHQ= 91439 -Y2hpbXA= 91440 -IGxlZ2dpbmdz 91441 -IGZsb3A= 91442 -4oCZaHVp 91443 -UlRPUw== 91444 -L3NwYW4= 91445 -LmJlZA== 91446 -LkxvZ2lj 91447 -IHVudHJhbnNsYXRlZA== 91448 -Q0xFQVI= 91449 -O2xlZnQ= 91450 -IEJGUw== 91451 -LWdyb3Vwcw== 91452 -dG9vaw== 91453 -X2FjY2VwdGVk 91454 -IGNhc2hpZXI= 91455 -ZXZlbnRJZA== 91456 -IGRvd25ncmFkZQ== 91457 -CQkJCQkJCQkJCQkK 91458 -0LDQvdC40Y4= 91459 -w6RuZGU= 91460 -IGNvdW5jaWxsb3I= 91461 -IGRyZWQ= 91462 -ZFQ= 91463 -V1JBUFBFUg== 91464 -Lm9s 91465 -5LiA6aG1 91466 -TUVB 91467 -IGtpbmV0aWNz 91468 -IGptcA== 91469 -X2ZsaWdodA== 91470 -RmVhcg== 91471 -IENoYW5lbA== 91472 -X21pZ3JhdGlvbg== 91473 -aGRs 91474 -ZXJlcXVpc2l0ZQ== 91475 -LnJhcg== 91476 -LU9uZQ== 91477 -IHNoZXBoZXJk 91478 -LmVhc2luZw== 91479 -KGRlc2NyaXB0b3I= 91480 -IHN1YnRvdGFs 91481 -44OT 91482 -Q29tcGlsZWQ= 91483 -IENvbHQ= 91484 -ZGxl 91485 -L21vY2s= 91486 -KXJvdw== 91487 -IHJlc2V0dA== 91488 -dGVybw== 91489 -IGFlcm9iaWM= 91490 -LmludHJv 91491 -IGNoZWNrYm94ZXM= 91492 -IE1jQ2FydG5leQ== 91493 -IENseWRl 91494 -77yM5bm2 91495 -Y29vbGRvd24= 91496 -LWluc3RhZ3JhbQ== 91497 -IE1QRw== 91498 -IExlaXN1cmU= 91499 -IG5hd2V0 91500 -IE5YVA== 91501 -UmVndWxhckV4cHJlc3Npb24= 91502 -IHJhdmU= 91503 -QklMTA== 91504 -IGJhcnRlbmRlcg== 91505 -RW5sYXJnZQ== 91506 -IHZhaXM= 91507 -IDoKCgoK 91508 -LkVuZHBvaW50 91509 -ICIsDQo= 91510 -fX0iPnt7JA== 91511 -dHJlZXM= 91512 -LmVuZw== 91513 -KmxvZw== 91514 -OltdLAo= 91515 -IGJhdHRhbGlvbg== 91516 -U3ViamVjdHM= 91517 -IGV4cG9zaXRpb24= 91518 -IFRvYXN0cg== 91519 -IHRvcExldmVs 91520 -IENFTA== 91521 -IGd1YmVybg== 91522 -dW5zdWJzY3JpYmU= 91523 -Y29uYQ== 91524 -X2FwcHJveA== 91525 -VFo= 91526 -IFRyZWVTZXQ= 91527 -LmNvbW11bml0eQ== 91528 -IG5hcnJvd2Vy 91529 -KEV4cGVjdGVk 91530 -Q2xy 91531 -IGdvcmU= 91532 -IGFjcXVpdHRlZA== 91533 -IEVVUk8= 91534 -G1s= 91535 -IHJlcHVibGljYW4= 91536 -IGF1dG9iaW9ncmFwaHk= 91537 -X2Zkcw== 91538 -Q29sbGFwc2Vk 91539 -IA0KIA0K 91540 -LXBpbGxz 91541 -TUJFRA== 91542 -IGlOZEV4 91543 -IHJlc3BvbnNlVHlwZQ== 91544 -Z2xmdw== 91545 -LXR1cm5lZA== 91546 -5Y+R5biD 91547 -CUJvb2xlYW4= 91548 -Lk9y 91549 -aW5pYQ== 91550 -IGhvdmVyZWQ= 91551 -IHNvcnRlcg== 91552 -IE5o 91553 -IEV4ZXJjaXNlcw== 91554 -bGVtZW50cw== 91555 -aWRvbg== 91556 -VG9l 91557 -IHLDqWbDqQ== 91558 -U1NGV29ya2Jvb2s= 91559 -IG9yZ2FuaXNlcnM= 91560 -IHJlc3VsdE1hcA== 91561 -X0hPUg== 91562 -RG9k 91563 -TG9jYWxTdG9yYWdl 91564 -IGpzb25SZXNwb25zZQ== 91565 -QXV0aFNlcnZpY2U= 91566 -IHNtZQ== 91567 -ZW1icm9z 91568 -IGxvYmJ5aXN0 91569 -b2d1aQ== 91570 -LnNwaW4= 91571 -IENvcnJlY3Rpb25z 91572 -X1JBRA== 91573 -IExTTQ== 91574 -KGN1cnJlbmN5 91575 -IOaA 91576 -IHByZWZldGNo 91577 -LkhlYWQ= 91578 -LXJlYWRlcg== 91579 -IFJveg== 91580 -CW1vdXNl 91581 -IFRMQw== 91582 -IFFUYWJsZVdpZGdldEl0ZW0= 91583 -IFNUT1JBR0U= 91584 -YW5uZWVy 91585 -IOyXkA== 91586 -YWNlbg== 91587 -U1g= 91588 -SW1hZ2VSZWxhdGlvbg== 91589 -IHJlc3VyZ2VuY2U= 91590 -aXp6eQ== 91591 -aWxvZ3Vl 91592 -SVZBTA== 91593 -IHNtYWNr 91594 -cnJoYQ== 91595 -KFBBUkFN 91596 -IUk= 91597 -IE1lY2g= 91598 -IElNYXBwZXI= 91599 -IGdpc3Q= 91600 -IFBPRA== 91601 -dm9yZQ== 91602 -dWxhw6fDo28= 91603 -ICwt 91604 -IGludm9sdW50YXJ5 91605 -UVJT 91606 -PXRpdGxl 91607 -IEJpb20= 91608 -IFNoZWxsZXk= 91609 -IENTUA== 91610 -UGVz 91611 -ZHJvcHM= 91612 -INGD0YHQv9C10Yg= 91613 -ZGl2ZXM= 91614 -IVsK 91615 -IExlYXN0 91616 -IGtha28= 91617 -IE1vZGVsbw== 91618 -IGZ1bmN0aW9uTmFtZQ== 91619 -IGNob2tpbmc= 91620 -IGRlZm9ybWF0aW9u 91621 -JywnJyk7Cg== 91622 -Y2HDp8Ojbw== 91623 -IHNxdWlycmVs 91624 -c2V0QmFja2dyb3VuZA== 91625 -QnJva2Vu 91626 -cG9saXQ= 91627 -Tm9uY2U= 91628 -IGtleWVk 91629 -TWVzaFBybw== 91630 -LnVzZXJJbnRlcmFjdGlvbkVuYWJsZWQ= 91631 -IGZsdXNoaW5n 91632 -IGJwcA== 91633 -IEFuZ2xpYw== 91634 -VHJvdQ== 91635 -IFdhbHRlcnM= 91636 -IHN0dXR0ZXI= 91637 -SGlw 91638 -X3dhcg== 91639 -aXZlbWVudA== 91640 -Q29ybg== 91641 -IHVuZHVl 91642 -YXBhdGthbg== 91643 -IG1pbmRlbg== 91644 -c2lnbmlmaWNhbnQ= 91645 -KHF1YW50aXR5 91646 -JGluc2VydA== 91647 -IEFMRVJU 91648 -LlVuaWNvZGU= 91649 -aWhu 91650 -XTo9 91651 -IHBpbk1vZGU= 91652 -IGZyYWlz 91653 -aW50ZXJwcmV0ZXI= 91654 -J2FjdGlvbg== 91655 -IGJsZWliZW4= 91656 -obQ= 91657 -cm93c2Vycw== 91658 -R0lU 91659 -X0RJUlM= 91660 -Rm9yZXZlcg== 91661 -IFBkZlBDZWxs 91662 -fG0= 91663 -LnNldEhlaWdodA== 91664 -IGZvcmVhcm0= 91665 -IGJhdHRsZWdyb3VuZA== 91666 -INC/0L7RgdC70LXQtA== 91667 -IEhhdGg= 91668 -IEF1dGhvcml6ZWQ= 91669 -IGNvbmZlcnJlZA== 91670 -IEJPVFRPTQ== 91671 -LmdldEZsb2F0 91672 -b2dyYXBoZWQ= 91673 -YXJkeQ== 91674 -IHNlcnZpw6dv 91675 -b3RveGlj 91676 -L2F1dGhlbnRpY2F0aW9u 91677 -IHJlcHLDqXNlbnQ= 91678 -IGNvbXBsZXhpb24= 91679 -CUNvbW1vbg== 91680 -X2Jo 91681 -V2hvbGU= 91682 -SW1hZ2VEYXRh 91683 -IHRpbms= 91684 -ZXF1YWxUbw== 91685 -IFRIUg== 91686 -IGRlbHRhcw== 91687 -IEFHRQ== 91688 -aXphZG9y 91689 -YWRtaW5pc3RyYXRpb24= 91690 -cXVldHM= 91691 -X2ZpbGxlZA== 91692 -IEjDpA== 91693 -YWxsb2Nh 91694 -IEJvb25l 91695 -CWxjZA== 91696 -Rm9sZGVyUGF0aA== 91697 -LlJhaXNl 91698 -XyN7 91699 -ZXJ0aW5v 91700 -IFRocm9uZQ== 91701 -4K6/ 91702 -b3hldGluZQ== 91703 -cHJheQ== 91704 -IGRpbGlnZW50bHk= 91705 -IEFyY2hpZQ== 91706 -Lm11bHRpcGFydA== 91707 -IHNlbw== 91708 -LmdldFByb2plY3Q= 91709 -IHBhag== 91710 -Y2xlcm9zaXM= 91711 -YW1lcm9u 91712 -IHRvdXJlZA== 91713 -IG5pa2U= 91714 -IEJha2VyeQ== 91715 -LHBhcmVudA== 91716 -X1RFTQ== 91717 -U3BhdGlhbA== 91718 -bGFwcGluZw== 91719 -UHJvZHVjZXNSZXNwb25zZVR5cGU= 91720 -KGJhbGFuY2U= 91721 -SHVuZHJlZHM= 91722 -LXRlcm1pbmFs 91723 -IkRv 91724 -Q29udGVudFNpemU= 91725 -IGJiYw== 91726 -IGTDqWNvdXZyaXI= 91727 -dXRpbHVz 91728 -LnVuZG8= 91729 -LG91dHB1dA== 91730 -Z3JvdXBOYW1l 91731 -JG1heA== 91732 -IEFsbGE= 91733 -INC60LDRgNGC 91734 -Lk9ORQ== 91735 -X2RlY2lzaW9u 91736 -RUVFRQ== 91737 -IHhPZmZzZXQ= 91738 -56o= 91739 -IHJ1bmF3YXk= 91740 -IGhhbmRqb2I= 91741 -IGdlbml0YWxz 91742 -KGpUZXh0RmllbGQ= 91743 -LnJhZGlhbnM= 91744 -IFBhZHJlcw== 91745 -ZGVwZW5kZW5jZQ== 91746 -IHN3YWxsb3dpbmc= 91747 -cm90ZWlu 91748 -IGZsZWV0cw== 91749 -IGNhcmF0dGVy 91750 -KGNhbg== 91751 -IEZsb3JhbA== 91752 -X01zZw== 91753 -IGRlY2xhcmFjacOzbg== 91754 -bHNydQ== 91755 -c2Nob29scw== 91756 -IGRlbGVnYXRlZA== 91757 -IFBlbmFs 91758 -IENoZXJu 91759 -U21hcnRQb2ludGVy 91760 -c3Rvcnlib29r 91761 -IE55bG9u 91762 -5oCd 91763 -X0xFU1M= 91764 -L2FkZHJlc3M= 91765 -IENPUlM= 91766 -IOydtOuvuA== 91767 -IG1vZGE= 91768 -bWRw 91769 -IGRlcmJ5 91770 -IFBoYXJtYWNldXRpY2Fscw== 91771 -IGV5ZWQ= 91772 -X2NwdXM= 91773 -6KaL 91774 -fHwK 91775 -Lm1hZw== 91776 -KFFM 91777 -IENpdmlsaXphdGlvbg== 91778 -6Yw= 91779 -X0RlcA== 91780 -IHN3ZWFyaW5n 91781 -IFNob3J0cw== 91782 -dWViYXM= 91783 -IGRlbGluZQ== 91784 -IEFkdmlzb3Jz 91785 -IOyeiOuLpA== 91786 -X0ZJTkU= 91787 -fSk6 91788 -LGFzc2lnbg== 91789 -IFBDSWU= 91790 -e3t7 91791 -U2Np 91792 -IGFtYm9z 91793 -aWxlZW4= 91794 -IHR1bmVy 91795 -IHBhcmFtTmFtZQ== 91796 -LHRvdGFs 91797 -KExvY2FsRGF0ZQ== 91798 -IHNwcA== 91799 -IGVycm9yZXM= 91800 -IEhlbHBpbmc= 91801 -X21lcmdlZA== 91802 -LnRpbWVTY2FsZQ== 91803 -X0VMRU0= 91804 -X1NPTA== 91805 -IGF2ZW50 91806 -PGQ= 91807 -SnVuaW9y 91808 -CWJhcg== 91809 -Lmx2 91810 -IOy5 91811 -PXd4 91812 -IG1pcmFjdWxvdXM= 91813 -IFJhbmRvbUZvcmVzdA== 91814 -IEZyYW5rZW4= 91815 -YGAs 91816 -KEluaXRpYWxpemVkVHlwZUluZm8= 91817 -IHN1cGVyaGVyb2Vz 91818 -IGFuc2libGU= 91819 -X1R5cGVEZWY= 91820 -IFBlcm0= 91821 -T0xFUg== 91822 -R3Jhbg== 91823 -LW5vdGlmaWNhdGlvbg== 91824 -IGtheg== 91825 -IGV4aGlsYXI= 91826 -c2VydGVy 91827 -IHN0b3JlZnJvbnQ= 91828 -X2VuZHM= 91829 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMK 91830 -CWdpdA== 91831 -RFNQ 91832 -Q0hBSU4= 91833 -rLQ= 91834 -SW52YWxpZE9wZXJhdGlvbkV4Y2VwdGlvbg== 91835 -IFNseQ== 91836 -77yaPA== 91837 -QnJpdGFpbg== 91838 -L3NsaWRlcg== 91839 -IHptcQ== 91840 -IGJhag== 91841 -YnJlZA== 91842 -LlZBTFVF 91843 -IGdyaWV2aW5n 91844 -IHBvcm7DtHM= 91845 -aWd1YQ== 91846 -SU5DTFVERUQ= 91847 -V2FrZQ== 91848 -Y2Jk 91849 -IE1vbmdvbGlh 91850 -aW52aXNpYmxl 91851 -IGNvcnJlY3RpdmU= 91852 -IGNlbnRlcnBpZWNl 91853 -Q2F1Z2h0 91854 -IGthcmFrdGVy 91855 -YWxtw7Y= 91856 -IGJlbHVt 91857 -IGFkam9pbmluZw== 91858 -Pygi 91859 -IFZpc3VhbGl6YXRpb24= 91860 -a2tl 91861 -aWZpY2Fkb3M= 91862 -c3Bk 91863 -X0NCQw== 91864 -LUxhbmd1YWdl 91865 -IHN0aWw= 91866 -b3JldGljYWw= 91867 -KGNvbXBsZXRpb24= 91868 -IFZlcmbDvGd1bmc= 91869 -X1RyZWU= 91870 -cmlwcGxpbmc= 91871 -LlJlbW92ZUVtcHR5RW50cmllcw== 91872 -IFRBWA== 91873 -CUNvZGU= 91874 -5YuV 91875 -dXJnYQ== 91876 -INGD0LbQtQ== 91877 -IGFpZGVy 91878 -IFByZXNjb3R0 91879 -IGZpbGFtZW50 91880 -IC0tLS0tLS0tLS0tLS0tLS0tLS0t 91881 -dGhlcm9z 91882 -0LXRgNCw 91883 -ZGViaWFu 91884 -w6RobA== 91885 -b2xhaA== 91886 -X1VOSVRT 91887 -QXJr 91888 -TW91bnRlZA== 91889 -LlRyaW1TcGFjZQ== 91890 -LmdldE51bWJlcg== 91891 -X2VvZg== 91892 -Lm5y 91893 -IFNIQVJFUw== 91894 -aWxhdGVy 91895 -IHdpY2h0 91896 -X2NvbXBhcmlzb24= 91897 -ICki 91898 -Y2xpbmljYWw= 91899 -IFRFbnRpdHk= 91900 -dmVuZXM= 91901 -LmdldFByb3BlcnRpZXM= 91902 -IHJlbGF0 91903 -IGFubm95YW5jZQ== 91904 -YmVi 91905 -IGFuZXN0aGVzaWE= 91906 -X2ludGVydmFscw== 91907 -X2Zo 91908 -IHN1ZG9rdQ== 91909 -IGRpc2Vu 91910 -Y29ubmVjdGluZw== 91911 -IG9h 91912 -IOKWkQ== 91913 -WkY= 91914 -IGN1eg== 91915 -U09FVkVS 91916 -IE3DtmdsaWNoa2VpdA== 91917 -Y2hhcnRlZA== 91918 -IGhhc2hlcg== 91919 -IEtlZXBz 91920 -QUVB 91921 -CWxvZ3J1cw== 91922 -CU5hbWVzcGFjZQ== 91923 -b3J0aG8= 91924 -JGFjdGlvbg== 91925 -IFJvYw== 91926 -Jyk7Pz4i 91927 -IFBST1Q= 91928 -QGFwaQ== 91929 -Y2hzZWw= 91930 -L2dpZg== 91931 -KEhhbmRsZQ== 91932 -IGFudW5jaQ== 91933 -L3B5 91934 -aW52YWxpZGF0ZQ== 91935 -IE1FUA== 91936 -dGVtcw== 91937 -O10v 91938 -6IM= 91939 -6L+Q 91940 -IHRhY28= 91941 -QURW 91942 -aHBw 91943 -QnV0dG9uQ2xpY2s= 91944 -IGJyaW5nZW4= 91945 -IFRJTUVPVVQ= 91946 -IGFzdHJvbG9neQ== 91947 -ZGF0ZUZvcm1hdA== 91948 -T0dSQVBI 91949 -RmlsZVN0cmVhbQ== 91950 -5a6h5qC4 91951 -LkNvbW0= 91952 -J2I= 91953 -IEdFVEdMT0JBTA== 91954 -ZWF0aW5n 91955 -YW5kZXN0 91956 -IFNFVFVQ 91957 -IEFkdmFuY2Vz 91958 -LnNjcm9sbEhlaWdodA== 91959 -QVpF 91960 -ZW5kdGltZQ== 91961 -d2VhdGhlcm1hcA== 91962 -IE1hbmdv 91963 -IFJJUA== 91964 -IGl0ZXJhdG9ycw== 91965 -IGNvYXg= 91966 -IOWbvg== 91967 -PG1haW4= 91968 -cm1z 91969 -cGNi 91970 -IHZhY2NpbmF0aW9ucw== 91971 -IGRpc2FncmVlbWVudHM= 91972 -CWV2ZW50cw== 91973 -PExvY2F0aW9u 91974 -Lk1lYXN1cmU= 91975 -IHF1ZWRh 91976 -IHNpZ25hbGxpbmc= 91977 -IGRlZ3JhZGVk 91978 -IEFtZWxpYQ== 91979 -LWNvbmZpZGVuY2U= 91980 -ZGJOYW1l 91981 -X2luYWN0aXZl 91982 -b25hdGlvbg== 91983 -IHBlcmlwaGVyYWxz 91984 -5qC3 91985 -U1VQRVI= 91986 -J1I= 91987 -LndheQ== 91988 -UExBSU4= 91989 -IEVuZ2Vs 91990 -cmVsYXk= 91991 -IGRlYmlkbw== 91992 -IFRyb3Rza3k= 91993 -6Iw= 91994 -INCw0LTRgNC10YE= 91995 -CXVzZXJz 91996 -ZXRjaHVw 91997 -dGVw 91998 -IG5ld1Bvc2l0aW9u 91999 -IHdhaXZlcnM= 92000 -ZWRpY2luZQ== 92001 -IHRhbmdnYWw= 92002 -IGFtbW9uaWE= 92003 -LWRldA== 92004 -L2V4ZWM= 92005 -KHBhZGRpbmc= 92006 -IFNob3BwaW5nQ2FydA== 92007 -IFByaW50Zg== 92008 -SGFuZGxlZA== 92009 -IE5BTUVT 92010 -KGNsb2Nr 92011 -IHt9Og== 92012 -IHNpbXM= 92013 -IFRlYXJz 92014 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0= 92015 -X0NBTk5PVA== 92016 -TEVHUk8= 92017 -LlNldFBhcmVudA== 92018 -5YW25Lit 92019 -IGVycmV1cg== 92020 -aXBp 92021 -PEV4cHJlc3Npb24= 92022 -LnRpbWVsaW5l 92023 -ICdfJyw= 92024 -IGNvYXRpbmdz 92025 -IHVzZUZvcm0= 92026 -LnRr 92027 -IEZlYXN0 92028 -LlNL 92029 -w6RzZW50 92030 -Y2h3aXR6 92031 -IGludmVudGl2ZQ== 92032 -IE1laQ== 92033 -IHZlc3RpYg== 92034 -IG7DpGNoc3Rlbg== 92035 -L2JpZw== 92036 -IHJldHJlYXRlZA== 92037 -IHByb3BhbmU= 92038 -dmljdGlt 92039 -QWt0 92040 -IFByZXNlcnZhdGlvbg== 92041 -IFBpcw== 92042 -X1NIQURPVw== 92043 -IHByaWNlbGVzcw== 92044 -csOzZA== 92045 -b2JibGVk 92046 -IHJvbGVOYW1l 92047 -IEdEUFI= 92048 -ICciLA== 92049 -Q2VudHJl 92050 -QXJjaGl0ZWN0dXJl 92051 -Q3BwQ2xhc3M= 92052 -IG1hdHRyZXNzZXM= 92053 -IGJlZXA= 92054 -IERhbWlhbg== 92055 -5p2D6ZmQ 92056 -YmV0dA== 92057 -X2Flcw== 92058 -KGNlbGxz 92059 -IOuwsOyXtA== 92060 -IGJpdG1hc2s= 92061 -Y291bGRu 92062 -LW5vdw== 92063 -IGlubm92YXRl 92064 -IGhhY2Vu 92065 -IEx5b25z 92066 -dGhpY2tuZXNz 92067 -IHdoaXN0bGVibG93ZXI= 92068 -JGZpbHRlcg== 92069 -IGV1bGVy 92070 -IEhhcm0= 92071 -IGxlZHM= 92072 -IEtlbHZpbg== 92073 -LnF1aWNr 92074 -IEzDs3Bleg== 92075 -cmV2ZQ== 92076 -IG5pZ2VyaWE= 92077 -IGp5bGxhbmQ= 92078 -LmVtcHR5TGlzdA== 92079 -IHVuc2V0dGxpbmc= 92080 -dXNiYW5k 92081 -IHRyYWNrZXJz 92082 -PVwiIjsK 92083 -IGNvbnRpbnVh 92084 -IE51bWVybw== 92085 -ZW5kb24= 92086 -IEdlcnJ5 92087 -LlRPRE8= 92088 -UmVwZWF0ZWQ= 92089 -IFNlcmVuYQ== 92090 -0LjQvNCw0LvRjA== 92091 -cHJvZmls 92092 -INCy0YHQtdGF 92093 -QGFkbWlu 92094 -LkxpbmVz 92095 -IHRyYW5zbWlzc2lvbnM= 92096 -IGNq 92097 -YW7Dp2E= 92098 -5Yig6Zmk5oiQ5Yqf 92099 -IGdldE1lbnVJbmZsYXRlcg== 92100 -dWZyZXE= 92101 -IE1hdGhlbWF0aWNhbA== 92102 -TmF2aWdhdG9yTW92ZQ== 92103 -IGZ3ZA== 92104 -dW5pdHRlc3Q= 92105 -IHN5bnRoZXNpemVk 92106 -IGNyZWVk 92107 -KEZyYW1l 92108 -cHN5Y2g= 92109 -dm9k 92110 -dUM= 92111 -4bqndQ== 92112 -IOKAnOKApg== 92113 -IGtyYXQ= 92114 -ZHJhd2FibGU= 92115 -w6ZyZQ== 92116 -PXRvcA== 92117 -KExvZ2dlcg== 92118 -RXJyb3JFeGNlcHRpb24= 92119 -YWlzYWw= 92120 -L3dz 92121 -dWxsZWQ= 92122 -QVJJTkc= 92123 -IG5JbmRleA== 92124 -IGludGVybmFscw== 92125 -IGVmZmljaWVuY2llcw== 92126 -ICNA 92127 -X2JyaWdodG5lc3M= 92128 -X25vcm1hbHM= 92129 -IFN0b3V0 92130 -IHVudmVpbA== 92131 -IFNob3Rz 92132 -LWNvbXBhbnk= 92133 -X2VsdA== 92134 -KGRsbGV4cG9ydA== 92135 -IHByb2R1Y2Npw7Nu 92136 -Q2lzY28= 92137 -Qmxha2U= 92138 -LW1vdXRo 92139 -UGVhcg== 92140 -INC00L7RgdGC0YPQvw== 92141 -IEpBQ0s= 92142 -IO2YuA== 92143 -IHN0b3B3b3Jkcw== 92144 -IFRlc3M= 92145 -IHBvc3Rl 92146 -cmF6aWVy 92147 -6K0= 92148 -TWVzc2FnaW5n 92149 -t+aWsA== 92150 -VGFtYmFo 92151 -IG5hcmNvdGljcw== 92152 -IGNhbXBlcg== 92153 -IHRyaXBvZA== 92154 -IGdsRW5k 92155 -IGdpb2M= 92156 -Y29tYmU= 92157 -VXNlclJvbGU= 92158 -VWw= 92159 -RXF1aXZhbGVudA== 92160 -IGdub21l 92161 -IEZ1w58= 92162 -cGFja2FnZU5hbWU= 92163 -X3Vl 92164 -RGlzY2xvc3VyZQ== 92165 -YW1hdGU= 92166 -X3RlbnNvcnM= 92167 -IEthdGhyeW4= 92168 -X0Jhcg== 92169 -VGhyZWFkSWQ= 92170 -IHZlcmlmaWNh 92171 -LmFzc2VydE51bGw= 92172 -IE9kaW4= 92173 -YsOp 92174 -INGB0L7RgdGC 92175 -IGp0 92176 -LlNlbGVjdGVkSXRlbXM= 92177 -IGFjdGlvbmFibGU= 92178 -IFJlZ2FyZHM= 92179 -aGVr 92180 -Om51bWVs 92181 -LEdM 92182 -IFBIT05F 92183 -CURlZmF1bHQ= 92184 -IGVsYXN0 92185 -IGJlY2s= 92186 -PWNyZWF0ZQ== 92187 -OicK 92188 -YXJodXM= 92189 -bW9kaWZpZXJz 92190 -aW50cHRy 92191 -IHByb3Bpbw== 92192 -77yI56yR 92193 -IHJlcXVlc3RPcHRpb25z 92194 -IGltcGxpYw== 92195 -IGR1cm8= 92196 -IFBDUw== 92197 -RGVsaW1pdGVy 92198 -KGxvZ2l0cw== 92199 -LkVWVA== 92200 -V2l0aENvbnRleHQ= 92201 -IG9sdHJl 92202 -X0VYRUNVVEU= 92203 -b2xpY2l0ZWQ= 92204 -X0VudGVy 92205 -L2Zyb20= 92206 -INGB0LvQvtCy 92207 -IEhvcm0= 92208 -dWliTW9kYWw= 92209 -X0lORklOSVRZ 92210 -77yM44CK 92211 -VUdJTlM= 92212 -T05HTA== 92213 -LGJ1Zg== 92214 -IHBvdXJyYWl0 92215 -cGo= 92216 -KGN1YmU= 92217 -IHVnbA== 92218 -IFNhd3llcg== 92219 -SUZFU1Q= 92220 -QXBpcw== 92221 -IENvcmVEYXRh 92222 -IHNlc2FtZQ== 92223 -LnB0aA== 92224 -LmdldFVzZXJOYW1l 92225 -Y2FzZWQ= 92226 -IHZhbmlzaA== 92227 -X0FwaQ== 92228 -Ly86 92229 -L25vbg== 92230 -LmRvY2tlcg== 92231 -LnNp 92232 -YWxlcnRz 92233 -IGludGVzdGluZQ== 92234 -cGFydGljaXBhbnRz 92235 -LXZpc2libGU= 92236 -ZW1zcA== 92237 -bXVl 92238 -X3B2 92239 -IENyaQ== 92240 -b2dyYQ== 92241 -X2V4cGVyaWVuY2U= 92242 -IElOVEVSVkFM 92243 -X3JlZ3Jlc3Npb24= 92244 -7ZWY7IS47JqU 92245 -ZW5kZXJlY28= 92246 -bGF0YWJsZQ== 92247 -LmxvY2FsdGltZQ== 92248 -IEJJVFM= 92249 -IEZvbGRpbmc= 92250 -CSAJCQ== 92251 -w6lzZQ== 92252 -LWJlYXJpbmc= 92253 -IFhQQVI= 92254 -T1BTSVM= 92255 -J14kJyw= 92256 -aW5jbA== 92257 -IE9wcmFo 92258 -IGJvb3Rocw== 92259 -IFJvaGluZw== 92260 -LkJvcmRlclNpZGU= 92261 -YXRhdHlwZQ== 92262 -Q3JlYXRlZEJ5 92263 -LOKAmeKAnQ== 92264 -ZG9jdHJpbmU= 92265 -IGJyZWF0aGVk 92266 -X2JlZw== 92267 -IGFmZmxpY3RlZA== 92268 -TW91bnRhaW4= 92269 -QmxvYw== 92270 -IHJ1aW5pbmc= 92271 -LkFubm90YXRpb25z 92272 -CWludGVudA== 92273 -IHN0YXRpY2FsbHk= 92274 -X1V0aWxz 92275 -TGF1bmNoZXI= 92276 -Om5vcm1hbA== 92277 -IHVzZXJpbmZv 92278 -LUp1bA== 92279 -S3lsZQ== 92280 -LlJlYWRVSW50 92281 -KHVybHM= 92282 -L2lm 92283 -bWl0dGVs 92284 -YmNt 92285 -QE1vZHVsZQ== 92286 -IENvbnN0YW50aW4= 92287 -IGJq 92288 -ZXJuYXV0 92289 -PHI= 92290 -IE1lbnRvcg== 92291 -IGVncmV0 92292 -X29hdXRo 92293 -LkRhdGFDb250ZXh0 92294 -X0NMSQ== 92295 -KENvbnN0cnVjdG9y 92296 -IHNldFBvc2l0aW9u 92297 -cmVzYXI= 92298 -ZW50aW5n 92299 -4Li54Lil 92300 -VHJhbnNtaXNzaW9u 92301 -IG5vdGlmeURhdGFTZXRDaGFuZ2Vk 92302 -IE1vdXNlQnV0dG9u 92303 -ICoi 92304 -ICAgICAgICAgICAgICAgDQo= 92305 -IEx5ZGlh 92306 -IHN3b3Jl 92307 -IHBsYXRhZm9ybWE= 92308 -CWJ1dHRvbnM= 92309 -IHNwcnVuZw== 92310 -KFRva2VuVHlwZQ== 92311 -Q3g= 92312 -QXF1 92313 -CQkJCQkJCQkJICA= 92314 -CUFERA== 92315 -dWlkcw== 92316 -IOCkrg== 92317 -IOaXtumXtA== 92318 -LkFjdGlvbkJhcg== 92319 -IG9jdXI= 92320 -IGlsbWE= 92321 -LW5ldXRyYWw= 92322 -ICIuIjsK 92323 -CVNpemU= 92324 -UGllY2Vz 92325 -IHN0aWY= 92326 -ICI9Iiw= 92327 -IEVxdWl2YWxlbnQ= 92328 -IGlnZW4= 92329 -ZGZk 92330 -X3RoaWNrbmVzcw== 92331 -X3JlYWRhYmxl 92332 -L2ZhbHNl 92333 -IHRvb2x0aXBz 92334 -b3BsYXN0 92335 -aHVh 92336 -aGFuZGxlUmVxdWVzdA== 92337 -LkxBWlk= 92338 -PFVGdW5jdGlvbg== 92339 -aW1tdXRhYmxl 92340 -aWhpbGF0aW9u 92341 -IG9ydGhvZG94 92342 -LnBvcHVsYXRl 92343 -IHZlcmE= 92344 -IG9iZXI= 92345 -c2FuZA== 92346 -dmln 92347 -Q29uZmVyZW5jZQ== 92348 -KENvbGxpc2lvbg== 92349 -L2F1dG8= 92350 -IFNvbGlkQ29sb3JCcnVzaA== 92351 -Kic= 92352 -LGFkZHJlc3M= 92353 -IHN3ZWV0aGVhcnQ= 92354 -w6F0aWNhcw== 92355 -YW5pbmU= 92356 -X3BheW1lbnRz 92357 -IHVubWlzdA== 92358 -IHRydW1wZXQ= 92359 -QkFM 92360 -IGZpbGVJZA== 92361 -bmllanM= 92362 -QURG 92363 -IG1uaXN0 92364 -IEZlaGxlcg== 92365 -44CRLA== 92366 -Q2hhcmFjdGVyU2V0 92367 -IFZhbmNl 92368 -SW5zZXJ0ZWQ= 92369 -IGRvd253YXJkcw== 92370 -IHJvdGF0aW9uYWw= 92371 -IGVuY291bnRlcmluZw== 92372 -TUJQcm9ncmVzc0hVRA== 92373 -L1N5c3RlbQ== 92374 -L3BvcA== 92375 -IH0pDQoNCg== 92376 -IC4nPC8= 92377 -77yJDQo= 92378 -IGRjYw== 92379 -YXN5YXJha2F0 92380 -IHByaW5jaXBhbGx5 92381 -5a6a5LmJ 92382 -KGNob2ljZXM= 92383 -LnBhZ2luYXRvcg== 92384 -IHVwYnJpbmdpbmc= 92385 -IGRvdGVudg== 92386 -KCkpLw== 92387 -IFRBUw== 92388 -Z2Nk 92389 -X2ludGY= 92390 -Lm11dGV4 92391 -cHJlc3Rhc2hvcA== 92392 -IGLDtnI= 92393 -ZGFw 92394 -X2RlbWFuZA== 92395 -XERlc2t0b3A= 92396 -dG9GbG9hdA== 92397 -IHNlZ3JlZ2F0ZWQ= 92398 -IGNsaW1hdGVz 92399 -Lk9yZGVyQnlEZXNjZW5kaW5n 92400 -KCcsJyk= 92401 -UHVsbFBhcnNlcg== 92402 -QXRvbXM= 92403 -IGJlbsO2dA== 92404 -IGhvbWVy 92405 -YW50dQ== 92406 -SXNFbXB0eQ== 92407 -IEJlZ2lucw== 92408 -PlNob3c= 92409 -IFN1cHBsZW1lbnRz 92410 -b2NjdXM= 92411 -IGRvcGU= 92412 -LmJvb2tpbmc= 92413 -IEFsbWlnaHR5 92414 -W2VkZ2U= 92415 -IEViYXk= 92416 -X3JhY2U= 92417 -RnJvemVu 92418 -X3RyYXZlbA== 92419 -IHBhc3RvcnM= 92420 -X1NVUkZBQ0U= 92421 -X2dlbnJl 92422 -X0hPVA== 92423 -LGRpbQ== 92424 -VGJs 92425 -bXRz 92426 -cHJlZGljdGlvbnM= 92427 -X2N1bQ== 92428 -IGRldGFsbGVz 92429 -LXRyYW5zaXRpb25hbA== 92430 -IHdha2V1cA== 92431 -UGVyc29ucw== 92432 -LmNvbG9yYmFy 92433 -U3RyYW5nZQ== 92434 -2K/Zhw== 92435 -Jlc= 92436 -IEFSUA== 92437 -X1NPRlQ= 92438 -X2RyYWZ0 92439 -SVZB 92440 -IGdyb3A= 92441 -IGxpZWJl 92442 -IGlpZA== 92443 -2KfYsw== 92444 -Y2FuZGlkYXRlcw== 92445 -Z2V0QXM= 92446 -PV8oIg== 92447 -LkdldE9yZGluYWw= 92448 -KSk9PQ== 92449 -YW5ub3RhdGU= 92450 -IEx1bWlh 92451 -SVJNV0FSRQ== 92452 -X09QRU5HTA== 92453 -KGZvcm1EYXRh 92454 -ZW50aW1lcw== 92455 -IHdhdGVyc2hlZA== 92456 -INCx0LXQtw== 92457 -IGZsb3BweQ== 92458 -VG93YXJkcw== 92459 -KGNvbXBhY3Q= 92460 -RERE 92461 -e24= 92462 -IHBva2luZw== 92463 -QG0= 92464 -IHJlY3ljbA== 92465 -c3RydWN0b3Jz 92466 -a2V5Q29kZQ== 92467 -IHZlaGVtZW50 92468 -IGxpdHJl 92469 -IEJJTkQ= 92470 -IEZyYW5jb2lz 92471 -IG51ZGl0eQ== 92472 -IGlzaXpl 92473 -CW9uQ2xpY2s= 92474 -eXN0YWxz 92475 -IGdldFN5c3RlbVNlcnZpY2U= 92476 -V2ViUmVzcG9uc2U= 92477 -ZmlsZXNpemU= 92478 -IENobG9y 92479 -Y29saQ== 92480 -X3NlYXQ= 92481 -LkFkZEluUGFyYW1ldGVy 92482 -KXRlc3Q= 92483 -IHF1ZXM= 92484 -IGNhdXRpb3VzbHk= 92485 -ImRpc3BsYXk= 92486 -LnNodG1s 92487 -IEdVSURBVEE= 92488 -KCIqKg== 92489 -IGdyYW5kZGF1Z2h0ZXI= 92490 -IEFzc2VtYmx5RGVzY3JpcHRpb24= 92491 -Rm9yRWFjaA== 92492 -V2lsc29u 92493 -LGVn 92494 -IGJlbGlldmFibGU= 92495 -IGNyb3Nzd29yZA== 92496 -bG9iYmVy 92497 -IFN0YXBsZXM= 92498 -KHNoaXA= 92499 -IHdhZ2Vk 92500 -IEJvbHNoZXZpaw== 92501 -LkFkZEl0ZW0= 92502 -KEZpbHRlcg== 92503 -X0FCQw== 92504 -IGBc 92505 -0L7RiQ== 92506 -IG1ib3g= 92507 -IE5lcw== 92508 -IEFWQ2FwdHVyZQ== 92509 -IGNvbmhl 92510 -IElOVEVSTkFUSU9OQUw= 92511 -b3Nn 92512 -IF0pLT4= 92513 -U0tUT1A= 92514 -IGtpZGQ= 92515 -IFNTVA== 92516 -IOWFsw== 92517 -IEV0aG5pYw== 92518 -RVJTSEVZ 92519 -IG11bHRpYw== 92520 -X01VTA== 92521 -IEZpbmRPYmplY3RPZlR5cGU= 92522 -IEV4cGVuc2Vz 92523 -Z2V0TW9ja0J1aWxkZXI= 92524 -LWd1aWRl 92525 -J0w= 92526 -IOeZuw== 92527 -IHJhag== 92528 -IEJsYW5jaA== 92529 -IEFkZHJlc3Nlcw== 92530 -Tng= 92531 -IElzbGFtYWJhZA== 92532 -0L7QutGD0LzQtdC90YI= 92533 -IEJlYXZlcg== 92534 -LnN0dWRlbnRz 92535 -IEFzeW5jQ2FsbGJhY2s= 92536 -c2hlZXRz 92537 -ZWNhc3Q= 92538 -IEZ1bmRhbWVudGFs 92539 -IHZlcmRpZW5lbg== 92540 -IGV4YWNlcmJhdGVk 92541 -IE1vZGVyYXRvcg== 92542 -Q0NDQ0ND 92543 -IHRpbWVvdXRz 92544 -IHN1YmRpdmlzaW9ucw== 92545 -IGNvbXByb21pc2Vz 92546 -dXp6ZXI= 92547 -fSwkew== 92548 -X2Jsb2NraW5n 92549 -ZXJtYW5u 92550 -IE1pa2hhaWw= 92551 -IFNlbGJzdA== 92552 -6ZSA 92553 -LnNob3dz 92554 -5LiH5YWD 92555 -IFRm 92556 -IElIdHRwQWN0aW9uUmVzdWx0 92557 -IElFbnRpdHk= 92558 -IGlx 92559 -Rk1M 92560 -b2RlbQ== 92561 -c3Rw 92562 -dWN0aW9ucw== 92563 -LmZhdm9yaXRl 92564 -LkdldERpcmVjdG9yeU5hbWU= 92565 -IGdyYWM= 92566 -IHhtbERvYw== 92567 -X3B1c2hCdXR0b24= 92568 -Y29sbGVjdG9y 92569 -PWV4cGxvZGU= 92570 -IGRlc3RpbmF0aW9uVmlld0NvbnRyb2xsZXI= 92571 -IFNlcmlhbGl6ZWQ= 92572 -Om1lc3NhZ2U= 92573 -IENDQw== 92574 -X3JlY292ZXJ5 92575 -LWtpdA== 92576 -c2hpbWE= 92577 -cm90Y2g= 92578 -IGB9Cg== 92579 -X3N1cHA= 92580 -VGFibGE= 92581 -0YDQtdC00LXQuw== 92582 -R3RrV2lkZ2V0 92583 -IFNJTVBMRQ== 92584 -LnBoaQ== 92585 -IExpYmVydGllcw== 92586 -LS1b 92587 -IHVudmVpbGluZw== 92588 -IGV4dGVudHM= 92589 -YmNk 92590 -IGh2YWQ= 92591 -CWNy 92592 -LnJlYWRkaXI= 92593 -IHJlYWRhYmlsaXR5 92594 -IGRpc21pc3Npbmc= 92595 -Q2FtYg== 92596 -IGNhc3VhbHR5 92597 -IElQVg== 92598 -bWl0ZXM= 92599 -IHB1cmlmaWVk 92600 -Lk9yaWVudGF0aW9u 92601 -IGxq 92602 -aW11bGF0b3I= 92603 -ZnJhbQ== 92604 -L2xvY2F0aW9u 92605 -IGNvbW11bmljYXRlcw== 92606 -OlVJQWxlcnQ= 92607 -L3NvY2lhbA== 92608 -ZWx5bg== 92609 -REVO 92610 -INee 92611 -IGJlZm9yZVNlbmQ= 92612 -IFVudGVycw== 92613 -JykuIg== 92614 -ICcnKTs= 92615 -LndyaXRlT2JqZWN0 92616 -KGdyYW1tYXJBY2Nlc3M= 92617 -IEFwcGxpY2F0aW9uQ29udGV4dA== 92618 -QnlVc2VybmFtZQ== 92619 -IHNraXBz 92620 -IGZpbGhv 92621 -IHZpZXV4 92622 -IG1SZWN5Y2xlclZpZXc= 92623 -IGFyb3VzZWQ= 92624 -Lm93bA== 92625 -IGN1cmxlZA== 92626 -L2NhbGxiYWNr 92627 -KCc6Jylb 92628 -IGludW5k 92629 -IGJyZWFrcG9pbnRz 92630 -LWV2ZW4= 92631 -LnN0ZW0= 92632 -IGRlcm9n 92633 -IG5lcA== 92634 -IENvbXBsZXRhYmxlRnV0dXJl 92635 -LUxpbmU= 92636 -Lyov 92637 -LkhleA== 92638 -IHJ1c3Nl 92639 -IGJpZg== 92640 -IEZvbmQ= 92641 -aWVjdA== 92642 -IGFsbG90dGVk 92643 -ZGV0ZWN0b3I= 92644 -IC8KCg== 92645 -ZW1vZGU= 92646 -dWhl 92647 -dWlzc2U= 92648 -IEZJWEVE 92649 -bWF0aHJt 92650 -IHVuc3Vz 92651 -IEF1dG9z 92652 -IC4uLi4uLi4uLi4= 92653 -LnRyYXZlbA== 92654 -TkFW 92655 -IGxlc2Jpc2s= 92656 -IMO8emVy 92657 -IGNsZXJpYw== 92658 -IGxpbWl0bGVzcw== 92659 -b2x1Y2lvbg== 92660 -IG5lY2tsaW5l 92661 -IGRyaWZ0ZWQ= 92662 -IFJlbGlhYmxl 92663 -IENhcnk= 92664 -IHRlbsOtYQ== 92665 -ID8+Jw== 92666 -L2NvbW1vbnM= 92667 -IEdNQw== 92668 -X05QQw== 92669 -IEJsaXNz 92670 -IEJ1cm1h 92671 -5ZCM5pe2 92672 -KGRlcGVuZA== 92673 -LXN1aXRl 92674 -CXN0YWdl 92675 -RG91Zw== 92676 -aWRlbnRpZmljYXRpb24= 92677 -X3Jlc29sdmVy 92678 -QmVnYW4= 92679 -W3RocmVhZA== 92680 -IDsKCgo= 92681 -TlRTVEFUVVM= 92682 -IGRpc29iZWQ= 92683 -fGg= 92684 -IGFjY3VtdWxhdGluZw== 92685 -ICIsIik7Cg== 92686 -dVBhcmFt 92687 -LmJpbGw= 92688 -cml0Y2g= 92689 -Q3JpbWU= 92690 -0LXRgdGM 92691 -IFJlbWFpbg== 92692 -54Sh5paZ 92693 -X1RIQVQ= 92694 -YCJdCg== 92695 -LnN0YW1w 92696 -IHBhcmFub3JtYWw= 92697 -IE1QQw== 92698 -InVybHM= 92699 -IEVzdGF0ZXM= 92700 -VG9Gcm9udA== 92701 -VGhpcnR5 92702 -QmV0aA== 92703 -J3U= 92704 -IOy9lOuTnA== 92705 -VUZBQ1Q= 92706 -IENyb20= 92707 -IE1pc3Rlcg== 92708 -IEVRVUFM 92709 -ZW5oZWlt 92710 -IC8vew== 92711 -X3dhcw== 92712 -IGJvdXF1ZXQ= 92713 -IE1pZGRsZXRvbg== 92714 -aXp1 92715 -X2hhc2hlcw== 92716 -IGhlbm5l 92717 -IExJTlVY 92718 -CVNlcnZpY2U= 92719 -IFRBTQ== 92720 -IGBf 92721 -IEFUQQ== 92722 -IGRhbmdsaW5n 92723 -cGFpbg== 92724 -X0JPVU5EUw== 92725 -cHJvZ3JhbW1pbmc= 92726 -IGN1cnJlbnRJdGVt 92727 -IGJlc2ll 92728 -ZW1ibGU= 92729 -KGNhbGM= 92730 -LlNraW4= 92731 -IHBlYXJscw== 92732 -IEJ1cmI= 92733 -LW1vbml0b3I= 92734 -L2Nz 92735 -Zmly 92736 -KHZlcg== 92737 -W2FyZ3M= 92738 -w7xja2Vu 92739 -ZXBhcmF0b3I= 92740 -RG91 92741 -LkVudA== 92742 -IEVTQQ== 92743 -KGZt 92744 -dG9uZXM= 92745 -IFphYw== 92746 -a3NhbQ== 92747 -4oCZYWxs 92748 -IE1TUw== 92749 -IkRvbg== 92750 -IHNpbXBsZXg= 92751 -IENvbnNjaW91cw== 92752 -IEFwcGxpY2FudA== 92753 -cGVsbGllcg== 92754 -IHBlZGVzdGFs 92755 -JGh0dHA= 92756 -IEF2YQ== 92757 -LkNH 92758 -IGludMOpcmVzcw== 92759 -IEludGVncmFs 92760 -cmVkZQ== 92761 -PWZvcm1hdA== 92762 -LlBhdGhz 92763 -X1BBUlRJVElPTg== 92764 -IHNlaA== 92765 -IFF1YW5kbw== 92766 -WW91dHViZQ== 92767 -LnB1dFRleHQ= 92768 -7KO87IS47JqU 92769 -LkFXUw== 92770 -IENzdg== 92771 -Q3Vyc29yUG9zaXRpb24= 92772 -LWJlZ2lu 92773 -X2NvdW50cmllcw== 92774 -LXJhbmRvbQ== 92775 -5Y2z 92776 -UGhpbGw= 92777 -IHBhbm9yYW1h 92778 -IHRoZXJlcw== 92779 -5Y+q 92780 -IHNpbGVuY2Vk 92781 -IEN1bWJlcmxhbmQ= 92782 -LlZpc2libGVJbmRleA== 92783 -LnN0YXRpc3RpY3M= 92784 -IHByb3BlbGxlZA== 92785 -QW1lcmljYW5z 92786 -IHZhbGlkYQ== 92787 -IEd1YW0= 92788 -IEZFTUE= 92789 -LnN5bnRheA== 92790 -ZGdl 92791 -IGRlZXBlbg== 92792 -ICAgICAgICAJCQkJ 92793 -IFNwZWNpYWxpc3Rz 92794 -IFNhbnRhbmE= 92795 -IEJlZXRsZQ== 92796 -ICUKCg== 92797 -VXNlclByb2ZpbGU= 92798 -KCIkLg== 92799 -IGVtcGxvaQ== 92800 -IGVtYWlsaW5n 92801 -Z2V0T3JFbHNl 92802 -X1VQUEVS 92803 -LmRyaXZl 92804 -IHJlZGhlYWQ= 92805 -Rk9VTkRBVElPTg== 92806 -IG11bHRpcGxpYw== 92807 -L2VmZmVjdHM= 92808 -IGhhbmR3cml0aW5n 92809 -X3Rh 92810 -IEJheg== 92811 -w7ZmZmVudA== 92812 -cHJpeA== 92813 -IGNoaXBzZXQ= 92814 -IGlwQWRkcmVzcw== 92815 -w61kYQ== 92816 -IFVuZw== 92817 -IFNjaGE= 92818 -LkZMT0FU 92819 -IHF1aWVybw== 92820 -b2Nocm9tZQ== 92821 -IHJlZWZz 92822 -YnNvbg== 92823 -IG3Dug== 92824 -IHRyYXlz 92825 -Qm9tYg== 92826 -IG15TGlzdA== 92827 -eGltaXR5 92828 -IERlbmc= 92829 -VW5p 92830 -LVNlcmllcw== 92831 -b2dhbnk= 92832 -bMSxaw== 92833 -L2NhbA== 92834 -IHJlYWxpemE= 92835 -IEhpYg== 92836 -CQoJCgo= 92837 -IGh1bWlsaWF0aW5n 92838 -WyR7 92839 -IHByZXRlbmRlZA== 92840 -IERhdGVuc2No 92841 -YW5zaWJsZQ== 92842 -CXJlbG9hZA== 92843 -IG1pZ2xpb3I= 92844 -X2JldA== 92845 -IHRvdGFsVGltZQ== 92846 -IEJheHRlcg== 92847 -IGVuYW1lbA== 92848 -L0ltYWdlcw== 92849 -IFNFUw== 92850 -IFNwcmluZ0FwcGxpY2F0aW9u 92851 -KWluaXRXaXRoRnJhbWU= 92852 -CWNhbA== 92853 -RUxFTUVOVA== 92854 -IEd1dGg= 92855 -KEJpZ0ludGVnZXI= 92856 -IE1lZGk= 92857 -Lk1lbWJlcnM= 92858 -IHJlam9pY2U= 92859 -IGRvZg== 92860 -UEVuZFBvaW50 92861 -IGNsaXQ= 92862 -X1JFVVNF 92863 -TWFrZXM= 92864 -IHN6eQ== 92865 -IHNoYWRlZA== 92866 -IGZhdm91cmVk 92867 -aXN0b2w= 92868 -ZGV4 92869 -IGZsZXhHcm93 92870 -hac= 92871 -X3ByaW50ZXI= 92872 -LmZuYW1l 92873 -cGVyYXRpb24= 92874 -IG7Ds3M= 92875 -Z2dlcg== 92876 -6ICB 92877 -INCy0YDQtdC80Y8= 92878 -KGVmZmVjdA== 92879 -QnlVcmw= 92880 -IEFQUw== 92881 -dHV0b3JpYWw= 92882 -ZWpz 92883 -U3FsUGFyYW1ldGVy 92884 -IHNjcmFwcw== 92885 -R3JlZXRpbmdz 92886 -RmVk 92887 -IFJFTkRFUg== 92888 -IGJsb29tcw== 92889 -IGRlYmlsaXRhdGluZw== 92890 -b21ldHJpY3M= 92891 -IHNpbWls 92892 -LWhlcm8= 92893 -IHJlYWxwYXRo 92894 -ZGVwYXJ0bWVudHM= 92895 -QklORA== 92896 -IENhc3NpZHk= 92897 -bGlhbg== 92898 -U0tJUA== 92899 -LWNsZWFu 92900 -IHNpbGRlbmFmaWw= 92901 -X211bHRpcA== 92902 -anNvbkRhdGE= 92903 -QWdlbnRz 92904 -LmZoaXI= 92905 -IHRyaXVt 92906 -IGFzdG9yZQ== 92907 -IG5leA== 92908 -OnVwZGF0ZQ== 92909 -INC00LA= 92910 -4KSy 92911 -OyIpCg== 92912 -LlRleHRJbWFnZVJlbGF0aW9u 92913 -IG1pY3Jvc2NvcHk= 92914 -U1VS 92915 -YW5reQ== 92916 -IFBldGl0 92917 -bWFya2V0aW5n 92918 -IHZlcmlmaWNhcg== 92919 -YW1hZ2Vk 92920 -Y3Ro 92921 -IGluY29uc2lzdGVuY2llcw== 92922 -IG1hasSF 92923 -IGdldEluZm8= 92924 -IHBhc3Npb25hdGVseQ== 92925 -IGljbXA= 92926 -W10+Cg== 92927 -U2luZ2Fwb3Jl 92928 -IE5ld3Rvd24= 92929 -IHJhaWxpbmc= 92930 -IEVubGlnaHRlbm1lbnQ= 92931 -dXRoZXJsYW5k 92932 -bGVpbmU= 92933 -X3JlZ2lzdHJv 92934 -IEVyaWNh 92935 -X3RpY2tldHM= 92936 -L21ldGhvZA== 92937 -aXp6YXRv 92938 -R2F0dA== 92939 -LWZlYXR1cmU= 92940 -IDotKQ== 92941 -IHNlcnBlbnQ= 92942 -IEdyb3VwTGF5b3V0 92943 -TmlrZQ== 92944 -dW5nYQ== 92945 -IE1pbQ== 92946 -IGluY2Vzcw== 92947 -IGRlcGxldGlvbg== 92948 -X2xvdA== 92949 -IGJpcnRoZGF5cw== 92950 -IHJlbnRlcnM= 92951 -IGVxdWlwb3M= 92952 -IExlaHI= 92953 -X1BsYXk= 92954 -IHNwaWVsZQ== 92955 -IExBTkQ= 92956 -IEVuY291bnRlcg== 92957 -aXphbmRv 92958 -IHBlcnU= 92959 -IHNsYW1taW5n 92960 -IHJlaW5zdGFsbA== 92961 -IGFuZ2k= 92962 -SW5UaGVEb2N1bWVudA== 92963 -IHZlcnNjaGlsbA== 92964 -IHZlcnNv 92965 -LnN0YWZm 92966 -KHZw 92967 -KGFjY291bnRz 92968 -Z2V0QXBwbGljYXRpb24= 92969 -IG1hbnRlbmVy 92970 -LlNP 92971 -LkFE 92972 -IE1vcm1vbnM= 92973 -CXJlYWw= 92974 -IGhvdGxpbmU= 92975 -IENhcmRpbw== 92976 -cGFnZUluZGV4 92977 -Ymplcmc= 92978 -Rm8= 92979 -IGNvbnNlaWxz 92980 -IG1pZ3JhaW5l 92981 -IGxhdGlubw== 92982 -IHRvcnBlZG8= 92983 -amFiaQ== 92984 -L3Jz 92985 -dWJiZXI= 92986 -IENsYXNzZQ== 92987 -4Lw= 92988 -KC9eXA== 92989 -X2RlcGxveQ== 92990 -R1JFUw== 92991 -IFdIQVRTT0VWRVI= 92992 -IGFyY3B5 92993 -IG1pZWpzYw== 92994 -QXJteQ== 92995 -IHNjaMO2bmU= 92996 -IGJtaQ== 92997 -IDoiOwo= 92998 -IENydWlzZXI= 92999 -cWg= 93000 -LnByZXBlbmQ= 93001 -IHZpdmU= 93002 -b3JpYXNpcw== 93003 -ICE9Cg== 93004 -dGVnYQ== 93005 -YW1lZGk= 93006 -UHJvamVjdGVk 93007 -LWJyZQ== 93008 -LHJlYWRvbmx5 93009 -IHN1YlRpdGxl 93010 -IG1pc3Ry 93011 -IEluaGFs 93012 -Y292ZXJpbmc= 93013 -IHppag== 93014 -IEFSVElDTEU= 93015 -UlVMRQ== 93016 -IGFsdHJv 93017 -IHNldHRsZXM= 93018 -aWRlbGJlcmc= 93019 -OiIuJA== 93020 -KGZl 93021 -X2Jt 93022 -IHByb3ByaWV0b3I= 93023 -IGtlZXI= 93024 -U2VwYXJhdGVk 93025 -X05FQVJFU1Q= 93026 -KHN0cnBvcw== 93027 -IENvbXB1dGF0aW9uYWw= 93028 -IGVybg== 93029 -SW5WaWV3 93030 -QWNyb3Nz 93031 -IGZydWl0eQ== 93032 -X21hcHBlZA== 93033 -IGdyYXR1aXRlbWVudA== 93034 -IHt9CgoK 93035 -cG90ZW50aWFs 93036 -cGFudHM= 93037 -IHNlbnRpbWVudGFs 93038 -IExpbmtlZGlu 93039 -KHBhdGNo 93040 -IGFkYXB0b3I= 93041 -IFVJU3Rvcnlib2FyZA== 93042 -IHNsYXNoaW5n 93043 -KCIvOg== 93044 -IHRleHREZWNvcmF0aW9u 93045 -LmRpYWc= 93046 -XFJlZGlyZWN0 93047 -IG5ldXJvc2NpZW5jZQ== 93048 -IEFkanVzdG1lbnQ= 93049 -IFNjb3RjaA== 93050 -IENvc2J5 93051 -U0VB 93052 -PXZpZXc= 93053 -IGV2b2x2ZXM= 93054 -IFNhbGlzYnVyeQ== 93055 -44CB4oCc 93056 -ZXZlcnlvbmU= 93057 -KGFyYw== 93058 -IGFwYXJ0aGVpZA== 93059 -IGF6aW11dGg= 93060 -IFNoYW1hbg== 93061 -2KU= 93062 -w7NuaWNh 93063 -OmNsYXNz 93064 -IEluamVjdG9y 93065 -YWhhcw== 93066 -YWJsZXI= 93067 -X2VzdGltYXRvcg== 93068 -X0NVQkU= 93069 -IEtyYW5r 93070 -IHVuZmF2b3JhYmxl 93071 -IHJlcHV0ZWQ= 93072 -IENvbmRpdGlvbmFs 93073 -IG1pbGZz 93074 -IFJlc3RyaWN0aW9ucw== 93075 -KGhyZWY= 93076 -SnVhbg== 93077 -PEVudHJ5 93078 -CXRlbXBsYXRlVXJs 93079 -X3Byb2R1Y3Rpb24= 93080 -VHlwZUlE 93081 -IGJhbGs= 93082 -IG5ld0Fycg== 93083 -IGxpY2VuY2Vz 93084 -LnNvbHV0aW9u 93085 -LnNhbQ== 93086 -IEh2 93087 -IHRyZW1ibGluZw== 93088 -WWF3 93089 -IGZsZWVjZQ== 93090 -IHNob3ZlbA== 93091 -V2Vy 93092 -IHBhdHRlcg== 93093 -PVk= 93094 -IEZybQ== 93095 -U2NyZWVucw== 93096 -JCI= 93097 -IEJsb25k 93098 -INGB0LjRgdGC0LXQvA== 93099 -KG9k 93100 -IG5vY3Q= 93101 -b3VudGVycw== 93102 -dXNlcHBl 93103 -fGludA== 93104 -LnJlbWFpbmluZw== 93105 -IHVsdGltbw== 93106 -IG1hc3R1cmJhdGluZw== 93107 -bW1j 93108 -PUc= 93109 -Il19Cg== 93110 -IGZlYXJsZXNz 93111 -IGFsZ3VtYXM= 93112 -Y3VsdA== 93113 -QWx0ZXJuYXRpdmVseQ== 93114 -5bKB 93115 -T0RFVg== 93116 -IEFkb3B0aW9u 93117 -IHdlYWx0aGllc3Q= 93118 -IG1lbnRyZQ== 93119 -L2dvdG8= 93120 -IGluZm9ybWFudA== 93121 -IFJvdXQ= 93122 -b2Zp 93123 -IGhhbW1lcmVk 93124 -IEVzdG8= 93125 -4oCZQnJpZW4= 93126 -IMWa 93127 -IGRlbWk= 93128 -INGB0LvQtdC0 93129 -IENsaW50b25z 93130 -7IWY 93131 -5aSn5bCP 93132 -RUNI 93133 -IGFuYXJjaGlzdHM= 93134 -IEJldmVyYWdl 93135 -IGdvdQ== 93136 -IGJyaWJlcnk= 93137 -IHBpY2t1cHM= 93138 -IHViZXI= 93139 -IHN5bmVyZ3k= 93140 -ZmNu 93141 -IEhlbnRhaQ== 93142 -IEJhc2VtZW50 93143 -IG1vcmI= 93144 -X2N1 93145 -amFkaQ== 93146 -KHByb2o= 93147 -IEJpbmdv 93148 -X2NhdGU= 93149 -W2VtYWls 93150 -Klg= 93151 -X1NFUA== 93152 -IHByaW5jaXBpbw== 93153 -dXBkYXRpbmc= 93154 -Ly99fQ== 93155 -Li4uKA== 93156 -IERPRQ== 93157 -IHpn 93158 -c2hhcGVz 93159 -PXRtcA== 93160 -Q3J1ZA== 93161 -IHdvcmtwbGFjZXM= 93162 -IHN0YWJpbGl6ZWQ= 93163 -IHRlbnRhbmc= 93164 -LnByb2R1Y3RJZA== 93165 -IFRyaWRlbnQ= 93166 -IG9yY2hlc3RyYXRlZA== 93167 -IEJ1Y2NhbmVlcnM= 93168 -X3RvbGVyYW5jZQ== 93169 -aWdyYXBoeQ== 93170 -w7xsZXI= 93171 -INi1 93172 -QVE= 93173 -IGF0aGxldGljaXNt 93174 -CVNlcnZlcg== 93175 -ZXdlZA== 93176 -RGlkRW50ZXI= 93177 -UmVnaXN0ZXJz 93178 -X2VtbHJ0 93179 -IGZ1bmN0aW9uYWxpdGllcw== 93180 -KGhkYw== 93181 -X21hcmtlcnM= 93182 -T3JlZ29u 93183 -KFN0cg== 93184 -IEdldEJ5SWQ= 93185 -IHp3YXJ0ZQ== 93186 -IE9DSQ== 93187 -IEphbWU= 93188 -X2NyaXQ= 93189 -IHN0b2NraG9sbQ== 93190 -CURpY3Rpb25hcnk= 93191 -X2NhcGFiaWxpdGllcw== 93192 -Q1RS 93193 -IG51bWE= 93194 -X2ZpcnN0bmFtZQ== 93195 -IE5TUmFuZ2U= 93196 -IG1vc3RyYQ== 93197 -IEFycml2YWw= 93198 -KElTZXJ2aWNlQ29sbGVjdGlvbg== 93199 -IHRlYXNwb29ucw== 93200 -IFNldFVw 93201 -CQkNCg0K 93202 -KGd1aWxk 93203 -LiJd 93204 -IG3hu5tp 93205 -YmZm 93206 -REFURVM= 93207 -KCldCgo= 93208 -IGh1bWFub2lk 93209 -dGhybw== 93210 -KGtsYXNz 93211 -IFZhZA== 93212 -ZnNw 93213 -LVNhaA== 93214 -IFVTRVJOQU1F 93215 -IFByb3BlcnR5Q2hhbmdlZEV2ZW50QXJncw== 93216 -IGxlc2lvbg== 93217 -X0RFTklFRA== 93218 -IFRISU5L 93219 -gqQ= 93220 -bWVudGFs 93221 -IHByZWNhcmlvdXM= 93222 -IE5vc2U= 93223 -IGNvbmNs 93224 -IHdpbGRmaXJl 93225 -IFRCcmFuY2g= 93226 -IEJBTQ== 93227 -L2Nzdg== 93228 -IE5BTg== 93229 -IENsZWFyYW5jZQ== 93230 -XEJsb2Nr 93231 -LmFubm90YXRl 93232 -5om+ 93233 -IFdISUxF 93234 -Z2VidW5n 93235 -Pkxpc3Q= 93236 -c2ht 93237 -Um9zcw== 93238 -YWZk 93239 -W3RpZA== 93240 -UGVyUGl4ZWw= 93241 -Kyhc 93242 -IEN5YW4= 93243 -IEtub3Q= 93244 -X3Zsb2c= 93245 -L3Zhcg== 93246 -W19f 93247 -IGhhc2htYXA= 93248 -KCk7DQ0K 93249 -IGFtYXNzZWQ= 93250 -IGRhdGVQaWNrZXI= 93251 -IFNhdG9zaGk= 93252 -X0NBUEFDSVRZ 93253 -IGJ1eg== 93254 -IE1pbmg= 93255 -U2V0Q29sb3I= 93256 -Kz0nPA== 93257 -IEludmVudA== 93258 -b3JjYQ== 93259 -aWdudW0= 93260 -IEFtcGg= 93261 -IHJlZmx1eA== 93262 -CiAgICAgICAgICAgICAgICAgICAgICAgIAo= 93263 -dWhu 93264 -KFRN 93265 -YWxsZXk= 93266 -IGxlZnRvdmVycw== 93267 -ZmRj 93268 -4oCcVGhlc2U= 93269 -IGNyYXdsZWQ= 93270 -KFZvaWQ= 93271 -aWd0ZQ== 93272 -8J+S 93273 -c2V0RGVmYXVsdA== 93274 -IEJlZ2lubmVy 93275 -UG9r 93276 -IEhMUw== 93277 -IGdhbWVJZA== 93278 -IEFtYmllbnQ= 93279 -X1BSRUQ= 93280 -LiJ9LAo= 93281 -w7xocnVuZw== 93282 -LlN5bmM= 93283 -IGludmU= 93284 -IE51cnNlcnk= 93285 -IGdsYXplZA== 93286 -q+yekA== 93287 -X2ZhdGFs 93288 -X2Rpc3BhdGNoZXI= 93289 -W10pDQo= 93290 -IGRldXRzY2hlbg== 93291 -6rGw 93292 -U2hhcGVz 93293 -IGlycmV2ZXJzaWJsZQ== 93294 -X3Blcw== 93295 -X2VzYw== 93296 -IHRoZXJtb21ldGVy 93297 -44OU44O8 93298 -X3NxcnQ= 93299 -Il09PSI= 93300 -IGN1bG1pbmF0aW9u 93301 -V29yZFByZXNz 93302 -IGxldmVu 93303 -VmVydGV4VXZz 93304 -IEhheXdhcmQ= 93305 -IEFzc2V0SW1hZ2U= 93306 -IG1haXpl 93307 -IGNoaWNhZ28= 93308 -IHRhdg== 93309 -ZXhwZW5zZXM= 93310 -0K0= 93311 -K2Y= 93312 -LiInIjsK 93313 -LVNB 93314 -IEtvdGE= 93315 -TWFpbkZyYW1l 93316 -LnNhbGU= 93317 -X0JV 93318 -IHN0cmVu 93319 -X2ZpbHQ= 93320 -L3ByaW50 93321 -KFBhY2tldA== 93322 -INC30LDQsg== 93323 -QWN0cw== 93324 -0LXQu9C10YQ= 93325 -IHJlbWF0Y2g= 93326 -IHJpZGRlbg== 93327 -IH0pKCk7Cg== 93328 -IGVuZG90aA== 93329 -IGNlcnRpZnk= 93330 -IFVJUGlja2VyVmlldw== 93331 -XE5vdGlmaWNhdGlvbnM= 93332 -CVRpdGxl 93333 -IGluZXF1YWxpdGllcw== 93334 -IE1vcmFu 93335 -IERhZW1vbg== 93336 -bGVzaWE= 93337 -IGhvcHBpbmc= 93338 -IGd1c3Rv 93339 -IEZpcmViYXNlRmlyZXN0b3Jl 93340 -IHBvbHlsaW5l 93341 -IHNwaWtlZA== 93342 -JSIpOwo= 93343 -IExBVElO 93344 -TGFiZWxUZXh0 93345 -IHN0cmFwb24= 93346 -X2ZpZA== 93347 -LXNwZWNpYWw= 93348 -YXJnZWQ= 93349 -IFNUSUxM 93350 -UXVhbGlmaWVkTmFtZQ== 93351 -LlJFUw== 93352 -I2M= 93353 -LndyaXRlbG4= 93354 -IEltbXV0YWJsZUxpc3Q= 93355 -IFRodW1i 93356 -IHNpbWQ= 93357 -RGVzY3JpY2Fv 93358 -LlNldFRleHQ= 93359 -IG5vbnByb2ZpdHM= 93360 -V2l0aGRyYXc= 93361 -LWVuY29kZWQ= 93362 -c2Jpbg== 93363 -IGFtb3J0 93364 -CWRk 93365 -cmlm 93366 -IHBhdGVybmFs 93367 -Lk1hcEZyb20= 93368 -X2Fzaw== 93369 -IHJlY291cnNl 93370 -IGJhY2tzdG9yeQ== 93371 -CW1hbmFnZXI= 93372 -X0RHUkFN 93373 -IEJpaGFy 93374 -aW50ZWxsaWdlbmNl 93375 -IHNraW1hZ2U= 93376 -KGVuY29kZXI= 93377 -IHN3aXJsaW5n 93378 -IEFwcGV0 93379 -X3NhbHQ= 93380 -IGF0dGU= 93381 -IFNRVUFSRQ== 93382 -IE5ldHo= 93383 -X3BhaW50 93384 -YXPEsQ== 93385 -aXNjaQ== 93386 -Rmxv 93387 -LWdvYWw= 93388 -LnNldFN0cm9rZQ== 93389 -IEF1c2Nod2l0eg== 93390 -IEFiZGVs 93391 -IGFuZXc= 93392 -IOWung== 93393 -IHRvdGFsUGFnZXM= 93394 -IHJlZmFjdG9y 93395 -IGNyZWF0aXZlbHk= 93396 -ZW1heA== 93397 -b2RveHk= 93398 -X3R4bg== 93399 -LlNvY2tldHM= 93400 -IFJpZGxleQ== 93401 -4buxYw== 93402 -c2FtcA== 93403 -TWluTWF4 93404 -IHdvcnNlbmluZw== 93405 -b3VudGFpbnM= 93406 -YXJ0bmVy 93407 -LXByb2Y= 93408 -c2luZ3VsYXI= 93409 -PWlz 93410 -IEZFQw== 93411 -X0ZN 93412 -IOaIlg== 93413 -IENhdWdodA== 93414 -X1NDTA== 93415 -IGV4cG8= 93416 -aW5mcmE= 93417 -IE1FUw== 93418 -Y2hhcA== 93419 -YWx0ZQ== 93420 -YXJraW4= 93421 -L21M 93422 -IHNlbmREYXRh 93423 -IGZyYW7Dp2Fpc2U= 93424 -IHPDpg== 93425 -X0RFRklOSVRJT04= 93426 -KioqKioqCgo= 93427 -XEN1c3RvbWVy 93428 -IOKWiOKWiOKWiOKWiOKWiA== 93429 -IHBlcnBldHJhdGVk 93430 -IEZ1cmlvdXM= 93431 -IHRlbmdh 93432 -bGVhcmVk 93433 -VUxMRVQ= 93434 -aW5pYw== 93435 -ZWFyY2hCYXI= 93436 -PENhcg== 93437 -IFJlbmV3YWJsZQ== 93438 -IGNvbnRlbXBsYXRlZA== 93439 -L2Zvcm1hdA== 93440 -IGZvcmdpdmluZw== 93441 -LlN1YkVsZW1lbnQ= 93442 -UFVURQ== 93443 -LmNvbnRlbnRTaXpl 93444 -IHJlc3BlY3RmdWxseQ== 93445 -4oCcCgo= 93446 -IHBvaWduYW50 93447 -dXJpbGU= 93448 -fSkiCg== 93449 -c2VxdWVudGlhbA== 93450 -L2Zhc3Q= 93451 -cHJ1bmc= 93452 -IFN0dW5uaW5n 93453 -IEJZVQ== 93454 -IGNvbXBhcmVy 93455 -CXJk 93456 -dW5pY29ybg== 93457 -xrBh 93458 -LkdldEl0ZW0= 93459 -IHNlY3Rpb25hbA== 93460 -anVkZ2U= 93461 -dXh0YXA= 93462 -IHN1bmRheQ== 93463 -IHDDpA== 93464 -TWlubmVzb3Rh 93465 -Ik4= 93466 -IGFwcGxpY2F0aW9uV2lsbA== 93467 -QU5HRVI= 93468 -IHJlYXNvbmVk 93469 -IFpFTkQ= 93470 -emFw 93471 -PWJhY2s= 93472 -b3NwaGF0ZQ== 93473 -6IqC54K5 93474 -IHRpdHRlbg== 93475 -IEFzc29j 93476 -QWN0aXZpdHlDcmVhdGVk 93477 -KVst 93478 -PyIKCgoK 93479 -IGpvdA== 93480 -2Lg= 93481 -IHVuY29tcHJlc3NlZA== 93482 -LklzREJOdWxs 93483 -IHZhc2U= 93484 -IGxvcmVt 93485 -IGVudHJlcHJpc2U= 93486 -IENvbnNlbnQ= 93487 -44Op44Oz 93488 -QnlWZXJzaW9u 93489 -IHF1aWVuZXM= 93490 -CWNvbnQ= 93491 -IEJsYWNraGF3a3M= 93492 -IEJsYXNpbw== 93493 -IHRhbmtlcg== 93494 -IHN0YXJ0dGltZQ== 93495 -IFNlYXM= 93496 -cGlvcw== 93497 -LlNwbGl0Q29udGFpbmVy 93498 -Y29tcGV0aXRpdmU= 93499 -IHBCdWZmZXI= 93500 -IGNvbnNlbnRpbmc= 93501 -LmFkZE9ic2VydmVy 93502 -aXRjaGVk 93503 -IG1pc2NlbGxhbmVvdXM= 93504 -IFRvcHM= 93505 -CWxw 93506 -Y21kcw== 93507 -LmRlcGFydA== 93508 -IGZOYW1l 93509 -CWJlc3Q= 93510 -OlA= 93511 -IHN3YXRo 93512 -IHZva3M= 93513 -YWxsb24= 93514 -IEh0bWxXZWJwYWNrUGx1Z2lu 93515 -LmxvZ2dlZElu 93516 -YnVja2V0cw== 93517 -IGhvbW9waG9iaWM= 93518 -IHN1YmR1ZWQ= 93519 -IG1lc3NhZ2Vib3g= 93520 -V2hhdHNBcHA= 93521 -IGRpc3NpcA== 93522 -IE1BTlVBTA== 93523 -TElLRUxZ 93524 -dGVzdGRhdGE= 93525 -LU9jdA== 93526 -RXhpdGVk 93527 -IFRhc21hbmlh 93528 -bGFj 93529 -IHRow7RuZw== 93530 -U3Rvcmllcw== 93531 -IGJpb2NoZW1pY2Fs 93532 -b3JyZQ== 93533 -IGVjbGlwcw== 93534 -IEFzc2VtYmx5UHJvZHVjdA== 93535 -cnRsZQ== 93536 -IFdpbGhlbG0= 93537 -cGl6emE= 93538 -X0RI 93539 -Y29uag== 93540 -IHB1ZWJsbw== 93541 -IGxpcXVl 93542 -IGN1cGlk 93543 -IEFjdGl2aXR5Q29tcGF0 93544 -LlNt 93545 -Il19 93546 -bWFpbGJveA== 93547 -Lm9wdFN0cmluZw== 93548 -LW9i 93549 -IE1hdWk= 93550 -YXRhaXJlcw== 93551 -IG1lcnJ5 93552 -Um5k 93553 -IGNhcmFjdGVyw61zdGljYXM= 93554 -VHJv 93555 -KGNu 93556 -Lmxk 93557 -LXBvaW50cw== 93558 -LnNi 93559 -IHZlag== 93560 -IGNhcmVnaXZlcg== 93561 -IG5hdQ== 93562 -RElSRUNUT1JZ 93563 -KGFuZw== 93564 -KC4p 93565 -IGV4cGxhbmF0b3J5 93566 -ZWxzZXk= 93567 -IE92ZXJuaWdodA== 93568 -IGxhaXNzZQ== 93569 -IFJBVEU= 93570 -IEdvdw== 93571 -UmVjb2duaXRpb25FeGNlcHRpb24= 93572 -aWNoZXJ0 93573 -IHJldm9sdXRpb25z 93574 -JGNhdGVnb3J5 93575 -IHVuZGVmZWF0ZWQ= 93576 -L2NvbW11bml0eQ== 93577 -LXBhcnRz 93578 -LWFwcGxpY2F0aW9u 93579 -K0E= 93580 -L3N3ZWV0YWxlcnQ= 93581 -IEtt 93582 -aWxhdGVk 93583 -YXRhdA== 93584 -UEFU 93585 -xI1l 93586 -IFRlYw== 93587 -Lm9uQWN0aXZpdHlSZXN1bHQ= 93588 -XFdlYg== 93589 -IEx1Zw== 93590 -b3ZvbHRh 93591 -IGFsdHJ1 93592 -aWd5 93593 -IGLEmWTEhQ== 93594 -IGFjdGl2YXRpb25z 93595 -IGF1ZGl0aW5n 93596 -RVJHRQ== 93597 -IOiLpQ== 93598 -Q2FybG9z 93599 -IGtJbnN0cnVjdGlvbg== 93600 -bWluZXI= 93601 -IH19Lw== 93602 -QW5kSGFzaENvZGU= 93603 -IEJvdXJib24= 93604 -LnByb2Y= 93605 -IGltcHJpbWly 93606 -IEZlcmRpbmFuZA== 93607 -0LzQtdC90YI= 93608 -L3t9Lw== 93609 -IENsYWly 93610 -IE9uQ29sbGlzaW9u 93611 -c2FsZG8= 93612 -cmFpc2Vk 93613 -IEFCT1ZF 93614 -KCk9Pg== 93615 -IGRldXRzY2hsYW5k 93616 -aGliaXRlZA== 93617 -RXh0cmVtZQ== 93618 -L2hvb2tz 93619 -IGRvdXQ= 93620 -IFZPQw== 93621 -ZXRob3Zlbg== 93622 -UE1D 93623 -IHJlc3RhcnRpbmc= 93624 -IFNDTg== 93625 -IEVP 93626 -IERKcw== 93627 -UGFzc3dvcmRGaWVsZA== 93628 -LkFjY2Vzc2libGU= 93629 -CWJ1cw== 93630 -U1RSVUNUSU9OUw== 93631 -IGxhdGVu 93632 -IFNOQVA= 93633 -X0hFUlNIRVk= 93634 -IG9uc3RhZ2U= 93635 -5bCP5pe2 93636 -IHNhaWxvcg== 93637 -IEN1cnNv 93638 -IGltcHJvdmlzZWQ= 93639 -IGdlbmVyYWxpemU= 93640 -IGJ1ZW5v 93641 -IGNlcmVtb25pYWw= 93642 -IENOUw== 93643 -IHBpZ2Vvbg== 93644 -bXNw 93645 -L0FJRFM= 93646 -bGluZUVkaXQ= 93647 -IEZpbmFuY2luZw== 93648 -IGpUYWJsZQ== 93649 -IGJvdHRvbXM= 93650 -IFRleHRJbnB1dFR5cGU= 93651 -IG1laXNqZQ== 93652 -LXNpZ25lZA== 93653 -IEdyZWVudmlsbGU= 93654 -b3BoaWxpYQ== 93655 -SWNvbk1vZHVsZQ== 93656 -IGNsYW5kZXN0 93657 -ZW1haW4= 93658 -U0NBTg== 93659 -X1RJTUVT 93660 -IGxlY2tlbg== 93661 -KGNhbmNlbA== 93662 -IGVjc3Rhc3k= 93663 -Lk1VTFQ= 93664 -IG1vZXRlbg== 93665 -IGFwcHJvcHJpYXRpb25z 93666 -IFFMRA== 93667 -IEd1aWw= 93668 -IHRyYXBwaW5n 93669 -eERB 93670 -IGvDtmxu 93671 -ZW51bXM= 93672 -4oCcVG8= 93673 -cG9ydG8= 93674 -bmluZ2Fy 93675 -IFRPTw== 93676 -LVNU 93677 -IE1hdGhz 93678 -IGt1cnM= 93679 -IFJFUEw= 93680 -X2NvbnRyaWI= 93681 -IFBoeQ== 93682 -cmFuZw== 93683 -Lm1hdmVu 93684 -LWZvbGxvdw== 93685 -IC0tLS0tLS0tLS0t 93686 -xLHEnw== 93687 -X3dpbm5lcg== 93688 -LkNyaXRlcmlh 93689 -KGRhdGFTb3VyY2U= 93690 -IHNldElucHV0 93691 -IFRJTUVTVEFNUA== 93692 -b3BlcmFuZHM= 93693 -Z2V0V2luZG93 93694 -LmZhY2VWZXJ0ZXhVdnM= 93695 -IEludmVzdGluZw== 93696 -Vnk= 93697 -IHBlcnNlY3V0ZWQ= 93698 -4bq/dQ== 93699 -IFBsdW1iaW5n 93700 -T05HT0RC 93701 -RXZpZGVuY2U= 93702 -IFN0cm9t 93703 -cXVvdGE= 93704 -TGl2ZXJwb29s 93705 -CWF0dGFjaw== 93706 -bWluaW1hbA== 93707 -IG9uS2V5RG93bg== 93708 -IG1vZHVsZUlk 93709 -IFZlcmFuc3Q= 93710 -bW9ydA== 93711 -YWNpc3Rz 93712 -IE1BU1M= 93713 -X1VOREVS 93714 -LmdldFJ1bnRpbWU= 93715 -RU5USUNBVElPTg== 93716 -Uk9LRQ== 93717 -IHNjYWxlWA== 93718 -IHNlcnRh 93719 -IEZyZXF1ZW50bHk= 93720 -X1RSQU5TRk9STQ== 93721 -IHR3aWxpZ2h0 93722 -IE1jS2Vuemll 93723 -bGVkZ2Vk 93724 -IEB7QCI= 93725 -X0FDVElW 93726 -IGhvb2tlcnM= 93727 -PWRlZmF1bHQ= 93728 -IHdhbG51dA== 93729 -IHVzZU5ld1VybFBhcnNlcg== 93730 -IENoZWVy 93731 -IHdyb25nZnVs 93732 -bmlv 93733 -YnRj 93734 -LnN0cmlkZQ== 93735 -IHN1Y2Nlc2Z1bGx5 93736 -IFRyb2xs 93737 -aWZpY2lv 93738 -LmNvbmQ= 93739 -IGhlYXBz 93740 -X1BIT1RP 93741 -PEFkZHJlc3M= 93742 -IFN0aWNreQ== 93743 -IG5pZ2h0dGltZQ== 93744 -IGRhbmRv 93745 -IEJJTEw= 93746 -INC+0YLQstC10YI= 93747 -RGV0ZXJtaW4= 93748 -IGZ6 93749 -KHNpZ25hdHVyZQ== 93750 -IHZpbmRlbg== 93751 -LkNPTk5FQ1Q= 93752 -cnVpc2U= 93753 -IHh1 93754 -cHJldmVudA== 93755 -Rk9Y 93756 -VUlBcHBsaWNhdGlvbkRlbGVnYXRl 93757 -U3BsYXNo 93758 -IGVtYnJvaWRlcmVk 93759 -IEhpbGZl 93760 -LnNoYWRlcg== 93761 -IGRvdWJ0ZWQ= 93762 -UmVzcG9uc2VTdGF0dXM= 93763 -IHVuc3RvcHBhYmxl 93764 -dW5sb2Fk 93765 -KyJd 93766 -ImxhYmVs 93767 -IGZyZWVsYW5jZXI= 93768 -RGlyZWN0ZWQ= 93769 -IHZvcmhhbmQ= 93770 -IFNubw== 93771 -ZXhpc3RlbmNl 93772 -b3JkaWFs 93773 -emFn 93774 -LkFnZQ== 93775 -IHNwYXducw== 93776 -IFBTRw== 93777 -c3RpdHV0aW9ucw== 93778 -IHNpZ2h0aW5n 93779 -LXRhbGs= 93780 -INGB0L7RhdGA0LDQvQ== 93781 -ZW5lcmltYQ== 93782 -IEJlbnRvbg== 93783 -X1N0b3Jl 93784 -VHJhbnNwYXJlbnRDb2xvcg== 93785 -IEV4cGxvc2lvbg== 93786 -X0lTUw== 93787 -Q2hlY2twb2ludA== 93788 -IGRlZmxhdGU= 93789 -0JLRi9Cx 93790 -LXRyYW5zZmVy 93791 -IEJhYmllcw== 93792 -IGltYQ== 93793 -LnVzYWdl 93794 -IG5lZ2F0aXZpdHk= 93795 -IEV4dHJlbWVseQ== 93796 -a2o= 93797 -RG93bmxvYWRlcg== 93798 -CWFjdA== 93799 -W2NoYXI= 93800 -Tm9ybWFscw== 93801 -X3JlZmVyZW5jZXM= 93802 -IGRyYWNvbg== 93803 -4bulYw== 93804 -X1RSTlM= 93805 -Y29tcGFueUlk 93806 -IFZlcmQ= 93807 -YW5pbw== 93808 -IE1hdGNoZXJz 93809 -KHJlbGF0aXZl 93810 -IHJlZWxlY3Rpb24= 93811 -LkhF 93812 -VGF1 93813 -INGB0YLRgNC+0LrQuA== 93814 -IE1ldGFscw== 93815 -IENvY2t0YWls 93816 -IGFwcmVuZGVy 93817 -X3ByZWZlcmVuY2U= 93818 -LlNjaGVtZQ== 93819 -IGdsR2V0VW5pZm9ybUxvY2F0aW9u 93820 -VXNpbmdFbmNvZGluZw== 93821 -0YDQsw== 93822 -ICJdIik7Cg== 93823 -TGVhZGVycw== 93824 -J8OqdHJl 93825 -X0RlbGF5 93826 -UHJvY2Vzc2Vz 93827 -aWN1bHR1cmU= 93828 -XCI6e1wi 93829 -4oCUIg== 93830 -RW1vamk= 93831 -LWdyb3c= 93832 -IENDRA== 93833 -Y29tcG9zZWQ= 93834 -TWFpbnRlbmFuY2U= 93835 -IFJ5emVu 93836 -KGFn 93837 -LnByb2I= 93838 -IFNpbmF0cmE= 93839 -IGhvcnJlbmQ= 93840 -IE1vdW50ZWQ= 93841 -X1BFRVI= 93842 -IGN1aw== 93843 -IHPDuGtlcg== 93844 -IFF1YXI= 93845 -X1JFU09MVVRJT04= 93846 -J2VhdQ== 93847 -IGJvdXJib24= 93848 -IGF0SW5kZXg= 93849 -L3BvbA== 93850 -IOq0gA== 93851 -CXB3 93852 -fSl9Cg== 93853 -LmZvcm1EYXRh 93854 -IHVkZW4= 93855 -IHJvYXJpbmc= 93856 -Tm90aWZpY2F0aW9uQ2VudGVy 93857 -IGNsdXN0ZXJlZA== 93858 -IHBhaXJ3aXNl 93859 -bXVsdGlsaW5l 93860 -R2FtZURhdGE= 93861 -Lkxhcmdl 93862 -KSc6 93863 -INGB0LXRgNCy0LXRgA== 93864 -IFVJTWFuYWdlcg== 93865 -U3Zj 93866 -IFBsYXlzdGF0aW9u 93867 -Lk1vcmU= 93868 -LnF1YWxpdHk= 93869 -IGNvbmZpZ0ZpbGU= 93870 -LWNvbnRhaW5pbmc= 93871 -IEdvYXQ= 93872 -ZW5jaW9u 93873 -IGxpa2VuZXNz 93874 -LXVzaW5n 93875 -IHNlYXNpZGU= 93876 -4bqpdQ== 93877 -YW50aWNpcGF0ZWQ= 93878 -Rm9sZGVycw== 93879 -LUxldmVs 93880 -b3BjaW9u 93881 -KXByZXBhcmVGb3JTZWd1ZQ== 93882 -PigpKQ== 93883 -PWFkZA== 93884 -XGdyaWQ= 93885 -IHln 93886 -X0RSSVZF 93887 -IEdldE5hbWU= 93888 -LkRBTw== 93889 -IGhhbm4= 93890 -CWNhdA== 93891 -IHZpZ24= 93892 -IEhlbGxlcg== 93893 -IENSRUFURUQ= 93894 -YmVyb3M= 93895 -YnV0dA== 93896 -IGJlbmRz 93897 -IExlZXI= 93898 -0KY= 93899 -IFNNUA== 93900 -VmVjdA== 93901 -IG9iamVjdFR5cGU= 93902 -OmFzeW5j 93903 -IGNvbXBldGVuY3k= 93904 -IFF0QXdz 93905 -TG91 93906 -L2NhdA== 93907 -UHJvc3RpdA== 93908 -LXZlcw== 93909 -CXR2 93910 -IEVJ 93911 -QW5kV2FpdA== 93912 -IFRPT0w= 93913 -fSo= 93914 -X1Jlcw== 93915 -IGFsaWdubWVudHM= 93916 -7KGw 93917 -IENsYW1w 93918 -LXBhZA== 93919 -IHdyaXRlRmlsZQ== 93920 -IEFwcHJlYw== 93921 -4oCZYXV0cmVz 93922 -dWRhZGVz 93923 -IGx1Z2FyZXM= 93924 -c3BlbmRlcg== 93925 -W2ltYWdl 93926 -RVhJU1Q= 93927 -IGRlY2VpdmU= 93928 -IGh1bnRz 93929 -X1ZPSUNF 93930 -X0RY 93931 -Q0FD 93932 -ICgoJw== 93933 -aXNrcw== 93934 -LGZpbGVuYW1l 93935 -IGxlYW5z 93936 -SW5wdXREaWFsb2c= 93937 -RGF0YUNvbnRyYWN0 93938 -IHNtb290aGVk 93939 -IHJlY3J1aXRlcnM= 93940 -IHRhbmdsZWQ= 93941 -X1RhYg== 93942 -IEZpbGVBY2Nlc3M= 93943 -WUM= 93944 -IHZY 93945 -PGR5bg== 93946 -TGV4ZXI= 93947 -IOKYhg== 93948 -IGdsR2Vu 93949 -VGVtcG9yYWw= 93950 -IEFURg== 93951 -YW5rbw== 93952 -VXNlckNvZGU= 93953 -IEtvdGxpbg== 93954 -Li4KCgoK 93955 -RU5DRUQ= 93956 -LnVudHJhY2tlZA== 93957 -X21y 93958 -IHdhdmVsZW5ndGhz 93959 -IGRpY2hv 93960 -IGltdQ== 93961 -X2NyZQ== 93962 -W0o= 93963 -X0RG 93964 -IGF0dGFpbm1lbnQ= 93965 -IGxpdGVycw== 93966 -W2tleXM= 93967 -IGxpc3Rhcg== 93968 -SHR0cHM= 93969 -IGJyZXdlcnM= 93970 -IGFjb21wYcOx 93971 -IHRvYXN0ZWQ= 93972 -LmZyaWVuZA== 93973 -IHJlbHU= 93974 -IFBzeWNoaWM= 93975 -TWFuaXA= 93976 -ZG5h 93977 -UHJp 93978 -LWZsYXNo 93979 -KGFydGlzdA== 93980 -IEtvdg== 93981 -cHJlc2VydmU= 93982 -X3BlbWI= 93983 -LnNldFByb2dyZXNz 93984 -IGR1c2s= 93985 -IGNhbm5hYmlub2lkcw== 93986 -IEt1bmQ= 93987 -IENvdW50aWVz 93988 -IO2OmOydtOyngA== 93989 -IHJlbmFtaW5n 93990 -IFJ1c3Nv 93991 -TlNTZXQ= 93992 -KEVYUFI= 93993 -5YW25LuW 93994 -RGlhZ3JhbQ== 93995 -LGxhc3Q= 93996 -KHdpdGhEdXJhdGlvbg== 93997 -IGluZGVidGVk 93998 -IERpY2tlbnM= 93999 -IEFscHM= 94000 -IERlZ3JlZXM= 94001 -aWRhcg== 94002 -LWJsb29k 94003 -K29mZnNldA== 94004 -IEh1ZA== 94005 -b3VuZGVy 94006 -dWxuZXJhYmxl 94007 -IHByaW8= 94008 -YmxpbmQ= 94009 -KHBhY2s= 94010 -IG5pZ2h0bGlmZQ== 94011 -IGlsbHVzdHJhdGluZw== 94012 -IG51dHNoZWxs 94013 -IGJyb2FkY2FzdGVycw== 94014 -IGNvbXBhbnlOYW1l 94015 -aXRvcmU= 94016 -LnJpZ2h0QmFyQnV0dG9uSXRlbQ== 94017 -Ym90ZQ== 94018 -IFBJVA== 94019 -LXNjcm9sbGJhcg== 94020 -IHdpbmR5 94021 -IFFNYWluV2luZG93 94022 -aHVl 94023 -LmVwb2No 94024 -IGNhbWVy 94025 -IENMVUI= 94026 -aWZhcg== 94027 -VW5hdmFpbGFibGU= 94028 -LXF1b3Rl 94029 -IEdyYXo= 94030 -IHZhbHU= 94031 -X01BVEVSSUFM 94032 -IHBlbnk= 94033 -IHRyYXR0 94034 -IGxpY2tlZA== 94035 -CWNhbg== 94036 -IFRhaXdhbmVzZQ== 94037 -UGFnZUluZGV4 94038 -LlRpcG8= 94039 -X1JlZA== 94040 -IHZmcw== 94041 -X3RyYW1wb2xpbmU= 94042 -IE1QUw== 94043 -IFBlYW51dA== 94044 -IExvY2tlZA== 94045 -CUFU 94046 -anNwYg== 94047 -X05PREVT 94048 -J1dl 94049 -IENvbnZlbmllbnQ= 94050 -X3N1Y2Nlc3NmdWw= 94051 -K3o= 94052 -WUxlYWY= 94053 -IHBlZGlncmVl 94054 -eHo= 94055 -IHNhbHZhcg== 94056 -X0Rlc2M= 94057 -IG5lc3Rh 94058 -IGhhcmRjb2RlZA== 94059 -LmdvbGQ= 94060 -LkltYWdlRmllbGQ= 94061 -X0JT 94062 -TEs= 94063 -Q2hvY29sYXRl 94064 -LlN0YXJ0dXA= 94065 -IGFuZWNkb3Rlcw== 94066 -Lk1h 94067 -P10= 94068 -L3RvcGlj 94069 -LlNjcm9sbEJhcnM= 94070 -0YHRgtCy0LA= 94071 -IE1PTQ== 94072 -IHFvcw== 94073 -YXJ5YW5h 94074 -w6RjaHN0 94075 -IE1jR2lsbA== 94076 -IEVEVUM= 94077 -KHBvc3Rz 94078 -IEVudHdpY2tsdW5n 94079 -X3NraWxscw== 94080 -LWd1YXJk 94081 -IHRleHRpbGVz 94082 -fHVuaXF1ZQ== 94083 -IEFyaXRobWV0aWM= 94084 -TG9hZElkZW50aXR5 94085 -KTt9Cgo= 94086 -IGFzc3VyZXM= 94087 -V2lsZGNhcmQ= 94088 -IGRlZmF1bHRlZA== 94089 -IE5vdFN1cHBvcnRlZEV4Y2VwdGlvbg== 94090 -IFRvbWF0bw== 94091 -LlN1bW1hcnk= 94092 -ISIu 94093 -dXRoZXJmb3Jk 94094 -IGxvb3Bob2xl 94095 -IGNtYWtl 94096 -LWRhdA== 94097 -IHJhZ2F6em8= 94098 -IGNhcGl0YWxz 94099 -IEltcG9ydGFuY2U= 94100 -IER1bmdlb25z 94101 -X3pvbmVz 94102 -LnNhdA== 94103 -ICAgICAgCiAgICAgIAo= 94104 -Y2F0ZWdvcmlhcw== 94105 -IGRhdGF0YWJsZQ== 94106 -IG5hamxl 94107 -KGdw 94108 -LXJlbg== 94109 -IHBhbmlja2Vk 94110 -IFNreWw= 94111 -IFFVSUNL 94112 -dmFsdWVPZg== 94113 -U3RhdGlzdGlj 94114 -IGRlbWVhbm9y 94115 -bmRlcm4= 94116 -IEFwcGVhcnM= 94117 -UHJhZ21h 94118 -X3Bhc3Q= 94119 -SGFzaHRhYmxl 94120 -IHRoYW5raW5n 94121 -LmNzcmY= 94122 -IHBhdmU= 94123 -IFZpY3RpbQ== 94124 -IFDDpQ== 94125 -Rmlyc3RuYW1l 94126 -Q0FURUdPUlk= 94127 -aWxlc3RvbmU= 94128 -JyktPl9fKCc= 94129 -IGluY2FwYWM= 94130 -U3RyZWFtV3JpdGVy 94131 -IGNvbW11bmlvbg== 94132 -X3N0ZGVycg== 94133 -6Ieq5rK7 94134 -IGh1bWFuaXRpZXM= 94135 -INC70Y4= 94136 -IFBhcmFz 94137 -bG9mZg== 94138 -SGVhZGVyVGV4dA== 94139 -Z3JlZ2F0ZWQ= 94140 -LlhSVGFibGVDZWxs 94141 -IGVudGl0eUlk 94142 -IE1hc3Rlcnk= 94143 -b2xkdA== 94144 -JykpKTsKCg== 94145 -aHVtaWRpdHk= 94146 -Li4uIik7Cgo= 94147 -RGVsdGFUaW1l 94148 -IG1rdGltZQ== 94149 -UGhvdG9u 94150 -IHBlbnNhcg== 94151 -c2NhbGluZw== 94152 -X3llbGxvdw== 94153 -X211bHRpcGx5 94154 -IFZ1bGNhbg== 94155 -IFBlYXJjZQ== 94156 -X2xj 94157 -LWV4Y2x1c2l2ZQ== 94158 -SXNVbmljb2Rl 94159 -IHBhZHI= 94160 -X1BDSUU= 94161 -IGdsaW1wcw== 94162 -IHJhbXBhZ2U= 94163 -IFBhZ2luYXRvcg== 94164 -IGNvbnZleWluZw== 94165 -bm9yZQ== 94166 -X2RldGFjaA== 94167 -J10hPSc= 94168 -IGJvbmE= 94169 -CUNvbg== 94170 -TmF6 94171 -IHNlZ3VpbnQ= 94172 -IG1pZXN6 94173 -IGVzb3M= 94174 -ICcvJykK 94175 -IGZhaXRoZnVsbHk= 94176 -IGJla29t 94177 -0LDQutGB 94178 -d2hlbG1pbmc= 94179 -LnR3bw== 94180 -IFNDRQ== 94181 -LW5h 94182 -ICgpew== 94183 -IERhbWVu 94184 -X3RndA== 94185 -YWRhbGFmaWw= 94186 -IE1NSQ== 94187 -VGhpbg== 94188 -IGRlcHJlY2lhdGlvbg== 94189 -IGFic2VudGVl 94190 -IHNhbGFyaW8= 94191 -IFNvbWVib2R5 94192 -IFNsb2Fu 94193 -IGVyZm9sZ3JlaWNo 94194 -Ok5TTG9jYWxpemVkU3RyaW5n 94195 -IGdlaMO2cnQ= 94196 -IGVtbw== 94197 -IExhZ3VuYQ== 94198 -w6FzYQ== 94199 -aXN0cmF0ZXM= 94200 -UmFpc2U= 94201 -IEFzdHJvcGg= 94202 -ICdcXCc= 94203 -X3BlZA== 94204 -IFRIUk9VR0g= 94205 -IE5pZXR6c2NoZQ== 94206 -ZW5lcmF0aW5n 94207 -b3BsYXllcg== 94208 -IHJvZGVudHM= 94209 -w7xobA== 94210 -R2FtZU1hbmFnZXI= 94211 -IEhlYWRlckNvbXBvbmVudA== 94212 -IG1pbGFu 94213 -cXVlZW4= 94214 -IFBPTEw= 94215 -IEx5bWU= 94216 -IEJyaWdncw== 94217 -ZWNlcg== 94218 -d2Fnb24= 94219 -LkRFU0M= 94220 -IGdsQmVnaW4= 94221 -U3RhdGVtZW50cw== 94222 -ZXRyaQ== 94223 -IG1vY2tlcg== 94224 -IEJsdWVwcmludFJlYWRPbmx5 94225 -L2NvbnRlbnRhc3Npc3Q= 94226 -ZW1hYWt0 94227 -L2xvYWRlcg== 94228 -X2xvd2VyY2FzZQ== 94229 -Y2l2aWw= 94230 -X3ZhbG9y 94231 -X0dsb2JhbA== 94232 -IGFkcg== 94233 -aXRpemVu 94234 -LlNpZGU= 94235 -IEVtYmxlbQ== 94236 -IHRoaXJkcw== 94237 -X1NIQVBF 94238 -UmVncmVzc29y 94239 -UFlUSE9O 94240 -IHBzeWNob3RpYw== 94241 -IGN2cw== 94242 -IEFwcGxpY2F0aW9uVXNlcg== 94243 -IGFsdW5vcw== 94244 -VG9nZ2xlQnV0dG9u 94245 -IG5nYQ== 94246 -IG3Do2U= 94247 -YWR2ZXJ0aXNlbWVudA== 94248 -5YiG5Lqr 94249 -Lm92 94250 -IEFPTA== 94251 -UkVX 94252 -INin2LPYqg== 94253 -IEdpbm55 94254 -IC8vLy8vLy8vLy8= 94255 -U29uZ3M= 94256 -YWNpYw== 94257 -Q01Q 94258 -IHJlY29nbml6ZXI= 94259 -IHDDq3I= 94260 -RElD 94261 -O1wiPg== 94262 -IGNsb3Q= 94263 -OkV2ZW50 94264 -LlRP 94265 -IEN1cnNvcnM= 94266 -XFN0b3JhZ2U= 94267 -IElvbmljUGFnZQ== 94268 -X2pldA== 94269 -KEJpdENvbnZlcnRlcg== 94270 -IGNoaWxkaXNo 94271 -VHJhZGVy 94272 -PEhUTUxJbnB1dEVsZW1lbnQ= 94273 -X0ZSRVFVRU5DWQ== 94274 -PSI7Cg== 94275 -eXN0YWNr 94276 -SnVy 94277 -IOmU 94278 -IHRjYg== 94279 -IHJlY2liaXI= 94280 -LnN6 94281 -IO2BtOuemOyKpA== 94282 -UEVSU09O 94283 -bm92YQ== 94284 -IGNvZXI= 94285 -IE1haG1vdWQ= 94286 -IFdvcmtwbGFjZQ== 94287 -IiIiKSwK 94288 -LlBhZ2VTaXpl 94289 -Z2V0Um9vdA== 94290 -KGJhc2VVcmw= 94291 -W1U= 94292 -IE1DUw== 94293 -IENsYXJrc29u 94294 -LnZvbA== 94295 -ICIifQo= 94296 -IHBldXg= 94297 -IFByb2R1Y3RTZXJ2aWNl 94298 -IG1vbmRheQ== 94299 -IFRlc3REYXRh 94300 -IE1hdWw= 94301 -IHN0cm5jbXA= 94302 -IHNob3BwZXI= 94303 -dGhlb3J5 94304 -IGV0aXF1ZXR0ZQ== 94305 -bGljZW5jZQ== 94306 -c2NhbA== 94307 -LWNsdXN0ZXI= 94308 -IGhpc3TDs3JpYQ== 94309 -IFN1YnRyYWN0 94310 -IGZpYmVyZ2xhc3M= 94311 -X2xhc3RuYW1l 94312 -IFJld3JpdGU= 94313 -L3RvZG8= 94314 -IG92ZXJmbG93aW5n 94315 -IEdhdXNz 94316 -b2theQ== 94317 -IGNsdW1zeQ== 94318 -KHh5 94319 -IGV4ZW1w 94320 -YW5hbHl6ZQ== 94321 -LXRpY2tldA== 94322 -bmluZQ== 94323 -IERlYWRwb29s 94324 -IGNvbHVt 94325 -IEpL 94326 -IFtdLA0K 94327 -IEFzcGVu 94328 -IG1hbGlnbmFudA== 94329 -aMO1ZXM= 94330 -U2NhbGE= 94331 -aW5uZQ== 94332 -IENPTlNUQU5UUw== 94333 -X1ByaWNl 94334 -IyUl 94335 -IGFyc2No 94336 -IE5TQXR0cmlidXRlZFN0cmluZw== 94337 -IEZpbGVUeXBl 94338 -YWxsb2NhdGlvbg== 94339 -X3Npbmd1bGFy 94340 -KFBvaW50ZXI= 94341 -YW5uaWVz 94342 -U3RvcmVk 94343 -ICc7Cgo= 94344 -4oCZZXg= 94345 -ZHJz 94346 -QnJpZ2h0bmVzcw== 94347 -L09S 94348 -VGV4dGJveA== 94349 -IGtuYWNr 94350 -IGplbmlz 94351 -IG9jYXM= 94352 -ZGF0YXA= 94353 -IGdhbWVUaW1l 94354 -IOCw 94355 -bmR4 94356 -IEVWVA== 94357 -QnlUZXh0 94358 -IGF0dHJpYnV0ZU5hbWU= 94359 -IGp1Z2Fy 94360 -X3NlcXM= 94361 -IEZFQVRVUkVT 94362 -OmRhdGU= 94363 -ZmJl 94364 -cmlwcGVy 94365 -56iN 94366 -LkV4cHI= 94367 -VXJiYW4= 94368 -aWRvdA== 94369 -IG9ibGl2aW91cw== 94370 -KERiQ29udGV4dA== 94371 -Q2Fyb2w= 94372 -KCcsJywk 94373 -IEJyaWxsaWFudA== 94374 -a2Fk 94375 -Y2VudHJhdGlvbg== 94376 -IGt1aw== 94377 -IE1BTkFHRU1FTlQ= 94378 -X1dFQVBPTg== 94379 -IGppaGFkaXN0cw== 94380 -IGVudHJlZw== 94381 -IGRvxJ8= 94382 -IGFwcGVuZGluZw== 94383 -IFpp 94384 -X2N0eHQ= 94385 -IHF1YWRyYW50 94386 -ZWxlbWVudFR5cGU= 94387 -PWltZw== 94388 -YnJ1YXI= 94389 -SUNBU1Q= 94390 -IGludGVsbGVjdHVhbGx5 94391 -LkFubm90YXRpb24= 94392 -IGNhbXBhaWduZXJz 94393 -LkRhdGFHcmlkVmlld0F1dG9TaXpl 94394 -IMWfZWs= 94395 -IC9eKA== 94396 -LkRhdGFUYWJsZQ== 94397 -IHdlYmxvZw== 94398 -KGxpYnJhcnk= 94399 -IEZ1cw== 94400 -IE9TVA== 94401 -X1Bhc3N3b3Jk 94402 -IEJ1Y2tsZXk= 94403 -aG9mZg== 94404 -QWxpZ25lZA== 94405 -X1JlYWw= 94406 -RU5USUM= 94407 -L2dyYXBocWw= 94408 -IFdlZWQ= 94409 -IExTQg== 94410 -b2NjYXNpb24= 94411 -YWRkYWZp 94412 -TGV0cw== 94413 -KCJg 94414 -IHdpZGVu 94415 -KHZpc2l0b3I= 94416 -ICJcCg== 94417 -QU5URQ== 94418 -LWNhbXB1cw== 94419 -LUJhcg== 94420 -Y2FtZWw= 94421 -Rm10 94422 -OmRlc2NyaXB0aW9u 94423 -LmFyZQ== 94424 -IEFuYXN0 94425 -IExvbmdlcg== 94426 -c2VyaW91cw== 94427 -IGRhaGVy 94428 -aXp6ZXI= 94429 -TXVsdGlwbGljaXR5 94430 -IEhvbGxhbmRl 94431 -IEFubm90YXRpb25z 94432 -KCk/ 94433 -IHByb3Rlc3Rlcg== 94434 -IFVyZHU= 94435 -IHNwZWNpYWx0aWVz 94436 -X2x5 94437 -Q2Fk 94438 -YW5udA== 94439 -anNw 94440 -IGpvZQ== 94441 -KXI= 94442 -IFBlcnNpc3Q= 94443 -IG9ibA== 94444 -IGRlYWRsb2Nr 94445 -IHNlcmk= 94446 -UmVsYXRpdmVUbw== 94447 -IFl1cw== 94448 -KFByaW50 94449 -YWJpbGlh 94450 -IHVucHJvdGVjdGVk 94451 -IEFTSUM= 94452 -Lk5vbWU= 94453 -IFdlYkNsaWVudA== 94454 -IElUVg== 94455 -w7xybmJlcmc= 94456 -aXRvcmk= 94457 -U2lnbmluZw== 94458 -IFJlYWRvbmx5 94459 -IGVsZHJl 94460 -IENoZWNrZWQ= 94461 -YWxudW0= 94462 -U291cmNlVHlwZQ== 94463 -bGV4aWNhbA== 94464 -IGlsbHVzdHJhdG9y 94465 -IERpcmVjdG9yYXRl 94466 -IFRyb20= 94467 -bXBw 94468 -bG9nZw== 94469 -Lmluc3RydW1lbnQ= 94470 -IHdvb2RlZA== 94471 -IFVzZXJUeXBl 94472 -IFJlbmNvbnRyZXM= 94473 -bW9kZWxOYW1l 94474 -QlRUYWdDb21wb3VuZA== 94475 -PlRv 94476 -IGZyZWV6ZXM= 94477 -IENvbnRl 94478 -IENyZWRlbnRpYWw= 94479 -Y2FsYQ== 94480 -L3dvcmtzcGFjZQ== 94481 -IGxpYmlkbw== 94482 -Y2hsdXNz 94483 -b2xsZXlFcnJvcg== 94484 -IGFjY2lvbmVz 94485 -IEppbnBpbmc= 94486 -YXTDqWc= 94487 -SW50ZXJzdGl0aWFs 94488 -KSkpKSk7DQo= 94489 -eWJyaWQ= 94490 -IFJvbGxlZA== 94491 -TW9kZWxDcmVhdGluZw== 94492 -IFJlZmxleA== 94493 -IEx1Y2lmZXI= 94494 -IGVoZXI= 94495 -IGNhcm5pdmFs 94496 -ISI7DQo= 94497 -X0xPT0tVUA== 94498 -IHN1Y2PDqHM= 94499 -IHJlb3BlbmluZw== 94500 -IGNyZWFkbw== 94501 -IFNteQ== 94502 -IEVudHM= 94503 -LlNpbmNl 94504 -IEZpc2hlcmllcw== 94505 -L2Nvbm5lY3Rpb24= 94506 -IENTQQ== 94507 -INC/0YDQvtCz0YDQsNC80Lw= 94508 -bHNydWhl 94509 -CWFjdG9y 94510 -IFN0cmF1c3M= 94511 -SnNvblZhbHVl 94512 -CWV2YWw= 94513 -bG9ja2Vy 94514 -IFhJVg== 94515 -X2h5cGVy 94516 -IFBvbGx5 94517 -4oCmdGhl 94518 -IEdVUkw= 94519 -0LXRgdGB 94520 -IGRpdmVz 94521 -dWdlb3Q= 94522 -aW5lbWE= 94523 -YmVyc29tZQ== 94524 -Q29tcHJh 94525 -LWN1bHR1cmFs 94526 -IGdyYW5kcw== 94527 -U2Fj 94528 -IEJhcm5leQ== 94529 -X1FVRVNUSU9O 94530 -IG1hbWFu 94531 -IGhhc3RpbHk= 94532 -IGNsdWJob3VzZQ== 94533 -IGdydW5k 94534 -X1dBTEw= 94535 -IHB1cmlmaWNhdGlvbg== 94536 -hOS7tg== 94537 -0LLQsA== 94538 -dmVzdG1lbnQ= 94539 -LkRpc3BsYXlTdHlsZQ== 94540 -X2NvcmVz 94541 -JVM= 94542 -IG9zw7Ni 94543 -IGRpc2I= 94544 -IEZyYW5raWU= 94545 -IGluZGlzY3JpbQ== 94546 -X0JlZ2lu 94547 -KGVy 94548 -O28= 94549 -44Oz44Kw 94550 -bm9kZU5hbWU= 94551 -IHJlZnVuZGVk 94552 -IGRpc21hbA== 94553 -IEh1ZmZQb3N0 94554 -IHVuZGVjaWRlZA== 94555 -d3JpdGVsbg== 94556 -a8Ozdw== 94557 -IEJvc2U= 94558 -CWxpYg== 94559 -b3BsYW4= 94560 -aW50ZXJwcmV0ZWQ= 94561 -IE1PTkVZ 94562 -dXZv 94563 -IG50b2hz 94564 -aXNldW0= 94565 -Pmo= 94566 -IHVuZml0 94567 -IGh1Z2dlZA== 94568 -IEplc3Q= 94569 -bXBz 94570 -IGJyb20= 94571 -J28= 94572 -IGZvdg== 94573 -IFNocmluZQ== 94574 -IEVJVEhFUg== 94575 -eWNhc3RsZQ== 94576 -IHNhdHVy 94577 -cmVxdWVzdERhdGE= 94578 -W2Rpcg== 94579 -T1VDSA== 94580 -X0Rv 94581 -IHlvbA== 94582 -IGluaXRpYWxWYWx1ZXM= 94583 -W3ZlcnRleA== 94584 -c2VydmljZU5hbWU= 94585 -LnNhbGFyeQ== 94586 -IEF1dGhlbnRpY2F0ZQ== 94587 -6L6+ 94588 -X1ZMQU4= 94589 -KFtdKTsKCg== 94590 -IFNlcnVt 94591 -UGF0aFBhcmFt 94592 -Zm9ybXVsYXJpbw== 94593 -IHN1bW1hcml6ZXM= 94594 -T0NS 94595 -b3JhbQ== 94596 -TERBUA== 94597 -Ymlj 94598 -cGlja2Vk 94599 -LXRoYXQ= 94600 -IGNkcw== 94601 -CWFuaW0= 94602 -IGludHJpYw== 94603 -IFdvcnQ= 94604 -IFZMQw== 94605 -IFNoaWl0ZQ== 94606 -U3R1ZGllcw== 94607 -LmRpc3BhdGNoZXI= 94608 -KGVuYWJsZQ== 94609 -Lm1peGlu 94610 -IFNleW1vdXI= 94611 -IGJpb21lZGljYWw= 94612 -IFNwb29u 94613 -IE5vcnNl 94614 -IGludGVudHM= 94615 -IMOpcXVpcA== 94616 -IERyZXNzZXM= 94617 -TFBBUkFN 94618 -LnNldFJlc3VsdA== 94619 -LmRlbGV0ZUJ5SWQ= 94620 -IG5ld2ZvdW5k 94621 -IE9TRA== 94622 -b3VzeQ== 94623 -IGVzdGFkb3M= 94624 -W0J5dGU= 94625 -Q2h1Y2s= 94626 -Lm9uVmlld0NyZWF0ZWQ= 94627 -IENvbnRyaWJ1dGlvbg== 94628 -X0VuYw== 94629 -SU5FVA== 94630 -IGZsYXZvcmZ1bA== 94631 -IOOCog== 94632 -dmlzYQ== 94633 -IEhlcmN1bGVz 94634 -LmdldEFwcA== 94635 -IFlvaw== 94636 -Lk1haW5BY3Rpdml0eQ== 94637 -KS5b 94638 -IGxhdXQ= 94639 -SW52aXRl 94640 -IENodXJjaGVz 94641 -LCcj 94642 -2YrYsQ== 94643 -KFNT 94644 -IHZlbmRh 94645 -YXNqb24= 94646 -LklOVEVS 94647 -aXBoZXJ5 94648 -KFN5bnRheA== 94649 -b25kcm91cw== 94650 -CWNlbnRlcg== 94651 -QnJhY2tldEFjY2Vzcw== 94652 -IENhcGNvbQ== 94653 -LmdldEZvbnQ= 94654 -IFZhdWx0cw== 94655 -IGRpc2XDsWFkb3I= 94656 -Om8= 94657 -KHNoZWxs 94658 -IGVDb21tZXJjZQ== 94659 -IGFsdHJl 94660 -X2F0dGFjaGVk 94661 -IGlzcg== 94662 -IG9idGFpbnM= 94663 -LkNvbnRleHRDb21wYXQ= 94664 -IGF0dGVuZGVl 94665 -IFR3aWNl 94666 -IE1vb2Q= 94667 -6YKu566x 94668 -bm9kb2M= 94669 -IFBJWEk= 94670 -c29mYXI= 94671 -IEJsb29keQ== 94672 -LkNvbXBsZXRl 94673 -IEJFUg== 94674 -IGdldENhdGVnb3J5 94675 -IGRpc3F1YWxpZmllZA== 94676 -X1RydWU= 94677 -J2Vy 94678 -LXRvbw== 94679 -IGh5cGVybGluaw== 94680 -X21heGltdW0= 94681 -TmVhbA== 94682 -IHBJbmZv 94683 -LmdldEVsZW1lbnRzQnlOYW1l 94684 -c2NoZWR1bGVk 94685 -cGF5ZXI= 94686 -CXZlcmlmeQ== 94687 -LWVudGl0eQ== 94688 -bWV0YXRhYmxl 94689 -YmlsZHVuZw== 94690 -IGRlbHRhWA== 94691 -ZW1wbGFjZQ== 94692 -IHJldmVydGVk 94693 -cmVwaWQ= 94694 -bGVhcm5lcg== 94695 -fSkpCgo= 94696 -dWNvc2U= 94697 -IHJpY28= 94698 -IGJhbmdlZA== 94699 -IEFmcm8= 94700 -KGluZXJ0aWE= 94701 -YW5zYQ== 94702 -IMOkdmVu 94703 -S2FyZW4= 94704 -IHN1cGVyc3Q= 94705 -IGZydWl0aW9u 94706 -b3RjaA== 94707 -IFBheXM= 94708 -UmVzaWRlbnRz 94709 -IHByaXNt 94710 -Jik7Cgo= 94711 -Lmptcw== 94712 -IFNsdWc= 94713 -PScnKQ== 94714 -IGd1dGVu 94715 -IFNwaWVsYmVyZw== 94716 -IFRGb3Jt 94717 -KGJlZm9yZQ== 94718 -IEZpbml0ZQ== 94719 -5paw5aKe 94720 -IG1laWxsZXVyZQ== 94721 -0L/QuNGB0LDQvdC40LU= 94722 -X0Vycg== 94723 -LWZ0 94724 -bmFubw== 94725 -LkFkZHI= 94726 -IC8vDQoNCg== 94727 -IEpvbmFo 94728 -IERpc2Nv 94729 -IGx1bmNoZXM= 94730 -IERGQQ== 94731 -ZXhwbGljaXQ= 94732 -XSc7Cg== 94733 -IHJlZmluZXJ5 94734 -IFN0cmluZ1R5cGU= 94735 -dW5zcXVlZXpl 94736 -IExpa2VseQ== 94737 -V3JpdGVz 94738 -LmJwbQ== 94739 -IHBJdGVt 94740 -b3Vuc2Vs 94741 -U3RhbmRpbmc= 94742 -IGNob2tlZA== 94743 -IGFuc2No 94744 -dXBpbA== 94745 -IERlYnVnZ2Vy 94746 -4qCA4qCA 94747 -PEdyb3Vw 94748 -IFNjYWxpYQ== 94749 -IHN1YnN0aXR1dGlvbnM= 94750 -IGNsaW1iZXJz 94751 -ICopIg== 94752 -IG5hbm9wYXJ0aWNsZXM= 94753 -IEFQUFJP 94754 -IHB1cmNoYXNlcnM= 94755 -IFFUZXN0 94756 -IEF3YWtlbmluZw== 94757 -CVNlcmlhbA== 94758 -LnJlcGFpbnQ= 94759 -IHNhdm9yeQ== 94760 -IHBvcm91cw== 94761 -IGFWYXI= 94762 -IFN1YXJleg== 94763 -LUVhc3Q= 94764 -Qm94ZXM= 94765 -IFdlaW5lcg== 94766 -IENSQQ== 94767 -IOqwkuydhA== 94768 -IHhsaW0= 94769 -Ij8KCg== 94770 -IHdhc2hpbmd0b24= 94771 -7Jq0 94772 -IHRvdGFsZW1lbnQ= 94773 -X210aW1l 94774 -LnNldFNjZW5l 94775 -IGxsYW1h 94776 -IGNibw== 94777 -ZWZk 94778 -IHVuZGVycmF0ZWQ= 94779 -cmFpc2luZw== 94780 -IE5BVElPTkFM 94781 -ICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKi8KCg== 94782 -b3B0aWM= 94783 -aWRlYXM= 94784 -IOaPkA== 94785 -IGxhaw== 94786 -ISEs 94787 -IGtvbW0= 94788 -cGFyYWd1cw== 94789 -U2l0ZXM= 94790 -IHN0cmVzc2luZw== 94791 -IE1hdEJ1dHRvbk1vZHVsZQ== 94792 -IENvbnZlcnRlZA== 94793 -YW5hbWU= 94794 -X1JFQURPTkxZ 94795 -XT0+ 94796 -IGJvcmRlbA== 94797 -IGJpYmxpb2dyYXBoeQ== 94798 -IGdyaWRDb2x1bW4= 94799 -IGpvdXJuYWxpc3RpYw== 94800 -7J6E 94801 -IHJhc3BiZXJyeQ== 94802 -c3RpY2U= 94803 -IGFicmFzaXZl 94804 -IERCSGVscGVy 94805 -IGludGY= 94806 -IFJUQlU= 94807 -fSciLA== 94808 -IEhhbw== 94809 -c3dhbmE= 94810 -IGphbnZpZXI= 94811 -IGluc3RpdHV0ZXM= 94812 -IFNlYmFzdA== 94813 -X0NPTFM= 94814 -IGZpZ3VyYQ== 94815 -IFp1c3Q= 94816 -Zm95 94817 -PigpKTsKCg== 94818 -IExpZWJl 94819 -QWdlbmN5 94820 -IOyLnOyekQ== 94821 -IFRodW1ibmFpbHM= 94822 -dGV4dFRoZW1l 94823 -IGVjaG9pbmc= 94824 -ZW1wZXJhdHVyZQ== 94825 -IGZpcmVwb3dlcg== 94826 -ZWRi 94827 -OicpOwo= 94828 -w6lnb3I= 94829 -L2ZlZWQ= 94830 -IGh1cmw= 94831 -LWF2YWlsYWJsZQ== 94832 -IFJlbmRlcnM= 94833 -IGZkcw== 94834 -IEpTR2xvYmFs 94835 -IENpdGl6ZW5zaGlw 94836 -a2llZ28= 94837 -U3RhbmRhcmRJdGVt 94838 -LnBsYWNlcw== 94839 -IHNjYWxhYmlsaXR5 94840 -IFRyYWlscw== 94841 -Zm9sbG93ZXI= 94842 -IHNlcnZpw6dvcw== 94843 -ID8+Ii8+Cg== 94844 -W21ldGhvZA== 94845 -KGli 94846 -IHJpZGljdWxl 94847 -IGFkYXB0YWJsZQ== 94848 -ZmlsdHJv 94849 -IGtldG9nZW5pYw== 94850 -LkltYWdlVHJhbnNwYXJlbnRDb2xvcg== 94851 -IENGTw== 94852 -IFBFRA== 94853 -ICIiKTs= 94854 -b2dsb2Jpbg== 94855 -W3NpemVvZg== 94856 -QnJhbmRvbg== 94857 -LlRvU2hvcnQ= 94858 -IG5pxbw= 94859 -IFRFUk1JTg== 94860 -LmdldFN0YXR1c0NvZGU= 94861 -IGRlYnRvcg== 94862 -IENPTlNUUkFJTlQ= 94863 -CXNpZGU= 94864 -IERvbWlubw== 94865 -0YLQvtC8 94866 -IGdsYWNpZXI= 94867 -IGdyb3U= 94868 -enA= 94869 -IENhcmxh 94870 -LUZlYg== 94871 -UGVs 94872 -LnJlYWRWYWx1ZQ== 94873 -Y2xpbWF0ZQ== 94874 -IHRpbGVTaXpl 94875 -LnRyaXA= 94876 -RU5URQ== 94877 -IGNodWJieQ== 94878 -IGltcG9zaXRpb24= 94879 -TE9XRVI= 94880 -LmJ5SWQ= 94881 -Lkxvb2tBbmRGZWVs 94882 -YXJpaA== 94883 -LmZpbmRCeUlkQW5kVXBkYXRl 94884 -IFN0b3JlZA== 94885 -IGJvdXJnZW9pc2ll 94886 -SFRUUFJlcXVlc3RPcGVyYXRpb24= 94887 -IHN1Y2tlcg== 94888 -LmRlcXVldWU= 94889 -bGlja2Vu 94890 -IHN1YnJhbmdl 94891 -X01FRElVTQ== 94892 -SXNsYW0= 94893 -IFNwYXJrcw== 94894 -77yaJQ== 94895 -aW1wb3J0ZQ== 94896 -IGAt 94897 -IGpveXM= 94898 -Z3JvdXBpZA== 94899 -Rmx5aW5n 94900 -CWJz 94901 -Z3Jvc3M= 94902 -IEZpZXN0YQ== 94903 -IGNzdA== 94904 -IGFmaWNpb24= 94905 -b3Bob24= 94906 -X0NJ 94907 -am4= 94908 -QmVhdXR5 94909 -IHNjZQ== 94910 -IGNyYWNrZXJz 94911 -YXBr 94912 -IGdvcmQ= 94913 -IHByZXRleHQ= 94914 -IFtc 94915 -IENhbmRpZA== 94916 -R29hbHM= 94917 -QWN0aW9uVHlwZXM= 94918 -LG51bWJlcg== 94919 -IHBvcHVsYWNl 94920 -IGVudHJlbg== 94921 -IEF1dG9m 94922 -6Zmi 94923 -QmFzZUNvbnRleHQ= 94924 -QmFsYW5jZXI= 94925 -KEJvcmRlcg== 94926 -IG1pbmNlZA== 94927 -cmVjYWxs 94928 -Y2Jh 94929 -IGFwcHJvdmVz 94930 -IEtsb3Bw 94931 -ZXJtaW50 94932 -X2Zyb250ZW5k 94933 -ZXNjbw== 94934 -IG5pbmV0ZWVu 94935 -RHJpdmluZw== 94936 -IFhWSQ== 94937 -IFRhY3RpY3M= 94938 -IHByb2dyYW1hcw== 94939 -aWVzZW4= 94940 -TW92 94941 -ZGlldA== 94942 -YXV0w6k= 94943 -KCIuIik= 94944 -IGdvdmVybm8= 94945 -X0FuZA== 94946 -L21pdA== 94947 -IGNhZmV0ZXJpYQ== 94948 -LXRyYWNraW5n 94949 -IGNvbW11dGluZw== 94950 -LnVua25vd24= 94951 -X3R5cGVvZg== 94952 -IFNTQQ== 94953 -UFJPVE8= 94954 -Lk1lcmdl 94955 -IGZvckNlbGxSZXVzZUlkZW50aWZpZXI= 94956 -IFNhdGlzZmFjdGlvbg== 94957 -ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 94958 -SU1QTElFRA== 94959 -IFJlc3RyaWN0ZWQ= 94960 -IE1hZ251bQ== 94961 -0L3QvtC8 94962 -S2Fuc2Fz 94963 -YXlsaWdodA== 94964 -IFRvd2FyZHM= 94965 -IFRvbWU= 94966 -IFRlbmRlcg== 94967 -X2RlcHQ= 94968 -LmNydA== 94969 -dHJlY2h0 94970 -U1RPTkU= 94971 -IGVtcHRpZWQ= 94972 -ICcpOwoK 94973 -4LiB4Liy4Lij 94974 -0Y/RgtGM 94975 -bGVjaw== 94976 -IFt+LA== 94977 -LmV4cGlyZXM= 94978 -IFRpZw== 94979 -IElyb25pY2FsbHk= 94980 -CUxM 94981 -Lk5vdE5pbA== 94982 -IOWKoA== 94983 -IEdvdmVy 94984 -IFBlcnNwZWN0aXZlcw== 94985 -IERWUg== 94986 -IGxva2FsZQ== 94987 -IHJlc2VuZA== 94988 -IGRvdWJseQ== 94989 -IGNvbXVuaWRhZA== 94990 -IEFzc2VtYmx5Q29tcGFueQ== 94991 -KHR1cm4= 94992 -IHN1Ymxpc3Q= 94993 -IGVuZG9yc2VtZW50cw== 94994 -X1JFR0lTVFJZ 94995 -ISIpDQo= 94996 -KTs7Cg== 94997 -IGdhbnpl 94998 -IEhhcm5lc3M= 94999 -X21hdGNoZWQ= 95000 -5L6h 95001 -4oCiCgo= 95002 -Q2hlZg== 95003 -CUluaXRpYWxpemU= 95004 -KTsiPgo= 95005 -IEZhcmFnZQ== 95006 -cmlzaA== 95007 -YWx0ZXQ= 95008 -RGVhbGVy 95009 -LkxvZ1dhcm5pbmc= 95010 -KGFmdGVy 95011 -IEdhcnRlbg== 95012 -IGV4cGxvZGVz 95013 -LkNMQVNT 95014 -IHVzZVJvdXRlcg== 95015 -LUxh 95016 -IHNhZGRlbmVk 95017 -YXJvdg== 95018 -VG9VcGRhdGU= 95019 -IOae 95020 -cGlp 95021 -JwoKCgo= 95022 -IFRSQU5TQUNUSU9O 95023 -b25nYQ== 95024 -bG9nYW4= 95025 -Q3Jvdw== 95026 -IGJyaXRpc2g= 95027 -IENvbnRlbnRWaWV3 95028 -X0JC 95029 -b2x2ZW5jeQ== 95030 -bG9hZE1vZGVs 95031 -VE9PTFM= 95032 -aGV0ZW4= 95033 -X25o 95034 -QUJM 95035 -LXZlcnM= 95036 -QXJlbmE= 95037 -LnNpbmdsZXRvbkxpc3Q= 95038 -KHBhdA== 95039 -CW5hbWVz 95040 -KHNx 95041 -IHZhbG9yZQ== 95042 -JHJlcQ== 95043 -IGFudGhyb3BvbG9neQ== 95044 -VGhpbmtpbmc= 95045 -IG1pc2NoaWVm 95046 -IGFyY2hpdmFs 95047 -4KS5 95048 -LlNldFRvb2xUaXA= 95049 -cHJhcg== 95050 -YW5qYQ== 95051 -IGZpcnN0bHk= 95052 -CWxpZ2h0 95053 -LS0s 95054 -IFNwZWFycw== 95055 -IG9nbA== 95056 -c3RlZW4= 95057 -aW1wbGVtZW50cw== 95058 -cmlzdHM= 95059 -K0U= 95060 -IEJhbnM= 95061 -IGZhc3RiYWxs 95062 -IEhlcm1lcw== 95063 -dmVsZWQ= 95064 -dHdlbnR5 95065 -IG5lY2VzaXRh 95066 -IE1vcm9jY2Fu 95067 -aXNMb2dnZWRJbg== 95068 -Q0xPQ0tT 95069 -LkFic3RyYWN0aW9ucw== 95070 -LlBhY2tldA== 95071 -IG1lbmFjaW5n 95072 -LXZlc20= 95073 -IExpdmluZ3N0b24= 95074 -IG9jaQ== 95075 -IGV4dHJhZGl0aW9u 95076 -ICQoJA== 95077 -IExvY2tlcg== 95078 -IFJlYmVsbGlvbg== 95079 -IG1peGlucw== 95080 -Y3RhbA== 95081 -L3JmYw== 95082 -IFNHRA== 95083 -LGlkeA== 95084 -IGJsZWlidA== 95085 -KFwk 95086 -IHBldGVy 95087 -IGJhcnJlbg== 95088 -IHBob3NwaG9yeQ== 95089 -IGdvZ2dsZXM= 95090 -LmhvbQ== 95091 -QGQ= 95092 -PSct 95093 -LmlzVXNlcg== 95094 -YWthc2g= 95095 -X2h1Yg== 95096 -aXBlbGluZXM= 95097 -IEB9 95098 -LnN1cm5hbWU= 95099 -SW50ZXJvcA== 95100 -IGluRmlsZQ== 95101 -IGVzcGVjaWFsbWVudGU= 95102 -IGF1dG9ub20= 95103 -IFphbWJpYQ== 95104 -X0NPVU5UUlk= 95105 -PENvdXJzZQ== 95106 -aWRlb2dyYXBoaWM= 95107 -IENhbWVyb29u 95108 -ZmluZEJ5SWQ= 95109 -KSIu 95110 -IERlcGVuZHM= 95111 -cml0b3M= 95112 -Lk91cg== 95113 -IHN1YnNpZGl6ZWQ= 95114 -JywnIis= 95115 -IGdsZWFu 95116 -IEFzc2VtYmx5Q29weXJpZ2h0 95117 -cGljYWJsZQ== 95118 -IHVud2l0dGluZw== 95119 -IG9tZGF0 95120 -IEVhc2U= 95121 -IGVtYm9kaWVz 95122 -KHBEWA== 95123 -IFZvdGVy 95124 -QXNzaWduZWQ= 95125 -cmV2ZWFs 95126 -IGZlbmQ= 95127 -KHBhcnNlRmxvYXQ= 95128 -IGRwcw== 95129 -dHBsaWI= 95130 -YXNzZXJ0Q291bnQ= 95131 -eG1heA== 95132 -VW51c2Vk 95133 -KGZi 95134 -IHN1Ym1pdHM= 95135 -IFJlcGxpY2E= 95136 -KGR5 95137 -IGJhbmRl 95138 -LnNlbWFudGlj 95139 -IHNlYXJjaFN0cmluZw== 95140 -IFNhbmZvcmQ= 95141 -CWZ1bGw= 95142 -cHJt 95143 -X3V0aWxpdGllcw== 95144 -VU5VU0VE 95145 -IHNjYW5uZXJz 95146 -IGJmZA== 95147 -Lk9yZ2FuaXphdGlvbg== 95148 -LWN1cg== 95149 -UmFpbA== 95150 -IHhueHg= 95151 -JSk7Cg== 95152 -IG92ZXJwb3N0aW5n 95153 -VmlldA== 95154 -IHRhcGVyZWQ= 95155 -IGNhbWVv 95156 -IFZpZXdpbmc= 95157 -IGRpc21hbnRsZQ== 95158 -IGZpc3M= 95159 -IFNlbnRyeQ== 95160 -aGVhdG1hcA== 95161 -IMOhcmVhcw== 95162 -IEdyw7w= 95163 -IGppZw== 95164 -LmNsZWFyUmVjdA== 95165 -ZXZlbnRUeXBl 95166 -IHR1cmJ1bGVuY2U= 95167 -Y2tpbGw= 95168 -LkZvY3VzZWQ= 95169 -IGludGVybWVkaWFyeQ== 95170 -IE9iZXNpdHk= 95171 -YXRlZ28= 95172 -bW9udG8= 95173 -IEFsYW1vZmlyZQ== 95174 -IFNoZWlsYQ== 95175 -IENPTExFQ1RJT04= 95176 -Q2FyZEJvZHk= 95177 -IEhhYml0 95178 -UExBTg== 95179 -LnZpc3VhbGl6YXRpb24= 95180 -JSkuCgo= 95181 -IEludGVsbGlK 95182 -IEdsb3Zlcg== 95183 -LnNwYXRpYWw= 95184 -IGdyZWV0aW5ncw== 95185 -IE9wZW5GaWxlRGlhbG9n 95186 -ey8q 95187 -IFTDqWzDqQ== 95188 -IEVm 95189 -ICJbJQ== 95190 -IG1hZ2lzdHJhdGU= 95191 -IExpdGVjb2lu 95192 -IFNlbGU= 95193 -IGNvbW1lcmM= 95194 -cHJpbnR3 95195 -bmV4dEludA== 95196 -LmdldENoaWxkQXQ= 95197 -IEdldEN1cnJlbnQ= 95198 -IGV1cm9ww6k= 95199 -IEFJUw== 95200 -ZXR0ZW4= 95201 -LkV2ZW50UXVldWU= 95202 -YW5mb3Jk 95203 -dW5ha2Fu 95204 -LnNldE91dHB1dA== 95205 -IGNtZGxpbmU= 95206 -LGdldA== 95207 -IEhlYXJk 95208 -LmNvbnRlbnRUeXBl 95209 -ZW1k 95210 -IFJldG9ybmE= 95211 -YWNk 95212 -IFBsYXlvZmY= 95213 -YWNtYW4= 95214 -LndlYnNvY2tldA== 95215 -Q2xpZW50SWQ= 95216 -LmV4YW0= 95217 -IGF0dGVudWF0aW9u 95218 -LnNldENoYXJhY3Rlcg== 95219 -CUNvbGxlY3Rpb24= 95220 -5rCX 95221 -IHByZWRpY3RvcnM= 95222 -IFNoZXJpZGFu 95223 -cmltaW5hdG9y 95224 -KFN0YWNr 95225 -X1BLRw== 95226 -PScnKToK 95227 -KHBhZA== 95228 -IE5vZG8= 95229 -IGludGVyb3Blcg== 95230 -IFRyYW5zcGFyZW5jeQ== 95231 -CWR4 95232 -emVt 95233 -IHByYXRpcXVl 95234 -IGZpYnI= 95235 -KCk/Owo= 95236 -X01PQklMRQ== 95237 -LlJFRw== 95238 -X1lFTExPVw== 95239 -VGl0YW4= 95240 -JykKCgoK 95241 -IGNvbXBvbmVudE5hbWU= 95242 -IENvb2xlcg== 95243 -aXNGdW5jdGlvbg== 95244 -LmZlZWRiYWNr 95245 -IHBlcmZlY3RlZA== 95246 -IHBhZWQ= 95247 -LXNjcmlwdHM= 95248 -U3VzcA== 95249 -PE9wdGlvbg== 95250 -IER0 95251 -7YS0 95252 -J1JF 95253 -IE5STA== 95254 -IE1hbm55 95255 -IHJvZw== 95256 -IEdhcnI= 95257 -X2Nvb2tpZXM= 95258 -U3Bs 95259 -IHByb21vdGVycw== 95260 -KmR0 95261 -XEFQSQ== 95262 -IGV2b2tl 95263 -X0VudHJ5 95264 -IGZpcmVmaWdodGVy 95265 -aXZpZGFk 95266 -SmFjb2I= 95267 -IGxlZ2lvbg== 95268 -KHBvbA== 95269 -CWZsYXNo 95270 -b29rZWVwZXI= 95271 -LmNsaXBzVG9Cb3VuZHM= 95272 -IGdyYXBoaXRl 95273 -J2h0dHA= 95274 -X1RSSUFOR0xF 95275 -IERyb3BJbmRleA== 95276 -LnNtdHA= 95277 -IFVOU0lHTkVE 95278 -X1BJQ1RVUkU= 95279 -X09SSUVOVEFUSU9O 95280 -IE9QUA== 95281 -Iyc= 95282 -w6FmaWNv 95283 -Lmhpc3RvZ3JhbQ== 95284 -IEJlbm55 95285 -Pldl 95286 -IHJlcG9zdA== 95287 -IGZpYW5jZQ== 95288 -IEJvdW50eQ== 95289 -c3RyZXNz 95290 -RGF0ZXRpbWU= 95291 -Okg= 95292 -IFNwaGlueA== 95293 -Tm9ybWFsbHk= 95294 -YXBpeGVs 95295 -IHVzZXJBZ2VudA== 95296 -IE1vcmk= 95297 -L2xhYg== 95298 -Lk1PREVM 95299 -IEVtb3Rpb25hbA== 95300 -U2NhbGVk 95301 -ZGV2aWNlSWQ= 95302 -IOqzhA== 95303 -Y2Vhc2Vk 95304 -PElN 95305 -Y2VlZGVk 95306 -IGxpYnJhcmlhbg== 95307 -KW51bGw= 95308 -IG1pY3Jvbg== 95309 -IEZvdQ== 95310 -dWxlbg== 95311 -L2xpdmU= 95312 -cnNjaGVpbg== 95313 -ZmVh 95314 -IGhhYmls 95315 -IE5hdkxpbms= 95316 -bmVjZXNzYXJ5 95317 -LmNvZGVz 95318 -LW1ha2U= 95319 -IHBQYXJlbnQ= 95320 -X3JlbGF0aW9ucw== 95321 -IHJ1c2hlcw== 95322 -IHByb3BlbnNpdHk= 95323 -IFNraW5ueQ== 95324 -V0VTVA== 95325 -X2NvcnB1cw== 95326 -KHJlb3JkZXJlZA== 95327 -ZmRi 95328 -IEdldE1lc3NhZ2U= 95329 -QnJ1bg== 95330 -LnZz 95331 -IHDFgg== 95332 -IGNydW5jaHk= 95333 -Qm9vbQ== 95334 -UEo= 95335 -SmFrZQ== 95336 -57qm 95337 -JGNsaWVudA== 95338 -IH1dKQo= 95339 -IGNvbnZlcnNl 95340 -IEdSQVQ= 95341 -IENSUw== 95342 -Lkxvdw== 95343 -KHZhbGlkYXRl 95344 -X0NMSUNLRUQ= 95345 -LmJsdWV0b290aA== 95346 -CXh0eXBl 95347 -IGNsb3NlTW9kYWw= 95348 -X2ludGVudA== 95349 -IHByb2dub3Npcw== 95350 -c2F2 95351 -Q3Rs 95352 -IGNob29zZXI= 95353 -IFN1ZG9rdQ== 95354 -PVVzZXI= 95355 -LmNsZg== 95356 -CWV4cGxpY2l0 95357 -IHBvdGVudGlhbHM= 95358 -IEdlb3JnZXM= 95359 -IGVsaWM= 95360 -IHRzbGli 95361 -IFJhZ25hcg== 95362 -X3JlcHJlc2VudGF0aW9u 95363 -LWxlZ2dlZA== 95364 -aGFtc3Rlcg== 95365 -IEZpcmVzdG9yZQ== 95366 -Y29udmVydFZpZXc= 95367 -Q29tYmluZWQ= 95368 -INC00LXQuw== 95369 -IGVzcGVjdA== 95370 -IOOCkg== 95371 -IFN0YW1pbmE= 95372 -bG9va3M= 95373 -RU5BUklP 95374 -L2ZpeHR1cmVz 95375 -LnNtcw== 95376 -IHNlbWljbGFzcw== 95377 -IHNlbWljbGFzc2ljYWw= 95378 -LlBlZWs= 95379 -XSQ= 95380 -X0RTUA== 95381 -X0xWTA== 95382 -VklSVFVBTA== 95383 -IENhcGl0YWxz 95384 -IFNDVA== 95385 -LldoaWxl 95386 -IFN1YnN0YW5jZQ== 95387 -LWRvbmU= 95388 -IGVuc2xhdmVk 95389 -Y2xhc3NpZnk= 95390 -ZW50YW55bA== 95391 -IFZlZ2V0YWJsZQ== 95392 -X0RFUEVORA== 95393 -RGFuaQ== 95394 -IHF1aWVyZXM= 95395 -IGFiYmlhbW8= 95396 -IExpYmVy 95397 -YWZj 95398 -6YCf 95399 -cHJlZGljdGVk 95400 -LlBORw== 95401 -IFdoaXA= 95402 -Ly89PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ== 95403 -IOKJoA== 95404 -IOWM 95405 -REVN 95406 -Q0NB 95407 -L2Nsb3Nl 95408 -IC8vLzwv 95409 -IG1lc21h 95410 -IEJlaXJ1dA== 95411 -IEluaXRpYWxpemluZw== 95412 -4buZdA== 95413 -TU9OVEg= 95414 -IO2bhA== 95415 -UGFya2luZw== 95416 -Q29tZm9ydA== 95417 -IEVuZ2luZXM= 95418 -d2VycA== 95419 -QFJlcXVlc3RQYXJhbQ== 95420 -LUtleQ== 95421 -IGJhY2tsaWdodA== 95422 -cGFzc2Vz 95423 -Lm51bWJlck9mTGluZXM= 95424 -L0xpbnV4 95425 -KEhUVFA= 95426 -IEh0dHBVUkxDb25uZWN0aW9u 95427 -b3Nvcw== 95428 -Lnh4 95429 -IGZpbG1wamVz 95430 -ID09PT4= 95431 -b3B0aW1pemU= 95432 -Q2Fub24= 95433 -IC4uLiIK 95434 -ICciJzsK 95435 -IGPDqWxpYg== 95436 -IHByaW5jaXBhbG1lbnRl 95437 -IFByb3BlcnR5VmFsdWU= 95438 -T1VOQ0U= 95439 -IGV4Y3Vyc2lvbg== 95440 -IEFjY2Vzc1Rva2Vu 95441 -cmVxdWV0ZQ== 95442 -Vm9sdGFnZQ== 95443 -ZXhwbGFpbg== 95444 -fSkoKTsKCg== 95445 -VVJMT1BU 95446 -IGZ1bmdhbA== 95447 -R3JlZWs= 95448 -LWJsaW5k 95449 -IGZldWRhbA== 95450 -IFNvbmF0YQ== 95451 -IERpYWdub3Npcw== 95452 -JHhtbA== 95453 -ZWRpdGFyeQ== 95454 -IHN0aW11bGF0ZXM= 95455 -UG9udA== 95456 -Lkhhc1ByZWZpeA== 95457 -Ym9hdHM= 95458 -IFNjYXR0ZXI= 95459 -IEdFTkVSSUM= 95460 -IGZpc2hlcw== 95461 -PWxlbmd0aA== 95462 -IG1lbGhvcmVz 95463 -c3BlbnQ= 95464 -w7Rt 95465 -IEluZ3JhbQ== 95466 -Pi4KCg== 95467 -cGFyaXR5 95468 -LlZpZGVvQ2FwdHVyZQ== 95469 -IFR1YmVz 95470 -IGNvbWVkaWM= 95471 -IHByb2Nlc3NEYXRh 95472 -QURC 95473 -KG5ld1N0YXRl 95474 -5YGc 95475 -IFdlYnNlaXRl 95476 -X09mZg== 95477 -LGJvZHk= 95478 -IHN1YmNvbnRyYWN0 95479 -IGNodXRl 95480 -IGNhcnRlc2lhbg== 95481 -dGhyZXNo 95482 -LkNhcnQ= 95483 -IG1ldG9k 95484 -Y3VzdG9taXpl 95485 -THRk 95486 -CXNvdW5k 95487 -V2ViU2VydmljZQ== 95488 -IEhpbmRlcmVk 95489 -W3Jlcw== 95490 -KFRpbGU= 95491 -Y2FwYWJpbGl0aWVz 95492 -X09WRVJGTE9X 95493 -INGB0YHRi9C7 95494 -IENvY2g= 95495 -IHRlc3ROYW1l 95496 -V09SRFM= 95497 -XE1vZHVsZXM= 95498 -P3VybA== 95499 -X2NvbnRpbnVvdXM= 95500 -IFFJY29u 95501 -IHN0YXJlcw== 95502 -IGVqZWN0ZWQ= 95503 -IEludmFzaW9u 95504 -ZmluYWxpemU= 95505 -IGdldg== 95506 -PGc= 95507 -IEVkaXRvckdVSQ== 95508 -QmVybGlu 95509 -LmxpbmVFZGl0 95510 -LXJlZ2V4cA== 95511 -IHNsZWQ= 95512 -IEVBQ0g= 95513 -dWNv 95514 -IHNlZWRpbmc= 95515 -IGxvY2FsaXpl 95516 -ZXR1 95517 -X2FsbW9zdA== 95518 -cGFuc2U= 95519 -IFNlbnNvcnM= 95520 -X1NJ 95521 -KnNw 95522 -IFByb3BlcnR5SW5mbw== 95523 -IGFwcm94aW0= 95524 -IGRhdGFHcmlkVmlld1RleHRCb3hDb2x1bW4= 95525 -16A= 95526 -IGRpZmVyZW5jaWE= 95527 -TE9PSw== 95528 -IG9tbmlw 95529 -IFR1cmluZw== 95530 -IHVuaWRhZGVz 95531 -77yfCg== 95532 -LlJvd0hlYWRlcnM= 95533 -X0FDVElPTlM= 95534 -IERhbHk= 95535 -IGZvcnRpZmllZA== 95536 -IFdhZ2U= 95537 -LnNpbXBz 95538 -KGlzc3Vl 95539 -IGxlcHQ= 95540 -T3duZXJJZA== 95541 -J29yZGVy 95542 -5Y+N 95543 -56Wo 95544 -IHJld3JpdGluZw== 95545 -Lkl0YWxpYw== 95546 -IEZvcmdvdHRlbg== 95547 -KElM 95548 -IE5vU3VjaEVsZW1lbnRFeGNlcHRpb24= 95549 -ZXdu 95550 -IHBvcHVsb3Vz 95551 -IFNoZWQ= 95552 -IyR7 95553 -IEFsbw== 95554 -RGV2aWNlSW5mbw== 95555 -KElOVk9LRQ== 95556 -IHBlbmE= 95557 -IEJCQg== 95558 -LmJi 95559 -IHRvcnM= 95560 -IGNvbmR1Y2l2ZQ== 95561 -LXB1cnBsZQ== 95562 -IHNxdWFyZWx5 95563 -Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCg== 95564 -0LrRgNGL 95565 -ZmFzdGE= 95566 -IGNwdA== 95567 -IEluZ2Vu 95568 -IHs/fQ== 95569 -0YPQsw== 95570 -UGVybA== 95571 -LnNreQ== 95572 -LWF1dG9tYXRpYw== 95573 -aW1wbGVtZW50 95574 -b3JubWVudA== 95575 -LklNQUdF 95576 -LVNwZWVk 95577 -CUZpZWxk 95578 -IHBvdW5kZWQ= 95579 -IExa 95580 -IGF1dG9Gb2N1cw== 95581 -IOC5gA== 95582 -LkNvbXBhbmlvbg== 95583 -IFZpbQ== 95584 -dW5jaWE= 95585 -X3NrYg== 95586 -IHVubWFycmllZA== 95587 -IFNvdXI= 95588 -Z2FhcmQ= 95589 -TGVvZA== 95590 -IOCq 95591 -LkNsb3Vk 95592 -IHJlaW5mb3JjZXM= 95593 -J10+ 95594 -IGZlbGl6 95595 -IFVBVg== 95596 -cmFuY2Vz 95597 -5Y2B 95598 -VG9MaXN0QXN5bmM= 95599 -LkV4ZWN1dG9y 95600 -LXRz 95601 -ICcuJzsK 95602 -IEtpbmVjdA== 95603 -44GE44GG 95604 -IGJldm9y 95605 -IEV4dHJhY3Rpb24= 95606 -X2RyYXdlcg== 95607 -JHN1Yg== 95608 -IHVwbGlmdGluZw== 95609 -LmJ0bkV4aXQ= 95610 -KCcvLypbQA== 95611 -UkVESVM= 95612 -c3RkZXhjZXB0 95613 -ZGVv 95614 -IGdpdmVy 95615 -X2JpbmRpbmdz 95616 -VG9EZXZpY2U= 95617 -Lm1p 95618 -IEVzdGltYXRlcw== 95619 -YWxsZWxl 95620 -Pz8/Cgo= 95621 -IFN0cmVhbXM= 95622 -IGFmZmxpY3Q= 95623 -LnNhcA== 95624 -IHF1YWxp 95625 -IEdhdWw= 95626 -U3BlY2lmaWVz 95627 -IHpr 95628 -IHNhbml0YXJ5 95629 -IG5ld0luZGV4 95630 -c3BlY3M= 95631 -IGZyYWdtZW50TWFuYWdlcg== 95632 -IE5lY2Vzc2FyeQ== 95633 -CVNwcmluZw== 95634 -PX4= 95635 -IE9NQVA= 95636 -Y2FyZWVy 95637 -KCItIik7Cg== 95638 -IERhcmxpbmc= 95639 -aXRhZw== 95640 -OnBr 95641 -IFN0ZWxsYXI= 95642 -IGluZmVydGlsaXR5 95643 -bGV4aWJsZQ== 95644 -VW5hcnk= 95645 -IDpdLA== 95646 -Lk5FVw== 95647 -Z3N1Yg== 95648 -X1VGdW5jdGlvbg== 95649 -LnNsaWRlcw== 95650 -IGRpdmVyc29z 95651 -X2xvY2Fscw== 95652 -XFwv 95653 -IHBjYXA= 95654 -IE9vaw== 95655 -LkRhdGFHcmlkVmlld0NvbnRlbnRBbGlnbm1lbnQ= 95656 -ZXJzb25pYw== 95657 -IHRyZWJ1aWU= 95658 -IHNlcXVlbnRpYWxseQ== 95659 -YWJhcg== 95660 -IElQQ0M= 95661 -IGRldm91dA== 95662 -XEhlbHBlcnM= 95663 -RVR3ZWV0 95664 -IHRyYWJhamFy 95665 -IFdpbGtpbnNvbg== 95666 -IGRhw58= 95667 -SHVtYW5z 95668 -VGVhY2hlcnM= 95669 -IERhdGFWaWV3 95670 -IFlvZw== 95671 -IGplZGU= 95672 -IGFtYmlhbmNl 95673 -dHJhbmQ= 95674 -IGVycmF0aWM= 95675 -IHThu6s= 95676 -LnJhYmJpdA== 95677 -IG5ld2JpZQ== 95678 -IGVudHJhbmNlcw== 95679 -IG9ydGhvZ29uYWw= 95680 -IERJU1BBVENI 95681 -IFNjaHJv 95682 -X1RVUk4= 95683 -Omludm9rZQ== 95684 -IHRhbnRhbA== 95685 -IFpvbmVz 95686 -c3RhdGVtZW50cw== 95687 -TGltaXRz 95688 -IEfDpA== 95689 -aWHFgmE= 95690 -LnByZWRpY2F0ZQ== 95691 -LkZS 95692 -IENocmlzdG9waA== 95693 -LkNvbnM= 95694 -IEhvcnRvbg== 95695 -X0N1c3RvbWVy 95696 -CU1E 95697 -IGVsa2Fhcg== 95698 -IE1TRQ== 95699 -IElzQWN0aXZl 95700 -XSop 95701 -XFVuaXQ= 95702 -IGVv 95703 -Rm9yT2JqZWN0 95704 -ZWxpYWM= 95705 -LWRldmVsb3BtZW50 95706 -IHRlYWw= 95707 -IHN0aXRjaGVk 95708 -IE91dGNvbWU= 95709 -b25jw6k= 95710 -ZW1iZWRkaW5n 95711 -IG9uTmV4dA== 95712 -IO2VtOuLuQ== 95713 -KGV4aXN0aW5n 95714 -LmJpZA== 95715 -CWFzc2VydEZhbHNl 95716 -e2w= 95717 -TEVycm9y 95718 -X2J1bGxldA== 95719 -KEh0bWw= 95720 -IGVCb29rcw== 95721 -cGVyUGFnZQ== 95722 -L3F1ZXN0aW9u 95723 -LmZha2U= 95724 -Lm1i 95725 -X2RsbA== 95726 -IGN1bXNob3Q= 95727 -IE1hZGFnYXNjYXI= 95728 -SE9MREVS 95729 -IHBlc3F1aXNh 95730 -X0RFQ0xT 95731 -XSxbLQ== 95732 -IEFsYmFuaWE= 95733 -LXRvYXN0 95734 -IHByb3RhZ29uaXN0cw== 95735 -IG15b2NhcmQ= 95736 -IHdhbGtlcnM= 95737 -ID09PT09PT0= 95738 -L1BhZ2U= 95739 -PTw/PQ== 95740 -IGVucXVhbnRv 95741 -X1RSVU5D 95742 -IHNlcHRlbWJyZQ== 95743 -IGxheW91dFBhcmFtcw== 95744 -ICcuLi8uLi8uLi8uLi8uLi8= 95745 -IFRyYWZmb3Jk 95746 -IHBhbGF2cmE= 95747 -IHJ1bmRvd24= 95748 -IGJyaXR0bGU= 95749 -w6RjaGU= 95750 -LllFTExPVw== 95751 -IENlcmVtb255 95752 -IG5ld1RleHQ= 95753 -dmVjcw== 95754 -IGVzc2Vu 95755 -IE1ldG9kbw== 95756 -IEdVSURF 95757 -IHBvc3Rwb25l 95758 -IFZTdGFjaw== 95759 -WyIk 95760 -IE1pY3Jvc3lzdGVtcw== 95761 -XFBhZ2U= 95762 -cG1hdA== 95763 -X0ZBVUxU 95764 -X21C 95765 -U3RhdGVNYWNoaW5l 95766 -RmFjdWx0eQ== 95767 -Lnd4 95768 -IE1vemFydA== 95769 -YW5pbWU= 95770 -IHB5dA== 95771 -IEJ1a2tpdA== 95772 -LUlORlJJTkdFTUVOVA== 95773 -IHNlYXJjaGVy 95774 -LWJhc2tldA== 95775 -IG9tYXM= 95776 -IFR1bmlz 95777 -IFBsYXR0 95778 -IHsNCg0KDQo= 95779 -eWFo 95780 -dG9sdWE= 95781 -SW50cm9kdWNlZA== 95782 -c3VwcGx5 95783 -IG1pc29neW4= 95784 -IFdhaXN0 95785 -IEVI 95786 -LW9wZXJhdG9y 95787 -IGRhcmtlbg== 95788 -IENvc21pYw== 95789 -IGdsYWNpZXJz 95790 -IA0NCg== 95791 -XVtf 95792 -Q29tcGFueUlk 95793 -IFJlY29uc3RydWN0aW9u 95794 -aXp6bGllcw== 95795 -IGzDrWRlcg== 95796 -IGNvbGxlZ2lhdGU= 95797 -IFBldHR5 95798 -T1VSTkFM 95799 -ZGVjb3JhdG9ycw== 95800 -cmFtcw== 95801 -KCgK 95802 -IEFzdHJvbm9teQ== 95803 -IHJpbw== 95804 -IEN5cmls 95805 -anVhbg== 95806 -IHJlaW5j 95807 -IFBpc3RvbnM= 95808 -IEJ1c3k= 95809 -cHRyb24= 95810 -IHBvbW9j 95811 -CVJUQ0s= 95812 -QnV5aW5n 95813 -Ly8qKgo= 95814 -IFdyYXBwZWQ= 95815 -IE1lZXI= 95816 -IGltYXA= 95817 -IGJlc3RpbW0= 95818 -IEFnaWxpdHk= 95819 -LlRvVGFibGU= 95820 -c3RpbmVuY2U= 95821 -XSkqKg== 95822 -IEF1dG9tYXRlZA== 95823 -ZHNw 95824 -IEdhcmxpYw== 95825 -aW9kZQ== 95826 -ZXhlbHM= 95827 -aW50cm9z 95828 -IGJlc3Rvd2Vk 95829 -KHZpc2libGU= 95830 -IGh5ZHJhdGVk 95831 -bm94aW91cw== 95832 -IEF1dGhlbnRpY2F0aW9uU2VydmljZQ== 95833 -IHNob3dNb2RhbA== 95834 -IGNvbXBvc2Vycw== 95835 -R0VORVJBTA== 95836 -Q1RT 95837 -IFNocg== 95838 -Y3JlYXQ= 95839 -IGNsb3NldHM= 95840 -IGdyb3VuZGluZw== 95841 -IENPTU1FTlRT 95842 -ICsj 95843 -IGdyb3VuZHdvcms= 95844 -KGluZGV4UGF0aA== 95845 -Z3JhdGlz 95846 -dXBwaWVz 95847 -IGt2bQ== 95848 -IGN1YWxlcw== 95849 -LkRlZXBFcXVhbA== 95850 -IGFsbG95cw== 95851 -LWJ1ZGdldA== 95852 -KF9fXw== 95853 -IGNvbmVjdGFy 95854 -LXJhZA== 95855 -IGl0Y2g= 95856 -bGFtcA== 95857 -LmdycA== 95858 -LWFkZG9ucw== 95859 -IHNlYWJvcm4= 95860 -IG5lZ2xpZ2VudA== 95861 -X0RldGFpbA== 95862 -IHNlcmVuZQ== 95863 -IGJhcnJhY2tz 95864 -IGJx 95865 -IFNlY3Q= 95866 -KGRhdG9z 95867 -IHRoZW1hdGlj 95868 -IHBvbGx1dGVk 95869 -CWFuaW1hdGlvbg== 95870 -SHVnaA== 95871 -RXhlY3V0YWJsZQ== 95872 -KCcvJylb 95873 -IGFwb3B0b3Npcw== 95874 -IGFiYnJldmlhdGVk 95875 -Zm9vbg== 95876 -UmFua2Vk 95877 -CWhpdA== 95878 -CQkgICAgICAgICAgICAgICAgICAgICAgIA== 95879 -Q29udGludW91cw== 95880 -IG1vdmVUbw== 95881 -REJPYmplY3Q= 95882 -IGNvbmNlaXZhYmxl 95883 -IEd3ZW4= 95884 -IMOhbGw= 95885 -X18oKQ== 95886 -IExhbmE= 95887 -IGVpbnplbA== 95888 -IHJlY291bnRz 95889 -eXN0ZW1z 95890 -b3dhbnk= 95891 -KTo/Pgo= 95892 -IEFrcm9u 95893 -b2xpbmk= 95894 -Q29ycA== 95895 -YXBocmFn 95896 -ICInLg== 95897 -IGNvbnZlbmVk 95898 -IC4uLi4KCg== 95899 -IGNhbGxlZQ== 95900 -IENsb3Zlcg== 95901 -LmRlc2NyaXB0b3I= 95902 -Lkl0ZW1TdGFjaw== 95903 -IHBlcnZlcnNl 95904 -X0NF 95905 -PUAi 95906 -LS0tDQo= 95907 -IGJldg== 95908 -c3VtYQ== 95909 -YWNjdW11bGF0b3I= 95910 -IGxpemFyZA== 95911 -INC+0Yc= 95912 -Z2V0RGVzY3JpcHRpb24= 95913 -IFNhcmFz 95914 -Lm5leHRTaWJsaW5n 95915 -IGVsYXN0aWNpdHk= 95916 -IGNoYWM= 95917 -bW92ZWQ= 95918 -X1RvcA== 95919 -dHJlcg== 95920 -KGRvd24= 95921 -ZWxlbXM= 95922 -b2JpbGk= 95923 -LnBvc3RNZXNzYWdl 95924 -ICjiiA== 95925 -Q3N2 95926 -IFlvc2VtaXRl 95927 -c3dlZXQ= 95928 -TUFUUklY 95929 -aWdyYXRlZA== 95930 -IGZvcmdpbmc= 95931 -IFBhZ2VTaXpl 95932 -dHJhbnNmb3Jtcw== 95933 -PVlFUw== 95934 -IGRpc2Nsb3Npbmc= 95935 -IFBlZGlhdHJpYw== 95936 -IERlYWRseQ== 95937 -UmVzb3VyY2VJZA== 95938 -LWJpbmFyeQ== 95939 -IFJvd2U= 95940 -IENhaXI= 95941 -X2V4dHJhY3Rpb24= 95942 -RGVjcmU= 95943 -IE9ic3Q= 95944 -cGxy 95945 -IFBoeXNpb2xvZ3k= 95946 -bXZj 95947 -aHRp 95948 -LlRl 95949 -IGV4dHJhdmFnYW50 95950 -IEFudGli 95951 -w7NzdA== 95952 -b3V0ZGly 95953 -IGNhcm5l 95954 -Vmlld1BhZ2Vy 95955 -IGltcGxhbnRlZA== 95956 -U2VhcmNoUGFyYW1z 95957 -w7xyZ2Vy 95958 -Y29uZGU= 95959 -YWNlbnRl 95960 -X0NVREE= 95961 -JHZhbA== 95962 -IldoaWxl 95963 -IHRlbXBMaXN0 95964 -IHN5bmFnb2d1ZQ== 95965 -Y21j 95966 -INGA0LDQsdC+0YLRiw== 95967 -IHNlem5hbQ== 95968 -IHNlc3N1YWxp 95969 -IGNhYmV6YQ== 95970 -ZXTDoA== 95971 -IGZhw6c= 95972 -Z2Vo 95973 -Y2VkZQ== 95974 -IlNvbWU= 95975 -Om9u 95976 -LWZvcm1lZA== 95977 -YnluYW1l 95978 -IOuwmO2ZmA== 95979 -IG5hw68= 95980 -IEFVRw== 95981 -IGVhc2Vk 95982 -XSl7 95983 -KHB0aHJlYWQ= 95984 -IGplZGVt 95985 -KGZpeHR1cmU= 95986 -IFBhcmw= 95987 -XX0pOwo= 95988 -IGV4cHVsc2lvbg== 95989 -IEluZXRBZGRyZXNz 95990 -IE1MUA== 95991 -LicpOw== 95992 -IG9ybw== 95993 -IFNldmlsbGE= 95994 -IGZvcm11bGFpcmU= 95995 -LXRlcnJvcmlzbQ== 95996 -L1dlYkFQSQ== 95997 -KmFuZ3N0cm9t 95998 -Y3Jhd2w= 95999 -X2xvYW4= 96000 -X0RJR0VTVA== 96001 -IEtub3h2aWxsZQ== 96002 -LmdjYQ== 96003 -IERpeQ== 96004 -bnRhZw== 96005 -YWJsZVZpZXdDb250cm9sbGVy 96006 -LkZlZWQ= 96007 -LXNoYXJlZA== 96008 -IGNvY2Np 96009 -X2ludml0ZQ== 96010 -IEJ1Y2tpbmdoYW0= 96011 -IEdsdXRlbg== 96012 -IGVuZGVtaWM= 96013 -UmFpc2Vk 96014 -IHF1ZXJ5SW50ZXJmYWNl 96015 -IG1hcnRpbg== 96016 -QuG6oW4= 96017 -IGhhcmU= 96018 -IGRlaW4= 96019 -cmFyaWFu 96020 -bXlmaWxl 96021 -IGFuZ3Vpc2g= 96022 -VGV4dG8= 96023 -IEJVRkY= 96024 -KGxu 96025 -bWFycw== 96026 -X3N1YnRpdGxl 96027 -X2dpZnQ= 96028 -IGJvbGRseQ== 96029 -IFNpbmd1bGFy 96030 -KExvZ0xldmVs 96031 -PEFydGljbGU= 96032 -L3N0YXRz 96033 -INC/0L7Qsg== 96034 -IGl0ZW5z 96035 -IGRlbm9taW5hdGlvbg== 96036 -LkRhdGFHcmlkVmlld1RyaVN0YXRl 96037 -X0xS 96038 -IER1Y2hlc3M= 96039 -CUJsb2Nr 96040 -dHJhY2Vy 96041 -LUNO 96042 -XEFwcERhdGE= 96043 -Lmxpc3Rz 96044 -KFJvdXRl 96045 -IEdPT0RNQU4= 96046 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCg== 96047 -IHRpbmhh 96048 -IGV2ZXJsYXN0aW5n 96049 -YURhdGE= 96050 -KGNvbXBhcmU= 96051 -IHJwdA== 96052 -XFBocA== 96053 -LkZJTEVT 96054 -IHNwYXJpbmc= 96055 -U2Nhcg== 96056 -INin2YTYqg== 96057 -IEJldGhsZWhlbQ== 96058 -IGJhY2twYWdl 96059 -c3BsaWNl 96060 -ZsO2cg== 96061 -QGR5bmFtaWM= 96062 -4bupYw== 96063 -7KY= 96064 -LnBhZ2luZw== 96065 -IEJlbG1vbnQ= 96066 -LkVYUA== 96067 -IGludGVybGU= 96068 -IENoZWNrbGlzdA== 96069 -IFVuaWNvcm4= 96070 -QkVTVA== 96071 -Z2V0UGxheWVy 96072 -LmFyZ3NvcnQ= 96073 -IHdpdGhTdHJpbmc= 96074 -IE1vZGVyYXRl 96075 -fSI+Cg== 96076 -LnNldEltYWdlQml0bWFw 96077 -IHRyZW5jaGVz 96078 -IGdlbmVyYXI= 96079 -IGZlcm1lbnRlZA== 96080 -IGRlanRpbmc= 96081 -Q3RybHM= 96082 -IGRpc2FncmVlcw== 96083 -UXVpZXQ= 96084 -KFNRTEV4Y2VwdGlvbg== 96085 -IFRlbnNvckZsb3c= 96086 -T05B 96087 -UG9ydGxhbmQ= 96088 -LlB0cg== 96089 -bGx4 96090 -YXN0b24= 96091 -Q2x1c3RlcnM= 96092 -IFVzdWFyaW9z 96093 -IGtoaQ== 96094 -IGdpYQ== 96095 -IERvbHBoaW4= 96096 -xZFz 96097 -IGx1ZGVy 96098 -IGRpc3Bvc2l0aXZv 96099 -IFZ5 96100 -b21wc29u 96101 -IO2VoA== 96102 -IGtjYWw= 96103 -IENhbGNpdW0= 96104 -U2VjdGlvbnNJbg== 96105 -IENhc2M= 96106 -IGdyYXR1aXRp 96107 -b3NvbWFs 96108 -IHVuZGVyY3V0 96109 -IENhaA== 96110 -OnBhcmFtcw== 96111 -IHJldHVyblVybA== 96112 -IEVyZQ== 96113 -w6lyYw== 96114 -IGludGw= 96115 -fS8jew== 96116 -IG91dHB1dFBhdGg= 96117 -IGZhbHNlaG9vZA== 96118 -IFVzZXJSb2xl 96119 -PEhhc2hNYXA= 96120 -IENyZWF0ZVVzZXI= 96121 -IENvd2JveQ== 96122 -CVVzZQ== 96123 -XSgK 96124 -IFNob3BpZnk= 96125 -Vmlld1N0YXRl 96126 -QWR2YW5jZQ== 96127 -LXRhbms= 96128 -IlQ= 96129 -IEplbnM= 96130 -PW9wdGlvbnM= 96131 -KCIuLg== 96132 -Lm1pbWU= 96133 -IENSVA== 96134 -IGjDpHR0ZQ== 96135 -KHNv 96136 -LlVOS05PV04= 96137 -IGRhcsO8YmVy 96138 -IENPVkVS 96139 -R2Vt 96140 -Q3Jv 96141 -X1JFQ1Y= 96142 -X2hpZXJhcmNoeQ== 96143 -Q2hvb3Npbmc= 96144 -SkVYRUM= 96145 -IGRvcnNhbA== 96146 -KyI8 96147 -IE5leQ== 96148 -V29tYW4= 96149 -QmV6aWVy 96150 -IHJpZ3M= 96151 -IG9udHZhbmc= 96152 -77yM5YiZ 96153 -IEdhdXQ= 96154 -Y21i 96155 -TmhhcA== 96156 -IG1vbm9j 96157 -IGVuZXJnaWE= 96158 -b2JzZXJ2ZU9u 96159 -c3Rha2Vz 96160 -LSot 96161 -IE5hY2s= 96162 -fX0iCg== 96163 -ZXJ2YXM= 96164 -IEhpbmRlcmVkUm90b3I= 96165 -QWRqYWNlbnQ= 96166 -IEludGVybmFjaW9uYWw= 96167 -CWFyZWE= 96168 -IPCflA== 96169 -IHNwYXJrbGU= 96170 -KCkuXw== 96171 -LmlkZWE= 96172 -IHV0cmVjaHQ= 96173 -IG1hcHBlZEJ5 96174 -IENvbG8= 96175 -CVRS 96176 -UG9zdGVy 96177 -IGNvbWJhdGluZw== 96178 -IFllbGxvd3N0b25l 96179 -aWVycmV6 96180 -YWNjdA== 96181 -IHPDoWNo 96182 -Lk5ld3M= 96183 -IGZpZWxkVmFsdWU= 96184 -IGNheg== 96185 -IEZyZWVt 96186 -CQkKCQo= 96187 -IHVzdXI= 96188 -IHNvbGE= 96189 -IGN1bWJlcnNvbWU= 96190 -IGNhdGFwdWx0 96191 -Ii4v 96192 -IEV4ZWN1dG9ycw== 96193 -IEFtZXM= 96194 -ICc8JT0= 96195 -ZmlsbG5h 96196 -LOKAlA== 96197 -OlNldFRleHQ= 96198 -LWNhdGVnb3JpZXM= 96199 -LWFyY2hpdmU= 96200 -IFBvbGx1dGlvbg== 96201 -Lk9m 96202 -4oCcQXQ= 96203 -X0NIQVJTRVQ= 96204 -KENvbHVtbg== 96205 -4oCZKQ== 96206 -IHVubWlzdGFr 96207 -IGVhcm0= 96208 -IFBsYXRmb3Jtcw== 96209 -IE1vbWVudHVt 96210 -VmVjdG9yaXplcg== 96211 -cmF3ZXI= 96212 -KHBhc3Nwb3J0 96213 -KHBsYW5l 96214 -IHJlcHJlc2VudGE= 96215 -IHB1YmtleQ== 96216 -IEphaW4= 96217 -IG1lbm5lcw== 96218 -IGluc3RhbnRhbmVvdXM= 96219 -IGV0aGVycw== 96220 -IG5lc3Rz 96221 -IFBhdHRvbg== 96222 -IEhBQ0s= 96223 -cGFja2luZw== 96224 -SVNlcnZpY2U= 96225 -IHJvY2tlcg== 96226 -IGZpY2E= 96227 -IEdsYWRpYXRvcg== 96228 -IFVQQw== 96229 -IExvd2VsbA== 96230 -YmVhcmVy 96231 -IHZpcGVy 96232 -X2dsb2I= 96233 -IG1hc2hlZA== 96234 -IGhhaXJzdHlsZQ== 96235 -IHVuZGVybWluZXM= 96236 -cmVzdGF1cmFudHM= 96237 -IHJlYWN0aW9uYXJ5 96238 -IGJpbGxpZw== 96239 -fSIpOw0K 96240 -IHZpc3Rhcw== 96241 -IG9wZW5kaXI= 96242 -CWxhYmVscw== 96243 -YWxsaXM= 96244 -IFdvbGZm 96245 -IENQQw== 96246 -IHJhaWx3YXlz 96247 -IFZhdWdoYW4= 96248 -IEFza2luZw== 96249 -Y2Fp 96250 -IEdu 96251 -X1BST0Y= 96252 -LVNlcA== 96253 -LmN1cnZl 96254 -TXVsdGlwbHk= 96255 -0YDQsNC90LjRhg== 96256 -IG1lZXR1cA== 96257 -Z2V0RGI= 96258 -KEdVSQ== 96259 -IHJlaW1idXJzZQ== 96260 -OnJlc3VsdA== 96261 -VHVtYmxy 96262 -LkNsb3NlZA== 96263 -IGNvbmZvcm1z 96264 -IEhvaw== 96265 -aWVkYWRl 96266 -TmV3TGFiZWw= 96267 -IG5hdkN0cmw= 96268 -RG9jdG9ycw== 96269 -IOyViA== 96270 -IGJvdXRz 96271 -IGlzYw== 96272 -Lyc7Cgo= 96273 -dWhs 96274 -LlVp 96275 -LXNhbWE= 96276 -IENhbm9uaWNhbA== 96277 -IG1ldGljdWxvdXM= 96278 -IGdyb3Rlcw== 96279 -IC8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8= 96280 -ZXRlcw== 96281 -IGxhbmd1ZQ== 96282 -IGZDaGFpbg== 96283 -IFR5cGVmYWNl 96284 -IEJyaWdoYW0= 96285 -aWFyZQ== 96286 -J8OpdGFpdA== 96287 -IEVGRg== 96288 -IGRlc3Ryb3llcg== 96289 -X21hdHJpY2Vz 96290 -TsO6bWVybw== 96291 -Y2FsbGFibGU= 96292 -X3BlcmlvZHM= 96293 -c3RydWs= 96294 -bWFq 96295 -LnJs 96296 -LmxpZnQ= 96297 -2YrZhA== 96298 -w5A= 96299 -UmV0VmFs 96300 -RGVudmVy 96301 -IFRyaWJ1dGU= 96302 -a2l5ZQ== 96303 -emV3 96304 -IFNwYXJl 96305 -IGxldWtlbWlh 96306 -IHdhaXRyZXNz 96307 -IHBsdXTDtHQ= 96308 -QWxpYXNlcw== 96309 -IExvY2F0ZQ== 96310 -5rY= 96311 -SWRlbnRpZmljYXRpb24= 96312 -LnRlbA== 96313 -LWRheXM= 96314 -dGVycml0 96315 -aW1idXM= 96316 -IEJ1dHRlcktuaWZl 96317 -64K0 96318 -cnVwdGN5 96319 -IEdyYWRlcw== 96320 -IHVuZGVyc2lkZQ== 96321 -IGhhcmRzaGlwcw== 96322 -dW5laQ== 96323 -LWNvbnRhaW5lZA== 96324 -IFsnLg== 96325 -T2Jzb2xldGU= 96326 -LlJldHJvZml0 96327 -IHVyYW51cw== 96328 -X3JnYmE= 96329 -IHJhcGVz 96330 -IEthcmU= 96331 -W+KApl0= 96332 -IEZpbmNo 96333 -LmJ1bmlmdUZsYXRCdXR0b24= 96334 -cXVpc2Fy 96335 -IE51cnNlcw== 96336 -ZWdhZGU= 96337 -IGhu 96338 -RXhjbHVkZQ== 96339 -IHN0b2NoYXN0aWM= 96340 -IHNvdHRv 96341 -IFBlbmFsdHk= 96342 -IHNvbnN0 96343 -IHJvc2E= 96344 -X0ZpbmQ= 96345 -IEludmFsaWRhdGU= 96346 -TGlzdEl0ZW1JY29u 96347 -JywNDQo= 96348 -X3BkdQ== 96349 -IE1lYWxz 96350 -YWrEhWM= 96351 -IE9vcHM= 96352 -IE5vdGljZXM= 96353 -IGRlcml2YXRpb24= 96354 -W10NCg== 96355 -6Lqr 96356 -eXN0ZXJ5 96357 -X2ZpdmU= 96358 -RWFybg== 96359 -PWV2ZW50 96360 -IG9ncg== 96361 -LVJFQUw= 96362 -IExpcHM= 96363 -c2VsZWN0b3Jz 96364 -YWRpZXI= 96365 -IHNldEJhY2tncm91bmRJbWFnZQ== 96366 -KHRoaW5n 96367 -IHNvZnRiYWxs 96368 -XHhhYQ== 96369 -KGlkZW50 96370 -IEp1cnk= 96371 -IFZveWFnZQ== 96372 -IFRBcnJheQ== 96373 -KFBhaW50 96374 -V2FybQ== 96375 -RVhURVJOQUw= 96376 -YXN1 96377 -ICghKCg= 96378 -LkZFVENI 96379 -IHNraXJt 96380 -T1JFRA== 96381 -Y2FuY2VsbGVk 96382 -aXR0ZWw= 96383 -IHNlZWR1 96384 -bGljaGVz 96385 -b2hv 96386 -LHJldGFpbg== 96387 -KFdlYkRyaXZlcg== 96388 -aXB0YWJsZXM= 96389 -RVJJQ0E= 96390 -IGNsZWFubGluZXNz 96391 -ZWxsb3dvcmxk 96392 -IGNvaGVzaW9u 96393 -Z2lzdA== 96394 -XS4n 96395 -ZXJnaW5n 96396 -IGlzcA== 96397 -Lm9mZnNldFRvcA== 96398 -KGZhY3Rvcg== 96399 -dW5pdmVyc2Fs 96400 -IFBsYXliYWNr 96401 -IEJ5dGVTdHJpbmc= 96402 -IGRhbW5pbmc= 96403 -IFNTUg== 96404 -YWN1cw== 96405 -IFN0YXRlbg== 96406 -IOWVhuWTgQ== 96407 -IFBlZQ== 96408 -IFNhbXBsaW5n 96409 -YXRvcmlh 96410 -c3RhcnRJbmRleA== 96411 -5ZCr 96412 -IOy0iOq4sA== 96413 -IE9saXZlaXJh 96414 -IEZsYWtl 96415 -Ym9vbQ== 96416 -X01TSw== 96417 -IEZhY2luZw== 96418 -b3JnaGluaQ== 96419 -Zm9vZHM= 96420 -VHJlZVdpZGdldEl0ZW0= 96421 -IEhBTEY= 96422 -IiIiKQo= 96423 -IENIQVBURVI= 96424 -IEV2ZWx5bg== 96425 -Pis= 96426 -IEhvcm5ldHM= 96427 -d29rZQ== 96428 -IC9b 96429 -YXRob2xpYw== 96430 -LnNlZ21lbnRz 96431 -Lm5hdmlnYXRlQnlVcmw= 96432 -IE1hbnVz 96433 -IHBlcHRpZGVz 96434 -IGZsZWV0aW5n 96435 -IEFUVg== 96436 -IFNoaWI= 96437 -SW50QXJyYXk= 96438 -IG1veg== 96439 -cHJvYmxlbXM= 96440 -b2duZQ== 96441 -Lk90aGVy 96442 -QWRtaW5pc3RyYXRpb24= 96443 -JSUqLw== 96444 -Il09PQ== 96445 -IEFuZHJlcw== 96446 -QWRh 96447 -aGludHM= 96448 -XCIiOwo= 96449 -KHBuZw== 96450 -IOqwgOuKpQ== 96451 -44OK 96452 -cmVqZWN0ZWQ= 96453 -IG1vdmVycw== 96454 -546H 96455 -IHBhcmVudGhlc2lz 96456 -KGFzc2lnbnM= 96457 -RWxpdGU= 96458 -UmVtaW5kZXI= 96459 -IHN1ZmZlcmVycw== 96460 -IFJlc291cmNlQnVuZGxl 96461 -dGhhZw== 96462 -PicNCg== 96463 -YW50aW5v 96464 -UGVyaXBo 96465 -IFNoYXJk 96466 -Q2hhcnREYXRh 96467 -KGpq 96468 -IG9zdGF0 96469 -aHVnZQ== 96470 -LWF1dGhvcmVk 96471 -LmNp 96472 -IHB5bXlzcWw= 96473 -IGxpbmVycw== 96474 -IEFUUw== 96475 -Pkxhc3Q= 96476 -KSIpCgo= 96477 -IGdldHBpZA== 96478 -R2V0U2l6ZQ== 96479 -IGV4dG9ydGlvbg== 96480 -W2Zsb2F0 96481 -IEVJTkE= 96482 -L0Jhc2U= 96483 -LnNldE9uQWN0aW9u 96484 -0L7Qu9GP 96485 -IEdsYWNpZXI= 96486 -X2F6 96487 -IHRyYW5zcG9ydGU= 96488 -IFNtcw== 96489 -dGh1bWJz 96490 -IHRyZWFzdXJlcg== 96491 -IG16 96492 -aXN0aWs= 96493 -UkVESUVOVA== 96494 -IGlzaQ== 96495 -X3N0dWZm 96496 -UE9TSVRPUlk= 96497 -c3RhcnRkYXRl 96498 -IFppbmM= 96499 -5rG9 96500 -IGthaw== 96501 -IGVyZmFocmVu 96502 -X0NPTUJP 96503 -IHVjd29yZHM= 96504 -LlBheQ== 96505 -IGtpbmdkb21z 96506 -IGV4Y2VsZW50ZQ== 96507 -aWduaXRl 96508 -X3ZhcmlhdGlvbg== 96509 -IG5hdmVnYWRvcg== 96510 -5LiT 96511 -dmlld0NvbnRyb2xsZXI= 96512 -cmlyZQ== 96513 -SG9uZXN0bHk= 96514 -Q2FzY2FkZQ== 96515 -ZXRyYWlu 96516 -QXJnZW50aW5h 96517 -Y3E= 96518 -IE1hcmlhbg== 96519 -L2Fy 96520 -IGludGVyZXNzZQ== 96521 -dXJhaGFu 96522 -KFBD 96523 -IGZyaXZvbA== 96524 -IFRydXN0ZWQ= 96525 -KElDb25maWd1cmF0aW9u 96526 -IFJpaGFubmE= 96527 -ZW5kb3ph 96528 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg 96529 -IHByb2NsYW1hdGlvbg== 96530 -IHByZWRvbWluYW50 96531 -IGNvbnN0cw== 96532 -LW5lY2s= 96533 -V29sZg== 96534 -LmNoZWNrYm94 96535 -IHN0YW56YQ== 96536 -IGVudGVuZGVy 96537 -Ly8o 96538 -SGFuZHM= 96539 -IGJpbGxlZGVy 96540 -IFRvc2hpYmE= 96541 -YWJiaXg= 96542 -RU5DSUVT 96543 -IGppbQ== 96544 -UFVS 96545 -Lmxlc3Nvbg== 96546 -IGJlcnRo 96547 -bGFyxLFu 96548 -Qmxv 96549 -CWV4dA== 96550 -ZWVs 96551 -IGRlbWFzaQ== 96552 -IGNvbG9uaXphdGlvbg== 96553 -L2Rpc2M= 96554 -77yP 96555 -Q2VydGFpbmx5 96556 -566h55CG5ZGY 96557 -IGpvZ2Fkb3I= 96558 -dcOp 96559 -Q29sdW1uc01vZGU= 96560 -IEpW 96561 -IEluc3RpdHV0 96562 -X3NwZWN0cnVt 96563 -LmRlbnNl 96564 -IFNob3J0Y3V0 96565 -IHNlYnVhaA== 96566 -IGZsYXNoeQ== 96567 -UmVnYXJkcw== 96568 -IHNoYXJwZXI= 96569 -Y2FuY2VsbGF0aW9uVG9rZW4= 96570 -X2RldGFsbGU= 96571 -IFNjYXJsZXR0 96572 -INC80LDRgg== 96573 -IG5lZ29jaW8= 96574 -4LiW 96575 -IEpX 96576 -d2ViZHJpdmVy 96577 -LndhbGw= 96578 -IHhhbWFyaW4= 96579 -b3BhcXVl 96580 -LkFkZFBhcmFtZXRlcg== 96581 -KENvbnRyb2xsZXI= 96582 -LWFib3J0aW9u 96583 -X0ZVTkNUSU9OUw== 96584 -Q3VzdG9tZXJJZA== 96585 -IHZlbmly 96586 -IEJ1c3Rlcg== 96587 -X3ByZWRpY3RlZA== 96588 -L3J1bGVz 96589 -LU1ldGhvZHM= 96590 -IGdkemll 96591 -Il0nKTsK 96592 -IFB4 96593 -Q09OUw== 96594 -LlNsaWNl 96595 -IHJldmFtcGVk 96596 -IFRhYmxlVmlldw== 96597 -IGRpY2tz 96598 -IO2YuOy2nA== 96599 -IEF1eGlsaWFyeQ== 96600 -T3BlcmE= 96601 -L3Jj 96602 -IHVudGhpbmthYmxl 96603 -IGRlZHVjdGVk 96604 -bHo= 96605 -IExhZ2U= 96606 -IFJvd2xpbmc= 96607 -cHJvdmVk 96608 -T2ZmZXJz 96609 -LHNldA== 96610 -UkdCTw== 96611 -IEZV 96612 -IENlbnRPUw== 96613 -b3pv 96614 -IFRyb2phbg== 96615 -IG1hw7FhbmE= 96616 -IC8vPQ== 96617 -Kio6 96618 -IHtcCg== 96619 -IEJvd2Vu 96620 -S25vd2luZw== 96621 -IOW6 96622 -PS09LT0tPS09LT0tPS09LQ== 96623 -IGViZW5mYWxscw== 96624 -XT17Cg== 96625 -Qk1J 96626 -KCk7KQ== 96627 -KHBlcm1pc3Npb24= 96628 -QW5kZXJzb24= 96629 -IGRlZ3JhZGU= 96630 -U29hcA== 96631 -dcWf 96632 -IFB1cHB5 96633 -IEV0aGlvcGlhbg== 96634 -IFRFU1RJTkc= 96635 -ZW5zZXg= 96636 -IGRyZXNzZXI= 96637 -IENob3Jl 96638 -VW5oYW5kbGVk 96639 -QXNzb2NpYXRl 96640 -LmFkZGl0aW9uYWw= 96641 -IGRpZmbDqXJlbnRlcw== 96642 -aXNxdWU= 96643 -IG5lY2Vzc8Ohcmlv 96644 -IGdlbmVyaWNz 96645 -KHBm 96646 -IFxg 96647 -IE5lYXJieQ== 96648 -YXBvcmF0aW9u 96649 -IFRoZW1lRGF0YQ== 96650 -V2lGaQ== 96651 -LlJlYWw= 96652 -YWN5ag== 96653 -TGl2 96654 -IHBzeWNob2xvZ2ljYWxseQ== 96655 -bWV0aG9kUG9pbnRlclR5cGU= 96656 -IE5pa29s 96657 -IERlZGljYXRlZA== 96658 -X1BPUlRT 96659 -IEphZQ== 96660 -TlNBdHRyaWJ1dGVkU3RyaW5n 96661 -IGFtYmFzc2Fkb3Jz 96662 -IEhhbmRsZXJz 96663 -IEFuYXQ= 96664 -IHZvY2FsaXN0 96665 -IHJhcg== 96666 -IGRldnVlbHZl 96667 -Lmdz 96668 -IHhjYg== 96669 -IHN1Ym1vZHVsZQ== 96670 -IEFTU0lHTg== 96671 -dXJlZW4= 96672 -IGNsYXNlcw== 96673 -ZW1vdGg= 96674 -X0NOVEw= 96675 -X2p3dA== 96676 -IOuniA== 96677 -IG91dHBvc3Q= 96678 -IEluYm94 96679 -CWZsZXg= 96680 -IEdyb2Nlcnk= 96681 -SUxJTkU= 96682 -Lm1vYg== 96683 -IENvbnN0cg== 96684 -XT1d 96685 -KHdhbGxldA== 96686 -IHNlZGU= 96687 -ZmFs 96688 -IGltcGFzcw== 96689 -PXtbJw== 96690 -IHVuZm9yZQ== 96691 -ZnVzZQ== 96692 -X0xlYW4= 96693 -IGF2YWxhbmNoZQ== 96694 -PXJhbmQ= 96695 -IGFkdWx0ZXJ5 96696 -IEdlZQ== 96697 -CUlucHV0U3RyZWFt 96698 -IGNhYmVs 96699 -X01PVU5U 96700 -IG5vdGljaWFz 96701 -IFJhdW0= 96702 -IGJ5dGVhcnJheQ== 96703 -IG9uSGlkZQ== 96704 -ICkuCg== 96705 -JGluc3RhbmNl 96706 -IGRpZFNlbGVjdFJvd0F0SW5kZXhQYXRo 96707 -YWNhbQ== 96708 -LWNvbGxlY3Rpb24= 96709 -IHVwaGU= 96710 -UG90ZW50aWFs 96711 -IFNEUw== 96712 -X2FwcHJvdmFs 96713 -RGFtbg== 96714 -OmNvbnZlcnQ= 96715 -IE1vZGlmaWNhdGlvbnM= 96716 -IOyYiA== 96717 -IHVuYWI= 96718 -IHNjcm9sbGVk 96719 -KyIpOwo= 96720 -IGdhdWNoZQ== 96721 -IEhPTA== 96722 -YW50YW5hbW8= 96723 -IGNvbHVtbkhlYWRlcg== 96724 -CVpFUEhJUg== 96725 -emFj 96726 -IG91dGluZ3M= 96727 -IGFwcGxhdWRlZA== 96728 -aG9yaWE= 96729 -bW9keA== 96730 -IG1pbGxlbm5pYQ== 96731 -Jm0= 96732 -Lkpzb25JZ25vcmU= 96733 -IHBpb25lZXJlZA== 96734 -IENhdnM= 96735 -CWpz 96736 -ZGVwYXJ0dXJlZGF5 96737 -X2ti 96738 -LlBhdGllbnQ= 96739 -IHBldGFscw== 96740 -cG9ydHJhaXQ= 96741 -In19Cg== 96742 -SG9tZUFzVXBFbmFibGVk 96743 -LnByZXR0eQ== 96744 -LGNsanM= 96745 -IG1lZGlvcw== 96746 -aGFzaGVk 96747 -ZW1vZGVs 96748 -IE1vam8= 96749 -LmZyb21SR0JP 96750 -LXBl 96751 -IGludGltYXRlbHk= 96752 -IGVsZ2c= 96753 -W107DQo= 96754 -L09ic2VydmFibGU= 96755 -IG9iZWRpZW50 96756 -IEphbWFs 96757 -UmVxdWlyZWRNaXhpbg== 96758 -IExpc3RWaWV3SXRlbQ== 96759 -CXBsYWNlaG9sZGVy 96760 -X3RyYW5zYWtzaQ== 96761 -PFNlcnZpY2U= 96762 -IGVuc3VlZA== 96763 -IFJpY2Fu 96764 -U2FnYQ== 96765 -QVVESU8= 96766 -IGpt 96767 -LXNhbGVz 96768 -LW11bHRp 96769 -JSI7Cg== 96770 -IGNsYXNzaWZpY2F0aW9ucw== 96771 -IHTDo28= 96772 -Q29hbA== 96773 -OycpOwo= 96774 -IGRlbGlnaHRz 96775 -X2h6 96776 -X2JvbGQ= 96777 -REVQRU5E 96778 -INCh0L7Qt9C0 96779 -YXRlZQ== 96780 -X3N1Ym5ldA== 96781 -IFRvd25zZW5k 96782 -IENhc3RpbGxv 96783 -IHBydA== 96784 -JC8p 96785 -IGZpbGli 96786 -KCcvJylbLQ== 96787 -IHVwaG9sc3Rlcnk= 96788 -IGNvbXBvbmVudGU= 96789 -IFhG 96790 -LlJldmVyc2U= 96791 -X3R1bm5lbA== 96792 -SW1tZWRpYXRlbHk= 96793 -LW1vdmU= 96794 -IGFsaXN0 96795 -V1ND 96796 -c3RydWN0dXJhbA== 96797 -aXN0b3JpY2Fs 96798 -VGFuZ2dhbA== 96799 -IENPVVJU 96800 -IG9ic2N1cmVk 96801 -IGxhbmRzbGlkZQ== 96802 -IGJlZHNpZGU= 96803 -IGJhcmFuZw== 96804 -LWVsZWN0ZWQ= 96805 -IGNlcmFtaWNz 96806 -LS0qLwo= 96807 -IFdhbm5h 96808 -RHlu 96809 -IHZlcnNjaGllZGVuZQ== 96810 -IGluZHVjaW5n 96811 -IGZsdXRl 96812 -LkFwcGVuZFRleHQ= 96813 -IFp1Yg== 96814 -IFB1bGl0emVy 96815 -OmJvdGg= 96816 -Lm1heExlbmd0aA== 96817 -LlByb3BlcnR5VHlwZQ== 96818 -YXd5 96819 -aXRlbU5hbWU= 96820 -IE5hcnJhdGl2ZQ== 96821 -cmV2b2x1dGlvbg== 96822 -IGhhbHRlbg== 96823 -IEVycm9yUmVzcG9uc2U= 96824 -Z2F0aGVy 96825 -L3V0aWxpdHk= 96826 -Oicn 96827 -IEtlZQ== 96828 -IE9seW1waWE= 96829 -Q2xpbmljYWw= 96830 -OmdyZWVu 96831 -IFBsZXg= 96832 -IEtlbnNpbmd0b24= 96833 -IFBob25ldGlj 96834 -IGRpc3RyaWJ1dGVz 96835 -X2V4ZW1wdA== 96836 -V2F0Y2hpbmc= 96837 -Lk1pc2M= 96838 -IGRvbWFpbmU= 96839 -OiIu 96840 -44OV44I= 96841 -X01PRFVMRVM= 96842 -IGhhYmxhcg== 96843 -IExhb3M= 96844 -LnNldFRleHRTaXpl 96845 -LnBhdXNlZA== 96846 -X1RX 96847 -IG92ZXJ3aGVsbQ== 96848 -IGhlbWF0 96849 -THVja2lseQ== 96850 -IFNFTlQ= 96851 -IEludmVzdGlnYXRvcnM= 96852 -Pih7 96853 -KGZvdXQ= 96854 -IEFVWA== 96855 -LnJhd1F1ZXJ5 96856 -LXN0cm9uZw== 96857 -IHJlc2VtYmxlZA== 96858 -IFNoYWZ0 96859 -IFhJSUk= 96860 -c3VnZ2VzdA== 96861 -IHNpbmdhcG9yZQ== 96862 -X2FiaWxpdHk= 96863 -JGs= 96864 -CWlOZEV4 96865 -XEltYWdl 96866 -Q2FkYXN0cm8= 96867 -LnBpdm90 96868 -IG1hbnBvd2Vy 96869 -X2F0dHM= 96870 -LnNldEZpbGw= 96871 -ZXdvcmxk 96872 -Y29uc3Rz 96873 -R2V0V2lkdGg= 96874 -IGdyYXR1aXRh 96875 -IFBldHI= 96876 -LWFuc3dlcg== 96877 -IEhlbWlzcGhlcmU= 96878 -IENhag== 96879 -IFRyYWRlcw== 96880 -xIdp 96881 -IEZyZWRkeQ== 96882 -T25DaGFuZ2U= 96883 -IHBvcm5vZ3JhZmlh 96884 -IFNVTU1BUlk= 96885 -X21lYXM= 96886 -IERSSVZF 96887 -IENyZWU= 96888 -X21hbGU= 96889 -IHN1aw== 96890 -IG1hbmV1dmVycw== 96891 -c2V0VmlzaWJpbGl0eQ== 96892 -YWxsaQ== 96893 -IGRpc2NyZXRpb25hcnk= 96894 -cmVnYXRpb24= 96895 -WVNUSUNL 96896 -OmhyZWY= 96897 -IHRhcmFm 96898 -IGNodQ== 96899 -IEBb 96900 -RW5vdWdo 96901 -LlRyYW5zZmVy 96902 -SWZOZWVkZWQ= 96903 -OildKQ== 96904 -CSAgICAgICAgICAgICAg 96905 -W2F4aXM= 96906 -VHJhbnNsYXRpb25z 96907 -LnNlcnZlcnM= 96908 -IEtFRVA= 96909 -JywpCg== 96910 -c3BvbnNvcg== 96911 -YXJjaGl2ZXM= 96912 -LlVsdHJhV2lu 96913 -IEhvbm91cg== 96914 -J10pKTs= 96915 -IGluZWxpZ2libGU= 96916 -IEFudHdvcnRlbg== 96917 -IEFwcGxpY2F0aW9uRXhjZXB0aW9u 96918 -IGNhdGVnb3JpZQ== 96919 -IFdFSUdIVA== 96920 -IEJ1bmR5 96921 -IFBJWEVM 96922 -IGR1a2U= 96923 -VG93ZXI= 96924 -U2NvdGxhbmQ= 96925 -IHJlZmVyZWVz 96926 -IEFzc2VtYmx5VHJhZGVtYXJr 96927 -CXN0YXJ0QWN0aXZpdHk= 96928 -Lk9uZVRvT25l 96929 -IEF1c3dhaGw= 96930 -IHN0cmVuZ3RoZW5z 96931 -LlF1aXQ= 96932 -IFVSTFJlcXVlc3Q= 96933 -ZWVj 96934 -IHJlZ2lzdHJhemlvbmU= 96935 -IGhvc2Vz 96936 -QWN0dWFsaXphcg== 96937 -L2FycmF5 96938 -IGNvbnN0cnVjdGlvbnM= 96939 -Y2Nk 96940 -IEZpbGVOb3RGb3VuZEVycm9y 96941 -VGjDqm0= 96942 -KHJlc3VsdGFkbw== 96943 -IFNFUklFUw== 96944 -U3BlYWs= 96945 -X0FIQg== 96946 -QmxvY2tlZA== 96947 -LWZvbnRhd2Vzb21l 96948 -Ol0p 96949 -b2JibGU= 96950 -KGxpbmtz 96951 -IENhdGFsb25pYQ== 96952 -R2VW 96953 -LkRhdGVGb3JtYXQ= 96954 -IGZsZWE= 96955 -LmVm 96956 -IHNvbGljaXR1ZA== 96957 -IERZ 96958 -Y29kZWdlbg== 96959 -eXRoZQ== 96960 -IGVwb2xs 96961 -X1RE 96962 -IGFmZmlybWF0aW9u 96963 -X2Zh 96964 -SVNUQQ== 96965 -IEVhdG9u 96966 -Y3JlYXRlUXVlcnk= 96967 -IGxvZ2lzdGljYWw= 96968 -IFJheWNhc3RIaXQ= 96969 -IGNhdWxpZmxvd2Vy 96970 -IHVsY2Vy 96971 -LkFscGhh 96972 -aW5rZQ== 96973 -Wy4u 96974 -RVhBTVBMRQ== 96975 -LXdhZ2U= 96976 -IHN0YXRp 96977 -ZWN0aXZl 96978 -LmdldE1pbg== 96979 -IFNVQkpFQ1Q= 96980 -IEF1ZGlvTWFuYWdlcg== 96981 -enphcmVsbGE= 96982 -IFNlbGVjdExpc3RJdGVt 96983 -ICQNCg== 96984 -IG9oaW8= 96985 -IFRhaG9l 96986 -IGtXaA== 96987 -cXVlcnlTdHJpbmc= 96988 -IGRlcGFydGFtZW50bw== 96989 -PWFkbWlu 96990 -IHdvcmtzdGF0aW9u 96991 -KSsrOwo= 96992 -SGVhZGVySW5TZWN0aW9u 96993 -IFRyaXVtcGg= 96994 -Q2hhcmxvdHRl 96995 -IFNNQQ== 96996 -Q8OzbW8= 96997 -IHZlcm0= 96998 -IHRoZWFubw== 96999 -Ymdjb2xvcg== 97000 -XCIiLAo= 97001 -IFJlbWluZGVy 97002 -QmlsbHk= 97003 -b3JhbFR5cGU= 97004 -Z2ViZXI= 97005 -KGNsb25l 97006 -IEt1dA== 97007 -Lz4u 97008 -QXBvbGxv 97009 -IHNobA== 97010 -Wkg= 97011 -VGh1bmRlcg== 97012 -IGdpZnM= 97013 -X2tlbGFz 97014 -IFJvdGhz 97015 -IH0o 97016 -IEJyb2FkY29t 97017 -IERlcHRocw== 97018 -CUlOTkVS 97019 -cGFyY2Vs 97020 -IGVqZXJjaWNpbw== 97021 -IGluZGVwZW5kZW50cw== 97022 -aWxsb3c= 97023 -ZXhlY3V0YWJsZQ== 97024 -RXZlbnRv 97025 -IHpvc3Q= 97026 -IEhNQUM= 97027 -W0RsbEltcG9ydA== 97028 -YWxsZXM= 97029 -X2Rlcml2YXRpdmU= 97030 -QXBpS2V5 97031 -IHN0ZXBwZXI= 97032 -PXBsdA== 97033 -Z2V0SW5kZXg= 97034 -IHZhbGV1cnM= 97035 -UG9saXRpY3M= 97036 -IElEWA== 97037 -IFVzYQ== 97038 -IExUQw== 97039 -Lm1pbkxlbmd0aA== 97040 -c3Rybw== 97041 -X05D 97042 -IHN0YWduYW50 97043 -IG1vbnRhZ2U= 97044 -IGJsb3VzZQ== 97045 -ZWxpZ2U= 97046 -IHR1cnF1b2lzZQ== 97047 -IFN1cGVybg== 97048 -5q2z 97049 -dmFyYQ== 97050 -TmV3SXRlbQ== 97051 -X0VYVEVOREVE 97052 -IHdvb2R3b3JraW5n 97053 -IEVwaXNjb3BhbA== 97054 -LnBhaXI= 97055 -LlVzZXJJbmZv 97056 -IGRpcmVudA== 97057 -L3RjcA== 97058 -IGZyYXVnaHQ= 97059 -U2xhdmU= 97060 -LmdldExhdGl0dWRl 97061 -IFRvb2xib3g= 97062 -IGVhcm5lcnM= 97063 -IEhPVVI= 97064 -0LDQu9Cw 97065 -cG9zYWJsZXM= 97066 -Y29uZGl0aW9uYWxseQ== 97067 -X3h4 97068 -IGxhbsOn 97069 -KHJw 97070 -Q2hh 97071 -IGluY2Fybg== 97072 -LkRhbw== 97073 -Li8o 97074 -2KfZgQ== 97075 -VGQ= 97076 -Q0VG 97077 -L3JhbmQ= 97078 -LlZpcnR1YWw= 97079 -IGRiSGVscGVy 97080 -YW1pbmVz 97081 -IGx6 97082 -IHN0b3M= 97083 -IEF0a2lucw== 97084 -X0RE 97085 -aXRvcmlv 97086 -IG1pbmltaXNl 97087 -aGlwc3Rlcg== 97088 -KHsuLi4= 97089 -X1NSVg== 97090 -W2ZyYW1l 97091 -IFJva3U= 97092 -R1JQ 97093 -IGJhcmJlcg== 97094 -LkZlY2hh 97095 -IOuwnA== 97096 -IGdyYW51bGFyaXR5 97097 -IFNheWluZw== 97098 -X2xpa2VsaWhvb2Q= 97099 -LmJhckRvY2tDb250cm9s 97100 -IGZyb250bGluZQ== 97101 -IFdoYWxl 97102 -IHNtZWxsaW5n 97103 -IENvbnRyaWJ1dGlvbnM= 97104 -aXZhbnQ= 97105 -IGNyaXBwbGluZw== 97106 -cHJlbG9hZA== 97107 -IEhlcnJlcmE= 97108 -X1dBVENI 97109 -LWV0 97110 -OmV4cHI= 97111 -aW52ZXN0bWVudA== 97112 -ZWRlcmF0aW9u 97113 -X21nbXQ= 97114 -IGhvb3Bz 97115 -bW9ua2V5 97116 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK 97117 -aW50ZXJzZWN0 97118 -IGNyaW1zb24= 97119 -IHN1b2k= 97120 -IFtdOgo= 97121 -WE9iamVjdA== 97122 -U0ZNTA== 97123 -RVFVQUw= 97124 -KCd+ 97125 -Y2VudHJvaWQ= 97126 -CXJlc3RvcmU= 97127 -IHByZW5hdGFs 97128 -IE1pc3RyZXNz 97129 -IHF4 97130 -dHBz 97131 -IHJlc3Bhd24= 97132 -IFtdKSwK 97133 -IGtvbnRyb2w= 97134 -44GC44KK44GM44Go44GG44GU44GW 97135 -TW9kdWxlTmFtZQ== 97136 -IG5ld1BhdGg= 97137 -IFBhZ2luZw== 97138 -IHJpbnM= 97139 -X21ha2Vy 97140 -XGJyaWVm 97141 -IGJpc2hlcg== 97142 -CVJlYWQ= 97143 -IGppaGFkaXN0 97144 -LnBlcnNpc3RlbnQ= 97145 -IFJvYm90cw== 97146 -L2dycGM= 97147 -IEpvdQ== 97148 -w6RyZW4= 97149 -77yM5Zyo 97150 -LXB0 97151 -IHpkYXJtYQ== 97152 -X05N 97153 -IENvbm5lY3Rpdml0eQ== 97154 -KGJj 97155 -IEZsb3JpYW4= 97156 -IFNvY2lvbG9neQ== 97157 -X3dv 97158 -QW5kU2VydmU= 97159 -XygpOwo= 97160 -IEZMVA== 97161 -X0RFUg== 97162 -IENvbm5pZQ== 97163 -IEJyb2FkY2FzdFJlY2VpdmVy 97164 -eyg= 97165 -IGNvbW1lbnRlcg== 97166 -IGRlbW9jcmF0 97167 -IGFtcGxpZnk= 97168 -LS0tLS0tLS0tLQ0K 97169 -IEhNUw== 97170 -IHRyYWlsZWQ= 97171 -IFNvZGE= 97172 -LXRlc3RlZA== 97173 -dWxpc3Q= 97174 -KW5ldw== 97175 -X1RocmVhZA== 97176 -VG9kZA== 97177 -IGRlYmlhbg== 97178 -Vms= 97179 -IHByZXNlbnRh 97180 -IGNvbWZvcnRz 97181 -IFdhc2hlcg== 97182 -IGdhcmc= 97183 -IEh1Y2thYmVl 97184 -INGB0LDQvA== 97185 -ICEi 97186 -QWRhcHRlck1hbmFnZXI= 97187 -IEVh 97188 -IEFzc29jaWF0aW9ucw== 97189 -CQkJCQkKCQkJCQkK 97190 -LmdldFdyaXRhYmxlRGF0YWJhc2U= 97191 -IG51Y2xlaQ== 97192 -w6lnb3JpZQ== 97193 -CSAgICAgICAgICAgICAgICAg 97194 -QkFC 97195 -IHVwa2VlcA== 97196 -IFR1cA== 97197 -LndpdGhPcGFjaXR5 97198 -bHlh 97199 -IGx1eGU= 97200 -dXBybw== 97201 -LWVuZw== 97202 -IHJlbGHDp8Ojbw== 97203 -IGtleVByZXNzZWQ= 97204 -IGh5YnJpZHM= 97205 -bGZ3 97206 -T3BlcmF0aW9uQ29udHJhY3Q= 97207 -IG5hbWVMYWJlbA== 97208 -IEhvcnQ= 97209 -X2dydXBv 97210 -IGJhbmRh 97211 -SXg= 97212 -SGVhbHRoeQ== 97213 -LmdldEVuZA== 97214 -ZnJhdQ== 97215 -KFNjZW5l 97216 -KENvbGxlY3Rpb25z 97217 -IFNraXBwaW5n 97218 -dWJv 97219 -IGbDvG4= 97220 -Ij4tLT4K 97221 -IGRyb2l0cw== 97222 -IGhvbW9zZXh1YWxz 97223 -IGFiZHVjdGlvbg== 97224 -CXdpZGdldA== 97225 -JGhlYWRlcnM= 97226 -IERBUg== 97227 -IGZsYQ== 97228 -dGhyZWF0 97229 -IGxvdWlz 97230 -LkdldFByb3BlcnR5 97231 -Ikp1c3Q= 97232 -KGZyYW1lcw== 97233 -cnlv 97234 -cHJvZmVzc2lvbg== 97235 -fGk= 97236 -7ZW07ISc 97237 -KHN2 97238 -IHVucmVjb2duaXplZA== 97239 -SW9uaWM= 97240 -RmFzaGlvbg== 97241 -U2NyZWVuU3RhdGU= 97242 -IEluY29taW5n 97243 -Tm90Tmls 97244 -IHN5bmNpbmc= 97245 -ZW1pZQ== 97246 -IHRoZXJtbw== 97247 -X3Byb2Nz 97248 -IGluY29uc2lzdGVuY3k= 97249 -cmVsaWdpb3Vz 97250 -Lm1q 97251 -IHBlcnNvbm4= 97252 -IG1vbWVudG9z 97253 -b3JhcmlseQ== 97254 -IOaK 97255 -X25ldXJvbnM= 97256 -SWxsdXN0cg== 97257 -aW1vdG8= 97258 -aWxpaw== 97259 -IFdvag== 97260 -VHJhZGluZw== 97261 -IGFwcGFyZQ== 97262 -IGVudHJlcHJpc2Vz 97263 -YWNoYXQ= 97264 -IMKs 97265 -IG5laWdo 97266 -QlVUVE9ORE9XTg== 97267 -IE1haGVy 97268 -YWdoYW4= 97269 -LWhhc2g= 97270 -ImY= 97271 -IGNsaWVudGVsZQ== 97272 -LmFkZEJ1dHRvbg== 97273 -CVNQ 97274 -UWk= 97275 -IGdyYXRlZA== 97276 -UE9TSVRF 97277 -Oj4= 97278 -IEhvd2VsbA== 97279 -IENvbXBhcmF0aXZl 97280 -IElTQw== 97281 -wq1p 97282 -T2NlYW4= 97283 -RGF2aXM= 97284 -IEZpbG1l 97285 -V2lucw== 97286 -IEpJVA== 97287 -b2NjZXI= 97288 -IENvcm0= 97289 -RU5DSE1BUks= 97290 -cmNoaXZl 97291 -aWNhw6fDo28= 97292 -IG1hdGE= 97293 -IGNoaWxkYmlydGg= 97294 -IE9wdGlvbmFsbHk= 97295 -RW5z 97296 -IHhodHRw 97297 -IGVsdWNpZA== 97298 -X09zY0luaXRTdHJ1Y3Q= 97299 -KSkpOgo= 97300 -IGludHVpdA== 97301 -IERvbmF0ZQ== 97302 -IGNvcnJlbGF0ZXM= 97303 -PkRlbGV0ZQ== 97304 -IGVxdWlwZQ== 97305 -IGJvY2E= 97306 -IGluZmxhdGFibGU= 97307 -ZXJhaA== 97308 -IERhdGVUaW1lS2luZA== 97309 -IGNhbHZlcw== 97310 -XExpYg== 97311 -IGVtbHJ0 97312 -IFRyaWxvZ3k= 97313 -IFBhbmM= 97314 -IER1aXM= 97315 -IHBlbMOtY3VsYQ== 97316 -V0FSRFM= 97317 -X0RFVEVDVA== 97318 -LXNlY3Rpb25hbA== 97319 -ZGhjcA== 97320 -Rm9yUm93 97321 -LWRlc3RydWN0 97322 -IFByZXNlbnRlcg== 97323 -L3NsaWNr 97324 -LG9u 97325 -IENpdGFkZWw= 97326 -bG9nZ2VkaW4= 97327 -X3N1YnR5cGU= 97328 -IHNpZ3Vl 97329 -IGN1cmluZw== 97330 -IEZpcmV3YWxs 97331 -IGZsdW9yZXNjZW5jZQ== 97332 -IEl0YWxpYW5z 97333 -0LjRgtGB0Y8= 97334 -LmdldFN0eWxl 97335 -SW5TZWNvbmRz 97336 -amll 97337 -LVNtaXRo 97338 -IHhsaW5r 97339 -IHN1Ym1pc3NpdmU= 97340 -0L7QvdGC 97341 -YXJib25hdGU= 97342 -IEZhdWw= 97343 -X2dvYWxz 97344 -IENvbW1pc3Npb25lcnM= 97345 -Y2hhcnRJbnN0YW5jZQ== 97346 -X1BPU1RGSUVMRFM= 97347 -IG1lZGlhbA== 97348 -IG1hbm9z 97349 -IGRlbHQ= 97350 -c3Zt 97351 -LkFwaXM= 97352 -ZXBoeQ== 97353 -IGFzeW1wdA== 97354 -IGFwcERlbGVnYXRl 97355 -IGltcHJvYmFibGU= 97356 -Y2th 97357 -c2ltZA== 97358 -L0Vycm9y 97359 -LuKAkw== 97360 -IFBUUw== 97361 -ZGVlcg== 97362 -IHNpbmE= 97363 -bWFnbml0dWRl 97364 -SURBREU= 97365 -J119Jw== 97366 -IG1heW9yZXM= 97367 -CWNvbW1lbnQ= 97368 -L2NvbnNvbGU= 97369 -IkA= 97370 -dm9sdA== 97371 -LnNlbGw= 97372 -IE1hY3k= 97373 -IG1lbG9k 97374 -IGltw6FnZW5lcw== 97375 -X2NoZw== 97376 -IGlub3V0 97377 -aWRlbnRl 97378 -KScpLAo= 97379 -ZG5p 97380 -LmJsb2I= 97381 -IHR5cG9ncmFwaHk= 97382 -IGVlcmll 97383 -X09JRA== 97384 -cGVzYW4= 97385 -YWphbg== 97386 -IGNob3BwaW5n 97387 -IGJsdWZm 97388 -YWRm 97389 -X2Jhc2Vz 97390 -LkZvcm1hdHRlcg== 97391 -IFwl 97392 -IFBhZ2VJbmZv 97393 -Q2Fycmllcg== 97394 -IENhbGlicmF0aW9u 97395 -Y29tbw== 97396 -LWJvZGllZA== 97397 -IGZpbmFuY2llcg== 97398 -IElOQQ== 97399 -LkVSUg== 97400 -IGhvb2RpZQ== 97401 -IFNhbml0eQ== 97402 -Z3VhcmRlZA== 97403 -Lm9wZW5kYXlsaWdodA== 97404 -SVNNQVRDSA== 97405 -SGlnaGxpZ2h0cw== 97406 -w7xuaw== 97407 -YW5pZW0= 97408 -YW5nZXJlZA== 97409 -YXNzaWdubWVudHM= 97410 -IHJlZ2lzdHJhZG8= 97411 -IFVQUEVS 97412 -YW1waWxrYW4= 97413 -YXNoaXJl 97414 -IE5pa29sYQ== 97415 -IENGTA== 97416 -IEhEQw== 97417 -IHBvaWRz 97418 -IElQcw== 97419 -IHByZXZlbnRhdGl2ZQ== 97420 -aXBzb2lk 97421 -aWZpeA== 97422 -LmNhbWVs 97423 -Lmdh 97424 -Vm9sdW1lcw== 97425 -LXN0ZQ== 97426 -WWFob28= 97427 -X3NpYmxpbmc= 97428 -SGlnaGVzdA== 97429 -b3B0Z3JvdXA= 97430 -IGt2aW5uYQ== 97431 -4oCd44CCCgo= 97432 -IEFwcGxpYW5jZXM= 97433 -ICI+PA== 97434 -JykiKQo= 97435 -aHR0 97436 -IElkZW50aWZpZWQ= 97437 -IHBlbmNpbHM= 97438 -IG1lbWJlcklk 97439 -IGFwcGVuZFN0cmluZw== 97440 -LmxvYWREYXRh 97441 -IG1vY2tNdmM= 97442 -IGp1Yg== 97443 -IFNsdXQ= 97444 -IFRhaXBlaQ== 97445 -c3RhdHQ= 97446 -UG9saXQ= 97447 -IHBhcnRhZ2Vy 97448 -RGlkQ2hhbmdl 97449 -SW5jcmVhc2Vz 97450 -KX0u 97451 -IEJhYmE= 97452 -X0NMSVA= 97453 -W3VuaXQ= 97454 -INC60LvRjtGH 97455 -IGFsY3VuaQ== 97456 -IExvbGE= 97457 -IGNsaW5naW5n 97458 -QFBvc3RNYXBwaW5n 97459 -KGNvbmNhdA== 97460 -IHNzaWQ= 97461 -IEZhdWM= 97462 -b2tpdA== 97463 -IFJlY29yZGVk 97464 -w6FsZXo= 97465 -KCQoJzw= 97466 -LmFzc2VydElzTm90 97467 -IGthbGk= 97468 -Vm9sdA== 97469 -IHdhcm1seQ== 97470 -IHNjYXJlcw== 97471 -Z2V0dGk= 97472 -ZsO8aHJ0 97473 -X2RvZXM= 97474 -LkVNQUlM 97475 -aW1hdGlvbnM= 97476 -IHNwcmluZ2ZveA== 97477 -IERlY29t 97478 -YXJjeQ== 97479 -IGdsaXRjaGVz 97480 -IE1vZmY= 97481 -IFZvbGw= 97482 -LmJldHdlZW4= 97483 -IGNvb3JkZW4= 97484 -IFBhcnRpY3VsYXJseQ== 97485 -R0JQ 97486 -IHNlbWJsZQ== 97487 -RWFzdGVybg== 97488 -X01TQg== 97489 -XSl7DQo= 97490 -bW9yZ2Fu 97491 -IEVWQUw= 97492 -ZGVyZQ== 97493 -SE9VU0U= 97494 -bW9pcmU= 97495 -aXN0aXF1ZQ== 97496 -X2xzdG0= 97497 -LWNvbW1pdA== 97498 -eXN0ZXJpb3Vz 97499 -IHR3aW5r 97500 -LXRodW1ibmFpbHM= 97501 -ZW7DrQ== 97502 -OicnLA== 97503 -IGJsYWNrb3V0 97504 -IEZsb29ycw== 97505 -IHNvZmFz 97506 -IG91aQ== 97507 -bGVzaG9vdA== 97508 -IFJhcQ== 97509 -LWFicw== 97510 -IGtyYQ== 97511 -TWluaW5n 97512 -c2hhZnQ= 97513 -LnNldENvbHVtbnM= 97514 -Q2xheno= 97515 -UFJFVFRZ 97516 -LnBsYXlsaXN0 97517 -6Zai 97518 -LVNhaGFyYW4= 97519 -TUlORw== 97520 -CWJs 97521 -6K6u 97522 -amY= 97523 -RE9DS0VS 97524 -aG9wZWZ1bGx5 97525 -KGlnbm9yZQ== 97526 -IFVzZXJzQ29udHJvbGxlcg== 97527 -IE1pdGFyYmVpdGVy 97528 -IExFUw== 97529 -SGFtaWx0b24= 97530 -LW1ldGFkYXRh 97531 -IEtL 97532 -aWt0aWc= 97533 -IHdvbGx0ZQ== 97534 -ZWdyYXRvcg== 97535 -XWJvb2w= 97536 -LGN1cnJlbnQ= 97537 -IHZhbHVlVHlwZQ== 97538 -IGV4Y2F2YXRpb24= 97539 -b2xhbmQ= 97540 -IHZlcnY= 97541 -L2ZpbGVwYXRo 97542 -QXV0aFByb3ZpZGVy 97543 -IHByb2NyYXN0 97544 -CVVMT05H 97545 -X01FTUJFUlM= 97546 -IHVwbGlmdA== 97547 -IEF1dG9ub21vdXM= 97548 -IGFydHdvcmtz 97549 -IE91dHJlYWNo 97550 -IHBvcmU= 97551 -SG9tZXBhZ2U= 97552 -RGlhbG9nVGl0bGU= 97553 -IEdlbmVyYXRpbmc= 97554 -UEFSU0U= 97555 -IHNlbWFuYXM= 97556 -IGh1bWFubw== 97557 -SlNHbG9iYWxTY29wZQ== 97558 -IHZvbHRl 97559 -IGJlbGxh 97560 -KGlzaW5zdGFuY2U= 97561 -IHBsYw== 97562 -XENhdGFsb2c= 97563 -IGVzdGVlbWVk 97564 -6Zu3 97565 -KHN1ZmZpeA== 97566 -IHN3ZWVwcw== 97567 -CU9SREVS 97568 -IGRvaXZlbnQ= 97569 -IFN3YXJt 97570 -IENvbXBpbGVk 97571 -Z2V0UGFnZQ== 97572 -QURS 97573 -LlJpY2hUZXh0Qm94 97574 -IE5hbWluZw== 97575 -YWdnZWQ= 97576 -IEdBTkc= 97577 -cmFzaW5n 97578 -b2RlbGVk 97579 -IGdhbGE= 97580 -IEpTTmFtZQ== 97581 -ZGRm 97582 -IGlsbHVzdA== 97583 -IExhbnNpbmc= 97584 -W3BvcnQ= 97585 -LWRlYXRo 97586 -IGRpbmhlaXJv 97587 -IEVpZ2h0aA== 97588 -IGJpYW4= 97589 -c3TDpQ== 97590 -IHZlcnNpw7Nu 97591 -IExpbmVhckdyYWRpZW50 97592 -IEhhcmRpbmc= 97593 -Liop 97594 -ZWN6eQ== 97595 -JGhlYWRlcg== 97596 -IHbDpXI= 97597 -VW5jaGVja2Vk 97598 -IGtvamU= 97599 -IFBhbGFkaW4= 97600 -KCkpKSw= 97601 -R2l2aW5n 97602 -KCl9KQo= 97603 -IGRpcHM= 97604 -RnJpZW5kbHk= 97605 -IHBvcnRyYXlz 97606 -IGhlbGl1bQ== 97607 -IGluc3VyZ2VuY3k= 97608 -X2V4cGlyeQ== 97609 -IHN0cmluZ0J5QXBwZW5kaW5nU3RyaW5n 97610 -IGFhbnRhbA== 97611 -c2xvcGU= 97612 -bWFzdA== 97613 -LmdldEludGVnZXI= 97614 -ICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw== 97615 -X1BJUEVMSU5F 97616 -IGRlbnNlbHk= 97617 -IG11dGF0aW5n 97618 -bWlkaQ== 97619 -IFNlaXQ= 97620 -YXluZQ== 97621 -Tk9XTEVE 97622 -IERlc21vbmQ= 97623 -IEZOYW1l 97624 -IE5haXJvYmk= 97625 -XENvbnRleHQ= 97626 -IGNhbGN1bGFy 97627 -LWRlbg== 97628 -IGNvdHQ= 97629 -XSk6DQo= 97630 -IFJlY29tbWVuZGF0aW9u 97631 -IFJvbGV4 97632 -IHZhbGlkYXRpb25SZXN1bHQ= 97633 -LnBhdA== 97634 -IG7DoHk= 97635 -IFJlc3RDbGllbnQ= 97636 -IEdQSQ== 97637 -IEFzaGV2aWxsZQ== 97638 -IE9TUA== 97639 -IFBFUk1JU1NJT04= 97640 -0JTQsNGC0LA= 97641 -L25vdGlmaWNhdGlvbg== 97642 -S25pZ2h0 97643 -X1dvcmQ= 97644 -IEJlbmRlcg== 97645 -cmFua2luZw== 97646 -IHBhcnRpZGE= 97647 -X3Jlc2VydmF0aW9u 97648 -zIA= 97649 -IG1OYW1l 97650 -IGdldGNo 97651 -IGJvcnI= 97652 -IGRpbGlnZW50 97653 -RGlzY3Vzcw== 97654 -5q2j5Zyo 97655 -YXBlYWtl 97656 -aW9uZWQ= 97657 -LU5hemk= 97658 -LmN1bQ== 97659 -IEtyb24= 97660 -PSQoJyM= 97661 -L3NpbmdsZQ== 97662 -IGVyb3Rpc2No 97663 -IFZpYg== 97664 -IHJhdGlmaWVk 97665 -IGNvbmNlcnRlZA== 97666 -IFJFR0FSRA== 97667 -IGRvYnI= 97668 -LkRyaXZlck1hbmFnZXI= 97669 -J3I= 97670 -UG9ydGFibGU= 97671 -CXN1aXRl 97672 -IHJlbGFjaW9uZXM= 97673 -IERvcA== 97674 -ZW1wbG9p 97675 -RE9C 97676 -IGNydW1icw== 97677 -IHhscw== 97678 -X0FwcGxpY2F0aW9u 97679 -KCc6Jyw= 97680 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQo= 97681 -bXNl 97682 -IGJlcms= 97683 -IFJldHVyblZhbHVl 97684 -IEJlbGx5 97685 -IGNhbWFy 97686 -IFBlZWs= 97687 -ZWxzaW5n 97688 -IG5vdGlmaWVz 97689 -IFRyaXN0YW4= 97690 -IEdBUg== 97691 -ZW1tZQ== 97692 -IEVsZXZhdGVk 97693 -X0NTVg== 97694 -KGNoYWxr 97695 -IHR3ZW50aWVz 97696 -IFNlYXJjaFJlc3VsdA== 97697 -PXNlYXJjaA== 97698 -IE1peGluZw== 97699 -w710 97700 -IHJlY3J1aXRlcg== 97701 -IElERU9HUkFQSA== 97702 -IEFnbw== 97703 -KE9wZXJhdGlvbg== 97704 -JHZhbHVlcw== 97705 -IHdvcmxkbHk= 97706 -IFJvc2VuYmVyZw== 97707 -IENvbmZpZ3VyZVNlcnZpY2Vz 97708 -Pio8Lw== 97709 -S0FOSkk= 97710 -IGNodWNrbGVk 97711 -IHN0cmlmZQ== 97712 -IEJvbWJheQ== 97713 -IEJBQ0tHUk9VTkQ= 97714 -ZXRhdA== 97715 -ZW51bWVyYXRvcg== 97716 -IHPDu3I= 97717 -IOOBrg== 97718 -X3BlZGlkbw== 97719 -L0Rr 97720 -IGplYW4= 97721 -X0NvbHVtbg== 97722 -IGhlYXRtYXA= 97723 -LlBlbmRpbmc= 97724 -IHVuc3VjY2Vzc2Z1bGx5 97725 -CWVw 97726 -IHNpbmZ1bA== 97727 -IEFudG9ueQ== 97728 -X0ZPQ1VT 97729 -VGV4dExhYmVs 97730 -X3JlYWN0aW9u 97731 -IElEaXJlY3Q= 97732 -IGNhcm5pdg== 97733 -V29ya3NoZWV0 97734 -IHN1ZWRl 97735 -CVJUQ1Q= 97736 -IHNldGJhY2tz 97737 -LnVuYmluZA== 97738 -IHNpw6g= 97739 -TGlxdWlk 97740 -X1JFTkRFUkVS 97741 -TWF0ZQ== 97742 -IE1pbGxlbm5pYWxz 97743 -IGVwb3h5 97744 -aXp6aW5lc3M= 97745 -IGJyYXppbA== 97746 -0L7RgdGC0Yw= 97747 -JnZpZXc= 97748 -L2dwaW8= 97749 -SmFtaWU= 97750 -LkdyYXZpdHk= 97751 -PSIuJF8= 97752 -IFZBTg== 97753 -IElEUg== 97754 -YXBwZWFyYW5jZQ== 97755 -LlNlbGVuaXVt 97756 -TGVhcA== 97757 -LlJlbGF0aXZlTGF5b3V0 97758 -U2lnbmFscw== 97759 -QWNjZWxlcmF0aW9u 97760 -CUhBTkRMRQ== 97761 -L09wZW4= 97762 -IGdldExvZ2dlcg== 97763 -U3Bp 97764 -LXdyaXRpbmc= 97765 -INCy0YvQtw== 97766 -LXdvcnRoeQ== 97767 -IHdjcw== 97768 -IFFUaW1lcg== 97769 -IFBvbHltZXI= 97770 -IHZhbnQ= 97771 -CURlbGV0ZQ== 97772 -aXR0ZQ== 97773 -V2hpbHN0 97774 -IGFsZ3Vt 97775 -IHNoaWVsZGluZw== 97776 -IGttcw== 97777 -CSAgICAJCQk= 97778 -TWV0ZW9y 97779 -IGFnZ3JlZ2F0b3I= 97780 -IFNpbmQ= 97781 -SG9zdEV4Y2VwdGlvbg== 97782 -PScnLAo= 97783 -IEpTQnJhY2tldEFjY2Vzcw== 97784 -T05P 97785 -X0J1aWxk 97786 -IHN0cmlwcGVy 97787 -IExK 97788 -PENvbXBvbmVudA== 97789 -L3NvdXJjZXM= 97790 -IGVyZ29ub21pYw== 97791 -IEFjY3JlZA== 97792 -dW5jZQ== 97793 -b25pcw== 97794 -emVpZ3Q= 97795 -IFNrYXRl 97796 -IFJlY3RUcmFuc2Zvcm0= 97797 -SW5jb21wbGV0ZQ== 97798 -IGluZ2VuaW91cw== 97799 -IGNvaXNh 97800 -IGNpdHlOYW1l 97801 -aGFiaXQ= 97802 -X1RW 97803 -IEFOU1c= 97804 -Li4uIj4K 97805 -IHNub3Jr 97806 -X29wYWNpdHk= 97807 -IGluaXRXaXRoTmliTmFtZQ== 97808 -aWFkbw== 97809 -QUFD 97810 -IF0pLg== 97811 -O3o= 97812 -X3BhcmFncmFwaA== 97813 -IG5vc2Vz 97814 -c3RhbmRz 97815 -aWZy 97816 -X21F 97817 -SXJhcQ== 97818 -LlByZWRpY2F0ZQ== 97819 -ZW5haXJl 97820 -XV1dOwo= 97821 -IHVuaWRhZA== 97822 -IHJldGlyZWVz 97823 -X2hlbGxv 97824 -IG1vZGVsZQ== 97825 -IFVJVGFibGVWaWV3Q29udHJvbGxlcg== 97826 -ZndyaXRl 97827 -X251bWVybw== 97828 -X3Zpc2l0ZWQ= 97829 -IHJlY2ViZQ== 97830 -KE5vdGlmaWNhdGlvbg== 97831 -RmFudGFzdGlj 97832 -X3N1Ym1lbnU= 97833 -IFBFTQ== 97834 -IEN1cGVydGlubw== 97835 -YXBwcm94aW1hdGVseQ== 97836 -Y2xhc3NlZA== 97837 -LlJlYWRTdHJpbmc= 97838 -IGRvbWljaWxl 97839 -X1BX 97840 -IGJhbGxwYXJr 97841 -IEthbGU= 97842 -Y29udHJh 97843 -X2Zhdm9yaXRl 97844 -L29m 97845 -UXVpdGU= 97846 -IE9UQQ== 97847 -IGFjY2VsZXJvbWV0ZXI= 97848 -ZGlkbg== 97849 -fF4= 97850 -IFJvaGluZ3lh 97851 -aXZpY3Jt 97852 -YW5uYWJpbg== 97853 -0L7QsdGL0YLQuA== 97854 -b3JhZG8= 97855 -Jykr 97856 -SGF1bnRlZA== 97857 -LElE 97858 -KFVJQWxlcnRBY3Rpb24= 97859 -dXJ2 97860 -X2JlbA== 97861 -IE1leGljYW5z 97862 -L3Rlcm1z 97863 -IFBhaW50ZXI= 97864 -SW5wdXRMYWJlbA== 97865 -IFZpbmNp 97866 -IFJvc2ll 97867 -XHVj 97868 -PE1lbnU= 97869 -IGNvb2xhbnQ= 97870 -KGN1cnJlbnRVc2Vy 97871 -X2R1YWw= 97872 -KSJ9LAo= 97873 -JnA= 97874 -IGNvbnZlcmdlZA== 97875 -IHJlc3RyYWlu 97876 -IFl1Z29zbGF2aWE= 97877 -PXRhcmdldA== 97878 -IGltcHVscw== 97879 -ZHNh 97880 -U2VhcmNoVHJlZQ== 97881 -IGhib3g= 97882 -IEltcHJlc3M= 97883 -wqfDgw== 97884 -Z2V0RnVsbFllYXI= 97885 -KGRh 97886 -IFlZUw== 97887 -LmFsaWdubWVudA== 97888 -LkdldFRleHQ= 97889 -LnRva2VuaXpl 97890 -IE9seW1wdXM= 97891 -IG11cmt5 97892 -b3Jlc3RhdGlvbg== 97893 -IGRpc3NhdGlzZmFjdGlvbg== 97894 -CVRBcnJheQ== 97895 -X2tzZXM= 97896 -LkFkZFNpbmdsZXRvbg== 97897 -IFN0YXJ0VGltZQ== 97898 -IGZhbmF0aWM= 97899 -ICAgICAgICAgICAgICAgICAgICAJ 97900 -IGVudGl0eVR5cGU= 97901 -Lm92ZXJyaWRl 97902 -IC0tLS0tLS0tLS0tLS0= 97903 -IERhdGFncmFt 97904 -Zm91dA== 97905 -KHdpdGhJZA== 97906 -ICNfXw== 97907 -n+iDvQ== 97908 -ZWt5bGw= 97909 -LmZyaWVuZHM= 97910 -YW1lbGVvbg== 97911 -IHphY2g= 97912 -LnNpbXBsZUJ1dHRvbg== 97913 -cmV0b3Jubw== 97914 -IGtvbms= 97915 -L3NtYWxs 97916 -IFF1aWNrbHk= 97917 -dW5yZWFk 97918 -RG9uYXRl 97919 -RGV0YWlsVmlldw== 97920 -IGR1YQ== 97921 -IHBlbmV0cmF0ZWQ= 97922 -T01VWA== 97923 -IG5pcg== 97924 -X3BkYXRh 97925 -Il0sWyI= 97926 -IGxvd2Vz 97927 -IGRvcGluZw== 97928 -IGFzeW1tZXRyaWM= 97929 -IG5lZWRsZXNz 97930 -b3VyY2Vt 97931 -IHVwcm8= 97932 -IEd1enpsZQ== 97933 -YWZi 97934 -IHNleHRyZWZmZW4= 97935 -LWNvbGxhcg== 97936 -IGNvbG9zc2Fs 97937 -TW9ua2V5 97938 -bmlzaA== 97939 -IGhhbmRsZU1lc3NhZ2U= 97940 -SW5jcmVhc2Vk 97941 -KmR4 97942 -IENoYXR0YW5vb2dh 97943 -Zm9yZw== 97944 -IE9yZGVu 97945 -IHNocmk= 97946 -IFZhbmQ= 97947 -ICJAIg== 97948 -SW1hZ2VTaGFycA== 97949 -IFdpbGRjYXRz 97950 -cG9uaWJsZQ== 97951 -LnNjZW5lcw== 97952 -IHBhaW50ZXJz 97953 -IFBmaXplcg== 97954 -IFphaA== 97955 -VG9Mb2NhbA== 97956 -IEZsYW0= 97957 -IMOpdGFpZW50 97958 -KSle 97959 -IFNhbmRib3g= 97960 -IFRSQURF 97961 -IGNocm9taXVt 97962 -IGFjY2xhaW0= 97963 -IHBhY21hbg== 97964 -wrR0 97965 -KXJlYWRlcg== 97966 -TWFyaQ== 97967 -LkRpc3BhdGNoZXI= 97968 -LkFETUlO 97969 -IFJlbWVk 97970 -U3dlZGVu 97971 -IG92ZXJsYXlz 97972 -LmVy 97973 -IHBhbmc= 97974 -IGNsZWFubHk= 97975 -YXZlbnBvcnQ= 97976 -VG95b3Rh 97977 -cGF0Y2hlcw== 97978 -IHZ0eA== 97979 -IEVpcw== 97980 -Y2xhZG8= 97981 -IFJpdGNo 97982 -Uk9MUw== 97983 -IGhhZGU= 97984 -IGNvbnNwaWN1b3Vz 97985 -IGRvY2tz 97986 -KGpx 97987 -IFByZW1pZXJzaGlw 97988 -IEJleg== 97989 -IOKElg== 97990 -INGD0YHQuw== 97991 -X3RvdGFscw== 97992 -IHByb3Zh 97993 -IEN1ZQ== 97994 -IHNhw7pkZQ== 97995 -IEdhbWVDb250cm9sbGVy 97996 -SU1JWkU= 97997 -LHBvcnQ= 97998 -44CCKA== 97999 -LkNkZWNs 98000 -SW5zdGFudGlhdGlvbkV4Y2VwdGlvbg== 98001 -IGNvbGxhZ2U= 98002 -IElPQw== 98003 -IGJhaXM= 98004 -IG9uRmluaXNo 98005 -LXN0YXJz 98006 -c2V0U2l6ZQ== 98007 -IG1vZ3Vs 98008 -IGRpc2lsbHVzaW9u 98009 -IGNoZXZ5 98010 -KFNjaGVkdWxlcnM= 98011 -KElS 98012 -X2xvY3M= 98013 -IGNhbm5vbnM= 98014 -IGNhbmNlbGxpbmc= 98015 -L2J1cw== 98016 -IGJ1Zmlv 98017 -IFlvdXJz 98018 -IFBpa2FjaHU= 98019 -IHRlcm1l 98020 -csOl 98021 -ZmFocmVu 98022 -IG93bmVySWQ= 98023 -IG9ibGlnYXRvcnk= 98024 -IGN1bHA= 98025 -IGFjaWRpdHk= 98026 -LW11bHQ= 98027 -IEJhbWJvbw== 98028 -ICciPg== 98029 -X2dz 98030 -IGNvbXBpbA== 98031 -bmFyZA== 98032 -LWV4Yw== 98033 -IHJoeW1l 98034 -IGJ1dHRv 98035 -c2F5cw== 98036 -YW50YXN5 98037 -67g= 98038 -IGNpdHTDoA== 98039 -IGNoZWc= 98040 -VGltZVN0cmluZw== 98041 -IHBvc2l0aXZpdHk= 98042 -IERhYmVp 98043 -IHdhbmc= 98044 -IGVzY3Jl 98045 -ImM= 98046 -CXZpZGVv 98047 -IFJhbmtlZA== 98048 -LnN0cmluZ3M= 98049 -Pj4+KA== 98050 -INC40L3RgtC10YA= 98051 -IHJlc3Rh 98052 -WzosOg== 98053 -IHJlbmRyZQ== 98054 -IGRlc2Vy 98055 -Sm9z 98056 -IGRpc3J1cHRpb25z 98057 -INC+0L/QtdGA 98058 -c2FtcGxpbmc= 98059 -c3VwcHJlc3M= 98060 -IGNvbnRhaW5lclZpZXc= 98061 -IFNlYW1sZXNz 98062 -IGFpcnk= 98063 -IG9ubG9hZA== 98064 -LldpbmRvd01hbmFnZXI= 98065 -IFBMQQ== 98066 -YnJhY28= 98067 -LnNldFBvc2l0aXZlQnV0dG9u 98068 -IHBkdQ== 98069 -IGdzaQ== 98070 -IENsaQ== 98071 -X2dyYWRpZW50cw== 98072 -0Y/QtA== 98073 -IFdoaXNwZXI= 98074 -Y3N0ZGludA== 98075 -IGzDpG5n 98076 -IGZvcm11bGF0aW9ucw== 98077 -w6lub20= 98078 -b3VybmVtb3V0aA== 98079 -WyRf 98080 -IG9yZGluYXJpbHk= 98081 -LnNldFVzZXJuYW1l 98082 -IGZhY3VsdGllcw== 98083 -TUlUVEVE 98084 -L3ZhbHVlcw== 98085 -IHdlaXI= 98086 -IEFwdA== 98087 -TVo= 98088 -CWNm 98089 -dWNrZW4= 98090 -CQkJCQkJCQkJCQkJCQkJCQkJCQk= 98091 -ZGVmZW5zZQ== 98092 -W2lWYXI= 98093 -IEJ1c2luZXNzRXhjZXB0aW9u 98094 -U2VsZWN0b3Jz 98095 -KGNvb3JkaW5hdGVz 98096 -IFJlc2V0cw== 98097 -IERyaW5rcw== 98098 -b2xlYW5z 98099 -KHN0eXB5 98100 -X0lPQw== 98101 -Lnh4eA== 98102 -IFNsYXRlcg== 98103 -IEJlbGl6ZQ== 98104 -IC8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKio= 98105 -YWRkaW4= 98106 -X2VwaXNvZGVz 98107 -IGlzY2hlbQ== 98108 -bGVnYWxBcmd1bWVudEV4Y2VwdGlvbg== 98109 -RGFubnk= 98110 -IHBhcmVk 98111 -LmNvZGVoYXVz 98112 -IEFzc3k= 98113 -CVJlY3Q= 98114 -4p4= 98115 -Lmxpc3Rh 98116 -INCy0LDRiA== 98117 -IHZldHM= 98118 -SFdORA== 98119 -aXNvbmVy 98120 -IHhv 98121 -IG9yYWxseQ== 98122 -IFN0bXQ= 98123 -LnJubg== 98124 -IERQSQ== 98125 -IFN0cmlrZXM= 98126 -LnNldFZpZXdwb3J0Vmlldw== 98127 -IOiHquWKqOeUn+aIkA== 98128 -WUVMTE9X 98129 -R0xlbnVt 98130 -cGFydG5lcnM= 98131 -IEltcGxpY2l0 98132 -IHRha28= 98133 -4oCZZWxsZQ== 98134 -IGVybcO2Zw== 98135 -dG90YWxDb3VudA== 98136 -R2ls 98137 -CXdvcms= 98138 -IHByYXRpYw== 98139 -aW5hdGk= 98140 -YWJpZXM= 98141 -IFNraW5uZXI= 98142 -IHNwaXJpdGVk 98143 -IHBhbmNyZWF0aWM= 98144 -IGhkZg== 98145 -J2Vt 98146 -IHBzeWNob3Npcw== 98147 -b2xpY2l0 98148 -ICJ7Ig== 98149 -X2F0dWFs 98150 -IMOpbGVjdA== 98151 -VEVBTQ== 98152 -IGRhaw== 98153 -IFNXQVQ= 98154 -LkZyYWdtZW50TWFuYWdlcg== 98155 -IHByb3Zpc2lvbmluZw== 98156 -bGlmZXRpbWU= 98157 -X0VYVEVOU0lPTlM= 98158 -IENBU0NBREU= 98159 -ICFb 98160 -KEtQ 98161 -IHZlbQ== 98162 -IEludGVycmFjaWFs 98163 -J119LAo= 98164 -c3BhY2Vy 98165 -X2t2 98166 -V2FyZWhvdXNl 98167 -UkRE 98168 -X2ZzbQ== 98169 -LlN0cmV0Y2hJbWFnZQ== 98170 -LFllcw== 98171 -IFJlZnVnZWU= 98172 -IEJyaW5naW5n 98173 -IHbDoWxpZG8= 98174 -LmludGVyc2VjdGlvbg== 98175 -IHNwb29reQ== 98176 -X3BvcnRhbA== 98177 -IG1vdGg= 98178 -IFpvZGlhYw== 98179 -IFNPQ0lBTA== 98180 -TWltZVR5cGU= 98181 -J119fTwv 98182 -IHJlc2l6YWJsZQ== 98183 -5Lqb 98184 -KHBoYXNl 98185 -KG1hcHBlZEJ5 98186 -IG11bmRpYWw= 98187 -IGNvbnZv 98188 -L2xlZnQ= 98189 -L2RvY3VtZW50cw== 98190 -d2FzaGluZw== 98191 -IEFtw6lyaWNh 98192 -X3F1b3Rh 98193 -LnBvc3Rlcg== 98194 -J10iKTsK 98195 -IHN0ZWxsdA== 98196 -IERJU0NMQUlNRVI= 98197 -W29wdA== 98198 -IGVkcw== 98199 -IFJhY2Vz 98200 -dmVudGFz 98201 -IHB6 98202 -IENhcGFj 98203 -IFVzZXJEYW8= 98204 -aXRlc3Q= 98205 -UHJvdmVlZG9y 98206 -IFNob3RndW4= 98207 -IHRoaXJzdHk= 98208 -IEJhbGFuY2Vk 98209 -aXF1ZXRh 98210 -IGhlYWxlcg== 98211 -LyIp 98212 -LlNkaw== 98213 -IHRlcnQ= 98214 -ImRhdGE= 98215 -X3Byb3ZpbmNl 98216 -LkF1dG9tYXRpb24= 98217 -IGZvbnRXaXRoTmFtZQ== 98218 -X0FOVA== 98219 -55WM 98220 -b29kbGVz 98221 -IFJFUFJFU0VOVA== 98222 -X0dQUw== 98223 -IHBlcnN1YXNpb24= 98224 -IERpc2N1c3Npb25z 98225 -IGZyZWQ= 98226 -TkVH 98227 -OmJvcmRlcg== 98228 -CWluaXRpYWxpemU= 98229 -CWdsb2c= 98230 -LWNhcGl0YWw= 98231 -IEltVmVj 98232 -IGRldmlz 98233 -Q2FuZGlkYXRlcw== 98234 -LmFuaW1hdGlvbnM= 98235 -IHJhZ2F6emk= 98236 -IFByb21ldGhldXM= 98237 -IEtpZGQ= 98238 -IHByb2dyYW1tYQ== 98239 -Q2VydGlmaWNhdGVz 98240 -Q29udGE= 98241 -LmVzcHJlc3Nv 98242 -IOuQmA== 98243 -IGJlaWRl 98244 -6ZmG 98245 -LmdldFJhdw== 98246 -IEZ1bGxOYW1l 98247 -IGlhbQ== 98248 -KCopKA== 98249 -bWFpZHM= 98250 -Qkg= 98251 -IENvbnNwaXJhY3k= 98252 -X0RV 98253 -IGJsYXRhbnRseQ== 98254 -IFx8 98255 -IFdpZw== 98256 -IENvbmo= 98257 -UmVuZGVyaW5nQ29udGV4dA== 98258 -TWl0Y2g= 98259 -IGFsbGVsZXM= 98260 -IOazqOaEjw== 98261 -IHJpbXM= 98262 -IE5laWdoYm9y 98263 -IEt5bGll 98264 -LnBhcnR5 98265 -dG9ycw== 98266 -IOyhsO2ajA== 98267 -IHdlcw== 98268 -IENyYWZ0aW5n 98269 -WyIu 98270 -LnNwb25nZQ== 98271 -IOqx 98272 -SXNsYW1pYw== 98273 -IHByb3NlY3V0aW5n 98274 -IHdpaw== 98275 -Lm9zZ2k= 98276 -b25pbmdlbg== 98277 -R3JhbW1hcg== 98278 -J2lt 98279 -IGF4aWFs 98280 -Q2xlYW5pbmc= 98281 -LmdldEV4dGVybmFsU3RvcmFnZQ== 98282 -PS4v 98283 -IGNocm9tYXQ= 98284 -0LXRhQ== 98285 -YWJheQ== 98286 -IGJvbGE= 98287 -LkFnZ3Jlc3NpdmU= 98288 -J10sJF8= 98289 -aXphY2Fv 98290 -UHJlcGFyaW5n 98291 -OkFueQ== 98292 -LkVOVEVS 98293 -LXdpbmRvd3M= 98294 -IGVucmFnZWQ= 98295 -X2RpY2U= 98296 -IGRldHRh 98297 -ZWNhbA== 98298 -X09SSUdJTg== 98299 -IC0tLS0tLT4= 98300 -X0JsdWU= 98301 -IGJvdGFuaWNhbA== 98302 -IGZyYWdz 98303 -IGZhbWlsaWFs 98304 -LWR1 98305 -IHNlaXppbmc= 98306 -KGJsb2Nrcw== 98307 -LnJk 98308 -LmNoZWNrTm90TnVsbA== 98309 -IG1pc2Vy 98310 -IG1heHg= 98311 -IEtuZWU= 98312 -Vmlld0l0ZW0= 98313 -SW5uZXJIVE1M 98314 -RGFuZ2Vy 98315 -KChfXw== 98316 -IHByenlwYWQ= 98317 -Y3JlYXRlVXJs 98318 -Kios 98319 -IERlY29yYXRpbmc= 98320 -QVRFR1k= 98321 -Pz4v 98322 -LkRlc2lnbmVy 98323 -aGV4ZGlnZXN0 98324 -IEV2ZXJ5d2hlcmU= 98325 -YWxsZXJpZXM= 98326 -LlRFWFRVUkU= 98327 -LkJsb2Nrcw== 98328 -emVsbA== 98329 -IHByZcOnbw== 98330 -U3VkZGVubHk= 98331 -aW5wdXRFbWFpbA== 98332 -KHN5bmM= 98333 -LmJk 98334 -Z29sZGVu 98335 -PicpOw== 98336 -IERpY2tpbnNvbg== 98337 -Pj4oCg== 98338 -IFFVRVVF 98339 -IGdldENvbHVtbg== 98340 -IFNBTkQ= 98341 -LnBpZWNl 98342 -bGljZXI= 98343 -Rmx1dHRlcg== 98344 -IGdldFZlcnNpb24= 98345 -IHJlc291cmNlSWQ= 98346 -b2ds 98347 -xYJhdw== 98348 -LkJyYW5jaA== 98349 -CXdlYg== 98350 -IGZyYW1lcmF0ZQ== 98351 -UFBQ 98352 -IGZyYXk= 98353 -Q05U 98354 -IGluZm9ybWF0aWU= 98355 -J10NCg0K 98356 -bmVhcw== 98357 -SGVhZGVyQ29kZQ== 98358 -IOa4 98359 -IHRyZw== 98360 -cmF3dHlwZXM= 98361 -SG9uZGE= 98362 -IG1hcmtldGVy 98363 -IHJlcXVlc3REYXRh 98364 -IFBn 98365 -CW5vdA== 98366 -IHBhZ2VJbmZv 98367 -IGFrdHVlbGxlbg== 98368 -44GV44KT 98369 -IEFNUw== 98370 -cHVzaFZpZXdDb250cm9sbGVy 98371 -CUFM 98372 -IHZlc3Rz 98373 -cHJvZHVjZQ== 98374 -LW3Dqm1l 98375 -IFJhaG1hbg== 98376 -RnVubnk= 98377 -RVo= 98378 -X1ZhbGlk 98379 -IHNxdWFkcm9u 98380 -IGxhc2g= 98381 -IGlybQ== 98382 -aWFzY28= 98383 -IFBhcmFu 98384 -IHBldGl0ZXM= 98385 -IERlY2F5 98386 -IHVuaW5pdGlhbGl6ZWQ= 98387 -cHJpdmlsZWdlZA== 98388 -IG1iZWR0bHM= 98389 -5aSH5rOo 98390 -IF4u 98391 -IGVjc3RhdGlj 98392 -RGV0cm9pdA== 98393 -IHBhcnRlbg== 98394 -IHNvdXZlbmly 98395 -LmdldExvZ2lu 98396 -0LzQvtGC0YA= 98397 -ZW7Dp8Ojbw== 98398 -IG3DrW5pbW8= 98399 -IEFjY2Vzc2Vk 98400 -cmnDsw== 98401 -TWlj 98402 -IFZvY2Fs 98403 -LlNldFN0cmluZw== 98404 -IG1lbnNhamVz 98405 -5YCN 98406 -IGF0dHJhdmVycw== 98407 -IEFwaA== 98408 -ICcpOw0K 98409 -w7xuZGU= 98410 -IGVuY2hhbnRlZA== 98411 -IFJvb3RTdGF0ZQ== 98412 -IENMT1NFRA== 98413 -CQkJCQkJCQkNCg== 98414 -IGNhbGllbnRl 98415 -b3JyaXM= 98416 -IHBoeXNpY2lzdHM= 98417 -aHduZA== 98418 -X3Zp 98419 -IHLDoXBpZG8= 98420 -IGNhcGl0YWxpemVk 98421 -ZWRCeQ== 98422 -IG1hY2hpbmluZw== 98423 -IGh1YmJ5 98424 -IFN0YWN5 98425 -LkJ1cw== 98426 -ZHJpbms= 98427 -SHVy 98428 -IHByb3BpYQ== 98429 -VW5pdFRlc3Q= 98430 -IG1pc2NvbmNlcHRpb24= 98431 -X18pKTsK 98432 -L2Rj 98433 -IE1heXdlYXRoZXI= 98434 -X21D 98435 -LmNyZWF0ZUZyb20= 98436 -IFFQYWludGVy 98437 -cm9wc3ljaA== 98438 -aW5uaXR1cw== 98439 -YXlhcw== 98440 -IGdlZw== 98441 -KGR3 98442 -IHVzYWRv 98443 -IHRyaWNrbGU= 98444 -IGFubmloaWw= 98445 -IFBhc3Rh 98446 -ICsrCg== 98447 -KEV4cGVjdGVkQ29uZGl0aW9ucw== 98448 -LnBvc3RWYWx1ZQ== 98449 -aWNhcA== 98450 -IERvbmV0c2s= 98451 -X3NvdXA= 98452 -LXB1Ymxpc2g= 98453 -IFBi 98454 -bWVudGlvbnM= 98455 -QUNDRVBU 98456 -LlB1bGw= 98457 -LOKAmeKAmQ== 98458 -IHJldGFyZGVk 98459 -X0FUT00= 98460 -IFRlcm1pbmF0b3I= 98461 -LWNvdXJ0 98462 -IENMTG9jYXRpb25Db29yZGluYXRl 98463 -IHJldmVyZW5jZQ== 98464 -IFNTQw== 98465 -dXRlbHk= 98466 -IFdPTg== 98467 -IEdTTA== 98468 -ZnJlaQ== 98469 -LmdldExvbmdpdHVkZQ== 98470 -IG9wZW5GaWxlRGlhbG9n 98471 -LkJ1dHRlcg== 98472 -LWltcG9ydGFudA== 98473 -X01BTlk= 98474 -IEdvbmc= 98475 -4oCcSG93 98476 -IGdvcmdl 98477 -PW1zZw== 98478 -IEV6ZWs= 98479 -Y3JlYXRlQ29tbWFuZA== 98480 -OmNoZWNrZWQ= 98481 -IGluZm9ncmFwaGlj 98482 -LldFU1Q= 98483 -RGlycw== 98484 -IGd1YXJkYQ== 98485 -IGJlZXRsZQ== 98486 -PHNtYWxs 98487 -LWFuZHJvaWQ= 98488 -IGNyZWRpdG9y 98489 -IE3DqWQ= 98490 -IGZpbmFsaXN0 98491 -IGFibA== 98492 -bmV2 98493 -X2ludGVyYWN0aW9u 98494 -IE1vbnRlcmV5 98495 -amFo 98496 -IGNhbmRpZXM= 98497 -IFF1aW5jeQ== 98498 -6Kqt 98499 -IGJhdGNoU2l6ZQ== 98500 -YWtpdA== 98501 -IG9iZQ== 98502 -KHBhcmE= 98503 -IGV4cGVyaW1lbnRlZA== 98504 -IGNvdW5jaWxsb3Jz 98505 -IGNsYXNoZWQ= 98506 -c3F1 98507 -LXN0cm9rZXM= 98508 -IEdL 98509 -IEV4cGlyZXM= 98510 -IHByb3NlY3V0aW9ucw== 98511 -IENyZWF0dXJlcw== 98512 -IHnDtg== 98513 -eGxpbQ== 98514 -X0lNUA== 98515 -RW50cnlQb2ludA== 98516 -ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA= 98517 -LkRlZmF1bHRDZWxsU3R5bGU= 98518 -IGJyZXZl 98519 -IEJyaXRhbm4= 98520 -IHN3ZWF0eQ== 98521 -IGxldGg= 98522 -IGZsYXNoYmFjaw== 98523 -cGVybWFuZW50 98524 -IEpESw== 98525 -X0RldGFpbHM= 98526 -RXVybw== 98527 -cHB0 98528 -IHJpY2hUZXh0Qm94 98529 -L2JvYXJk 98530 -IHRyYW5jZQ== 98531 -LmN5Y2xl 98532 -Jyk7Iik7Cg== 98533 -IHRveGlu 98534 -X2RlaW5pdA== 98535 -IG92ZXJhcmNoaW5n 98536 -IGNvbmZpZ3BhcnNlcg== 98537 -IEthd2FzYWtp 98538 -LnRodW1i 98539 -IHBsYXlh 98540 -IEpvc2Vm 98541 -K18= 98542 -IHplcm9lcw== 98543 -IGF1cA== 98544 -IEhhcmk= 98545 -Y29tbWl0dGVk 98546 -Tml0 98547 -LmZpbGVQYXRo 98548 -IERpc2FiaWxpdGllcw== 98549 -bWFudWZhY3Q= 98550 -LWFsaWduZWQ= 98551 -LlJFU0VU 98552 -IHJ1c3R5 98553 -RXk= 98554 -IG91c3RlZA== 98555 -Y29zYQ== 98556 -U3RydWN0dXJlZA== 98557 -LmdldEQ= 98558 -IHPDoWJhZG8= 98559 -PkxvYWRpbmc= 98560 -X21B 98561 -LmdldFJhbmRvbQ== 98562 -Ymxpbmdz 98563 -IGNoZWVzZXM= 98564 -dHRp 98565 -LuKAog== 98566 -IEJ1cmdlc3M= 98567 -ZW5kZXJpdA== 98568 -LicsDQo= 98569 -KCIiKw== 98570 -YWNi 98571 -JXA= 98572 -aW5kZXhlZA== 98573 -X3ByZWRpY2F0ZQ== 98574 -bmVzaWE= 98575 -IGJpZWQ= 98576 -IENJVA== 98577 -KFBvcw== 98578 -X3JhZGk= 98579 -5Lu35qC8 98580 -Qml6 98581 -IEFkb2xlc2NlbnQ= 98582 -IHZpw6pu 98583 -Y3ljbA== 98584 -X0NhbmNlbA== 98585 -IGNvbmNsdXNpdmU= 98586 -IGFwcGVsbGF0ZQ== 98587 -aW5mb3JtYXRpY3M= 98588 -U0o= 98589 -IGVsZWN0aXZl 98590 -cm9sZUlk 98591 -RmV0Y2hlcg== 98592 -CUNvbW1hbmQ= 98593 -KCIoJQ== 98594 -IGZhcnQ= 98595 -SUxB 98596 -Z2V0QmxvY2s= 98597 -QVVTRQ== 98598 -INC00LDQvQ== 98599 -IEFydGU= 98600 -IG5vdGlmeWluZw== 98601 -IGdlbGU= 98602 -LnNhbWU= 98603 -IFJlZ2Vs 98604 -IEJhxZ8= 98605 -LmNyZWF0aW9u 98606 -IFZO 98607 -X2NvbW11bml0eQ== 98608 -IHVuc3VzdGFpbmFibGU= 98609 -U0VY 98610 -IGdyaWRTaXpl 98611 -cmVzY2lh 98612 -YXZlcnNhYmxl 98613 -KCcsJylb 98614 -IFBoZWxwcw== 98615 -4buVaQ== 98616 -QU5DRUxFRA== 98617 -LUlT 98618 -LnJ1bm5lcnM= 98619 -IFN0b2tlcw== 98620 -LlByb2R1 98621 -IHdoaXBwaW5n 98622 -X2FjcXVpcmU= 98623 -IGludmVzdGlnYWNpw7Nu 98624 -ZnJpZWQ= 98625 -LmNvcHlXaXRo 98626 -IEhhcmRjb3Zlcg== 98627 -LVNl 98628 -4Z624Z4= 98629 -aW52aXRhdGlvbg== 98630 -bGVzYWk= 98631 -IERvcm0= 98632 -INGB0L/QuNGB0LrQsA== 98633 -IGNvbmNhdGVuYXRlZA== 98634 -b3BoaWw= 98635 -IHRoaW5rZXI= 98636 -L2ZvbnRhd2Vzb21l 98637 -IExlb3BhcmQ= 98638 -ICIvIik7Cg== 98639 -IHJlc2lkdWFscw== 98640 -IE1pY3Jvd2F2ZQ== 98641 -IGNvbmZvcm1l 98642 -dGhyb3A= 98643 -IGRpc2VtYg== 98644 -IE9NRw== 98645 -IERpc2NpcGxpbmU= 98646 -IEFjcm9iYXQ= 98647 -L3JlcG9zaXRvcnk= 98648 -ZGZh 98649 -X01FRA== 98650 -YnVmaW8= 98651 -IG3DqXRob2Rl 98652 -X0hPTEQ= 98653 -aWFzaQ== 98654 -X2xlZ2FjeQ== 98655 -KQ0NCg== 98656 -5qOA 98657 -R2V0UHJvY0FkZHJlc3M= 98658 -IHlheQ== 98659 -b3RlbmNl 98660 -b3JkZXJpZA== 98661 -LXR3 98662 -IGRlYXJseQ== 98663 -SW5jb21pbmc= 98664 -L2ls 98665 -IG5ldXJvcA== 98666 -dWN6 98667 -KTsNDQ0K 98668 -IElubm92YXRpdmU= 98669 -IHByb2Z1bmQ= 98670 -aWdtYXQ= 98671 -U2VsZWN0aW9uTW9kZQ== 98672 -cmVsZXZhbnQ= 98673 -LkdP 98674 -IGJydWlzZXM= 98675 -IHNhY2g= 98676 -b2RlZg== 98677 -IHJlaW1i 98678 -L2Rlc2t0b3A= 98679 -LXNwb3Q= 98680 -dW5kYW5jZQ== 98681 -RW50cm9weQ== 98682 -XGNvcmU= 98683 -IHN1Z2Vy 98684 -IE12Yw== 98685 -IEdOT01F 98686 -X2luZHg= 98687 -IFlZU1RZUEU= 98688 -IE1hdGxhYg== 98689 -IENJRg== 98690 -ICopKQ== 98691 -IHByb2R1Y3RMaXN0 98692 -IEFscmlnaHQ= 98693 -YWNlbWFyaw== 98694 -0YLQuNCy 98695 -bW9kaWZpY2F0aW9u 98696 -aW50ZXJuYXRpb25hbA== 98697 -IGhvbWVycw== 98698 -IGRpY3Rz 98699 -IFFGb250 98700 -LlNRTGl0ZQ== 98701 -IHRyYW5zcGxhbnRhdGlvbg== 98702 -IE1lc3NhZ2VCb3hCdXR0b24= 98703 -IEVsdmVz 98704 -J11dKQo= 98705 -KFFJY29u 98706 -IGNpbmVtYXM= 98707 -Q09PUkQ= 98708 -LUNoaW5h 98709 -IGto4bqpdQ== 98710 -5oiR55qE 98711 -IHNrdWxscw== 98712 -IHBhaW5zdGFraW5n 98713 -ZmNl 98714 -LlhSTGFiZWw= 98715 -IHNwZWNpZmllcg== 98716 -IHByZWZlcnJpbmc= 98717 -L2FjdGl2aXR5 98718 -KFBob3Rv 98719 -w6FsdA== 98720 -LmxvdA== 98721 -Jycu 98722 -YW5ub25jZQ== 98723 -Lmdvb2dsZWNvZGU= 98724 -LXBkZg== 98725 -IFBva2U= 98726 -X0FDTA== 98727 -IGVuZG93ZWQ= 98728 -ZGlzY292ZXI= 98729 -Lm9tZw== 98730 -IHdvb2RsYW5k 98731 -Lk1hZ2lj 98732 -IHZvbG9udA== 98733 -Tm90QWxsb3dlZA== 98734 -IGNoYXZl 98735 -Qk1X 98736 -JywnPScs 98737 -IFNJWA== 98738 -5oiR5Lus 98739 -IGtvc2hlcg== 98740 -IGFzcGlyYXRpb24= 98741 -aW50bA== 98742 -X3JlZnB0cg== 98743 -JysK 98744 -bWVudG9y 98745 -LmNsdWI= 98746 -V2luZG93U3RhdGU= 98747 -LkFSUg== 98748 -IHp6YQ== 98749 -IG1lc3NhZ2VUeXBl 98750 -LmVxdQ== 98751 -VGhvcg== 98752 -IGluanVzdA== 98753 -IGd1bXM= 98754 -IGJvcmRlclNpZGU= 98755 -Ly8vLy8= 98756 -IFRyYW5zbWl0 98757 -IGJ1ZnNpemU= 98758 -IGhhaw== 98759 -IGVsbGFz 98760 -UkFORE9N 98761 -CW1j 98762 -IHBlYQ== 98763 -ZWtv 98764 -ZG9jdW1lbnRv 98765 -IGh5c3Rlcmlh 98766 -IGFyZW5hcw== 98767 -IGd1bm1lbg== 98768 -IG1pa2U= 98769 -IGltcHVuaXR5 98770 -YXRpc2F0aW9u 98771 -X1plcm8= 98772 -X0NPTVBBTlk= 98773 -IEdvcnM= 98774 -IHVzZUNsYXNz 98775 -KHJlZGlz 98776 -IFJVTk5JTkc= 98777 -IEJhaXI= 98778 -dmVsdGU= 98779 -ICcsJy4= 98780 -0LDRgtGM0YHRjw== 98781 -w7ZzdA== 98782 -ZW5jb2RlVVJJQ29tcG9uZW50 98783 -X3Jlc3RyaWN0 98784 -IGRlY2Fscw== 98785 -IFBlZGlkbw== 98786 -IGFsdGVyY2F0aW9u 98787 -RGlzcGxheXM= 98788 -IEFwcGxpY2FudHM= 98789 -Q1VT 98790 -VGV4dGFyZWE= 98791 -IEFuZ29sYQ== 98792 -LmZ1dHVyZQ== 98793 -IFVTSE9SVA== 98794 -IHN1cHByZXNzaW5n 98795 -IHNldHplbg== 98796 -QVBvbHlub21pYWw= 98797 -IHRvY2g= 98798 -IGhhbGxtYXJr 98799 -ICQkJA== 98800 -IENIQVJTRVQ= 98801 -LnJwbQ== 98802 -IERpY2g= 98803 -LS0tLS0tLS0tLS0tLS0tLS0tLS0= 98804 -X3Bhcm0= 98805 -6L+Y 98806 -YWNjaW9uZXM= 98807 -aGFpdA== 98808 -V0FSREVE 98809 -X3JvdXRpbmc= 98810 -IE5PTQ== 98811 -IGVuY2xhdmU= 98812 -IExvdHRv 98813 -CWZy 98814 -Y29tcGxleENvbnRlbnQ= 98815 -IEJhbGxhcmQ= 98816 -a3ViZQ== 98817 -L3dpbg== 98818 -LmdldENvbHVtbk1vZGVs 98819 -X1JFUExBQ0U= 98820 -SGVhZGVyVmFsdWU= 98821 -IGVzdHVkaWFudGVz 98822 -IGFwaXM= 98823 -IGJwbQ== 98824 -IFR5cGVOYW1l 98825 -QW5kR2V0 98826 -cml0YQ== 98827 -UGxhbnM= 98828 -Pk5vdGU= 98829 -IGZldGlzY2g= 98830 -IHRvbmVk 98831 -X2dvdG8= 98832 -b25zZW5zZQ== 98833 -IG1vbGRz 98834 -IGluZmlsdHJhdGlvbg== 98835 -IEd1ZXJyZXJv 98836 -dWJibw== 98837 -Y2tp 98838 -KCQoIi4= 98839 -X2FjdGl2aXRpZXM= 98840 -KGNoYW5nZXM= 98841 -IG9mQXBw 98842 -IEtlcGxlcg== 98843 -IERlbXA= 98844 -IENvbnRpbmVudA== 98845 -LlRpY2tz 98846 -IFVuc2lnbmVk 98847 -IEphaHJlcw== 98848 -IGZyZXNobWVu 98849 -IEFyY2hpdmVk 98850 -INC60L7RgtC+0YDRi9C5 98851 -ICc6Og== 98852 -VHV0b3JpYWw= 98853 -Q2M= 98854 -IHRhYmxlTGF5b3V0UGFuZWw= 98855 -ZnJvbUpzb24= 98856 -LmxldmVscw== 98857 -X3RyYW5zaWVudA== 98858 -IGVuZG9yc2luZw== 98859 -IERJQw== 98860 -bGF1Zg== 98861 -IHNocmVk 98862 -X0VNSVQ= 98863 -aWZpY2FudGx5 98864 -QUxB 98865 -L3Byb3Rv 98866 -IG5hcnJvd2luZw== 98867 -VXRj 98868 -RmFjdG9ycw== 98869 -IHNlbnRpZW50 98870 -5p6Q 98871 -bGl4aXI= 98872 -IENST1NT 98873 -bWV0ZW9y 98874 -IGdyb2lu 98875 -IG1kYg== 98876 -IFJvdHRlcmRhbQ== 98877 -IGNvbWlkYQ== 98878 -IE9wQ29kZQ== 98879 -IERlZmF1bHRWYWx1ZQ== 98880 -UGVybWlzc2lvbnNSZXN1bHQ= 98881 -IGhldGVyb2dlbmVvdXM= 98882 -IG1vb3Q= 98883 -IGRlY2VpdmVk 98884 -LWluZGVwZW5kZW50 98885 -IE9iamVjdE91dHB1dFN0cmVhbQ== 98886 -IG92ZXJwb3dlcg== 98887 -LmR1cA== 98888 -IGxkYg== 98889 -IGRvbWVzdGljYWxseQ== 98890 -IGJlc3RlbGxlbg== 98891 -IGxvdg== 98892 -IENvbnRyYWN0b3Jz 98893 -VHJpYW5nbGVz 98894 -IGZvZGRlcg== 98895 -IGZpbG1lcw== 98896 -5LyB 98897 -IHJldm9sdmVy 98898 -U3RhcnR1cFNjcmlwdA== 98899 -L3ZhbGlkYXRpb24= 98900 -IFJlc291cmNlVHlwZQ== 98901 -acWf 98902 -IExheg== 98903 -ZmVm 98904 -IGxzdG0= 98905 -eyo= 98906 -LmF0dGFjaG1lbnQ= 98907 -LmhpdHM= 98908 -ZXdpdGg= 98909 -RE9H 98910 -QWxhYmFtYQ== 98911 -IG1lZGl1bXM= 98912 -Lm1Db250ZXh0 98913 -LWNvbHM= 98914 -5Y+L 98915 -Lm5vdGljZQ== 98916 -IGF0dG4= 98917 -IFBhY2tpbmc= 98918 -IExu 98919 -X0NPTVBMRVg= 98920 -L1VzZXJz 98921 -LnNhdmV0eHQ= 98922 -IFJvdW5kcw== 98923 -Pyw/LD8sPyw= 98924 -IGluZ2w= 98925 -IFJPQw== 98926 -X2ZlbWFsZQ== 98927 -IFN0YXJk 98928 -XV07 98929 -IHdyZXN0bGVycw== 98930 -IHRvcnJlbnRz 98931 -IHNpbmg= 98932 -77u/Cgo= 98933 -67O1 98934 -c2Vuc2U= 98935 -aG93ZXZlcg== 98936 -LlBoeXNpY3M= 98937 -SW5mcmFzdHJ1Y3R1cmU= 98938 -IFNhY3I= 98939 -RmVs 98940 -IERJU1RSSUJVVA== 98941 -w6ltZW50cw== 98942 -IFZhbGlkYXRlcw== 98943 -IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMj 98944 -IHwv 98945 -IGVzbA== 98946 -IHLDqXNlYXU= 98947 -IEJpcA== 98948 -QllURVM= 98949 -X1dBVEVS 98950 -VHVybmluZw== 98951 -RUxT 98952 -IGp1eHRhcA== 98953 -IGxlc2Jpc2NoZQ== 98954 -w71jaA== 98955 -KFVua25vd24= 98956 -TmVv 98957 -QEpzb25Qcm9wZXJ0eQ== 98958 -IGFsdW1ub3M= 98959 -IFJhcXFh 98960 -aW1laQ== 98961 -LmdldEJvdW5kcw== 98962 -Lk1vdXNlRXZlbnRIYW5kbGVy 98963 -IyMjIyMjIw== 98964 -R2VuZXJpY1R5cGU= 98965 -L2Ntcw== 98966 -IHR1cm5v 98967 -INC80LjQvQ== 98968 -IGZvbGtsb3Jl 98969 -IEV2bw== 98970 -IGNvbmR1Y3Rpdml0eQ== 98971 -IGxlYmVu 98972 -IGdlYXJib3g= 98973 -LXZz 98974 -IM+G 98975 -IGRyaW5rZXJz 98976 -IGNvbmV4YW8= 98977 -IFRlZXRo 98978 -IGdldEFyZ3VtZW50cw== 98979 -IFJBVA== 98980 -ZW50aW91cw== 98981 -RWR1Yw== 98982 -K1c= 98983 -IEluc3RpdHV0aW9uYWw= 98984 -IEJvcmQ= 98985 -aXNFcXVhbA== 98986 -KHB3ZA== 98987 -IGlnbml0ZWQ= 98988 -IFJvdXNzZQ== 98989 -IGltcGFjdGZ1bA== 98990 -IE1hbGs= 98991 -IGdlcmFs 98992 -IFBpdm90 98993 -IGF6dA== 98994 -IGNzdmZpbGU= 98995 -IFJvcGU= 98996 -IFNPTFVUSU9O 98997 -IEFyYml0cmFyeQ== 98998 -IGxldHRv 98999 -Lk1vdXNlQWRhcHRlcg== 99000 -IH19fQ== 99001 -IFNhaWxvcg== 99002 -ZGVyYQ== 99003 -UHV0dGluZw== 99004 -IGNvbmNlbnRyYXRlcw== 99005 -IGF1dGhEb21haW4= 99006 -4oCd55qE 99007 -LWZpbmFscw== 99008 -LHN0cmxlbg== 99009 -TXVvbg== 99010 -IE9yZGluYXJ5 99011 -ZmlyZWZveA== 99012 -IExhVGVY 99013 -IEh1bmQ= 99014 -ZW5naW5lZXJpbmc= 99015 -L2JsdWU= 99016 -ZWRUZXh0Qm94 99017 -KCIiKTs= 99018 -IENEREw= 99019 -a2VwdA== 99020 -IEdldFN0cmluZw== 99021 -S2ly 99022 -KCk9Jw== 99023 -IE9DRA== 99024 -YW50aXVt 99025 -JG1lbnU= 99026 -IEFwcGFsYWNoaWFu 99027 -U2VjcmV0YXJ5 99028 -66WY 99029 -4Li14Lii 99030 -U2VtYW50aWM= 99031 -ICpb 99032 -ZXN0b25l 99033 -dW5na2lu 99034 -TWF4WQ== 99035 -LXRvbmU= 99036 -In07DQo= 99037 -X1BhcnQ= 99038 -PE1lbWJlcg== 99039 -dHJhbQ== 99040 -IHRyYW5zaXN0b3I= 99041 -IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCg== 99042 -IERlc2Rl 99043 -IHJpZ2h0ZnVs 99044 -IENvcm5lbA== 99045 -5pE= 99046 -LkhPVVI= 99047 -IHNpZGVsaW5lZA== 99048 -cmVmZXJyZXI= 99049 -bWF6ZQ== 99050 -IGhvbHN0ZXI= 99051 -IGNyaXBwbGVk 99052 -IERhdGVGb3JtYXR0ZXI= 99053 -b3BoYWdl 99054 -X21E 99055 -IGRlc2VsZWN0 99056 -cmF1ZA== 99057 -IFBLSw== 99058 -cm93RGF0YQ== 99059 -IGxvY2tzbWl0aA== 99060 -LnJlc3BvbnNlcw== 99061 -KHByb2R1Y3RJZA== 99062 -X1NUTVQ= 99063 -S2V5VHlwZQ== 99064 -LlRoZW4= 99065 -emVl 99066 -IGNydA== 99067 -IEdyYW5kbWE= 99068 -QFJlc291cmNl 99069 -IGJpdHdpc2U= 99070 -LWNtcHI= 99071 -44CCd3d3 99072 -emVpdGln 99073 -JmRpc3BsYXk= 99074 -Q2FydEl0ZW0= 99075 -LU5v 99076 -IG51bcOpcm8= 99077 -IG1hdXI= 99078 -IGluc3RhbmNpYQ== 99079 -CWR0 99080 -X25wYw== 99081 -IHNrYXRlYm9hcmQ= 99082 -4oCcQWxs 99083 -IENyb3dk 99084 -IMOkbg== 99085 -IGJyYXo= 99086 -Y2Fl 99087 -eW5ldA== 99088 -L3Bt 99089 -L3NjcmVlbg== 99090 -T1BUQVJH 99091 -IFZCb3g= 99092 -IGxlb3BhcmQ= 99093 -X2dyZWF0ZXI= 99094 -Y3B0 99095 -PGRk 99096 -IG1lY2hhbmljYWxseQ== 99097 -b3NwZWxz 99098 -KWY= 99099 -Lmx3amds 99100 -LmdldFBvcnQ= 99101 -IFBSRUY= 99102 -LkFkZFRyYW5zaWVudA== 99103 -cHBhcmQ= 99104 -IO2ajA== 99105 -RXRoZXJuZXQ= 99106 -IHNhbGluZQ== 99107 -KGxldmVscw== 99108 -IHNlcnZpY2VQcm92aWRlcg== 99109 -LkFuZ2xl 99110 -YWx0aXR1ZGU= 99111 -aWxsYXVtZQ== 99112 -IHNjYXBl 99113 -X0NBTEM= 99114 -X3F1ZXN0 99115 -IERpc3NlcnRhdGlvbg== 99116 -IEVETQ== 99117 -LUNkcw== 99118 -IGhvbm9yYXJ5 99119 -c3RvcHM= 99120 -IHN1YmRpcg== 99121 -IFZI 99122 -IENoZWF0 99123 -IHJpZ2h0ZnVsbHk= 99124 -UUU= 99125 -LldyaXRlQnl0ZQ== 99126 -ZmlndXJlcw== 99127 -ZW5uaWU= 99128 -KERCRw== 99129 -IHZva3NuZQ== 99130 -IGV4cGVuZGVk 99131 -VU5JQ0FUSU9O 99132 -aWxpbng= 99133 -IFJlY2Fw 99134 -X3ZlcnRz 99135 -IHRyYXVtYXQ= 99136 -IGdldFBsYXllcg== 99137 -IHZlcmJlc3M= 99138 -IGN1bHRpdmF0aW5n 99139 -IGluaXRpYXRvcg== 99140 -VGjDtG5n 99141 -ZmluZEZpcnN0 99142 -X3Blcm1z 99143 -IGJ1Yw== 99144 -ICIiIg0KDQo= 99145 -VFlQRVM= 99146 -b2JqZWN0TWFuYWdlcg== 99147 -KENvbmZpZ3VyYXRpb25NYW5hZ2Vy 99148 -IHRpbWlk 99149 -IHNuYXBjaGF0 99150 -IGNvbnNlZw== 99151 -CWRpc3RhbmNl 99152 -X3JpZ2h0cw== 99153 -X0Rlcw== 99154 -IEZsZXNo 99155 -LXZlcg== 99156 -IGFmbA== 99157 -ZnJhdWVu 99158 -IGJsYXNwaA== 99159 -IFF1YWxpdMOkdA== 99160 -bWFm 99161 -TW9uaXRvcmluZw== 99162 -LkRpZmY= 99163 -IHNob3JlbGluZQ== 99164 -IHJlc3BvbnNlQm9keQ== 99165 -bWVtc2V0 99166 -PGRlY2ltYWw= 99167 -U21hcnR5SGVhZGVyQ29kZQ== 99168 -IGluc2V0cw== 99169 -IEJpbmFyeVRyZWU= 99170 -YW1lZGE= 99171 -IG5paGls 99172 -IE5heQ== 99173 -eW1vbG9neQ== 99174 -IFdH 99175 -IHRhcGk= 99176 -IEluc3RhbGxlZA== 99177 -bWFpbnRlbmFuY2U= 99178 -KX0iCg== 99179 -IFhP 99180 -LXBlcmlvZA== 99181 -c2Fy 99182 -IG5pbmd1bmE= 99183 -T1JNQVQ= 99184 -LnNldFByb3RvdHlwZU9m 99185 -IEti 99186 -IEhlbnJpaw== 99187 -w6l0aXF1ZQ== 99188 -IExhaG9yZQ== 99189 -CUFkZHJlc3M= 99190 -IG1lbHRz 99191 -Tnk= 99192 -X2FkdmFuY2U= 99193 -IHZlbG9jaWRhZA== 99194 -IGFsdW1ubw== 99195 -IHNhbml0aXplcg== 99196 -IHBoaXNoaW5n 99197 -IENvbWV0 99198 -IGNoaWFy 99199 -CXNwZWM= 99200 -dHJpbW1lZA== 99201 -KHN0YXRlYXJy 99202 -b25uZW4= 99203 -UmV2ZW51ZQ== 99204 -TGVucw== 99205 -IGNoYWlyZWQ= 99206 -IEFzc3VtZXM= 99207 -VHJhc2g= 99208 -X3Vuc2V0 99209 -XEJyaWRnZQ== 99210 -UG9pbnRTaXpl 99211 -IFBvbGlj 99212 -IHNleHVhbGVz 99213 -CWRmcw== 99214 -IFdpZGVTdHJpbmc= 99215 -IGFjY3J1ZWQ= 99216 -WVc= 99217 -X1NDSEVEVUxF 99218 -IGtpdGU= 99219 -IHBhcmFjaHV0ZQ== 99220 -W3RhYmxl 99221 -IGFjdGl2ZUNsYXNzTmFtZQ== 99222 -LlF1YWQ= 99223 -SXNyYWVsaQ== 99224 -IMWT 99225 -IGhvb2c= 99226 -IGNo4buJ 99227 -ZXdlYXI= 99228 -IHRpcmVsZXNzbHk= 99229 -c2V0RXJyb3I= 99230 -LmdldEFtb3VudA== 99231 -LnNldEl0ZW1z 99232 -IE1hbnNvbg== 99233 -IEJheWVzaWFu 99234 -X0ZsYWc= 99235 -QUNIRVI= 99236 -L29yaWdpbmFs 99237 -IGltbWFj 99238 -IExvc2luZw== 99239 -Jz4KCg== 99240 -TGlj 99241 -IE1pcmFnZQ== 99242 -IEFzc2VtYmx5RmlsZVZlcnNpb24= 99243 -VGVW 99244 -IFZhbHVlRXZlbnRMaXN0ZW5lcg== 99245 -LXNvbHZpbmc= 99246 -VGhv 99247 -cm91bGV0dGU= 99248 -X1dQ 99249 -IHVuaW50ZXJydXB0ZWQ= 99250 -IGZpZWxkVHlwZQ== 99251 -LlR5cGVk 99252 -IGFtb3Vy 99253 -IG1vY2tlcnk= 99254 -KHZvbA== 99255 -IFN1YmNvbW1pdHRlZQ== 99256 -IFJ1Zg== 99257 -ZXJveA== 99258 -OlVJQnV0dG9uVHlwZUN1c3RvbQ== 99259 -IEJsdXI= 99260 -IHd5a29u 99261 -bmNlcw== 99262 -QVNIQk9BUkQ= 99263 -ISEiKTsK 99264 -IG11cmRlcmVycw== 99265 -LmRhaWx5 99266 -IERJQUc= 99267 -amluZw== 99268 -IGRvbHBoaW4= 99269 -IGzDsm5n 99270 -IGLDtg== 99271 -IFZvY2FidWxhcnk= 99272 -LlN0T2JqZWN0 99273 -JykiPg== 99274 -IHp1bg== 99275 -IHNjcmltbWFnZQ== 99276 -dHLDqWFs 99277 -IExpZw== 99278 -W3Zp 99279 -Q29sZQ== 99280 -IGZyb3N0aW5n 99281 -LlBsYXllcnM= 99282 -LXRyYW5zbGF0ZQ== 99283 -RmVlbHM= 99284 -PVwiLw== 99285 -LkJ1dHRlcktuaWZl 99286 -ID8+Owo= 99287 -IGF2aQ== 99288 -aW5uaWU= 99289 -LkZhaWx1cmU= 99290 -IHNwaW5kbGU= 99291 -Q29uZmlndXJhdGlvbkV4Y2VwdGlvbg== 99292 -X2hvcA== 99293 -IHBvc2nDp8Ojbw== 99294 -IEF3YWl0 99295 -VUlJbWFnZVBpY2tlckNvbnRyb2xsZXI= 99296 -CWRheQ== 99297 -IGdlbm9t 99298 -Q2Fi 99299 -INGA0LXQt9GD0LvRjNGC0LDRgg== 99300 -T1JJR0lOQUw= 99301 -IGVqYWN1bGF0aW9u 99302 -KHRjcA== 99303 -U0VDT05E 99304 -IHRvbmlj 99305 -IExpc3RCb3g= 99306 -IAkJCg== 99307 -KCk+Cg== 99308 -IHF1YXRyZQ== 99309 -xrDhu6NuZw== 99310 -d2l0aEVycm9ycw== 99311 -Lk1heWJl 99312 -LOKApg== 99313 -dG9rZW5JZA== 99314 -X1VOREVG 99315 -IGZyZXNobmVzcw== 99316 -IEFtZW5kbWVudHM= 99317 -Lm1hcGJveA== 99318 -LkNW 99319 -KGJsb2c= 99320 -X2dldHRpbWU= 99321 -LnF1ZXN0 99322 -c3BhcnNl 99323 -IHJlc2FsZQ== 99324 -IGVudGh1c2lhc3RpY2FsbHk= 99325 -IFByb3N0aXR1dGFz 99326 -V2E= 99327 -Q2FyZ28= 99328 -LlBhcmNlbGFibGU= 99329 -U0VOU09S 99330 -IFJ5dQ== 99331 -TGF1Z2hz 99332 -X05hdGl2ZQ== 99333 -L3Bn 99334 -eXN0cw== 99335 -IHBob3RvYw== 99336 -566A 99337 -YWRvcHQ= 99338 -LnNwZWNpZXM= 99339 -Y29uY2lsaWF0aW9u 99340 -QWRqdXN0ZWQ= 99341 -LkZpcmViYXNlQXV0aA== 99342 -dXR0bGU= 99343 -b3JkaW5hdGlvbg== 99344 -IG11bmNo 99345 -IFN0YWtl 99346 -LnBpbmc= 99347 -YW5rZXI= 99348 -KFFTdHJpbmdMaXRlcmFs 99349 -IHN1YnNjcmlwdA== 99350 -ICAJCg== 99351 -IE1DQw== 99352 -X0NtZA== 99353 -c2V4eQ== 99354 -aW91 99355 -IE1BTlk= 99356 -IG5hbm55 99357 -VFJBSU4= 99358 -IGZsb3VyaXNoaW5n 99359 -IFdhdGNoZXM= 99360 -IFFNYXA= 99361 -IEZlcm0= 99362 -IHdhc20= 99363 -IEFiZWQ= 99364 -X1VE 99365 -IEdsYXNzZXM= 99366 -K3Y= 99367 -QXR0ZW5k 99368 -LkNoYWlu 99369 -IGRlY2VuY3k= 99370 -IFN1cHBsZW1lbnRhcnk= 99371 -aHVudGVy 99372 -LXR4dA== 99373 -ICJ9IjsK 99374 -LnNldFdpbmRvd1RpdGxl 99375 -KCI8Pw== 99376 -IG51bWJlcldpdGhJbnQ= 99377 -IGFmYXI= 99378 -56e75Yiw 99379 -cml0dGU= 99380 -L2xpc3Rz 99381 -KeKAnQ== 99382 -IGRpdmVyc2Fz 99383 -IGVtYmVy 99384 -LlJlYWN0Tm9kZQ== 99385 -IGthbmc= 99386 -IFN0YW1mb3Jk 99387 -W2F0 99388 -LmNsb3NlUGF0aA== 99389 -IGNvbnRyYWNlcHRpdmU= 99390 -KGxvY2F0aW9ucw== 99391 -IGF2YW56 99392 -IENvbnRhaW5lcnM= 99393 -IFNjaG9sYXJz 99394 -LmFjY3VyYWN5 99395 -INCy0YvQv9C+0LvQvQ== 99396 -5ZWP 99397 -PSItLQ== 99398 -IFdyZXN0bGU= 99399 -IEd1YW50YW5hbW8= 99400 -IG55bXBo 99401 -KGd1ZXNz 99402 -LnNldENvbHVtbg== 99403 -X3RF 99404 -LmNvbnRlbnRNb2Rl 99405 -IGludmFsaWRhdGVk 99406 -IFNob290ZXI= 99407 -IE1hdGVy 99408 -LlN1Ym1pdA== 99409 -IGFuZ2xlZA== 99410 -bmF2YmFyRHJvcGRvd24= 99411 -QW8= 99412 -IOa1 99413 -0LjRgdC6 99414 -IFNDQU4= 99415 -CWNt 99416 -IE1hcmt0 99417 -dHJ1Y2s= 99418 -OycK 99419 -Ly8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8KCg== 99420 -IGdoZXR0bw== 99421 -IGJ1aXRlbg== 99422 -IENsb3du 99423 -OiE= 99424 -IGNoaW1wYW4= 99425 -J2ZpZWxk 99426 -YW1tbw== 99427 -IERlcGVuZA== 99428 -KX0p 99429 -KEZMQUdT 99430 -IFJDQQ== 99431 -IENob2ly 99432 -TG9naW5QYWdl 99433 -IEdvcmQ= 99434 -Q29tcGFjdA== 99435 -LXBvY2tldA== 99436 -IGNvbnN1bHRhcg== 99437 -IEludGVyY2VwdA== 99438 -xZ90aXI= 99439 -dWV0eXBl 99440 -b25lbnRz 99441 -IHN0YXJ0UG9zaXRpb24= 99442 -IHBvc2l4 99443 -IFdvaG51bmc= 99444 -X0VYUFJFU1NJT04= 99445 -IExvZ2luQWN0aXZpdHk= 99446 -KG9wY29kZQ== 99447 -IFRhbmdv 99448 -IE51bWJlck9m 99449 -Lm92ZXJmbG93 99450 -IFdDUw== 99451 -IE9jY3VwYXRpb24= 99452 -X2Nn 99453 -LlRvcGlj 99454 -IENhcmVlcnM= 99455 -QVJBVElPTg== 99456 -LmdldExpbmU= 99457 -IOyihQ== 99458 -IE5hY2h0 99459 -IHRvSXRlbQ== 99460 -aW5jbHVzaXZl 99461 -YXZpZXN0 99462 -LWFwcG9pbnRlZA== 99463 -KGludGVybmFs 99464 -Q09OVEVYVA== 99465 -KGRpZ2l0cw== 99466 -PXsiLw== 99467 -IHBsYXl3cmlnaHQ= 99468 -IGRlYWRsaWVzdA== 99469 -bGVhZHM= 99470 -LlBVVA== 99471 -ICp9Cgo= 99472 -IFBhY3Q= 99473 -IERpc2NvdW50cw== 99474 -TG9jYWxpemVkTWVzc2FnZQ== 99475 -IE3DpG5uZXI= 99476 -Xz4= 99477 -IG1hc2NhcmE= 99478 -KFByb2ZpbGU= 99479 -5Yqf6IO9 99480 -aW1pdMOp 99481 -IHdpbGRmaXJlcw== 99482 -LVJPTQ== 99483 -LmlzT24= 99484 -KGdyb3VwSWQ= 99485 -UmVwYWly 99486 -YWNjdW11bGF0ZQ== 99487 -IDwiLA== 99488 -IGhhbmR3cml0dGVu 99489 -IGFjaGV0ZXI= 99490 -IE1HTQ== 99491 -IElybWE= 99492 -LT57Xw== 99493 -Z2Vl 99494 -Y3JpbWluYWw= 99495 -IOiLpeimgQ== 99496 -IG1vbWVudGFyaWx5 99497 -IikhPQ== 99498 -X2xpdA== 99499 -IGV4cGlyZXNJbg== 99500 -LiIpLg== 99501 -6ZW/5bqm 99502 -IGZyw6Zra2U= 99503 -dmxj 99504 -IG9yYnM= 99505 -KSwk 99506 -IHZlbnR1cmVk 99507 -Lz5c 99508 -Y2hhcm0= 99509 -TnVpdGth 99510 -ZWxkaWc= 99511 -YXRvbmlu 99512 -V2l0bmVzcw== 99513 -LWxhdA== 99514 -IHNldEhpZGRlbg== 99515 -IHJlbGljcw== 99516 -IGNvbnN1bGF0ZQ== 99517 -LklHTk9SRQ== 99518 -IkFmdGVy 99519 -IHNldEFkZHJlc3M= 99520 -IGJlc3RlaHQ= 99521 -ICcnKQoK 99522 -LnhheGlz 99523 -IHNlcsOjbw== 99524 -IG1pc2xlZA== 99525 -X1VOSUZPUk0= 99526 -IFZJQQ== 99527 -aW5jcg== 99528 -IHplbml0aA== 99529 -IHZpc2Nvc2l0eQ== 99530 -IHRoaW5seQ== 99531 -LmdldFNoYXJlZFByZWZlcmVuY2Vz 99532 -LkVycm9yQ29kZQ== 99533 -IiksIg== 99534 -IE1pbGxpb25lbg== 99535 -IC8+KQo= 99536 -U2Nyb2xsSW5kaWNhdG9y 99537 -LXNlZWtpbmc= 99538 -IFBPTElUSUNP 99539 -YXNjYQ== 99540 -X3Js 99541 -TmF2aWc= 99542 -KGZ1bGxmaWxl 99543 -IHNvbGl0dWRl 99544 -IGp1dmVu 99545 -IGhhdWxpbmc= 99546 -IE1hY3Jvcw== 99547 -IEdyeQ== 99548 -IGV4ZXJjaXRhdGlvbg== 99549 -IEFUVEFDSw== 99550 -VGlja0NvdW50 99551 -IHJpdGVz 99552 -IGRvZQ== 99553 -UGFydGljbGVTeXN0ZW0= 99554 -IHNsdQ== 99555 -V2luZG93VGV4dA== 99556 -IENsYXNzTmFtZQ== 99557 -IHNsYW5kZXI= 99558 -CVBvcnQ= 99559 -am9uZw== 99560 -P2E= 99561 -LkRpYWw= 99562 -4oCUYXQ= 99563 -JG9ialBIUEV4Y2Vs 99564 -IHNvYXI= 99565 -RU5O 99566 -YXBwZWFyZWQ= 99567 -IHF1b3RpZA== 99568 -ZW1hY2hpbmU= 99569 -IG5pcA== 99570 -IG1pY3JvdGltZQ== 99571 -IEFsbWE= 99572 -OyE= 99573 -LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t 99574 -IFBhc3NhZ2U= 99575 -IGR1bXBzdGVycw== 99576 -IEV4Y2x1ZGU= 99577 -IHN1Z2dlc3RpdmU= 99578 -IENpcmN1bGFyUHJvZ3Jlc3NJbmRpY2F0b3I= 99579 -X2Nscg== 99580 -QXJyYXlUeXBl 99581 -SUxMQQ== 99582 -RWxhcHNlZFRpbWU= 99583 -RHJpdmVu 99584 -IHJlc291cmNlTmFtZQ== 99585 -IEdhcnJpc29u 99586 -c2VyaXI= 99587 -LWFoZWFk 99588 -IHBpbm5hY2xl 99589 -IEVzcHJlc3Nv 99590 -U3BhcnNl 99591 -IGFzc2F5cw== 99592 -IEdpcmxmcmllbmQ= 99593 -aW1pZA== 99594 -XT0nXA== 99595 -T05HTE9ORw== 99596 -IHBvcnRyYXlpbmc= 99597 -TGFuZQ== 99598 -IGLDunNxdWVkYQ== 99599 -IHJlaW5mb3JjZW1lbnRz 99600 -IFNwcmVhZHNoZWV0 99601 -IEFycmF5Q29sbGVjdGlvbg== 99602 -LGFycg== 99603 -bGlnaHRib3g= 99604 -aWNhbmE= 99605 -PCI= 99606 -YnVpbGRlcnM= 99607 -S2lk 99608 -IE1hdFNuYWNrQmFy 99609 -RVhQUg== 99610 -b2RjYXN0 99611 -IEZvdW5kYXRpb25z 99612 -IGluZHM= 99613 -PSckew== 99614 -Rml6eg== 99615 -LWZ1bmN0aW9uYWw= 99616 -KHdvcmtzcGFjZQ== 99617 -IHN0ZW1tZWQ= 99618 -X3BhdGNoZXM= 99619 -IEphcnZpcw== 99620 -UkVBRElORw== 99621 -IGRpc3Jlc3BlY3RmdWw= 99622 -IFFEb20= 99623 -ICR7Cg== 99624 -ZXN0YXR1cw== 99625 -UmVhY2hlZA== 99626 -IS4KCg== 99627 -SUxU 99628 -IE5ERUJVRw== 99629 -IENvdXJhZ2U= 99630 -YmlydGhkYXRl 99631 -IFRpbmc= 99632 -IHV0aWxpemFkbw== 99633 -w6FuY2hleg== 99634 -T3V0ZG9vcg== 99635 -IGhhbmRndW5z 99636 -UmVmQ291bnQ= 99637 -yZk= 99638 -cm9tbw== 99639 -IHR0cw== 99640 -LlNoZQ== 99641 -IFBhbmU= 99642 -44CRLOOAkA== 99643 -IElPQ1RM 99644 -L2JsYWNr 99645 -aW5zY3JpcHRpb24= 99646 -IGJpb3BzeQ== 99647 -IFRpbWVJbnRlcnZhbA== 99648 -LlRlc3RDaGVjaw== 99649 -IEdVSVN0eWxl 99650 -IENhcGFiaWxpdHk= 99651 -IEJlaXRyYWc= 99652 -ZG9ubmVlcw== 99653 -VHJlYXRtZW50 99654 -LmJhY2t1cA== 99655 -IHNpZ25pbmdz 99656 -IEJvY2E= 99657 -ZHJt 99658 -Lk1BSU4= 99659 -IGdvZWRl 99660 -IE1hcmt1cA== 99661 -R1JFRQ== 99662 -IEJhc2VTZXJ2aWNl 99663 -LkNyZWF0b3I= 99664 -IGphaWxz 99665 -IEthaG4= 99666 -SXBBZGRyZXNz 99667 -QUNISQ== 99668 -IGluaGliaXRlZA== 99669 -IEAkXw== 99670 -IEFzc2Fzcw== 99671 -IGVudmlhZG8= 99672 -SGVyb2Vz 99673 -0J/QtdGA 99674 -IE1hdmVu 99675 -Lmxz 99676 -IGl2ZQ== 99677 -fFJG 99678 -IHJlc2l6ZU1vZGU= 99679 -IHJ1bXBl 99680 -X2F0dGFjaG1lbnRz 99681 -VFU= 99682 -IHRhY3RpbGU= 99683 -QXR0ZW1wdGluZw== 99684 -IHJvYmlu 99685 -eWF3 99686 -IG1lcmNlbmFyaWVz 99687 -IEhhYml0YXQ= 99688 -ZW5kZGF0ZQ== 99689 -IG94eQ== 99690 -CVJhbmRvbQ== 99691 -b2hvbg== 99692 -SXNOdWxs 99693 -IFZhbGlkYXRpb25SZXN1bHQ= 99694 -44Oa 99695 -dW1iZWQ= 99696 -cHB2 99697 -IGFycA== 99698 -aWNoaWNr 99699 -X3Jubg== 99700 -IFRGVA== 99701 -VGV4SW1hZ2U= 99702 -Ik9u 99703 -IFNhbXBsZXI= 99704 -dG9wbA== 99705 -IGphbmU= 99706 -eWxpbmc= 99707 -IFVOSUNPREU= 99708 -VGFiSW5kZXg= 99709 -PHsK 99710 -c3VzcGVuZA== 99711 -dXZpYW4= 99712 -LGFwcGxpY2F0aW9u 99713 -0L7Qu9C40YfQtdGB0YLQstC+ 99714 -eWF0 99715 -ZXppZXI= 99716 -IENIVU5L 99717 -IEFkbGVy 99718 -L0FkZA== 99719 -IEtleVZhbHVl 99720 -IHNwb3PDs2I= 99721 -U2FtcGxpbmc= 99722 -Y2hlcnM= 99723 -X0FNRA== 99724 -UnU= 99725 -Lk11c3RDb21waWxl 99726 -TmF0aW9u 99727 -QXNzb2M= 99728 -TWFuYWdpbmc= 99729 -IEVuZ2w= 99730 -X0dC 99731 -IHN1Y2NpbmN0 99732 -IGRpc2xpa2Vk 99733 -IElrZQ== 99734 -QnVsbGV0aW4= 99735 -X0FSQ0hJVkU= 99736 -UHJvcG9zYWw= 99737 -IGpvZ2dpbmc= 99738 -LkNSRUFURUQ= 99739 -IGNob2w= 99740 -6KOF 99741 -jKg= 99742 -LXB1c2g= 99743 -IHJlc2VydmE= 99744 -Y29yZXY= 99745 -w6h0cmU= 99746 -VEhS 99747 -IGluY29tcGV0ZW5jZQ== 99748 -IGNoYXJpc21h 99749 -5oSf 99750 -ICI9PQ== 99751 -QlRO 99752 -IExvY2F0b3I= 99753 -aXZldA== 99754 -KCcuJykK 99755 -IGZvckluZGV4UGF0aA== 99756 -w7RtZQ== 99757 -IGNhcGFjaXQ= 99758 -d2F0ZXJz 99759 -IFdST05H 99760 -aG9h 99761 -IE1JUFM= 99762 -IGVtaXNz 99763 -IEphY3F1ZWxpbmU= 99764 -KGNtcA== 99765 -IGVlbnM= 99766 -TGVv 99767 -LnRpbWluZw== 99768 -Q0xVU0lPTg== 99769 -ICgiLQ== 99770 -5ZOI 99771 -LmtvZGU= 99772 -IFVuZGVydA== 99773 -IGJld2lsZA== 99774 -IEVzc2Vu 99775 -Lmhk 99776 -IHJlbmVnb3Q= 99777 -IG1vd2Vy 99778 -IGxzcA== 99779 -IHBlbmNoYW50 99780 -IG1hbm9l 99781 -IGFnbGk= 99782 -IHJlY2Fs 99783 -IE9QRVJBVElPTg== 99784 -KF4pKA== 99785 -IM69 99786 -IFNjb3BlZA== 99787 -IEAiCg== 99788 -PWxhYmVs 99789 -W2xvYw== 99790 -SW50bA== 99791 -IE56 99792 -dGFibGV0 99793 -LkNvbHVtbk5hbWU= 99794 -IHNjcmVlblNpemU= 99795 -REJ1cw== 99796 -Y29va2Vk 99797 -LXJlZ2lzdHJhdGlvbg== 99798 -4oCcT25l 99799 -LW5vbg== 99800 -IHdpxJlj 99801 -IGNvc3Rh 99802 -LmFkZFRhYg== 99803 -LmNvbmRpdGlvbnM= 99804 -IEhlc3M= 99805 -TUVNT1JZ 99806 -IEF2YWxhbmNoZQ== 99807 -KCl9fQo= 99808 -IHRyaXBsZXQ= 99809 -IGxhYnlyaW50aA== 99810 -IE5vZGVMaXN0 99811 -IE5ZVA== 99812 -IHllbmk= 99813 -ZGZm 99814 -Lkh0bWxDb250cm9scw== 99815 -QVZJUw== 99816 -L01hdGg= 99817 -IG1lbWNtcA== 99818 -2KfYoQ== 99819 -0L7RgdGM 99820 -Y3JhcA== 99821 -KHBhZ2Vz 99822 -IGx4bWw= 99823 -IFFEYXRlVGltZQ== 99824 -X3RjYg== 99825 -IG9wZW5pZA== 99826 -IHN5bmFwdGlj 99827 -IE1ETUE= 99828 -KHNsdWc= 99829 -aWdtYXRpYw== 99830 -ZW5vcg== 99831 -IGNyYW1wZWQ= 99832 -R09Q 99833 -rZA= 99834 -LmlzRmlsZQ== 99835 -IERpZmZlcmVudGlhbA== 99836 -ID0iIjsK 99837 -CQkJICAgIAk= 99838 -IENvb2tl 99839 -CVVGVU5DVElPTg== 99840 -IHBlcnNldmVyYW5jZQ== 99841 -UmVsYXRpdmVMYXlvdXQ= 99842 -SU1QT1JUQU5U 99843 -IGV4b24= 99844 -INC+0L0= 99845 -aWJhc2U= 99846 -KENPTlQ= 99847 -bm92YXRpb24= 99848 -5L2V 99849 -W3N1Yg== 99850 -QWRtaW5Db250cm9sbGVy 99851 -SFRUUEhlYWRlcg== 99852 -Y3JlYXI= 99853 -IE5JUg== 99854 -IERyb3BEb3duTGlzdA== 99855 -IHZhbGlkZQ== 99856 -IGRlaHlkcmF0aW9u 99857 -Lidd 99858 -KFdJTg== 99859 -IC4uLlw= 99860 -IHBob3Rvc2hvcA== 99861 -CUluaXQ= 99862 -X2NvdQ== 99863 -IHRpbWVab25l 99864 -ZGFyd2lu 99865 -cm9tYXRpYw== 99866 -TmF2aWdhdGlvbkl0ZW1TZWxlY3RlZExpc3RlbmVy 99867 -YnJhdGVz 99868 -XS0tOwo= 99869 -IHRyYWdlZGllcw== 99870 -IFBlZGlhdHJpY3M= 99871 -U01BUlQ= 99872 -LUFQSQ== 99873 -IE1lc3NhZ2VMb29rdXA= 99874 -CXZv 99875 -IHByZWp1ZGljZXM= 99876 -IG1B 99877 -VXBz 99878 -IE1JU1NJTkc= 99879 -CWFk 99880 -Q3JlYW0= 99881 -IFRi 99882 -IE1vbmE= 99883 -X2dob3N0 99884 -CXR5cGVz 99885 -RW1i 99886 -IERvY3VtZW50YXJ5 99887 -Jyk7CgoKCg== 99888 -IGx1cA== 99889 -X1JlZmVyZW5jZQ== 99890 -IEJBVENI 99891 -IGludGVydHdpbmVk 99892 -PENlbGw= 99893 -IENhYnI= 99894 -bmF0aW9u 99895 -IGlzQ29ubmVjdGVk 99896 -LnJlbW92ZUxpc3RlbmVy 99897 -IGNvbmc= 99898 -X3Rp 99899 -IFNpbGljb25l 99900 -IOqysOqzvA== 99901 -IFdBTg== 99902 -IEdpYnJhbHRhcg== 99903 -L3Jlc3BvbnNl 99904 -CXBlcnNvbg== 99905 -Y2hhbnRz 99906 -VklQ 99907 -ZW1lcmdlbmN5 99908 -UGl4ZWxGb3JtYXQ= 99909 -LUFt 99910 -IHNvdXRod2VzdGVybg== 99911 -X3BsbA== 99912 -aWZlcnM= 99913 -X09OQ0U= 99914 -IEZheWV0dGU= 99915 -Lm5jYmk= 99916 -X1BhbmVs 99917 -LlF1YWw= 99918 -IHBvbHlz 99919 -IGNyZWF0ZVN0YWNrTmF2aWdhdG9y 99920 -77+9dA== 99921 -IGxheW9mZnM= 99922 -IEJsYW5jbw== 99923 -RmVhdA== 99924 -IFZpbWVv 99925 -X2NoaQ== 99926 -X2xpZmV0aW1l 99927 -UE9JTlRT 99928 -LHByaXZhdGU= 99929 -IHVuYmVhcmFibGU= 99930 -cHJpbnRpbmc= 99931 -IGNnaQ== 99932 -LkJBQ0s= 99933 -IGludGVybnM= 99934 -IE5ld2x5 99935 -aW5mZWxk 99936 -KElC 99937 -IEthdGE= 99938 -IERlZmVuZGFudHM= 99939 -VGhy 99940 -6aKE 99941 -X1ZG 99942 -RkZGRkZGRkY= 99943 -IGRhdmlkamw= 99944 -IGJpdHRlcmx5 99945 -U3VnZ2VzdGlvbnM= 99946 -LnNldENhbmNlbGFibGU= 99947 -RklOQUw= 99948 -YXNvbnM= 99949 -X3J3bG9jaw== 99950 -X1dSQVBQRVI= 99951 -IGhhcHBpZXN0 99952 -KHJvd0luZGV4 99953 -w7NzaXRv 99954 -VE9UWVBF 99955 -QXV0b21hdGlvbg== 99956 -TG9nRmlsZQ== 99957 -IGNvbnNvbGF0aW9u 99958 -44OA 99959 -IHTDqm0= 99960 -IHByZXI= 99961 -cmd5eg== 99962 -IEdlZw== 99963 -CWR0bw== 99964 -LmRlZmF1bHRWYWx1ZQ== 99965 -IEthbWk= 99966 -IEFTRQ== 99967 -b3B0aW1pemVk 99968 -IO2PrA== 99969 -IG9yaWdpbmF0ZXM= 99970 -ZXJyTXNn 99971 -IGVzcGHDp28= 99972 -KFNZUw== 99973 -IE1jQg== 99974 -ZGFuY2U= 99975 -X2RldGVjdGVk 99976 -IGZyw7w= 99977 -CQkgICAgCQk= 99978 -PERhdGU= 99979 -KGNvbWI= 99980 -IERlY2lkZQ== 99981 -XEZpZWxk 99982 -IFByb3Bvc2Vk 99983 -Umli 99984 -IGRpc2xpa2Vz 99985 -IFdpZW4= 99986 -CURvY3VtZW50 99987 -IHRyYWY= 99988 -IHN0b3JpYQ== 99989 -IFRlbGxz 99990 -Jyk9PQ== 99991 -Q3Jp 99992 -KFZBTFVF 99993 -IEJ1cm5ldHQ= 99994 -LHZvaWQ= 99995 -IGRhbmg= 99996 -IGNjcA== 99997 -QmxvY2tjaGFpbg== 99998 -OiItImAK 99999 -SUNsaWVudA== 100000 -SVNPREU= 100001 -SXNzdWVy 100002 -KX0NCg== 100003 -LGJ1dA== 100004 -IFVwaA== 100005 -KFN1Yg== 100006 -IHTDqWzDqXBob25l 100007 -IG9uRGF0YUNoYW5nZQ== 100008 -IG1hcnNoYWxsZXI= 100009 -LWFuYWx5dGljcw== 100010 -LGNvbnRlbnQ= 100011 -IGRlYmFjbGU= 100012 -X1ZhbHVlQ2hhbmdlZA== 100013 -IGZhdW5h 100014 -ICM9Pg== 100015 -IGZveWVy 100016 -J3V0aWxpc2F0aW9u 100017 -IE3DvGxsZXI= 100018 -IEZldGlzaA== 100019 -IGRlZmF1bHRNYW5hZ2Vy 100020 -IGJhY2t0cmFjaw== 100021 -QmFo 100022 -RXhwbGljaXQ= 100023 -X0FTQ0lJ 100024 -IG1BY3Rpdml0eQ== 100025 -KE1zZw== 100026 -IOqyjA== 100027 -IFRFUk1T 100028 -IEFuZ2ll 100029 -SFNW 100030 -IE1vc3F1ZQ== 100031 -Lk5hbWVz 100032 -7Yq8 100033 -cmVzdGU= 100034 -X3Bhcm1z 100035 -IGdhcGluZw== 100036 -IGNyb3BwaW5n 100037 -RGF0YUZyYW1l 100038 -IHJlc3BvbnNpdmVuZXNz 100039 -X3VuZG8= 100040 -X3RyYW4= 100041 -LnRlcm1pbmF0ZQ== 100042 -IGl0YWxpYW5l 100043 -IHdhbGt0aHJvdWdo 100044 -IGF0dHJhY3RpdmVuZXNz 100045 -0LTQtQ== 100046 -X1NUUw== 100047 -X2xlYXJu 100048 -IGNob2NvbGF0ZXM= 100049 -aWVyYXJjaGljYWw= 100050 -LXRoaW5raW5n 100051 -ICkpKQ== 100052 -aXNobWVudHM= 100053 -LkxvZ2Y= 100054 -IFRNWg== 100055 -IENhbmFyeQ== 100056 -Zm9pbA== 100057 -IFZhY2NpbmU= 100058 -LnZ4 100059 -IFN1cnJvdW5k 100060 -SW50ZXJtZWRpYXRl 100061 -IGlvdg== 100062 -dmFpcw== 100063 -JzsiOwo= 100064 -772eCgo= 100065 -6YCB5paZ 100066 -4oCmaXQ= 100067 -U2VhdHM= 100068 -Q2xhcg== 100069 -V2Fycw== 100070 -IEh1dGNoaW5zb24= 100071 -IEhhc2Fu 100072 -IScpCgo= 100073 -IFJpY2hpZQ== 100074 -Y2hlaWRlbg== 100075 -KCQoJw== 100076 -WW9yaw== 100077 -IGxpZHM= 100078 -IGFscGhhbnVtZXJpYw== 100079 -IEdsb2Nr 100080 -LnNoYXBlcw== 100081 -IHNwYXJraW5n 100082 -X2Vwc2lsb24= 100083 -dXBsaWNhdGVk 100084 -LmRpcnR5 100085 -XSk9PQ== 100086 -IOychOy5mA== 100087 -IHNjbg== 100088 -IC8qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq 100089 -X1BSRVZJRVc= 100090 -X0hD 100091 -aWVsZGluZw== 100092 -ZmdldHM= 100093 -IEFkZGlzb24= 100094 -IHByb2R1Y3RTZXJ2aWNl 100095 -LWZpZ3VyZQ== 100096 -KHJldHZhbA== 100097 -emFubw== 100098 -IGF1dG9i 100099 -CXNk 100100 -X251bWVy 100101 -IFNldExhc3RFcnJvcg== 100102 -IEZpb3I= 100103 -aWZpY2FuY2U= 100104 -VW50aXRsZWQ= 100105 -IGluZmllbGQ= 100106 -IHt9KSk7Cg== 100107 -IHNwYWM= 100108 -IHJvb2tpZXM= 100109 -KGRlc2NyaWJpbmc= 100110 -bmdlbg== 100111 -4K6/4K4= 100112 -LnJkZg== 100113 -Lk11dGV4 100114 -IGtuZWVsaW5n 100115 -IFFF 100116 -c2V0TWF4 100117 -UmVhZFN0cmVhbQ== 100118 -IHZlbnRhcw== 100119 -c3V0 100120 -Y21wZXE= 100121 -LldyaXRlQWxsVGV4dA== 100122 -IEV4cGVyaWVuY2Vk 100123 -JF9f 100124 -IGthdW0= 100125 -IExJUw== 100126 -IGRvY3VtZW50b3M= 100127 -X0hFQUxUSA== 100128 -aWNvbnRhaW5z 100129 -IGFydGlzYW5z 100130 -T1dORVI= 100131 -IGJsaW5rZWQ= 100132 -Z2V0RGlzcGxheQ== 100133 -IHRvZW4= 100134 -IHJvd051bQ== 100135 -IGF2cmls 100136 -IGludmlz 100137 -IEtlYXI= 100138 -dG9CZUluVGhlRG9jdW1lbnQ= 100139 -YXB1cg== 100140 -IHJhY2tlZA== 100141 -IE1jTWFzdGVy 100142 -X0FUVFJJQg== 100143 -SGF6 100144 -IGZhY3R1cmE= 100145 -L3Rz 100146 -INGA0LDQt9C80LXRgA== 100147 -IHpm 100148 -IHNob3J0ZmFsbA== 100149 -LmZhc3Rh 100150 -IENPTlNUQU5U 100151 -Lm1hbmFnZWQ= 100152 -Z2Vtcw== 100153 -U2hhcmVkUG9pbnRlcg== 100154 -IGJsdXJyeQ== 100155 -YnJpZ2h0bmVzcw== 100156 -KGNvbXBvbmVudHM= 100157 -IC4uLiIKCg== 100158 -U0VMTA== 100159 -IElsbHVzdHJhdG9y 100160 -LmdldENoYW5uZWw= 100161 -IHRyb3V2w6k= 100162 -eXN0ZXJz 100163 -IHZvaXM= 100164 -IExpbmRlbg== 100165 -IGVtb2ppcw== 100166 -IGJyYXds 100167 -IE1TUg== 100168 -IEVsbw== 100169 -IENyb2F0aWFu 100170 -UG9wdXBNZW51 100171 -TGV3aXM= 100172 -LkpXVA== 100173 -IGFzdG9uaXNoZWQ= 100174 -QnVzaA== 100175 -KGl0ZW1JZA== 100176 -IGRldGFjaG1lbnQ= 100177 -IEVuY29yZQ== 100178 -5bCU 100179 -IHJla2w= 100180 -IGNyYW0= 100181 -KSQv 100182 -LmdldEhvc3Q= 100183 -X3JlY29tbWVuZA== 100184 -LUhU 100185 -X2NhbGlicmF0aW9u 100186 -QXV0aGVudGljYXRl 100187 -LmZpcmViYXNlYXBw 100188 -VU5JWA== 100189 -CUNhbWVyYQ== 100190 -IEhFQVA= 100191 -SWRlYWw= 100192 -Lm9mZmljZQ== 100193 -IGdvb2Z5 100194 -KFN5bWJvbA== 100195 -IGpvdWVy 100196 -X3BhcnRpdGlvbnM= 100197 -IHJhcGlkZW1lbnQ= 100198 -IEdOVU5FVA== 100199 -aWRVc2Vy 100200 -IHN1cGVydmlzZQ== 100201 -KENvbnRhY3Q= 100202 -QVdO 100203 -44GY 100204 -IG5hYW0= 100205 -IGF1c3Q= 100206 -5Zyo57q/ 100207 -X3NvZnRtYXg= 100208 -QWxsb3dBbm9ueW1vdXM= 100209 -YW1tYWJsZQ== 100210 -Uk9VVEU= 100211 -KkQ= 100212 -IGFkZW4= 100213 -IENyaXN0aW5h 100214 -IENyaXN0aWFubw== 100215 -IGJsb29kc3RyZWFt 100216 -c3ViY2xhc3M= 100217 -X3BlcnNvbmE= 100218 -Q0hJTEQ= 100219 -LWtub3c= 100220 -IG5hdmlnYXRpb25PcHRpb25z 100221 -IFp1a3VuZnQ= 100222 -IFBpeGFy 100223 -VHlsZXI= 100224 -IHVuZGVyd29ybGQ= 100225 -IHNpbmNlcml0eQ== 100226 -IGRpc3BlbnNlcg== 100227 -IGt0ZXI= 100228 -aWRkZXJz 100229 -LmFkZE5vZGU= 100230 -LWNoZWNrZWQ= 100231 -IGtleXN0 100232 -IFdUTw== 100233 -LnNpZ25hbHM= 100234 -IGFkdmVudHVyZXI= 100235 -IFBhbmc= 100236 -XFI= 100237 -PXBvcw== 100238 -IGRpc3BlbnNhcmllcw== 100239 -IENsb3NldA== 100240 -KCJ7XCI= 100241 -aWRlb24= 100242 -IG7DqWNlc3NhaXJl 100243 -KCkiCg== 100244 -X1JFQ0VJVkVE 100245 -IHLDqXN1bHRhdHM= 100246 -IG1vZGVu 100247 -IEljZWxhbmRpYw== 100248 -O2Q= 100249 -LmFsbG93ZWQ= 100250 -KG5ld1VzZXI= 100251 -IG1lcmNpbGVzcw== 100252 -LldhaXRGb3I= 100253 -IGRheWNhcmU= 100254 -IENvbnZleW9y 100255 diff --git a/harness/effect.go b/harness/effect.go index 5b7925fb..fd6ca466 100644 --- a/harness/effect.go +++ b/harness/effect.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "math/big" "net" "path/filepath" "strings" @@ -42,9 +43,10 @@ const executionInterruptedDetail = "agent interrupted" // modelEffectIntent is the committed intent of one logical model effect: the // facts the result transaction needs after the callback returns. type modelEffectIntent struct { - sessionID string - expected model.ModelRef - resultID string + sessionID string + expected model.ModelRef + resultID string + compaction bool // true for the compact effect's intent: its usage is summary usage } // modelResult is the settlement content of one logical model effect: the @@ -53,7 +55,9 @@ type modelEffectIntent struct { // classification when the effect settles the Operation, and the terminal // no-output model usage when a reported usage has no assistant entry to ride. // The usage model is the intent's expected identity while an intent is live; -// without one the result declares its own usage model. +// without one the result declares its own usage model. The compaction flag +// carries that usage's provenance: true marks summary usage produced by the +// compaction summarizer, and conversation producers leave it false. type modelResult struct { assistant *assistantEntry // nil when no eligible assistant payload exists signal SignalKind // empty unless the disposition continues @@ -61,6 +65,7 @@ type modelResult struct { detail string // terminal detail; empty for ready/continue usage *UsageCount // terminal no-output usage; nil when the assistant entry carries it or none was reported usageModel model.ModelRef // the terminal no-output usage model when no intent is live; zero otherwise + compaction bool // true when the carried terminal usage is summary usage // successContext is the execution context of an outer prospective // success only; the publisher's shared guard consults it before the // transaction. Effect-origin terminals, compaction commits and recovery @@ -83,26 +88,31 @@ func (h *Harness) modelEffect(c *coordinator, operationID string, exec Execution advertised := advertisedToolNames(capture) return func(ctx context.Context, req model.Request, assemble agent.AssemblyCallback) (agent.ModelSettlement, error) { // The compaction trigger sits before every model boundary, before any - // intent commits: a request whose messages plus the output reserve - // exceed the conversation window compacts first. The request's - // messages already carry the projected system message, so the - // estimate covers the complete request; the frozen snapshot is the - // projected conversation minus the leading system message. The - // orchestration and the automatic commit run to completion, then the - // request is rebuilt in full from the pure projection without - // draining steering again — steering that arrived during the - // orchestration stays buffered for the next model boundary. Every - // orchestration or commit failure converts through the settled-state - // discriminator: one whose terminal already settled durably (a piece - // failure inside its own effect, any other failure through the - // direct terminal settlement) keeps the failure settlement — the - // request whose checkpoint failed is never sent, with no retry and - // no uncompacted fallback — while a failure that left the Operation - // running returns its raw error exactly like the conversation - // effect's own publication failures, for recovery. The compact model - // effect performs no trigger check — the structural recursion guard - // keeps the compact Agent outside Session compaction. - if estimateTokens("", req.Messages)+capture.OutputReserve > capture.ContextWindow { + // intent commits: the newest reported conversation usage after the + // current compaction boundary — InputTokens plus CachedInputTokens + // plus OutputTokens, compared with big.Int exactness against the + // captured context window minus the output reserve — compacts the + // conversation first when it strictly exceeds that threshold. A + // missing report never triggers, and a present zero obeys the same + // inequality, including when the reserve exceeds the window; the + // report is the last provider measurement, not an exact pre-count of + // the next request. The frozen snapshot is the projected conversation + // minus the leading system message. The orchestration and the + // automatic commit run to completion, then the request is rebuilt in + // full from the pure projection without draining steering again — + // steering that arrived during the orchestration stays buffered for + // the next model boundary. Every orchestration or commit failure + // converts through the settled-state discriminator: one whose + // terminal already settled durably (a summary failure inside its own + // effect, any other failure through the direct terminal settlement) + // keeps the failure settlement — the request whose checkpoint failed + // is never sent, with no retry and no uncompacted fallback — while a + // failure that left the Operation running returns its raw error + // exactly like the conversation effect's own publication failures, + // for recovery. The compact model effect performs no trigger check — + // the structural recursion guard keeps the compact Agent outside + // Session compaction. + if conversationOverflows(c, capture) { snapshot := req.Messages if len(snapshot) > 0 && snapshot[0].Role == model.RoleSystem { snapshot = snapshot[1:] @@ -218,6 +228,25 @@ func (h *Harness) modelEffect(c *coordinator, operationID string, exec Execution } } +// conversationOverflows samples the newest reported conversation usage after +// the current compaction boundary under a brief coordinator hold and reports +// whether its exact signed total exceeds the captured window minus the output +// reserve. Input is already uncached, so cached input is added once. A +// missing report never overflows. +func conversationOverflows(c *coordinator, capture ExecutionCapture) bool { + c.mu.Lock() + usage := latestContextUsage(c.graph.Entries, c.graph.Session.State.CompactionEntryID) + c.mu.Unlock() + if usage == nil { + return false + } + total := new(big.Int).SetInt64(usage.InputTokens) + total.Add(total, big.NewInt(usage.CachedInputTokens)) + total.Add(total, big.NewInt(usage.OutputTokens)) + threshold := big.NewInt(int64(capture.ContextWindow) - int64(capture.OutputReserve)) + return total.Cmp(threshold) > 0 +} + // interruptModelEffect settles one committed model effect intent as the fixed // terminal interruption after observed execution cancellation before stream // acceptance: no output, no assembly call, and the committed settlement @@ -450,14 +479,16 @@ func (h *Harness) commitEffectResult(ctx context.Context, c *coordinator, operat // A terminal result runs the common terminal helper on this // transaction's own decoded records. if res.terminal != "" { - // The usage model is the live intent's expected identity; a - // quiet direct settlement declares its own. + // The usage model and the usage provenance come from the live + // intent; a quiet direct settlement declares its own. usageModel := res.usageModel + compactionUsage := res.compaction if intent != nil { usageModel = intent.expected + compactionUsage = intent.compaction } var err error - committedOp, committedSess, newEntries, err = commitTerminalSettlement(tx, sessionID, operationID, currentSession, currentOp, sreg.Revision, oreg.Revision, res.terminal, res.detail, res.assistant, usageModel, res.usage) + committedOp, committedSess, newEntries, err = commitTerminalSettlement(tx, sessionID, operationID, currentSession, currentOp, sreg.Revision, oreg.Revision, res.terminal, res.detail, res.assistant, usageModel, res.usage, compactionUsage) if err != nil { return err } @@ -580,7 +611,7 @@ func (h *Harness) commitEffectResult(ctx context.Context, c *coordinator, operat // transaction after its own preconditions; recovery calls it from its own // re-reading transaction. It performs no precondition check of its own: the // caller passes only records whose state it may settle. -func commitTerminalSettlement(tx Transaction, sessionID, operationID string, currentSession SessionRecord, currentOp OperationRecord, sregRevision, oregRevision int64, terminal OperationState, detail string, assistant *assistantEntry, usageModel model.ModelRef, usage *UsageCount) (OperationRecord, SessionRecord, []graphEntry, error) { +func commitTerminalSettlement(tx Transaction, sessionID, operationID string, currentSession SessionRecord, currentOp OperationRecord, sregRevision, oregRevision int64, terminal OperationState, detail string, assistant *assistantEntry, usageModel model.ModelRef, usage *UsageCount, compactionUsage bool) (OperationRecord, SessionRecord, []graphEntry, error) { var newEntries []graphEntry // The transaction's usage contribution rides the produced assistant @@ -713,11 +744,12 @@ func commitTerminalSettlement(tx Transaction, sessionID, operationID string, cur } } settlement := operationSettlementEntry{ - SessionID: sessionID, - EntryID: settlementID, - OperationID: operationID, - Status: terminal, - Detail: detail, + SessionID: sessionID, + EntryID: settlementID, + OperationID: operationID, + Status: terminal, + Detail: detail, + CompactionUsage: compactionUsage, } if assistant == nil && usage != nil { // terminal no-output model usage rides the settlement entry modelRef := usageModel @@ -1751,7 +1783,7 @@ func (h *Harness) execute(c *coordinator, operationID string, prepared PreparedE // orchestration over it, and commits the manual compaction whose one // transaction also settles the Operation's success. Every orchestration // failure or interruption has already settled the terminal durably with - // the accumulated usage (a piece failure inside its own effect, any other + // the reported usage (a summary failure inside its own effect, any other // failure through the direct terminal settlement), and only an actually // empty conversation fails with the retained detail. if admission.RequestKind == RequestKindCompact { diff --git a/harness/effect_test.go b/harness/effect_test.go index 333e782d..7aa7cc1d 100644 --- a/harness/effect_test.go +++ b/harness/effect_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/agent" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -34,15 +35,12 @@ func newAdmittedHarness(t *testing.T, hctx context.Context, prepared PreparedExe h, err := New(hctx, Dependencies{Storage: store, Prepare: func(context.Context, PreparationRequest) (PreparedExecution, error) { return prepared, nil }}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) session, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if _, disposition := mustAdmitWithoutExecution(t, h, session.Identity.SessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + testassert.NoError(t, err) + { + _, disposition := mustAdmitWithoutExecution(t, h, session.Identity.SessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := leaseCoordinator(t, h, session.Identity.SessionID) return h, store, c, session.Identity.SessionID @@ -56,23 +54,24 @@ func newEffectHarness(t *testing.T, modelFn func(context.Context, model.Request) return newAdmittedHarness(t, context.Background(), modelPrepared(modelFn)) } -// invokeModelEffect drives one model effect with a validated request; a nil -// assemble callback runs the real Agent assembly. -func invokeModelEffect(t *testing.T, me agent.ModelEffect, assemble agent.AssemblyCallback) (agent.ModelSettlement, error) { +// defaultModelRequest builds the fixture conversation request the default +// invocation callers drive. +func defaultModelRequest(t *testing.T) model.Request { t.Helper() req, err := model.NewRequest(model.Request{ Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("hello")}}, Tools: []model.ToolDefinition{testToolDefinition()}, }) - if err != nil { - t.Fatalf("request: %v", err) - } - if assemble == nil { - assemble = func(source model.ModelRef, stream model.Stream) (model.Output, error) { - return agent.Assemble(context.Background(), source, stream) - } - } - return me(context.Background(), req, assemble) + testassert.NoError(t, err) + return req +} + +// invokeModelEffect drives one model effect with the given validated request +// under a background context; a nil assemble callback runs the real Agent +// assembly. +func invokeModelEffect(t *testing.T, me agent.ModelEffect, req model.Request, assemble agent.AssemblyCallback) (agent.ModelSettlement, error) { + t.Helper() + return invokeModelEffectCtx(t, me, context.Background(), req, assemble) } func testToolCall(id string) model.ToolCall { @@ -219,96 +218,52 @@ func TestModelEffectReadyRemainsRunning(t *testing.T) { calls := 0 me := h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()) - set, err := invokeModelEffect(t, me, func(source model.ModelRef, stream model.Stream) (model.Output, error) { + set, err := invokeModelEffect(t, me, defaultModelRequest(t), func(source model.ModelRef, stream model.Stream) (model.Output, error) { calls++ return agent.Assemble(context.Background(), source, stream) }) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready", set.Disposition) - } - if calls != 1 { - t.Fatalf("assembly callback calls = %d, want exactly one", calls) - } + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, calls, 1) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-ready graph: %v", err) - } + testassert.NoError(t, err) var assistant *assistantEntry for i := range graph.Entries { if graph.Entries[i].Assistant != nil { assistant = graph.Entries[i].Assistant } } - if assistant == nil { - t.Fatalf("ready settlement published no assistant entry") - } - if assistant.EntryID != reservedID { - t.Fatalf("assistant entry %s does not consume the reserved identity %s", assistant.EntryID, reservedID) - } - if assistant.Status != model.OutputCompleted || len(assistant.ToolCalls) != 2 { - t.Fatalf("assistant entry = %+v, want completed output with two calls", assistant) - } - if assistant.Usage == nil || *assistant.Usage != (UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2}) { - t.Fatalf("assistant usage = %+v, want the reported counts", assistant.Usage) - } + testassert.Require(t, assistant != nil, "ready settlement published no assistant entry") + testassert.Require(t, assistant.EntryID == reservedID && (assistant.Status == model.OutputCompleted && len(assistant.ToolCalls) == 2), "got %v, want %v; assistant entry = %+v, want completed output with two calls", assistant.EntryID, reservedID, assistant) + testassert.Require(t, assistant.Usage != nil && *assistant.Usage == (UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2}), "assistant usage = %+v, want the reported counts", assistant.Usage) for i, call := range assistant.ToolCalls { - if call.Ordinal != int64(i) || call.ID != fmt.Sprintf("call-%d", i+1) || call.ResultEntryID == "" { - t.Fatalf("tool_calls[%d] = %+v, want ordered reserved call", i, call) - } - if raw, derr := base64.StdEncoding.DecodeString(call.ArgumentsBase64); derr != nil || string(raw) != `{"x":1}` { - t.Fatalf("tool_calls[%d] arguments = %q (%v), want the exact raw bytes", i, raw, derr) + testassert.Require(t, call.Ordinal == int64(i) && call.ID == fmt.Sprintf("call-%d", i+1) && call.ResultEntryID != "", "tool_calls[%d] = %+v, want ordered reserved call", i, call) + { + raw, derr := base64.StdEncoding.DecodeString(call.ArgumentsBase64) + testassert.Require(t, derr == nil && string(raw) == `{"x":1}`, "tool_calls[%d] arguments = %q (%v), want the exact raw bytes", i, raw, derr) } } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil { - t.Fatalf("operation state = %+v, want running with the effect cleared", rec.State) - } - if len(rec.State.PendingToolCalls) != 2 { - t.Fatalf("pending calls = %+v, want the two published calls", rec.State.PendingToolCalls) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 2, "operation state = %+v, want running with the effect cleared; pending calls = %+v, want the two published calls", rec.State, rec.State.PendingToolCalls) for i, call := range rec.State.PendingToolCalls { - if call.CallID != assistant.ToolCalls[i].ID || call.ResultEntryID != assistant.ToolCalls[i].ResultEntryID || - call.AssistantEntry.EntryID != assistant.EntryID { - t.Fatalf("pending call %d = %+v, want the assistant's reservation", i, call) - } + testassert.Require(t, call.CallID == assistant.ToolCalls[i].ID && call.ResultEntryID == assistant.ToolCalls[i].ResultEntryID && call.AssistantEntry.EntryID == assistant.EntryID, "pending call %d = %+v, want the assistant's reservation", i, call) } want := UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2}}}} - if !usageTotalsEqual(rec.State.Usage, want) { - t.Fatalf("operation usage = %+v, want %+v", rec.State.Usage, want) - } + testassert.Require(t, usageTotalsEqual(rec.State.Usage, want), "operation usage = %+v, want %+v", rec.State.Usage, want) view, err := h.ReadSessionView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if view.Header.CurrentOperationID != testOpID { - t.Fatalf("current operation %q, want the still-running %q", view.Header.CurrentOperationID, testOpID) - } - if !usageTotalsEqual(view.TotalUsage, want) { - t.Fatalf("session usage = %+v, want %+v", view.TotalUsage, want) - } + testassert.NoError(t, err) + testassert.Require(t, view.Header.CurrentOperationID == testOpID && usageTotalsEqual(view.TotalUsage, want), "got %v, want %v; session usage = %+v, want %+v", view.Header.CurrentOperationID, testOpID, view.TotalUsage, want) // The second context projection reads the entry the first effect committed. source := h.contextSource(c, testOpID) msgs, err := source(context.Background()) - if err != nil { - t.Fatalf("second context projection: %v", err) - } - if len(msgs) != 3 { // system prompt, admitted input, committed assistant - t.Fatalf("second projection carried %d messages, want 3", len(msgs)) - } + testassert.NoError(t, err) + // system prompt, admitted input, committed assistant + testassert.Equal(t, len(msgs), 3) last := msgs[len(msgs)-1] - if last.Role != model.RoleAssistant || last.Source != testModelRef() || last.TextContent() != "done" || len(last.ToolCalls) != 2 { - t.Fatalf("second projection's last message = %+v, want the committed assistant", last) - } - if string(last.ToolCalls[0].Arguments) != `{"x":1}` { - t.Fatalf("projected call arguments = %s, want the exact raw bytes", last.ToolCalls[0].Arguments) - } + testassert.Require(t, last.Role == model.RoleAssistant && last.Source == testModelRef() && last.TextContent() == "done" && len(last.ToolCalls) == 2, "second projection's last message = %+v, want the committed assistant", last) + testassert.Require(t, string(last.ToolCalls[0].Arguments) == `{"x":1}`, "projected call arguments = %s, want the exact raw bytes", last.ToolCalls[0].Arguments) } // TestModelEffectContinueRemainsRunning proves the continuation transition: @@ -319,17 +274,11 @@ func TestModelEffectContinueRemainsRunning(t *testing.T) { return erroredTurnStream(), nil } h, store, c, sessionID := newEffectHarness(t, modelFn) - set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoContinue { - t.Fatalf("settlement disposition %q, want continue", set.Disposition) - } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoContinue) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-continue graph: %v", err) - } + testassert.NoError(t, err) var assistant *assistantEntry var signal *signalEntry for i := range graph.Entries { @@ -340,30 +289,16 @@ func TestModelEffectContinueRemainsRunning(t *testing.T) { signal = graph.Entries[i].Signal } } - if assistant == nil || assistant.Status != model.OutputErrored || len(assistant.Content) != 1 || assistant.Content[0].Text != "partial" { - t.Fatalf("assistant entry = %+v, want the retained errored partial", assistant) - } - if signal == nil || signal.Signal != SignalModelFailureContinuation || signal.Content != signalModelFailureContinuationContent { - t.Fatalf("signal entry = %+v, want the fixed continuation signal", signal) - } + testassert.Require(t, assistant != nil && assistant.Status == model.OutputErrored && len(assistant.Content) == 1 && assistant.Content[0].Text == "partial", "assistant entry = %+v, want the retained errored partial", assistant) + testassert.Require(t, signal != nil && signal.Signal == SignalModelFailureContinuation && signal.Content == signalModelFailureContinuationContent, "signal entry = %+v, want the fixed continuation signal", signal) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("operation state = %+v, want running with the effect cleared and no pending calls", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 0, "operation state = %+v, want running with the effect cleared and no pending calls", rec.State) want := UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: UsageCount{InputTokens: 5, OutputTokens: 7}}}} - if !usageTotalsEqual(rec.State.Usage, want) { - t.Fatalf("operation usage = %+v, want %+v", rec.State.Usage, want) - } + testassert.Require(t, usageTotalsEqual(rec.State.Usage, want), "operation usage = %+v, want %+v", rec.State.Usage, want) view, err := h.ReadSessionView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if view.Header.CurrentOperationID != testOpID || !usageTotalsEqual(view.TotalUsage, want) { - t.Fatalf("session view = %+v, want the running operation and updated usage", view) - } + testassert.NoError(t, err) + testassert.Require(t, view.Header.CurrentOperationID == testOpID && usageTotalsEqual(view.TotalUsage, want), "session view = %+v, want the running operation and updated usage", view) } // TestModelEffectTerminalSettlement proves the model-derived failure and @@ -389,34 +324,22 @@ func TestModelEffectTerminalSettlement(t *testing.T) { } h, st, c, sid := newEffectHarness(t, modelFn) store, sessionID = st, sid - set, err := invokeModelEffectCtx(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), ctx, nil) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoInterruption { - t.Fatalf("settlement disposition %q, want interruption", set.Disposition) - } + set, err := invokeModelEffectCtx(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), ctx, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoInterruption) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-interruption graph: %v", err) - } + testassert.NoError(t, err) var assistant *assistantEntry for i := range graph.Entries { if graph.Entries[i].Assistant != nil { assistant = graph.Entries[i].Assistant } } - if assistant == nil || assistant.EntryID != reservedID { - t.Fatalf("assistant entry %v does not consume the reserved identity %s", assistant, reservedID) - } + testassert.Require(t, assistant != nil && assistant.EntryID == reservedID, "assistant entry %v does not consume the reserved identity %s", assistant, reservedID) requireSessionCleared(t, h, sessionID) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != "stream interrupted: context canceled" { - t.Fatalf("operation state = %+v, want terminal interruption with the assembly's detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "stream interrupted: context canceled", "operation state = %+v, want terminal interruption with the assembly's detail", rec.State) }) t.Run("nonretryable invocation failure settles under the reserved identity without an assistant payload", func(t *testing.T) { @@ -431,24 +354,13 @@ func TestModelEffectTerminalSettlement(t *testing.T) { } h, st, c, sid := newEffectHarness(t, modelFn) store, sessionID = st, sid - set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoFailure || set.Detail != "model failure" { - t.Fatalf("settlement = %+v, want the committed failure with its diagnostic", set) - } + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Detail == "model failure", "settlement = %+v, want the committed failure with its diagnostic", set) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Assistant != nil { - t.Fatalf("assistant entry %s published for a payload-less failure", entry.Envelope.ID) - } - if entry.Settlement != nil && entry.Envelope.ID != reservedID { - t.Fatalf("settlement entry %s does not consume the reserved identity %s", entry.Envelope.ID, reservedID) - } + testassert.Require(t, entry.Assistant == nil && (entry.Settlement == nil || entry.Envelope.ID == reservedID), "assistant entry %s published for a payload-less failure; settlement entry %s does not consume the reserved identity %s", entry.Envelope.ID, entry.Envelope.ID, reservedID) } requireSessionCleared(t, h, sessionID) }) @@ -463,20 +375,12 @@ func TestModelEffectTerminalSettlement(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() // the execution context dies before any attempt could start me := h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()) - set, err := invokeModelEffectCtx(t, me, ctx, nil) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoInterruption || set.Detail != executionInterruptedDetail { - t.Fatalf("settlement = %+v, want the committed interruption settlement", set) - } - if attempts != 0 { - t.Fatalf("physical attempts = %d, want zero after observed cancellation", attempts) - } + set, err := invokeModelEffectCtx(t, me, ctx, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoInterruption && set.Detail == executionInterruptedDetail, "settlement = %+v, want the committed interruption settlement", set) + testassert.Equal(t, attempts, 0) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-interruption graph: %v", err) - } + testassert.NoError(t, err) signals, settlements := 0, 0 for _, entry := range graph.Entries { switch { @@ -490,9 +394,7 @@ func TestModelEffectTerminalSettlement(t *testing.T) { settlements++ } } - if signals != 1 || settlements != 1 { - t.Fatalf("committed %d signals and %d settlements, want one of each", signals, settlements) - } + testassert.Require(t, signals == 1 && settlements == 1, "committed %d signals and %d settlements, want one of each", signals, settlements) requireSessionCleared(t, h, sessionID) }) } @@ -514,34 +416,25 @@ func TestModelEffectRepeatedCallIDSettlesFailure(t *testing.T) { } h, store, c, sessionID := newEffectHarness(t, modelFn) me := h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()) - if _, err := invokeModelEffect(t, me, nil); err != nil { - t.Fatalf("first effect: %v", err) - } - _, err := invokeModelEffect(t, me, nil) - if err == nil { - t.Fatalf("second effect settled, want the repeated-call protocol violation") + { + _, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.NoError(t, err) } + _, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.Require(t, err != nil, "second effect settled, want the repeated-call protocol violation") wantErr := err graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the repeated-call failure: %v", err) - } + testassert.NoError(t, err) assistants := 0 for _, entry := range graph.Entries { if entry.Assistant != nil { assistants++ } } - if assistants != 1 { - t.Fatalf("%d assistant entries after the repeated-call failure, want only turn 1's", assistants) - } + testassert.Equal(t, assistants, 1) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != wantErr.Error() { - t.Fatalf("operation state = %+v, want terminal failure with the protocol error detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == wantErr.Error(), "operation state = %+v, want terminal failure with the protocol error detail", rec.State) requireSessionCleared(t, h, sessionID) } @@ -553,38 +446,29 @@ func TestModelEffectTerminalNoOutputUsageOnSettlement(t *testing.T) { return failStream(errors.New("empty failure"), usageDelta(model.Usage{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 6})), nil } h, store, c, sessionID := newEffectHarness(t, modelFn) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("post-failure graph: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.Settlement == nil { continue } - if entry.Settlement.Model == nil || *entry.Settlement.Model != testModelRef() { - t.Fatalf("settlement model = %+v, want the expected identity beside its usage", entry.Settlement.Model) - } - if entry.Settlement.Usage == nil || *entry.Settlement.Usage != (UsageCount{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 6}) { - t.Fatalf("settlement usage = %+v, want the reported no-output counts", entry.Settlement.Usage) - } + testassert.Require(t, entry.Settlement.Model != nil && *entry.Settlement.Model == testModelRef(), "settlement model = %+v, want the expected identity beside its usage", entry.Settlement.Model) + testassert.Require(t, entry.Settlement.Usage != nil && *entry.Settlement.Usage == (UsageCount{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 6}) && !entry.Settlement.CompactionUsage, "settlement usage = %+v, want the reported no-output counts; settlement compaction_usage = true, want the conversation usage unmarked", entry.Settlement.Usage) } want := UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: UsageCount{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 6}}}} rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if !usageTotalsEqual(rec.State.Usage, want) { - t.Fatalf("operation usage = %+v, want %+v", rec.State.Usage, want) - } + testassert.NoError(t, err) + testassert.Require(t, usageTotalsEqual(rec.State.Usage, want), "operation usage = %+v, want %+v", rec.State.Usage, want) view, err := h.ReadSessionView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if !usageTotalsEqual(view.TotalUsage, want) { - t.Fatalf("session usage = %+v, want %+v", view.TotalUsage, want) + testassert.NoError(t, err) + testassert.Require(t, usageTotalsEqual(view.TotalUsage, want), "session usage = %+v, want %+v", view.TotalUsage, want) + { + measured := view.History.LatestContextUsage() + testassert.Require(t, measured != nil && *measured == (UsageCount{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 6}), "latest context measurement = %+v, want the conversation no-output usage eligible", measured) } } @@ -594,16 +478,10 @@ func TestModelEffectTerminalNoOutputUsageOnSettlement(t *testing.T) { func readReservedIntent(t *testing.T, store *graphStorage, sessionID string) string { t.Helper() reg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID}) - if err != nil { - t.Fatalf("read operation register mid-effect: %v", err) - } + testassert.NoError(t, err) op, err := decodeOperationRegister(reg) - if err != nil { - t.Fatalf("decode operation register mid-effect: %v", err) - } - if op.State.ActiveEffect == nil || op.State.ActiveEffect.Kind != EffectModel { - t.Fatalf("no model effect intent is durable while the effect runs: %+v", op.State) - } + testassert.NoError(t, err) + testassert.Require(t, op.State.ActiveEffect != nil && op.State.ActiveEffect.Kind == EffectModel, "no model effect intent is durable while the effect runs: %+v", op.State) return op.State.ActiveEffect.ResultEntryID } @@ -612,12 +490,8 @@ func readReservedIntent(t *testing.T, store *graphStorage, sessionID string) str func requireSessionCleared(t *testing.T, h *Harness, sessionID string) { t.Helper() session, err := h.ReadSessionHeader(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if session.CurrentOperationID != "" { - t.Fatalf("current operation %q survived a terminal settlement", session.CurrentOperationID) - } + testassert.NoError(t, err) + testassert.Equal(t, session.CurrentOperationID, "") } // TestModelEffectPublicationFailureLeavesIntentState proves the model result @@ -649,23 +523,17 @@ func TestModelEffectPublicationFailureLeavesIntentState(t *testing.T) { } return nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err == nil { - t.Fatalf("model effect succeeded past an injected %s failure", fail.step) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.Require(t, err != nil, "model effect succeeded past an injected %s failure", fail.step) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after rollback: %v", err) - } - if len(graph.Entries) != 1 { // only the admitted input entry - t.Fatalf("%d entries committed after rollback, want only the admitted input", len(graph.Entries)) - } + testassert.NoError(t, err) + // only the admitted input entry + testassert.Equal(t, len(graph.Entries), 1) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect == nil { - t.Fatalf("operation state = %+v, want the committed running/intent state", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect != nil, "operation state = %+v, want the committed running/intent state", rec.State) }) } } @@ -717,32 +585,15 @@ func TestContextSourceProjectsFullHistory(t *testing.T) { h := newTestHarness(t, store, nil) c := leaseCoordinator(t, h, testSessionID) msgs, err := h.contextSource(c, testOpID)(context.Background()) - if err != nil { - t.Fatalf("context projection: %v", err) - } - if len(msgs) != 5 { - t.Fatalf("projection carried %d messages, want 5", len(msgs)) - } - if msgs[0].Role != model.RoleSystem || msgs[0].TextContent() != "system" { - t.Fatalf("first message = %+v, want the captured system prompt", msgs[0]) - } - if msgs[1].Role != model.RoleUser || msgs[1].TextContent() != "hello" { - t.Fatalf("input message = %+v, want the admitted user input", msgs[1]) - } + testassert.NoError(t, err) + testassert.Equal(t, len(msgs), 5) + testassert.Require(t, msgs[0].Role == model.RoleSystem && msgs[0].TextContent() == "system", "first message = %+v, want the captured system prompt", msgs[0]) + testassert.Require(t, msgs[1].Role == model.RoleUser && msgs[1].TextContent() == "hello", "input message = %+v, want the admitted user input", msgs[1]) assistant := msgs[2] - if assistant.Role != model.RoleAssistant || assistant.Source != testModelRef() || assistant.TextContent() != "hi" { - t.Fatalf("assistant message = %+v, want the committed assistant payload", assistant) - } - if len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].ID != "call-1" || string(assistant.ToolCalls[0].Arguments) != `{"x":1}` { - t.Fatalf("assistant call = %+v, want the call with its exact raw argument bytes", assistant.ToolCalls) - } - if msgs[3].Role != model.RoleTool || msgs[3].ToolCallID != "call-1" || msgs[3].TextContent() != "done" { - t.Fatalf("tool message = %+v, want the tool result under its call identity", msgs[3]) - } + testassert.Require(t, assistant.Role == model.RoleAssistant && assistant.Source == testModelRef() && assistant.TextContent() == "hi" && (len(assistant.ToolCalls) == 1 && assistant.ToolCalls[0].ID == "call-1" && string(assistant.ToolCalls[0].Arguments) == `{"x":1}`), "assistant message = %+v, want the committed assistant payload; assistant call = %+v, want the call with its exact raw argument bytes", assistant, assistant.ToolCalls) + testassert.Require(t, msgs[3].Role == model.RoleTool && msgs[3].ToolCallID == "call-1" && msgs[3].TextContent() == "done", "tool message = %+v, want the tool result under its call identity", msgs[3]) want := "" + signalInterruptionContent + "" - if msgs[4].Role != model.RoleUser || msgs[4].TextContent() != want { - t.Fatalf("signal message = %+v, want the wrapped signal text %q", msgs[4], want) - } + testassert.Require(t, msgs[4].Role == model.RoleUser && msgs[4].TextContent() == want, "signal message = %+v, want the wrapped signal text %q", msgs[4], want) // The two live boundary-selection cases share the fixture pieces: one // immediate tool plan and the real model/tool effect drivers, whose @@ -753,11 +604,13 @@ func TestContextSourceProjectsFullHistory(t *testing.T) { runEffects := func(t *testing.T, h *Harness, c *coordinator, modelFn func(context.Context, model.Request) (model.Stream, error)) { t.Helper() exec := effectExecution(modelFn, plan) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } - if _, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")); err != nil { - t.Fatalf("tool effect: %v", err) + { + _, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) + testassert.NoError(t, err) } } @@ -766,16 +619,13 @@ func TestContextSourceProjectsFullHistory(t *testing.T) { return completedTurnStream(testToolCall("call-1")), nil } h, _, c, sessionID := newEffectHarness(t, modelFn) - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } first, err := h.contextSource(c, testOpID)(context.Background()) - if err != nil { - t.Fatalf("first projection with pending steering: %v", err) - } - if len(first) != 2 || first[1].TextContent() != "hello" { - t.Fatalf("first projection = %+v, want the admitted input only despite the pending head", first) - } + testassert.NoError(t, err) + testassert.Require(t, len(first) == 2 && first[1].TextContent() == "hello", "first projection = %+v, want the admitted input only despite the pending head", first) }) t.Run("late arrival selects the boundary at the next call", func(t *testing.T) { @@ -793,33 +643,26 @@ func TestContextSourceProjectsFullHistory(t *testing.T) { c.mu.Unlock() source := h.contextSource(c, testOpID) first, err := source(context.Background()) // steering is empty through the first projection - if err != nil || len(first) != 2 || first[1].TextContent() != "hello" { - t.Fatalf("first projection = (%+v, %v), want the admitted input with empty steering", first, err) - } + testassert.Require(t, err == nil && len(first) == 2 && first[1].TextContent() == "hello", "first projection = (%+v, %v), want the admitted input with empty steering", first, err) runEffects(t, h, c, modelFn) // the real model and tool callbacks return before the head arrives - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("late steering submit: %v", err) + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } before := storedEntryCount(store, sessionID) msgs, err := source(context.Background()) - if err != errSteeringBoundary { // exact identity: the private token, never a text or cancellation class - t.Fatalf("boundary selection = (%v, %v), want the private boundary token verbatim", msgs, err) - } - if msgs != nil { - t.Fatalf("boundary selection projected %v, want no messages", msgs) - } - if got := storedEntryCount(store, sessionID); got != before { - t.Fatalf("boundary selection wrote %d entries, want no storage", got-before) - } - if modelCalls != 1 { - t.Fatalf("model calls = %d, want no second request behind the token", modelCalls) + // exact identity: the private token, never a text or cancellation class + testassert.Require(t, err == errSteeringBoundary, "boundary selection = (%v, %v), want the private boundary token verbatim", msgs, err) + testassert.Require(t, msgs == nil, "boundary selection projected %v, want no messages", msgs) + { + got := storedEntryCount(store, sessionID) + testassert.Require(t, got == before, "boundary selection wrote %d entries, want no storage", got-before) } + testassert.Equal(t, modelCalls, 1) c.mu.Lock() buffered := len(c.steering) c.mu.Unlock() - if buffered != 1 { - t.Fatalf("buffered heads = %d, want the sole head still pending after selection", buffered) - } + testassert.Equal(t, buffered, 1) if run.execCtx.Err() != nil { t.Fatalf("execution context = %v, want it unchanged and live after selection", run.execCtx.Err()) } @@ -830,9 +673,7 @@ func TestContextSourceProjectsFullHistory(t *testing.T) { // in the signal content. func TestSignalProjectionEscaping(t *testing.T) { got := signalProjectedText(`a & b < c > d`) - if got != "a & b < c > d" { - t.Fatalf("signalProjectedText = %q, want the escaped wrapper", got) - } + testassert.Equal(t, got, "a & b < c > d") } // foreignEffectRace writes one foreign state change under the coordinator's @@ -876,9 +717,7 @@ func foreignEffectRace(t *testing.T, store *graphStorage, sessionID, operationID _, err = tx.ReplaceRegister(opKey, oreg.Revision, opPayload) return err }) - if err != nil { - t.Fatalf("foreign race write: %v", err) - } + testassert.NoError(t, err) } // TestEffectTransactionsRematerializeOnRevisionRace proves every effect @@ -892,11 +731,13 @@ func TestEffectTransactionsRematerializeOnRevisionRace(t *testing.T) { } h, store, c, sessionID := newEffectHarness(t, modelFn) foreignEffectRace(t, store, sessionID, testOpID, "foreign") - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); !errors.Is(err, ErrConflict) { - t.Fatalf("intent over a foreign revision = %v, want the revision-race conflict", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.Require(t, errors.Is(err, ErrConflict), "intent over a foreign revision = %v, want the revision-race conflict", err) } - if session, err := h.ReadSessionHeader(context.Background(), sessionID); err != nil || session.CurrentAgentType != "foreign" { - t.Fatalf("session after the race = %+v (%v), want the foreign agent type", session, err) + { + session, err := h.ReadSessionHeader(context.Background(), sessionID) + testassert.Require(t, err == nil && session.CurrentAgentType == "foreign", "session after the race = %+v (%v), want the foreign agent type", session, err) } }) t.Run("result transaction", func(t *testing.T) { @@ -910,11 +751,13 @@ func TestEffectTransactionsRematerializeOnRevisionRace(t *testing.T) { } h, st, c, sid := newEffectHarness(t, modelFn) store, sessionID = st, sid - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); !errors.Is(err, ErrConflict) { - t.Fatalf("result over a foreign revision = %v, want the revision-race conflict", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.Require(t, errors.Is(err, ErrConflict), "result over a foreign revision = %v, want the revision-race conflict", err) } - if session, err := h.ReadSessionHeader(context.Background(), sessionID); err != nil || session.CurrentAgentType != "foreign" { - t.Fatalf("session after the race = %+v (%v), want the foreign agent type", session, err) + { + session, err := h.ReadSessionHeader(context.Background(), sessionID) + testassert.Require(t, err == nil && session.CurrentAgentType == "foreign", "session after the race = %+v (%v), want the foreign agent type", session, err) } }) } @@ -947,9 +790,7 @@ func foreignTerminalSettle(t *testing.T, store *graphStorage, sessionID, operati _, err = tx.ReplaceRegister(key, reg.Revision, payload) return err }) - if err != nil { - t.Fatalf("foreign terminal settle: %v", err) - } + testassert.NoError(t, err) } // TestEffectTransactionsPreconditionsOutrankRevisionRace proves the mismatch @@ -964,8 +805,9 @@ func TestEffectTransactionsPreconditionsOutrankRevisionRace(t *testing.T) { return completedTurnStream(), nil } me := h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()) - if _, err := invokeModelEffect(t, me, nil); !errors.Is(err, ErrInvalid) { - t.Fatalf("intent over a foreign terminal operation = %v, want ErrInvalid", err) + { + _, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.Require(t, errors.Is(err, ErrInvalid), "intent over a foreign terminal operation = %v, want ErrInvalid", err) } }) t.Run("result over a foreign terminal operation", func(t *testing.T) { @@ -979,8 +821,9 @@ func TestEffectTransactionsPreconditionsOutrankRevisionRace(t *testing.T) { } h, st, c, sid := newEffectHarness(t, modelFn) store, sessionID = st, sid - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); !errors.Is(err, ErrInvalid) { - t.Fatalf("result over a foreign terminal operation = %v, want ErrInvalid", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.Require(t, errors.Is(err, ErrInvalid), "result over a foreign terminal operation = %v, want ErrInvalid", err) } }) } @@ -1025,25 +868,17 @@ func newOpenerHarness(t *testing.T, open func(context.Context, OperationAdmissio // the execution fixtures need. func newExecutionHarness(t *testing.T, modelFn func(context.Context, model.Request) (model.Stream, error), toolFn func(context.Context, model.ToolCall) PreparedTool) (*Harness, *graphStorage, *coordinator, string, PreparedExecution, context.CancelFunc) { t.Helper() - if toolFn == nil { - t.Fatalf("execution fixtures require a prepared tool function") - } + testassert.Require(t, toolFn != nil, "execution fixtures require a prepared tool function") return newOpenerHarness(t, func(context.Context, OperationAdmission) (Execution, error) { return effectExecution(modelFn, toolFn), nil }) } -// invokeModelEffectCtx drives one model effect with an explicit context; a nil -// assemble callback runs the real Agent assembly. -func invokeModelEffectCtx(t *testing.T, me agent.ModelEffect, ctx context.Context, assemble agent.AssemblyCallback) (agent.ModelSettlement, error) { +// invokeModelEffectCtx drives one model effect with the given validated +// request and an explicit context; a nil assemble callback runs the real Agent +// assembly under that context. +func invokeModelEffectCtx(t *testing.T, me agent.ModelEffect, ctx context.Context, req model.Request, assemble agent.AssemblyCallback) (agent.ModelSettlement, error) { t.Helper() - req, err := model.NewRequest(model.Request{ - Messages: []model.Message{{Role: model.RoleUser, Content: admissionContent("hello")}}, - Tools: []model.ToolDefinition{testToolDefinition()}, - }) - if err != nil { - t.Fatalf("request: %v", err) - } if assemble == nil { assemble = func(source model.ModelRef, stream model.Stream) (model.Output, error) { return agent.Assemble(ctx, source, stream) @@ -1071,26 +906,17 @@ func TestModelEffectGateSkipsCallbackOnCancellation(t *testing.T) { return nil } me := h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()) - set, err := invokeModelEffectCtx(t, me, ctx, nil) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if invoked != 0 { - t.Fatalf("prepared callback invoked %d times after cancellation, want zero", invoked) - } - if set.Disposition != agent.DispoInterruption || set.Detail != executionInterruptedDetail { - t.Fatalf("settlement = %+v, want the committed interruption settlement", set) - } + set, err := invokeModelEffectCtx(t, me, ctx, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Equal(t, invoked, 0) + testassert.Require(t, set.Disposition == agent.DispoInterruption && set.Detail == executionInterruptedDetail, "settlement = %+v, want the committed interruption settlement", set) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption", rec.State) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the gate: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } } @@ -1103,33 +929,24 @@ func TestExecuteSuccessSettlesOuterTerminal(t *testing.T) { } spy := &toolSpy{} h, store, c, sessionID, prepared, _ := newExecutionHarness(t, modelFn, spy.tool) - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationSuccess || rec.State.Terminal == nil || rec.State.Terminal.Detail != "" { - t.Fatalf("operation state = %+v, want terminal success without detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationSuccess && rec.State.Terminal != nil && rec.State.Terminal.Detail == "", "operation state = %+v, want terminal success without detail", rec.State) requireSessionCleared(t, h, sessionID) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after success: %v", err) - } + testassert.NoError(t, err) settlements := 0 for _, entry := range graph.Entries { if entry.Settlement != nil { settlements++ - if entry.Settlement.Status != OperationSuccess || entry.Settlement.Detail != "" { - t.Fatalf("settlement = %+v, want success without detail", entry.Settlement) - } + testassert.Require(t, entry.Settlement.Status == OperationSuccess && entry.Settlement.Detail == "", "settlement = %+v, want success without detail", entry.Settlement) } } - if settlements != 1 { - t.Fatalf("%d settlement entries, want exactly one", settlements) - } + testassert.Equal(t, settlements, 1) } // TestExecuteOpensOnceWithCommittedAdmission proves the opener row: execute @@ -1150,29 +967,17 @@ func TestExecuteOpensOnceWithCommittedAdmission(t *testing.T) { opens = append(opens, openRecord{ctx: ctx, adm: adm}) return effectExecution(modelFn, spy.tool), nil }) - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) - } - if len(opens) != 1 { - t.Fatalf("%d opener calls, want exactly one per execution", len(opens)) - } - if opens[0].ctx != h.ctx { - t.Fatalf("opener ran on a foreign context, want the execution context") + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } + testassert.Equal(t, len(opens), 1) + testassert.Equal(t, opens[0].ctx, h.ctx) adm := opens[0].adm - if adm.SessionID != sessionID || adm.OperationID != testOpID || adm.AgentType != "coder" { - t.Fatalf("opener admission = %+v, want the owned committed admission", adm) - } - if !reflect.DeepEqual(adm.Execution, testCapture()) { - t.Fatalf("opener capture = %+v, want the prepared capture", adm.Execution) - } + testassert.Require(t, adm.SessionID == sessionID && adm.OperationID == testOpID && adm.AgentType == "coder" && reflect.DeepEqual(adm.Execution, testCapture()), "opener admission = %+v, want the owned committed admission; opener capture = %+v, want the prepared capture", adm, adm.Execution) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationSuccess { - t.Fatalf("operation status = %s, want the Agent run over the opened effects", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationSuccess) } // TestExecuteCanceledBeforeOpenSkipsOpener proves the canceled-execution row: @@ -1188,22 +993,18 @@ func TestExecuteCanceledBeforeOpenSkipsOpener(t *testing.T) { return effectExecution(modelFn, func(context.Context, model.ToolCall) PreparedTool { return PreparedTool{} }), nil }) cancel() // the execution context is lost before the opener could start - if err := executeDirect(h, c, h.ctx, testOpID, prepared); !errors.Is(err, context.Canceled) { - t.Fatalf("execute = %v, want the context error", err) - } - if opens != 0 { - t.Fatalf("opener calls = %d, want a canceled execution to skip the unstarted opener", opens) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.Require(t, errors.Is(err, context.Canceled), "execute = %v, want the context error", err) } + testassert.Equal(t, opens, 0) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption", rec.State) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the skipped opener: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } } @@ -1225,22 +1026,18 @@ func TestExecuteOpenerErrorSettlesOrdinaryTerminals(t *testing.T) { Tool: func(context.Context, model.ToolCall) PreparedTool { return PreparedTool{} }, }, openErr }) - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != openErr { - t.Fatalf("execute = %v, want the exact opener error", err) - } - if modelRuns != 0 { - t.Fatalf("model effect ran %d times after a failed open, want zero", modelRuns) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.Equal(t, err, openErr) } + testassert.Equal(t, modelRuns, 0) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != "open broke" { - t.Fatalf("operation state = %+v, want terminal failure with the opener error text", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "open broke", "operation state = %+v, want terminal failure with the opener error text", rec.State) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the opening failure: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) @@ -1249,23 +1046,16 @@ func TestExecuteOpenerErrorSettlesOrdinaryTerminals(t *testing.T) { h, _, c, sessionID, prepared, _ := newOpenerHarness(t, func(context.Context, OperationAdmission) (Execution, error) { return Execution{}, storageErr }) - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != storageErr { - t.Fatalf("execute = %v, want the exact storage-class error", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.Equal(t, err, storageErr) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning { - t.Fatalf("operation status = %s, want the committed running state preserved for recovery", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationRunning) session, err := h.ReadSessionHeader(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if session.CurrentOperationID != testOpID { - t.Fatalf("session current operation = %q, want the running Operation still current", session.CurrentOperationID) - } + testassert.NoError(t, err) + testassert.Equal(t, session.CurrentOperationID, testOpID) }) } @@ -1297,9 +1087,7 @@ func TestExecuteInvalidOpenedExecutionClosesBeforeRejection(t *testing.T) { exec.Close = func() error { closes++ rec, err := h.ReadOperation(context.Background(), adm.SessionID, adm.OperationID) - if err != nil { - t.Errorf("read operation from the rejection closer: %v", err) - } + testassert.Check(t, err == nil, "read operation from the rejection closer: %v", err) statusDuringClose = rec.State.Status return nil } @@ -1311,28 +1099,17 @@ func TestExecuteInvalidOpenedExecutionClosesBeforeRejection(t *testing.T) { var prepared PreparedExecution h, store, c, sessionID, prepared, _ = newOpenerHarness(t, open) err := executeDirect(h, c, h.ctx, testOpID, prepared) - if !errors.Is(err, ErrInvalid) { - t.Fatalf("execute = %v, want the invalid-execution rejection", err) - } - if modelRuns != 0 { - t.Fatalf("model effect ran %d times with an invalid execution, want zero: no Agent runs with invalid effects", modelRuns) - } - if closes != 1 { - t.Fatalf("Close calls = %d, want exactly one for an invalid successful Execution", closes) - } - if statusDuringClose != OperationRunning { - t.Fatalf("close observed status %s, want the rejection published only after the cleanup", statusDuringClose) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "execute = %v, want the invalid-execution rejection", err) + testassert.Equal(t, modelRuns, 0) + testassert.Equal(t, closes, 1) + testassert.Equal(t, statusDuringClose, OperationRunning) settled, readErr := h.ReadOperation(context.Background(), sessionID, testOpID) - if readErr != nil { - t.Fatalf("ReadOperation: %v", readErr) - } - if settled.State.Status != OperationFailure || settled.State.Terminal == nil || settled.State.Terminal.Detail != err.Error() { - t.Fatalf("operation state = %+v, want terminal failure with the rejection text %q", settled.State, err) - } + testassert.NoError(t, readErr) + testassert.Require(t, settled.State.Status == OperationFailure && settled.State.Terminal != nil && settled.State.Terminal.Detail == err.Error(), "operation state = %+v, want terminal failure with the rejection text %q", settled.State, err) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the rejected execution: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) } @@ -1352,8 +1129,9 @@ func TestToolEffectPlansAndOutcomes(t *testing.T) { modelFn := func(context.Context, model.Request) (model.Stream, error) { return completedTurnStream(testToolCall("call-1")), nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } } success := func(_ context.Context, call model.ToolCall) PreparedTool { @@ -1506,41 +1284,23 @@ func TestToolEffectPlansAndOutcomes(t *testing.T) { return inner } got, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != tc.want { - t.Fatalf("committed result = %+v, want %+v", got, tc.want) - } - if executions != tc.wantExecutions { - t.Fatalf("executor ran %d times, want %d", executions, tc.wantExecutions) - } - if replaces != tc.wantReplaces { - t.Fatalf("%d register replacements, want %d", replaces, tc.wantReplaces) - } + testassert.NoError(t, err) + testassert.Equal(t, got, tc.want) + testassert.Equal(t, executions, tc.wantExecutions) + testassert.Equal(t, replaces, tc.wantReplaces) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the tool effect: %v", err) - } + testassert.NoError(t, err) results := 0 for _, entry := range graph.Entries { if entry.ToolResult != nil { results++ - if entry.ToolResult.ToolCallID != "call-1" || entry.ToolResult.Status != tc.want.Status || entry.ToolResult.Content != tc.want.Content { - t.Fatalf("tool result = %+v, want %+v", entry.ToolResult, tc.want) - } + testassert.Require(t, entry.ToolResult.ToolCallID == "call-1" && entry.ToolResult.Status == tc.want.Status && entry.ToolResult.Content == tc.want.Content, "tool result = %+v, want %+v", entry.ToolResult, tc.want) } } - if results != 1 { - t.Fatalf("%d tool result entries, want exactly one", results) - } + testassert.Equal(t, results, 1) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("operation state = %+v, want running with the effect cleared and the call resolved", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 0, "operation state = %+v, want running with the effect cleared and the call resolved", rec.State) }) } } @@ -1570,6 +1330,11 @@ func TestToolEffectPermissionBoundary(t *testing.T) { filePlan := func(permissions ...PermissionRequest) func(_ context.Context, call model.ToolCall) PreparedTool { return filePlanRoots("/w", "/w/sub", permissions...) } + captureWith := func(writeDir string, readonly bool) ExecutionCapture { + capture := testCapture() + capture.WriteDir, capture.Readonly = writeDir, readonly + return capture + } cases := []struct { name string capture ExecutionCapture @@ -1639,46 +1404,46 @@ func TestToolEffectPermissionBoundary(t *testing.T) { }, { name: "a relative canonical write root is denied", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), policy: allowAllWrites, plan: filePlanRoots("/w", "sub", PermissionRequest{Permission: PermissionFileWrite, Target: "/w/sub/a.txt"}), wantContent: PermissionDeniedContent, wantDenied: true, }, { name: "a lexically unclean canonical write root is denied", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), policy: allowAllWrites, plan: filePlanRoots("/w", "/w/sub/../sub", PermissionRequest{Permission: PermissionFileWrite, Target: "/w/sub/a.txt"}), wantContent: PermissionDeniedContent, wantDenied: true, }, { name: "readonly without write_dir denies file.write", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, Readonly: true}, + capture: captureWith("", true), plan: filePlan(PermissionRequest{Permission: PermissionFileWrite, Target: "/w/a.txt"}), wantContent: PermissionDeniedContent, wantDenied: true, }, { name: "readonly without write_dir still allows file.read", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, Readonly: true}, + capture: captureWith("", true), plan: filePlan(PermissionRequest{Permission: PermissionFileRead, Target: "/w/a.txt"}), wantContent: "ran", wantExecuted: true, }, { name: "write_dir confines file.write inside it", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), plan: filePlan(PermissionRequest{Permission: PermissionFileWrite, Target: "/w/sub/a.txt"}), wantContent: "ran", wantExecuted: true, }, { name: "write_dir denies file.write outside it even when policy allows", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), policy: allowAllWrites, plan: filePlan(PermissionRequest{Permission: PermissionFileWrite, Target: "/w/other.txt"}), wantContent: PermissionDeniedContent, wantDenied: true, }, { name: "configured write_dir without a canonical prepared write root is denied", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), policy: allowAllWrites, plan: func(_ context.Context, call model.ToolCall) PreparedTool { return PreparedTool{ @@ -1693,20 +1458,20 @@ func TestToolEffectPermissionBoundary(t *testing.T) { }, { name: "readonly with write_dir allows confined writes", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, Readonly: true, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", true), policy: allowAllWrites, plan: filePlan(PermissionRequest{Permission: PermissionFileWrite, Target: "/w/sub/a.txt"}), wantContent: "ran", wantExecuted: true, }, { name: "write_dir does not lift the built-in sensitive-basename denial", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), plan: filePlan(PermissionRequest{Permission: PermissionFileWrite, Target: "/w/sub/.env"}), wantContent: PermissionDeniedContent, wantDenied: true, }, { name: "an explicit user rule overrides the sensitive denial inside write_dir", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, WriteDir: "/w/sub"}, + capture: captureWith("/w/sub", false), policy: allowAllWrites, plan: filePlan(PermissionRequest{Permission: PermissionFileWrite, Target: "/w/sub/.env"}), wantContent: "ran", wantExecuted: true, @@ -1765,7 +1530,7 @@ func TestToolEffectPermissionBoundary(t *testing.T) { }, { name: "immediate success denied by the boundary carries no result content", - capture: ExecutionCapture{ConfigurationRevision: "rev-1", Model: testModelRef(), ContextWindow: 4096, OutputReserve: 2048, Compact: testCompactCapture(), SystemPrompt: "system", Tools: testCapture().Tools, Readonly: true}, + capture: captureWith("", true), plan: func(_ context.Context, call model.ToolCall) PreparedTool { return PreparedTool{ Permissions: []PermissionRequest{{Permission: PermissionFileWrite, Target: "/w/a.txt"}}, @@ -1798,28 +1563,18 @@ func TestToolEffectPermissionBoundary(t *testing.T) { return inner } got, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: objectNormalize, Permissions: tc.policy}, capture)(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } + testassert.NoError(t, err) wantStatus := model.ResultSuccess if tc.wantDenied { wantStatus = model.ResultDenied } - if got.Status != wantStatus || got.Content != tc.wantContent { - t.Fatalf("committed result = %+v, want status %s content %q", got, wantStatus, tc.wantContent) - } - if tc.wantExecuted != (executions == 1) || executions > 1 { - t.Fatalf("executor ran %d times, want executed=%v", executions, tc.wantExecuted) - } + testassert.Require(t, got.Status == wantStatus && got.Content == tc.wantContent, "committed result = %+v, want status %s content %q", got, wantStatus, tc.wantContent) + testassert.Require(t, tc.wantExecuted == (executions == 1) && executions <= 1, "executor ran %d times, want executed=%v", executions, tc.wantExecuted) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the tool effect: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.ToolResult != nil { - if tc.wantDenied && len(entry.ToolResult.Metadata) != 0 { - t.Fatalf("denied result carries metadata %s, want none", entry.ToolResult.Metadata) - } + testassert.Require(t, !tc.wantDenied || len(entry.ToolResult.Metadata) == 0, "denied result carries metadata %s, want none", entry.ToolResult.Metadata) } } }) @@ -1854,52 +1609,35 @@ func TestToolEffectDeniedSettlesOnlyItsCall(t *testing.T) { return nil } got, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("denied tool effect: %v", err) - } - if got.Status != model.ResultDenied || got.Content != PermissionDeniedContent { - t.Fatalf("call-1 result = %+v, want the fixed denial", got) - } - if executions != 0 { - t.Fatalf("concrete effects started = %d, want none for the denied call", executions) - } - if replaces != 2 { // Operation + Session only: denial never writes an effect intent - t.Fatalf("%d register replacements for the denial, want the intent-free transition (2)", replaces) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultDenied && got.Content == PermissionDeniedContent, "call-1 result = %+v, want the fixed denial", got) + testassert.Equal(t, executions, 0) + // Operation + Session only: denial never writes an effect intent + testassert.Equal(t, replaces, 2) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil { - t.Fatalf("operation after the denial = %+v, want running with no active effect", rec.State) - } - if len(rec.State.PendingToolCalls) != 1 || rec.State.PendingToolCalls[0].CallID != "call-2" { - t.Fatalf("pending calls = %+v, want the later call untouched", rec.State.PendingToolCalls) - } - if _, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-2")); err != nil { - t.Fatalf("later tool effect: %v", err) - } - if executions != 1 { - t.Fatalf("concrete effects started = %d, want exactly the later allowed call", executions) + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && (len(rec.State.PendingToolCalls) == 1 && rec.State.PendingToolCalls[0].CallID == "call-2"), "operation after the denial = %+v, want running with no active effect; pending calls = %+v, want the later call untouched", rec.State, rec.State.PendingToolCalls) + { + _, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-2")) + testassert.NoError(t, err) } + testassert.Equal(t, executions, 1) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after both calls: %v", err) - } + testassert.NoError(t, err) byCall := map[string]toolResultEntry{} for _, entry := range graph.Entries { if entry.ToolResult != nil { byCall[entry.ToolResult.ToolCallID] = *entry.ToolResult } } - if len(byCall) != 2 { - t.Fatalf("%d settled calls, want exactly one per call", len(byCall)) - } - if got := byCall["call-1"]; got.Status != model.ResultDenied || len(got.Metadata) != 0 { - t.Fatalf("call-1 result = %+v, want the metadata-less denial", got) + testassert.Equal(t, len(byCall), 2) + { + got := byCall["call-1"] + testassert.Require(t, got.Status == model.ResultDenied && len(got.Metadata) == 0, "call-1 result = %+v, want the metadata-less denial", got) } - if got := byCall["call-2"]; got.Status != model.ResultSuccess || got.Content != "ran" { - t.Fatalf("call-2 result = %+v, want the allowed execution", got) + { + got := byCall["call-2"] + testassert.Require(t, got.Status == model.ResultSuccess && got.Content == "ran", "call-2 result = %+v, want the allowed execution", got) } } @@ -1920,38 +1658,29 @@ func TestToolEffectAdvertisementGate(t *testing.T) { testToolCall("call-2"), ), nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) - } - if normalized != 1 { - t.Fatalf("normalization callback ran %d times, want only the advertised call", normalized) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } + testassert.Equal(t, normalized, 1) prepared := 0 plan := func(_ context.Context, call model.ToolCall) PreparedTool { prepared++ return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "done"}}} } got, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("unadvertised tool effect: %v", err) - } - if got.Status != model.ResultError || got.Content != `Tool "ghost" is not available.` { - t.Fatalf("unadvertised result = %+v, want the ordinary unavailable-tool error", got) - } - if normalized != 1 { - t.Fatalf("normalization callback after the rejection = %d, want the gate to precede any normalization", normalized) - } - if prepared != 0 { - t.Fatalf("preparations = %d, want none before the advertisement gate", prepared) - } - if _, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-2")); err != nil { - t.Fatalf("advertised tool effect: %v", err) - } - if prepared != 1 { - t.Fatalf("preparations = %d, want the advertised sibling to reach the preparer", prepared) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the gate: %v", err) + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultError && got.Content == `Tool "ghost" is not available.`, "unadvertised result = %+v, want the ordinary unavailable-tool error", got) + testassert.Equal(t, normalized, 1) + testassert.Equal(t, prepared, 0) + { + _, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-2")) + testassert.NoError(t, err) + } + testassert.Equal(t, prepared, 1) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } } @@ -1968,16 +1697,15 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { modelFn := func(context.Context, model.Request) (model.Stream, error) { return completedTurnStream(calls...), nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } } committedAssistant := func(t *testing.T, store *graphStorage, sessionID string) toolCallRecord { t.Helper() graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.Assistant != nil && len(entry.Assistant.ToolCalls) > 0 { return entry.Assistant.ToolCalls[0] @@ -1999,13 +1727,9 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { return objectNormalize(model.ToolCall{ID: call.ID, Name: call.Name, Arguments: original}) } publishWithNormalize(t, h, c, normalize, model.ToolCall{ID: "call-1", Name: "echo", Arguments: json.RawMessage(` {"x": 1} `)}) - if normalized != 1 { - t.Fatalf("producer normalizations = %d, want one", normalized) - } + testassert.Equal(t, normalized, 1) before := committedAssistant(t, store, sessionID) - if string(before.NormalizedArguments) != `{"x":1}` { - t.Fatalf("committed member = %q, want the compacted object", before.NormalizedArguments) - } + testassert.Require(t, string(before.NormalizedArguments) == `{"x":1}`, "committed member = %q, want the compacted object", before.NormalizedArguments) var seenName string var seen json.RawMessage plan := func(_ context.Context, call model.ToolCall) PreparedTool { @@ -2022,27 +1746,16 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { // unadvertised name would trip the advertisement gate if it were used. forged := model.ToolCall{ID: "call-1", Name: "forged", Arguments: json.RawMessage(`{"y":99}`)} got, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: normalize}, testCapture())(context.Background(), forged) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != model.ResultSuccess || got.Content != "done" { - t.Fatalf("result = %+v, want the committed call to execute, not a forged-name rejection", got) - } - if normalized != 1 { - t.Fatalf("normalizations after the boundary = %d, want the committed value consumed without a second pass", normalized) - } - if seenName != "echo" { - t.Fatalf("preparer name = %q, want the committed record's name", seenName) - } - if string(seen) != `{"x":1}` { - t.Fatalf("preparer arguments = %q, want byte-identical committed bytes", seen) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultSuccess && got.Content == "done", "result = %+v, want the committed call to execute, not a forged-name rejection", got) + testassert.Equal(t, normalized, 1) + testassert.Equal(t, seenName, "echo") + testassert.Require(t, string(seen) == `{"x":1}`, "preparer arguments = %q, want byte-identical committed bytes", seen) after := committedAssistant(t, store, sessionID) - if string(after.NormalizedArguments) != string(before.NormalizedArguments) { - t.Fatalf("assistant normalized member mutated to %q", after.NormalizedArguments) - } - if raw, err := base64.StdEncoding.DecodeString(after.ArgumentsBase64); err != nil || string(raw) != ` {"x": 1} ` { - t.Fatalf("assistant raw arguments mutated to %q (%v)", raw, err) + testassert.Require(t, string(after.NormalizedArguments) == string(before.NormalizedArguments), "assistant normalized member mutated to %q", after.NormalizedArguments) + { + raw, err := base64.StdEncoding.DecodeString(after.ArgumentsBase64) + testassert.Require(t, err == nil && string(raw) == ` {"x": 1} `, "assistant raw arguments mutated to %q (%v)", raw, err) } }) @@ -2054,9 +1767,7 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { return objectNormalize(call) } publishWithNormalize(t, h, c, normalize, model.ToolCall{ID: "call-1", Name: "echo", Arguments: json.RawMessage(` {"x": 1} `)}) - if normalized != 1 { - t.Fatalf("producer normalizations = %d, want one", normalized) - } + testassert.Equal(t, normalized, 1) var seen json.RawMessage plan := func(_ context.Context, call model.ToolCall) PreparedTool { seen = append(json.RawMessage(nil), call.Arguments...) @@ -2072,37 +1783,22 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { return nil } got, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != model.ResultSuccess || got.Content != "ran" { - t.Fatalf("result = %+v, want the executed outcome", got) - } - if replaces != 3 { // Operation + Session + the tool intent - t.Fatalf("%d register replacements, want the intent-backed 3", replaces) - } - if normalized != 1 { - t.Fatalf("normalizations = %d, want the committed value consumed through intent without a second pass", normalized) - } - if string(seen) != `{"x":1}` { - t.Fatalf("preparer arguments = %q, want the committed normalized bytes, not the raw %q", seen, ` {"x": 1} `) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultSuccess && got.Content == "ran", "result = %+v, want the executed outcome", got) + // Operation + Session + the tool intent + testassert.Equal(t, replaces, 3) + testassert.Equal(t, normalized, 1) + testassert.Require(t, string(seen) == `{"x":1}`, "preparer arguments = %q, want the committed normalized bytes, not the raw %q", seen, ` {"x": 1} `) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) results := 0 for _, entry := range graph.Entries { if entry.ToolResult != nil { results++ - if entry.ToolResult.Status != model.ResultSuccess || entry.ToolResult.Content != "ran" { - t.Fatalf("committed result = %+v, want the executor's outcome", *entry.ToolResult) - } + testassert.Require(t, entry.ToolResult.Status == model.ResultSuccess && entry.ToolResult.Content == "ran", "committed result = %+v, want the executor's outcome", *entry.ToolResult) } } - if results != 1 { - t.Fatalf("%d tool results committed, want the one executor outcome", results) - } + testassert.Equal(t, results, 1) }) t.Run("invalid original re-normalizes once at the boundary for its diagnostic", func(t *testing.T) { @@ -2113,30 +1809,18 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { return objectNormalize(call) } publishWithNormalize(t, h, c, normalize, model.ToolCall{ID: "call-1", Name: "echo", Arguments: json.RawMessage(`not json`)}) - if normalized != 1 { - t.Fatalf("producer normalizations = %d, want one failed attempt", normalized) - } - if len(committedAssistant(t, store, sessionID).NormalizedArguments) != 0 { - t.Fatalf("committed member present for an invalid original") - } + testassert.Equal(t, normalized, 1) + testassert.Equal(t, len(committedAssistant(t, store, sessionID).NormalizedArguments), 0) prepared := 0 plan := func(context.Context, model.ToolCall) PreparedTool { prepared++ return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: "call-1", Status: model.ResultSuccess, Content: "done"}}} } got, err := h.toolEffect(c, testOpID, Execution{Tool: plan, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if normalized != 2 { - t.Fatalf("normalizations = %d, want one more at the boundary for the diagnostic", normalized) - } - if got.Status != model.ResultError || got.Content != "arguments must be one non-null JSON object" { - t.Fatalf("result = %+v, want the callback's useful validation diagnostic", got) - } - if prepared != 0 { - t.Fatalf("preparations = %d, want none for an invalid original", prepared) - } + testassert.NoError(t, err) + testassert.Equal(t, normalized, 2) + testassert.Require(t, got.Status == model.ResultError && got.Content == "arguments must be one non-null JSON object", "result = %+v, want the callback's useful validation diagnostic", got) + testassert.Equal(t, prepared, 0) }) t.Run("committed extras reach the boundary in the decoded encoded form", func(t *testing.T) { @@ -2152,11 +1836,13 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { d.ToolFragments[0].Extra = model.Extra{"form": producer} return streamOf(d, usageDelta(model.Usage{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2})), nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } - if got := string(committedAssistant(t, store, sessionID).Extra["form"]); got != `{"n":[1,1.0,9007199254740993]}` { - t.Fatalf("committed extra = %q, want the decoded encoded form", got) + { + got := string(committedAssistant(t, store, sessionID).Extra["form"]) + testassert.Equal(t, got, `{"n":[1,1.0,9007199254740993]}`) } for i := range producer { // ownership oracle: clobber the producer's buffer after the commit producer[i] = ' ' @@ -2165,15 +1851,9 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { t.Fatalf("preparer must not run for the invalid original") return PreparedTool{} }, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != model.ResultError { - t.Fatalf("result = %+v, want the invalid original's validation error", got) - } - if seenAtBoundary != `{"n":[1,1.0,9007199254740993]}` { - t.Fatalf("boundary saw extra = %q, want the committed encoded form", seenAtBoundary) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultError, "result = %+v, want the invalid original's validation error", got) + testassert.Equal(t, seenAtBoundary, `{"n":[1,1.0,9007199254740993]}`) }) t.Run("oversized diagnostic is bounded", func(t *testing.T) { @@ -2187,12 +1867,8 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { t.Fatalf("preparer must not run for an invalid original") return PreparedTool{} }, NormalizeTool: normalize}, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != model.ResultError || len(got.Content) > maxToolDiagnosticBytes || !strings.HasPrefix(got.Content, "xyyyyy") { - t.Fatalf("result = %+v, want the diagnostic truncated within %d bytes", got, maxToolDiagnosticBytes) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultError && len(got.Content) <= maxToolDiagnosticBytes && strings.HasPrefix(got.Content, "xyyyyy"), "result = %+v, want the diagnostic truncated within %d bytes", got, maxToolDiagnosticBytes) }) t.Run("malformed callback outcome at the boundary is the internal-validation error", func(t *testing.T) { @@ -2211,15 +1887,9 @@ func TestToolEffectConsumesCommittedNormalization(t *testing.T) { t.Fatalf("preparer must not run for a malformed callback outcome") return PreparedTool{} }, NormalizeTool: boundaryNormalize}, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if normalized != 2 { - t.Fatalf("normalizations = %d, want one producer failure and one boundary failure", normalized) - } - if got.Status != model.ResultError || got.Content != invalidToolResultContent { - t.Fatalf("result = %+v, want the fixed internal-validation error", got) - } + testassert.NoError(t, err) + testassert.Equal(t, normalized, 2) + testassert.Require(t, got.Status == model.ResultError && got.Content == invalidToolResultContent, "result = %+v, want the fixed internal-validation error", got) }) } @@ -2267,33 +1937,20 @@ func TestToolOutcomeMetadataRules(t *testing.T) { return PreparedTool{Permissions: fixturePermission, Execute: func(context.Context) ToolOutcome { return outcome }} } got, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } + testassert.NoError(t, err) want := model.ToolResult{CallID: "call-1", Status: model.ResultSuccess, Content: "ran"} - if got != want { - t.Fatalf("agent-facing result = %+v, want only the plain Result", got) - } + testassert.Equal(t, got, want) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the tool effect: %v", err) - } + testassert.NoError(t, err) var result *toolResultEntry for i := range graph.Entries { if graph.Entries[i].ToolResult != nil { result = graph.Entries[i].ToolResult } } - if result == nil { - t.Fatalf("no tool result committed, want the result to survive any metadata decision") - } - if result.Status != model.ResultSuccess || result.Content != "ran" { - t.Fatalf("committed result = %+v, want the settled result", result) - } + testassert.Require(t, result != nil && (result.Status == model.ResultSuccess && result.Content == "ran"), "no tool result committed, want the result to survive any metadata decision; committed result = %+v, want the settled result", result) if tc.stored == "" { - if len(result.Metadata) != 0 { - t.Fatalf("committed metadata = %s, want absent", result.Metadata) - } + testassert.Require(t, len(result.Metadata) == 0, "committed metadata = %s, want absent", result.Metadata) } else if string(result.Metadata) != tc.stored { t.Fatalf("committed metadata = %q..., want %q...", result.Metadata[:min(len(result.Metadata), 20)], tc.stored[:min(len(tc.stored), 20)]) } @@ -2320,60 +1977,50 @@ func TestExecuteOrderedBatchSettlesExactlyOnce(t *testing.T) { toolFn := func(ctx context.Context, call model.ToolCall) PreparedTool { if !steered { // pending steering arrives inside the batch steered = true - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "steer-batch"); err != nil { - t.Errorf("steering submit: %v", err) + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "steer-batch") + testassert.Check(t, err == nil, "steering submit: %v", err) } } return spy.tool(ctx, call) } h, store, c, sessionID, prepared, _ := newExecutionHarness(t, modelFn, toolFn) - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } awaitOperationTerminal(t, store, sessionID, "op-2", OperationSuccess) // the pending head's own successor settles awaitHarnessQuiet(t, h, sessionID) - if got := spy.dispatched(); len(got) != 2 || got[0] != "call-1" || got[1] != "call-2" { - t.Fatalf("dispatch order = %v, want [call-1 call-2]", got) + { + got := spy.dispatched() + testassert.Require(t, len(got) == 2 && got[0] == "call-1" && got[1] == "call-2", "dispatch order = %v, want [call-1 call-2]", got) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the batch: %v", err) - } + testassert.NoError(t, err) results := map[string]int{} var lastSeq, firstAdmission int64 for _, entry := range graph.Entries { if entry.ToolResult != nil { results[entry.ToolResult.ToolCallID]++ - if entry.Envelope.Sequence <= lastSeq { - t.Fatalf("tool result %s committed out of order", entry.Envelope.ID) - } + testassert.Require(t, entry.Envelope.Sequence > lastSeq, "tool result %s committed out of order", entry.Envelope.ID) } if entry.Input != nil && entry.Input.Content[0].Text == "steer-batch" { firstAdmission = entry.Envelope.Sequence } lastSeq = entry.Envelope.Sequence } - if results["call-1"] != 1 || results["call-2"] != 1 { - t.Fatalf("result counts = %v, want exactly one per call", results) - } - if firstAdmission == 0 || lastSeq == 0 { - t.Fatalf("missing the successor admission or the last result entry") - } + testassert.Require(t, results["call-1"] == 1 && results["call-2"] == 1, "result counts = %v, want exactly one per call", results) + testassert.Require(t, firstAdmission != 0 && lastSeq != 0, "missing the successor admission or the last result entry") for _, entry := range graph.Entries { - if entry.ToolResult != nil && entry.Envelope.Sequence >= firstAdmission { - t.Fatalf("tool result %s committed after the successor admission", entry.Envelope.ID) - } + testassert.Require(t, entry.ToolResult == nil || entry.Envelope.Sequence < firstAdmission, "tool result %s committed after the successor admission", entry.Envelope.ID) } - if owner, _ := steeringInputOwner(t, store, sessionID, "steer-batch"); owner != "op-2" { - t.Fatalf("steering input owned by %q, want its own Operation op-2", owner) + { + owner, _ := steeringInputOwner(t, store, sessionID, "steer-batch") + testassert.Equal(t, owner, "op-2") } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationSuccess || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("operation state = %+v, want terminal success with every call resolved", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationSuccess && len(rec.State.PendingToolCalls) == 0, "operation state = %+v, want terminal success with every call resolved", rec.State) } // TestToolEffectRealOutcomeWinsCancellationRace proves a returned real outcome @@ -2396,27 +2043,20 @@ func TestToolEffectRealOutcomeWinsCancellationRace(t *testing.T) { } h, store, c, sessionID, prepared, harnessCancel := newExecutionHarness(t, modelFn, toolFn) cancel = harnessCancel - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the race: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.ToolResult != nil && entry.ToolResult.ToolCallID == "call-1" { - if entry.ToolResult.Status != model.ResultSuccess || entry.ToolResult.Content != "ran" { - t.Fatalf("tool result = %+v, want the real outcome", entry.ToolResult) - } + testassert.Require(t, entry.ToolResult.Status == model.ResultSuccess && entry.ToolResult.Content == "ran", "tool result = %+v, want the real outcome", entry.ToolResult) } } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption { - t.Fatalf("operation status = %s, want the outer interruption after the real outcome", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationInterruption) } // TestToolOriginatedInterruptionSettlesUnstartedCalls proves the reused @@ -2439,42 +2079,43 @@ func TestToolOriginatedInterruptionSettlesUnstartedCalls(t *testing.T) { } spy := &toolSpy{plan: toolFn} h, store, c, sessionID, prepared, _ := newExecutionHarness(t, modelFn, spy.tool) - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "steer-int"); err != nil { // pending steering through the stopped batch - t.Fatalf("steering submit: %v", err) + // pending steering through the stopped batch + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "steer-int") + testassert.NoError(t, err) } - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } awaitOperationTerminal(t, store, sessionID, "op-2", OperationSuccess) // the pending head drains as its own Operation awaitHarnessQuiet(t, h, sessionID) - if got := spy.dispatched(); len(got) != 1 || got[0] != "call-1" { - t.Fatalf("dispatch order = %v, want the batch stopped after call-1", got) + { + got := spy.dispatched() + testassert.Require(t, len(got) == 1 && got[0] == "call-1", "dispatch order = %v, want the batch stopped after call-1", got) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the interruption: %v", err) - } + testassert.NoError(t, err) byCall := map[string]toolResultEntry{} for _, entry := range graph.Entries { if entry.ToolResult != nil { byCall[entry.ToolResult.ToolCallID] = *entry.ToolResult } } - if got := byCall["call-1"]; got.Status != model.ResultInterrupted || got.Content != "stopped by the tool" { - t.Fatalf("call-1 result = %+v, want the executor's real interrupted result", got) + { + got := byCall["call-1"] + testassert.Require(t, got.Status == model.ResultInterrupted && got.Content == "stopped by the tool", "call-1 result = %+v, want the executor's real interrupted result", got) } - if got := byCall["call-2"]; got.Status != model.ResultInterrupted || got.Content != interruptedToolResultContent { - t.Fatalf("call-2 result = %+v, want the fixed interrupted-before-execution result", got) + { + got := byCall["call-2"] + testassert.Require(t, got.Status == model.ResultInterrupted && got.Content == interruptedToolResultContent, "call-2 result = %+v, want the fixed interrupted-before-execution result", got) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != "agent interrupted" { - t.Fatalf("operation state = %+v, want terminal interruption with the Agent's detail", rec.State) - } - if owner, _ := steeringInputOwner(t, store, sessionID, "steer-int"); owner != "op-2" { - t.Fatalf("steering input owned by %q, want its own Operation op-2", owner) + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "agent interrupted", "operation state = %+v, want terminal interruption with the Agent's detail", rec.State) + { + owner, _ := steeringInputOwner(t, store, sessionID, "steer-int") + testassert.Equal(t, owner, "op-2") } requireSessionCleared(t, h, sessionID) } @@ -2500,19 +2141,17 @@ func TestExecuteBetweenEffectCancellationSettlesInterruption(t *testing.T) { return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "done"}}} }) cancel = harnessCancel - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != "agent interrupted" { - t.Fatalf("operation state = %+v, want terminal interruption with the Agent's detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "agent interrupted", "operation state = %+v, want terminal interruption with the Agent's detail", rec.State) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the cancellation: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } } @@ -2546,26 +2185,22 @@ func TestExecuteCapSettlesFailure(t *testing.T) { done := make(chan error, 1) go func() { done <- executeDirect(h, c, h.ctx, testOpID, prepared) }() receiveBounded(t, arrived25, "the 25th model effect") - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } close(gate25) - if err := <-done; err != nil { - t.Fatalf("execute: %v", err) + { + err := <-done + testassert.NoError(t, err) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != "agent exceeded 25 model effects" { - t.Fatalf("operation state = %+v, want terminal failure at the cap", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "agent exceeded 25 model effects", "operation state = %+v, want terminal failure at the cap", rec.State) awaitOperationTerminal(t, store, sessionID, "op-2", OperationSuccess) // the pending head drains as its own Operation awaitHarnessQuiet(t, h, sessionID) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph at the cap: %v", err) - } + testassert.NoError(t, err) assistants, signals := 0, 0 for _, entry := range graph.Entries { if entry.Assistant != nil && entry.Envelope.OperationID == testOpID { @@ -2575,11 +2210,10 @@ func TestExecuteCapSettlesFailure(t *testing.T) { signals++ } } - if assistants != 25 || signals != 25 { - t.Fatalf("%d assistants and %d signals committed, want 25 of each", assistants, signals) - } - if owner, _ := steeringInputOwner(t, store, sessionID, "s1"); owner != "op-2" { - t.Fatalf("steering input owned by %q, want its own Operation op-2", owner) + testassert.Require(t, assistants == 25 && signals == 25, "%d assistants and %d signals committed, want 25 of each", assistants, signals) + { + owner, _ := steeringInputOwner(t, store, sessionID, "s1") + testassert.Equal(t, owner, "op-2") } requireSessionCleared(t, h, sessionID) } @@ -2608,33 +2242,25 @@ func TestToolResultAdoptsIntoView(t *testing.T) { immediate := func(_ context.Context, call model.ToolCall) PreparedTool { return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "ran " + call.ID}}} } - if _, err := h.toolEffect(c, testOpID, effectExecution(nil, immediate), testCapture())(context.Background(), testToolCall("call-1")); err != nil { - t.Fatalf("first tool effect: %v", err) + { + _, err := h.toolEffect(c, testOpID, effectExecution(nil, immediate), testCapture())(context.Background(), testToolCall("call-1")) + testassert.NoError(t, err) } msgs, err := source(context.Background()) - if err != nil { - t.Fatalf("projection after the first tool result: %v", err) - } - if len(msgs) != 4 { // system, input, assistant, tool result - t.Fatalf("projection carried %d messages, want the committed tool result included (4)", len(msgs)) - } + testassert.NoError(t, err) + // system, input, assistant, tool result + testassert.Equal(t, len(msgs), 4) tool := msgs[len(msgs)-1] - if tool.Role != model.RoleTool || tool.ToolCallID != "call-1" || tool.TextContent() != "ran call-1" { - t.Fatalf("projected tool message = %+v, want the committed call-1 result", tool) - } - if _, err := h.toolEffect(c, testOpID, effectExecution(nil, immediate), testCapture())(context.Background(), testToolCall("call-2")); err != nil { - t.Fatalf("second tool effect: %v", err) + testassert.Require(t, tool.Role == model.RoleTool && tool.ToolCallID == "call-1" && tool.TextContent() == "ran call-1", "projected tool message = %+v, want the committed call-1 result", tool) + { + _, err := h.toolEffect(c, testOpID, effectExecution(nil, immediate), testCapture())(context.Background(), testToolCall("call-2")) + testassert.NoError(t, err) } msgs, err = source(context.Background()) - if err != nil { - t.Fatalf("projection after the second tool result: %v", err) - } - if len(msgs) != 5 { - t.Fatalf("projection carried %d messages, want both tool results (5)", len(msgs)) - } - if tool = msgs[len(msgs)-1]; tool.ToolCallID != "call-2" || tool.TextContent() != "ran call-2" { - t.Fatalf("projected tool message = %+v, want the committed call-2 result", tool) - } + testassert.NoError(t, err) + testassert.Equal(t, len(msgs), 5) + tool = msgs[len(msgs)-1] + testassert.Require(t, tool.ToolCallID == "call-2" && tool.TextContent() == "ran call-2", "projected tool message = %+v, want the committed call-2 result", tool) } // publishCalls drives one ready model effect publishing the given calls as @@ -2644,8 +2270,9 @@ func publishCalls(t *testing.T, h *Harness, c *coordinator, sessionID string, ca modelFn := func(context.Context, model.Request) (model.Stream, error) { return completedTurnStream(calls...), nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } } @@ -2672,76 +2299,56 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { return nil } err := executeDirect(h, c, h.ctx, testOpID, prepared) - if !errors.Is(err, ErrStorage) { - t.Fatalf("execute = %v, want the injected storage failure", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "execute = %v, want the injected storage failure", err) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning { - t.Fatalf("operation status = %s, want the committed running state left for recovery", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationRunning) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the storage failure: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Settlement != nil { - t.Fatalf("settlement entry %s published over a storage failure", entry.Envelope.ID) - } + testassert.Require(t, entry.Settlement == nil, "settlement entry %s published over a storage failure", entry.Envelope.ID) } }) t.Run("execution-context error settles the fixed interruption detail", func(t *testing.T) { for _, runErr := range []error{context.Canceled, context.DeadlineExceeded} { h, store, c, sessionID := newEffectHarness(t, nil) publishCalls(t, h, c, sessionID, testToolCall("call-1")) - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{}, runErr, context.Background()); !errors.Is(err, runErr) { - t.Fatalf("settleAgentTerminal(%v) = %v, want the run error back", runErr, err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{}, runErr, context.Background()) + testassert.Require(t, errors.Is(err, runErr), "settleAgentTerminal(%v) = %v, want the run error back", runErr, err) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the fixed detail", rec.State) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the interruption: %v", err) + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the fixed detail", rec.State) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } } }) t.Run("non-storage protocol error still settles failure", func(t *testing.T) { h, _, c, sessionID := newEffectHarness(t, nil) runErr := errors.New("context source broke") - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{}, runErr, context.Background()); !errors.Is(err, runErr) { - t.Fatalf("settleAgentTerminal = %v, want the run error back", err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{}, runErr, context.Background()) + testassert.Require(t, errors.Is(err, runErr), "settleAgentTerminal = %v, want the run error back", err) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != runErr.Error() { - t.Fatalf("operation state = %+v, want terminal failure with the error text", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == runErr.Error(), "operation state = %+v, want terminal failure with the error text", rec.State) }) t.Run("boundary token settles the quiet success", func(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{}, errSteeringBoundary, context.Background()); err != nil { - t.Fatalf("settleAgentTerminal(token) = %v, want the cleared run error", err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{}, errSteeringBoundary, context.Background()) + testassert.NoError(t, err) } rec := settledOperation(t, store, sessionID, testOpID) - if rec.State.Status != OperationSuccess || rec.State.Terminal == nil || rec.State.Terminal.Detail != "" { - t.Fatalf("operation state = %+v, want quiet success with empty detail", rec.State) - } + testassert.Require(t, rec.State.Status == OperationSuccess && rec.State.Terminal != nil && rec.State.Terminal.Detail == "", "operation state = %+v, want quiet success with empty detail", rec.State) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the token: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Signal != nil || (entry.Assistant != nil && entry.Envelope.OperationID == testOpID) { - t.Fatalf("entry %s added a signal or assistant behind the boundary token", entry.Envelope.ID) - } + testassert.Require(t, entry.Signal == nil && (entry.Assistant == nil || entry.Envelope.OperationID != testOpID), "entry %s added a signal or assistant behind the boundary token", entry.Envelope.ID) } }) t.Run("shared success guard converts only the matching interruption", func(t *testing.T) { @@ -2771,23 +2378,21 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { cancel() execCtx = canceled } - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, execCtx); err != nil { - t.Fatalf("settleAgentTerminal = %v, want the guarded success settled", err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalSuccess}, nil, execCtx) + testassert.NoError(t, err) } rec := settledOperation(t, store, sessionID, testOpID) - if rec.State.Status != tc.want { - t.Fatalf("operation state = %+v, want %s", rec.State, tc.want) - } - if tc.want == OperationInterruption && (rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail) { - t.Fatalf("terminal = %+v, want the fixed interrupted detail", rec.State.Terminal) - } + testassert.Require(t, rec.State.Status == tc.want, "operation state = %+v, want %s", rec.State, tc.want) + testassert.Require(t, tc.want != OperationInterruption || rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "terminal = %+v, want the fixed interrupted detail", rec.State.Terminal) }) } }) t.Run("one attempted success publication keeps its original error", func(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } attempts := 0 injected := fmt.Errorf("%w: injected publication failure", ErrStorage) @@ -2800,27 +2405,18 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { } run := &activeExecution{execCtx: context.Background(), cancel: func() {}} err := h.finishExecution(c, run, testOpID, &agent.TerminalResult{Status: agent.TerminalSuccess}, nil, nil) - if !errors.Is(err, ErrStorage) || err.Error() != injected.Error() { - t.Fatalf("finishExecution = %v, want the original injected error", err) - } + testassert.Require(t, errors.Is(err, ErrStorage) && err.Error() == injected.Error(), "finishExecution = %v, want the original injected error", err) store.txHook = nil - if attempts != 1 { - t.Fatalf("publication attempts = %d, want exactly one", attempts) - } + testassert.Equal(t, attempts, 1) c.mu.Lock() held, buffered := c.reserved != nil, len(c.steering) c.mu.Unlock() - if held { - t.Fatalf("admission reservation stayed held after the completion") - } - if buffered != 1 { - t.Fatalf("buffered heads = %d, want the pending head retained", buffered) - } - if storedOperationExists(store, sessionID, "op-2") { - t.Fatalf("successor admitted while the current work remains") - } - if rec := settledOperation(t, store, sessionID, testOpID); rec.State.Status != OperationRunning { - t.Fatalf("operation state = %+v, want the running state left for recovery", rec.State) + testassert.Require(t, !held, "admission reservation stayed held after the completion") + testassert.Equal(t, buffered, 1) + testassert.Require(t, !storedOperationExists(store, sessionID, "op-2"), "successor admitted while the current work remains") + { + rec := settledOperation(t, store, sessionID, testOpID) + testassert.Require(t, rec.State.Status == OperationRunning, "operation state = %+v, want the running state left for recovery", rec.State) } }) t.Run("acquisition cancellation skips only the drain", func(t *testing.T) { @@ -2840,13 +2436,12 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { }, func(_ context.Context, call model.ToolCall) PreparedTool { return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "ok"}}} }) - if _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, sessionID, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } release, err := h.reserve(context.Background(), c) // another holder owns the reservation - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) defer release() harnessCancel() // the Harness context dies: the acquisition is canceled closes := 0 @@ -2855,47 +2450,32 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { closes++ return nil }) - if tc.res == nil && got != tc.runErr { - t.Fatalf("finishExecution = %v, want the original run error retained", got) - } - if tc.res != nil && got != nil { - t.Fatalf("finishExecution = %v, want the settled outcome, never the acquisition error", got) - } - if closes != 1 { - t.Fatalf("Close calls = %d, want exactly one", closes) - } + testassert.Require(t, tc.res != nil || got == tc.runErr, "finishExecution = %v, want the original run error retained", got) + testassert.Require(t, tc.res == nil || got == nil, "finishExecution = %v, want the settled outcome, never the acquisition error", got) + testassert.Equal(t, closes, 1) c.mu.Lock() held, buffered := c.reserved != nil, len(c.steering) c.mu.Unlock() - if !held { - t.Fatalf("the other holder's reservation was released") - } - if buffered != 1 { - t.Fatalf("buffered heads = %d, want the drain skipped and the head retained", buffered) - } - if rec := settledOperation(t, store, sessionID, testOpID); rec.State.Status != tc.want { - t.Fatalf("operation state = %+v, want %s", rec.State, tc.want) + testassert.Require(t, held, "the other holder's reservation was released") + testassert.Equal(t, buffered, 1) + { + rec := settledOperation(t, store, sessionID, testOpID) + testassert.Require(t, rec.State.Status == tc.want, "operation state = %+v, want %s", rec.State, tc.want) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) settlements := 0 for _, entry := range graph.Entries { if entry.Settlement != nil && entry.Envelope.OperationID == testOpID { settlements++ } } - if settlements != tc.wantSet { - t.Fatalf("settlement entries = %d, want %d", settlements, tc.wantSet) - } + testassert.Equal(t, settlements, tc.wantSet) h.mu.Lock() latched, cleanup := h.storageFailure, h.cleanupFailure h.mu.Unlock() if tc.res == nil { - if latched != tc.runErr { - t.Fatalf("recorded storage failure = %v, want the authoritative original %v", latched, tc.runErr) - } + testassert.Equal(t, latched, tc.runErr) } else if latched != nil || cleanup != nil { t.Fatalf("recorded failures = (%v, %v), want the successful outcome unmodified", latched, cleanup) } @@ -2912,28 +2492,30 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { foreignEffectRace(t, store, sessionID, testOpID, "foreign") }}, {"retained active effect", func(t *testing.T, h *Harness, store *graphStorage, c *coordinator, sessionID string) { - if err := store.Transact(context.Background(), func(tx Transaction) error { - key := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID} - reg, err := tx.ReadRegister(key) - if err != nil { + { + err := store.Transact(context.Background(), func(tx Transaction) error { + key := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID} + reg, err := tx.ReadRegister(key) + if err != nil { + return err + } + op, err := decodeOperationRegister(reg) + if err != nil { + return err + } + op.State.ActiveEffect = &ActiveEffect{Kind: EffectModel, ResultEntryID: hexID(7)} + payload, err := encodeOperationRegister(op) + if err != nil { + return err + } + _, err = tx.ReplaceRegister(key, reg.Revision, payload) return err - } - op, err := decodeOperationRegister(reg) - if err != nil { - return err - } - op.State.ActiveEffect = &ActiveEffect{Kind: EffectModel, ResultEntryID: hexID(7)} - payload, err := encodeOperationRegister(op) - if err != nil { - return err - } - _, err = tx.ReplaceRegister(key, reg.Revision, payload) - return err - }); err != nil { - t.Fatalf("foreign active effect: %v", err) + }) + testassert.NoError(t, err) } - if err := h.rematerialize(context.Background(), c, sessionID); err != nil { - t.Fatalf("rematerialize: %v", err) + { + err := h.rematerialize(context.Background(), c, sessionID) + testassert.NoError(t, err) } }}, } { @@ -2942,23 +2524,16 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { tc.foreign(t, h, store, c, sessionID) run := &activeExecution{execCtx: context.Background(), cancel: func() {}} err := h.finishExecution(c, run, testOpID, &agent.TerminalResult{Status: agent.TerminalSuccess}, nil, nil) - if tc.name == "quiet running state" && !errors.Is(err, ErrConflict) { - t.Fatalf("finishExecution = %v, want the revision race's original error", err) - } - if tc.name == "retained active effect" && err != nil { - t.Fatalf("finishExecution = %v, want the guarded skip's cleared value", err) - } + testassert.Require(t, tc.name != "quiet running state" || errors.Is(err, ErrConflict), "finishExecution = %v, want the revision race's original error", err) + testassert.Require(t, tc.name != "retained active effect" || err == nil, "finishExecution = %v, want the guarded skip's cleared value", err) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the completion: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Settlement != nil && entry.Envelope.OperationID == testOpID { - t.Fatalf("settlement entry %s published over the rematerialized running state", entry.Envelope.ID) - } + testassert.Require(t, entry.Settlement == nil || entry.Envelope.OperationID != testOpID, "settlement entry %s published over the rematerialized running state", entry.Envelope.ID) } - if rec := settledOperation(t, store, sessionID, testOpID); rec.State.Status != OperationRunning { - t.Fatalf("operation state = %+v, want the running state left for recovery", rec.State) + { + rec := settledOperation(t, store, sessionID, testOpID) + testassert.Require(t, rec.State.Status == OperationRunning, "operation state = %+v, want the running state left for recovery", rec.State) } }) } @@ -2966,53 +2541,51 @@ func TestSettleAgentTerminalClassifiesRunError(t *testing.T) { t.Run("an already committed settlement is never rewritten", func(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) var settlementID string - if err := store.Transact(context.Background(), func(tx Transaction) error { // one coherent foreign settle - skey := RegisterKey{SessionID: sessionID, Kind: RegisterSession} - sreg, err := tx.ReadRegister(skey) - if err != nil { - return err - } - session, err := decodeSessionRegister(sreg) - if err != nil { - return err - } - okey := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID} - oreg, err := tx.ReadRegister(okey) - if err != nil { - return err - } - op, err := decodeOperationRegister(oreg) - if err != nil { - return err - } - committed, _, _, err := commitTerminalSettlement(tx, sessionID, testOpID, session, op, sreg.Revision, oreg.Revision, OperationSuccess, "", nil, model.ModelRef{}, nil) - if err != nil { - return err - } - settlementID = committed.State.Terminal.SettlementEntry.EntryID - return nil - }); err != nil { - t.Fatalf("foreign settle: %v", err) + // one coherent foreign settle + { + err := store.Transact(context.Background(), func(tx Transaction) error { // one coherent foreign settle + skey := RegisterKey{SessionID: sessionID, Kind: RegisterSession} + sreg, err := tx.ReadRegister(skey) + if err != nil { + return err + } + session, err := decodeSessionRegister(sreg) + if err != nil { + return err + } + okey := RegisterKey{SessionID: sessionID, Kind: RegisterOperation, OperationID: testOpID} + oreg, err := tx.ReadRegister(okey) + if err != nil { + return err + } + op, err := decodeOperationRegister(oreg) + if err != nil { + return err + } + committed, _, _, err := commitTerminalSettlement(tx, sessionID, testOpID, session, op, sreg.Revision, oreg.Revision, OperationSuccess, "", nil, model.ModelRef{}, nil, false) + if err != nil { + return err + } + settlementID = committed.State.Terminal.SettlementEntry.EntryID + return nil + }) + testassert.NoError(t, err) } - if err := h.rematerialize(context.Background(), c, sessionID); err != nil { - t.Fatalf("rematerialize: %v", err) + { + err := h.rematerialize(context.Background(), c, sessionID) + testassert.NoError(t, err) } run := &activeExecution{execCtx: context.Background(), cancel: func() {}} - if err := h.finishExecution(c, run, testOpID, &agent.TerminalResult{Status: agent.TerminalSuccess}, nil, nil); err != nil { - t.Fatalf("finishExecution = %v, want the guarded skip's cleared value", err) + { + err := h.finishExecution(c, run, testOpID, &agent.TerminalResult{Status: agent.TerminalSuccess}, nil, nil) + testassert.NoError(t, err) } rec := settledOperation(t, store, sessionID, testOpID) - if rec.State.Status != OperationSuccess || rec.State.Terminal == nil || rec.State.Terminal.SettlementEntry.EntryID != settlementID { - t.Fatalf("operation state = %+v, want the committed settlement %s preserved", rec.State, settlementID) - } + testassert.Require(t, rec.State.Status == OperationSuccess && rec.State.Terminal != nil && rec.State.Terminal.SettlementEntry.EntryID == settlementID, "operation state = %+v, want the committed settlement %s preserved", rec.State, settlementID) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the completion: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Settlement != nil && entry.Envelope.OperationID == testOpID && entry.Envelope.ID != settlementID { - t.Fatalf("settlement entry %s rewrote the committed settlement", entry.Envelope.ID) - } + testassert.Require(t, entry.Settlement == nil || entry.Envelope.OperationID != testOpID || entry.Envelope.ID == settlementID, "settlement entry %s rewrote the committed settlement", entry.Envelope.ID) } }) } @@ -3035,43 +2608,35 @@ func TestToolEffectGateBeforePreparation(t *testing.T) { return nil } got, err := h.toolEffect(c, testOpID, effectExecution(nil, spy.tool), testCapture())(ctx, testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if dispatched := spy.dispatched(); len(dispatched) != 0 { - t.Fatalf("prepared invoked for %v, want zero preparations", dispatched) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent}) { - t.Fatalf("committed result = %+v, want the interrupted-before-execution result", got) - } - if replaces != 2 { // Operation + Session registers only: no effect intent - t.Fatalf("%d register replacements, want the intent-free transition (2)", replaces) - } + testassert.NoError(t, err) + { + dispatched := spy.dispatched() + testassert.Require(t, len(dispatched) == 0, "prepared invoked for %v, want zero preparations", dispatched) + } + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent})) + // Operation + Session registers only: no effect intent + testassert.Equal(t, replaces, 2) // The run settles interruption and the terminal helper interrupts the // remaining unstarted call. - if err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalInterruption, Detail: executionInterruptedDetail}, nil, context.Background()); err != nil { - t.Fatalf("outer settlement: %v", err) + { + err := h.settleAgentTerminal(c, testOpID, agent.TerminalResult{Status: agent.TerminalInterruption, Detail: executionInterruptedDetail}, nil, context.Background()) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the interruption: %v", err) - } + testassert.NoError(t, err) byCall := map[string]toolResultEntry{} for _, entry := range graph.Entries { if entry.ToolResult != nil { byCall[entry.ToolResult.ToolCallID] = *entry.ToolResult } } - if got := byCall["call-2"]; got.Status != model.ResultInterrupted || got.Content != interruptedToolResultContent { - t.Fatalf("call-2 result = %+v, want the terminal helper's interrupted result", got) + { + got := byCall["call-2"] + testassert.Require(t, got.Status == model.ResultInterrupted && got.Content == interruptedToolResultContent, "call-2 result = %+v, want the terminal helper's interrupted result", got) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption", rec.State) requireSessionCleared(t, h, sessionID) } @@ -3103,39 +2668,30 @@ func TestAssistantEntryOriginalNormalizationCommitsAtProducer(t *testing.T) { ), nil } h, store, c, sessionID := newEffectHarness(t, modelFn) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) - } - if normalized != 3 { - t.Fatalf("normalization callback ran %d times, want exactly one per advertised completed call", normalized) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, Execution{Model: modelFn, NormalizeTool: normalize}, testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } + testassert.Equal(t, normalized, 3) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the model effect: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.Assistant == nil || len(entry.Assistant.ToolCalls) != 4 { continue } calls := entry.Assistant.ToolCalls - if raw, err := base64.StdEncoding.DecodeString(calls[0].ArgumentsBase64); err != nil || string(raw) != string(valid) { - t.Fatalf("call-1 raw arguments = %q err %v, want the exact caller bytes", raw, err) - } - if string(calls[0].NormalizedArguments) != `{"x":1}` { - t.Fatalf("call-1 normalized arguments = %q, want the successful callback object", calls[0].NormalizedArguments) - } - if raw, err := base64.StdEncoding.DecodeString(calls[1].ArgumentsBase64); err != nil || string(raw) != string(malformed) { - t.Fatalf("call-2 raw arguments = %q err %v, want the exact malformed bytes preserved", raw, err) - } - if len(calls[1].NormalizedArguments) != 0 { - t.Fatalf("call-2 normalized arguments = %q, want the member absent for a failed normalization", calls[1].NormalizedArguments) - } - if len(calls[2].NormalizedArguments) != 0 { - t.Fatalf("call-3 normalized arguments = %q, want the member absent for a malformed callback result", calls[2].NormalizedArguments) + { + raw, err := base64.StdEncoding.DecodeString(calls[0].ArgumentsBase64) + testassert.Require(t, err == nil && string(raw) == string(valid), "call-1 raw arguments = %q err %v, want the exact caller bytes", raw, err) } - if len(calls[3].NormalizedArguments) != 0 { - t.Fatalf("call-4 normalized arguments = %q, want the member absent for an unadvertised name", calls[3].NormalizedArguments) + testassert.Require(t, string(calls[0].NormalizedArguments) == `{"x":1}`, "call-1 normalized arguments = %q, want the successful callback object", calls[0].NormalizedArguments) + { + raw, err := base64.StdEncoding.DecodeString(calls[1].ArgumentsBase64) + testassert.Require(t, err == nil && string(raw) == string(malformed), "call-2 raw arguments = %q err %v, want the exact malformed bytes preserved", raw, err) } + testassert.Require(t, len(calls[1].NormalizedArguments) == 0, "call-2 normalized arguments = %q, want the member absent for a failed normalization", calls[1].NormalizedArguments) + testassert.Require(t, len(calls[2].NormalizedArguments) == 0, "call-3 normalized arguments = %q, want the member absent for a malformed callback result", calls[2].NormalizedArguments) + testassert.Require(t, len(calls[3].NormalizedArguments) == 0, "call-4 normalized arguments = %q, want the member absent for an unadvertised name", calls[3].NormalizedArguments) return } t.Fatalf("no assistant entry with four calls committed") @@ -3163,24 +2719,17 @@ func TestModelEffectIntentCancellationSettlesInterruption(t *testing.T) { } return nil } - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the fixed detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the fixed detail", rec.State) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the cancellation: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { - if entry.Assistant != nil { - t.Fatalf("assistant entry %s published after an aborted intent", entry.Envelope.ID) - } + testassert.Require(t, entry.Assistant == nil, "assistant entry %s published after an aborted intent", entry.Envelope.ID) } requireSessionCleared(t, h, sessionID) } @@ -3217,37 +2766,32 @@ func TestToolEffectIntentCancellationSettlesInterrupted(t *testing.T) { } return nil } - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the cancellation: %v", err) - } + testassert.NoError(t, err) byCall := map[string]toolResultEntry{} for _, entry := range graph.Entries { if entry.ToolResult != nil { byCall[entry.ToolResult.ToolCallID] = *entry.ToolResult } } - if got := byCall["call-1"]; got.Status != model.ResultInterrupted || got.Content != interruptedToolResultContent { - t.Fatalf("call-1 result = %+v, want the interrupted-before-execution result", got) + { + got := byCall["call-1"] + testassert.Require(t, got.Status == model.ResultInterrupted && got.Content == interruptedToolResultContent, "call-1 result = %+v, want the interrupted-before-execution result", got) } - if got := byCall["call-2"]; got.Status != model.ResultInterrupted || got.Content != interruptedToolResultContent { - t.Fatalf("call-2 result = %+v, want the terminal helper's interrupted result", got) + { + got := byCall["call-2"] + testassert.Require(t, got.Status == model.ResultInterrupted && got.Content == interruptedToolResultContent, "call-2 result = %+v, want the terminal helper's interrupted result", got) } for call, got := range byCall { - if len(got.Metadata) != 0 { - t.Fatalf("%s result carries metadata %s, want none on synthetic results", call, got.Metadata) - } + testassert.Require(t, len(got.Metadata) == 0, "%s result carries metadata %s, want none on synthetic results", call, got.Metadata) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the fixed detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the fixed detail", rec.State) requireSessionCleared(t, h, sessionID) } @@ -3276,24 +2820,16 @@ func TestSessionGraphReplaceOperation(t *testing.T) { t.Run("replaces only the addressed record", func(t *testing.T) { g := &sessionGraph{Operations: []OperationRecord{record("op-1", 1), record("op-2", 2), record("op-3", 3)}} - if !g.replaceOperation("op-2", record("op-2", 42)) { - t.Fatalf("replaceOperation of a carried identity = false, want true") - } + testassert.Require(t, g.replaceOperation("op-2", record("op-2", 42)), "replaceOperation of a carried identity = false, want true") want := []OperationRecord{record("op-1", 1), record("op-2", 42), record("op-3", 3)} - if !reflect.DeepEqual(g.Operations, want) { - t.Fatalf("operations after replacement = %+v, want %+v", g.Operations, want) - } + testassert.Require(t, reflect.DeepEqual(g.Operations, want), "operations after replacement = %+v, want %+v", g.Operations, want) }) t.Run("missing identity is a no-op", func(t *testing.T) { g := &sessionGraph{Operations: []OperationRecord{record("op-1", 1), record("op-2", 2), record("op-3", 3)}} - if g.replaceOperation("op-ghost", record("op-ghost", 9)) { - t.Fatalf("replaceOperation of an absent identity = true, want false") - } + testassert.Require(t, !g.replaceOperation("op-ghost", record("op-ghost", 9)), "replaceOperation of an absent identity = true, want false") want := []OperationRecord{record("op-1", 1), record("op-2", 2), record("op-3", 3)} - if !reflect.DeepEqual(g.Operations, want) { - t.Fatalf("operations after a missing identity = %+v, want the untouched records %+v", g.Operations, want) - } + testassert.Require(t, reflect.DeepEqual(g.Operations, want), "operations after a missing identity = %+v, want the untouched records %+v", g.Operations, want) }) t.Run("replacement allocates nothing", func(t *testing.T) { @@ -3304,8 +2840,9 @@ func TestSessionGraphReplaceOperation(t *testing.T) { } g := &sessionGraph{Operations: operations} updated := record("op-63", 999) - if n := testing.AllocsPerRun(100, func() { g.replaceOperation("op-63", updated) }); n != 0 { - t.Fatalf("one-record replacement over %d operations allocated %v times, want zero", count, n) + { + n := testing.AllocsPerRun(100, func() { g.replaceOperation("op-63", updated) }) + testassert.Require(t, n == 0, "one-record replacement over %d operations allocated %v times, want zero", count, n) } }) } diff --git a/harness/graph_test.go b/harness/graph_test.go index f90fd66e..40b8e133 100644 --- a/harness/graph_test.go +++ b/harness/graph_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -301,19 +302,12 @@ type testGraph struct { entries []testEntry } -func mustEncode(t testing.TB, err error) { - t.Helper() - if err != nil { - t.Fatalf("fixture encode: %v", err) - } -} - // storage encodes every fixture record into a fresh read-only substitute. func (g *testGraph) storage(t testing.TB) *graphStorage { t.Helper() out := &graphStorage{registers: map[string][]Register{}, entries: map[string][]Entry{}} sessionRaw, err := encodeSessionRegister(g.session) - mustEncode(t, err) + testassert.NoError(t, err) if g.sessionRawOverride != nil { sessionRaw = g.sessionRawOverride } @@ -322,7 +316,7 @@ func (g *testGraph) storage(t testing.TB) *graphStorage { for i := range g.ops { op := &g.ops[i] opRaw, err := encodeOperationRegister(*op) - mustEncode(t, err) + testassert.NoError(t, err) out.registers[g.session.Identity.SessionID] = append(out.registers[g.session.Identity.SessionID], Register{Key: RegisterKey{SessionID: g.session.Identity.SessionID, Kind: RegisterOperation, OperationID: op.Admission.OperationID}, Revision: op.Revision, Payload: opRaw}) } @@ -333,31 +327,31 @@ func (g *testGraph) storage(t testing.TB) *graphStorage { env.Payload = entry.rawOverride case entry.input != nil: raw, err := encodeInputEntry(*entry.input) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw case entry.assistant != nil: raw, err := encodeAssistantEntry(*entry.assistant) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw case entry.toolResult != nil: raw, err := encodeToolResultEntry(*entry.toolResult) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw case entry.signal != nil: raw, err := encodeSignalEntry(*entry.signal) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw case entry.hookResult != nil: raw, err := encodeHookResultEntry(*entry.hookResult) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw case entry.settlement != nil: raw, err := encodeOperationSettlementEntry(*entry.settlement) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw case entry.compaction != nil: raw, err := encodeCompactionEntry(*entry.compaction) - mustEncode(t, err) + testassert.NoError(t, err) env.Payload = raw default: t.Fatalf("fixture entry %s has no payload", env.ID) @@ -401,24 +395,16 @@ func mustState(t *testing.T, raw json.RawMessage) json.RawMessage { t.Helper() obj := wireObject(t, raw) state, ok := obj["state"] - if !ok { - t.Fatalf("session payload carries no state member") - } + testassert.Require(t, ok, "session payload carries no state member") return state } func wantCorruption(t *testing.T, err error) *CorruptionError { t.Helper() - if err == nil { - t.Fatalf("expected corruption, got a valid graph") - } + testassert.Require(t, err != nil, "expected corruption, got a valid graph") var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("error %v is not a CorruptionError", err) - } - if corrupt.SessionID != testSessionID { - t.Fatalf("corruption names session %q, want %q", corrupt.SessionID, testSessionID) - } + testassert.Require(t, errors.As(err, &corrupt), "error %v is not a CorruptionError", err) + testassert.Equal(t, corrupt.SessionID, testSessionID) return corrupt } @@ -447,49 +433,30 @@ func TestGraphValidatorAcceptsCoherentSessions(t *testing.T) { t.Run("running session", func(t *testing.T) { fixture := validTestGraph() view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid graph rejected: %v", err) - } - if view.Session.Revision != validSessionRecord().Revision || - view.Session.Identity.SessionID != testSessionID || - view.Session.State.CurrentOperationID != testOpID { - t.Fatalf("view session = %+v", view.Session) - } - if len(view.Operations) != 1 || view.Operations[0].Admission.OperationID != testOpID { - t.Fatalf("view operations = %+v", view.Operations) - } - if _, ok := view.Operation(testOpID); !ok { - t.Fatalf("Operation lookup by identity failed") - } - if len(view.Entries) != 2 || view.Entries[0].Input == nil || view.Entries[1].Assistant == nil { - t.Fatalf("view entries = %+v", view.Entries) - } - if view.Entries[0].Envelope.ID != hexID(1) || view.Entries[1].Envelope.ID != hexID(2) { - t.Fatalf("view entries are not in committed order: %+v", view.Entries) + testassert.NoError(t, err) + testassert.Require(t, view.Session.Revision == validSessionRecord().Revision && view.Session.Identity.SessionID == testSessionID && view.Session.State.CurrentOperationID == testOpID, "view session = %+v", view.Session) + testassert.Require(t, len(view.Operations) == 1 && view.Operations[0].Admission.OperationID == testOpID, "view operations = %+v", view.Operations) + { + _, ok := view.Operation(testOpID) + testassert.Require(t, ok, "Operation lookup by identity failed") } + testassert.Require(t, len(view.Entries) == 2 && view.Entries[0].Input != nil && view.Entries[1].Assistant != nil, "view entries = %+v", view.Entries) + testassert.Require(t, view.Entries[0].Envelope.ID == hexID(1) && view.Entries[1].Envelope.ID == hexID(2), "view entries are not in committed order: %+v", view.Entries) }) t.Run("running model effect", func(t *testing.T) { fixture := validTestGraph() fixture.ops[0].State.ActiveEffect = &ActiveEffect{Kind: EffectModel, ResultEntryID: hexID(3)} view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid model-effect graph rejected: %v", err) - } - if view.Operations[0].State.ActiveEffect == nil || view.Operations[0].State.ActiveEffect.Kind != EffectModel { - t.Fatalf("typed view lost the active model effect") - } + testassert.NoError(t, err) + testassert.Require(t, view.Operations[0].State.ActiveEffect != nil && view.Operations[0].State.ActiveEffect.Kind == EffectModel, "typed view lost the active model effect") }) t.Run("pending tool call", func(t *testing.T) { fixture := singleCallGraph(hexID(3)) view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid pending shape rejected: %v", err) - } - if len(view.Entries[1].Assistant.ToolCalls) != 1 { - t.Fatalf("typed assistant payload lost its call: %+v", view.Entries[1]) - } + testassert.NoError(t, err) + testassert.Require(t, len(view.Entries[1].Assistant.ToolCalls) == 1, "typed assistant payload lost its call: %+v", view.Entries[1]) }) t.Run("resolved tool result", func(t *testing.T) { @@ -507,8 +474,9 @@ func TestGraphValidatorAcceptsCoherentSessions(t *testing.T) { env: Entry{SessionID: testSessionID, ID: hexID(3), OperationID: testOpID, Kind: EntryToolResult, Sequence: 3, CommittedAt: testTime}, toolResult: &result, }) - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("valid resolved shape rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -537,8 +505,9 @@ func TestGraphValidatorAcceptsCoherentSessions(t *testing.T) { totals := UsageTotals{ByModel: []ModelUsage{{Model: ref, Usage: *testUsage(5)}}} op.State.Usage = totals session.State.Usage = totals - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("valid terminal shape rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -565,8 +534,9 @@ func TestGraphValidatorAcceptsCoherentSessions(t *testing.T) { {env: Entry{SessionID: testSessionID, ID: hexID(3), Kind: EntryToolResult, Sequence: 3, CommittedAt: testTime}, toolResult: &copiedResult}, {env: Entry{SessionID: testSessionID, ID: hexID(4), Kind: EntrySignal, Sequence: 4, CommittedAt: testTime}, signal: &copiedSignal}, } - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("valid fork prefix rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -587,24 +557,16 @@ func TestGraphValidatorAcceptsCoherentSessions(t *testing.T) { testEntry{env: Entry{SessionID: testSessionID, ID: hexID(5), OperationID: testOpID, Kind: EntryHookResult, Sequence: 5, CommittedAt: testTime}, hookResult: &second}, ) view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid hook-result shape rejected: %v", err) - } - if len(view.Entries) != 4 || view.Entries[2].HookResult == nil || view.Entries[3].HookResult == nil { - t.Fatalf("typed view lost the hook results: %+v", view.Entries) - } + testassert.NoError(t, err) + testassert.Require(t, len(view.Entries) == 4 && view.Entries[2].HookResult != nil && view.Entries[3].HookResult != nil, "typed view lost the hook results: %+v", view.Entries) }) t.Run("running hook effect", func(t *testing.T) { fixture := singleCallGraph(hexID(3)) fixture.ops[0].State.ActiveEffect = &ActiveEffect{Kind: EffectHook, ResultEntryID: hexID(9), ToolCallID: "call-1", HookID: "cap.hook.one"} view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid hook-effect shape rejected: %v", err) - } - if view.Operations[0].State.ActiveEffect == nil || view.Operations[0].State.ActiveEffect.Kind != EffectHook { - t.Fatalf("typed view lost the active hook effect") - } + testassert.NoError(t, err) + testassert.Require(t, view.Operations[0].State.ActiveEffect != nil && view.Operations[0].State.ActiveEffect.Kind == EffectHook, "typed view lost the active hook effect") }) t.Run("distinct hook identities containing separators", func(t *testing.T) { @@ -633,8 +595,9 @@ func TestGraphValidatorAcceptsCoherentSessions(t *testing.T) { testEntry{env: Entry{SessionID: testSessionID, ID: hexID(4), OperationID: testOpID, Kind: EntryToolResult, Sequence: 4, CommittedAt: testTime}, toolResult: &firstResult}, testEntry{env: Entry{SessionID: testSessionID, ID: hexID(6), OperationID: testOpID, Kind: EntryHookResult, Sequence: 6, CommittedAt: testTime}, hookResult: &two}, ) - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("distinct hook identities whose joined forms collide rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) } @@ -661,9 +624,7 @@ func TestGraphValidatorSessionUsageOverflowAcrossOperations(t *testing.T) { _, err := validateFixture(t, fixture.storage(t), testSessionID) if tc.overflow { corrupt := wantCorruption(t, err) - if !strings.Contains(corrupt.Error(), "session usage overflows:") { - t.Fatalf("overflow = %v, want the Session accumulator's rejection", corrupt) - } + testassert.Require(t, strings.Contains(corrupt.Error(), "session usage overflows:"), "overflow = %v, want the Session accumulator's rejection", corrupt) } else if err != nil { t.Fatalf("representable total rejected: %v", err) } @@ -693,8 +654,9 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { t.Run("valid sibling stays usable", func(t *testing.T) { store := sibling.storage(t) - if _, err := validateFixture(t, store, siblingID); err != nil { - t.Fatalf("valid sibling rejected: %v", err) + { + _, err := validateFixture(t, store, siblingID) + testassert.NoError(t, err) } }) @@ -704,21 +666,21 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { }{ {"corrupt entry payload", func(t *testing.T, fixture *testGraph) { raw, err := encodeInputEntry(*fixture.entries[0].input) - mustEncode(t, err) + testassert.NoError(t, err) fixture.entries[0].rawOverride = setKey(raw, "bogus", json.RawMessage(`1`)) }}, {"corrupt session register payload", func(t *testing.T, fixture *testGraph) { raw, err := encodeSessionRegister(fixture.session) - mustEncode(t, err) + testassert.NoError(t, err) state, err := decodePayloadObject(mustState(t, raw)) - mustEncode(t, err) + testassert.NoError(t, err) state["lifecycle"] = json.RawMessage(`"stopping"`) patchedState, err := json.Marshal(state) - mustEncode(t, err) + testassert.NoError(t, err) obj := wireObject(t, raw) obj["state"] = patchedState patched, err := json.Marshal(obj) - mustEncode(t, err) + testassert.NoError(t, err) fixture.sessionRawOverride = patched }}, {"two running operations", func(t *testing.T, fixture *testGraph) { @@ -1159,7 +1121,7 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { copied := validAssistantEntry("") copied.EntryID = hexID(1) raw, err := encodeAssistantEntry(copied) - mustEncode(t, err) + testassert.NoError(t, err) withUsage := setKey(raw, "usage", json.RawMessage(`{"input_tokens":2,"cached_input_tokens":0,"output_tokens":0}`)) fixture.entries = []testEntry{ {env: Entry{SessionID: testSessionID, ID: hexID(1), Kind: EntryAssistant, Sequence: 1, CommittedAt: testTime}, rawOverride: withUsage}, @@ -1435,11 +1397,10 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { _, err := validateFixture(t, store, testSessionID) return err }()) - if ownership[tc.name] && corrupt.Detail != ownershipDetail { - t.Fatalf("ownership corruption detail = %q, want the one generic %q", corrupt.Detail, ownershipDetail) - } - if want, ok := usageOverflowDetail[tc.name]; ok && corrupt.Detail != want { - t.Fatalf("overflow corruption detail = %q, want %q", corrupt.Detail, want) + testassert.Require(t, !ownership[tc.name] || corrupt.Detail == ownershipDetail, "ownership corruption detail = %q, want the one generic %q", corrupt.Detail, ownershipDetail) + { + want, ok := usageOverflowDetail[tc.name] + testassert.Require(t, !ok || corrupt.Detail == want, "overflow corruption detail = %q, want %q", corrupt.Detail, want) } }) } @@ -1447,7 +1408,7 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { t.Run("one corrupt record among valid siblings", func(t *testing.T) { broken := validTestGraph() raw, err := encodeInputEntry(*broken.entries[0].input) - mustEncode(t, err) + testassert.NoError(t, err) broken.entries[0].rawOverride = setKey(raw, "bogus", json.RawMessage(`1`)) store := broken.storage(t) for id, regs := range sibling.storage(t).registers { @@ -1460,8 +1421,9 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { _, err := validateFixture(t, store, testSessionID) return err }()) - if _, err := validateFixture(t, store, siblingID); err != nil { - t.Fatalf("valid sibling session not usable alongside corruption: %v", err) + { + _, err := validateFixture(t, store, siblingID) + testassert.NoError(t, err) } }) @@ -1478,13 +1440,9 @@ func TestGraphValidatorSurfacesCorruption(t *testing.T) { t.Run("fully absent session passes not-found through", func(t *testing.T) { store := &graphStorage{registers: map[string][]Register{}, entries: map[string][]Entry{}} _, err := validateFixture(t, store, testSessionID) - if err == nil || !errors.Is(err, ErrNotFound) { - t.Fatalf("absent session = %v, want the ErrNotFound class", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrNotFound), "absent session = %v, want the ErrNotFound class", err) var corrupt *CorruptionError - if errors.As(err, &corrupt) { - t.Fatalf("absent session misclassified as corruption: %v", err) - } + testassert.Require(t, !errors.As(err, &corrupt), "absent session misclassified as corruption: %v", err) }) } @@ -1499,34 +1457,26 @@ func TestGraphValidatorPassesStorageFailuresThrough(t *testing.T) { store := fixture.storage(t) store.registersErr = failure _, err := validateFixture(t, store, testSessionID) - if !errors.Is(err, failure) || err != failure { - t.Fatalf("registers failure = %v, want the exact error unmodified", err) - } + testassert.Require(t, errors.Is(err, failure) && err == failure, "registers failure = %v, want the exact error unmodified", err) wantNotCorruption(t, err) }) t.Run("read entries failure", func(t *testing.T) { store := fixture.storage(t) store.entriesErr = failure _, err := validateFixture(t, store, testSessionID) - if err != failure { - t.Fatalf("entries failure = %v, want the exact error unmodified", err) - } + testassert.Equal(t, err, failure) wantNotCorruption(t, err) }) t.Run("addressed-but-absent record", func(t *testing.T) { store := fixture.storage(t) store.registersErr = fmt.Errorf("session row: %w", ErrNotFound) _, err := validateFixture(t, store, testSessionID) - if !errors.Is(err, ErrNotFound) { - t.Fatalf("absent record = %v, want the landed not-found class unchanged", err) - } + testassert.Require(t, errors.Is(err, ErrNotFound), "absent record = %v, want the landed not-found class unchanged", err) wantNotCorruption(t, err) }) t.Run("malformed session identity is invalid input", func(t *testing.T) { _, err := validateFixture(t, fixture.storage(t), "not-an-id") - if !errors.Is(err, ErrInvalid) { - t.Fatalf("malformed session id = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "malformed session id = %v, want ErrInvalid", err) wantNotCorruption(t, err) }) } @@ -1534,9 +1484,7 @@ func TestGraphValidatorPassesStorageFailuresThrough(t *testing.T) { func wantNotCorruption(t *testing.T, err error) { t.Helper() var corrupt *CorruptionError - if errors.As(err, &corrupt) { - t.Fatalf("storage failure reclassified as corruption: %v", err) - } + testassert.Require(t, !errors.As(err, &corrupt), "storage failure reclassified as corruption: %v", err) } // TestGraphValidatorForkReferenceRules proves the rewritten-reference rules @@ -1579,8 +1527,9 @@ func TestGraphValidatorForkReferenceRules(t *testing.T) { env: Entry{SessionID: testSessionID, ID: hexID(4), Kind: EntryToolResult, Sequence: 4, CommittedAt: testTime}, toolResult: &copiedResult, }) - if _, err := validateFixture(t, fork.storage(t), testSessionID); err != nil { - t.Fatalf("valid copied prefix rejected: %v", err) + { + _, err := validateFixture(t, fork.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1622,8 +1571,9 @@ func TestGraphValidatorForkReferenceRules(t *testing.T) { env: Entry{SessionID: testSessionID, ID: hexID(3), OperationID: testOpID, Kind: EntrySignal, Sequence: 3, CommittedAt: testTime}, signal: &owned, }) - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("owned signal with resolvable reference rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1656,8 +1606,9 @@ func TestGraphValidatorBackgroundCompletionOwnership(t *testing.T) { t.Run("root with operationless completion validates", func(t *testing.T) { fixture := &testGraph{session: validSessionRecord()} fixture.entries = []testEntry{completion(hexID(9), 1)} - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("root with operationless completion rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1665,8 +1616,9 @@ func TestGraphValidatorBackgroundCompletionOwnership(t *testing.T) { fixture := &testGraph{session: validSessionRecord()} fixture.session.Identity.ParentSessionID = otherSession() fixture.entries = []testEntry{completion(hexID(9), 1)} - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("child with operationless completion rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1675,8 +1627,9 @@ func TestGraphValidatorBackgroundCompletionOwnership(t *testing.T) { fixture.session.Identity.SourceSessionID = otherSession() fixture.session.Identity.SourceBoundaryEntryID = otherEntry() fixture.entries = append(fixture.entries, completion(hexID(9), 3)) - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("fork completion after an owned entry rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1690,9 +1643,7 @@ func TestGraphValidatorBackgroundCompletionOwnership(t *testing.T) { } _, err := validateFixture(t, fixture.storage(t), testSessionID) corrupt := wantCorruption(t, err) - if !strings.Contains(corrupt.Detail, "root Session carries independently copied entry") { - t.Fatalf("corruption detail = %q, want the copied-entry rule", corrupt.Detail) - } + testassert.Require(t, strings.Contains(corrupt.Detail, "root Session carries independently copied entry"), "corruption detail = %q, want the copied-entry rule", corrupt.Detail) }) } @@ -1703,18 +1654,10 @@ func TestGraphViewCarriesEnvelopeMetadata(t *testing.T) { fixture.session.Revision = 42 fixture.ops[0].Revision = 7 view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid graph rejected: %v", err) - } - if view.Session.Revision != 42 { - t.Fatalf("view session revision = %d", view.Session.Revision) - } - if view.Operations[0].Revision != 7 { - t.Fatalf("view operation revision = %d", view.Operations[0].Revision) - } - if !view.Entries[0].Envelope.CommittedAt.Equal(testTime) || view.Entries[0].Envelope.Sequence != 1 { - t.Fatalf("view entry envelope = %+v", view.Entries[0].Envelope) - } + testassert.NoError(t, err) + testassert.Equal(t, view.Session.Revision, 42) + testassert.Equal(t, view.Operations[0].Revision, 7) + testassert.Require(t, view.Entries[0].Envelope.CommittedAt.Equal(testTime) && view.Entries[0].Envelope.Sequence == 1, "view entry envelope = %+v", view.Entries[0].Envelope) } // Compile-time proof the fixture storage implements the landed read side. @@ -1741,12 +1684,8 @@ func TestGraphValidatorCompactionRules(t *testing.T) { fixture.entries = append(fixture.entries, compactionAt(hexID(3), hexID(2), testOpID, 3)) fixture.session.State.CompactionEntryID = hexID(3) view, err := validateFixture(t, fixture.storage(t), testSessionID) - if err != nil { - t.Fatalf("valid compaction shape rejected: %v", err) - } - if len(view.Entries) != 3 || view.Entries[2].Compaction == nil { - t.Fatalf("decoded view does not carry the compaction payload") - } + testassert.NoError(t, err) + testassert.Require(t, len(view.Entries) == 3 && view.Entries[2].Compaction != nil, "decoded view does not carry the compaction payload") if view.Entries[2].Compaction.BoundaryEntryID != hexID(2) { t.Fatalf("decoded boundary %q, want %q", view.Entries[2].Compaction.BoundaryEntryID, hexID(2)) } @@ -1757,9 +1696,7 @@ func TestGraphValidatorCompactionRules(t *testing.T) { fixture.session.State.CompactionEntryID = otherEntry() _, err := validateFixture(t, fixture.storage(t), testSessionID) corrupt := wantCorruption(t, err) - if !strings.Contains(corrupt.Detail, "compaction") { - t.Fatalf("corruption detail = %q, want the compaction rule", corrupt.Detail) - } + testassert.Require(t, strings.Contains(corrupt.Detail, "compaction"), "corruption detail = %q, want the compaction rule", corrupt.Detail) }) t.Run("projection naming a non-compaction entry corrupts", func(t *testing.T) { @@ -1802,8 +1739,9 @@ func TestGraphValidatorCompactionRules(t *testing.T) { compactionAt(hexID(4), hexID(3), testOpID, 4), ) fixture.session.State.CompactionEntryID = hexID(4) - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("two same-operation compaction entries rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1840,8 +1778,9 @@ func TestGraphValidatorCompactionRules(t *testing.T) { {env: Entry{SessionID: testSessionID, ID: hexID(5), OperationID: "op-2", Kind: EntryInput, Sequence: 5, CommittedAt: testTime}, input: &secondInput}, compactionAt(hexID(6), hexID(5), "op-2", 6), } - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("two different-operation compaction entries rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1856,8 +1795,9 @@ func TestGraphValidatorCompactionRules(t *testing.T) { {env: Entry{SessionID: testSessionID, ID: hexID(2), Kind: EntryAssistant, Sequence: 2, CommittedAt: testTime}, assistant: &copiedAssistant}, compactionAt(hexID(3), hexID(2), "", 3), } - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("copied compaction prefix rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } }) @@ -1866,9 +1806,7 @@ func TestGraphValidatorCompactionRules(t *testing.T) { fixture.entries = []testEntry{compactionAt(hexID(3), hexID(2), "", 1)} _, err := validateFixture(t, fixture.storage(t), testSessionID) corrupt := wantCorruption(t, err) - if !strings.Contains(corrupt.Detail, "root Session carries independently copied entry") { - t.Fatalf("corruption detail = %q, want the copied-entry rule", corrupt.Detail) - } + testassert.Require(t, strings.Contains(corrupt.Detail, "root Session carries independently copied entry"), "corruption detail = %q, want the copied-entry rule", corrupt.Detail) }) } @@ -1897,9 +1835,7 @@ func TestGraphValidatorCompactionUsage(t *testing.T) { UsageTotals{ByModel: []ModelUsage{{Model: testModelRef(), Usage: assistantUsage}}}, UsageTotals{ByModel: []ModelUsage{{Model: otherRef, Usage: compactionUsage}}}, ) - if err != nil { - t.Fatalf("merge: %v", err) - } + testassert.NoError(t, err) fixture.ops[0].State.Usage = merged fixture.session.State.Usage = merged }, @@ -1929,9 +1865,7 @@ func TestGraphValidatorCompactionUsage(t *testing.T) { wantCorruption(t, err) return } - if err != nil { - t.Fatalf("valid compaction usage rejected: %v", err) - } + testassert.NoError(t, err) }) } } @@ -1989,9 +1923,7 @@ func TestForkCopyCompactionEntries(t *testing.T) { Content: []model.ContentPart{{Kind: model.PartText, Text: "continue"}}, }, nil, testCapture(), &commit) }) - if err != nil { - t.Fatalf("forkTransaction: %v", err) - } + testassert.NoError(t, err) return &commit } @@ -2011,82 +1943,46 @@ func TestForkCopyCompactionEntries(t *testing.T) { copiedCompactionIDs = append(copiedCompactionIDs, entry.Envelope.ID) } } - if len(copiedCompactions) != 2 { - t.Fatalf("copied %d compaction entries, want 2", len(copiedCompactions)) - } - if copiedCompactionIDs[0] == hexID(3) || copiedCompactionIDs[1] == hexID(4) { - t.Fatalf("copied compaction identities %v are not both fresh", copiedCompactionIDs) - } + testassert.Equal(t, len(copiedCompactions), 2) + testassert.Require(t, copiedCompactionIDs[0] != hexID(3) && copiedCompactionIDs[1] != hexID(4), "copied compaction identities %v are not both fresh", copiedCompactionIDs) for i, v := range copiedCompactions { - if v.SessionID != destID || v.EntryID != copiedCompactionIDs[i] { - t.Fatalf("copied compaction identity %s/%s, want destination identities", v.SessionID, v.EntryID) - } - if v.OperationID != "" { - t.Fatalf("copied compaction carries operation ownership %q", v.OperationID) - } + testassert.Require(t, v.SessionID == destID && v.EntryID == copiedCompactionIDs[i], "copied compaction identity %s/%s, want destination identities", v.SessionID, v.EntryID) + testassert.Equal(t, v.OperationID, "") if v.Usage != nil { t.Fatalf("copied compaction carries source usage %+v", *v.Usage) } - if v.Model != testModelRef() { - t.Fatalf("copied model %+v did not survive", v.Model) - } - if v.ConfigurationRevision != "rev-1" { - t.Fatalf("copied configuration revision %q did not survive", v.ConfigurationRevision) - } - } - if copiedCompactions[0].Summary != "Summary of the earlier conversation." || copiedCompactions[1].Summary != "Second summary." { - t.Fatalf("copied summaries %q and %q did not survive", copiedCompactions[0].Summary, copiedCompactions[1].Summary) - } - if copiedCompactions[0].BoundaryEntryID != copiedAssistantID { - t.Fatalf("first copied boundary %q, want the copied assistant %q", copiedCompactions[0].BoundaryEntryID, copiedAssistantID) - } - if copiedCompactions[1].BoundaryEntryID != copiedCompactionIDs[0] { - t.Fatalf("second copied boundary %q, want the first copied compaction entry %q", copiedCompactions[1].BoundaryEntryID, copiedCompactionIDs[0]) - } - if commit.session.State.CompactionEntryID != copiedCompactionIDs[1] { - t.Fatalf("destination CompactionEntryID = %q, want the last copied compaction entry %q", commit.session.State.CompactionEntryID, copiedCompactionIDs[1]) + testassert.Equal(t, v.Model, testModelRef()) + testassert.Equal(t, v.ConfigurationRevision, "rev-1") } + testassert.Require(t, copiedCompactions[0].Summary == "Summary of the earlier conversation." && copiedCompactions[1].Summary == "Second summary.", "copied summaries %q and %q did not survive", copiedCompactions[0].Summary, copiedCompactions[1].Summary) + testassert.Equal(t, copiedCompactions[0].BoundaryEntryID, copiedAssistantID) + testassert.Equal(t, copiedCompactions[1].BoundaryEntryID, copiedCompactionIDs[0]) + testassert.Equal(t, commit.session.State.CompactionEntryID, copiedCompactionIDs[1]) }) t.Run("prefix without compaction entries yields an empty field", func(t *testing.T) { commit := runFork(t, buildSource(t, false)) for _, entry := range commit.entries { - if entry.Compaction != nil { - t.Fatalf("prefix without compaction entries copied one") - } - } - if commit.session.State.CompactionEntryID != "" { - t.Fatalf("destination CompactionEntryID = %q, want empty", commit.session.State.CompactionEntryID) + testassert.Require(t, entry.Compaction == nil, "prefix without compaction entries copied one") } + testassert.Equal(t, commit.session.State.CompactionEntryID, "") }) t.Run("source session is unchanged", func(t *testing.T) { fixture := buildSource(t, true) store := fixture.storage(t) before, err := store.ReadEntries(context.Background(), testSessionID, 0) - if err != nil { - t.Fatalf("read source entries: %v", err) - } + testassert.NoError(t, err) runFork(t, fixture) after, err := store.ReadEntries(context.Background(), testSessionID, 0) - if err != nil { - t.Fatalf("read source entries: %v", err) - } - if len(after) != len(before) { - t.Fatalf("source entry count changed: %d, want %d", len(after), len(before)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(after), len(before)) for i := range before { - if after[i].ID != before[i].ID || string(after[i].Payload) != string(before[i].Payload) { - t.Fatalf("source entry %d changed", i) - } + testassert.Require(t, after[i].ID == before[i].ID && string(after[i].Payload) == string(before[i].Payload), "source entry %d changed", i) } reg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: testSessionID, Kind: RegisterSession}) - if err != nil { - t.Fatalf("read source register: %v", err) - } - if reg.Revision != fixture.session.Revision { - t.Fatalf("source register revision = %d, want %d", reg.Revision, fixture.session.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, reg.Revision, fixture.session.Revision) }) } @@ -2154,7 +2050,8 @@ func TestGraphValidatorGroupsEntriesByOperation(t *testing.T) { {env: Entry{SessionID: testSessionID, ID: hexID(7), OperationID: "op-2", Kind: EntryToolResult, Sequence: 7, CommittedAt: testTime}, toolResult: &resultB}, {env: Entry{SessionID: testSessionID, ID: hexID(8), OperationID: "op-2", Kind: EntryOperationSettlement, Sequence: 8, CommittedAt: testTime}, settlement: &settlementB}, } - if _, err := validateFixture(t, fixture.storage(t), testSessionID); err != nil { - t.Fatalf("interleaved multi-operation history rejected: %v", err) + { + _, err := validateFixture(t, fixture.storage(t), testSessionID) + testassert.NoError(t, err) } } diff --git a/harness/graph_validator_bench_test.go b/harness/graph_validator_bench_test.go index cd717a96..921e1c84 100644 --- a/harness/graph_validator_bench_test.go +++ b/harness/graph_validator_bench_test.go @@ -80,16 +80,17 @@ func benchValidatorFixture(n int) *testGraph { return fixture } -// BenchmarkGraphValidatorOperationShapes measures the invariant traversal at -// 250, 1,000 and 4,000 Operations. It is scaling evidence only: no threshold -// and no CI gate. Each size is decoded once through the real storage path, and -// the loop measures validateGraphInvariants, the pass whose per-Operation -// scans read each Operation's entry group instead of rescanning the whole -// history. -func BenchmarkGraphValidatorOperationShapes(b *testing.B) { +// benchmarkGraphValidator runs the invariant traversal at 250, 1,000 and +// 4,000 Operations over the given fixture builder. Each size is built and +// decoded once through the real storage path before timing, and the timed loop +// calls validateGraphInvariants directly, the pass whose per-Operation scans +// read each Operation's entry group instead of rescanning the whole history. +// These are scaling evidence only: no threshold and no CI gate. +func benchmarkGraphValidator(b *testing.B, fixtureBuilder func(int) *testGraph) { + b.Helper() for _, n := range []int{250, 1000, 4000} { b.Run(fmt.Sprintf("%d_operations", n), func(b *testing.B) { - fixture := benchValidatorFixture(n) + fixture := fixtureBuilder(n) graph, err := validateSessionGraph(context.Background(), fixture.storage(b), testSessionID) if err != nil { b.Fatalf("benchmark fixture rejected: %v", err) @@ -105,6 +106,12 @@ func BenchmarkGraphValidatorOperationShapes(b *testing.B) { } } +// BenchmarkGraphValidatorOperationShapes measures the invariant traversal at +// the runner's sizes over interleaved Operation shapes. +func BenchmarkGraphValidatorOperationShapes(b *testing.B) { + benchmarkGraphValidator(b, benchValidatorFixture) +} + // benchValidatorUsageFixture extends benchValidatorFixture so every terminal // Operation's settlement carries usage under its own model identity: K = n // distinct model identities, one per Operation, and a Session total with one @@ -127,26 +134,9 @@ func benchValidatorUsageFixture(n int) *testGraph { return fixture } -// BenchmarkGraphValidatorUsageShapes measures the same invariant traversal at -// 250, 1,000 and 4,000 Operations with per-Operation settlement usage under a -// distinct model identity per Operation, so the Session-total accumulation -// walks n distinct models. Scaling evidence only: no threshold and no CI -// gate. +// BenchmarkGraphValidatorUsageShapes measures the same invariant traversal +// with per-Operation settlement usage under a distinct model identity per +// Operation, so the Session-total accumulation walks n distinct models. func BenchmarkGraphValidatorUsageShapes(b *testing.B) { - for _, n := range []int{250, 1000, 4000} { - b.Run(fmt.Sprintf("%d_operations", n), func(b *testing.B) { - fixture := benchValidatorUsageFixture(n) - graph, err := validateSessionGraph(context.Background(), fixture.storage(b), testSessionID) - if err != nil { - b.Fatalf("benchmark fixture rejected: %v", err) - } - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := validateGraphInvariants(testSessionID, graph); err != nil { - b.Fatalf("benchmark graph rejected: %v", err) - } - } - }) - } + benchmarkGraphValidator(b, benchValidatorUsageFixture) } diff --git a/harness/hooks_public_test.go b/harness/hooks_public_test.go index 203812ad..158f2924 100644 --- a/harness/hooks_public_test.go +++ b/harness/hooks_public_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -83,44 +84,39 @@ func TestPublicToolArgumentHookTurn(t *testing.T) { }, nil } session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the published call's turn <-script.arrived // the completing turn // A queued submit proves op-1's terminal committed: the drain admits // it only after the terminal settlement. - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } <-f.prepare // the drain admitted op-2: op-1's terminal success committed <-script.arrived - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } mu.Lock() seen := append([]string{}, hookSeen...) toolInput := append([]string{}, prepared...) mu.Unlock() - if len(seen) != 2 || seen[0] != `{"x":1}` || seen[1] != `{"x":"\u003c"}` { - t.Fatalf("hook chain received %q, want the producer's committed normalization then the first hook's committed replacement", seen) - } - if len(toolInput) != 1 || toolInput[0] != `{"y":"\u0026\u003e"}` { - t.Fatalf("concrete tool received %q, want the final committed replacement bytes", toolInput) - } + testassert.Require(t, len(seen) == 2 && seen[0] == `{"x":1}` && seen[1] == `{"x":"\u003c"}`, "hook chain received %q, want the producer's committed normalization then the first hook's committed replacement", seen) + testassert.Require(t, len(toolInput) == 1 && toolInput[0] == `{"y":"\u0026\u003e"}`, "concrete tool received %q, want the final committed replacement bytes", toolInput) rec, err := f.h.ReadOperation(ctx, session, "op-1") - if err != nil || rec.State.Status != harness.OperationSuccess || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("hook turn = %+v err %v, want terminal success with every effect settled", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 0, "hook turn = %+v err %v, want terminal success with every effect settled", rec, err) // The committed evidence: one success hook_result per hook, in // configured order, carrying the durable replacement bytes. entries, err := store.ReadEntries(ctx, session, 0) - if err != nil { - t.Fatalf("read entries: %v", err) - } + testassert.NoError(t, err) type hookWire struct { HookID string `json:"hook_id"` ToolCallID string `json:"tool_call_id"` @@ -134,44 +130,37 @@ func TestPublicToolArgumentHookTurn(t *testing.T) { continue } var wire hookWire - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode hook result: %v", err) + { + err := json.Unmarshal(entry.Payload, &wire) + testassert.NoError(t, err) } hookResults = append(hookResults, wire) } - if len(hookResults) != 2 || - hookResults[0].HookID != "cap.hook.one" || hookResults[0].Status != "success" || string(hookResults[0].Arguments) != `{"x":"\u003c"}` || - hookResults[1].HookID != "cap.hook.two" || hookResults[1].Status != "success" || string(hookResults[1].Arguments) != `{"y":"\u0026\u003e"}` { - t.Fatalf("committed hook results = %+v, want one success per hook with the durable bytes", hookResults) - } + testassert.Require(t, len(hookResults) == 2 && hookResults[0].HookID == "cap.hook.one" && hookResults[0].Status == "success" && string(hookResults[0].Arguments) == `{"x":"\u003c"}` && hookResults[1].HookID == "cap.hook.two" && hookResults[1].Status == "success" && string(hookResults[1].Arguments) == `{"y":"\u0026\u003e"}`, "committed hook results = %+v, want one success per hook with the durable bytes", hookResults) // The evidence never projects as conversation: the completing turn's // projection carries the call result, not the hook replacements. projection := texts(script.seen()[1]) for _, text := range projection { - if text == `{"y":"\u0026\u003e"}` { - t.Fatalf("hook evidence projected as conversation content") - } + testassert.Require(t, text != `{"y":"\u0026\u003e"}`, "hook evidence projected as conversation content") } // A restart revalidates the Session and Operation without any plugin: // the closed decoder interprets the hook evidence itself. - if err := harness.Recover(ctx, store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := harness.Recover(ctx, store) + testassert.NoError(t, err) } restarted, err := harness.New(ctx, harness.Dependencies{Storage: store, Prepare: func(context.Context, harness.PreparationRequest) (harness.PreparedExecution, error) { return harness.PreparedExecution{}, errors.New("no preparation after restart") }}) - if err != nil { - t.Fatalf("restarted Harness: %v", err) - } - if _, err := restarted.ReadSessionHeader(ctx, session); err != nil { - t.Fatalf("restarted ReadSession: %v", err) + testassert.NoError(t, err) + { + _, err := restarted.ReadSessionHeader(ctx, session) + testassert.NoError(t, err) } rec, err = restarted.ReadOperation(ctx, session, "op-1") - if err != nil || rec.State.Status != harness.OperationSuccess { - t.Fatalf("restarted ReadOperation = %+v err %v, want the revalidated settled Operation", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess, "restarted ReadOperation = %+v err %v, want the revalidated settled Operation", rec, err) // A Fork copies no hook effect: the fork boundary is op-2's input, so // the copied prefix actually contains op-1's committed hook evidence — @@ -182,13 +171,12 @@ func TestPublicToolArgumentHookTurn(t *testing.T) { f2 := newPublicFixture(t, store, forkScript, nil) defer f2.close() res, err := f2.h.Fork(ctx, harness.ForkRequest{SourceSessionID: session, BoundaryEntryID: boundary.ID, OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}}) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) <-forkScript.arrived forkScript.releaseGate() - if err := converge(t, f2); err != nil { - t.Fatalf("fork Wait: %v", err) + { + err := converge(t, f2) + testassert.NoError(t, err) } assertForkSourceUnchanged(t, store, session, before) var sourceHooks int @@ -197,17 +185,11 @@ func TestPublicToolArgumentHookTurn(t *testing.T) { sourceHooks++ } } - if sourceHooks != 2 { - t.Fatalf("source prefix hook evidence = %d entries, want the two committed hook results before the boundary", sourceHooks) - } + testassert.Equal(t, sourceHooks, 2) copied, err := store.ReadEntries(ctx, res.Session.Identity.SessionID, 0) - if err != nil { - t.Fatalf("read fork entries: %v", err) - } + testassert.NoError(t, err) for _, entry := range copied { - if entry.Kind == harness.EntryHookResult { - t.Fatalf("fork copied a hook effect: entry %s", entry.ID) - } + testassert.Require(t, entry.Kind != harness.EntryHookResult, "fork copied a hook effect: entry %s", entry.ID) } }) } diff --git a/harness/hooks_test.go b/harness/hooks_test.go index 10b65809..92dfa150 100644 --- a/harness/hooks_test.go +++ b/harness/hooks_test.go @@ -9,6 +9,7 @@ import ( "sync" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -59,11 +60,10 @@ func newHookHarness(t *testing.T, exec Execution) (*Harness, *graphStorage, *coo return prepared, nil }) session, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if _, disposition := mustAdmitWithoutExecution(t, h, session.Identity.SessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + testassert.NoError(t, err) + { + _, disposition := mustAdmitWithoutExecution(t, h, session.Identity.SessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := leaseCoordinator(t, h, session.Identity.SessionID) return h, store, c, session.Identity.SessionID @@ -77,8 +77,9 @@ func publishHookCalls(t *testing.T, h *Harness, c *coordinator, exec Execution, exec.Model = func(context.Context, model.Request) (model.Stream, error) { return completedTurnStream(calls...), nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } } @@ -87,9 +88,7 @@ func publishHookCalls(t *testing.T, h *Harness, c *coordinator, exec Execution, func hookGraphHookResults(t *testing.T, store *graphStorage, sessionID string) []hookResultEntry { t.Helper() graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) var out []hookResultEntry for _, entry := range graph.Entries { if entry.HookResult != nil { @@ -144,77 +143,50 @@ func TestToolArgumentHookChainSettlement(t *testing.T) { te := h.toolEffect(c, testOpID, exec, testCapture()) got, err := te(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultSuccess, Content: "done"}) { - t.Fatalf("tool result = %+v, want the prepared success", got) - } + testassert.NoError(t, err) + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultSuccess, Content: "done"})) // Callback counts: one normalization for the valid original at the // producer plus exactly one per hook replacement. mu.Lock() count, chain := normalizations, append([]string{}, received...) mu.Unlock() - if count != 3 { - t.Fatalf("normalization callback ran %d times, want one per producer replacement (3)", count) - } - if len(chain) != 2 || chain[0] != ` {"x": 1} ` || chain[1] != `{"x":"\u003c"}` { - t.Fatalf("hook chain received %q, want the raw arguments then the committed replacement bytes", chain) - } + testassert.Equal(t, count, 3) + testassert.Require(t, len(chain) == 2 && chain[0] == ` {"x": 1} ` && chain[1] == `{"x":"\u003c"}`, "hook chain received %q, want the raw arguments then the committed replacement bytes", chain) prepared := spy.received() - if len(prepared) != 1 || prepared[0].ID != "call-1" || prepared[0].Name != "echo" || string(prepared[0].Arguments) != `{"y":"\u0026\u003e"}` { - t.Fatalf("preparer received %+v, want the final committed replacement bytes under the bound identity", prepared) - } + testassert.Require(t, len(prepared) == 1 && prepared[0].ID == "call-1" && prepared[0].Name == "echo" && string(prepared[0].Arguments) == `{"y":"\u0026\u003e"}`, "preparer received %+v, want the final committed replacement bytes under the bound identity", prepared) // The committed evidence: one success hook_result per hook, whose stored // arguments are exactly the bytes the continuation consumed. committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 2 { - t.Fatalf("%d hook_result entries, want one per hook", len(committed)) - } - if committed[0].HookID != "cap.hook.one" || committed[0].Status != hookSucceeded || string(committed[0].Arguments) != `{"x":"\u003c"}` || committed[0].Error != "" { - t.Fatalf("first hook result = %+v, want the committed compacted replacement", committed[0]) - } - if committed[1].HookID != "cap.hook.two" || committed[1].Status != hookSucceeded || string(committed[1].Arguments) != `{"y":"\u0026\u003e"}` { - t.Fatalf("second hook result = %+v, want the committed escaped replacement", committed[1]) - } + testassert.Equal(t, len(committed), 2) + testassert.Require(t, committed[0].HookID == "cap.hook.one" && committed[0].Status == hookSucceeded && string(committed[0].Arguments) == `{"x":"\u003c"}` && committed[0].Error == "", "first hook result = %+v, want the committed compacted replacement", committed[0]) + testassert.Require(t, committed[1].HookID == "cap.hook.two" && committed[1].Status == hookSucceeded && string(committed[1].Arguments) == `{"y":"\u0026\u003e"}`, "second hook result = %+v, want the committed escaped replacement", committed[1]) // The assistant entry never mutated: original raw bytes retained, the // producer's own committed normalization untouched. graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after the chain: %v", err) - } + testassert.NoError(t, err) for _, entry := range graph.Entries { if entry.Assistant == nil { continue } record := entry.Assistant.ToolCalls[0] - if raw, derr := base64.StdEncoding.DecodeString(record.ArgumentsBase64); derr != nil || string(raw) != ` {"x": 1} ` { - t.Fatalf("assistant raw arguments = %q err %v, want the original bytes retained", raw, derr) - } - if string(record.NormalizedArguments) != `{"x":1}` { - t.Fatalf("assistant normalized_arguments = %q, want the producer's committed value unchanged", record.NormalizedArguments) + { + raw, derr := base64.StdEncoding.DecodeString(record.ArgumentsBase64) + testassert.Require(t, derr == nil && string(raw) == ` {"x": 1} `, "assistant raw arguments = %q err %v, want the original bytes retained", raw, derr) } + testassert.Require(t, string(record.NormalizedArguments) == `{"x":1}`, "assistant normalized_arguments = %q, want the producer's committed value unchanged", record.NormalizedArguments) } // Hook results are execution evidence, not conversation messages. rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("operation state = %+v, want a quiet running Operation with the call settled", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 0, "operation state = %+v, want a quiet running Operation with the call settled", rec.State) messages, err := h.contextSource(c, testOpID)(context.Background()) - if err != nil { - t.Fatalf("context source: %v", err) - } + testassert.NoError(t, err) for _, msg := range messages { for _, part := range msg.Content { - if part.Text == `{"y":"\u0026\u003e"}` { - t.Fatalf("hook evidence projected as conversation content") - } + testassert.Require(t, part.Text != `{"y":"\u0026\u003e"}`, "hook evidence projected as conversation content") } } } @@ -236,18 +208,13 @@ func TestToolArgumentHookInterruptionRules(t *testing.T) { }} exec.ToolHooks = []ToolArgumentsHook{hook} got, err := h.toolEffect(c, testOpID, exec, testCapture())(ctx, testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent}) { - t.Fatalf("tool result = %+v, want the interrupted-before-execution result", got) - } + testassert.NoError(t, err) + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent})) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].Status != hookInterrupted || committed[0].HookID != "cap.hook.one" || committed[0].ToolCallID != "call-1" || committed[0].Error == "" || len(committed[0].Arguments) != 0 { - t.Fatalf("hook results = %+v, want one interrupted result under the reserved identity", committed) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the interruption: %v", err) + testassert.Require(t, len(committed) == 1 && committed[0].Status == hookInterrupted && committed[0].HookID == "cap.hook.one" && committed[0].ToolCallID == "call-1" && committed[0].Error != "" && len(committed[0].Arguments) == 0, "hook results = %+v, want one interrupted result under the reserved identity", committed) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) @@ -274,21 +241,14 @@ func TestToolArgumentHookInterruptionRules(t *testing.T) { } baselineReplaces = replaces got, err := h.toolEffect(c, testOpID, exec, testCapture())(ctx, testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if ran != 0 { - t.Fatalf("hook ran %d times after the intent, want zero", ran) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent}) { - t.Fatalf("tool result = %+v, want the interrupted-before-execution result", got) - } + testassert.NoError(t, err) + testassert.Equal(t, ran, 0) + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent})) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].Status != hookInterrupted { - t.Fatalf("hook results = %+v, want one interrupted result under the intent's reserved identity", committed) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the interruption: %v", err) + testassert.Require(t, len(committed) == 1 && committed[0].Status == hookInterrupted, "hook results = %+v, want one interrupted result under the intent's reserved identity", committed) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) @@ -309,21 +269,14 @@ func TestToolArgumentHookInterruptionRules(t *testing.T) { }}, } got, err := h.toolEffect(c, testOpID, exec, testCapture())(ctx, testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent}) { - t.Fatalf("tool result = %+v, want the interrupted-before-execution result", got) - } - if secondRan != 0 { - t.Fatalf("second hook ran %d times, want later starts prevented", secondRan) - } + testassert.NoError(t, err) + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent})) + testassert.Equal(t, secondRan, 0) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].HookID != "cap.hook.one" || committed[0].Status != hookSucceeded || string(committed[0].Arguments) != `{"first":true}` { - t.Fatalf("hook results = %+v, want the earlier real hook result preserved", committed) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the interrupted chain: %v", err) + testassert.Require(t, len(committed) == 1 && committed[0].HookID == "cap.hook.one" && committed[0].Status == hookSucceeded && string(committed[0].Arguments) == `{"first":true}`, "hook results = %+v, want the earlier real hook result preserved", committed) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) @@ -335,16 +288,10 @@ func TestToolArgumentHookInterruptionRules(t *testing.T) { h, store, c, sessionID := newHookHarness(t, exec) publishHookCalls(t, h, c, exec, testToolCall("call-1")) got, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != model.ResultError || got.Content != "context canceled" { - t.Fatalf("tool result = %+v, want the validation error with the hook's diagnostic", got) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultError && got.Content == "context canceled", "tool result = %+v, want the validation error with the hook's diagnostic", got) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].Status != hookFailed || committed[0].Error != "context canceled" { - t.Fatalf("hook results = %+v, want one error result carrying the diagnostic", committed) - } + testassert.Require(t, len(committed) == 1 && committed[0].Status == hookFailed && committed[0].Error == "context canceled", "hook results = %+v, want one error result carrying the diagnostic", committed) }) t.Run("real hook result commits before the next boundary observes cancellation", func(t *testing.T) { @@ -362,20 +309,15 @@ func TestToolArgumentHookInterruptionRules(t *testing.T) { }} exec.ToolHooks = []ToolArgumentsHook{hook} got, err := h.toolEffect(c, testOpID, exec, testCapture())(ctx, testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } + testassert.NoError(t, err) // The successful replacement committed as a real result; the // interruption appears at the next boundary (the tool intent). - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent}) { - t.Fatalf("tool result = %+v, want the interrupted-before-execution result at the next boundary", got) - } + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultInterrupted, Content: interruptedToolResultContent})) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].Status != hookSucceeded || string(committed[0].Arguments) != `{"fixed":true}` { - t.Fatalf("hook results = %+v, want the committed real result", committed) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the boundary interruption: %v", err) + testassert.Require(t, len(committed) == 1 && committed[0].Status == hookSucceeded && string(committed[0].Arguments) == `{"fixed":true}`, "hook results = %+v, want the committed real result", committed) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) } @@ -463,36 +405,21 @@ func TestToolArgumentHookFailureRules(t *testing.T) { publishHookCalls(t, h, c, exec, testToolCall("call-1"), testToolCall("call-2")) got, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != tc.want { - t.Fatalf("tool result = %+v, want %+v", got, tc.want) - } - if later != 0 { - t.Fatalf("remaining hook ran %d times, want zero after the failure", later) - } + testassert.NoError(t, err) + testassert.Equal(t, got, tc.want) + testassert.Equal(t, later, 0) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].Status != tc.wantStatus || committed[0].HookID != "cap.hook.one" || len(committed[0].Arguments) != 0 || committed[0].Error == "" { - t.Fatalf("hook results = %+v, want one %s result with a non-empty diagnostic", committed, tc.wantStatus) - } - if tc.wantError != "" && committed[0].Error != tc.wantError { - t.Fatalf("hook result error = %q, want %q", committed[0].Error, tc.wantError) - } + testassert.Require(t, len(committed) == 1 && committed[0].Status == tc.wantStatus && committed[0].HookID == "cap.hook.one" && len(committed[0].Arguments) == 0 && committed[0].Error != "", "hook results = %+v, want one %s result with a non-empty diagnostic", committed, tc.wantStatus) + testassert.Require(t, tc.wantError == "" || committed[0].Error == tc.wantError, "hook result error = %q, want %q", committed[0].Error, tc.wantError) // Later calls still run: call-2 executes after the failed chain. got, err = h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-2")) - if err != nil { - t.Fatalf("later tool effect: %v", err) - } - if got.Status != model.ResultSuccess { - t.Fatalf("later call result = %+v, want success", got) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultSuccess, "later call result = %+v, want success", got) prepareCalls := spy.received() - if len(prepareCalls) != 1 || prepareCalls[0].ID != "call-2" { - t.Fatalf("preparer calls = %+v, want only the later call", prepareCalls) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the failure: %v", err) + testassert.Require(t, len(prepareCalls) == 1 && prepareCalls[0].ID == "call-2", "preparer calls = %+v, want only the later call", prepareCalls) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) } @@ -536,80 +463,59 @@ func TestToolArgumentHookFailedResultCommitLeavesRecovery(t *testing.T) { } return nil } - if _, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")); !errors.Is(err, ErrStorage) { - t.Fatalf("tool effect = %v, want the injected storage failure", err) + { + _, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) + testassert.Require(t, errors.Is(err, ErrStorage), "tool effect = %v, want the injected storage failure", err) } mu.Lock() ran, ranSecond := runs, second mu.Unlock() - if ran != 1 || ranSecond { - t.Fatalf("hooks ran %d/%v, want the first only: a failed result transaction starts no later effect", ran, ranSecond) - } + testassert.Require(t, ran == 1 && !ranSecond, "hooks ran %d/%v, want the first only: a failed result transaction starts no later effect", ran, ranSecond) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect == nil || rec.State.ActiveEffect.Kind != EffectHook || - rec.State.ActiveEffect.HookID != "cap.hook.one" || rec.State.ActiveEffect.ToolCallID != "call-1" { - t.Fatalf("operation state = %+v, want the committed running hook intent left for recovery", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect != nil && rec.State.ActiveEffect.Kind == EffectHook && rec.State.ActiveEffect.HookID == "cap.hook.one" && rec.State.ActiveEffect.ToolCallID == "call-1", "operation state = %+v, want the committed running hook intent left for recovery", rec.State) reserved := rec.State.ActiveEffect.ResultEntryID - if len(rec.State.PendingToolCalls) != 1 || rec.State.PendingToolCalls[0].CallID != "call-1" { - t.Fatalf("pending calls = %+v, want the first call still pending", rec.State.PendingToolCalls) - } + testassert.Require(t, len(rec.State.PendingToolCalls) == 1 && rec.State.PendingToolCalls[0].CallID == "call-1", "pending calls = %+v, want the first call still pending", rec.State.PendingToolCalls) // Recovery interrupts the reserved active hook effect through the common // terminal helper: the interrupted hook_result under the reserved // identity, the interrupted tool result, and the terminal Operation. - if err := Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := Recover(context.Background(), store) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after recovery: %v", err) - } + testassert.NoError(t, err) var hookResults []hookResultEntry for _, entry := range graph.Entries { if entry.HookResult != nil { hookResults = append(hookResults, *entry.HookResult) } } - if len(hookResults) != 1 || hookResults[0].EntryID != reserved || hookResults[0].HookID != "cap.hook.one" || - hookResults[0].ToolCallID != "call-1" || hookResults[0].Status != hookInterrupted || hookResults[0].Error != recoveryInterruptedDetail { - t.Fatalf("recovered hook results = %+v, want one interrupted result under the reserved identity %q", hookResults, reserved) - } + testassert.Require(t, len(hookResults) == 1 && hookResults[0].EntryID == reserved && hookResults[0].HookID == "cap.hook.one" && hookResults[0].ToolCallID == "call-1" && hookResults[0].Status == hookInterrupted && hookResults[0].Error == recoveryInterruptedDetail, "recovered hook results = %+v, want one interrupted result under the reserved identity %q", hookResults, reserved) rec, err = h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation after recovery: %v", err) - } + testassert.NoError(t, err) // The live harness holds its stale coordinator view; the durable truth is // the recovered store's own validated graph. rec = graph.Operations[0] - if rec.State.Status != OperationInterruption || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("recovered operation = %+v, want the terminal interruption", rec.State) - } + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 0, "recovered operation = %+v, want the terminal interruption", rec.State) // No hook or tool replays: the store's next recovery run writes nothing. before := snapshotHookStore(t, store, sessionID) - if err := Recover(context.Background(), store); err != nil { - t.Fatalf("second Recover: %v", err) + { + err := Recover(context.Background(), store) + testassert.NoError(t, err) } after := snapshotHookStore(t, store, sessionID) - if before != after { - t.Fatalf("second recovery changed the record count (%d -> %d): replay is forbidden", before, after) - } + testassert.Equal(t, before, after) } // snapshotHookStore counts one Session's committed entries and registers. func snapshotHookStore(t *testing.T, store *graphStorage, sessionID string) int { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("read entries: %v", err) - } + testassert.NoError(t, err) registers, err := store.ReadRegisters(context.Background(), sessionID) - if err != nil { - t.Fatalf("read registers: %v", err) - } + testassert.NoError(t, err) return len(entries) + len(registers) } @@ -638,19 +544,11 @@ func TestToolArgumentHookDenialSettlesAfterHooks(t *testing.T) { h, store, c, sessionID := newHookHarness(t, exec) publishHookCalls(t, h, c, exec, testToolCall("call-1")) got, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultDenied, Content: PermissionDeniedContent}) { - t.Fatalf("tool result = %+v, want the permission denial", got) - } - if executed != 0 { - t.Fatalf("denied executor ran %d times, want the forbidden concrete start prevented", executed) - } + testassert.NoError(t, err) + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultDenied, Content: PermissionDeniedContent})) + testassert.Equal(t, executed, 0) committed := hookGraphHookResults(t, store, sessionID) - if len(committed) != 1 || committed[0].Status != hookSucceeded || string(committed[0].Arguments) != `{"repaired":true}` { - t.Fatalf("hook results = %+v, want the settled hook effect left intact", committed) - } + testassert.Require(t, len(committed) == 1 && committed[0].Status == hookSucceeded && string(committed[0].Arguments) == `{"repaired":true}`, "hook results = %+v, want the settled hook effect left intact", committed) }) t.Run("allowed sibling executes with the final committed value", func(t *testing.T) { @@ -659,16 +557,10 @@ func TestToolArgumentHookDenialSettlesAfterHooks(t *testing.T) { h, _, c, _ := newHookHarness(t, exec) publishHookCalls(t, h, c, exec, testToolCall("call-1")) got, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != model.ResultSuccess { - t.Fatalf("tool result = %+v, want success", got) - } + testassert.NoError(t, err) + testassert.Require(t, got.Status == model.ResultSuccess, "tool result = %+v, want success", got) prepared := spy.received() - if len(prepared) != 1 || string(prepared[0].Arguments) != `{"repaired":true}` { - t.Fatalf("preparer received %+v, want the final committed replacement", prepared) - } + testassert.Require(t, len(prepared) == 1 && string(prepared[0].Arguments) == `{"repaired":true}`, "preparer received %+v, want the final committed replacement", prepared) }) } @@ -686,17 +578,12 @@ func TestToolArgumentHookAdvertisementGate(t *testing.T) { publishHookCalls(t, h, c, exec, model.ToolCall{ID: "call-1", Name: "ghost", Arguments: json.RawMessage(`{}`)}) got, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), model.ToolCall{ID: "call-1", Name: "ghost"}) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got != (model.ToolResult{CallID: "call-1", Status: model.ResultError, Content: `Tool "ghost" is not available.`}) { - t.Fatalf("tool result = %+v, want the unavailable-tool error", got) - } - if ran != 0 { - t.Fatalf("hook ran %d times behind the gate, want zero", ran) - } - if got := hookGraphHookResults(t, store, sessionID); len(got) != 0 { - t.Fatalf("hook results = %+v, want none", got) + testassert.NoError(t, err) + testassert.Equal(t, got, (model.ToolResult{CallID: "call-1", Status: model.ResultError, Content: `Tool "ghost" is not available.`})) + testassert.Equal(t, ran, 0) + { + got := hookGraphHookResults(t, store, sessionID) + testassert.Require(t, len(got) == 0, "hook results = %+v, want none", got) } } @@ -727,20 +614,15 @@ func TestToolArgumentHookInvalidOpenedExecutionRejectsHooks(t *testing.T) { return nil }}, nil }) - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err == nil { - t.Fatalf("execute = nil, want the invalid-hook rejection") - } - if closed != 1 { - t.Fatalf("close ran %d times, want exactly one before rejection", closed) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.Require(t, err != nil, "execute = nil, want the invalid-hook rejection") } + testassert.Equal(t, closed, 1) closed = 0 rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure { - t.Fatalf("operation status = %s, want the ordinary failure settlement", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationFailure) }) } } @@ -771,38 +653,33 @@ func TestToolArgumentHookIntentFailureLeavesRecovery(t *testing.T) { } return nil } - if _, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")); !errors.Is(err, ErrStorage) { - t.Fatalf("tool effect = %v, want the injected storage failure", err) + { + _, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) + testassert.Require(t, errors.Is(err, ErrStorage), "tool effect = %v, want the injected storage failure", err) } - if ran != 0 { - t.Fatalf("hook ran %d times, want zero after the failed intent", ran) - } - if prepared := spy.received(); len(prepared) != 0 { - t.Fatalf("preparer ran for %v, want zero preparations", prepared) + testassert.Equal(t, ran, 0) + { + prepared := spy.received() + testassert.Require(t, len(prepared) == 0, "preparer ran for %v, want zero preparations", prepared) } rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 1 { - t.Fatalf("operation state = %+v, want the quiet running state with the call still pending", rec.State) - } - if got := hookGraphHookResults(t, store, sessionID); len(got) != 0 { - t.Fatalf("hook results = %+v, want none without a committed reservation", got) + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 1, "operation state = %+v, want the quiet running state with the call still pending", rec.State) + { + got := hookGraphHookResults(t, store, sessionID) + testassert.Require(t, len(got) == 0, "hook results = %+v, want none without a committed reservation", got) } - if err := Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := Recover(context.Background(), store) + testassert.NoError(t, err) } graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph after recovery: %v", err) - } + testassert.NoError(t, err) rec = graph.Operations[0] - if rec.State.Status != OperationInterruption || rec.State.ActiveEffect != nil || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("recovered operation = %+v, want the terminal interruption", rec.State) - } - if got := hookGraphHookResults(t, store, sessionID); len(got) != 0 { - t.Fatalf("hook results after recovery = %+v, want none: no reservation, no replay", got) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.ActiveEffect == nil && len(rec.State.PendingToolCalls) == 0, "recovered operation = %+v, want the terminal interruption", rec.State) + { + got := hookGraphHookResults(t, store, sessionID) + testassert.Require(t, len(got) == 0, "hook results after recovery = %+v, want none: no reservation, no replay", got) } } diff --git a/harness/interrupt_test.go b/harness/interrupt_test.go index d658afe2..7a0e965c 100644 --- a/harness/interrupt_test.go +++ b/harness/interrupt_test.go @@ -7,6 +7,7 @@ import ( "sync" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -28,21 +29,22 @@ func TestInterruptBlockedModelAttemptSettlesContractInterruption(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-started // parked inside the blocked model attempt - if err := h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } watch.next() rec := settledOperation(t, store, session, "op-1") - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the contract detail", rec.State) - } + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the contract detail", rec.State) requireSessionCleared(t, h, session) - if _, err := validateFixture(t, store, session); err != nil { - t.Fatalf("graph after the interrupt: %v", err) + { + _, err := validateFixture(t, store, session) + testassert.NoError(t, err) } } @@ -93,59 +95,55 @@ func TestInterruptBlockedForegroundToolSettlesRealResult(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-started // the foreground tool is parked in its executor - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer"); err != nil { - t.Fatalf("steering submit while the tool is blocked: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer") + testassert.NoError(t, err) } - if err := h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } watch.next() rec := settledOperation(t, store, session, "op-1") - if rec.State.Status != OperationInterruption { - t.Fatalf("operation status = %s, want interruption", rec.State.Status) - } + testassert.Equal(t, rec.State.Status, OperationInterruption) mu.Lock() ran := call2Ran mu.Unlock() - if ran { - t.Fatalf("the later unstarted call began after the interrupt") - } + testassert.Require(t, !ran, "the later unstarted call began after the interrupt") graph, err := validateFixture(t, store, session) - if err != nil { - t.Fatalf("graph after the interrupt: %v", err) - } + testassert.NoError(t, err) var call1 *toolResultEntry for i := range graph.Entries { if tr := graph.Entries[i].ToolResult; tr != nil && tr.ToolCallID == "call-1" { call1 = tr } } - if call1 == nil || call1.Status != model.ResultInterrupted || call1.Content != "tool canceled" { - t.Fatalf("call-1 result = %+v, want the tool's real interrupted result", call1) - } + testassert.Require(t, call1 != nil && call1.Status == model.ResultInterrupted && call1.Content == "tool canceled", "call-1 result = %+v, want the tool's real interrupted result", call1) // The steering buffered while the tool was blocked drains after the // interrupted Operation's terminal into its own admission, whose own // model request carries it: exactly two model effects in total. watch.next() - if rec := settledOperation(t, store, session, "op-2"); rec.State.Status != OperationSuccess { - t.Fatalf("op-2 status = %s, want the buffered steering admitted and successful", rec.State.Status) + { + rec := settledOperation(t, store, session, "op-2") + testassert.Equal(t, rec.State.Status, OperationSuccess) } mu.Lock() calls, reqs := calls, append([]model.Request(nil), requests...) mu.Unlock() - if calls != 2 { - t.Fatalf("model effects = %d, want the blocked Operation's turn plus the steering admission's own request", calls) - } - if got := strings.Join(textsOf(reqs[1]), "|"); !strings.Contains(got, "steer") { - t.Fatalf("the steering admission's request = %q, want the steering text at its own model boundary", got) + testassert.Equal(t, calls, 2) + { + got := strings.Join(textsOf(reqs[1]), "|") + testassert.Require(t, strings.Contains(got, "steer"), "the steering admission's request = %q, want the steering text at its own model boundary", got) } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,steer" { - t.Fatalf("committed inputs = %q, want the interrupted turn's input and the steering admission's own input", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,steer") } requireSessionCleared(t, h, session) } @@ -169,31 +167,27 @@ func TestInterruptSettlesUnstartedExecutionAtEntry(t *testing.T) { opens := 0 h, store, c, sessionID, prepared, cancel := newOpenerHarness(t, opener(&opens)) defer cancel() - if err := h.Interrupt(context.Background(), sessionID); err != nil { // the durable Operation runs; no execution is installed yet - t.Fatalf("Interrupt: %v", err) - } - if err := executeDirect(h, c, h.ctx, testOpID, prepared); !errors.Is(err, context.Canceled) { - t.Fatalf("execute = %v, want the entry interruption", err) + // the durable Operation runs; no execution is installed yet + { + err := h.Interrupt(context.Background(), sessionID) + testassert.NoError(t, err) } - if opens != 0 { - t.Fatalf("opener calls = %d, want the interrupted execution to settle at entry", opens) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.Require(t, errors.Is(err, context.Canceled), "execute = %v, want the entry interruption", err) } + testassert.Equal(t, opens, 0) c.mu.Lock() consumed := c.interruptOp == "" c.mu.Unlock() - if !consumed { - t.Fatalf("the interrupt marker survived its consumption") - } + testassert.Require(t, consumed, "the interrupt marker survived its consumption") rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the contract detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the contract detail", rec.State) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the entry settlement: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) @@ -208,30 +202,28 @@ func TestInterruptSettlesUnstartedExecutionAtEntry(t *testing.T) { c.mu.Lock() c.run = retired c.mu.Unlock() - if err := h.Interrupt(context.Background(), sessionID); err != nil { // canceling the retiring predecessor is the harmless no-op - t.Fatalf("Interrupt: %v", err) + // canceling the retiring predecessor is the harmless no-op + { + err := h.Interrupt(context.Background(), sessionID) + testassert.NoError(t, err) } select { case <-retired.done: default: t.Fatalf("the retiring predecessor's done channel was reopened") } - if err := executeDirect(h, c, h.ctx, testOpID, prepared); !errors.Is(err, context.Canceled) { - t.Fatalf("execute = %v, want the entry interruption", err) - } - if opens != 0 { - t.Fatalf("opener calls = %d, want the interrupted execution to settle at entry", opens) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.Require(t, errors.Is(err, context.Canceled), "execute = %v, want the entry interruption", err) } + testassert.Equal(t, opens, 0) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the contract detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the contract detail", rec.State) requireSessionCleared(t, h, sessionID) - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the entry settlement: %v", err) + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) @@ -242,27 +234,21 @@ func TestInterruptSettlesUnstartedExecutionAtEntry(t *testing.T) { c.mu.Lock() c.interruptOp = "op-other" c.mu.Unlock() - if err := executeDirect(h, c, h.ctx, testOpID, prepared); err != nil { - t.Fatalf("execute: %v", err) - } - if opens != 1 { - t.Fatalf("opener calls = %d, want the execution to run past a foreign marker", opens) + { + err := executeDirect(h, c, h.ctx, testOpID, prepared) + testassert.NoError(t, err) } + testassert.Equal(t, opens, 1) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationSuccess { - t.Fatalf("operation status = %s, want success past a foreign marker", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationSuccess) c.mu.Lock() marker := c.interruptOp c.mu.Unlock() - if marker != "op-other" { - t.Fatalf("foreign marker = %q, want it retained", marker) - } - if _, err := validateFixture(t, store, sessionID); err != nil { - t.Fatalf("graph after the run: %v", err) + testassert.Equal(t, marker, "op-other") + { + _, err := validateFixture(t, store, sessionID) + testassert.NoError(t, err) } }) } @@ -313,44 +299,52 @@ func TestInterruptSteeringPreservesBufferOrder(t *testing.T) { session := createSession(t, h) watch := watchSettlements(store) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, toolArrived, "the first tool batch") // op-1 parks inside its tool batch, before any conversation boundary - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "s1") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "op-4", MessageModeQueued, "q1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, h, session, "op-4", MessageModeQueued, "q1") + testassert.NoError(t, err) } - if err := h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } close(toolGate) receiveBounded(t, watch.seen, "op-1's interrupted terminal") // op-1 settles as the interrupted Operation - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationInterruption || - rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("op-1 state = %+v, want terminal interruption with the contract detail", rec.State) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "op-1 state = %+v, want terminal interruption with the contract detail", rec.State) } receiveBounded(t, watch.seen, "the steering head's settlement") // the buffered steering drains through ordinary admission if rec := settledOperation(t, store, session, "op-2"); rec.State.Status != OperationSuccess { t.Fatalf("op-2 status = %s (%s), want the preserved steering admitted and successful", rec.State.Status, rec.State.Terminal.Detail) } receiveBounded(t, watch.seen, "the queued head's settlement") // the queued input drains next - if rec := settledOperation(t, store, session, "op-4"); rec.State.Status != OperationSuccess { - t.Fatalf("op-4 status = %s, want the queued input admitted and successful", rec.State.Status) + { + rec := settledOperation(t, store, session, "op-4") + testassert.Equal(t, rec.State.Status, OperationSuccess) } awaitHarnessQuiet(t, h, session) // raw graph readers await Session convergence after the successors' terminals - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,s1,q1" { - t.Fatalf("committed inputs = %q, want the buffers drained in FIFO order", got) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,s1,q1") } - if owner, _ := steeringInputOwner(t, store, session, "s1"); owner != "op-2" { - t.Fatalf("steering input s1 owned by %q, want op-2", owner) + { + owner, _ := steeringInputOwner(t, store, session, "s1") + testassert.Equal(t, owner, "op-2") } requireSessionCleared(t, h, session) - if _, err := validateFixture(t, store, session); err != nil { - t.Fatalf("graph after the drains: %v", err) + { + _, err := validateFixture(t, store, session) + testassert.NoError(t, err) } } @@ -373,24 +367,29 @@ func TestInterruptLeavesBackgroundGroupOpen(t *testing.T) { watch := watchSettlements(store) var first []string - if err := h.StartJob(context.Background(), session, jobFixtureID, spawnRecord(&first, nil)); err != nil { - t.Fatalf("first StartJob: %v", err) + { + err := h.StartJob(context.Background(), session, jobFixtureID, spawnRecord(&first, nil)) + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } <-started - if err := h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } watch.next() - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationInterruption { - t.Fatalf("op-1 status = %s, want interruption", rec.State.Status) + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationInterruption) } var second []string - if err := h.StartJob(context.Background(), session, "feedface", spawnRecord(&second, nil)); err != nil { - t.Fatalf("StartJob after the interrupt = %v, want the group still admitting", err) + { + err := h.StartJob(context.Background(), session, "feedface", spawnRecord(&second, nil)) + testassert.NoError(t, err) } c := leaseCoordinator(t, h, session) c.mu.Lock() @@ -398,9 +397,7 @@ func TestInterruptLeavesBackgroundGroupOpen(t *testing.T) { _, firstLive := c.group.members[first[0]] _, secondLive := c.group.members[second[0]] c.mu.Unlock() - if members != 2 || !firstLive || !secondLive { - t.Fatalf("group members = %d (first %v, second %v), want both members live after the interrupt", members, firstLive, secondLive) - } + testassert.Require(t, members == 2 && firstLive && secondLive, "group members = %d (first %v, second %v), want both members live after the interrupt", members, firstLive, secondLive) } // TestInterruptIdleAndUnknown proves the idle row: an idle Session returns @@ -408,10 +405,12 @@ func TestInterruptLeavesBackgroundGroupOpen(t *testing.T) { func TestInterruptIdleAndUnknown(t *testing.T) { h := newTestHarness(t, emptyStore(t), nil) session := createSession(t, h) - if err := h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt on an idle Session = %v, want nil", err) + { + err := h.Interrupt(context.Background(), session) + testassert.NoError(t, err) } - if err := h.Interrupt(context.Background(), "ffffffffffffffffffffffffffffffff"); !errors.Is(err, ErrNotFound) { - t.Fatalf("Interrupt on an unknown Session = %v, want ErrNotFound", err) + { + err := h.Interrupt(context.Background(), "ffffffffffffffffffffffffffffffff") + testassert.Require(t, errors.Is(err, ErrNotFound), "Interrupt on an unknown Session = %v, want ErrNotFound", err) } } diff --git a/harness/launch_test.go b/harness/launch_test.go index 1552f937..d5ed9b3a 100644 --- a/harness/launch_test.go +++ b/harness/launch_test.go @@ -9,6 +9,8 @@ import ( "strings" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // spawnRecord wraps one spawn callback with a plain call counter that also @@ -46,9 +48,7 @@ func advanceSessionRevision(t *testing.T, store *graphStorage, sessionID string) _, err = tx.ReplaceRegister(key, reg.Revision, reg.Payload) return err }) - if err != nil { - t.Fatalf("foreign revision advance: %v", err) - } + testassert.NoError(t, err) } // TestStartJobRejectsBeforeSpawn proves every StartJob rejection happens @@ -66,29 +66,19 @@ func TestStartJobRejectsBeforeSpawn(t *testing.T) { wantErr func(t *testing.T, err error) }{ {"wrong length", "abc", stubJobStopper{}, spawn, func(t *testing.T, err error) { - if !errors.Is(err, ErrInvalid) { - t.Fatalf("wrong-length job id = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "wrong-length job id = %v, want ErrInvalid", err) }}, {"non-hex", "zzzzzzzz", stubJobStopper{}, spawn, func(t *testing.T, err error) { - if !errors.Is(err, ErrInvalid) { - t.Fatalf("non-hex job id = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "non-hex job id = %v, want ErrInvalid", err) }}, {"uppercase", "DEADBEEF", stubJobStopper{}, spawn, func(t *testing.T, err error) { - if !errors.Is(err, ErrInvalid) { - t.Fatalf("uppercase job id = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "uppercase job id = %v, want ErrInvalid", err) }}, {"nil stopper", jobFixtureID, nil, spawn, func(t *testing.T, err error) { - if !errors.Is(err, ErrInvalid) || !strings.Contains(err.Error(), "no job stopper configured") { - t.Fatalf("nil stopper = %v, want the no-job-stopper rejection", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "no job stopper configured"), "nil stopper = %v, want the no-job-stopper rejection", err) }}, {"nil spawn", jobFixtureID, stubJobStopper{}, nil, func(t *testing.T, err error) { - if !errors.Is(err, ErrInvalid) { - t.Fatalf("nil spawn = %v, want ErrInvalid", err) - } + testassert.Require(t, errors.Is(err, ErrInvalid), "nil spawn = %v, want ErrInvalid", err) }}, } for _, tc := range cases { @@ -96,13 +86,9 @@ func TestStartJobRejectsBeforeSpawn(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) h.deps.Jobs = tc.jobs err := h.StartJob(context.Background(), testSessionID, tc.jobID, tc.spawn) - if err == nil { - t.Fatalf("StartJob succeeded, want a rejection") - } + testassert.Require(t, err != nil, "StartJob succeeded, want a rejection") tc.wantErr(t, err) - if len(spawned) != 0 { - t.Fatalf("the rejected start spawned %d times", len(spawned)) - } + testassert.Equal(t, len(spawned), 0) }) } @@ -115,12 +101,8 @@ func TestStartJobRejectsBeforeSpawn(t *testing.T) { h.deps.Jobs = stubJobStopper{} err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn) want := invalidInput("session %q is archived; admission requires an open Session", testSessionID) - if err == nil || err.Error() != want.Error() { - t.Fatalf("archived start = %v, want %v", err, want) - } - if len(spawned) != 0 { - t.Fatalf("the archived start spawned") - } + testassert.Require(t, err != nil && err.Error() == want.Error(), "archived start = %v, want %v", err, want) + testassert.Equal(t, len(spawned), 0) }) t.Run("closed group", func(t *testing.T) { @@ -130,19 +112,15 @@ func TestStartJobRejectsBeforeSpawn(t *testing.T) { c.mu.Lock() c.bgState = bgStopping c.mu.Unlock() - if err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn); err == nil || - err.Error() != "harness: invalid storage request: background group is closed or stopping" { - t.Fatalf("closed-group start = %v, want the closed-or-stopping rejection", err) - } - if len(spawned) != 0 { - t.Fatalf("the closed-group start spawned") + { + err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn) + testassert.Require(t, err != nil && err.Error() == "harness: invalid storage request: background group is closed or stopping", "closed-group start = %v, want the closed-or-stopping rejection", err) } + testassert.Equal(t, len(spawned), 0) c.mu.Lock() hasGroup := c.group != nil c.mu.Unlock() - if hasGroup { - t.Fatalf("the rejected start created a member") - } + testassert.Require(t, !hasGroup, "the rejected start created a member") }) t.Run("canceled harness", func(t *testing.T) { @@ -153,17 +131,14 @@ func TestStartJobRejectsBeforeSpawn(t *testing.T) { return PreparedExecution{}, errors.New("no preparation configured for this test") }, }) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) h.deps.Jobs = stubJobStopper{} cancel() - if err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn); !errors.Is(err, context.Canceled) || err.Error() != "context canceled" { - t.Fatalf("canceled start = %v, want the raw context.Canceled", err) - } - if len(spawned) != 0 { - t.Fatalf("the canceled start spawned") + { + err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn) + testassert.Require(t, errors.Is(err, context.Canceled) && err.Error() == "context canceled", "canceled start = %v, want the raw context.Canceled", err) } + testassert.Equal(t, len(spawned), 0) }) } @@ -176,24 +151,20 @@ func TestStartJobHandoffLossFinishesMemberOnce(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) c.mu.Lock() c.bgState = bgStopping c.mu.Unlock() - if err := h.backgroundHandoffFailure(c, member); err == nil || - err.Error() != "harness: invalid storage request: background group is closed or stopping" { - t.Fatalf("handoff loss = %v, want the closed-or-stopping rejection", err) + { + err := h.backgroundHandoffFailure(c, member) + testassert.Require(t, err != nil && err.Error() == "harness: invalid storage request: background group is closed or stopping", "handoff loss = %v, want the closed-or-stopping rejection", err) } h.claimFinishBackgroundMember(c, member.completionID) waitMemberDone(t, member) c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the finished member stayed in the group") - } + testassert.Require(t, empty, "the finished member stayed in the group") h.claimFinishBackgroundMember(c, member.completionID) // a second ending is a no-op }) @@ -205,17 +176,14 @@ func TestStartJobHandoffLossFinishesMemberOnce(t *testing.T) { return PreparedExecution{}, errors.New("no preparation configured for this test") }, }) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) cancel() // the Harness closes after the admission, before the handoff - if err := h.backgroundHandoffFailure(c, member); !errors.Is(err, context.Canceled) || err.Error() != "context canceled" { - t.Fatalf("handoff loss = %v, want the raw context.Canceled", err) + { + err := h.backgroundHandoffFailure(c, member) + testassert.Require(t, errors.Is(err, context.Canceled) && err.Error() == "context canceled", "handoff loss = %v, want the raw context.Canceled", err) } }) } @@ -229,21 +197,16 @@ func TestStartJobSpawnFailureFinishesMemberOnce(t *testing.T) { c := leaseCoordinator(t, h, testSessionID) var spawned []string spawn := spawnRecord(&spawned, errors.New("spawn down")) - if err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn); err == nil || err.Error() != "spawn down" { - t.Fatalf("spawn failure = %v, want the unchanged spawn error", err) - } - if len(spawned) != 1 { - t.Fatalf("spawn calls = %d, want 1", len(spawned)) - } - if !completionIDShape.MatchString(spawned[0]) { - t.Fatalf("spawned completion id %q is not 32 lowercase hex", spawned[0]) + { + err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn) + testassert.Require(t, err != nil && err.Error() == "spawn down", "spawn failure = %v, want the unchanged spawn error", err) } + testassert.Equal(t, len(spawned), 1) + testassert.Require(t, completionIDShape.MatchString(spawned[0]), "spawned completion id %q is not 32 lowercase hex", spawned[0]) c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the failed spawn left the member live") - } + testassert.Require(t, empty, "the failed spawn left the member live") } // TestStartJobDrivesSpawnThroughAdmission proves the success path: StartJob @@ -255,21 +218,16 @@ func TestStartJobDrivesSpawnThroughAdmission(t *testing.T) { c := leaseCoordinator(t, h, testSessionID) var spawned []string spawn := spawnRecord(&spawned, nil) - if err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn); err != nil { - t.Fatalf("StartJob: %v", err) - } - if len(spawned) != 1 { - t.Fatalf("spawn calls = %d, want 1", len(spawned)) - } - if !completionIDShape.MatchString(spawned[0]) { - t.Fatalf("spawned completion id %q is not 32 lowercase hex", spawned[0]) + { + err := h.StartJob(context.Background(), testSessionID, jobFixtureID, spawn) + testassert.NoError(t, err) } + testassert.Equal(t, len(spawned), 1) + testassert.Require(t, completionIDShape.MatchString(spawned[0]), "spawned completion id %q is not 32 lowercase hex", spawned[0]) c.mu.Lock() member := c.group.members[spawned[0]] c.mu.Unlock() - if member == nil || member.kind != memberJob || member.id != jobFixtureID || member.claimed { - t.Fatalf("admitted member = %+v, want the unclaimed job member", member) - } + testassert.Require(t, member != nil && member.kind == memberJob && member.id == jobFixtureID && !member.claimed, "admitted member = %+v, want the unclaimed job member", member) h.claimFinishBackgroundMember(c, member.completionID) // test hygiene waitMemberDone(t, member) } @@ -295,8 +253,9 @@ func TestLaunchChildSessionRejectsInvalidRequests(t *testing.T) { t.Run(tc.name, func(t *testing.T) { req := launchRequest() tc.mut(&req) - if _, err := h.LaunchChildSession(context.Background(), req); !errors.Is(err, ErrInvalid) { - t.Fatalf("%s = %v, want ErrInvalid", tc.name, err) + { + _, err := h.LaunchChildSession(context.Background(), req) + testassert.Require(t, errors.Is(err, ErrInvalid), "%s = %v, want ErrInvalid", tc.name, err) } }) } @@ -313,19 +272,15 @@ func TestLaunchChildSessionRefusesChildParent(t *testing.T) { stub := newPrepareStub(validPrepared()) h := newTestHarness(t, (&testGraph{session: childParent}).storage(t), stub.prepare) c := leaseCoordinator(t, h, testSessionID) - if _, err := h.LaunchChildSession(context.Background(), launchRequest()); !errors.Is(err, ErrInvalid) || - !strings.Contains(err.Error(), "child sessions cannot launch child sessions") { - t.Fatalf("child-parent launch = %v, want the refusal", err) - } - if stub.callCount() != 0 { - t.Fatalf("the refused launch prepared") + { + _, err := h.LaunchChildSession(context.Background(), launchRequest()) + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "child sessions cannot launch child sessions"), "child-parent launch = %v, want the refusal", err) } + testassert.Equal(t, stub.callCount(), 0) c.mu.Lock() hasGroup := c.group != nil c.mu.Unlock() - if hasGroup { - t.Fatalf("the refused launch created a member") - } + testassert.Require(t, !hasGroup, "the refused launch created a member") } // TestLaunchChildSessionArchivedParent proves the archived-parent rejection: @@ -340,17 +295,14 @@ func TestLaunchChildSessionArchivedParent(t *testing.T) { c := leaseCoordinator(t, h, testSessionID) _, err := h.LaunchChildSession(context.Background(), launchRequest()) want := invalidInput("session %q is archived; admission requires an open Session", testSessionID) - if err == nil || err.Error() != want.Error() { - t.Fatalf("archived launch = %v, want %v", err, want) - } + testassert.Require(t, err != nil && err.Error() == want.Error(), "archived launch = %v, want %v", err, want) c.mu.Lock() hasGroup := c.group != nil c.mu.Unlock() - if hasGroup { - t.Fatalf("the rejected launch created a member") - } - if n := durableRegisterCount(fixtureStore(t, h)); n != 1 { - t.Fatalf("durable registers = %d, want only the parent session register", n) + testassert.Require(t, !hasGroup, "the rejected launch created a member") + { + n := durableRegisterCount(fixtureStore(t, h)) + testassert.Equal(t, n, 1) } } @@ -358,9 +310,7 @@ func TestLaunchChildSessionArchivedParent(t *testing.T) { func fixtureStore(t *testing.T, h *Harness) *graphStorage { t.Helper() store, ok := h.deps.Storage.(*graphStorage) - if !ok { - t.Fatalf("the fixture harness does not use a graphStorage") - } + testassert.Require(t, ok, "the fixture harness does not use a graphStorage") return store } @@ -386,38 +336,30 @@ func TestLaunchChildSessionCapRejection(t *testing.T) { defer cancel() c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, "existing-child", 0) - if err != nil { - t.Fatalf("pre-admission: %v", err) - } + testassert.NoError(t, err) req := launchRequest() req.MaxConcurrent = 1 _, err = h.LaunchChildSession(context.Background(), req) want := "harness: invalid storage request: background group is full (1/1). Wait for a background member to complete, then retry" - if err == nil || err.Error() != want { - t.Fatalf("capped launch = %v, want %q", err, want) - } + testassert.Require(t, err != nil && err.Error() == want, "capped launch = %v, want %q", err, want) c.mu.Lock() members := len(c.group.members) c.mu.Unlock() - if members != 1 { - t.Fatalf("group holds %d members, want only the pre-admitted one", members) - } - if n := durableRegisterCount(fixtureStore(t, h)); n != 1 { - t.Fatalf("the rejected launch published %d registers, want only the parent", n) + testassert.Equal(t, members, 1) + { + n := durableRegisterCount(fixtureStore(t, h)) + testassert.Equal(t, n, 1) } // Completing the live member frees the cap: the next public launch // succeeds and publishes a fresh child identity. h.finishBackgroundMember(c, member) res, err := h.LaunchChildSession(context.Background(), req) - if err != nil { - t.Fatalf("post-completion launch: %v", err) - } - if !completionIDShape.MatchString(res.ChildSessionID) || res.ChildSessionID == testSessionID { - t.Fatalf("launched child session id %q, want a fresh generated identity", res.ChildSessionID) - } - if _, err := fixtureStore(t, h).ReadRegister(context.Background(), RegisterKey{SessionID: res.ChildSessionID, Kind: RegisterSession}); err != nil { - t.Fatalf("launched child session register: %v", err) + testassert.NoError(t, err) + testassert.Require(t, completionIDShape.MatchString(res.ChildSessionID) && res.ChildSessionID != testSessionID, "launched child session id %q, want a fresh generated identity", res.ChildSessionID) + { + _, err := fixtureStore(t, h).ReadRegister(context.Background(), RegisterKey{SessionID: res.ChildSessionID, Kind: RegisterSession}) + testassert.NoError(t, err) } } @@ -429,8 +371,9 @@ func TestLaunchChildSessionPrepareFailure(t *testing.T) { stub.err = errors.New("prepare down") h := newTestHarness(t, freshSessionStore(t), stub.prepare) c := leaseCoordinator(t, h, testSessionID) - if _, err := h.LaunchChildSession(context.Background(), launchRequest()); err == nil || err.Error() != "prepare down" { - t.Fatalf("prepare failure = %v, want the unchanged preparation error", err) + { + _, err := h.LaunchChildSession(context.Background(), launchRequest()) + testassert.Require(t, err != nil && err.Error() == "prepare down", "prepare failure = %v, want the unchanged preparation error", err) } if stub.callCount() != 1 { t.Fatalf("prepare calls = %d, want 1", stub.callCount()) @@ -438,11 +381,10 @@ func TestLaunchChildSessionPrepareFailure(t *testing.T) { c.mu.Lock() hasGroup := c.group != nil c.mu.Unlock() - if hasGroup { - t.Fatalf("the failed launch created a member") - } - if n := durableRegisterCount(fixtureStore(t, h)); n != 1 { - t.Fatalf("the failed launch published %d registers, want only the parent", n) + testassert.Require(t, !hasGroup, "the failed launch created a member") + { + n := durableRegisterCount(fixtureStore(t, h)) + testassert.Equal(t, n, 1) } } @@ -457,17 +399,14 @@ func TestLaunchChildSessionPublicationFailure(t *testing.T) { store.txHook = func(string) error { return fmt.Errorf("%w: storage down", ErrStorage) } _, err := h.LaunchChildSession(context.Background(), launchRequest()) store.txHook = nil - if !errors.Is(err, ErrStorage) { - t.Fatalf("publication failure = %v, want the storage class", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "publication failure = %v, want the storage class", err) c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the failed launch left its member live") - } - if n := durableRegisterCount(store); n != 1 { - t.Fatalf("the failed launch published %d registers, want only the parent", n) + testassert.Require(t, empty, "the failed launch left its member live") + { + n := durableRegisterCount(store) + testassert.Equal(t, n, 1) } } @@ -510,46 +449,35 @@ func TestLaunchChildSessionRevisionDrift(t *testing.T) { t.Run("same-Harness advance returns the conflict on the rematerialized register", func(t *testing.T) { h, store, stub, _ := newDriftHarness(t) err := runLaunch(t, h, stub, func() { - if _, err := h.ChangeAgentType(context.Background(), testSessionID, "other"); err != nil { - t.Errorf("ChangeAgentType: %v", err) + { + _, err := h.ChangeAgentType(context.Background(), testSessionID, "other") + testassert.Check(t, err == nil, "ChangeAgentType: %v", err) } }) - if !errors.Is(err, ErrConflict) { - t.Fatalf("drifted launch = %v, want the conflict class", err) - } + testassert.Require(t, errors.Is(err, ErrConflict), "drifted launch = %v, want the conflict class", err) rec, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if rec.Revision.DurableRevision != 2 || rec.CurrentAgentType != "other" { - t.Fatalf("subsequent read = revision %d agent %q, want the rematerialized register", rec.Revision.DurableRevision, rec.CurrentAgentType) - } - if n := durableRegisterCount(store); n != 1 { - t.Fatalf("the drifted launch published %d registers, want only the parent", n) + testassert.NoError(t, err) + testassert.Require(t, rec.Revision.DurableRevision == 2 && rec.CurrentAgentType == "other", "subsequent read = revision %d agent %q, want the rematerialized register", rec.Revision.DurableRevision, rec.CurrentAgentType) + { + n := durableRegisterCount(store) + testassert.Equal(t, n, 1) } }) t.Run("storage-side advance returns the conflict without retry", func(t *testing.T) { h, store, stub, c := newDriftHarness(t) err := runLaunch(t, h, stub, func() { advanceSessionRevision(t, store, testSessionID) }) - if !errors.Is(err, ErrConflict) { - t.Fatalf("drifted launch = %v, want the conflict class", err) - } + testassert.Require(t, errors.Is(err, ErrConflict), "drifted launch = %v, want the conflict class", err) rec, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if rec.Revision.DurableRevision != 2 { - t.Fatalf("subsequent read revision = %d, want the rematerialized 2", rec.Revision.DurableRevision) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.Revision.DurableRevision, 2) c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the drifted launch left its member live") - } - if n := durableRegisterCount(store); n != 1 { - t.Fatalf("the drifted launch published %d registers, want only the parent", n) + testassert.Require(t, empty, "the drifted launch left its member live") + { + n := durableRegisterCount(store) + testassert.Equal(t, n, 1) } }) @@ -557,30 +485,28 @@ func TestLaunchChildSessionRevisionDrift(t *testing.T) { h, store, stub, c := newDriftHarness(t) err := runLaunch(t, h, stub, func() { advanceSessionRevision(t, store, testSessionID) - if err := store.Transact(context.Background(), func(tx Transaction) error { - key := RegisterKey{SessionID: testSessionID, Kind: RegisterSession} - reg, err := tx.ReadRegister(key) - if err != nil { + { + err := store.Transact(context.Background(), func(tx Transaction) error { + key := RegisterKey{SessionID: testSessionID, Kind: RegisterSession} + reg, err := tx.ReadRegister(key) + if err != nil { + return err + } + _, err = tx.ReplaceRegister(key, reg.Revision, json.RawMessage(`{"bogus":true}`)) return err - } - _, err = tx.ReplaceRegister(key, reg.Revision, json.RawMessage(`{"bogus":true}`)) - return err - }); err != nil { - t.Errorf("corrupt register: %v", err) + }) + testassert.Check(t, err == nil, "corrupt register: %v", err) } }) var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("failed-rematerialization launch = %v, want the corruption class", err) - } + testassert.Require(t, errors.As(err, &corrupt), "failed-rematerialization launch = %v, want the corruption class", err) c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the failed launch left its member live") - } - if n := durableRegisterCount(store); n != 1 { - t.Fatalf("the failed launch published %d registers, want only the parent", n) + testassert.Require(t, empty, "the failed launch left its member live") + { + n := durableRegisterCount(store) + testassert.Equal(t, n, 1) } }) } @@ -604,9 +530,7 @@ func TestLaunchChildSessionSettlesRejectedHandoff(t *testing.T) { } _, err := h.LaunchChildSession(context.Background(), launchRequest()) store.txHook = nil - if err == nil || err.Error() != "harness: invalid storage request: background group is closed or stopping" { - t.Fatalf("rejected launch = %v, want the closed-or-stopping rejection", err) - } + testassert.Require(t, err != nil && err.Error() == "harness: invalid storage request: background group is closed or stopping", "rejected launch = %v, want the closed-or-stopping rejection", err) var childID string for id := range store.registers { @@ -614,37 +538,32 @@ func TestLaunchChildSessionSettlesRejectedHandoff(t *testing.T) { childID = id } } - if childID == "" { - t.Fatalf("the committed child register is missing") - } + testassert.Require(t, childID != "", "the committed child register is missing") rec := settledOperation(t, store, childID, "child-op-1") - if rec.State.Status != OperationInterruption { - t.Fatalf("child operation settled %q, want interruption", rec.State.Status) - } + testassert.Equal(t, rec.State.Status, OperationInterruption) childRec, err := h.ReadSessionHeader(context.Background(), childID) - if err != nil { - t.Fatalf("ReadSessionHeader(child): %v", err) - } - if childRec.CurrentOperationID != "" { - t.Fatalf("settled child still runs operation %q", childRec.CurrentOperationID) + testassert.NoError(t, err) + testassert.Equal(t, childRec.CurrentOperationID, "") + // input, interruption signal, settlement + { + n := storedEntryCount(store, childID) + testassert.Equal(t, n, 3) } - if n := storedEntryCount(store, childID); n != 3 { // input, interruption signal, settlement - t.Fatalf("child entries = %d, want input, interruption signal and settlement", n) + { + n := storedEntryCount(store, testSessionID) + testassert.Equal(t, n, 0) } - if n := storedEntryCount(store, testSessionID); n != 0 { - t.Fatalf("parent entries = %d, want no completion delivery", n) - } - if signals := storedSignals(t, store, testSessionID); len(signals) != 0 { - t.Fatalf("the rejected launch delivered %+v, want no completion", signals) + { + signals := storedSignals(t, store, testSessionID) + testassert.Require(t, len(signals) == 0, "the rejected launch delivered %+v, want no completion", signals) } c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the rejected launch left its member live") - } - if n := len(script.seen()); n != 0 { - t.Fatalf("the child execution ran %d model attempts, want none", n) + testassert.Require(t, empty, "the rejected launch left its member live") + { + n := len(script.seen()) + testassert.Equal(t, n, 0) } } @@ -662,41 +581,32 @@ func TestLaunchChildSessionStartFailureLeavesChildForRecovery(t *testing.T) { childID = id } } - if childID == "" { - t.Fatalf("the committed child register is missing") - } - if rec := settledOperation(t, store, childID, "child-op-1"); rec.State.Status != OperationRunning { - t.Fatalf("child operation status = %q, want running for recovery", rec.State.Status) + testassert.Require(t, childID != "", "the committed child register is missing") + { + rec := settledOperation(t, store, childID, "child-op-1") + testassert.Equal(t, rec.State.Status, OperationRunning) } reg, err := store.ReadRegister(context.Background(), RegisterKey{SessionID: childID, Kind: RegisterSession}) - if err != nil { - t.Fatalf("read child register: %v", err) - } + testassert.NoError(t, err) childRec, err := decodeSessionRegister(reg) - if err != nil { - t.Fatalf("decode child register: %v", err) - } - if childRec.State.CurrentOperationID != "child-op-1" { - t.Fatalf("child current operation = %q, want the running launch operation", childRec.State.CurrentOperationID) - } + testassert.NoError(t, err) + testassert.Equal(t, childRec.State.CurrentOperationID, "child-op-1") c.mu.Lock() empty := c.group == nil || len(c.group.members) == 0 c.mu.Unlock() - if !empty { - t.Fatalf("the failed start left its member live") + testassert.Require(t, empty, "the failed start left its member live") + { + n := storedEntryCount(store, testSessionID) + testassert.Equal(t, n, 0) } - if n := storedEntryCount(store, testSessionID); n != 0 { - t.Fatalf("parent entries = %d, want no delivery", n) - } - if n := len(script.seen()); n != 0 { - t.Fatalf("the child execution ran %d model attempts, want none", n) + { + n := len(script.seen()) + testassert.Equal(t, n, 0) } h.mu.Lock() latched := h.storageFailure != nil h.mu.Unlock() - if !latched { - t.Fatalf("the storage-class start failure was not latched") - } + testassert.Require(t, latched, "the storage-class start failure was not latched") } t.Run("child materialization failure", func(t *testing.T) { @@ -710,9 +620,7 @@ func TestLaunchChildSessionStartFailureLeavesChildForRecovery(t *testing.T) { store.registersErr = fmt.Errorf("%w: storage down", ErrStorage) // the child materialization read fails _, err := h.LaunchChildSession(context.Background(), launchRequest()) store.registersErr = nil - if !errors.Is(err, ErrStorage) { - t.Fatalf("materialization failure = %v, want the storage class", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "materialization failure = %v, want the storage class", err) assertRecoveryState(t, h, store, c, script) }) @@ -744,9 +652,7 @@ func TestLaunchChildSessionStartFailureLeavesChildForRecovery(t *testing.T) { } _, err := h.LaunchChildSession(context.Background(), launchRequest()) store.txHook = nil - if !errors.Is(err, ErrStorage) { - t.Fatalf("settlement failure = %v, want the storage class", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "settlement failure = %v, want the storage class", err) assertRecoveryState(t, h, store, c, script) }) } @@ -765,57 +671,26 @@ func TestLaunchChildSessionStartsChild(t *testing.T) { c := leaseCoordinator(t, h, testSessionID) res, err := h.LaunchChildSession(context.Background(), launchRequest()) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } - if !completionIDShape.MatchString(res.ChildSessionID) || res.ChildSessionID == testSessionID { - t.Fatalf("child id = %q, want a fresh 32-hex identity", res.ChildSessionID) - } + testassert.NoError(t, err) + testassert.Require(t, completionIDShape.MatchString(res.ChildSessionID) && res.ChildSessionID != testSessionID, "child id = %q, want a fresh 32-hex identity", res.ChildSessionID) call := stub.lastCall() - if call.req.RequestKind != RequestKindMessage { - t.Fatalf("prepare request kind = %q, want %q", call.req.RequestKind, RequestKindMessage) - } + testassert.Equal(t, call.req.RequestKind, RequestKindMessage) ident := call.req.Session.Identity - if ident.SessionID != res.ChildSessionID || ident.ParentSessionID != testSessionID || - ident.Workspace != "/tmp/works" || ident.CreatedAt.IsZero() || - ident.SourceSessionID != "" || ident.SourceBoundaryEntryID != "" { - t.Fatalf("prepared identity = %+v, want the complete child lineage", ident) - } - if call.req.Session.AgentType != "child-agent" { - t.Fatalf("prepared agent type = %q, want child-agent", call.req.Session.AgentType) - } + testassert.Require(t, ident.SessionID == res.ChildSessionID && ident.ParentSessionID == testSessionID && ident.Workspace == "/tmp/works" && !ident.CreatedAt.IsZero() && ident.SourceSessionID == "" && ident.SourceBoundaryEntryID == "", "prepared identity = %+v, want the complete child lineage", ident) + testassert.Equal(t, call.req.Session.AgentType, "child-agent") childRec, err := h.ReadSessionHeader(context.Background(), res.ChildSessionID) - if err != nil { - t.Fatalf("ReadSessionHeader(child): %v", err) - } - if childRec.Identity.ParentSessionID != testSessionID || childRec.Identity.Workspace != "/tmp/works" || - !childRec.Identity.CreatedAt.Equal(ident.CreatedAt) || childRec.Identity.SourceSessionID != "" { - t.Fatalf("durable child identity = %+v, want the prepared identity", childRec.Identity) - } - if childRec.Lifecycle != LifecycleOpen || childRec.CurrentAgentType != "child-agent" || - childRec.CurrentOperationID != "child-op-1" || childRec.ArchivedAt != nil { - t.Fatalf("durable child state = %+v, want the initial launch state", childRec) - } + testassert.NoError(t, err) + testassert.Require(t, childRec.Identity.ParentSessionID == testSessionID && childRec.Identity.Workspace == "/tmp/works" && childRec.Identity.CreatedAt.Equal(ident.CreatedAt) && childRec.Identity.SourceSessionID == "" && (childRec.Lifecycle == LifecycleOpen && childRec.CurrentAgentType == "child-agent" && childRec.CurrentOperationID == "child-op-1" && childRec.ArchivedAt == nil), "durable child identity = %+v, want the prepared identity; durable child state = %+v, want the initial launch state", childRec.Identity, childRec) op := settledOperation(t, store, res.ChildSessionID, "child-op-1") - if op.State.Status != OperationRunning || op.Admission.RequestKind != RequestKindMessage || - op.Admission.AgentType != "child-agent" { - t.Fatalf("durable child operation = %+v, want the running plugin admission", op) - } + testassert.Require(t, op.State.Status == OperationRunning && op.Admission.RequestKind == RequestKindMessage && op.Admission.AgentType == "child-agent", "durable child operation = %+v, want the running plugin admission", op) capture := op.Admission.Execution want := testCapture() - if !reflect.DeepEqual(capture, want) { - t.Fatalf("durable child capture = %+v, want the sentinel capture %+v", capture, want) - } + testassert.Require(t, reflect.DeepEqual(capture, want), "durable child capture = %+v, want the sentinel capture %+v", capture, want) graph, err := validateFixture(t, store, res.ChildSessionID) - if err != nil { - t.Fatalf("child graph: %v", err) - } - if len(graph.Entries) != 1 || graph.Entries[0].Input == nil || - graph.Entries[0].Input.Origin != InputOriginPlugin || graph.Entries[0].Input.Content[0].Text != "child task" { - t.Fatalf("child entries = %+v, want the plugin-origin input", graph.Entries) - } + testassert.NoError(t, err) + testassert.Require(t, len(graph.Entries) == 1 && graph.Entries[0].Input != nil && graph.Entries[0].Input.Origin == InputOriginPlugin && graph.Entries[0].Input.Content[0].Text == "child task", "child entries = %+v, want the plugin-origin input", graph.Entries) c.mu.Lock() members := len(c.group.members) @@ -824,16 +699,12 @@ func TestLaunchChildSessionStartsChild(t *testing.T) { member = m } c.mu.Unlock() - if members != 1 || member == nil || member.kind != memberChild || member.id != res.ChildSessionID || member.claimed { - t.Fatalf("parent members = %d (%+v), want the unclaimed child member", members, member) - } + testassert.Require(t, members == 1 && member != nil && member.kind == memberChild && member.id == res.ChildSessionID && !member.claimed, "parent members = %d (%+v), want the unclaimed child member", members, member) child := cachedCoordinator(t, h, res.ChildSessionID) child.mu.Lock() pending := child.pendingCompletion child.mu.Unlock() - if pending == nil || pending.completionID != member.completionID || pending.outputLimit != 1024 { - t.Fatalf("child pending completion = %+v, want the launch info under the reserved identity", pending) - } + testassert.Require(t, pending != nil && pending.completionID == member.completionID && pending.outputLimit == 1024, "child pending completion = %+v, want the launch info under the reserved identity", pending) select { case <-script.arrived: // the child execution started and parked at its model boundary @@ -844,9 +715,7 @@ func TestLaunchChildSessionStartsChild(t *testing.T) { child.mu.Lock() run := child.run child.mu.Unlock() - if run == nil { - t.Fatalf("the child execution is not installed") - } + testassert.Require(t, run != nil, "the child execution is not installed") script.releaseGate() // let the child run converge before the harness cancels cancel() select { diff --git a/harness/lease_test.go b/harness/lease_test.go index 85b458f1..909ae06c 100644 --- a/harness/lease_test.go +++ b/harness/lease_test.go @@ -3,6 +3,8 @@ package harness import ( "context" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // leaseCoordinator acquires one Session's coordinator through the production @@ -23,8 +25,6 @@ func leaseCoordinator(t *testing.T, h *Harness, sessionID string) *coordinator { func leaseCoordinatorNow(t *testing.T, h *Harness, sessionID string) (*coordinator, func()) { t.Helper() c, release, err := h.acquireCoordinator(context.Background(), sessionID) - if err != nil { - t.Fatalf("lease session %q: %v", sessionID, err) - } + testassert.Require(t, err == nil, "lease session %q: %v", sessionID, err) return c, release } diff --git a/harness/model_effect_test.go b/harness/model_effect_test.go index e52c8b63..97772d87 100644 --- a/harness/model_effect_test.go +++ b/harness/model_effect_test.go @@ -20,6 +20,7 @@ import ( "time" "github.com/MMinasyan/lightcode/agent" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -108,9 +109,7 @@ func (s *retrySpy) retry(cause error, failed int) (time.Duration, bool) { func statusErrorOf(t *testing.T, cause error) *model.HTTPStatusError { t.Helper() var status *model.HTTPStatusError - if !errors.As(cause, &status) || status.StatusCode != http.StatusTooManyRequests { - t.Fatalf("attempt failure %v, want the transport's 429 status error", cause) - } + testassert.Require(t, errors.As(cause, &status) && status.StatusCode == http.StatusTooManyRequests, "attempt failure %v, want the transport's 429 status error", cause) return status } @@ -121,9 +120,7 @@ func statusErrorOf(t *testing.T, cause error) *model.HTTPStatusError { func requireIntentAndResultCounts(t *testing.T, h *Harness, store *graphStorage, sessionID string, assistant bool) { t.Helper() graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) assistants, settlements := 0, 0 for _, entry := range graph.Entries { if entry.Assistant != nil { @@ -133,19 +130,11 @@ func requireIntentAndResultCounts(t *testing.T, h *Harness, store *graphStorage, settlements++ } } - if assistant && (assistants != 1 || settlements != 0) { - t.Fatalf("committed %d assistants and %d settlements, want the one intent/result pair riding one assistant entry", assistants, settlements) - } - if !assistant && (assistants != 0 || settlements != 1) { - t.Fatalf("committed %d assistants and %d settlements, want the one intent/result pair riding one settlement entry", assistants, settlements) - } + testassert.Require(t, !assistant || assistants == 1 && settlements == 0, "committed %d assistants and %d settlements, want the one intent/result pair riding one assistant entry", assistants, settlements) + testassert.Require(t, assistant || assistants == 0 && settlements == 1, "committed %d assistants and %d settlements, want the one intent/result pair riding one settlement entry", assistants, settlements) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.ActiveEffect != nil { - t.Fatalf("settled effect keeps an active intent %+v", rec.State.ActiveEffect) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.ActiveEffect == nil, "settled effect keeps an active intent %+v", rec.State.ActiveEffect) } // TestModelEffectRetriesTransportFailures proves the retry row over the real @@ -165,48 +154,32 @@ func TestModelEffectRetriesTransportFailures(t *testing.T) { h, store, c, sessionID := newEffectHarness(t, nil) me := h.modelEffect(c, testOpID, transportExecution(srv, spy.retry), testCapture()) assemblies := 0 - set, err := invokeModelEffect(t, me, func(source model.ModelRef, stream model.Stream) (model.Output, error) { + set, err := invokeModelEffect(t, me, defaultModelRequest(t), func(source model.ModelRef, stream model.Stream) (model.Output, error) { assemblies++ return agent.Assemble(context.Background(), source, stream) }) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready after the retries", set.Disposition) - } + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) if hits.Load() != 3 { t.Fatalf("physical requests = %d, want the two retries plus the accepted attempt", hits.Load()) } - if assemblies != 1 { - t.Fatalf("assembly callback calls = %d, want exactly one after acceptance", assemblies) - } - if len(spy.attempts) != 2 || spy.attempts[0] != 1 || spy.attempts[1] != 2 { - t.Fatalf("attempt numbers = %v, want the 1-based failed attempts [1 2]", spy.attempts) - } + testassert.Equal(t, assemblies, 1) + testassert.Require(t, len(spy.attempts) == 2 && spy.attempts[0] == 1 && spy.attempts[1] == 2, "attempt numbers = %v, want the 1-based failed attempts [1 2]", spy.attempts) statusErrorOf(t, spy.seen[0]) statusErrorOf(t, spy.seen[1]) requireIntentAndResultCounts(t, h, store, sessionID, true) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning { - t.Fatalf("operation status = %s, want the ready effect to keep it running", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, OperationRunning) var assistant *assistantEntry for i := range graph.Entries { if graph.Entries[i].Assistant != nil { assistant = graph.Entries[i].Assistant } } - if assistant == nil || assistant.Status != model.OutputCompleted || assistant.Content[0].Text != "done" { - t.Fatalf("assistant entry = %+v, want the accepted turn's completed payload", assistant) - } + testassert.Require(t, assistant != nil && assistant.Status == model.OutputCompleted && assistant.Content[0].Text == "done", "assistant entry = %+v, want the accepted turn's completed payload", assistant) } // TestModelEffectExhaustedRetriesSettleFailure proves the exhaustion row: an @@ -218,33 +191,20 @@ func TestModelEffectExhaustedRetriesSettleFailure(t *testing.T) { spy := &retrySpy{limit: 2} h, store, c, sessionID := newEffectHarness(t, nil) me := h.modelEffect(c, testOpID, transportExecution(srv, spy.retry), testCapture()) - set, err := invokeModelEffect(t, me, func(model.ModelRef, model.Stream) (model.Output, error) { + set, err := invokeModelEffect(t, me, defaultModelRequest(t), func(model.ModelRef, model.Stream) (model.Output, error) { t.Fatalf("assembly callback ran for a never-accepted attempt") return model.Output{}, nil }) - if err != nil { - t.Fatalf("model effect = %v, want the committed failure settlement with a nil error", err) - } - if set.Disposition != agent.DispoFailure || set.Output != nil || set.Detail == "" { - t.Fatalf("settlement = %+v, want the committed outputless failure with its diagnostic", set) - } - if !strings.Contains(set.Detail, "429") { - t.Fatalf("failure detail = %q, want the transport's 429 diagnostic", set.Detail) - } + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Output == nil && set.Detail != "" && strings.Contains(set.Detail, "429"), "settlement = %+v, want the committed outputless failure with its diagnostic; failure detail = %q, want the transport's 429 diagnostic", set, set.Detail) if hits.Load() != 3 { t.Fatalf("physical requests = %d, want the initial attempt plus both retries", hits.Load()) } - if len(spy.attempts) != 3 || spy.attempts[2] != 3 { - t.Fatalf("attempt numbers = %v, want the policy consulted through attempt 3", spy.attempts) - } + testassert.Require(t, len(spy.attempts) == 3 && spy.attempts[2] == 3, "attempt numbers = %v, want the policy consulted through attempt 3", spy.attempts) requireIntentAndResultCounts(t, h, store, sessionID, false) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != set.Detail { - t.Fatalf("operation state = %+v, want terminal failure with the settled diagnostic", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == set.Detail, "operation state = %+v, want terminal failure with the settled diagnostic", rec.State) } // TestModelEffectConnectionFailureRetries proves the connection-failure row: @@ -258,20 +218,12 @@ func TestModelEffectConnectionFailureRetries(t *testing.T) { spy := &retrySpy{limit: 1} h, store, c, sessionID := newEffectHarness(t, nil) me := h.modelEffect(c, testOpID, transportExecution(srv, spy.retry), testCapture()) - set, err := invokeModelEffect(t, me, nil) - if err != nil { - t.Fatalf("model effect = %v, want the committed failure settlement", err) - } - if set.Disposition != agent.DispoFailure || set.Detail == "" { - t.Fatalf("settlement = %+v, want the committed failure with its diagnostic", set) - } - if len(spy.attempts) != 2 || spy.attempts[0] != 1 || spy.attempts[1] != 2 { - t.Fatalf("attempt numbers = %v, want the one allowed retry", spy.attempts) - } + set, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Detail != "", "settlement = %+v, want the committed failure with its diagnostic", set) + testassert.Require(t, len(spy.attempts) == 2 && spy.attempts[0] == 1 && spy.attempts[1] == 2, "attempt numbers = %v, want the one allowed retry", spy.attempts) var op *net.OpError - if !errors.As(spy.seen[0], &op) { - t.Fatalf("attempt failure %v, want the transport's wrapped net.OpError", spy.seen[0]) - } + testassert.Require(t, errors.As(spy.seen[0], &op), "attempt failure %v, want the transport's wrapped net.OpError", spy.seen[0]) requireIntentAndResultCounts(t, h, store, sessionID, false) }) @@ -279,16 +231,10 @@ func TestModelEffectConnectionFailureRetries(t *testing.T) { spy := &retrySpy{limit: 0} h, _, c, _ := newEffectHarness(t, nil) me := h.modelEffect(c, testOpID, transportExecution(srv, spy.retry), testCapture()) - set, err := invokeModelEffect(t, me, nil) - if err != nil { - t.Fatalf("model effect = %v, want the committed failure settlement", err) - } - if set.Disposition != agent.DispoFailure || set.Detail == "" { - t.Fatalf("settlement = %+v, want the committed failure with its diagnostic", set) - } - if len(spy.attempts) != 1 { - t.Fatalf("attempt numbers = %v, want the false classification to stop at attempt 1", spy.attempts) - } + set, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Detail != "", "settlement = %+v, want the committed failure with its diagnostic", set) + testassert.Require(t, len(spy.attempts) == 1, "attempt numbers = %v, want the false classification to stop at attempt 1", spy.attempts) }) t.Run("a negative delay also means no retry", func(t *testing.T) { @@ -299,16 +245,10 @@ func TestModelEffectConnectionFailureRetries(t *testing.T) { } h, _, c, _ := newEffectHarness(t, nil) me := h.modelEffect(c, testOpID, transportExecution(srv, policy), testCapture()) - set, err := invokeModelEffect(t, me, nil) - if err != nil { - t.Fatalf("model effect = %v, want the committed failure settlement", err) - } - if set.Disposition != agent.DispoFailure || set.Detail == "" { - t.Fatalf("settlement = %+v, want the committed failure with its diagnostic", set) - } - if attempts != 1 { - t.Fatalf("policy calls = %d, want the negative delay to stop at the first failure", attempts) - } + set, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoFailure && set.Detail != "", "settlement = %+v, want the committed failure with its diagnostic", set) + testassert.Equal(t, attempts, 1) }) } @@ -334,24 +274,14 @@ func TestModelEffectAttemptFailureUnderCancellationSettlesInterruption(t *testin exec := effectExecution(modelFn, nil) exec.Retry = policy me := h.modelEffect(c, testOpID, exec, testCapture()) - set, err := invokeModelEffectCtx(t, me, ctx, nil) - if err != nil { - t.Fatalf("model effect = %v, want the committed interruption settlement", err) - } - if set.Disposition != agent.DispoInterruption || set.Detail != executionInterruptedDetail { - t.Fatalf("settlement = %+v, want the committed interruption settlement", set) - } - if attempts != 1 || policyCalls != 0 { - t.Fatalf("attempts = %d policy calls = %d, want the done context to settle before any classification or second attempt", attempts, policyCalls) - } + set, err := invokeModelEffectCtx(t, me, ctx, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoInterruption && set.Detail == executionInterruptedDetail, "settlement = %+v, want the committed interruption settlement", set) + testassert.Require(t, attempts == 1 && policyCalls == 0, "attempts = %d policy calls = %d, want the done context to settle before any classification or second attempt", attempts, policyCalls) requireIntentAndResultCounts(t, h, store, sessionID, false) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the fixed detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the fixed detail", rec.State) } // TestModelEffectNilRetryCancelsDuringStandardBackoff proves nil Retry selects @@ -370,28 +300,18 @@ func TestModelEffectNilRetryCancelsDuringStandardBackoff(t *testing.T) { }() me := h.modelEffect(c, testOpID, transportExecution(srv, nil), testCapture()) started := time.Now() - set, err := invokeModelEffectCtx(t, me, ctx, nil) + set, err := invokeModelEffectCtx(t, me, ctx, defaultModelRequest(t), nil) elapsed := time.Since(started) - if err != nil { - t.Fatalf("model effect = %v, want the committed interruption settlement", err) - } - if set.Disposition != agent.DispoInterruption || set.Detail != executionInterruptedDetail { - t.Fatalf("settlement = %+v, want the committed interruption settlement", set) - } - if elapsed >= 2*time.Second { - t.Fatalf("cancellation during the 2s backoff returned after %v, want the wait interrupted promptly", elapsed) - } + testassert.NoError(t, err) + testassert.Require(t, set.Disposition == agent.DispoInterruption && set.Detail == executionInterruptedDetail, "settlement = %+v, want the committed interruption settlement", set) + testassert.Require(t, elapsed < 2*time.Second, "cancellation during the 2s backoff returned after %v, want the wait interrupted promptly", elapsed) if hits.Load() != 1 { t.Fatalf("physical requests = %d, want no second attempt after the cancellation", hits.Load()) } requireIntentAndResultCounts(t, h, store, sessionID, false) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationInterruption || rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("operation state = %+v, want terminal interruption with the fixed detail", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "operation state = %+v, want terminal interruption with the fixed detail", rec.State) } // TestModelEffectStandardClassifierRows pins the private standard classifier: @@ -420,17 +340,17 @@ func TestModelEffectStandardClassifierRows(t *testing.T) { for _, cause := range retryable { for failed, want := range []time.Duration{2 * time.Second, 4 * time.Second, 8 * time.Second} { delay, again := standardRetryPolicy(cause, failed+1) - if !again || delay != want { - t.Fatalf("standardRetryPolicy(%v, %d) = (%v, %v), want (%v, true)", cause, failed+1, delay, again, want) - } + testassert.Require(t, again && delay == want, "standardRetryPolicy(%v, %d) = (%v, %v), want (%v, true)", cause, failed+1, delay, again, want) } - if _, again := standardRetryPolicy(cause, 4); again { - t.Fatalf("standardRetryPolicy(%v, 4) retries, want the 1..3 bound to stop", cause) + { + _, again := standardRetryPolicy(cause, 4) + testassert.Require(t, !again, "standardRetryPolicy(%v, 4) retries, want the 1..3 bound to stop", cause) } } for _, cause := range stopping { - if delay, again := standardRetryPolicy(cause, 1); again || delay != 0 { - t.Fatalf("standardRetryPolicy(%v, 1) = (%v, %v), want no retry", cause, delay, again) + { + delay, again := standardRetryPolicy(cause, 1) + testassert.Require(t, !again && delay == 0, "standardRetryPolicy(%v, 1) = (%v, %v), want no retry", cause, delay, again) } } } @@ -454,23 +374,15 @@ func TestModelEffectAcceptedStreamNeverRetries(t *testing.T) { spy := &retrySpy{limit: 5} h, store, c, sessionID := newEffectHarness(t, nil) me := h.modelEffect(c, testOpID, transportExecution(srv, spy.retry), testCapture()) - set, err := invokeModelEffect(t, me, nil) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoContinue { - t.Fatalf("settlement disposition %q, want the derived continuation from the accepted stream's errored partial", set.Disposition) - } + set, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoContinue) if hits.Load() != 1 { t.Fatalf("physical requests = %d, want the accepted stream to never re-enter retry", hits.Load()) } - if len(spy.attempts) != 0 { - t.Fatalf("retry policy consulted %d times, want never after acceptance", len(spy.attempts)) - } + testassert.Equal(t, len(spy.attempts), 0) graph, err := validateFixture(t, store, sessionID) - if err != nil { - t.Fatalf("graph: %v", err) - } + testassert.NoError(t, err) var assistant *assistantEntry var signal *signalEntry for i := range graph.Entries { @@ -481,32 +393,19 @@ func TestModelEffectAcceptedStreamNeverRetries(t *testing.T) { signal = graph.Entries[i].Signal } } - if assistant == nil || assistant.Status != model.OutputErrored || assistant.Content[0].Text != "partial" { - t.Fatalf("assistant entry = %+v, want the committed partial", assistant) - } - if signal == nil || signal.Signal != SignalModelFailureContinuation { - t.Fatalf("signal entry = %+v, want the fixed continuation signal", signal) - } + testassert.Require(t, assistant != nil && assistant.Status == model.OutputErrored && assistant.Content[0].Text == "partial", "assistant entry = %+v, want the committed partial", assistant) + testassert.Require(t, signal != nil && signal.Signal == SignalModelFailureContinuation, "signal entry = %+v, want the fixed continuation signal", signal) requireIntentAndResultCounts(t, h, store, sessionID, true) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationRunning || rec.State.ActiveEffect != nil { - t.Fatalf("operation state = %+v, want running with the effect cleared", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationRunning && rec.State.ActiveEffect == nil, "operation state = %+v, want running with the effect cleared", rec.State) // Partial continuation: the next context projection reads the committed // partial from history. msgs, err := h.contextSource(c, testOpID)(context.Background()) - if err != nil { - t.Fatalf("continuation projection: %v", err) - } - if len(msgs) != 4 { // system, input, committed partial, continuation signal - t.Fatalf("continuation projection carried %d messages, want 4", len(msgs)) - } - if msgs[2].Role != model.RoleAssistant || msgs[2].TextContent() != "partial" { - t.Fatalf("projected partial = %+v, want the committed errored assistant", msgs[2]) - } + testassert.NoError(t, err) + // system, input, committed partial, continuation signal + testassert.Equal(t, len(msgs), 4) + testassert.Require(t, msgs[2].Role == model.RoleAssistant && msgs[2].TextContent() == "partial", "projected partial = %+v, want the committed errored assistant", msgs[2]) } // TestModelEffectOneAssemblyAndClose pins the ownership boundary on an @@ -519,22 +418,14 @@ func TestModelEffectOneAssemblyAndClose(t *testing.T) { return stream, nil }, nil) assemblies := 0 - set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, testCapture()), func(source model.ModelRef, s model.Stream) (model.Output, error) { + set, err := invokeModelEffect(t, h.modelEffect(c, testOpID, exec, testCapture()), defaultModelRequest(t), func(source model.ModelRef, s model.Stream) (model.Output, error) { assemblies++ return agent.Assemble(context.Background(), source, s) }) - if err != nil { - t.Fatalf("model effect: %v", err) - } - if set.Disposition != agent.DispoReady { - t.Fatalf("settlement disposition %q, want ready", set.Disposition) - } - if assemblies != 1 { - t.Fatalf("assembly calls = %d, want exactly one", assemblies) - } - if stream.closes != 1 { - t.Fatalf("stream closes = %d, want exactly once", stream.closes) - } + testassert.NoError(t, err) + testassert.Equal(t, set.Disposition, agent.DispoReady) + testassert.Equal(t, assemblies, 1) + testassert.Equal(t, stream.closes, 1) } // TestModelEffectInvalidAttemptOutcomeSettlesBoundaryFailure proves the @@ -549,21 +440,13 @@ func TestModelEffectInvalidAttemptOutcomeSettlesBoundaryFailure(t *testing.T) { return stream, errors.New("ambiguous outcome") }, nil) me := h.modelEffect(c, testOpID, exec, testCapture()) - _, boundaryErr := invokeModelEffect(t, me, nil) - if boundaryErr == nil { - t.Fatalf("model effect settled, want the boundary failure") - } - if stream.closes != 1 { - t.Fatalf("supplied stream closes = %d, want exactly one before the failure", stream.closes) - } + _, boundaryErr := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.Require(t, boundaryErr != nil, "model effect settled, want the boundary failure") + testassert.Equal(t, stream.closes, 1) requireIntentAndResultCounts(t, h, store, sessionID, false) rec, err := h.ReadOperation(context.Background(), sessionID, testOpID) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != boundaryErr.Error() { - t.Fatalf("operation state = %+v, want terminal failure with the boundary diagnostic", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == boundaryErr.Error(), "operation state = %+v, want terminal failure with the boundary diagnostic", rec.State) }) t.Run("neither a stream nor an error", func(t *testing.T) { @@ -572,10 +455,8 @@ func TestModelEffectInvalidAttemptOutcomeSettlesBoundaryFailure(t *testing.T) { return nil, nil }, nil) me := h.modelEffect(c, testOpID, exec, testCapture()) - _, err := invokeModelEffect(t, me, nil) - if err == nil { - t.Fatalf("model effect settled, want the boundary failure") - } + _, err := invokeModelEffect(t, me, defaultModelRequest(t), nil) + testassert.Require(t, err != nil, "model effect settled, want the boundary failure") requireIntentAndResultCounts(t, h, store, sessionID, false) }) } diff --git a/harness/observe_public_test.go b/harness/observe_public_test.go index ab2c96b3..1f4a1e8c 100644 --- a/harness/observe_public_test.go +++ b/harness/observe_public_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -111,72 +112,37 @@ func awaitPublicQuiet(t *testing.T, h *harness.Harness, session string, sink *pu deadline := time.Now().Add(10 * time.Second) for { view, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) if !view.History.ExecutionBusy && view.Header.CurrentOperationID == "" && len(sink.invalidations()) > 0 { return view } - if time.Now().After(deadline) { - t.Fatalf("session %s never reached the observed quiet pair", session) - } + testassert.Require(t, !time.Now().After(deadline), "session %s never reached the observed quiet pair", session) time.Sleep(time.Millisecond) } } // publicChildSummary is the child fixture's projected summary turn: its -// message text and its pinned cl100k_base cost below describe the same -// literal. +// message text as framed by the projection. const publicChildSummary = "[Previous conversation summary]\n\nchild summary\n\n[End of summary. Continue from here.]" -// publicFixtureTokens pins the cl100k_base cost of every literal this fixture -// sizes with, over the bundled tokenizer data. The estimator below adds the -// flat +4 per message, so the fixture window is the exact cost of the -// assembled request minus one token. -var publicFixtureTokens = map[string]int{ - "system": 1, - strings.Repeat("seed ", 80): 81, - "child answer": 2, - publicChildSummary: 17, - strings.Repeat("word ", 4000): 4001, -} - // TestPublicLaunchChildSessionAutomaticCompaction retains the launched-child // trigger oracle on both stores and extends it with the frozen-checkpoint -// steering row: the child's actual compact request parks inside the -// checkpoint, a user steering head submitted during the freeze stays pending -// with no successor scoped, the compaction summary and usage commit once -// under the preceding child Operation, and the successor then owns its caller -// identity/input and receives the summary projection. Completion delivery, -// terminal publication, scoped Close and run retirement are separate -// observations. +// steering row: the first child turn reports the conversation usage that +// overflows the captured window, the child's actual compact request parks +// inside the checkpoint, a user steering head submitted during the freeze +// stays pending with no successor scoped, the compaction summary and usage +// commit once under the preceding child Operation, and the successor then owns +// its caller identity/input and receives the summary projection. Completion +// delivery, terminal publication, scoped Close and run retirement are +// separate observations. func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { eachStore(t, func(t *testing.T, store harness.Storage) { ctx, cancel := context.WithCancel(context.Background()) sink := &publicFactSink{} - fixtureTokens := func(text string) int { - tokens, ok := publicFixtureTokens[text] - if !ok { - t.Fatalf("no pinned token vector for fixture text %q", text) - } - return tokens - } - estimate := func(messages []model.Message) int { - total := 0 - for _, message := range messages { - total += fixtureTokens(message.TextContent()) + 4 - for _, call := range message.ToolCalls { - total += fixtureTokens(call.Name) - total += fixtureTokens(string(call.Arguments)) - } - } - return total - } first := strings.Repeat("seed ", 80) huge := strings.Repeat("word ", 4000) capture := compactManualCapture() - capture.ContextWindow, capture.OutputReserve = 262144, 64 - capture.Compact.ContextWindow, capture.Compact.OutputReserve = 65536, 4096 + capture.ContextWindow, capture.OutputReserve = 4096, 2048 var mu sync.Mutex var prepares []harness.PreparationRequest type requestObservation struct { @@ -210,6 +176,12 @@ func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { parentRequest <- admission.OperationID return summaryTurnStream("parent complete", model.Usage{}), nil } + if admission.OperationID == "child-op-1" { + // The report that will overflow the + // checkpoint: window 4096 minus reserve + // 2048. + return summaryTurnStream("child answer", model.Usage{InputTokens: 5000, CachedInputTokens: 100, OutputTokens: 50}), nil + } return summaryTurnStream("child answer", model.Usage{}), nil }, CompactModel: func(_ context.Context, req model.Request) (model.Stream, error) { @@ -242,20 +214,20 @@ func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { } defer func() { cancel() - if err := h.Wait(context.Background()); err != nil { - t.Errorf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.Check(t, err == nil, "Wait: %v", err) } }() root = createSession(t, h) launch := publicChildLaunch(root) launch.Content = []model.ContentPart{{Kind: model.PartText, Text: first}} res, err := h.LaunchChildSession(context.Background(), launch) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) child := res.ChildSessionID - if rec := awaitTerminal(t, h, child, "child-op-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("first child operation: %+v", rec.State) + { + rec := awaitTerminal(t, h, child, "child-op-1") + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "first child operation: %+v", rec.State) } // A terminal child register does not prove that its completion reached // the parent. Join the actual parent execution and its lifetime. @@ -265,24 +237,19 @@ func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { case <-time.After(2 * time.Second): t.Fatal("first child completion never reached the parent model boundary") } - if completionID == "" || completionID == launch.OperationID { - t.Fatalf("parent completion identity = %q", completionID) - } - if rec := awaitTerminal(t, h, root, completionID); rec.State.Status != harness.OperationSuccess || rec.Admission.RequestKind != harness.RequestKindMessage { - t.Fatalf("parent completion operation: %+v", rec) + testassert.Require(t, completionID != "" && completionID != launch.OperationID, "parent completion identity = %q", completionID) + { + rec := awaitTerminal(t, h, root, completionID) + testassert.Require(t, rec.State.Status == harness.OperationSuccess && rec.Admission.RequestKind == harness.RequestKindMessage, "parent completion operation: %+v", rec) } select { case closed := <-parentClose: - if closed != completionID { - t.Fatalf("closed parent operation %q, want %q", closed, completionID) - } + testassert.Equal(t, closed, completionID) case <-time.After(2 * time.Second): t.Fatal("parent completion execution never closed") } parentQuiet := awaitPublicQuiet(t, h, root, sink) - if len(parentQuiet.Background) != 0 { - t.Fatalf("first child membership not retired: %+v", parentQuiet.Background) - } + testassert.Require(t, len(parentQuiet.Background) == 0, "first child membership not retired: %+v", parentQuiet.Background) awaitPublicQuiet(t, h, child, sink) parentBefore := snapshotSession(t, store, root) @@ -298,31 +265,19 @@ func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { foundParent = len(got) == 2 && got[1] == "child answer" } } - if len(compactRequests) != 0 { - t.Errorf("ordinary first child turn compacted") - } + testassert.Check(t, len(compactRequests) == 0, "ordinary first child turn compacted") mu.Unlock() - if !foundFirst || !foundParent { - t.Fatalf("missing first child or actual parent completion request: child=%v parent=%v", foundFirst, foundParent) - } - if got := texts(firstRequest); len(got) != 2 || got[1] != first { - t.Fatalf("first child projection = %q", got) + testassert.Require(t, foundFirst && foundParent, "missing first child or actual parent completion request: child=%v parent=%v", foundFirst, foundParent) + { + got := texts(firstRequest) + testassert.Require(t, len(got) == 2 && got[1] == first, "first child projection = %q", got) } - assembled := append(append([]model.Message(nil), firstRequest.Messages...), - model.Message{Role: model.RoleAssistant, Source: publicModelRef, Content: []model.ContentPart{{Kind: model.PartText, Text: "child answer"}}}, - model.Message{Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: huge}}}) mu.Lock() - capture.ContextWindow = estimate(assembled) + capture.OutputReserve - 1 - window, reserve, compactRef := capture.ContextWindow, capture.OutputReserve, capture.Compact.Model + window, compactRef := capture.ContextWindow, capture.Compact.Model mu.Unlock() - if fits := estimate([]model.Message{ - firstRequest.Messages[0], - {Role: model.RoleAssistant, Content: []model.ContentPart{{Kind: model.PartText, Text: publicChildSummary}}}, - }) + reserve; fits > window { - t.Fatalf("summary request %d does not fit overflowing window %d", fits, window) - } - if result, err := submit(t, h, child, "child-op-2", harness.MessageModeRegular, huge); err != nil || result.Disposition != harness.DispositionAdmitted { - t.Fatalf("second child submit = %+v, %v", result, err) + { + result, err := submit(t, h, child, "child-op-2", harness.MessageModeRegular, huge) + testassert.Require(t, err == nil && result.Disposition == harness.DispositionAdmitted, "second child submit = %+v, %v", result, err) } select { case <-compactArrived: // the overflowing turn is inside its frozen compact checkpoint @@ -330,45 +285,33 @@ func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { t.Fatal("the child compact request never arrived") } // a user steering head submitted during the frozen checkpoint stays - // pending: no compact piece exits at a boundary and no successor is scoped + // pending: the single summary request drains no steering and no + // successor is scoped steered, err := submit(t, h, child, "child-op-3", harness.MessageModeRegular, "steered child") - if err != nil || steered.Disposition != harness.DispositionSteering || steered.Operation != nil { - t.Fatalf("checkpoint steering submit = %+v, %v", steered, err) - } + testassert.Require(t, err == nil && steered.Disposition == harness.DispositionSteering && steered.Operation == nil, "checkpoint steering submit = %+v, %v", steered, err) frozen, err := h.ReadSessionView(context.Background(), child) - if err != nil { - t.Fatalf("frozen read: %v", err) - } - if len(frozen.Steering) != 1 || frozen.Steering[0].OperationID != "child-op-3" { - t.Fatalf("steering during the checkpoint = %+v, want the head pending", frozen.Steering) - } + testassert.NoError(t, err) + testassert.Require(t, len(frozen.Steering) == 1 && frozen.Steering[0].OperationID == "child-op-3", "steering during the checkpoint = %+v, want the head pending", frozen.Steering) for _, op := range frozen.Operations { - if op.OperationID == "child-op-3" { - t.Fatalf("the pending head was scoped as an Operation inside the frozen checkpoint") - } + testassert.Require(t, op.OperationID != "child-op-3", "the pending head was scoped as an Operation inside the frozen checkpoint") } - close(compactGate) // the pieces complete and the turn settles + close(compactGate) // the summary completes and the turn settles secondOperation := awaitTerminal(t, h, child, "child-op-2") - if rec := secondOperation; rec.State.Status != harness.OperationSuccess || rec.Admission.Execution.ContextWindow != window { - t.Fatalf("second child capture/terminal: %+v", rec) + { + rec := secondOperation + testassert.Require(t, rec.State.Status == harness.OperationSuccess && rec.Admission.Execution.ContextWindow == window, "second child capture/terminal: %+v", rec) } thirdOperation := awaitSettled(t, h, child, "child-op-3") - if thirdOperation.State.Status != harness.OperationSuccess { - t.Fatalf("checkpoint successor terminal: %+v", thirdOperation.State) - } + testassert.Require(t, thirdOperation.State.Status == harness.OperationSuccess, "checkpoint successor terminal: %+v", thirdOperation.State) childQuiet := awaitPublicQuiet(t, h, child, sink) mu.Lock() observedPieces := append([]model.Request(nil), compactRequests...) observedRequests := append([]requestObservation(nil), requests...) observedPrepares := append([]harness.PreparationRequest(nil), prepares...) mu.Unlock() - if len(observedRequests) != 4 { - t.Fatalf("conversation requests = %d, want child, parent completion, rebuilt child, checkpoint successor", len(observedRequests)) - } - if len(observedPieces) != 1 { - t.Fatalf("child compact calls = %d, want one complete piece", len(observedPieces)) - } + testassert.Equal(t, len(observedRequests), 4) + testassert.Equal(t, len(observedPieces), 1) var rebuilt model.Request var successor model.Request rebuiltCalls, successorCalls := 0, 0 @@ -381,73 +324,48 @@ func TestPublicLaunchChildSessionAutomaticCompaction(t *testing.T) { } } got := texts(rebuilt) - if rebuiltCalls != 1 || len(got) != 2 || got[1] != publicChildSummary || rebuilt.Messages[1].Source != compactRef { - t.Fatalf("child rebuilt calls/projection = %d/%q", rebuiltCalls, got) - } + testassert.Require(t, rebuiltCalls == 1 && len(got) == 2 && got[1] == publicChildSummary && rebuilt.Messages[1].Source == compactRef, "child rebuilt calls/projection = %d/%q", rebuiltCalls, got) successorGot := texts(successor) - if successorCalls != 1 || len(successorGot) != 4 || successorGot[1] != got[1] || successorGot[2] != "child answer" || successorGot[3] != "steered child" { - t.Fatalf("checkpoint successor calls/projection = %d/%v, want the summary, the post-checkpoint turn, then its own admitted input", successorCalls, successorGot) - } + testassert.Require(t, successorCalls == 1 && len(successorGot) == 4 && successorGot[1] == got[1] && successorGot[2] == "child answer" && successorGot[3] == "steered child", "checkpoint successor calls/projection = %d/%v, want the summary, the post-checkpoint turn, then its own admitted input", successorCalls, successorGot) childFacts := childQuiet.History.HistoryFacts(historyIDsOf(childQuiet.History.HistoryHeaders())) childBoundaryReg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: child, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("child session register: %v", err) - } + testassert.NoError(t, err) var childSessionState struct { State struct { CompactionEntryID string `json:"compaction_entry_id"` } `json:"state"` } - if err := json.Unmarshal(childBoundaryReg.Payload, &childSessionState); err != nil { - t.Fatalf("decode child session register: %v", err) + { + err := json.Unmarshal(childBoundaryReg.Payload, &childSessionState) + testassert.NoError(t, err) } steerOwned := false for _, fact := range childFacts { if fact.Kind == harness.EntryInput && fact.Input != nil && len(fact.Input.Content) > 0 && fact.Input.Content[0].Text == "steered child" { - if fact.OperationID != "child-op-3" { - t.Fatalf("checkpoint steering input owned by %q, want child-op-3", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "child-op-3") steerOwned = true } } - if !steerOwned { - t.Fatalf("the checkpoint steering head never committed under its own Operation") - } + testassert.Require(t, steerOwned, "the checkpoint steering head never committed under its own Operation") compactions := 0 for _, fact := range childFacts { if fact.Kind == harness.EntryCompaction { comp := fact.Compaction - if comp == nil { - t.Fatal("compaction fact has no payload") - } + testassert.Require(t, comp != nil, "%v", "compaction fact has no payload") compactions++ - if comp.OperationID != "child-op-2" || comp.Summary != "child summary" || comp.Usage == nil || *comp.Usage != (harness.UsageCount{InputTokens: 1, OutputTokens: 1}) || childSessionState.State.CompactionEntryID != comp.EntryID { - t.Fatalf("child compaction/projection = %+v", comp) - } + testassert.Require(t, comp.OperationID == "child-op-2" && comp.Summary == "child summary" && comp.Usage != nil && *comp.Usage == (harness.UsageCount{InputTokens: 1, OutputTokens: 1}) && childSessionState.State.CompactionEntryID == comp.EntryID, "child compaction/projection = %+v", comp) } } - if compactions != 1 { - t.Fatalf("child committed compactions = %d, want one", compactions) - } + testassert.Equal(t, compactions, 1) pieceCount := func(totals harness.UsageTotals) bool { - for _, usage := range totals.ByModel { - if usage.Model == compactRef { - return usage.Usage == (harness.UsageCount{InputTokens: 1, OutputTokens: 1}) - } - } - return false - } - if !pieceCount(secondOperation.State.Usage) || !pieceCount(childQuiet.TotalUsage) { - t.Fatalf("child compact usage missing: operation=%+v session=%+v", secondOperation.State.Usage, childQuiet.TotalUsage) - } - if len(observedPrepares) != 4 || observedPrepares[0].Session.Identity.SessionID != child || observedPrepares[1].Session.Identity.SessionID != root || observedPrepares[2].Session.Identity.SessionID != child || observedPrepares[3].Session.Identity.SessionID != child { - t.Fatalf("shared preparation lineage = %+v", observedPrepares) + pieces := harness.UsageByModelForTest(totals, compactRef) + return len(pieces.ByModel) == 1 && pieces.ByModel[0].Usage == (harness.UsageCount{InputTokens: 1, OutputTokens: 1}) } + testassert.Require(t, pieceCount(secondOperation.State.Usage) && pieceCount(childQuiet.TotalUsage), "child compact usage missing: operation=%+v session=%+v", secondOperation.State.Usage, childQuiet.TotalUsage) + testassert.Require(t, len(observedPrepares) == 4 && observedPrepares[0].Session.Identity.SessionID == child && observedPrepares[1].Session.Identity.SessionID == root && observedPrepares[2].Session.Identity.SessionID == child && observedPrepares[3].Session.Identity.SessionID == child, "shared preparation lineage = %+v", observedPrepares) for _, preparation := range observedPrepares { - if preparation.RequestKind != harness.RequestKindMessage { - t.Fatalf("preparation kind = %q, want message", preparation.RequestKind) - } + testassert.Equal(t, preparation.RequestKind, harness.RequestKindMessage) } assertForkSourceUnchanged(t, store, root, parentBefore) }) @@ -467,11 +385,13 @@ func TestObservePublicBothStores(t *testing.T) { _ = h.Wait(context.Background()) }() session := createSession(t, h) - if _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } - if rec := awaitTerminal(t, h, session, "op-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, session, "op-1") + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } snap := awaitPublicQuiet(t, h, session, sink) @@ -481,26 +401,18 @@ func TestObservePublicBothStores(t *testing.T) { finished int ) for _, fact := range sink.snapshot() { - if fact.SessionID != session { - t.Fatalf("fact names a foreign session: %+v", fact) - } + testassert.Require(t, fact.SessionID == session, "fact names a foreign session: %+v", fact) switch fact.Kind { case harness.FactInvalidation: case harness.FactTextDelta: deltas++ - if fact.OperationID != "op-1" || fact.Position < 0 || fact.Content == "" { - t.Fatalf("text delta fact = %+v", fact) - } + testassert.Require(t, fact.OperationID == "op-1" && fact.Position >= 0 && fact.Content != "", "text delta fact = %+v", fact) case harness.FactToolStarted: started++ - if fact.OperationID != "op-1" || fact.CallID != "call-1" || fact.Ordinal != 0 || fact.Name != "echo" { - t.Fatalf("tool started fact = %+v", fact) - } + testassert.Require(t, fact.OperationID == "op-1" && fact.CallID == "call-1" && fact.Ordinal == 0 && fact.Name == "echo", "tool started fact = %+v", fact) case harness.FactToolFinished: finished++ - if fact.OperationID != "op-1" || fact.CallID != "call-1" || fact.Status != model.ResultSuccess { - t.Fatalf("tool finished fact = %+v", fact) - } + testassert.Require(t, fact.OperationID == "op-1" && fact.CallID == "call-1" && fact.Status == model.ResultSuccess, "tool finished fact = %+v", fact) default: t.Fatalf("fact kind %q is outside the closed set", fact.Kind) } @@ -509,41 +421,48 @@ func TestObservePublicBothStores(t *testing.T) { t.Fatalf("no invalidation was delivered: %+v", sink.snapshot()) } current := harness.SessionRevision{DurableRevision: snap.Header.Revision.DurableRevision, LocalRevision: snap.Header.Revision.LocalRevision} - if _, pair, ok := h.ReadObservation(session); !ok || pair != current { - t.Fatalf("cached publication read = %+v %v, quiet snapshot = %+v", pair, ok, current) - } - if deltas != 2 || started != 1 || finished != 1 { - t.Fatalf("progress counts = deltas %d started %d finished %d", deltas, started, finished) + { + _, pair, ok := h.ReadObservation(session) + testassert.Require(t, ok && pair == current, "cached publication read = %+v %v, quiet snapshot = %+v", pair, ok, current) } + testassert.Require(t, deltas == 2 && started == 1 && finished == 1, "progress counts = deltas %d started %d finished %d", deltas, started, finished) // Restart: recovery and the new owner's reads replay nothing. cancel() - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } - if err := harness.Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := harness.Recover(context.Background(), store) + testassert.NoError(t, err) } restartSink := &publicFactSink{} restarted, restartCancel := newObservedPublicHarness(t, store, newScriptModel(), nil, restartSink.observe) defer restartCancel() - if _, err := restarted.ListSessions(context.Background()); err != nil { - t.Fatalf("ListSessions: %v", err) + { + _, err := restarted.ListSessions(context.Background()) + testassert.NoError(t, err) } - if _, err := restarted.ReadSessionHeader(context.Background(), session); err != nil { - t.Fatalf("ReadSession: %v", err) + { + _, err := restarted.ReadSessionHeader(context.Background(), session) + testassert.NoError(t, err) } - if _, err := restarted.ReadHistoryView(context.Background(), session); err != nil { - t.Fatalf("ReadHistoryView: %v", err) + { + _, err := restarted.ReadHistoryView(context.Background(), session) + testassert.NoError(t, err) } - if got := restartSink.snapshot(); len(got) != 0 { - t.Fatalf("the restarted owner replayed %+v", got) + { + got := restartSink.snapshot() + testassert.Require(t, len(got) == 0, "the restarted owner replayed %+v", got) } - if _, err := restarted.ChangeAgentType(context.Background(), session, "planner"); err != nil { - t.Fatalf("ChangeAgentType: %v", err) + { + _, err := restarted.ChangeAgentType(context.Background(), session, "planner") + testassert.NoError(t, err) } - if got := restartSink.invalidations(); len(got) != 1 { - t.Fatalf("the restarted owner's first publication emitted %+v", got) + { + got := restartSink.invalidations() + testassert.Require(t, len(got) == 1, "the restarted owner's first publication emitted %+v", got) } restartCancel() _ = restarted.Wait(context.Background()) @@ -579,11 +498,13 @@ func TestObservePublicObserverFailures(t *testing.T) { _ = h.Wait(context.Background()) }() session := createSession(t, h) - if _, err := submit(t, h, session, "op-"+mode.name, harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, session, "op-"+mode.name, harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } - if rec := awaitTerminal(t, h, session, "op-"+mode.name); rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, session, "op-"+mode.name) + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } }) } @@ -606,63 +527,44 @@ func TestObservePublicJobAndChild(t *testing.T) { root := createSession(t, h) beforeAdmit, err := h.ReadSessionHeader(context.Background(), root) - if err != nil { - t.Fatalf("ReadSessionHeader before the job admission: %v", err) - } + testassert.NoError(t, err) var completionID string - if err := h.StartJob(context.Background(), root, "deadbeef", func(_ context.Context, id string) error { - completionID = id - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(context.Background(), root, "deadbeef", func(_ context.Context, id string) error { + completionID = id + return nil + }) + testassert.NoError(t, err) } jobFacts := jobInvalidations(sink, root) - if len(jobFacts) != 1 { - t.Fatalf("job admission facts = %+v, want one job-attributed invalidation", jobFacts) - } + testassert.Require(t, len(jobFacts) == 1, "job admission facts = %+v, want one job-attributed invalidation", jobFacts) afterAdmit, err := h.ReadSessionHeader(context.Background(), root) - if err != nil { - t.Fatalf("ReadSessionHeader after the job admission: %v", err) - } - if afterAdmit.Revision.DurableRevision != beforeAdmit.Revision.DurableRevision || afterAdmit.Revision.LocalRevision != beforeAdmit.Revision.LocalRevision+1 { - t.Fatalf("job admission pair = %d/%d, want one local publication over %d/%d", - afterAdmit.Revision.DurableRevision, afterAdmit.Revision.LocalRevision, beforeAdmit.Revision.DurableRevision, beforeAdmit.Revision.LocalRevision) - } + testassert.NoError(t, err) + testassert.Require(t, afterAdmit.Revision.DurableRevision == beforeAdmit.Revision.DurableRevision && afterAdmit.Revision.LocalRevision == beforeAdmit.Revision.LocalRevision+1, "job admission pair = %d/%d, want one local publication over %d/%d", afterAdmit.Revision.DurableRevision, afterAdmit.Revision.LocalRevision, beforeAdmit.Revision.DurableRevision, beforeAdmit.Revision.LocalRevision) beforeFinish, err := h.ReadSessionHeader(context.Background(), root) - if err != nil { - t.Fatalf("ReadSessionHeader before the job finish: %v", err) - } - if err := h.DeliverBackgroundCompletion(context.Background(), root, completionID, "job done"); err != nil { - t.Fatalf("DeliverBackgroundCompletion: %v", err) + testassert.NoError(t, err) + { + err := h.DeliverBackgroundCompletion(context.Background(), root, completionID, "job done") + testassert.NoError(t, err) } afterFinish, err := h.ReadSessionHeader(context.Background(), root) - if err != nil { - t.Fatalf("ReadSessionHeader after the job finish: %v", err) - } + testassert.NoError(t, err) jobFacts = jobInvalidations(sink, root) - if len(jobFacts) != 2 { - t.Fatalf("job facts = %+v, want the admission and the finish", jobFacts) - } - if afterFinish.Revision.DurableRevision <= beforeFinish.Revision.DurableRevision { - t.Fatalf("job finish pair = %d/%d, want a durable advance past the before read %d", - afterFinish.Revision.DurableRevision, afterFinish.Revision.LocalRevision, beforeFinish.Revision.DurableRevision) - } + testassert.Require(t, len(jobFacts) == 2, "job facts = %+v, want the admission and the finish", jobFacts) + testassert.Require(t, afterFinish.Revision.DurableRevision > beforeFinish.Revision.DurableRevision, "job finish pair = %d/%d, want a durable advance past the before read %d", afterFinish.Revision.DurableRevision, afterFinish.Revision.LocalRevision, beforeFinish.Revision.DurableRevision) script.releaseGate() - if rec := awaitTerminal(t, h, root, completionID); rec.State.Status != harness.OperationSuccess { - t.Fatalf("completion operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, root, completionID) + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } before := len(sink.snapshot()) res, err := h.LaunchChildSession(context.Background(), publicChildLaunch(root)) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) var childCreate bool for _, fact := range sink.snapshot()[before:] { - if fact.JobID != "" { - t.Fatalf("child flow fact carries a Job identity: %+v", fact) - } + testassert.Require(t, fact.JobID == "", "child flow fact carries a Job identity: %+v", fact) if fact.SessionID == res.ChildSessionID { childCreate = true } @@ -671,9 +573,7 @@ func TestObservePublicJobAndChild(t *testing.T) { t.Fatalf("the child creation emitted no fact: %+v", sink.snapshot()) } for _, fact := range sink.snapshot() { - if fact.Kind != harness.FactInvalidation && fact.JobID != "" { - t.Fatalf("non-invalidation fact carries a Job identity: %+v", fact) - } + testassert.Require(t, fact.Kind == harness.FactInvalidation || fact.JobID == "", "non-invalidation fact carries a Job identity: %+v", fact) } }) } @@ -727,25 +627,17 @@ func observedBlockedStreamFixture(t *testing.T, store harness.Storage) (*publicF func assertRunningTextDelta(t *testing.T, h *harness.Harness, sink *publicFactSink, session, operation string) { t.Helper() rec, err := h.ReadOperation(context.Background(), session, operation) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != harness.OperationRunning || rec.State.Terminal != nil { - t.Fatalf("operation state = %+v, want still running", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == harness.OperationRunning && rec.State.Terminal == nil, "operation state = %+v, want still running", rec.State) var deltas int for _, fact := range sink.snapshot() { if fact.Kind != harness.FactTextDelta { continue } deltas++ - if fact.SessionID != session || fact.OperationID != operation || fact.Position != 3 || fact.Content != "partial" { - t.Fatalf("text delta fact = %+v", fact) - } - } - if deltas != 1 { - t.Fatalf("text delta facts = %d, want one", deltas) + testassert.Require(t, fact.SessionID == session && fact.OperationID == operation && fact.Position == 3 && fact.Content == "partial", "text delta fact = %+v", fact) } + testassert.Equal(t, deltas, 1) } // TestObservePublicAcceptedStreamRootAndChild proves a nonempty accepted-stream @@ -761,14 +653,16 @@ func TestObservePublicAcceptedStreamRootAndChild(t *testing.T) { _ = h.Wait(context.Background()) }() session := createSession(t, h) - if _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-arrived assertRunningTextDelta(t, h, sink, session, "op-1") release() - if rec := awaitTerminal(t, h, session, "op-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, session, "op-1") + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } }) t.Run("child", func(t *testing.T) { @@ -780,14 +674,13 @@ func TestObservePublicAcceptedStreamRootAndChild(t *testing.T) { }() root := createSession(t, h) res, err := h.LaunchChildSession(context.Background(), publicChildLaunch(root)) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) <-arrived assertRunningTextDelta(t, h, sink, res.ChildSessionID, "child-op-1") release() - if rec := awaitTerminal(t, h, res.ChildSessionID, "child-op-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("child operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, res.ChildSessionID, "child-op-1") + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } }) }) @@ -826,27 +719,19 @@ func observedBlockedToolFixture(t *testing.T, store harness.Storage) (*publicFac func assertRunningToolStarted(t *testing.T, h *harness.Harness, sink *publicFactSink, session, operation string) { t.Helper() rec, err := h.ReadOperation(context.Background(), session, operation) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != harness.OperationRunning || rec.State.Terminal != nil { - t.Fatalf("operation state = %+v, want still running", rec.State) - } + testassert.NoError(t, err) + testassert.Require(t, rec.State.Status == harness.OperationRunning && rec.State.Terminal == nil, "operation state = %+v, want still running", rec.State) var started, finished int for _, fact := range sink.snapshot() { switch fact.Kind { case harness.FactToolStarted: started++ - if fact.SessionID != session || fact.OperationID != operation || fact.CallID != "call-1" || fact.Ordinal != 0 || fact.Name != "echo" { - t.Fatalf("tool started fact = %+v", fact) - } + testassert.Require(t, fact.SessionID == session && fact.OperationID == operation && fact.CallID == "call-1" && fact.Ordinal == 0 && fact.Name == "echo", "tool started fact = %+v", fact) case harness.FactToolFinished: finished++ } } - if started != 1 || finished != 0 { - t.Fatalf("tool progress while blocked = started %d finished %d, want one started and none finished", started, finished) - } + testassert.Require(t, started == 1 && finished == 0, "tool progress while blocked = started %d finished %d, want one started and none finished", started, finished) } // assertOneToolFinished asserts exactly one finished fact with the expected @@ -859,13 +744,9 @@ func assertOneToolFinished(t *testing.T, sink *publicFactSink, session, operatio continue } finished++ - if fact.SessionID != session || fact.OperationID != operation || fact.CallID != "call-1" || fact.Status != model.ResultSuccess { - t.Fatalf("tool finished fact = %+v", fact) - } - } - if finished != 1 { - t.Fatalf("tool finished facts = %d, want one", finished) + testassert.Require(t, fact.SessionID == session && fact.OperationID == operation && fact.CallID == "call-1" && fact.Status == model.ResultSuccess, "tool finished fact = %+v", fact) } + testassert.Equal(t, finished, 1) } // TestObservePublicToolProgressRootAndChild proves one tool_started fact while @@ -881,14 +762,16 @@ func TestObservePublicToolProgressRootAndChild(t *testing.T) { _ = h.Wait(context.Background()) }() session := createSession(t, h) - if _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-execStarted assertRunningToolStarted(t, h, sink, session, "op-1") release() - if rec := awaitTerminal(t, h, session, "op-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, session, "op-1") + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } assertOneToolFinished(t, sink, session, "op-1") }) @@ -901,14 +784,13 @@ func TestObservePublicToolProgressRootAndChild(t *testing.T) { }() root := createSession(t, h) res, err := h.LaunchChildSession(context.Background(), publicChildLaunch(root)) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) <-execStarted assertRunningToolStarted(t, h, sink, res.ChildSessionID, "child-op-1") release() - if rec := awaitTerminal(t, h, res.ChildSessionID, "child-op-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("child operation status = %s, want success", rec.State.Status) + { + rec := awaitTerminal(t, h, res.ChildSessionID, "child-op-1") + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } assertOneToolFinished(t, sink, res.ChildSessionID, "child-op-1") }) diff --git a/harness/observe_test.go b/harness/observe_test.go index 70336c81..a1c54188 100644 --- a/harness/observe_test.go +++ b/harness/observe_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -70,9 +71,7 @@ func observeHarness(h *Harness) *factCollector { func snapshotPair(t *testing.T, h *Harness, sessionID string) SessionRevision { t.Helper() header, err := h.ReadSessionHeader(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSessionHeader: %v", err) - } + testassert.NoError(t, err) return header.Revision } @@ -82,8 +81,9 @@ func snapshotPair(t *testing.T, h *Harness, sessionID string) SessionRevision { func wantPairAt(t *testing.T, h *Harness, sessionID string, durable int64, local uint64) { t.Helper() want := SessionRevision{DurableRevision: durable, LocalRevision: local} - if got := snapshotPair(t, h, sessionID); got != want { - t.Fatalf("session pair = %+v, want %+v", got, want) + { + got := snapshotPair(t, h, sessionID) + testassert.Equal(t, got, want) } } @@ -94,15 +94,11 @@ func awaitHarnessQuiet(t *testing.T, h *Harness, sessionID string) SessionReadVi deadline := time.Now().Add(10 * time.Second) for { view, err := h.ReadSessionView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) if !view.History.ExecutionBusy && view.Header.CurrentOperationID == "" { return view } - if time.Now().After(deadline) { - t.Fatalf("session %s never went quiet", sessionID) - } + testassert.Require(t, !time.Now().After(deadline), "session %s never went quiet", sessionID) time.Sleep(time.Millisecond) } } @@ -116,14 +112,16 @@ func TestObserveFactKinds(t *testing.T) { } h, _, c, sessionID := newEffectHarness(t, modelFn) col := observeHarness(h) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } plan := func(_ context.Context, call model.ToolCall) PreparedTool { return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "done"}}} } - if _, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")); err != nil { - t.Fatalf("tool effect: %v", err) + { + _, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")) + testassert.NoError(t, err) } var ( @@ -136,39 +134,21 @@ func TestObserveFactKinds(t *testing.T) { switch fact.Kind { case FactInvalidation: invalidations++ - if fact.SessionID != sessionID || fact.OperationID != "" || fact.JobID != "" || - fact.Position != 0 || fact.Content != "" || fact.CallID != "" || fact.Ordinal != 0 || - fact.Name != "" || fact.Status != "" { - t.Fatalf("invalidation fact carries unrelated fields: %+v", fact) - } + testassert.Require(t, fact.SessionID == sessionID && fact.OperationID == "" && fact.JobID == "" && fact.Position == 0 && fact.Content == "" && fact.CallID == "" && fact.Ordinal == 0 && fact.Name == "" && fact.Status == "", "invalidation fact carries unrelated fields: %+v", fact) case FactTextDelta: deltas++ - if fact.SessionID != sessionID || fact.OperationID != testOpID || fact.Position != 0 || - fact.Content != "done" || fact.JobID != "" || fact.CallID != "" || fact.Ordinal != 0 || - fact.Name != "" || fact.Status != "" { - t.Fatalf("text_delta fact carries unrelated fields: %+v", fact) - } + testassert.Require(t, fact.SessionID == sessionID && fact.OperationID == testOpID && fact.Position == 0 && fact.Content == "done" && fact.JobID == "" && fact.CallID == "" && fact.Ordinal == 0 && fact.Name == "" && fact.Status == "", "text_delta fact carries unrelated fields: %+v", fact) case FactToolStarted: started++ - if fact.SessionID != sessionID || fact.OperationID != testOpID || fact.CallID != "call-1" || - fact.Ordinal != 0 || fact.Name != "echo" || fact.JobID != "" || fact.Position != 0 || - fact.Content != "" || fact.Status != "" { - t.Fatalf("tool_started fact carries unrelated fields: %+v", fact) - } + testassert.Require(t, fact.SessionID == sessionID && fact.OperationID == testOpID && fact.CallID == "call-1" && fact.Ordinal == 0 && fact.Name == "echo" && fact.JobID == "" && fact.Position == 0 && fact.Content == "" && fact.Status == "", "tool_started fact carries unrelated fields: %+v", fact) case FactToolFinished: finished++ - if fact.SessionID != sessionID || fact.OperationID != testOpID || fact.CallID != "call-1" || - fact.Status != model.ResultSuccess || fact.JobID != "" || fact.Position != 0 || - fact.Content != "" || fact.Ordinal != 0 || fact.Name != "" { - t.Fatalf("tool_finished fact carries unrelated fields: %+v", fact) - } + testassert.Require(t, fact.SessionID == sessionID && fact.OperationID == testOpID && fact.CallID == "call-1" && fact.Status == model.ResultSuccess && fact.JobID == "" && fact.Position == 0 && fact.Content == "" && fact.Ordinal == 0 && fact.Name == "", "tool_finished fact carries unrelated fields: %+v", fact) default: t.Fatalf("fact kind %q is outside the closed set", fact.Kind) } } - if invalidations < 2 || deltas != 1 || started != 1 || finished != 1 { - t.Fatalf("fact counts = invalidations %d deltas %d started %d finished %d", invalidations, deltas, started, finished) - } + testassert.Require(t, invalidations >= 2 && deltas == 1 && started == 1 && finished == 1, "fact counts = invalidations %d deltas %d started %d finished %d", invalidations, deltas, started, finished) } // TestObserveReservationClocks proves each reservation acquisition and release @@ -179,20 +159,14 @@ func TestObserveReservationClocks(t *testing.T) { base := snapshotPair(t, h, sessionID) release, err := h.reserve(context.Background(), c) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) release() facts := col.invalidations() - if len(facts) != 2 { - t.Fatalf("reservation facts = %d, want exactly the acquisition and release", len(facts)) - } + testassert.Equal(t, len(facts), 2) wantPairAt(t, h, sessionID, base.DurableRevision, base.LocalRevision+2) for _, fact := range facts { - if fact.SessionID != sessionID || fact.JobID != "" || fact.OperationID != "" { - t.Fatalf("reservation fact carries unrelated identity: %+v", fact) - } + testassert.Require(t, fact.SessionID == sessionID && fact.JobID == "" && fact.OperationID == "", "reservation fact carries unrelated identity: %+v", fact) } } @@ -207,8 +181,9 @@ func TestObserveRunLifecycle(t *testing.T) { stub := newPrepareStub(modelPrepared(script.model)) h, cancel := newCancelableHarness(t, store, PreparedExecution{}, stub.prepare) defer cancel() - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := cachedCoordinator(t, h, testSessionID) col := observeHarness(h) @@ -216,17 +191,13 @@ func TestObserveRunLifecycle(t *testing.T) { h.startExecution(c, testOpID, modelPrepared(script.model)) facts := col.invalidations() - if len(facts) != 1 { - t.Fatalf("installation facts = %d, want one", len(facts)) - } + testassert.Equal(t, len(facts), 1) wantPairAt(t, h, testSessionID, base.DurableRevision, base.LocalRevision+1) // the run slot's installation c.mu.Lock() run := c.run c.mu.Unlock() - if run == nil { - t.Fatalf("the run slot was not installed") - } + testassert.Require(t, run != nil, "the run slot was not installed") script.releaseGate() select { case <-run.done: @@ -234,9 +205,7 @@ func TestObserveRunLifecycle(t *testing.T) { t.Fatalf("the execution never converged") } final := snapshotPair(t, h, testSessionID) - if final.DurableRevision <= base.DurableRevision || final.LocalRevision <= base.LocalRevision { - t.Fatalf("converged pair = %+v, want the model result's durable advance and the drain's local publications over %+v", final, base) - } + testassert.Require(t, final.DurableRevision > base.DurableRevision && final.LocalRevision > base.LocalRevision, "converged pair = %+v, want the model result's durable advance and the drain's local publications over %+v", final, base) }) t.Run("successor slot survives the predecessor", func(t *testing.T) { @@ -257,11 +226,13 @@ func TestObserveRunLifecycle(t *testing.T) { h, cancel := newCancelableHarness(t, store, PreparedExecution{}, stub.prepare) defer cancel() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("op-1 submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "op-2", MessageModeQueued, "queued"); err != nil { - t.Fatalf("op-2 submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeQueued, "queued") + testassert.NoError(t, err) } c := cachedCoordinator(t, h, session) col := observeHarness(h) @@ -271,9 +242,7 @@ func TestObserveRunLifecycle(t *testing.T) { c.mu.Lock() run1 := c.run c.mu.Unlock() - if run1 == nil { - t.Fatalf("run1 was not installed") - } + testassert.Require(t, run1 != nil, "run1 was not installed") select { case <-run1.done: case <-time.After(10 * time.Second): @@ -282,21 +251,13 @@ func TestObserveRunLifecycle(t *testing.T) { // The queued successor was installed by the drain and is parked at // gate2: the predecessor's tail must not have cleared its slot. view, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if !view.History.ExecutionBusy { - t.Fatalf("the drain-installed successor slot was cleared by its predecessor") - } + testassert.NoError(t, err) + testassert.Require(t, view.History.ExecutionBusy, "the drain-installed successor slot was cleared by its predecessor") close(gate2) final := awaitHarnessQuiet(t, h, session) facts := col.invalidations() - if len(facts) < 2 { - t.Fatalf("successor facts = %d, want at least the successor's retire and the predecessor's release", len(facts)) - } - if final.Header.Revision.DurableRevision <= base.DurableRevision || final.Header.Revision.LocalRevision <= base.LocalRevision { - t.Fatalf("successor pair = %+v, want the successor's durable admission and its local publications over %+v", final.Header.Revision, base) - } + testassert.Require(t, len(facts) >= 2, "successor facts = %d, want at least the successor's retire and the predecessor's release", len(facts)) + testassert.Require(t, final.Header.Revision.DurableRevision > base.DurableRevision && final.Header.Revision.LocalRevision > base.LocalRevision, "successor pair = %+v, want the successor's durable admission and its local publications over %+v", final.Header.Revision, base) }) } @@ -312,16 +273,15 @@ func TestObserveBufferClocks(t *testing.T) { base := snapshotPair(t, h, sessionID) // The active route holds the reservation across the enqueue. - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: sessionID, OperationID: "op-steer", Origin: InputOriginUser, - Content: admissionContent("steer"), Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: sessionID, OperationID: "op-steer", Origin: InputOriginUser, + Content: admissionContent("steer"), Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } facts := col.invalidations() - if len(facts) != 3 { - t.Fatalf("active submit facts = %d, want reserve, enqueue and release", len(facts)) - } + testassert.Equal(t, len(facts), 3) wantPairAt(t, h, sessionID, base.DurableRevision, base.LocalRevision+3) }) @@ -333,12 +293,14 @@ func TestObserveBufferClocks(t *testing.T) { cancel() _ = h.Wait(context.Background()) }() - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := cachedCoordinator(t, h, testSessionID) - if _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationSuccess}); err != nil { - t.Fatalf("terminal settlement: %v", err) + { + _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationSuccess}) + testassert.NoError(t, err) } col := observeHarness(h) @@ -353,15 +315,11 @@ func TestObserveBufferClocks(t *testing.T) { col.reset() // isolate the drain's own publications release, err := h.reserve(h.ctx, c) - if err != nil { - t.Fatalf("drain reservation: %v", err) - } + testassert.NoError(t, err) h.drainBuffers(c, run) release() facts := col.invalidations() - if len(facts) != 4 { - t.Fatalf("drain facts = %d, want reservation, admission, installation and release (the selection publishes nothing)", len(facts)) - } + testassert.Equal(t, len(facts), 4) wantPairAt(t, h, testSessionID, base.DurableRevision+1, base.LocalRevision+3) // one durable admission — the removal rode it — and three local publications }) @@ -373,18 +331,18 @@ func TestObserveBufferClocks(t *testing.T) { return modelPrepared(nil), nil }, }) - if err != nil { - t.Fatalf("New: %v", err) - } - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + testassert.NoError(t, err) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := cachedCoordinator(t, h, testSessionID) - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: testSessionID, OperationID: "op-steer", Origin: InputOriginUser, - Content: admissionContent("steer"), Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: testSessionID, OperationID: "op-steer", Origin: InputOriginUser, + Content: admissionContent("steer"), Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } col := observeHarness(h) base := snapshotPair(t, h, testSessionID) @@ -395,25 +353,20 @@ func TestObserveBufferClocks(t *testing.T) { run := c.run c.mu.Unlock() release, err := h.reserve(h.ctx, c) - if err != nil { - t.Fatalf("drain reservation: %v", err) - } + testassert.NoError(t, err) h.drainBuffers(c, run) // the drain's loss gate discards both buffers release() facts := col.invalidations() - if len(facts) != 3 { - t.Fatalf("discard facts = %d, want the drain's reservation, the buffer discard and the release", len(facts)) - } + testassert.Equal(t, len(facts), 3) wantPairAt(t, h, testSessionID, base.DurableRevision, base.LocalRevision+3) release, err = h.reserve(h.ctx, c) - if err != nil { - t.Fatalf("drain reservation: %v", err) - } + testassert.NoError(t, err) h.drainBuffers(c, run) // an empty discard emits only the reservation cycle release() - if got := len(col.invalidations()); got != 5 { - t.Fatalf("empty discard advanced the facts to %d", got) + { + got := len(col.invalidations()) + testassert.Equal(t, got, 5) } }) } @@ -433,26 +386,24 @@ func TestObserveBackgroundClocks(t *testing.T) { col := observeHarness(h) var completionID string - if err := h.StartJob(context.Background(), testSessionID, jobFixtureID, func(_ context.Context, id string) error { - completionID = id - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(context.Background(), testSessionID, jobFixtureID, func(_ context.Context, id string) error { + completionID = id + return nil + }) + testassert.NoError(t, err) } facts := col.invalidations() - if len(facts) != 1 || facts[0].SessionID != testSessionID || facts[0].JobID != jobFixtureID { - t.Fatalf("job admission facts = %+v, want one job-attributed invalidation", facts) - } + testassert.Require(t, len(facts) == 1 && facts[0].SessionID == testSessionID && facts[0].JobID == jobFixtureID, "job admission facts = %+v, want one job-attributed invalidation", facts) col.reset() - if err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, completionID, "job done"); err != nil { - t.Fatalf("DeliverBackgroundCompletion: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), testSessionID, completionID, "job done") + testassert.NoError(t, err) } facts = col.invalidations() last := facts[len(facts)-1] - if last.SessionID != testSessionID || last.JobID != jobFixtureID { - t.Fatalf("job finish fact = %+v, want the job-attributed finish", last) - } + testassert.Require(t, last.SessionID == testSessionID && last.JobID == jobFixtureID, "job finish fact = %+v, want the job-attributed finish", last) }) t.Run("child member admit, create and finish carry no Job", func(t *testing.T) { @@ -465,17 +416,13 @@ func TestObserveBackgroundClocks(t *testing.T) { col := observeHarness(h) res, err := h.LaunchChildSession(context.Background(), launchRequest()) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) child := res.ChildSessionID facts := col.invalidations() var sawParentAdmit, sawChild bool childFacts := 0 for _, fact := range facts { - if fact.JobID != "" { - t.Fatalf("child flow fact carries a Job identity: %+v", fact) - } + testassert.Require(t, fact.JobID == "", "child flow fact carries a Job identity: %+v", fact) if fact.SessionID == testSessionID { sawParentAdmit = true } @@ -484,13 +431,12 @@ func TestObserveBackgroundClocks(t *testing.T) { childFacts++ } } - if !sawParentAdmit || !sawChild || childFacts < 2 { - t.Fatalf("child flow facts = %+v (parent %v child %v childFacts %d)", facts, sawParentAdmit, sawChild, childFacts) - } + testassert.Require(t, sawParentAdmit && sawChild && childFacts >= 2, "child flow facts = %+v (parent %v child %v childFacts %d)", facts, sawParentAdmit, sawChild, childFacts) // The child's own publications: the durable creation and its run // installation. The pair is read after the producer released its lock. - if childPair := snapshotPair(t, h, child); childPair != (SessionRevision{DurableRevision: 2, LocalRevision: 1}) { - t.Fatalf("child pair = %+v, want the durable creation and one local installation", childPair) + { + childPair := snapshotPair(t, h, child) + testassert.Equal(t, childPair, (SessionRevision{DurableRevision: 2, LocalRevision: 1})) } rootC := cachedCoordinator(t, h, testSessionID) @@ -502,16 +448,12 @@ func TestObserveBackgroundClocks(t *testing.T) { } } rootC.mu.Unlock() - if member == nil { - t.Fatalf("the launched child member is absent") - } + testassert.Require(t, member != nil, "the launched child member is absent") col.reset() h.claimFinishBackgroundMember(rootC, member.completionID) waitMemberDone(t, member) facts = col.invalidations() - if len(facts) != 1 || facts[0].SessionID != testSessionID || facts[0].JobID != "" { - t.Fatalf("child member finish facts = %+v, want one unattributed invalidation", facts) - } + testassert.Require(t, len(facts) == 1 && facts[0].SessionID == testSessionID && facts[0].JobID == "", "child member finish facts = %+v, want one unattributed invalidation", facts) }) t.Run("stop publications", func(t *testing.T) { @@ -523,24 +465,24 @@ func TestObserveBackgroundClocks(t *testing.T) { col := observeHarness(h) root := createSession(t, h) res, err := h.LaunchChildSession(context.Background(), launchRequestFor(root)) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) completions := map[string]string{} stopper := &gatedJobStopper{deliver: func(sessionID, jobID string) { h.DeliverBackgroundCompletion(context.Background(), sessionID, completions[jobID], "job done") }} h.deps.Jobs = stopper - if err := h.StartJob(context.Background(), root, jobFixtureID, func(_ context.Context, completionID string) error { - completions[jobFixtureID] = completionID - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(context.Background(), root, jobFixtureID, func(_ context.Context, completionID string) error { + completions[jobFixtureID] = completionID + return nil + }) + testassert.NoError(t, err) } col.reset() - if err := h.Stop(context.Background(), root); err != nil { - t.Fatalf("Stop: %v", err) + { + err := h.Stop(context.Background(), root) + testassert.NoError(t, err) } facts := col.invalidations() var childClosed, jobFinished, rootReopened bool @@ -555,9 +497,7 @@ func TestObserveBackgroundClocks(t *testing.T) { rootReopened = true } } - if !childClosed || !jobFinished || !rootReopened { - t.Fatalf("stop facts = %+v (child %v job %v reopen %v)", facts, childClosed, jobFinished, rootReopened) - } + testassert.Require(t, childClosed && jobFinished && rootReopened, "stop facts = %+v (child %v job %v reopen %v)", facts, childClosed, jobFinished, rootReopened) close(release) }) } @@ -570,54 +510,53 @@ func TestObserveDurableAdvances(t *testing.T) { h := newTestHarness(t, emptyStore(t), nil) col := observeHarness(h) created, err := h.CreateSession(context.Background(), CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) facts := col.invalidations() - if len(facts) != 1 { - t.Fatalf("create facts = %d, want one", len(facts)) - } + testassert.Equal(t, len(facts), 1) wantPairAt(t, h, created.Identity.SessionID, 1, 0) }) t.Run("agent type and lifecycle", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), nil) col := observeHarness(h) - if _, err := h.ChangeAgentType(context.Background(), testSessionID, "planner"); err != nil { - t.Fatalf("ChangeAgentType: %v", err) + { + _, err := h.ChangeAgentType(context.Background(), testSessionID, "planner") + testassert.NoError(t, err) } - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if _, err := h.ReopenSession(context.Background(), testSessionID); err != nil { - t.Fatalf("ReopenSession: %v", err) + { + _, err := h.ReopenSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("second ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } facts := col.invalidations() - if len(facts) != 4 { - t.Fatalf("lifecycle facts = %d, want four durable advances", len(facts)) - } + testassert.Equal(t, len(facts), 4) wantPairAt(t, h, testSessionID, 5, 0) - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("no-write archive: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if got := len(col.invalidations()); got != 4 { - t.Fatalf("no-write archive advanced the facts to %d", got) + { + got := len(col.invalidations()) + testassert.Equal(t, got, 4) } }) t.Run("admission", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(validPrepared()).prepare) col := observeHarness(h) - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } facts := col.invalidations() - if len(facts) != 3 { - t.Fatalf("admission facts = %d, want reservation, admission and release", len(facts)) - } + testassert.Equal(t, len(facts), 3) wantPairAt(t, h, testSessionID, 2, 2) }) @@ -628,30 +567,27 @@ func TestObserveDurableAdvances(t *testing.T) { h, _, c, sessionID := newEffectHarness(t, modelFn) col := observeHarness(h) base := snapshotPair(t, h, sessionID) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } before := snapshotPair(t, h, sessionID) - if got := col.invalidations(); len(got) == 0 { - t.Fatalf("model result emitted no invalidation") - } - if before.DurableRevision <= base.DurableRevision { - t.Fatalf("model result pair = %+v, want a durable advance over %+v", before, base) + { + got := col.invalidations() + testassert.Require(t, len(got) != 0, "model result emitted no invalidation") } + testassert.Require(t, before.DurableRevision > base.DurableRevision, "model result pair = %+v, want a durable advance over %+v", before, base) col.reset() compactionBase := snapshotPair(t, h, sessionID) - if err := h.commitCompaction(c, testOpID, testCapture(), "summary", 1, nil, false); err != nil { - t.Fatalf("commitCompaction: %v", err) + { + err := h.commitCompaction(c, testOpID, testCapture(), "summary", 1, nil, false) + testassert.NoError(t, err) } facts := col.invalidations() - if len(facts) != 1 { - t.Fatalf("compaction facts = %d, want one", len(facts)) - } + testassert.Equal(t, len(facts), 1) after := snapshotPair(t, h, sessionID) - if after.DurableRevision <= compactionBase.DurableRevision { - t.Fatalf("compaction pair = %+v, want a durable advance over %+v", after, compactionBase) - } + testassert.Require(t, after.DurableRevision > compactionBase.DurableRevision, "compaction pair = %+v, want a durable advance over %+v", after, compactionBase) }) t.Run("fork destination", func(t *testing.T) { @@ -662,17 +598,17 @@ func TestObserveDurableAdvances(t *testing.T) { h, cancel := newCancelableHarness(t, store, PreparedExecution{}, stub.prepare) defer cancel() defer script.releaseGate() // a parked destination execution always unblocks - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := cachedCoordinator(t, h, testSessionID) - if _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationSuccess}); err != nil { - t.Fatalf("terminal settlement: %v", err) + { + _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationSuccess}) + testassert.NoError(t, err) } sourceFacts := readSessionFacts(t, h, testSessionID) - if len(sourceFacts) == 0 || sourceFacts[0].Kind != EntryInput { - t.Fatalf("source facts = %+v", sourceFacts) - } + testassert.Require(t, len(sourceFacts) != 0 && sourceFacts[0].Kind == EntryInput, "source facts = %+v", sourceFacts) col := observeHarness(h) res, err := h.Fork(context.Background(), ForkRequest{ SourceSessionID: testSessionID, @@ -680,9 +616,7 @@ func TestObserveDurableAdvances(t *testing.T) { OperationID: "fork-op-1", Content: admissionContent("forked"), }) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) dest := res.Session.Identity.SessionID destFacts := 0 for _, fact := range col.invalidations() { @@ -690,9 +624,7 @@ func TestObserveDurableAdvances(t *testing.T) { destFacts++ } } - if destFacts < 2 { - t.Fatalf("fork destination facts = %d, want the durable creation and the local installation", destFacts) - } + testassert.Require(t, destFacts >= 2, "fork destination facts = %d, want the durable creation and the local installation", destFacts) // The destination's execution is held at the model gate, so the // exact pair cannot race its settlement. wantPairAt(t, h, dest, 2, 1) @@ -707,13 +639,8 @@ func TestObserveDurableAdvances(t *testing.T) { settled = op.Status == OperationSuccess && op.SettledAt != nil } } - if !settled { - t.Fatalf("fork destination operations = %+v, want the fork Operation settled success", final.Operations) - } - if final.Header.Revision.DurableRevision <= 2 || final.Header.Revision.LocalRevision <= 1 { - t.Fatalf("completed destination pair = {%d %d}, want the settlement's durable advance and the drain's local publications over the held {2 1}", - final.Header.Revision.DurableRevision, final.Header.Revision.LocalRevision) - } + testassert.Require(t, settled, "fork destination operations = %+v, want the fork Operation settled success", final.Operations) + testassert.Require(t, final.Header.Revision.DurableRevision > 2 && final.Header.Revision.LocalRevision > 1, "completed destination pair = {%d %d}, want the settlement's durable advance and the drain's local publications over the held {2 1}", final.Header.Revision.DurableRevision, final.Header.Revision.LocalRevision) }) } @@ -731,22 +658,22 @@ func TestObserveRollbackNoDurableHint(t *testing.T) { } return nil } - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: testSessionID, OperationID: "op-1", Origin: InputOriginUser, - Content: admissionContent("hello"), Mode: MessageModeRegular, - }); err == nil { - t.Fatalf("the failing admission succeeded") + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: testSessionID, OperationID: "op-1", Origin: InputOriginUser, + Content: admissionContent("hello"), Mode: MessageModeRegular, + }) + testassert.Require(t, err != nil, "the failing admission succeeded") } store.txHook = nil facts := col.invalidations() - if len(facts) != 2 { - t.Fatalf("rollback facts = %d, want exactly the reservation and release", len(facts)) - } + testassert.Equal(t, len(facts), 2) // The two facts are the reservation and its release: their publication // pairs are only readable after the producer released its lock, and the // final pair proves no durable hint landed. - if got := snapshotPair(t, h, testSessionID); got.DurableRevision != base.DurableRevision { - t.Fatalf("the rolled-back admission advanced the durable revision to %d", got.DurableRevision) + { + got := snapshotPair(t, h, testSessionID) + testassert.Equal(t, got.DurableRevision, base.DurableRevision) } } @@ -760,21 +687,24 @@ func TestObserveIntentOnly(t *testing.T) { stub := newPrepareStub(modelPrepared(script.model)) h, cancel := newCancelableHarness(t, store, PreparedExecution{}, stub.prepare) defer cancel() - if _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")); disposition != DispositionAdmitted { - t.Fatalf("disposition %q, want admitted", disposition) + { + _, disposition := mustAdmitWithoutExecution(t, h, testSessionID, testOpID, admissionContent("hello")) + testassert.Equal(t, disposition, DispositionAdmitted) } c := cachedCoordinator(t, h, testSessionID) col := observeHarness(h) done := make(chan struct{}) go func() { defer close(done) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(script.model, nil), testCapture()), nil); err != nil { - t.Errorf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(script.model, nil), testCapture()), defaultModelRequest(t), nil) + testassert.Check(t, err == nil, "model effect: %v", err) } }() <-script.arrived // the intent committed and the physical attempt started - if got := col.snapshot(); len(got) != 0 { - t.Fatalf("the model intent emitted %+v", got) + { + got := col.snapshot() + testassert.Require(t, len(got) == 0, "the model intent emitted %+v", got) } script.releaseGate() <-done @@ -785,8 +715,9 @@ func TestObserveIntentOnly(t *testing.T) { return completedTurnStream(testToolCall("call-1")), nil } h, _, c, _ := newEffectHarness(t, modelFn) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } executeStarted := make(chan struct{}) release := make(chan struct{}) @@ -801,15 +732,14 @@ func TestObserveIntentOnly(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) - if _, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")); err != nil { - t.Errorf("tool effect: %v", err) + { + _, err := h.toolEffect(c, testOpID, effectExecution(nil, plan), testCapture())(context.Background(), testToolCall("call-1")) + testassert.Check(t, err == nil, "tool effect: %v", err) } }() <-executeStarted // the tool intent committed and the executor started facts := col.snapshot() - if len(facts) != 1 || facts[0].Kind != FactToolStarted { - t.Fatalf("tool intent facts = %+v, want only the started fact", facts) - } + testassert.Require(t, len(facts) == 1 && facts[0].Kind == FactToolStarted, "tool intent facts = %+v, want only the started fact", facts) close(release) <-done }) @@ -820,28 +750,38 @@ func TestObserveIntentOnly(t *testing.T) { func TestObserveNoWriteAndDelete(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(validPrepared()).prepare) col := observeHarness(h) - if err := h.Interrupt(context.Background(), testSessionID); err != nil { // idle interrupt - t.Fatalf("idle Interrupt: %v", err) + // idle interrupt + { + err := h.Interrupt(context.Background(), testSessionID) + testassert.NoError(t, err) } - if _, err := h.ReopenSession(context.Background(), testSessionID); err != nil { // reopen open - t.Fatalf("no-write ReopenSession: %v", err) + // reopen open + { + _, err := h.ReopenSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := h.Stop(context.Background(), testSessionID); err != nil { // empty root stop - t.Fatalf("empty Stop: %v", err) + // empty root stop + { + err := h.Stop(context.Background(), testSessionID) + testassert.NoError(t, err) } - if got := col.snapshot(); len(got) != 0 { - t.Fatalf("no-write paths emitted %+v", got) + { + got := col.snapshot() + testassert.Require(t, len(got) == 0, "no-write paths emitted %+v", got) } - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } col.reset() - if err := h.DeleteSession(context.Background(), testSessionID); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := h.DeleteSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if got := col.snapshot(); len(got) != 0 { - t.Fatalf("deletion emitted %+v", got) + { + got := col.snapshot() + testassert.Require(t, len(got) == 0, "deletion emitted %+v", got) } } @@ -850,31 +790,36 @@ func TestObserveNoWriteAndDelete(t *testing.T) { func TestObserveSuppression(t *testing.T) { t.Run("deleted", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(validPrepared()).prepare) - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } c := cachedCoordinator(t, h, testSessionID) col := observeHarness(h) - if err := h.DeleteSession(context.Background(), testSessionID); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := h.DeleteSession(context.Background(), testSessionID) + testassert.NoError(t, err) } h.observeInvalidation(c) - if got := col.snapshot(); len(got) != 0 { - t.Fatalf("the deleted coordinator emitted %+v", got) + { + got := col.snapshot() + testassert.Require(t, len(got) == 0, "the deleted coordinator emitted %+v", got) } }) t.Run("corrupt", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(parkedPrepared()).prepare) - if _, err := h.ReadSessionHeader(context.Background(), testSessionID); err != nil { - t.Fatalf("ReadSession: %v", err) + { + _, err := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.NoError(t, err) } c := cachedCoordinator(t, h, testSessionID) col := observeHarness(h) h.markCorrupt(testSessionID, corruptSession(testSessionID, "injected corruption")) h.observeInvalidation(c) - if got := col.snapshot(); len(got) != 0 { - t.Fatalf("the corrupt coordinator emitted %+v", got) + { + got := col.snapshot() + testassert.Require(t, len(got) == 0, "the corrupt coordinator emitted %+v", got) } }) } @@ -884,8 +829,10 @@ func TestObserveSuppression(t *testing.T) { // producer's lock time. func TestObserveCurrentPair(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(parkedPrepared()).prepare) - if _, err := h.ReadSessionView(context.Background(), testSessionID); err != nil { // a real history use establishes the cell - t.Fatalf("ReadSessionView: %v", err) + // a real history use establishes the cell + { + _, err := h.ReadSessionView(context.Background(), testSessionID) + testassert.NoError(t, err) } c := cachedCoordinator(t, h, testSessionID) col := observeHarness(h) @@ -896,25 +843,19 @@ func TestObserveCurrentPair(t *testing.T) { c.mu.Unlock() // A later publication lands before the delayed emission. release, err := h.reserve(context.Background(), c) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) release() col.reset() h.observeInvalidation(c) // the delayed producer's emission facts := col.invalidations() - if len(facts) != 1 { - t.Fatalf("delayed emission facts = %d, want one", len(facts)) - } + testassert.Equal(t, len(facts), 1) // The passive publication read used by passive publishers samples // the pair current at publication time, after the later publications // above landed. current := snapshotPair(t, h, testSessionID) identity, pair, ok := h.ReadObservation(testSessionID) - if !ok || identity.SessionID != testSessionID || pair != current { - t.Fatalf("ReadObservation = %+v %+v %v, want the current pair %+v", identity, pair, ok, current) - } + testassert.Require(t, ok && identity.SessionID == testSessionID && pair == current, "ReadObservation = %+v %+v %v, want the current pair %+v", identity, pair, ok, current) } // TestObserveConcurrentCommits proves one parked observer callback holds no @@ -940,8 +881,9 @@ func TestObserveConcurrentCommits(t *testing.T) { firstDone := make(chan struct{}) go func() { defer close(firstDone) - if _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{}); err != nil { - t.Errorf("first commit: %v", err) + { + _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{}) + testassert.Check(t, err == nil, "first commit: %v", err) } }() <-firstArrived // the first commit emitted and its callback is parked @@ -949,8 +891,9 @@ func TestObserveConcurrentCommits(t *testing.T) { secondDone := make(chan struct{}) go func() { defer close(secondDone) - if _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{}); err != nil { - t.Errorf("second commit: %v", err) + { + _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{}) + testassert.Check(t, err == nil, "second commit: %v", err) } }() select { @@ -962,13 +905,9 @@ func TestObserveConcurrentCommits(t *testing.T) { <-firstDone got := col.snapshot() - if len(got) != 2 { - t.Fatalf("concurrent commit facts = %+v, want two", got) - } + testassert.Require(t, len(got) == 2, "concurrent commit facts = %+v, want two", got) for i, fact := range got { - if fact.Kind != FactInvalidation || fact.SessionID != sessionID { - t.Fatalf("fact %d lost its owner attribution: %+v", i, fact) - } + testassert.Require(t, fact.Kind == FactInvalidation && fact.SessionID == sessionID, "fact %d lost its owner attribution: %+v", i, fact) } // Both commits durably advanced; the pair is read after both producers // released their locks, so the publisher's current-pair rule is proven @@ -1029,23 +968,20 @@ func TestObserveReentrancy(t *testing.T) { probe.h = h h.deps.Observe = probe.observe - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, - Content: admissionContent("hello"), Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("Submit: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, + Content: admissionContent("hello"), Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } awaitHarnessQuiet(t, h, testSessionID) probe.mu.Lock() violations, readErrs := probe.violations, append([]error(nil), probe.readErrs...) probe.mu.Unlock() - if violations != 0 { - t.Fatalf("the observer ran inside a storage transaction %d times", violations) - } - if len(readErrs) != 0 { - t.Fatalf("the observer's reentrant reads failed: %v", readErrs) - } + testassert.Equal(t, violations, 0) + testassert.Require(t, len(readErrs) == 0, "the observer's reentrant reads failed: %v", readErrs) } // duplexStream returns one delta and one error from the same read. @@ -1081,23 +1017,21 @@ func TestObserveStreamFilter(t *testing.T) { {Position: 2, Kind: model.PartImageURL, URL: "u"}, }}) wrapper, got := collect(t, inner) - if _, err := wrapper.Recv(); err != nil { - t.Fatalf("Recv: %v", err) - } - if len(*got) != 1 || (*got)[0] != (emitted{kind: FactTextDelta, position: 0, content: "a"}) { - t.Fatalf("emitted = %+v, want only the nonempty text fragment", *got) + { + _, err := wrapper.Recv() + testassert.NoError(t, err) } + testassert.Require(t, len(*got) == 1 && (*got)[0] == (emitted{kind: FactTextDelta, position: 0, content: "a"}), "emitted = %+v, want only the nonempty text fragment", *got) }) t.Run("refusal only", func(t *testing.T) { inner := streamOf(model.StreamDelta{HasChoice: true, Role: "assistant", RefusalFragment: "I cannot"}) wrapper, got := collect(t, inner) - if _, err := wrapper.Recv(); err != nil { - t.Fatalf("Recv: %v", err) - } - if len(*got) != 1 || (*got)[0] != (emitted{kind: FactRefusalDelta, position: 0, content: "I cannot"}) { - t.Fatalf("emitted = %+v, want only the nonempty refusal fragment without a position", *got) + { + _, err := wrapper.Recv() + testassert.NoError(t, err) } + testassert.Require(t, len(*got) == 1 && (*got)[0] == (emitted{kind: FactRefusalDelta, position: 0, content: "I cannot"}), "emitted = %+v, want only the nonempty refusal fragment without a position", *got) }) t.Run("mixed delta emits content text before refusal", func(t *testing.T) { @@ -1112,17 +1046,16 @@ func TestObserveStreamFilter(t *testing.T) { }, }) wrapper, got := collect(t, inner) - if _, err := wrapper.Recv(); err != nil { - t.Fatalf("Recv: %v", err) + { + _, err := wrapper.Recv() + testassert.NoError(t, err) } want := []emitted{ {kind: FactTextDelta, position: 0, content: "working"}, {kind: FactTextDelta, position: 2, content: "harder"}, {kind: FactRefusalDelta, position: 0, content: "but no"}, } - if !slices.Equal(*got, want) { - t.Fatalf("emitted = %+v, want the content-text emissions before the refusal %+v", *got, want) - } + testassert.Require(t, slices.Equal(*got, want), "emitted = %+v, want the content-text emissions before the refusal %+v", *got, want) }) t.Run("empty refusal emits only the text", func(t *testing.T) { @@ -1134,12 +1067,11 @@ func TestObserveStreamFilter(t *testing.T) { }, }) wrapper, got := collect(t, inner) - if _, err := wrapper.Recv(); err != nil { - t.Fatalf("Recv: %v", err) - } - if len(*got) != 1 || (*got)[0].kind != FactTextDelta { - t.Fatalf("emitted = %+v, want the text fragment and no refusal emission", *got) + { + _, err := wrapper.Recv() + testassert.NoError(t, err) } + testassert.Require(t, len(*got) == 1 && (*got)[0].kind == FactTextDelta, "emitted = %+v, want the text fragment and no refusal emission", *got) }) t.Run("original delta and error preserved", func(t *testing.T) { @@ -1147,26 +1079,19 @@ func TestObserveStreamFilter(t *testing.T) { inner := &duplexStream{delta: delta, err: errors.New("read failed")} wrapper, got := collect(t, inner) returned, err := wrapper.Recv() - if err == nil || err.Error() != "read failed" { - t.Fatalf("error = %v, want the original read failure", err) - } - if len(returned.ContentFragments) != 1 || &returned.ContentFragments[0] != &delta.ContentFragments[0] { - t.Fatalf("the original delta was not preserved: %+v", returned) - } - if len(*got) != 0 { - t.Fatalf("a delta returned with an error emitted %+v", *got) - } + testassert.Require(t, err != nil && err.Error() == "read failed", "error = %v, want the original read failure", err) + testassert.Require(t, len(returned.ContentFragments) == 1 && &returned.ContentFragments[0] == &delta.ContentFragments[0], "the original delta was not preserved: %+v", returned) + testassert.Require(t, len(*got) == 0, "a delta returned with an error emitted %+v", *got) }) t.Run("refusal with an error emits none", func(t *testing.T) { inner := &duplexStream{delta: model.StreamDelta{HasChoice: true, RefusalFragment: "no"}, err: errors.New("read failed")} wrapper, got := collect(t, inner) - if _, err := wrapper.Recv(); err == nil { - t.Fatal("Recv: want the original read failure") - } - if len(*got) != 0 { - t.Fatalf("a refusal delta returned with an error emitted %+v", *got) + { + _, err := wrapper.Recv() + testassert.Require(t, err != nil, "%v", "Recv: want the original read failure") } + testassert.Require(t, len(*got) == 0, "a refusal delta returned with an error emitted %+v", *got) }) t.Run("choiceless empty nontext and invalid emit none", func(t *testing.T) { @@ -1181,12 +1106,11 @@ func TestObserveStreamFilter(t *testing.T) { } for i, delta := range cases { wrapper, got := collect(t, streamOf(delta)) - if _, err := wrapper.Recv(); err != nil { - t.Fatalf("case %d Recv: %v", i, err) - } - if len(*got) != 0 { - t.Fatalf("case %d emitted %+v", i, *got) + { + _, err := wrapper.Recv() + testassert.Require(t, err == nil, "case %d Recv: %v", i, err) } + testassert.Require(t, len(*got) == 0, "case %d emitted %+v", i, *got) } }) @@ -1212,27 +1136,21 @@ func TestObserveStreamFilter(t *testing.T) { delta: textDelta, want: []emitted{{kind: FactTextDelta, position: 3, content: "working"}, {kind: FactRefusalDelta, position: 0, content: "but no"}}, checkOrig: func(t *testing.T, returned model.StreamDelta) { - if &returned.ContentFragments[0] != &textDelta.ContentFragments[0] { - t.Fatal("the original delta was not returned unchanged") - } + testassert.Require(t, &returned.ContentFragments[0] == &textDelta.ContentFragments[0], "%v", "the original delta was not returned unchanged") }, }, { name: "same delta choice-less emits none", delta: choiceless, checkOrig: func(t *testing.T, returned model.StreamDelta) { - if &returned.ContentFragments[0] != &choiceless.ContentFragments[0] { - t.Fatal("the original choice-less delta was not returned unchanged") - } + testassert.Require(t, &returned.ContentFragments[0] == &choiceless.ContentFragments[0], "%v", "the original choice-less delta was not returned unchanged") }, }, { name: "usage-only choice-less emits none and still passes through", delta: usageOnly, checkOrig: func(t *testing.T, returned model.StreamDelta) { - if returned.Usage != usageOnly.Usage { - t.Fatal("the original usage pointer was not returned unchanged") - } + testassert.Require(t, returned.Usage == usageOnly.Usage, "%v", "the original usage pointer was not returned unchanged") }, }, { @@ -1248,12 +1166,8 @@ func TestObserveStreamFilter(t *testing.T) { t.Run(row.name, func(t *testing.T) { wrapper, got := collect(t, streamOf(row.delta)) returned, err := wrapper.Recv() - if err != nil { - t.Fatalf("Recv: %v", err) - } - if !slices.Equal(*got, row.want) { - t.Fatalf("emitted = %+v, want %+v", *got, row.want) - } + testassert.NoError(t, err) + testassert.Require(t, slices.Equal(*got, row.want), "emitted = %+v, want %+v", *got, row.want) if row.checkOrig != nil { row.checkOrig(t, returned) } @@ -1264,12 +1178,11 @@ func TestObserveStreamFilter(t *testing.T) { t.Run("close delegates once", func(t *testing.T) { inner := streamOf() wrapper, _ := collect(t, inner) - if err := wrapper.Close(); err != nil { - t.Fatalf("Close: %v", err) - } - if inner.closes != 1 { - t.Fatalf("close count = %d, want one delegation", inner.closes) + { + err := wrapper.Close() + testassert.NoError(t, err) } + testassert.Equal(t, inner.closes, 1) }) t.Run("assembly close stays exactly once through the wrapper", func(t *testing.T) { @@ -1277,23 +1190,21 @@ func TestObserveStreamFilter(t *testing.T) { modelFn := func(context.Context, model.Request) (model.Stream, error) { return stream, nil } h, _, c, _ := newEffectHarness(t, modelFn) observeHarness(h) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) - } - if stream.closes != 1 { - t.Fatalf("accepted stream close count = %d, want one", stream.closes) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } + testassert.Equal(t, stream.closes, 1) }) t.Run("eof emits none", func(t *testing.T) { inner := streamOf() wrapper, got := collect(t, inner) - if _, err := wrapper.Recv(); !errors.Is(err, io.EOF) { - t.Fatalf("EOF = %v", err) - } - if len(*got) != 0 { - t.Fatalf("EOF emitted %+v", *got) + { + _, err := wrapper.Recv() + testassert.Require(t, errors.Is(err, io.EOF), "EOF = %v", err) } + testassert.Require(t, len(*got) == 0, "EOF emitted %+v", *got) }) } @@ -1307,9 +1218,11 @@ func TestObserveCompactAttribution(t *testing.T) { col := observeHarness(h) exec := effectExecution(nil, nil) exec.CompactModel = compactFn - effect := h.compactModelEffect(c, testOpID, exec, testCapture(), &usageAccumulator{}) - if _, err := invokeModelEffect(t, effect, nil); err != nil { - t.Fatalf("compact model effect: %v", err) + var reported *UsageCount + effect := h.compactModelEffect(c, testOpID, exec, testCapture(), &reported) + { + _, err := invokeModelEffect(t, effect, defaultModelRequest(t), nil) + testassert.NoError(t, err) } deltas := 0 for _, fact := range col.snapshot() { @@ -1317,13 +1230,9 @@ func TestObserveCompactAttribution(t *testing.T) { continue } deltas++ - if fact.SessionID != sessionID || fact.OperationID != testOpID || fact.Position != 0 || fact.Content != "done" { - t.Fatalf("compact delta fact = %+v, want the enclosing Operation attribution", fact) - } - } - if deltas != 1 { - t.Fatalf("compact text deltas = %d, want one", deltas) + testassert.Require(t, fact.SessionID == sessionID && fact.OperationID == testOpID && fact.Position == 0 && fact.Content == "done", "compact delta fact = %+v, want the enclosing Operation attribution", fact) } + testassert.Equal(t, deltas, 1) } // TestObserveRefusalFacts proves a real accepted refusal stream emits one @@ -1341,8 +1250,9 @@ func TestObserveRefusalFacts(t *testing.T) { } h, _, c, sessionID := newEffectHarness(t, modelFn) col := observeHarness(h) - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } var refusals []HarnessFact for _, fact := range col.snapshot() { @@ -1352,15 +1262,9 @@ func TestObserveRefusalFacts(t *testing.T) { refusals = append(refusals, fact) } want := []string{"I cannot", " help with that."} - if len(refusals) != len(want) { - t.Fatalf("refusal facts = %+v, want one per fragment %v", refusals, want) - } + testassert.Require(t, len(refusals) == len(want), "refusal facts = %+v, want one per fragment %v", refusals, want) for i, fact := range refusals { - if fact.SessionID != sessionID || fact.OperationID != testOpID || fact.Content != want[i] || - fact.Position != 0 || fact.JobID != "" || fact.CallID != "" || fact.Ordinal != 0 || - fact.Name != "" || fact.Status != "" { - t.Fatalf("refusal fact %d = %+v, want the enclosed Operation attribution and no position", i, fact) - } + testassert.Require(t, fact.SessionID == sessionID && fact.OperationID == testOpID && fact.Content == want[i] && fact.Position == 0 && fact.JobID == "" && fact.CallID == "" && fact.Ordinal == 0 && fact.Name == "" && fact.Status == "", "refusal fact %d = %+v, want the enclosed Operation attribution and no position", i, fact) } // Final refusal retention: the committed assistant entry keeps the // fragments joined in arrival order. @@ -1370,14 +1274,10 @@ func TestObserveRefusalFacts(t *testing.T) { if fact.Assistant == nil { continue } - if fact.Assistant.Refusal != "I cannot help with that." { - t.Fatalf("committed assistant refusal = %q, want the joined fragments", fact.Assistant.Refusal) - } + testassert.Equal(t, fact.Assistant.Refusal, "I cannot help with that.") retained = true } - if !retained { - t.Fatalf("no committed assistant fact in %+v", facts) - } + testassert.Require(t, retained, "no committed assistant fact in %+v", facts) } // TestObserveToolLifecycle proves the tool lifecycle: one started fact at the @@ -1389,8 +1289,9 @@ func TestObserveToolLifecycle(t *testing.T) { publishStream := func(t *testing.T, h *Harness, c *coordinator, stream model.Stream) { t.Helper() modelFn := func(context.Context, model.Request) (model.Stream, error) { return stream, nil } - if _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), nil); err != nil { - t.Fatalf("model effect: %v", err) + { + _, err := invokeModelEffect(t, h.modelEffect(c, testOpID, effectExecution(modelFn, nil), testCapture()), defaultModelRequest(t), nil) + testassert.NoError(t, err) } } publishCall := func(t *testing.T, h *Harness, c *coordinator, call model.ToolCall) { @@ -1433,40 +1334,26 @@ func TestObserveToolLifecycle(t *testing.T) { ctx = canceled } got, err := h.toolEffect(c, testOpID, effectExecution(nil, tc.plan), testCapture())(ctx, testToolCall("call-1")) - if err != nil { - t.Fatalf("tool effect: %v", err) - } - if got.Status != tc.status { - t.Fatalf("committed status = %s, want %s", got.Status, tc.status) - } + testassert.NoError(t, err) + testassert.Equal(t, got.Status, tc.status) facts := col.snapshot() var startedAt, finishedAt = -1, -1 for i, fact := range facts { switch fact.Kind { case FactToolStarted: - if startedAt >= 0 { - t.Fatalf("the call started more than once: %+v", facts) - } + testassert.Require(t, startedAt < 0, "the call started more than once: %+v", facts) startedAt = i - if fact.CallID != "call-1" || fact.Ordinal != 0 || fact.Name != "echo" || fact.OperationID != testOpID { - t.Fatalf("started fact = %+v", fact) - } + testassert.Require(t, fact.CallID == "call-1" && fact.Ordinal == 0 && fact.Name == "echo" && fact.OperationID == testOpID, "started fact = %+v", fact) case FactToolFinished: - if finishedAt >= 0 { - t.Fatalf("the call finished more than once: %+v", facts) - } + testassert.Require(t, finishedAt < 0, "the call finished more than once: %+v", facts) finishedAt = i - if fact.CallID != "call-1" || fact.Status != tc.status || fact.OperationID != testOpID { - t.Fatalf("finished fact = %+v, want status %s", fact, tc.status) - } + testassert.Require(t, fact.CallID == "call-1" && fact.Status == tc.status && fact.OperationID == testOpID, "finished fact = %+v, want status %s", fact, tc.status) case FactInvalidation: default: t.Fatalf("unexpected progress kind %q in %+v", fact.Kind, facts) } } - if startedAt < 0 || finishedAt < 0 || startedAt > finishedAt { - t.Fatalf("lifecycle order = started %d finished %d in %+v", startedAt, finishedAt, facts) - } + testassert.Require(t, startedAt >= 0 && finishedAt >= 0 && startedAt <= finishedAt, "lifecycle order = started %d finished %d in %+v", startedAt, finishedAt, facts) }) } }) @@ -1488,13 +1375,9 @@ func TestObserveToolLifecycle(t *testing.T) { publishStream(t, h, c, stream) col := observeHarness(h) got, err := h.toolEffect(c, testOpID, effectExecution(nil, nil), testCapture())(context.Background(), testToolCall("call-1")) - if err != nil || got.Status != model.ResultError { - t.Fatalf("unavailable tool = %+v err %v, want the error result", got, err) - } + testassert.Require(t, err == nil && got.Status == model.ResultError, "unavailable tool = %+v err %v, want the error result", got, err) facts := col.snapshot() - if len(facts) != 3 || facts[0].Kind != FactToolStarted || facts[1].Kind != FactInvalidation || facts[2].Kind != FactToolFinished { - t.Fatalf("advertisement-gate facts = %+v, want started, the result commit, then finished", facts) - } + testassert.Require(t, len(facts) == 3 && facts[0].Kind == FactToolStarted && facts[1].Kind == FactInvalidation && facts[2].Kind == FactToolFinished, "advertisement-gate facts = %+v, want started, the result commit, then finished", facts) }) t.Run("synthetic unstarted result finishes without started", func(t *testing.T) { @@ -1503,12 +1386,14 @@ func TestObserveToolLifecycle(t *testing.T) { successPlan := func(_ context.Context, call model.ToolCall) PreparedTool { return PreparedTool{Permissions: fixturePermission, Immediate: &ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "done"}}} } - if _, err := h.toolEffect(c, testOpID, effectExecution(nil, successPlan), testCapture())(context.Background(), testToolCall("call-1")); err != nil { - t.Fatalf("call-1 dispatch: %v", err) + { + _, err := h.toolEffect(c, testOpID, effectExecution(nil, successPlan), testCapture())(context.Background(), testToolCall("call-1")) + testassert.NoError(t, err) } col := observeHarness(h) - if _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationInterruption, detail: "stopped"}); err != nil { - t.Fatalf("terminal settlement: %v", err) + { + _, err := h.commitEffectResult(context.Background(), c, testOpID, nil, modelResult{terminal: OperationInterruption, detail: "stopped"}) + testassert.NoError(t, err) } facts := col.snapshot() var finished int @@ -1518,14 +1403,10 @@ func TestObserveToolLifecycle(t *testing.T) { t.Fatalf("a synthetic unstarted result emitted a started fact: %+v", fact) case FactToolFinished: finished++ - if fact.CallID != "call-2" || fact.Status != model.ResultInterrupted { - t.Fatalf("synthetic finished fact = %+v", fact) - } + testassert.Require(t, fact.CallID == "call-2" && fact.Status == model.ResultInterrupted, "synthetic finished fact = %+v", fact) } } - if finished != 1 { - t.Fatalf("synthetic finished facts = %d, want one; facts %+v", finished, facts) - } + testassert.Require(t, finished == 1, "synthetic finished facts = %d, want one; facts %+v", finished, facts) }) t.Run("hooks have no progress kind", func(t *testing.T) { @@ -1539,9 +1420,7 @@ func TestObserveToolLifecycle(t *testing.T) { }}} col := observeHarness(h) got, err := h.toolEffect(c, testOpID, exec, testCapture())(context.Background(), testToolCall("call-1")) - if err != nil || got.Status != model.ResultError { - t.Fatalf("hook-settled tool = %+v err %v, want the error result", got, err) - } + testassert.Require(t, err == nil && got.Status == model.ResultError, "hook-settled tool = %+v err %v, want the error result", got, err) var started, finished, invalidations int for _, fact := range col.snapshot() { switch fact.Kind { @@ -1555,9 +1434,8 @@ func TestObserveToolLifecycle(t *testing.T) { t.Fatalf("hook flow emitted progress kind %q", fact.Kind) } } - if started != 1 || finished != 1 || invalidations != 2 { // the hook result and the tool result - t.Fatalf("hook flow counts = started %d finished %d invalidations %d", started, finished, invalidations) - } + // the hook result and the tool result + testassert.Require(t, started == 1 && finished == 1 && invalidations == 2, "hook flow counts = started %d finished %d invalidations %d", started, finished, invalidations) }) } @@ -1566,17 +1444,21 @@ func TestObserveToolLifecycle(t *testing.T) { func TestObserveRematerializationNoHint(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, newPrepareStub(parkedPrepared()).prepare) - leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races - if _, err := h.ReadHistoryView(context.Background(), testSessionID); err != nil { // a real history use under the held lease - t.Fatalf("ReadHistoryView: %v", err) + leaseCoordinator(t, h, testSessionID) // the controlled warm view the foreign write races + // a real history use under the held lease + { + _, err := h.ReadHistoryView(context.Background(), testSessionID) + testassert.NoError(t, err) } col := observeHarness(h) advanceSessionRevision(t, store, testSessionID) // the foreign writer - if _, err := h.ChangeAgentType(context.Background(), testSessionID, "planner"); !errors.Is(err, ErrConflict) { - t.Fatalf("foreign-revision change = %v, want the conflict class", err) + { + _, err := h.ChangeAgentType(context.Background(), testSessionID, "planner") + testassert.Require(t, errors.Is(err, ErrConflict), "foreign-revision change = %v, want the conflict class", err) } - if got := col.snapshot(); len(got) != 0 { - t.Fatalf("rematerialization emitted %+v", got) + { + got := col.snapshot() + testassert.Require(t, len(got) == 0, "rematerialization emitted %+v", got) } } @@ -1591,46 +1473,51 @@ func TestObserveRestartNoReplay(t *testing.T) { _ = h.Wait(context.Background()) }() col := observeHarness(h) - if _, err := h.Submit(context.Background(), SubmitRequest{ - SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, - Content: admissionContent("hello"), Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("Submit: %v", err) + { + _, err := h.Submit(context.Background(), SubmitRequest{ + SessionID: testSessionID, OperationID: testOpID, Origin: InputOriginUser, + Content: admissionContent("hello"), Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } awaitHarnessQuiet(t, h, testSessionID) - if len(col.snapshot()) == 0 { - t.Fatalf("the first owner emitted no facts") - } + testassert.Require(t, len(col.snapshot()) != 0, "the first owner emitted no facts") cancel() - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } - if err := Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := Recover(context.Background(), store) + testassert.NoError(t, err) } sink := &factCollector{} restarted, err := New(context.Background(), Dependencies{Storage: store, Prepare: stub.prepare, Observe: sink.observe}) - if err != nil { - t.Fatalf("New: %v", err) - } - if _, err := restarted.ReadSessionHeader(context.Background(), testSessionID); err != nil { - t.Fatalf("ReadSession: %v", err) + testassert.NoError(t, err) + { + _, err := restarted.ReadSessionHeader(context.Background(), testSessionID) + testassert.NoError(t, err) } - if _, err := restarted.ReadHistoryView(context.Background(), testSessionID); err != nil { - t.Fatalf("ReadHistoryView: %v", err) + { + _, err := restarted.ReadHistoryView(context.Background(), testSessionID) + testassert.NoError(t, err) } - if _, err := restarted.ListSessions(context.Background()); err != nil { - t.Fatalf("ListSessions: %v", err) + { + _, err := restarted.ListSessions(context.Background()) + testassert.NoError(t, err) } - if got := sink.snapshot(); len(got) != 0 { - t.Fatalf("the new owner's reads replayed %+v", got) + { + got := sink.snapshot() + testassert.Require(t, len(got) == 0, "the new owner's reads replayed %+v", got) } - if _, err := restarted.ChangeAgentType(context.Background(), testSessionID, "planner"); err != nil { - t.Fatalf("ChangeAgentType: %v", err) + { + _, err := restarted.ChangeAgentType(context.Background(), testSessionID, "planner") + testassert.NoError(t, err) } - if got := sink.invalidations(); len(got) != 1 { - t.Fatalf("the new owner's first publication emitted %+v", got) + { + got := sink.invalidations() + testassert.Require(t, len(got) == 1, "the new owner's first publication emitted %+v", got) } } @@ -1664,71 +1551,67 @@ func (s *observationCountingStorage) ReadRegisters(ctx context.Context, sessionI func TestReadObservation(t *testing.T) { t.Run("valid coordinator with a released graph", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(validPrepared()).prepare) - if _, err := h.ReadSessionView(context.Background(), testSessionID); err != nil { - t.Fatalf("ReadSessionView: %v", err) + { + _, err := h.ReadSessionView(context.Background(), testSessionID) + testassert.NoError(t, err) } cell := cachedCoordinator(t, h, testSessionID) cell.mu.Lock() graphless := cell.graph == nil cell.mu.Unlock() - if !graphless { - t.Fatalf("the read's graph stayed resident, want the released state") - } + testassert.Require(t, graphless, "the read's graph stayed resident, want the released state") identity, revision, ok := h.ReadObservation(testSessionID) - if !ok { - t.Fatal("ReadObservation on a released established coordinator reported unavailable") - } - if identity.SessionID != testSessionID { - t.Fatalf("identity session = %q, want %q", identity.SessionID, testSessionID) - } - if identity.Workspace == "" { - t.Fatal("identity carries no workspace") - } - if want := snapshotPair(t, h, testSessionID); revision != want { - t.Fatalf("pair = %+v, want the current pair %+v", revision, want) + testassert.Require(t, ok, "%v", "ReadObservation on a released established coordinator reported unavailable") + testassert.Require(t, identity.SessionID == testSessionID && identity.Workspace != "", "got %v, want %v; %v", identity.SessionID, testSessionID, "identity carries no workspace") + { + want := snapshotPair(t, h, testSessionID) + testassert.Equal(t, revision, want) } }) t.Run("never materialized performs no storage work", func(t *testing.T) { store := &observationCountingStorage{Storage: freshSessionStore(t)} h := newTestHarness(t, store, newPrepareStub(validPrepared()).prepare) - if _, _, ok := h.ReadObservation(testSessionID); ok { - t.Fatal("ReadObservation on a never-materialized Session reported available") - } - if store.calls != 0 { - t.Fatalf("storage calls on a never-materialized read = %d, want zero", store.calls) + { + _, _, ok := h.ReadObservation(testSessionID) + testassert.Require(t, !ok, "%v", "ReadObservation on a never-materialized Session reported available") } + testassert.Equal(t, store.calls, 0) // The read left the registry cold: a repeat miss performs no work and // the Session stays unmaterialized. - if _, _, ok := h.ReadObservation(testSessionID); ok { - t.Fatal("repeat ReadObservation materialized the Session") - } - if store.calls != 0 { - t.Fatalf("storage calls after repeat = %d, want zero", store.calls) + { + _, _, ok := h.ReadObservation(testSessionID) + testassert.Require(t, !ok, "%v", "repeat ReadObservation materialized the Session") } + testassert.Equal(t, store.calls, 0) }) t.Run("deleted", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(validPrepared()).prepare) - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if err := h.DeleteSession(context.Background(), testSessionID); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := h.DeleteSession(context.Background(), testSessionID) + testassert.NoError(t, err) } - if _, _, ok := h.ReadObservation(testSessionID); ok { - t.Fatal("ReadObservation after deletion reported available") + { + _, _, ok := h.ReadObservation(testSessionID) + testassert.Require(t, !ok, "%v", "ReadObservation after deletion reported available") } }) t.Run("corrupt", func(t *testing.T) { h := newTestHarness(t, freshSessionStore(t), newPrepareStub(parkedPrepared()).prepare) - if _, err := h.ReadSessionHeader(context.Background(), testSessionID); err != nil { - t.Fatalf("ReadSession: %v", err) + { + _, err := h.ReadSessionHeader(context.Background(), testSessionID) + testassert.NoError(t, err) } h.markCorrupt(testSessionID, corruptSession(testSessionID, "injected corruption")) - if _, _, ok := h.ReadObservation(testSessionID); ok { - t.Fatal("ReadObservation on a corrupt Session reported available") + { + _, _, ok := h.ReadObservation(testSessionID) + testassert.Require(t, !ok, "%v", "ReadObservation on a corrupt Session reported available") } }) } diff --git a/harness/payload.go b/harness/payload.go index 2ee4691f..1d799f69 100644 --- a/harness/payload.go +++ b/harness/payload.go @@ -1003,21 +1003,23 @@ func encodeOperationSettlementEntry(v operationSettlementEntry) (json.RawMessage modelRaw = raw } wire, err := json.Marshal(struct { - SessionID string `json:"session_id"` - EntryID string `json:"entry_id"` - OperationID string `json:"operation_id"` - Status string `json:"status"` - Detail string `json:"detail,omitempty"` - Model json.RawMessage `json:"model,omitempty"` - Usage *UsageCount `json:"usage,omitempty"` + SessionID string `json:"session_id"` + EntryID string `json:"entry_id"` + OperationID string `json:"operation_id"` + Status string `json:"status"` + Detail string `json:"detail,omitempty"` + Model json.RawMessage `json:"model,omitempty"` + Usage *UsageCount `json:"usage,omitempty"` + CompactionUsage bool `json:"compaction_usage,omitempty"` }{ - SessionID: v.SessionID, - EntryID: v.EntryID, - OperationID: v.OperationID, - Status: string(v.Status), - Detail: v.Detail, - Model: modelRaw, - Usage: v.Usage, + SessionID: v.SessionID, + EntryID: v.EntryID, + OperationID: v.OperationID, + Status: string(v.Status), + Detail: v.Detail, + Model: modelRaw, + Usage: v.Usage, + CompactionUsage: v.CompactionUsage, }) if err != nil { return nil, err @@ -1036,7 +1038,7 @@ func decodeOperationSettlementEntry(env Entry) (operationSettlementEntry, error) if err != nil { return operationSettlementEntry{}, err } - if err := rejectUnknownMembers(obj, "session_id", "entry_id", "operation_id", "status", "detail", "model", "usage"); err != nil { + if err := rejectUnknownMembers(obj, "session_id", "entry_id", "operation_id", "status", "detail", "model", "usage", "compaction_usage"); err != nil { return operationSettlementEntry{}, err } sessionID, err := stringMember(obj, "session_id", true) @@ -1083,6 +1085,10 @@ func decodeOperationSettlementEntry(env Entry) (operationSettlementEntry, error) } usage = &counts } + compactionUsage, err := boolMember(obj, "compaction_usage", false) + if err != nil { + return operationSettlementEntry{}, err + } if sessionID != env.SessionID { return operationSettlementEntry{}, fmt.Errorf("payload session id %q does not agree with the envelope", sessionID) } @@ -1093,13 +1099,14 @@ func decodeOperationSettlementEntry(env Entry) (operationSettlementEntry, error) return operationSettlementEntry{}, fmt.Errorf("payload operation id %q does not agree with the envelope", operationID) } v := operationSettlementEntry{ - SessionID: sessionID, - EntryID: entryID, - OperationID: operationID, - Status: OperationState(status), - Detail: detail, - Model: ref, - Usage: usage, + SessionID: sessionID, + EntryID: entryID, + OperationID: operationID, + Status: OperationState(status), + Detail: detail, + Model: ref, + Usage: usage, + CompactionUsage: compactionUsage, } if err := validateOperationSettlementEntry(v); err != nil { return operationSettlementEntry{}, err @@ -1795,15 +1802,11 @@ func encodeExecutionCapture(v ExecutionCapture) (json.RawMessage, error) { return nil, err } compactRaw, err := json.Marshal(struct { - Model json.RawMessage `json:"model"` - ContextWindow int `json:"context_window"` - OutputReserve int `json:"output_reserve"` - SystemPrompt string `json:"system_prompt"` + Model json.RawMessage `json:"model"` + SystemPrompt string `json:"system_prompt"` }{ - Model: compactModelRaw, - ContextWindow: v.Compact.ContextWindow, - OutputReserve: v.Compact.OutputReserve, - SystemPrompt: v.Compact.SystemPrompt, + Model: compactModelRaw, + SystemPrompt: v.Compact.SystemPrompt, }) if err != nil { return nil, err @@ -2082,10 +2085,10 @@ func decodeExecutionCapture(obj map[string]json.RawMessage) (ExecutionCapture, e } // decodeCompactCapture reads the durable compaction configuration's exact -// keys: the durable two-field model identity, window, reserve, and system -// prompt. Their values are validated by the enclosing capture validation. +// keys: the durable two-field model identity and the system prompt. Their +// values are validated by the enclosing capture validation. func decodeCompactCapture(obj map[string]json.RawMessage) (CompactCapture, error) { - if err := rejectUnknownMembers(obj, "model", "context_window", "output_reserve", "system_prompt"); err != nil { + if err := rejectUnknownMembers(obj, "model", "system_prompt"); err != nil { return CompactCapture{}, err } modelObj, err := objectMember(obj, "model", true) @@ -2096,19 +2099,11 @@ func decodeCompactCapture(obj map[string]json.RawMessage) (CompactCapture, error if err != nil { return CompactCapture{}, fmt.Errorf("member %q: %w", "model", err) } - contextWindow, err := int64Member(obj, "context_window", true) - if err != nil { - return CompactCapture{}, err - } - outputReserve, err := int64Member(obj, "output_reserve", true) - if err != nil { - return CompactCapture{}, err - } systemPrompt, err := stringMember(obj, "system_prompt", true) if err != nil { return CompactCapture{}, err } - v := CompactCapture{Model: ref, ContextWindow: int(contextWindow), OutputReserve: int(outputReserve), SystemPrompt: systemPrompt} + v := CompactCapture{Model: ref, SystemPrompt: systemPrompt} return v, nil } @@ -2152,18 +2147,11 @@ func validateExecutionCapture(v ExecutionCapture) error { } // validateCompactCapture enforces the compaction configuration's closed -// shape: a complete model identity, positive window and reserve, and a -// non-empty compact system prompt. +// shape: a complete model identity and a non-empty compact system prompt. func validateCompactCapture(v CompactCapture) error { if v.Model.Provider == "" || v.Model.Model == "" { return fmt.Errorf("model %q must be a complete model identity", v.Model.String()) } - if v.ContextWindow <= 0 { - return fmt.Errorf("context_window %d must be positive", v.ContextWindow) - } - if v.OutputReserve <= 0 { - return fmt.Errorf("output_reserve %d must be positive", v.OutputReserve) - } if v.SystemPrompt == "" { return errors.New("system_prompt must be non-empty") } diff --git a/harness/permission_test.go b/harness/permission_test.go index d0de64fd..3d0b9022 100644 --- a/harness/permission_test.go +++ b/harness/permission_test.go @@ -7,6 +7,8 @@ import ( "reflect" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // isBuiltInOnly reports whether a resolved policy carries no user-level state @@ -121,8 +123,9 @@ func TestResolvePermissionPolicyFallbackChain(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - if got := policy(tc.global, tc.workspe).evaluate(tc.root, tc.req); got != tc.want { - t.Errorf("evaluate(%q, %+v) = %v, want %v", tc.root, tc.req, got, tc.want) + { + got := policy(tc.global, tc.workspe).evaluate(tc.root, tc.req) + testassert.Check(t, got == tc.want, "evaluate(%q, %+v) = %v, want %v", tc.root, tc.req, got, tc.want) } }) } @@ -136,9 +139,7 @@ func TestResolvePermissionPolicyBuiltins(t *testing.T) { for _, path := range inside { t.Run("in-workspace file allowed "+path, func(t *testing.T) { for _, permission := range []string{PermissionFileRead, PermissionFileWrite} { - if !policy.evaluate("/w", PermissionRequest{permission, path}) { - t.Errorf("built-in %s of %s denied, want allow", permission, path) - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{permission, path}), "built-in %s of %s denied, want allow", permission, path) } }) } @@ -146,16 +147,12 @@ func TestResolvePermissionPolicyBuiltins(t *testing.T) { for _, path := range outside { t.Run("outside-workspace file denied "+path, func(t *testing.T) { for _, permission := range []string{PermissionFileRead, PermissionFileWrite} { - if policy.evaluate("/w", PermissionRequest{permission, path}) { - t.Errorf("built-in %s of %s allowed, want deny", permission, path) - } + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{permission, path}), "built-in %s of %s allowed, want deny", permission, path) } }) } t.Run("workspace root itself is inside", func(t *testing.T) { - if !policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/w"}) { - t.Error("built-in file.read of the root denied, want allow") - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/w"}), "%v", "built-in file.read of the root denied, want allow") }) t.Run("plan-asserted sensitive versus sensitive-looking pairs", func(t *testing.T) { type pair struct { @@ -167,12 +164,8 @@ func TestResolvePermissionPolicyBuiltins(t *testing.T) { {"/w/.env.cache/README.md", "/w/sub/.env.cache"}, {"/w/credentials-cache/data.json", "/w/sub/credentials-cache.json"}, } { - if !policy.evaluate("/w", PermissionRequest{permission, p.allow}) { - t.Errorf("built-in %s of %s denied, want allow", permission, p.allow) - } - if policy.evaluate("/w", PermissionRequest{permission, p.deny}) { - t.Errorf("built-in %s of %s allowed, want deny", permission, p.deny) - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{permission, p.allow}), "built-in %s of %s denied, want allow", permission, p.allow) + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{permission, p.deny}), "built-in %s of %s allowed, want deny", permission, p.deny) } } }) @@ -223,69 +216,45 @@ func TestResolvePermissionPolicyBuiltins(t *testing.T) { dir := strings.ReplaceAll(base, "*", "x") for _, permission := range []string{PermissionFileRead, PermissionFileWrite} { target := "/w/" + dir + "/f" - if !policy.evaluate("/w", PermissionRequest{permission, target}) { - t.Errorf("built-in %s of %s denied, want allow", permission, target) - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{permission, target}), "built-in %s of %s denied, want allow", permission, target) nested := "/w/" + dir + "/sub/" + base - if policy.evaluate("/w", PermissionRequest{permission, nested}) { - t.Errorf("built-in %s of %s allowed, want deny", permission, nested) - } + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{permission, nested}), "built-in %s of %s allowed, want deny", permission, nested) } } }) t.Run("literal glob characters in a real path are plain data", func(t *testing.T) { for _, base := range []string{"*", "?", "[x]", "a?b", "x[1].txt"} { - if !policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/w/" + base}) { - t.Errorf("built-in file.read of %s denied, want allow", base) - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/w/" + base}), "built-in file.read of %s denied, want allow", base) } }) t.Run("root path metacharacters stay literal path data", func(t *testing.T) { root := "/w/[x]" - if !policy.evaluate(root, PermissionRequest{PermissionFileRead, root + "/a.txt"}) { - t.Error("file under a metacharacter root denied, want allow") - } - if !policy.evaluate(root, PermissionRequest{PermissionWorkspaceInspect, root}) { - t.Error("inspect of the metacharacter root denied, want allow") - } - if policy.evaluate(root, PermissionRequest{PermissionWorkspaceInspect, "/w/y"}) { - t.Error("inspect of a different root allowed, want deny") - } + testassert.Check(t, policy.evaluate(root, PermissionRequest{PermissionFileRead, root + "/a.txt"}), "%v", "file under a metacharacter root denied, want allow") + testassert.Check(t, policy.evaluate(root, PermissionRequest{PermissionWorkspaceInspect, root}), "%v", "inspect of the metacharacter root denied, want allow") + testassert.Check(t, !policy.evaluate(root, PermissionRequest{PermissionWorkspaceInspect, "/w/y"}), "%v", "inspect of a different root allowed, want deny") }) t.Run("command run allows any canonical command target", func(t *testing.T) { for _, target := range []string{"*", "git push origin main", "rm -rf /tmp/x", "echo\nls"} { - if !policy.evaluate("/w", PermissionRequest{PermissionCommandRun, target}) { - t.Errorf("built-in command.run of %q denied, want allow", target) - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{PermissionCommandRun, target}), "built-in command.run of %q denied, want allow", target) } }) t.Run("workspace inspect allows only the canonical root", func(t *testing.T) { - if !policy.evaluate("/w", PermissionRequest{PermissionWorkspaceInspect, "/w"}) { - t.Error("built-in inspect of the root denied, want allow") - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{PermissionWorkspaceInspect, "/w"}), "%v", "built-in inspect of the root denied, want allow") for _, target := range []string{"/w/keys", "/etc", "*", ""} { - if policy.evaluate("/w", PermissionRequest{PermissionWorkspaceInspect, target}) { - t.Errorf("built-in inspect of %q allowed, want deny", target) - } + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{PermissionWorkspaceInspect, target}), "built-in inspect of %q allowed, want deny", target) } }) t.Run("sleep allows the fixed target only", func(t *testing.T) { - if !policy.evaluate("/w", PermissionRequest{PermissionSleep, "*"}) { - t.Error("built-in sleep of the fixed target denied, want allow") - } + testassert.Check(t, policy.evaluate("/w", PermissionRequest{PermissionSleep, "*"}), "%v", "built-in sleep of the fixed target denied, want allow") for _, target := range []string{"1", ""} { - if policy.evaluate("/w", PermissionRequest{PermissionSleep, target}) { - t.Errorf("built-in sleep of %q allowed, want deny", target) - } + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{PermissionSleep, target}), "built-in sleep of %q allowed, want deny", target) } }) t.Run("uncovered future permission namespaces deny", func(t *testing.T) { for _, permission := range []string{ "process.list", "process.read", "process.stop", "agent.start", "owned.process.list", } { - if policy.evaluate("/w", PermissionRequest{permission, "*"}) { - t.Errorf("built-in %s allowed, want deny", permission) - } + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{permission, "*"}), "built-in %s allowed, want deny", permission) } }) } @@ -301,9 +270,7 @@ func TestBuiltInAllowsChildLaunchAndProcessOwn(t *testing.T) { } for _, req := range concrete { t.Run("built-in "+req.permission+" of "+req.target, func(t *testing.T) { - if !builtIn.evaluate("/w", PermissionRequest{req.permission, req.target}) { - t.Errorf("built-in %s of %q denied, want allow", req.permission, req.target) - } + testassert.Check(t, builtIn.evaluate("/w", PermissionRequest{req.permission, req.target}), "built-in %s of %q denied, want allow", req.permission, req.target) }) } denied := ResolvePermissionPolicy(nil, json.RawMessage(levelJSON(rulesJSON( @@ -312,9 +279,7 @@ func TestBuiltInAllowsChildLaunchAndProcessOwn(t *testing.T) { )))) for _, req := range concrete { t.Run("user deny decides "+req.permission+" of "+req.target, func(t *testing.T) { - if denied.evaluate("/w", PermissionRequest{req.permission, req.target}) { - t.Errorf("user deny did not decide %s of %q: allowed, want deny", req.permission, req.target) - } + testassert.Check(t, !denied.evaluate("/w", PermissionRequest{req.permission, req.target}), "user deny did not decide %s of %q: allowed, want deny", req.permission, req.target) }) } } @@ -351,11 +316,10 @@ func TestPermissionGlobMatcher(t *testing.T) { for _, tc := range globs { t.Run(tc.name, func(t *testing.T) { glob, err := compileGlob(tc.pattern) - if err != nil { - t.Fatalf("compileGlob(%q): %v", tc.pattern, err) - } - if got := glob.MatchString(tc.target); got != tc.want { - t.Errorf("glob %q match %q = %v, want %v", tc.pattern, tc.target, got, tc.want) + testassert.Require(t, err == nil, "compileGlob(%q): %v", tc.pattern, err) + { + got := glob.MatchString(tc.target) + testassert.Check(t, got == tc.want, "glob %q match %q = %v, want %v", tc.pattern, tc.target, got, tc.want) } }) } @@ -364,21 +328,19 @@ func TestPermissionGlobMatcher(t *testing.T) { ruleJSON(PermissionFileRead, "*", permissionAccessAllow), ruleJSON(PermissionFileRead, "/w/docs/*", permissionAccessDeny), )))) - if policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/w/docs/a.txt"}) { - t.Error("the complete-target deny rule did not match its canonical target") - } - if !policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/etc/docs/a.txt"}) { - t.Error("the complete-target deny rule matched outside its pattern") - } + testassert.Check(t, !policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/w/docs/a.txt"}), "%v", "the complete-target deny rule did not match its canonical target") + testassert.Check(t, policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/etc/docs/a.txt"}), "%v", "the complete-target deny rule matched outside its pattern") }) } func TestCompileGlobRejectsTrailingBackslash(t *testing.T) { - if _, err := compileGlob(`/w/sub\`); err == nil { - t.Fatal("compileGlob accepted a trailing backslash") + { + _, err := compileGlob(`/w/sub\`) + testassert.Require(t, err != nil, "%v", "compileGlob accepted a trailing backslash") } - if _, err := compileGlob(`\`); err == nil { - t.Fatal("compileGlob accepted a lone backslash") + { + _, err := compileGlob(`\`) + testassert.Require(t, err != nil, "%v", "compileGlob accepted a lone backslash") } } @@ -432,17 +394,16 @@ func TestResolvePermissionPolicyMalformedDiscardsBothLevels(t *testing.T) { ResolvePermissionPolicy(malformed, valid), ResolvePermissionPolicy(valid, malformed), } { - if !isBuiltInOnly(policy) { - t.Errorf("malformed %s did not discard both levels: %+v", tc.name, policy) - } + testassert.Check(t, isBuiltInOnly(policy), "malformed %s did not discard both levels: %+v", tc.name, policy) } }) } } func TestResolvePermissionPolicyValidShapes(t *testing.T) { - if got := ResolvePermissionPolicy(nil, nil); !isBuiltInOnly(got) { - t.Errorf("absent levels resolved to %+v, want the zero built-in policy", got) + { + got := ResolvePermissionPolicy(nil, nil) + testassert.Check(t, isBuiltInOnly(got), "absent levels resolved to %+v, want the zero built-in policy", got) } outsideAllow := json.RawMessage(levelJSON(rulesJSON( ruleJSON(PermissionFileRead, "/elsewhere/*", permissionAccessAllow), @@ -450,14 +411,8 @@ func TestResolvePermissionPolicyValidShapes(t *testing.T) { workspaceDeny := json.RawMessage(levelJSON(rulesJSON( ruleJSON(PermissionFileRead, "/w/private/*", permissionAccessDeny), ))) - if !ResolvePermissionPolicy(outsideAllow, workspaceDeny).evaluate("/w", - PermissionRequest{PermissionFileRead, "/elsewhere/x"}) { - t.Error("valid global level lost when a valid workspace block is present") - } - if ResolvePermissionPolicy(outsideAllow, workspaceDeny).evaluate("/w", - PermissionRequest{PermissionFileRead, "/w/private/x"}) { - t.Error("valid workspace level lost when a valid global block is present") - } + testassert.Check(t, ResolvePermissionPolicy(outsideAllow, workspaceDeny).evaluate("/w", PermissionRequest{PermissionFileRead, "/elsewhere/x"}), "%v", "valid global level lost when a valid workspace block is present") + testassert.Check(t, !ResolvePermissionPolicy(outsideAllow, workspaceDeny).evaluate("/w", PermissionRequest{PermissionFileRead, "/w/private/x"}), "%v", "valid workspace level lost when a valid global block is present") // A malformed Workspace block affects only resolutions carrying it: the // same global level still decides for a valid Workspace sibling. for _, tc := range []struct { @@ -470,37 +425,30 @@ func TestResolvePermissionPolicyValidShapes(t *testing.T) { t.Run(tc.name, func(t *testing.T) { policy := ResolvePermissionPolicy(outsideAllow, tc.workspace) want := tc.name != "malformed workspace block" - if got := policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/elsewhere/x"}); got != want { - t.Errorf("global decision under %s = %v, want %v", tc.name, got, want) + { + got := policy.evaluate("/w", PermissionRequest{PermissionFileRead, "/elsewhere/x"}) + testassert.Check(t, got == want, "global decision under %s = %v, want %v", tc.name, got, want) } }) } emptyRules := json.RawMessage(levelJSON(rulesJSON())) - if got := ResolvePermissionPolicy(emptyRules, nil); !isBuiltInOnly(got) { - t.Errorf("empty rules list resolved to %+v, want the zero built-in policy", got) + { + got := ResolvePermissionPolicy(emptyRules, nil) + testassert.Check(t, isBuiltInOnly(got), "empty rules list resolved to %+v, want the zero built-in policy", got) } // Duplicate members resolve by encoding/json's ordinary last-value // decoding, not a second duplicate-key parser. duplicates := json.RawMessage(`{"default":"deny","default":"allow","rules":[],"rules":[]}`) - if !ResolvePermissionPolicy(nil, duplicates).evaluate("/w", - PermissionRequest{PermissionFileRead, "/elsewhere/x"}) { - t.Error("last duplicate default did not win") - } + testassert.Check(t, ResolvePermissionPolicy(nil, duplicates).evaluate("/w", PermissionRequest{PermissionFileRead, "/elsewhere/x"}), "%v", "last duplicate default did not win") lastRule := json.RawMessage(levelJSON(rulesJSON( ruleJSON(PermissionFileRead, "/w/private/*", permissionAccessDeny), ruleJSON(PermissionFileRead, "/w/private/*", permissionAccessAllow), ))) - if !ResolvePermissionPolicy(nil, lastRule).evaluate("/w", - PermissionRequest{PermissionFileRead, "/w/private/x"}) { - t.Error("last duplicate rule did not win") - } + testassert.Check(t, ResolvePermissionPolicy(nil, lastRule).evaluate("/w", PermissionRequest{PermissionFileRead, "/w/private/x"}), "%v", "last duplicate rule did not win") // Whitespace before the top-level value is ordinary JSON, not trailing // garbage. padded := json.RawMessage(" \n\t" + levelJSON(`"default":"allow"`)) - if !ResolvePermissionPolicy(padded, nil).evaluate("/w", - PermissionRequest{PermissionSleep, "1"}) { - t.Error("whitespace-padded block did not parse as a valid level") - } + testassert.Check(t, ResolvePermissionPolicy(padded, nil).evaluate("/w", PermissionRequest{PermissionSleep, "1"}), "%v", "whitespace-padded block did not parse as a valid level") } func TestPermissionPolicyCallAllowedAllPairs(t *testing.T) { @@ -533,8 +481,9 @@ func TestPermissionPolicyCallAllowedAllPairs(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - if got := policy.callAllowed("/w", tc.requests); got != tc.want { - t.Errorf("callAllowed(%+v) = %v, want %v", tc.requests, got, tc.want) + { + got := policy.callAllowed("/w", tc.requests) + testassert.Check(t, got == tc.want, "callAllowed(%+v) = %v, want %v", tc.requests, got, tc.want) } }) } @@ -542,13 +491,15 @@ func TestPermissionPolicyCallAllowedAllPairs(t *testing.T) { func TestPermissionLevelDecideSemantics(t *testing.T) { level := permissionLevel{} - if _, decided := level.decide(PermissionRequest{PermissionFileRead, "/w/a"}); decided { - t.Error("empty level without a default decided a request") + { + _, decided := level.decide(PermissionRequest{PermissionFileRead, "/w/a"}) + testassert.Check(t, !decided, "%v", "empty level without a default decided a request") } level.defaultAllow = true level.hasDefault = true - if allow, decided := level.decide(PermissionRequest{PermissionFileRead, "/w/a"}); !decided || !allow { - t.Errorf("level default did not decide: allow=%v decided=%v", allow, decided) + { + allow, decided := level.decide(PermissionRequest{PermissionFileRead, "/w/a"}) + testassert.Check(t, decided && allow, "level default did not decide: allow=%v decided=%v", allow, decided) } glob := mustCompileGlob("/w/*") level.rules = []permissionRule{ @@ -556,14 +507,17 @@ func TestPermissionLevelDecideSemantics(t *testing.T) { {permission: PermissionFileWrite, glob: glob, allow: true}, {permission: PermissionFileRead, glob: glob, allow: true}, } - if allow, decided := level.decide(PermissionRequest{PermissionFileRead, "/w/a"}); !decided || !allow { - t.Errorf("last matching rule did not decide over the level default: allow=%v decided=%v", allow, decided) + { + allow, decided := level.decide(PermissionRequest{PermissionFileRead, "/w/a"}) + testassert.Check(t, decided && allow, "last matching rule did not decide over the level default: allow=%v decided=%v", allow, decided) } - if allow, decided := level.decide(PermissionRequest{PermissionCommandRun, "x"}); !decided || !allow { - t.Errorf("level default did not decide an unmatched permission: allow=%v decided=%v", allow, decided) + { + allow, decided := level.decide(PermissionRequest{PermissionCommandRun, "x"}) + testassert.Check(t, decided && allow, "level default did not decide an unmatched permission: allow=%v decided=%v", allow, decided) } - if allow, decided := level.decide(PermissionRequest{PermissionFileRead, "/other/a"}); !decided || !allow { - t.Errorf("level default did not decide an unmatched target: allow=%v decided=%v", allow, decided) + { + allow, decided := level.decide(PermissionRequest{PermissionFileRead, "/other/a"}) + testassert.Check(t, decided && allow, "level default did not decide an unmatched target: allow=%v decided=%v", allow, decided) } } @@ -580,17 +534,12 @@ func TestPermissionNamesKeepPublishedLiterals(t *testing.T) { PermissionChildLaunch: "child.launch", PermissionProcessOwn: "process.own", } - if len(want) != 7 { // distinct literals: a duplicated value would collapse the map. - t.Fatalf("published permission vocabulary carries %d distinct literals, want 7", len(want)) - } + // distinct literals: a duplicated value would collapse the map. + testassert.Equal(t, len(want), 7) for name, literal := range want { - if name != literal { - t.Errorf("permission constant %q = %q, want the published literal", name, literal) - } - } - if PermissionDeniedContent != "Permission denied." { - t.Errorf("PermissionDeniedContent = %q, want the published denial content", PermissionDeniedContent) + testassert.Check(t, name == literal, "permission constant %q = %q, want the published literal", name, literal) } + testassert.Check(t, PermissionDeniedContent == "Permission denied.", "PermissionDeniedContent = %q, want the published denial content", PermissionDeniedContent) } // readmePermissionsDocument extracts the one fenced JSON document in README.md @@ -598,9 +547,7 @@ func TestPermissionNamesKeepPublishedLiterals(t *testing.T) { func readmePermissionsDocument(t *testing.T) []byte { t.Helper() data, err := os.ReadFile(filepath.Join("..", "README.md")) - if err != nil { - t.Fatalf("read README.md: %v", err) - } + testassert.NoError(t, err) lines := strings.Split(string(data), "\n") for i := 0; i < len(lines); i++ { if strings.TrimSpace(lines[i]) != "```json" { @@ -634,16 +581,13 @@ func TestReadmePermissionsExampleAppliesItsRules(t *testing.T) { var doc struct { Permissions json.RawMessage `json:"permissions"` } - if err := json.Unmarshal(block, &doc); err != nil { - t.Fatalf("README permissions example is not a JSON document: %v", err) - } - if len(doc.Permissions) == 0 { - t.Fatal("README permissions example carries no permissions member") + { + err := json.Unmarshal(block, &doc) + testassert.NoError(t, err) } + testassert.Require(t, len(doc.Permissions) != 0, "%v", "README permissions example carries no permissions member") policy := ResolvePermissionPolicy(doc.Permissions, nil) - if isBuiltInOnly(policy) { - t.Fatal("README permissions example fell back to the built-in policy instead of parsing") - } + testassert.Require(t, !isBuiltInOnly(policy), "%v", "README permissions example fell back to the built-in policy instead of parsing") for _, tc := range []struct { name string req PermissionRequest @@ -655,8 +599,9 @@ func TestReadmePermissionsExampleAppliesItsRules(t *testing.T) { {"unmatched read falls to the example default deny", PermissionRequest{PermissionFileRead, "/w/plain.txt"}, false}, } { t.Run(tc.name, func(t *testing.T) { - if got := policy.evaluate("/w", tc.req); got != tc.want { - t.Errorf("README example %s = %v, want %v", tc.name, got, tc.want) + { + got := policy.evaluate("/w", tc.req) + testassert.Check(t, got == tc.want, "README example %s = %v, want %v", tc.name, got, tc.want) } }) } @@ -667,12 +612,8 @@ func TestReadmePermissionsExampleAppliesItsRules(t *testing.T) { // extracted rule can deny the target. The full-policy row above stays // masked by the default by design; this observation is not. generatedWrite := PermissionRequest{PermissionFileWrite, "/w/generated/x.go"} - if !(PermissionPolicy{}).evaluate("/w", generatedWrite) { - t.Fatal("built-in control: a plain workspace write must be allowed without any user level") - } + testassert.Require(t, (PermissionPolicy{}).evaluate("/w", generatedWrite), "%v", "built-in control: a plain workspace write must be allowed without any user level") ruleOnly := policy ruleOnly.global.hasDefault = false - if ruleOnly.evaluate("/w", generatedWrite) { - t.Error("extracted generated-file deny rule did not deny its target once the example default was cleared") - } + testassert.Check(t, !ruleOnly.evaluate("/w", generatedWrite), "%v", "extracted generated-file deny rule did not deny its target once the example default was cleared") } diff --git a/harness/public_test.go b/harness/public_test.go index 18cf945b..59945d4c 100644 --- a/harness/public_test.go +++ b/harness/public_test.go @@ -28,6 +28,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -47,10 +48,8 @@ func publicCapture() harness.ExecutionCapture { Parameters: json.RawMessage(`{"type":"object"}`), }}, Compact: harness.CompactCapture{ - Model: publicModelRef, - ContextWindow: 4096, - OutputReserve: 2048, - SystemPrompt: "summarize", + Model: publicModelRef, + SystemPrompt: "summarize", }, } } @@ -240,7 +239,8 @@ func (s *scriptModel) releaseGate() { // publicFixture wires one Harness over the given store with a scripted // preparation callback: every admission receives the fixture's prepared // execution (or the per-call prepare hook's result) and signals the -// preparation arrival. The model effect defaults to the script's own effect. +// preparation arrival. The conversation effect defaults to the script's own +// effect and the compact callback to the resolved conversation callback. type publicFixture struct { h *harness.Harness store harness.Storage @@ -259,24 +259,33 @@ type publicFixture struct { // prepareHook, when non-nil, answers one preparation call with its own // result or error; the call index counts from zero. prepareHook func(call int, req harness.PreparationRequest) (harness.PreparedExecution, error) + + // defaultPrep answers a preparation call when no hook is installed: the + // fixture's capture over the resolved conversation and compact callbacks. + // Consumers with a different capture set it before any admission. + defaultPrep harness.PreparedExecution } func newPublicFixture(t *testing.T, store harness.Storage, script *scriptModel, modelFn func(context.Context, model.Request) (model.Stream, error)) *publicFixture { - return newPublicFixtureWithJobs(t, store, nil, script, modelFn) + return newPublicFixtureWithJobs(t, store, nil, script, modelFn, nil) } // newPublicFixtureWithJobs is the fixture's core builder: the same scripted -// preparation over a Harness whose optional Jobs seam is installed. -func newPublicFixtureWithJobs(t *testing.T, store harness.Storage, stopper harness.JobStopper, script *scriptModel, modelFn func(context.Context, model.Request) (model.Stream, error)) *publicFixture { +// preparation over a Harness whose optional Jobs seam is installed. A nil +// compact callback resolves to the conversation callback. +func newPublicFixtureWithJobs(t *testing.T, store harness.Storage, stopper harness.JobStopper, script *scriptModel, modelFn, compactFn func(context.Context, model.Request) (model.Stream, error)) *publicFixture { t.Helper() if modelFn == nil { modelFn = script.effect } + if compactFn == nil { + compactFn = modelFn + } f := &publicFixture{store: store, cancel: func() {}, prepare: make(chan struct{}, 16), model: script} - prepared := harness.PreparedExecution{ + f.defaultPrep = harness.PreparedExecution{ Capture: publicCapture(), Open: func(context.Context, harness.OperationAdmission) (harness.Execution, error) { - return publicExecution(modelFn, modelFn), nil + return publicExecution(modelFn, compactFn), nil }, } ctx, cancel := context.WithCancel(context.Background()) @@ -291,11 +300,9 @@ func newPublicFixtureWithJobs(t *testing.T, store harness.Storage, stopper harne if hook != nil { return hook(call, req) } - return prepared, nil + return f.defaultPrep, nil }, Jobs: stopper}) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) f.h = h return f } @@ -311,9 +318,7 @@ func (f *publicFixture) close() { f.cancel() } func createSession(t *testing.T, h *harness.Harness) string { t.Helper() session, err := h.CreateSession(context.Background(), harness.CreateSessionRequest{Workspace: "/tmp/works", AgentType: "coder"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) return session.Identity.SessionID } @@ -344,15 +349,11 @@ func awaitTerminal(t *testing.T, h *harness.Harness, session, operation string) deadline := time.Now().Add(10 * time.Second) for { rec, err := h.ReadOperation(context.Background(), session, operation) - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } + testassert.NoError(t, err) if rec.State.Terminal != nil { return rec } - if time.Now().After(deadline) { - t.Fatalf("operation %s never settled within the wait bound", operation) - } + testassert.Require(t, !time.Now().After(deadline), "operation %s never settled within the wait bound", operation) time.Sleep(time.Millisecond) } } @@ -365,9 +366,7 @@ func eachStore(t *testing.T, run func(t *testing.T, store harness.Storage)) { }) t.Run("sqlite", func(t *testing.T) { store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) defer store.Close() run(t, store) }) @@ -392,65 +391,48 @@ func TestPublicTurnLifecycle(t *testing.T) { session := createSession(t, f.h) res, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") - if err != nil || res.Disposition != harness.DispositionAdmitted || res.Operation == nil { - t.Fatalf("first submit = %+v err %v, want admitted", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted && res.Operation != nil, "first submit = %+v err %v, want admitted", res, err) rec := res.Operation - if rec.State.Status != harness.OperationRunning || rec.Admission.AgentType != "coder" || - rec.Admission.Execution.ConfigurationRevision != "rev-1" || rec.Admission.Execution.Model != publicModelRef { - t.Fatalf("admitted record = %+v", rec) - } + testassert.Require(t, rec.State.Status == harness.OperationRunning && rec.Admission.AgentType == "coder" && rec.Admission.Execution.ConfigurationRevision == "rev-1" && rec.Admission.Execution.Model == publicModelRef, "admitted record = %+v", rec) <-script.arrived // parked at the first model boundary: the Session is active - if got := texts(script.seen()[0]); got[0] != "system" || got[1] != "hello" || len(got) != 2 { - t.Fatalf("first projection = %v, want the captured system prompt and the admitted input", got) + { + got := texts(script.seen()[0]) + testassert.Require(t, got[0] == "system" && got[1] == "hello" && len(got) == 2, "first projection = %v, want the captured system prompt and the admitted input", got) } - if queued, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil || - queued.Disposition != harness.DispositionQueued || queued.Operation != nil { - t.Fatalf("active queued submit = %+v err %v, want queued", queued, err) + { + queued, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.Require(t, err == nil && queued.Disposition == harness.DispositionQueued && queued.Operation == nil, "active queued submit = %+v err %v, want queued", queued, err) } script.releaseGate() <-f.prepare // the drain admitted the queued item: op-1's terminal committed <-script.arrived - if got := texts(script.seen()[1]); got[0] != "system" || got[1] != "hello" || got[2] != "done" || got[3] != "queued-1" { - t.Fatalf("second projection = %v, want the first turn's committed history before the queued input", got) + { + got := texts(script.seen()[1]) + testassert.Require(t, got[0] == "system" && got[1] == "hello" && got[2] == "done" && got[3] == "queued-1", "second projection = %v, want the first turn's committed history before the queued input", got) } awaitTerminal(t, f.h, session, "op-2") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } first, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || first.State.Status != harness.OperationSuccess || first.State.Terminal == nil { - t.Fatalf("first operation = %+v err %v, want terminal success", first, err) - } + testassert.Require(t, err == nil && first.State.Status == harness.OperationSuccess && first.State.Terminal != nil, "first operation = %+v err %v, want terminal success", first, err) want := harness.UsageTotals{ByModel: []harness.ModelUsage{{ Model: publicModelRef, Usage: harness.UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2}, }}} - if len(first.State.Usage.ByModel) != 1 || first.State.Usage.ByModel[0] != want.ByModel[0] { - t.Fatalf("operation usage = %+v, want the reported counts", first.State.Usage) - } + testassert.Require(t, len(first.State.Usage.ByModel) == 1 && first.State.Usage.ByModel[0] == want.ByModel[0], "operation usage = %+v, want the reported counts", first.State.Usage) sessionHeader, err := f.h.ReadSessionHeader(context.Background(), session) - if err != nil || sessionHeader.CurrentOperationID != "" { - t.Fatalf("session after convergence = %+v err %v", sessionHeader, err) - } + testassert.Require(t, err == nil && sessionHeader.CurrentOperationID == "", "session after convergence = %+v err %v", sessionHeader, err) sessionView, err := f.h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if len(sessionView.TotalUsage.ByModel) != 1 || sessionView.TotalUsage.ByModel[0] != want.ByModel[0] { - t.Fatalf("session usage = %+v, want the same totals", sessionView.TotalUsage) - } + testassert.NoError(t, err) + testassert.Require(t, len(sessionView.TotalUsage.ByModel) == 1 && sessionView.TotalUsage.ByModel[0] == want.ByModel[0], "session usage = %+v, want the same totals", sessionView.TotalUsage) second, err := f.h.ReadOperation(context.Background(), session, "op-2") - if err != nil || second.State.Status != harness.OperationFailure || - second.State.Terminal == nil || second.State.Terminal.Detail != "second turn failed" { - t.Fatalf("drained operation = %+v err %v, want terminal failure with its detail", second, err) - } + testassert.Require(t, err == nil && second.State.Status == harness.OperationFailure && second.State.Terminal != nil && second.State.Terminal.Detail == "second turn failed", "drained operation = %+v err %v, want terminal failure with its detail", second, err) calls := f.preparationCalls() - if len(calls) != 2 || calls[0].Session.AgentType != "coder" || calls[1].Session.AgentType != "coder" { - t.Fatalf("preparation calls = %+v, want one per admission with the Session type", calls) - } + testassert.Require(t, len(calls) == 2 && calls[0].Session.AgentType == "coder" && calls[1].Session.AgentType == "coder", "preparation calls = %+v, want one per admission with the Session type", calls) }) } @@ -468,43 +450,40 @@ func TestPublicSteeringContinuation(t *testing.T) { defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // parked at the first model boundary - if steered, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "steer"); err != nil || - steered.Disposition != harness.DispositionSteering || steered.Operation != nil { - t.Fatalf("steering submit = %+v err %v, want steering", steered, err) + { + steered, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "steer") + testassert.Require(t, err == nil && steered.Disposition == harness.DispositionSteering && steered.Operation == nil, "steering submit = %+v err %v, want steering", steered, err) } - if queued, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "queued-1"); err != nil || - queued.Disposition != harness.DispositionQueued { - t.Fatalf("queued submit = %+v err %v, want queued", queued, err) + { + queued, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "queued-1") + testassert.Require(t, err == nil && queued.Disposition == harness.DispositionQueued, "queued submit = %+v err %v, want queued", queued, err) } script.releaseGate() <-f.prepare // the drain admitted the steering item: op-1's quiet terminal committed <-script.arrived // the steering item's own Operation makes the next request - if got := texts(script.seen()[1]); got[0] != "system" || got[1] != "hello" || got[2] != "done" || got[3] != "steer" { - t.Fatalf("successor projection = %v, want the steering as its own Operation's first request", got) + { + got := texts(script.seen()[1]) + testassert.Require(t, got[0] == "system" && got[1] == "hello" && got[2] == "done" && got[3] == "steer", "successor projection = %v, want the steering as its own Operation's first request", got) } awaitTerminal(t, f.h, session, "op-2") <-f.prepare // the queued item drains after the successor's terminal <-script.arrived - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } rec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || rec.State.Status != harness.OperationSuccess || rec.State.Terminal == nil || rec.State.Terminal.Detail != "" { - t.Fatalf("settled predecessor = %+v err %v, want quiet success with empty detail", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess && rec.State.Terminal != nil && rec.State.Terminal.Detail == "", "settled predecessor = %+v err %v, want quiet success with empty detail", rec, err) successor, err := f.h.ReadOperation(context.Background(), session, "op-2") - if err != nil || successor.State.Status != harness.OperationSuccess { - t.Fatalf("steering operation = %+v err %v, want the successor's success", successor, err) - } + testassert.Require(t, err == nil && successor.State.Status == harness.OperationSuccess, "steering operation = %+v err %v, want the successor's success", successor, err) for _, fact := range readHistoryFacts(t, f.h, session) { if fact.Kind == harness.EntryInput && fact.Input != nil && len(fact.Input.Content) > 0 && fact.Input.Content[0].Text == "steer" { - if fact.OperationID != "op-2" { - t.Fatalf("steering input owned by %q, want its own Operation op-2", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "op-2") } } }) @@ -524,31 +503,27 @@ func TestPublicIdempotency(t *testing.T) { session := createSession(t, f.h) first, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "first") - if err != nil || first.Disposition != harness.DispositionAdmitted || first.Operation == nil { - t.Fatalf("first submit = %+v err %v, want admitted", first, err) - } + testassert.Require(t, err == nil && first.Disposition == harness.DispositionAdmitted && first.Operation != nil, "first submit = %+v err %v, want admitted", first, err) <-script.arrived // the Operation stays active retry, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "changed") - if err != nil || retry.Disposition != harness.DispositionExisting || retry.Operation == nil || - retry.Operation.Admission.AdmittedEntry != first.Operation.Admission.AdmittedEntry { - t.Fatalf("active retry = %+v err %v, want the first record", retry, err) - } + testassert.Require(t, err == nil && retry.Disposition == harness.DispositionExisting && retry.Operation != nil && retry.Operation.Admission.AdmittedEntry == first.Operation.Admission.AdmittedEntry, "active retry = %+v err %v, want the first record", retry, err) queuedRetry, err := submit(t, f.h, session, "op-1", harness.MessageModeQueued, "changed-again") - if err != nil || queuedRetry.Disposition != harness.DispositionExisting || queuedRetry.Operation == nil { - t.Fatalf("queued retry = %+v err %v, want existing before routing", queuedRetry, err) - } + testassert.Require(t, err == nil && queuedRetry.Disposition == harness.DispositionExisting && queuedRetry.Operation != nil, "queued retry = %+v err %v, want existing before routing", queuedRetry, err) other := createSession(t, f.h) - if _, err := submit(t, f.h, other, "op-1", harness.MessageModeRegular, "reused"); err == nil { - t.Fatalf("cross-session reuse succeeded, want an error") + { + _, err := submit(t, f.h, other, "op-1", harness.MessageModeRegular, "reused") + testassert.Require(t, err != nil, "cross-session reuse succeeded, want an error") } script.releaseGate() - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } - if _, err := f.h.ReadOperation(context.Background(), other, "op-1"); err == nil { - t.Fatalf("cross-session reuse published an operation, want nothing") + { + _, err := f.h.ReadOperation(context.Background(), other, "op-1") + testassert.Require(t, err != nil, "cross-session reuse published an operation, want nothing") } t.Run("reuse after delete", func(t *testing.T) { @@ -569,41 +544,41 @@ func TestPublicIdempotency(t *testing.T) { } source := createSession(t, f.h) - if _, err := submit(t, f.h, source, "reuse-1", harness.MessageModeRegular, "first"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, source, "reuse-1", harness.MessageModeRegular, "first") + testassert.NoError(t, err) } <-f.prepare // the first admission prepared <-script.arrived // parked at the model boundary: reuse-1 is running - if _, err := submit(t, f.h, source, "reuse-x", harness.MessageModeQueued, "queued"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, source, "reuse-x", harness.MessageModeQueued, "queued") + testassert.NoError(t, err) } script.releaseGate() <-f.prepare // barrier: the drain reached the queued item's preparation — reuse-1's terminal committed // the failed preparation is final for the item and the drain retires, leaving // the source Session idle; ArchiveSession's waitIdle joins that retirement - if _, err := f.h.ArchiveSession(context.Background(), source); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f.h.ArchiveSession(context.Background(), source) + testassert.NoError(t, err) } - if err := f.h.DeleteSession(context.Background(), source); err != nil { - t.Fatalf("delete: %v", err) + { + err := f.h.DeleteSession(context.Background(), source) + testassert.NoError(t, err) } other := createSession(t, f.h) res, err := submit(t, f.h, other, "reuse-1", harness.MessageModeRegular, "reused") - if err != nil || res.Disposition != harness.DispositionAdmitted || res.Operation == nil { - t.Fatalf("reuse after delete = %+v err %v, want admitted in another session", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted && res.Operation != nil, "reuse after delete = %+v err %v, want admitted in another session", res, err) <-script.arrived // the reused ID's Operation reached its model boundary awaitTerminal(t, f.h, other, "reuse-1") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } rec, err := f.h.ReadOperation(context.Background(), other, "reuse-1") - if err != nil || rec.State.Status != harness.OperationFailure || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "reused turn settled" { - t.Fatalf("reused operation = %+v err %v, want the model-originated terminal in the other session", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "reused turn settled", "reused operation = %+v err %v, want the model-originated terminal in the other session", rec, err) }) }) } @@ -698,40 +673,36 @@ func TestPublicSubmitRoutesAfterReservationWinner(t *testing.T) { close(releasePrep) // the winner publishes and installs its execution; its model stays gated out := <-winner - if out.err != nil || out.res.Disposition != harness.DispositionAdmitted || out.res.Operation == nil { - t.Fatalf("winning submit = %+v err %v, want admitted", out.res, out.err) - } + testassert.Require(t, out.err == nil && out.res.Disposition == harness.DispositionAdmitted && out.res.Operation != nil, "winning submit = %+v err %v, want admitted", out.res, out.err) <-script.arrived // the winner is parked at its model boundary: the Session stays active out = <-steerer - if out.err != nil || out.res.Disposition != harness.DispositionSteering || out.res.Operation != nil { - t.Fatalf("waiting regular submit = %+v err %v, want steering after the winner's admission", out.res, out.err) - } + testassert.Require(t, out.err == nil && out.res.Disposition == harness.DispositionSteering && out.res.Operation == nil, "waiting regular submit = %+v err %v, want steering after the winner's admission", out.res, out.err) out = <-queuer - if out.err != nil || out.res.Disposition != harness.DispositionQueued || out.res.Operation != nil { - t.Fatalf("waiting queued submit = %+v err %v, want queued after the winner's admission", out.res, out.err) - } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want only the winner's", len(calls)) + testassert.Require(t, out.err == nil && out.res.Disposition == harness.DispositionQueued && out.res.Operation == nil, "waiting queued submit = %+v err %v, want queued after the winner's admission", out.res, out.err) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } - if _, err := f.h.ReadOperation(context.Background(), session, "op-2"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("buffered steering operation read = err %v, want ErrNotFound while buffered", err) + { + _, err := f.h.ReadOperation(context.Background(), session, "op-2") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "buffered steering operation read = err %v, want ErrNotFound while buffered", err) } - if _, err := f.h.ReadOperation(context.Background(), session, "op-3"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("buffered queued operation read = err %v, want ErrNotFound while buffered", err) + { + _, err := f.h.ReadOperation(context.Background(), session, "op-3") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "buffered queued operation read = err %v, want ErrNotFound while buffered", err) } rec, err := f.h.ReadSessionHeader(context.Background(), session) - if err != nil || rec.CurrentOperationID != "op-1" { - t.Fatalf("session while both items wait buffered = %+v err %v, want the winner as the one active Operation", rec, err) - } + testassert.Require(t, err == nil && rec.CurrentOperationID == "op-1", "session while both items wait buffered = %+v err %v, want the winner as the one active Operation", rec, err) script.releaseGate() // every buffered item is delivered or discarded per the landed lifetime semantics - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } - if rec, err := f.h.ReadSessionHeader(context.Background(), session); err != nil || - rec.CurrentOperationID != "" { - t.Fatalf("session after convergence = %+v err %v, want no operation left running", rec, err) + { + rec, err := f.h.ReadSessionHeader(context.Background(), session) + testassert.Require(t, err == nil && rec.CurrentOperationID == "", "session after convergence = %+v err %v, want no operation left running", rec, err) } }) } @@ -761,40 +732,39 @@ func TestPublicSubmitSameIDResolvesExistingUnderReservation(t *testing.T) { close(releasePrep) winOut := <-winner - if winOut.err != nil || winOut.res.Disposition != harness.DispositionAdmitted || winOut.res.Operation == nil { - t.Fatalf("winning submit = %+v err %v, want admitted", winOut.res, winOut.err) - } + testassert.Require(t, winOut.err == nil && winOut.res.Disposition == harness.DispositionAdmitted && winOut.res.Operation != nil, "winning submit = %+v err %v, want admitted", winOut.res, winOut.err) <-script.arrived // the winner is parked at its model boundary out := <-retry - if out.err != nil || out.res.Disposition != harness.DispositionExisting || out.res.Operation == nil || - out.res.Operation.Admission.AdmittedEntry != winOut.res.Operation.Admission.AdmittedEntry { - t.Fatalf("same-ID retry = %+v err %v, want the first Operation as existing", out.res, out.err) - } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want no repeated preparation for the same ID", len(calls)) + testassert.Require(t, out.err == nil && out.res.Disposition == harness.DispositionExisting && out.res.Operation != nil && out.res.Operation.Admission.AdmittedEntry == winOut.res.Operation.Admission.AdmittedEntry, "same-ID retry = %+v err %v, want the first Operation as existing", out.res, out.err) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } - if got := texts(script.seen()[0]); got[len(got)-1] != "first" { - t.Fatalf("admitted projection = %v, want the originally admitted content, not the retry's", got) + { + got := texts(script.seen()[0]) + testassert.Require(t, got[len(got)-1] == "first", "admitted projection = %v, want the originally admitted content, not the retry's", got) } - if rec, err := f.h.ReadOperation(context.Background(), session, "op-1"); err != nil || - rec.State.Status != harness.OperationRunning { - t.Fatalf("same-ID operation = %+v err %v, want the one running Operation", rec, err) + { + rec, err := f.h.ReadOperation(context.Background(), session, "op-1") + testassert.Require(t, err == nil && rec.State.Status == harness.OperationRunning, "same-ID operation = %+v err %v, want the one running Operation", rec, err) } - if rec, err := f.h.ReadSessionHeader(context.Background(), session); err != nil || - rec.CurrentOperationID != "op-1" { - t.Fatalf("session = %+v err %v, want the same ID still current", rec, err) + { + rec, err := f.h.ReadSessionHeader(context.Background(), session) + testassert.Require(t, err == nil && rec.CurrentOperationID == "op-1", "session = %+v err %v, want the same ID still current", rec, err) } script.releaseGate() // the winner settles; no buffered duplicate exists to drain - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls after convergence = %d, want the same ID never independently executed", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } - if rec, err := f.h.ReadSessionHeader(context.Background(), session); err != nil || - rec.CurrentOperationID != "" { - t.Fatalf("session after convergence = %+v err %v, want no buffered duplicate delivered", rec, err) + { + rec, err := f.h.ReadSessionHeader(context.Background(), session) + testassert.Require(t, err == nil && rec.CurrentOperationID == "", "session after convergence = %+v err %v, want no buffered duplicate delivered", rec, err) } }) } @@ -824,33 +794,33 @@ func TestPublicSubmitCanceledWaiterPublishesNothing(t *testing.T) { cancelWait() out := <-canceled - if !errors.Is(out.err, context.Canceled) || out.res.Operation != nil { - t.Fatalf("canceled waiter = %+v err %v, want a context error and no result", out.res, out.err) - } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want no preparation for the canceled caller", len(calls)) + testassert.Require(t, errors.Is(out.err, context.Canceled) && out.res.Operation == nil, "canceled waiter = %+v err %v, want a context error and no result", out.res, out.err) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } - if _, err := f.h.ReadOperation(context.Background(), session, "op-2"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("canceled operation read = err %v, want ErrNotFound: nothing published", err) + { + _, err := f.h.ReadOperation(context.Background(), session, "op-2") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "canceled operation read = err %v, want ErrNotFound: nothing published", err) } - if rec, err := f.h.ReadSessionHeader(context.Background(), session); err != nil || - rec.CurrentOperationID != "" { - t.Fatalf("session after the canceled waiter = %+v err %v, want nothing published", rec, err) + { + rec, err := f.h.ReadSessionHeader(context.Background(), session) + testassert.Require(t, err == nil && rec.CurrentOperationID == "", "session after the canceled waiter = %+v err %v, want nothing published", rec, err) } close(releasePrep) // the winner's publication wins after the waiter's cancellation out = <-winner - if out.err != nil || out.res.Disposition != harness.DispositionAdmitted { - t.Fatalf("winner after a canceled waiter = %+v err %v, want admitted", out.res, out.err) - } + testassert.Require(t, out.err == nil && out.res.Disposition == harness.DispositionAdmitted, "winner after a canceled waiter = %+v err %v, want admitted", out.res, out.err) <-script.arrived script.releaseGate() - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls after convergence = %d, want the canceled item never delivered", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) } @@ -870,32 +840,29 @@ func TestPublicAgentTypeChange(t *testing.T) { defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } changed, err := f.h.ChangeAgentType(context.Background(), session, "reviewer") - if err != nil || changed.State.CurrentAgentType != "reviewer" { - t.Fatalf("ChangeAgentType = %+v err %v, want success during the blocked effect", changed, err) - } + testassert.Require(t, err == nil && changed.State.CurrentAgentType == "reviewer", "ChangeAgentType = %+v err %v, want success during the blocked effect", changed, err) script.releaseGate() <-f.prepare // the drained admission prepared again <-script.arrived - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } calls := f.preparationCalls() - if len(calls) != 2 || calls[0].Session.AgentType != "coder" || calls[1].Session.AgentType != "reviewer" { - t.Fatalf("preparation agent types = %+v, want the capture-time value then the changed one", calls) - } + testassert.Require(t, len(calls) == 2 && calls[0].Session.AgentType == "coder" && calls[1].Session.AgentType == "reviewer", "preparation agent types = %+v, want the capture-time value then the changed one", calls) first, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || first.Admission.AgentType != "coder" || first.State.Status != harness.OperationSuccess { - t.Fatalf("first operation = %+v err %v, want the immutable capture and success", first, err) - } + testassert.Require(t, err == nil && first.Admission.AgentType == "coder" && first.State.Status == harness.OperationSuccess, "first operation = %+v err %v, want the immutable capture and success", first, err) }) } @@ -910,41 +877,43 @@ func TestPublicHarnessLifetime(t *testing.T) { f := newPublicFixture(t, store, script, nil) session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // parked inside the model effect - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "steer"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "steer") + testassert.NoError(t, err) } - if _, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } waited := make(chan error, 1) go func() { waited <- f.h.Wait(context.Background()) }() // Wait begins before cancellation f.cancel() // Harness loss closes admission and ends the run script.releaseGate() - if err := <-waited; err != nil { - t.Fatalf("Wait: %v", err) + { + err := <-waited + testassert.NoError(t, err) } - if _, err := submit(t, f.h, session, "op-4", harness.MessageModeRegular, "late"); err == nil { - t.Fatalf("submit after cancellation succeeded, want admission closed") + { + _, err := submit(t, f.h, session, "op-4", harness.MessageModeRegular, "late") + testassert.Require(t, err != nil, "submit after cancellation succeeded, want admission closed") } rec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || rec.State.Status != harness.OperationInterruption { - t.Fatalf("converged operation = %+v err %v, want interruption", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationInterruption, "converged operation = %+v err %v, want interruption", rec, err) for _, id := range []string{"op-2", "op-3"} { - if _, err := f.h.ReadOperation(context.Background(), session, id); err == nil { - t.Fatalf("buffered item %q became an operation, want the buffers discarded", id) + { + _, err := f.h.ReadOperation(context.Background(), session, id) + testassert.Require(t, err != nil, "buffered item %q became an operation, want the buffers discarded", id) } } calls := f.preparationCalls() - if len(calls) != 1 { - t.Fatalf("preparation calls = %d, want no buffered delivery attempts after Harness loss", len(calls)) - } + testassert.Equal(t, len(calls), 1) }) } @@ -960,9 +929,7 @@ func TestPublicWaitOwnContextCancelsOnlyTheWait(t *testing.T) { go func() { done <- f.h.Wait(waitCtx) }() select { case err := <-done: - if err == nil { - t.Fatalf("Wait over a canceled wait context returned nil, want its error") - } + testassert.Require(t, err != nil, "Wait over a canceled wait context returned nil, want its error") case <-time.After(5 * time.Second): t.Fatalf("Wait ignored its own context cancellation") } @@ -1017,12 +984,14 @@ func submitRequest(sessionID, operationID, text string) harness.SubmitRequest { // the Session carries no entries and no Operation register. func assertNothingAdmitted(t *testing.T, store harness.Storage, sessionID, operationID string) { t.Helper() - if entries := recoverEntries(t, store, sessionID); len(entries) != 0 { - t.Fatalf("aborted admission published %d entries, want none", len(entries)) + { + entries := recoverEntries(t, store, sessionID) + testassert.Equal(t, len(entries), 0) } key := harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: operationID} - if _, err := store.ReadRegister(context.Background(), key); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("operation register after the aborted admission = err %v, want absence", err) + { + _, err := store.ReadRegister(context.Background(), key) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "operation register after the aborted admission = err %v, want absence", err) } } @@ -1034,12 +1003,14 @@ func seedIdleSource(t *testing.T, store harness.Storage, operationID string) str script := newScriptModel(publicTurn()) f := newPublicFixture(t, store, script, nil) source := createSession(t, f.h) - if _, err := submit(t, f.h, source, operationID, harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("seed submit: %v", err) + { + _, err := submit(t, f.h, source, operationID, harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if err := converge(t, f); err != nil { - t.Fatalf("seed Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } return source } @@ -1065,16 +1036,13 @@ func TestPublicPublicationContextLifetime(t *testing.T) { probe.atEntry = f.cancel // the Harness context dies as the publication transaction begins res, err := f.h.Submit(ctx, submitRequest(session, "op-a", "hello")) - if !errors.Is(err, context.Canceled) { - t.Fatalf("submit whose publication lost the Harness context = %+v err %v, want a context error", res, err) - } - if res.Operation != nil { - t.Fatalf("submit result = %+v, want no admitted operation", res) - } + testassert.Require(t, errors.Is(err, context.Canceled), "submit whose publication lost the Harness context = %+v err %v, want a context error", res, err) + testassert.Require(t, res.Operation == nil, "submit result = %+v, want no admitted operation", res) assertNothingAdmitted(t, store, session, "op-a") assertSessionIDs(t, store, session) - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want exactly one with no retry", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) @@ -1088,15 +1056,12 @@ func TestPublicPublicationContextLifetime(t *testing.T) { probe.atEntry = cancelCaller res, err := f.h.Submit(caller, submitRequest(session, "op-b", "hello")) - if !errors.Is(err, context.Canceled) { - t.Fatalf("submit whose publication lost the caller context = %+v err %v, want a context error", res, err) - } - if res.Operation != nil { - t.Fatalf("submit result = %+v, want no admitted operation", res) - } + testassert.Require(t, errors.Is(err, context.Canceled), "submit whose publication lost the caller context = %+v err %v, want a context error", res, err) + testassert.Require(t, res.Operation == nil, "submit result = %+v, want no admitted operation", res) assertNothingAdmitted(t, store, session, "op-b") - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want exactly one with no retry", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) @@ -1104,9 +1069,7 @@ func TestPublicPublicationContextLifetime(t *testing.T) { source := seedIdleSource(t, store, "seed-op-1") boundary := forkEntryOf(t, store, source, harness.EntryInput, "seed-op-1").ID before, err := store.ListSessionIDs(ctx) // subtests share the store: no destination means an unchanged listing - if err != nil { - t.Fatalf("list sessions: %v", err) - } + testassert.NoError(t, err) probe := &publicationCancelStore{Storage: store} f := newPublicFixture(t, probe, newScriptModel(), nil) @@ -1119,23 +1082,16 @@ func TestPublicPublicationContextLifetime(t *testing.T) { OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if !errors.Is(err, context.Canceled) { - t.Fatalf("fork whose publication lost the Harness context = %+v err %v, want a context error", res, err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "fork whose publication lost the Harness context = %+v err %v, want a context error", res, err) after, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("list sessions: %v", err) - } - if len(after) != len(before) { - t.Fatalf("session listing after the aborted fork = %v, want it unchanged at %v", after, before) - } + testassert.NoError(t, err) + testassert.Require(t, len(after) == len(before), "session listing after the aborted fork = %v, want it unchanged at %v", after, before) for i := range after { - if after[i] != before[i] { - t.Fatalf("session listing after the aborted fork = %v, want it unchanged at %v", after, before) - } + testassert.Require(t, after[i] == before[i], "session listing after the aborted fork = %v, want it unchanged at %v", after, before) } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want exactly one with no retry", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) @@ -1143,9 +1099,7 @@ func TestPublicPublicationContextLifetime(t *testing.T) { source := seedIdleSource(t, store, "seed-op-3") boundary := forkEntryOf(t, store, source, harness.EntryInput, "seed-op-3").ID before, err := store.ListSessionIDs(ctx) // subtests share the store: no destination means an unchanged listing - if err != nil { - t.Fatalf("list sessions: %v", err) - } + testassert.NoError(t, err) probe := &publicationCancelStore{Storage: store} f := newPublicFixture(t, probe, newScriptModel(), nil) @@ -1160,23 +1114,16 @@ func TestPublicPublicationContextLifetime(t *testing.T) { OperationID: "fork-2", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if !errors.Is(err, context.Canceled) { - t.Fatalf("fork whose publication lost the caller context = %+v err %v, want a context error", res, err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "fork whose publication lost the caller context = %+v err %v, want a context error", res, err) after, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("list sessions: %v", err) - } - if len(after) != len(before) { - t.Fatalf("session listing after the aborted fork = %v, want it unchanged at %v", after, before) - } + testassert.NoError(t, err) + testassert.Require(t, len(after) == len(before), "session listing after the aborted fork = %v, want it unchanged at %v", after, before) for i := range after { - if after[i] != before[i] { - t.Fatalf("session listing after the aborted fork = %v, want it unchanged at %v", after, before) - } + testassert.Require(t, after[i] == before[i], "session listing after the aborted fork = %v, want it unchanged at %v", after, before) } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want exactly one with no retry", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) @@ -1190,15 +1137,12 @@ func TestPublicPublicationContextLifetime(t *testing.T) { probe.atEntry = func() { <-caller.Done() } // publication gated until the caller deadline expires res, err := f.h.Submit(caller, submitRequest(session, "op-d", "hello")) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("submit whose publication lost the caller deadline = %+v err %v, want DeadlineExceeded", res, err) - } - if res.Operation != nil { - t.Fatalf("submit result = %+v, want no admitted operation", res) - } + testassert.Require(t, errors.Is(err, context.DeadlineExceeded), "submit whose publication lost the caller deadline = %+v err %v, want DeadlineExceeded", res, err) + testassert.Require(t, res.Operation == nil, "submit result = %+v, want no admitted operation", res) assertNothingAdmitted(t, store, session, "op-d") - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want exactly one with no retry", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) @@ -1206,9 +1150,7 @@ func TestPublicPublicationContextLifetime(t *testing.T) { source := seedIdleSource(t, store, "seed-op-4") boundary := forkEntryOf(t, store, source, harness.EntryInput, "seed-op-4").ID before, err := store.ListSessionIDs(ctx) // subtests share the store: no destination means an unchanged listing - if err != nil { - t.Fatalf("list sessions: %v", err) - } + testassert.NoError(t, err) probe := &publicationCancelStore{Storage: store} f := newPublicFixture(t, probe, newScriptModel(), nil) @@ -1223,23 +1165,16 @@ func TestPublicPublicationContextLifetime(t *testing.T) { OperationID: "fork-3", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("fork whose publication lost the caller deadline = %+v err %v, want DeadlineExceeded", res, err) - } + testassert.Require(t, errors.Is(err, context.DeadlineExceeded), "fork whose publication lost the caller deadline = %+v err %v, want DeadlineExceeded", res, err) after, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("list sessions: %v", err) - } - if len(after) != len(before) { - t.Fatalf("session listing after the aborted fork = %v, want it unchanged at %v", after, before) - } + testassert.NoError(t, err) + testassert.Require(t, len(after) == len(before), "session listing after the aborted fork = %v, want it unchanged at %v", after, before) for i := range after { - if after[i] != before[i] { - t.Fatalf("session listing after the aborted fork = %v, want it unchanged at %v", after, before) - } + testassert.Require(t, after[i] == before[i], "session listing after the aborted fork = %v, want it unchanged at %v", after, before) } - if calls := f.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want exactly one with no retry", len(calls)) + { + calls := f.preparationCalls() + testassert.Equal(t, len(calls), 1) } }) @@ -1254,21 +1189,16 @@ func TestPublicPublicationContextLifetime(t *testing.T) { probe.afterCommit = cancelCaller res, err := f.h.Submit(caller, submitRequest(session, "op-c", "hello")) - if err != nil || res.Disposition != harness.DispositionAdmitted || res.Operation == nil { - t.Fatalf("submit over a caller context canceled after the commit = %+v err %v, want the committed admission", res, err) - } - if caller.Err() == nil { - t.Fatalf("the caller context was not canceled after the commit") - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted && res.Operation != nil, "submit over a caller context canceled after the commit = %+v err %v, want the committed admission", res, err) + testassert.Require(t, caller.Err() != nil, "the caller context was not canceled after the commit") <-script.arrived // the admitted execution reached its model boundary; its terminal commits in the effect's own transaction awaitTerminal(t, f.h, session, "op-c") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } rec, err := f.h.ReadOperation(ctx, session, "op-c") - if err != nil || rec.State.Status != harness.OperationFailure || rec.State.Terminal == nil || rec.State.Terminal.Detail != "committed turn" { - t.Fatalf("committed operation = %+v err %v, want the model-originated terminal", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "committed turn", "committed operation = %+v err %v, want the model-originated terminal", rec, err) }) t.Run("post-commit caller cancellation returns the committed fork", func(t *testing.T) { @@ -1289,23 +1219,18 @@ func TestPublicPublicationContextLifetime(t *testing.T) { OperationID: "fork-2", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("fork over a caller context canceled after the commit = err %v, want the committed result", err) - } + testassert.NoError(t, err) dest := res.Session.Identity.SessionID - if dest == source || res.Session.Identity.SourceSessionID != source || - res.Operation.Admission.OperationID != "fork-2" || res.Operation.State.Status != harness.OperationRunning { - t.Fatalf("fork result = %+v, want the committed destination and its running fork operation", res) - } - if caller.Err() == nil { - t.Fatalf("the caller context was not canceled after the commit") - } - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + testassert.Require(t, dest != source && res.Session.Identity.SourceSessionID == source && res.Operation.Admission.OperationID == "fork-2" && res.Operation.State.Status == harness.OperationRunning, "fork result = %+v, want the committed destination and its running fork operation", res) + testassert.Require(t, caller.Err() != nil, "the caller context was not canceled after the commit") + { + err := converge(t, f) + testassert.NoError(t, err) } assertForkSourceUnchanged(t, store, source, snapshotSession(t, store, source)) // the source is untouched - if entries := recoverEntries(t, store, dest); len(entries) == 0 { - t.Fatalf("committed destination %q has no entries", dest) + { + entries := recoverEntries(t, store, dest) + testassert.Require(t, len(entries) != 0, "committed destination %q has no entries", dest) } }) }) @@ -1379,41 +1304,31 @@ func TestPublicOpenerInputMutationKeepsAdmittedExecution(t *testing.T) { }, nil } session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } select { case <-settled: case <-time.After(10 * time.Second): t.Fatalf("the execution cleanup never ran after the settlement") } - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } if opens.Load() != 1 { t.Fatalf("opener calls = %d, want the mutation assertion to run through a real open", opens.Load()) } seen := script.seen() - if len(seen) != 1 { - t.Fatalf("%d model requests, want one", len(seen)) - } + testassert.Equal(t, len(seen), 1) got := seen[0].Tools - if len(got) != 1 || got[0].Name != "echo" || got[0].Description != "echoes" || string(got[0].Parameters) != string(admittedParams) { - t.Fatalf("advertised tools = %+v, want the admitted definitions, not the opener's local mutations", got) - } + testassert.Require(t, len(got) == 1 && got[0].Name == "echo" && got[0].Description == "echoes" && string(got[0].Parameters) == string(admittedParams), "advertised tools = %+v, want the admitted definitions, not the opener's local mutations", got) rec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation status = %s, want success over the admitted capture", rec.State.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) durable := rec.Admission.Execution - if len(durable.Tools) != 1 || durable.Tools[0].Name != "echo" || durable.Tools[0].Description != "echoes" || - string(durable.Tools[0].Parameters) != string(admittedParams) || - !reflect.DeepEqual(durable.Capabilities, []string{"cap.one", "cap.two"}) { - t.Fatalf("durable capture = %+v, want the admitted values", durable) - } + testassert.Require(t, len(durable.Tools) == 1 && durable.Tools[0].Name == "echo" && durable.Tools[0].Description == "echoes" && string(durable.Tools[0].Parameters) == string(admittedParams) && reflect.DeepEqual(durable.Capabilities, []string{"cap.one", "cap.two"}), "durable capture = %+v, want the admitted values", durable) }) }) } @@ -1443,33 +1358,35 @@ func TestPublicBufferedItemFailure(t *testing.T) { } session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-f.prepare // the first admission prepared <-script.arrived // parked: the Session is active - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "q1"); err != nil { - t.Fatalf("queued submit 1: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "q1") + testassert.NoError(t, err) } - if _, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "q2"); err != nil { - t.Fatalf("queued submit 2: %v", err) + { + _, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "q2") + testassert.NoError(t, err) } script.releaseGate() <-f.prepare // the failed item was dropped and the next one admitted <-script.arrived awaitTerminal(t, f.h, session, "op-3") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } - if _, err := f.h.ReadOperation(context.Background(), session, "op-2"); err == nil { - t.Fatalf("failed item's operation exists, want nothing retained") + { + _, err := f.h.ReadOperation(context.Background(), session, "op-2") + testassert.Require(t, err != nil, "failed item's operation exists, want nothing retained") } rec, err := f.h.ReadOperation(context.Background(), session, "op-3") - if err != nil || rec.State.Status != harness.OperationFailure || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "next item settled" { - t.Fatalf("next buffered message = %+v err %v, want admitted and settled", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "next item settled", "next buffered message = %+v err %v, want admitted and settled", rec, err) }) }) @@ -1486,16 +1403,19 @@ func TestPublicBufferedItemFailure(t *testing.T) { defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-f.prepare // the first admission prepared <-script.arrived // parked: the Session is active - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "q1"); err != nil { - t.Fatalf("queued submit 1: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "q1") + testassert.NoError(t, err) } - if _, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "q2"); err != nil { - t.Fatalf("queued submit 2: %v", err) + { + _, err := submit(t, f.h, session, "op-3", harness.MessageModeQueued, "q2") + testassert.NoError(t, err) } script.releaseGate() @@ -1504,19 +1424,14 @@ func TestPublicBufferedItemFailure(t *testing.T) { <-f.prepare // q1's failed terminal let the drain admit the next item <-script.arrived awaitTerminal(t, f.h, session, "op-3") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } failed, err := f.h.ReadOperation(context.Background(), session, "op-2") - if err != nil || failed.State.Status != harness.OperationFailure || - failed.State.Terminal == nil || failed.State.Terminal.Detail != "queued turn failed" { - t.Fatalf("failed delivered operation = %+v err %v, want terminal failure", failed, err) - } + testassert.Require(t, err == nil && failed.State.Status == harness.OperationFailure && failed.State.Terminal != nil && failed.State.Terminal.Detail == "queued turn failed", "failed delivered operation = %+v err %v, want terminal failure", failed, err) rec, err := f.h.ReadOperation(context.Background(), session, "op-3") - if err != nil || rec.State.Status != harness.OperationFailure || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "next item settled" { - t.Fatalf("next buffered message = %+v err %v, want admitted and settled", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "next item settled", "next buffered message = %+v err %v, want admitted and settled", rec, err) }) }) } @@ -1543,8 +1458,9 @@ func TestPublicFinalBoundarySerialization(t *testing.T) { boundary := func(ctx context.Context, req model.Request) (model.Stream, error) { if first { // submit at the model-result boundary, before the settlement returns first = false - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "at-boundary"); err != nil { - t.Errorf("boundary submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeRegular, "at-boundary") + testassert.Check(t, err == nil, "boundary submit: %v", err) } } return inner(ctx, req) @@ -1553,32 +1469,28 @@ func TestPublicFinalBoundarySerialization(t *testing.T) { defer f.close() session = createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived <-script.arrived // the boundary input's own Operation makes the next request - if got := texts(script.seen()[1]); got[0] != "system" || got[1] != "hello" || got[2] != "done" || got[3] != "at-boundary" { - t.Fatalf("successor projection = %v, want the boundary steering as its own Operation's first request", got) + { + got := texts(script.seen()[1]) + testassert.Require(t, got[0] == "system" && got[1] == "hello" && got[2] == "done" && got[3] == "at-boundary", "successor projection = %v, want the boundary steering as its own Operation's first request", got) } awaitTerminal(t, f.h, session, "op-2") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } rec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || rec.State.Status != harness.OperationSuccess || rec.State.Terminal == nil { - t.Fatalf("operation = %+v err %v, want the natural-success boundary's quiet success", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess && rec.State.Terminal != nil, "operation = %+v err %v, want the natural-success boundary's quiet success", rec, err) successor, err := f.h.ReadOperation(context.Background(), session, "op-2") - if err != nil || successor.State.Status != harness.OperationFailure || - successor.State.Terminal == nil || successor.State.Terminal.Detail != "successor settled" { - t.Fatalf("successor operation = %+v err %v, want the boundary input's own Operation at its terminal", successor, err) - } + testassert.Require(t, err == nil && successor.State.Status == harness.OperationFailure && successor.State.Terminal != nil && successor.State.Terminal.Detail == "successor settled", "successor operation = %+v err %v, want the boundary input's own Operation at its terminal", successor, err) for _, fact := range readHistoryFacts(t, f.h, session) { if fact.Kind == harness.EntryInput && fact.Input != nil && len(fact.Input.Content) > 0 && fact.Input.Content[0].Text == "at-boundary" { - if fact.OperationID != "op-2" { - t.Fatalf("boundary input owned by %q, want its own Operation op-2", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "op-2") } } }) @@ -1599,8 +1511,9 @@ func TestPublicFinalBoundarySerialization(t *testing.T) { boundary := func(ctx context.Context, req model.Request) (model.Stream, error) { if first { // defer one queued message at the final model-result boundary first = false - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "at-boundary"); err != nil { - t.Errorf("boundary submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "at-boundary") + testassert.Check(t, err == nil, "boundary submit: %v", err) } } return inner(ctx, req) @@ -1609,25 +1522,22 @@ func TestPublicFinalBoundarySerialization(t *testing.T) { defer f.close() session = createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived <-f.prepare // the drain admitted the boundary item after the terminal commit <-script.arrived awaitTerminal(t, f.h, session, "op-2") // the settlement commits before the convergence cancellation - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } firstRec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || firstRec.State.Status != harness.OperationFailure || - firstRec.State.Terminal == nil || firstRec.State.Terminal.Detail != "first settled" { - t.Fatalf("first operation = %+v err %v, want its terminal before the drain", firstRec, err) - } + testassert.Require(t, err == nil && firstRec.State.Status == harness.OperationFailure && firstRec.State.Terminal != nil && firstRec.State.Terminal.Detail == "first settled", "first operation = %+v err %v, want its terminal before the drain", firstRec, err) rec, err := f.h.ReadOperation(context.Background(), session, "op-2") - if err != nil || rec.State.Status != harness.OperationFailure { - t.Fatalf("drained operation = %+v err %v, want the boundary queued input admitted after the terminal", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure, "drained operation = %+v err %v, want the boundary queued input admitted after the terminal", rec, err) }) }) } @@ -1659,33 +1569,30 @@ func TestPublicSessionsExecuteConcurrently(t *testing.T) { sessionA := createSession(t, f.h) sessionB := createSession(t, f.h) - if _, err := submit(t, f.h, sessionA, "op-a", harness.MessageModeRegular, "first"); err != nil { - t.Fatalf("first session submit: %v", err) + { + _, err := submit(t, f.h, sessionA, "op-a", harness.MessageModeRegular, "first") + testassert.NoError(t, err) } <-f.prepare // the first admission prepared <-parked.arrived // the first Session is parked at its model boundary - if _, err := submit(t, f.h, sessionB, "op-b", harness.MessageModeRegular, "second"); err != nil { - t.Fatalf("second session submit: %v", err) + { + _, err := submit(t, f.h, sessionB, "op-b", harness.MessageModeRegular, "second") + testassert.NoError(t, err) } <-f.prepare // the second Session was admitted while the first is parked <-finished.arrived // and reached its model boundary before the first one resumed parked.releaseGate() // the first Session settles through its own model-originated terminal awaitTerminal(t, f.h, sessionA, "op-a") // both failing attempts' settlements commit awaitTerminal(t, f.h, sessionB, "op-b") // before the convergence cancellation can race either - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } doneRec, err := f.h.ReadOperation(context.Background(), sessionB, "op-b") - if err != nil || doneRec.State.Status != harness.OperationFailure || - doneRec.State.Terminal == nil || doneRec.State.Terminal.Detail != "second session settled" { - t.Fatalf("second session = %+v err %v, want it settled while the first was parked", doneRec, err) - } + testassert.Require(t, err == nil && doneRec.State.Status == harness.OperationFailure && doneRec.State.Terminal != nil && doneRec.State.Terminal.Detail == "second session settled", "second session = %+v err %v, want it settled while the first was parked", doneRec, err) rec, err := f.h.ReadOperation(context.Background(), sessionA, "op-a") - if err != nil || rec.State.Status != harness.OperationFailure || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "first session settled" { - t.Fatalf("first session = %+v err %v, want its model-originated terminal after its release", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "first session settled", "first session = %+v err %v, want its model-originated terminal after its release", rec, err) }) } @@ -1746,10 +1653,7 @@ func TestPublicOrderedToolCallsSettle(t *testing.T) { t.Errorf("decode operation register during execution: %v", err) return harness.ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "ran call-1"}} } - if wire.State.ActiveEffect == nil || wire.State.ActiveEffect.Kind != "tool" || - wire.State.ActiveEffect.ToolCallID != "call-1" || wire.State.ActiveEffect.ResultEntryID == "" { - t.Errorf("tool intent during execution = %+v, want the committed call-1 intent with a reserved result", wire.State.ActiveEffect) - } + testassert.Check(t, wire.State.ActiveEffect != nil && wire.State.ActiveEffect.Kind == "tool" && wire.State.ActiveEffect.ToolCallID == "call-1" && wire.State.ActiveEffect.ResultEntryID != "", "tool intent during execution = %+v, want the committed call-1 intent with a reserved result", wire.State.ActiveEffect) return harness.ToolOutcome{Result: model.ToolResult{CallID: call.ID, Status: model.ResultSuccess, Content: "ran call-1"}} }} } @@ -1763,39 +1667,36 @@ func TestPublicOrderedToolCallsSettle(t *testing.T) { script.gate = make(chan struct{}) // the first turn parks at its model boundary until released below session = createSession(t, f.h) res, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") - if err != nil || res.Disposition != harness.DispositionAdmitted { - t.Fatalf("first submit = %+v err %v, want the auto-started admission", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted, "first submit = %+v err %v, want the auto-started admission", res, err) <-f.prepare // the initial admission prepared: the later prepare barrier is the drain's alone <-script.arrived // the first model boundary — parked at the gate - if got := texts(script.seen()[0]); got[0] != "system" || got[1] != "hello" || len(got) != 2 { - t.Fatalf("first projection = %v, want the system prompt and the admitted input", got) + { + got := texts(script.seen()[0]) + testassert.Require(t, got[0] == "system" && got[1] == "hello" && len(got) == 2, "first projection = %v, want the system prompt and the admitted input", got) } - if queued, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil || queued.Disposition != harness.DispositionQueued { - t.Fatalf("queued submit = %+v err %v, want queued", queued, err) + { + queued, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.Require(t, err == nil && queued.Disposition == harness.DispositionQueued, "queued submit = %+v err %v, want queued", queued, err) } script.releaseGate() <-script.arrived // the second request: both committed results project before it - if got := texts(script.seen()[1]); len(got) != 5 || got[0] != "system" || got[1] != "hello" || got[2] != "done" || - got[3] != "ran call-1" || got[4] != "immediate call-2" { - t.Fatalf("subsequent projection = %v, want both tool results in dispatch order", got) + { + got := texts(script.seen()[1]) + testassert.Require(t, len(got) == 5 && got[0] == "system" && got[1] == "hello" && got[2] == "done" && got[3] == "ran call-1" && got[4] == "immediate call-2", "subsequent projection = %v, want both tool results in dispatch order", got) } <-f.prepare // the drain admitted the queued item: op-1's terminal success committed <-script.arrived - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } rec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || rec.State.Status != harness.OperationSuccess || len(rec.State.PendingToolCalls) != 0 { - t.Fatalf("tool turn = %+v err %v, want terminal success with every call settled", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess && len(rec.State.PendingToolCalls) == 0, "tool turn = %+v err %v, want terminal success with every call settled", rec, err) toolMu.Lock() defer toolMu.Unlock() - if len(toolOrder) != 2 || toolOrder[0] != "call-1" || toolOrder[1] != "call-2" { - t.Fatalf("dispatch order = %v, want the completed calls in published order", toolOrder) - } + testassert.Require(t, len(toolOrder) == 2 && toolOrder[0] == "call-1" && toolOrder[1] == "call-2", "dispatch order = %v, want the completed calls in published order", toolOrder) }) } @@ -1821,9 +1722,7 @@ func TestPublicToolMetadataPersistsOpaqueAcrossStores(t *testing.T) { // durable payload, expanding it far past the bound. const durableBound = 1 << 20 expanded := json.RawMessage(`"` + strings.Repeat("<", durableBound-2) + `"`) - if len(expanded) != durableBound { - t.Fatalf("fixture raw size = %d, want exactly the durable bound", len(expanded)) - } + testassert.Equal(t, len(expanded), durableBound) script := newScriptModel( publicTurn("call-1", "call-2", "call-3"), publicTurn(), @@ -1862,16 +1761,19 @@ func TestPublicToolMetadataPersistsOpaqueAcrossStores(t *testing.T) { }, nil } session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the turn that published the calls <-script.arrived // the completion turn: all three results projected before it - if got := texts(script.seen()[1]); len(got) != 6 || got[3] != "ran call-1" || got[4] != "no preview" || got[5] != "expanded metadata" { - t.Fatalf("projection = %v, want the committed contents only", got) + { + got := texts(script.seen()[1]) + testassert.Require(t, len(got) == 6 && got[3] == "ran call-1" && got[4] == "no preview" && got[5] == "expanded metadata", "projection = %v, want the committed contents only", got) } - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } readResults := func() map[string]map[string]json.RawMessage { @@ -1881,44 +1783,41 @@ func TestPublicToolMetadataPersistsOpaqueAcrossStores(t *testing.T) { continue } var wire map[string]json.RawMessage - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode tool result payload: %v", err) + { + err := json.Unmarshal(entry.Payload, &wire) + testassert.NoError(t, err) } var callID string - if err := json.Unmarshal(wire["tool_call_id"], &callID); err != nil { - t.Fatalf("decode tool call id: %v", err) + { + err := json.Unmarshal(wire["tool_call_id"], &callID) + testassert.NoError(t, err) } results[callID] = wire } return results } results := readResults() - if string(results["call-1"]["metadata"]) != encoded { - t.Fatalf("call-1 metadata = %s, want the committed encoded bytes verbatim", results["call-1"]["metadata"]) - } - if _, present := results["call-2"]["metadata"]; present { - t.Fatalf("call-2 carries malformed metadata %s, want it dropped while the result committed", results["call-2"]["metadata"]) - } - if string(results["call-2"]["content"]) != `"no preview"` { - t.Fatalf("call-2 payload = %s, want the settled result alongside the dropped metadata", results["call-2"]["content"]) - } - if _, present := results["call-3"]["metadata"]; present { - t.Fatalf("call-3 carries durable-oversized metadata, want it dropped while the result committed") + testassert.Require(t, string(results["call-1"]["metadata"]) == encoded, "call-1 metadata = %s, want the committed encoded bytes verbatim", results["call-1"]["metadata"]) + { + _, present := results["call-2"]["metadata"] + testassert.Require(t, !present, "call-2 carries malformed metadata %s, want it dropped while the result committed", results["call-2"]["metadata"]) } - if string(results["call-3"]["content"]) != `"expanded metadata"` { - t.Fatalf("call-3 payload = %s, want the settled result committed past the metadata drop", results["call-3"]["content"]) + testassert.Require(t, string(results["call-2"]["content"]) == `"no preview"`, "call-2 payload = %s, want the settled result alongside the dropped metadata", results["call-2"]["content"]) + { + _, present := results["call-3"]["metadata"] + testassert.Require(t, !present, "call-3 carries durable-oversized metadata, want it dropped while the result committed") } + testassert.Require(t, string(results["call-3"]["content"]) == `"expanded metadata"`, "call-3 payload = %s, want the settled result committed past the metadata drop", results["call-3"]["content"]) // a restarted Harness revalidates every committed payload: the durable // states prove no transaction failed and the Session stayed sound, // and the committed encoded member survives recovery unchanged. - if err := harness.Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + { + err := harness.Recover(context.Background(), store) + testassert.NoError(t, err) } results = readResults() - if string(results["call-1"]["metadata"]) != encoded { - t.Fatalf("call-1 metadata after Recover = %s, want the committed encoded bytes verbatim", results["call-1"]["metadata"]) - } + testassert.Require(t, string(results["call-1"]["metadata"]) == encoded, "call-1 metadata after Recover = %s, want the committed encoded bytes verbatim", results["call-1"]["metadata"]) }) } @@ -1973,33 +1872,27 @@ func TestPublicPreparedPermissionBoundarySurvivesRestart(t *testing.T) { }, nil } session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the turn publishing both calls <-script.arrived // the continuation after the settled calls - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) - } - if executions != 1 { - t.Fatalf("concrete effects started = %d, want only the declared call", executions) - } - if string(consumed) != `{"x":1}` { - t.Fatalf("executor saw arguments %s, want the committed normalized bytes", consumed) + { + err := converge(t, f) + testassert.NoError(t, err) } + testassert.Equal(t, executions, 1) + testassert.Require(t, string(consumed) == `{"x":1}`, "executor saw arguments %s, want the committed normalized bytes", consumed) rec, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil { - t.Fatalf("ReadOperation: %v", err) - } - if !rec.Admission.Execution.Readonly || rec.Admission.Execution.WriteDir != "/w/sub" { - t.Fatalf("committed capture = %v %q, want the durable permission capability members", - rec.Admission.Execution.Readonly, rec.Admission.Execution.WriteDir) - } + testassert.NoError(t, err) + testassert.Require(t, rec.Admission.Execution.Readonly && rec.Admission.Execution.WriteDir == "/w/sub", "committed capture = %v %q, want the durable permission capability members", rec.Admission.Execution.Readonly, rec.Admission.Execution.WriteDir) var normalized, denial, settled int for _, entry := range recoverEntries(t, store, session) { var wire map[string]json.RawMessage - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode %s payload: %v", entry.ID, err) + { + err := json.Unmarshal(entry.Payload, &wire) + testassert.Require(t, err == nil, "decode %s payload: %v", entry.ID, err) } switch entry.Kind { case harness.EntryAssistant: @@ -2007,47 +1900,44 @@ func TestPublicPreparedPermissionBoundarySurvivesRestart(t *testing.T) { ID string `json:"id"` NormalizedArguments json.RawMessage `json:"normalized_arguments"` } - if err := json.Unmarshal(wire["tool_calls"], &calls); err != nil { - t.Fatalf("decode assistant tool calls: %v", err) + { + err := json.Unmarshal(wire["tool_calls"], &calls) + testassert.NoError(t, err) } for _, call := range calls { - if string(call.NormalizedArguments) != `{"x":1}` { - t.Fatalf("%s normalized_arguments = %s, want the producer's committed object", call.ID, call.NormalizedArguments) - } + testassert.Require(t, string(call.NormalizedArguments) == `{"x":1}`, "%s normalized_arguments = %s, want the producer's committed object", call.ID, call.NormalizedArguments) normalized++ } case harness.EntryToolResult: settled++ var callID, status string - if err := json.Unmarshal(wire["tool_call_id"], &callID); err != nil { - t.Fatalf("decode tool call id: %v", err) + { + err := json.Unmarshal(wire["tool_call_id"], &callID) + testassert.NoError(t, err) } - if err := json.Unmarshal(wire["status"], &status); err != nil { - t.Fatalf("decode status: %v", err) + { + err := json.Unmarshal(wire["status"], &status) + testassert.NoError(t, err) } if callID == "call-2" { denial++ - if status != "denied" || string(wire["content"]) != `"Permission denied."` { - t.Fatalf("call-2 result = %s, want the fixed public denial", entry.Payload) - } - if _, present := wire["metadata"]; present { - t.Fatalf("denied call-2 carries a metadata member") + testassert.Require(t, status == "denied" && string(wire["content"]) == `"Permission denied."`, "call-2 result = %s, want the fixed public denial", entry.Payload) + { + _, present := wire["metadata"] + testassert.Require(t, !present, "denied call-2 carries a metadata member") } } else if status != "success" { t.Fatalf("call-1 result = %s, want the allowed execution", entry.Payload) } } } - if normalized != 2 || denial != 1 || settled != 2 { - t.Fatalf("committed shape = %d normalized calls, %d denials, %d results, want 2/1/2", normalized, denial, settled) - } - if err := harness.Recover(context.Background(), store); err != nil { - t.Fatalf("Recover: %v", err) + testassert.Require(t, normalized == 2 && denial == 1 && settled == 2, "committed shape = %d normalized calls, %d denials, %d results, want 2/1/2", normalized, denial, settled) + { + err := harness.Recover(context.Background(), store) + testassert.NoError(t, err) } after, err := f.h.ReadOperation(context.Background(), session, "op-1") - if err != nil || after.Admission.Execution.WriteDir != "/w/sub" || !after.Admission.Execution.Readonly { - t.Fatalf("capture after recovery = %+v err %v, want the same durable constraints", after.Admission.Execution, err) - } + testassert.Require(t, err == nil && after.Admission.Execution.WriteDir == "/w/sub" && after.Admission.Execution.Readonly, "capture after recovery = %+v err %v, want the same durable constraints", after.Admission.Execution, err) }) } @@ -2055,9 +1945,7 @@ func TestPublicPreparedPermissionBoundarySurvivesRestart(t *testing.T) { func sessionRegister(t *testing.T, store harness.Storage, sessionID string) harness.Register { t.Helper() reg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("read session register: %v", err) - } + testassert.NoError(t, err) return reg } @@ -2076,56 +1964,35 @@ func TestPublicSessionLifecycle(t *testing.T) { defer f.close() session := createSession(t, f.h) first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || first.Lifecycle != harness.LifecycleOpen || first.Revision.DurableRevision != 1 { - t.Fatalf("created session = %+v err %v, want open at revision 1", first, err) - } + testassert.Require(t, err == nil && first.Lifecycle == harness.LifecycleOpen && first.Revision.DurableRevision == 1, "created session = %+v err %v, want open at revision 1", first, err) originalActivity := first.LastActivity reopened, err := f.h.ReopenSession(ctx, session) - if err != nil || reopened.State.Lifecycle != harness.LifecycleOpen || reopened.State.ArchivedAt != nil || - reopened.Revision != first.Revision.DurableRevision || !reopened.State.LastActivity.Equal(originalActivity) { - t.Fatalf("reopen of open = %+v err %v, want the no-write success", reopened, err) - } + testassert.Require(t, err == nil && reopened.State.Lifecycle == harness.LifecycleOpen && reopened.State.ArchivedAt == nil && reopened.Revision == first.Revision.DurableRevision && reopened.State.LastActivity.Equal(originalActivity), "reopen of open = %+v err %v, want the no-write success", reopened, err) archived, err := f.h.ArchiveSession(ctx, session) - if err != nil || archived.State.Lifecycle != harness.LifecycleArchived || archived.Revision != first.Revision.DurableRevision+1 { - t.Fatalf("archive of open = %+v err %v, want archived at the next revision", archived, err) - } - if reg := sessionRegister(t, store, session); reg.Revision != archived.Revision { - t.Fatalf("durable register revision %d disagrees with the returned record's %d", reg.Revision, archived.Revision) - } - if archived.State.ArchivedAt == nil || archived.State.ArchivedAt.IsZero() || - archived.State.ArchivedAt.Location() != time.UTC { - t.Fatalf("archived_at = %v, want one sampled UTC time", archived.State.ArchivedAt) - } - if !archived.State.LastActivity.Equal(originalActivity) { - t.Fatalf("archive moved last activity to %v, want it unchanged at %v", archived.State.LastActivity, originalActivity) + testassert.Require(t, err == nil && archived.State.Lifecycle == harness.LifecycleArchived && archived.Revision == first.Revision.DurableRevision+1, "archive of open = %+v err %v, want archived at the next revision", archived, err) + { + reg := sessionRegister(t, store, session) + testassert.Equal(t, reg.Revision, archived.Revision) } + testassert.Require(t, archived.State.ArchivedAt != nil && !archived.State.ArchivedAt.IsZero() && archived.State.ArchivedAt.Location() == time.UTC, "archived_at = %v, want one sampled UTC time", archived.State.ArchivedAt) + testassert.Require(t, archived.State.LastActivity.Equal(originalActivity), "archive moved last activity to %v, want it unchanged at %v", archived.State.LastActivity, originalActivity) again, err := f.h.ArchiveSession(ctx, session) - if err != nil || again.Revision != archived.Revision || !again.State.ArchivedAt.Equal(*archived.State.ArchivedAt) { - t.Fatalf("archive of archived = %+v err %v, want the no-write success", again, err) - } + testassert.Require(t, err == nil && again.Revision == archived.Revision && again.State.ArchivedAt.Equal(*archived.State.ArchivedAt), "archive of archived = %+v err %v, want the no-write success", again, err) back, err := f.h.ReopenSession(ctx, session) - if err != nil || back.State.Lifecycle != harness.LifecycleOpen || back.State.ArchivedAt != nil || - back.Revision != archived.Revision+1 { - t.Fatalf("reopen of archived = %+v err %v, want open with the archived time cleared", back, err) - } - if reg := sessionRegister(t, store, session); reg.Revision != back.Revision { - t.Fatalf("durable register revision %d disagrees with the returned record's %d", reg.Revision, back.Revision) - } - if !back.State.LastActivity.After(originalActivity) { - t.Fatalf("reopen left last activity at %v, want it advanced past %v", back.State.LastActivity, originalActivity) - } - if back.Identity != first.Identity || back.State.CurrentAgentType != "coder" { - t.Fatalf("reopen changed identity or Agent type: %+v", back) + testassert.Require(t, err == nil && back.State.Lifecycle == harness.LifecycleOpen && back.State.ArchivedAt == nil && back.Revision == archived.Revision+1, "reopen of archived = %+v err %v, want open with the archived time cleared", back, err) + { + reg := sessionRegister(t, store, session) + testassert.Equal(t, reg.Revision, back.Revision) } + testassert.Require(t, back.State.LastActivity.After(originalActivity), "reopen left last activity at %v, want it advanced past %v", back.State.LastActivity, originalActivity) + testassert.Require(t, back.Identity == first.Identity && back.State.CurrentAgentType == "coder", "reopen changed identity or Agent type: %+v", back) againOpen, err := f.h.ReopenSession(ctx, session) - if err != nil || againOpen.Revision != back.Revision || !againOpen.State.LastActivity.Equal(back.State.LastActivity) { - t.Fatalf("second reopen = %+v err %v, want the no-write success", againOpen, err) - } + testassert.Require(t, err == nil && againOpen.Revision == back.Revision && againOpen.State.LastActivity.Equal(back.State.LastActivity), "second reopen = %+v err %v, want the no-write success", againOpen, err) }) t.Run("archive rejects a running session without losing its buffers", func(t *testing.T) { @@ -2134,34 +2001,34 @@ func TestPublicSessionLifecycle(t *testing.T) { f := newPublicFixture(t, store, script, nil) defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-f.prepare // the first admission prepared <-script.arrived // parked at the model boundary: the Session is running - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } before := sessionRegister(t, store, session) - if _, err := f.h.ArchiveSession(ctx, session); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("archive of a running session = err %v, want ErrInvalid", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "archive of a running session = err %v, want ErrInvalid", err) } after := sessionRegister(t, store, session) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("rejected archive changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "rejected archive changed the durable register (revision %d -> %d)", before.Revision, after.Revision) script.releaseGate() <-f.prepare // the rejected archive kept the buffered item: the drain admitted it <-script.arrived // the admitted item reached its model boundary: its admission committed - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } drained, err := f.h.ReadOperation(ctx, session, "op-2") - if err != nil || drained.State.Terminal == nil { - t.Fatalf("buffered item after rejected archive = %+v err %v, want it delivered and settled", drained, err) - } + testassert.Require(t, err == nil && drained.State.Terminal != nil, "buffered item after rejected archive = %+v err %v, want it delivered and settled", drained, err) }) t.Run("delete requires archived and invalidates the coordinator", func(t *testing.T) { @@ -2170,56 +2037,61 @@ func TestPublicSessionLifecycle(t *testing.T) { session := createSession(t, f.h) before := sessionRegister(t, store, session) - if err := f.h.DeleteSession(ctx, session); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("delete of an open session = err %v, want ErrInvalid", err) + { + err := f.h.DeleteSession(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "delete of an open session = err %v, want ErrInvalid", err) } after := sessionRegister(t, store, session) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("rejected delete changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "rejected delete changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - if _, err := f.h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.NoError(t, err) } - if err := f.h.DeleteSession(ctx, session); err != nil { - t.Fatalf("delete of archived: %v", err) + { + err := f.h.DeleteSession(ctx, session) + testassert.NoError(t, err) } key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} - if _, err := store.ReadRegister(ctx, key); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("deleted session register = err %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, key) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "deleted session register = err %v, want ErrNotFound", err) } ids, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("list sessions: %v", err) - } + testassert.NoError(t, err) for _, id := range ids { // deletion is absence: no tombstone, no listing - if id == session { - t.Fatalf("deleted session still listed") - } + testassert.Require(t, id != session, "deleted session still listed") } // holders of the materialized coordinator and later materialization both get ErrNotFound - if _, err := f.h.ReadSessionHeader(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read after delete = err %v, want ErrNotFound, not a stale record", err) + { + _, err := f.h.ReadSessionHeader(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read after delete = err %v, want ErrNotFound, not a stale record", err) } - if _, err := f.h.ReadOperation(ctx, session, "op-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("operation read after delete = err %v, want ErrNotFound", err) + { + _, err := f.h.ReadOperation(ctx, session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "operation read after delete = err %v, want ErrNotFound", err) } - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "late"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("submit after delete = err %v, want ErrNotFound", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "late") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "submit after delete = err %v, want ErrNotFound", err) } - if _, err := f.h.ChangeAgentType(ctx, session, "reviewer"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("agent-type change after delete = err %v, want ErrNotFound", err) + { + _, err := f.h.ChangeAgentType(ctx, session, "reviewer") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "agent-type change after delete = err %v, want ErrNotFound", err) } - if _, err := f.h.ReopenSession(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("reopen after delete = err %v, want ErrNotFound", err) + { + _, err := f.h.ReopenSession(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "reopen after delete = err %v, want ErrNotFound", err) } - if _, err := f.h.ArchiveSession(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("archive after delete = err %v, want ErrNotFound", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "archive after delete = err %v, want ErrNotFound", err) } - if err := f.h.DeleteSession(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("second delete = err %v, want ErrNotFound", err) + { + err := f.h.DeleteSession(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "second delete = err %v, want ErrNotFound", err) } }) @@ -2227,24 +2099,30 @@ func TestPublicSessionLifecycle(t *testing.T) { f := newPublicFixture(t, store, newScriptModel(), nil) defer f.close() unknown := "0123456789abcdef0123456789abcdef" // valid hex identity, absent Session - if _, err := f.h.ReopenSession(ctx, unknown); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("reopen of unknown = err %v, want ErrNotFound", err) + { + _, err := f.h.ReopenSession(ctx, unknown) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "reopen of unknown = err %v, want ErrNotFound", err) } - if _, err := f.h.ArchiveSession(ctx, unknown); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("archive of unknown = err %v, want ErrNotFound", err) + { + _, err := f.h.ArchiveSession(ctx, unknown) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "archive of unknown = err %v, want ErrNotFound", err) } - if err := f.h.DeleteSession(ctx, unknown); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("delete of unknown = err %v, want ErrNotFound", err) + { + err := f.h.DeleteSession(ctx, unknown) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "delete of unknown = err %v, want ErrNotFound", err) } malformed := "not-a-session-id" - if _, err := f.h.ReopenSession(ctx, malformed); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("reopen of malformed = err %v, want ErrInvalid", err) + { + _, err := f.h.ReopenSession(ctx, malformed) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "reopen of malformed = err %v, want ErrInvalid", err) } - if _, err := f.h.ArchiveSession(ctx, malformed); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("archive of malformed = err %v, want ErrInvalid", err) + { + _, err := f.h.ArchiveSession(ctx, malformed) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "archive of malformed = err %v, want ErrInvalid", err) } - if err := f.h.DeleteSession(ctx, malformed); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("delete of malformed = err %v, want ErrInvalid", err) + { + err := f.h.DeleteSession(ctx, malformed) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "delete of malformed = err %v, want ErrInvalid", err) } }) }) @@ -2262,42 +2140,33 @@ func TestPublicSweepArchiveBoundary(t *testing.T) { ctx := context.Background() session := createSession(t, f.h) first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } + testassert.NoError(t, err) policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour, DeleteAfterArchive: 12 * time.Hour} boundary := first.LastActivity.Add(policy.ArchiveAfter) before := sessionRegister(t, store, session) - if _, err := f.h.SweepSession(ctx, session, policy, boundary); err != nil { - t.Fatalf("sweep at the boundary: %v", err) + { + _, err := f.h.SweepSession(ctx, session, policy, boundary) + testassert.NoError(t, err) } still, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || still.Lifecycle != harness.LifecycleOpen || still.Revision != first.Revision { - t.Fatalf("boundary sweep = %+v err %v, want the open Session unchanged", still, err) - } + testassert.Require(t, err == nil && still.Lifecycle == harness.LifecycleOpen && still.Revision == first.Revision, "boundary sweep = %+v err %v, want the open Session unchanged", still, err) after := sessionRegister(t, store, session) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("no-op boundary sweep changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "no-op boundary sweep changed the durable register (revision %d -> %d)", before.Revision, after.Revision) past := boundary.Add(time.Nanosecond) - if _, err := f.h.SweepSession(ctx, session, policy, past); err != nil { - t.Fatalf("sweep past the boundary: %v", err) + { + _, err := f.h.SweepSession(ctx, session, policy, past) + testassert.NoError(t, err) } archived, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || archived.Lifecycle != harness.LifecycleArchived || archived.Revision.DurableRevision != first.Revision.DurableRevision+1 { - t.Fatalf("past-boundary sweep = %+v err %v, want archived at the next revision", archived, err) - } - if reg := sessionRegister(t, store, session); reg.Revision != archived.Revision.DurableRevision { - t.Fatalf("durable register revision %d disagrees with the swept record's %d", reg.Revision, archived.Revision.DurableRevision) - } - if archived.ArchivedAt == nil || !archived.ArchivedAt.Equal(past) { - t.Fatalf("sweep archive stamped %v, want the explicit sweep time %v", archived.ArchivedAt, past) - } - if !archived.LastActivity.Equal(first.LastActivity) { - t.Fatalf("sweep archive moved last activity to %v, want it unchanged", archived.LastActivity) + testassert.Require(t, err == nil && archived.Lifecycle == harness.LifecycleArchived && archived.Revision.DurableRevision == first.Revision.DurableRevision+1, "past-boundary sweep = %+v err %v, want archived at the next revision", archived, err) + { + reg := sessionRegister(t, store, session) + testassert.Equal(t, reg.Revision, archived.Revision.DurableRevision) } + testassert.Require(t, archived.ArchivedAt != nil && archived.ArchivedAt.Equal(past), "sweep archive stamped %v, want the explicit sweep time %v", archived.ArchivedAt, past) + testassert.Require(t, archived.LastActivity.Equal(first.LastActivity), "sweep archive moved last activity to %v, want it unchanged", archived.LastActivity) }) } @@ -2313,42 +2182,39 @@ func TestPublicSweepDeleteBoundary(t *testing.T) { ctx := context.Background() session := createSession(t, f.h) archived, err := f.h.ArchiveSession(ctx, session) - if err != nil || archived.State.ArchivedAt == nil { - t.Fatalf("archive = %+v err %v", archived, err) - } + testassert.Require(t, err == nil && archived.State.ArchivedAt != nil, "archive = %+v err %v", archived, err) policy := harness.SweepPolicy{DeleteAfterArchive: 12 * time.Hour} boundary := archived.State.ArchivedAt.Add(policy.DeleteAfterArchive) before := sessionRegister(t, store, session) - if _, err := f.h.SweepSession(ctx, session, policy, boundary); err != nil { - t.Fatalf("sweep at the delete boundary: %v", err) + { + _, err := f.h.SweepSession(ctx, session, policy, boundary) + testassert.NoError(t, err) } - if _, err := f.h.ReadSessionHeader(ctx, session); err != nil { - t.Fatalf("delete-boundary sweep removed the session: %v", err) + { + _, err := f.h.ReadSessionHeader(ctx, session) + testassert.NoError(t, err) } after := sessionRegister(t, store, session) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("no-op delete-boundary sweep changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "no-op delete-boundary sweep changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - if _, err := f.h.SweepSession(ctx, session, policy, boundary.Add(time.Nanosecond)); err != nil { - t.Fatalf("sweep past the delete boundary: %v", err) + { + _, err := f.h.SweepSession(ctx, session, policy, boundary.Add(time.Nanosecond)) + testassert.NoError(t, err) } key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} - if _, err := store.ReadRegister(ctx, key); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("sweep-deleted session register = err %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, key) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "sweep-deleted session register = err %v, want ErrNotFound", err) } ids, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("list sessions: %v", err) - } + testassert.NoError(t, err) for _, id := range ids { - if id == session { - t.Fatalf("sweep-deleted session still listed") - } + testassert.Require(t, id != session, "sweep-deleted session still listed") } - if _, err := f.h.ReadSessionHeader(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read after sweep delete = err %v, want ErrNotFound, not a stale record", err) + { + _, err := f.h.ReadSessionHeader(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read after sweep delete = err %v, want ErrNotFound, not a stale record", err) } }) } @@ -2366,31 +2232,26 @@ func TestPublicSweepDisabledThresholds(t *testing.T) { defer f.close() open := createSession(t, f.h) archivedID := createSession(t, f.h) - if _, err := f.h.ArchiveSession(ctx, archivedID); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f.h.ArchiveSession(ctx, archivedID) + testassert.NoError(t, err) } beforeOpen, err := f.h.ReadSessionHeader(ctx, open) - if err != nil { - t.Fatalf("read open: %v", err) - } + testassert.NoError(t, err) beforeArchived, err := f.h.ReadSessionHeader(ctx, archivedID) - if err != nil { - t.Fatalf("read archived: %v", err) - } - if _, err := f.h.SweepSession(ctx, open, harness.SweepPolicy{}, farPast(beforeOpen.LastActivity)); err != nil { - t.Fatalf("sweep with disabled thresholds: %v", err) + testassert.NoError(t, err) + { + _, err := f.h.SweepSession(ctx, open, harness.SweepPolicy{}, farPast(beforeOpen.LastActivity)) + testassert.NoError(t, err) } - if _, err := f.h.SweepSession(ctx, archivedID, harness.SweepPolicy{}, farPast(beforeOpen.LastActivity)); err != nil { - t.Fatalf("sweep with disabled thresholds: %v", err) + { + _, err := f.h.SweepSession(ctx, archivedID, harness.SweepPolicy{}, farPast(beforeOpen.LastActivity)) + testassert.NoError(t, err) } afterOpen, err := f.h.ReadSessionHeader(ctx, open) - if err != nil || afterOpen.Revision != beforeOpen.Revision || afterOpen.Lifecycle != harness.LifecycleOpen { - t.Fatalf("disabled sweep touched the open session: %+v err %v", afterOpen, err) - } + testassert.Require(t, err == nil && afterOpen.Revision == beforeOpen.Revision && afterOpen.Lifecycle == harness.LifecycleOpen, "disabled sweep touched the open session: %+v err %v", afterOpen, err) afterArchived, err := f.h.ReadSessionHeader(ctx, archivedID) - if err != nil || afterArchived.Revision != beforeArchived.Revision || afterArchived.Lifecycle != harness.LifecycleArchived { - t.Fatalf("disabled sweep touched the archived session: %+v err %v", afterArchived, err) - } + testassert.Require(t, err == nil && afterArchived.Revision == beforeArchived.Revision && afterArchived.Lifecycle == harness.LifecycleArchived, "disabled sweep touched the archived session: %+v err %v", afterArchived, err) }) t.Run("delete disabled archives but does not delete", func(t *testing.T) { @@ -2398,29 +2259,30 @@ func TestPublicSweepDisabledThresholds(t *testing.T) { defer f.close() open := createSession(t, f.h) archivedID := createSession(t, f.h) - if _, err := f.h.ArchiveSession(ctx, archivedID); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f.h.ArchiveSession(ctx, archivedID) + testassert.NoError(t, err) } beforeOpen, err := f.h.ReadSessionHeader(ctx, open) - if err != nil { - t.Fatalf("read open: %v", err) - } + testassert.NoError(t, err) policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour} - if _, err := f.h.SweepSession(ctx, open, policy, farPast(beforeOpen.LastActivity)); err != nil { - t.Fatalf("sweep: %v", err) + { + _, err := f.h.SweepSession(ctx, open, policy, farPast(beforeOpen.LastActivity)) + testassert.NoError(t, err) } - if _, err := f.h.SweepSession(ctx, archivedID, policy, farPast(beforeOpen.LastActivity)); err != nil { - t.Fatalf("sweep: %v", err) + { + _, err := f.h.SweepSession(ctx, archivedID, policy, farPast(beforeOpen.LastActivity)) + testassert.NoError(t, err) } sweptOpen, err := f.h.ReadSessionHeader(ctx, open) - if err != nil || sweptOpen.Lifecycle != harness.LifecycleArchived { - t.Fatalf("enabled archive transition = %+v err %v, want archived", sweptOpen, err) - } - if _, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: archivedID, Kind: harness.RegisterSession}); err != nil { - t.Fatalf("disabled delete removed the archived session: %v", err) + testassert.Require(t, err == nil && sweptOpen.Lifecycle == harness.LifecycleArchived, "enabled archive transition = %+v err %v, want archived", sweptOpen, err) + { + _, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: archivedID, Kind: harness.RegisterSession}) + testassert.NoError(t, err) } - if _, err := f.h.ReadSessionHeader(ctx, archivedID); err != nil { - t.Fatalf("archived sibling after sweep = err %v, want it still present", err) + { + _, err := f.h.ReadSessionHeader(ctx, archivedID) + testassert.NoError(t, err) } }) @@ -2429,28 +2291,28 @@ func TestPublicSweepDisabledThresholds(t *testing.T) { defer f.close() open := createSession(t, f.h) archivedID := createSession(t, f.h) - if _, err := f.h.ArchiveSession(ctx, archivedID); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f.h.ArchiveSession(ctx, archivedID) + testassert.NoError(t, err) } beforeOpen, err := f.h.ReadSessionHeader(ctx, open) - if err != nil { - t.Fatalf("read open: %v", err) - } + testassert.NoError(t, err) policy := harness.SweepPolicy{DeleteAfterArchive: 12 * time.Hour} now := beforeOpen.LastActivity.Add(100 * time.Hour) - if _, err := f.h.SweepSession(ctx, open, policy, now); err != nil { - t.Fatalf("sweep: %v", err) + { + _, err := f.h.SweepSession(ctx, open, policy, now) + testassert.NoError(t, err) } - if _, err := f.h.SweepSession(ctx, archivedID, policy, now); err != nil { - t.Fatalf("sweep: %v", err) + { + _, err := f.h.SweepSession(ctx, archivedID, policy, now) + testassert.NoError(t, err) } stillOpen, err := f.h.ReadSessionHeader(ctx, open) - if err != nil || stillOpen.Lifecycle != harness.LifecycleOpen { - t.Fatalf("disabled archive transition = %+v err %v, want the session still open", stillOpen, err) - } + testassert.Require(t, err == nil && stillOpen.Lifecycle == harness.LifecycleOpen, "disabled archive transition = %+v err %v, want the session still open", stillOpen, err) key := harness.RegisterKey{SessionID: archivedID, Kind: harness.RegisterSession} - if _, err := store.ReadRegister(ctx, key); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("enabled delete transition left the archived session: err %v", err) + { + _, err := store.ReadRegister(ctx, key) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "enabled delete transition left the archived session: err %v", err) } }) }) @@ -2469,47 +2331,41 @@ func TestPublicSweepRunningLeftUnchanged(t *testing.T) { ctx := context.Background() session := createSession(t, f.h) first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } + testassert.NoError(t, err) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-f.prepare // the first admission prepared <-script.arrived // parked at the model boundary: the Session is running - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } active, err := f.h.ReadSessionHeader(ctx, session) // the admission's own write is the sweep's baseline - if err != nil { - t.Fatalf("read active session: %v", err) - } + testassert.NoError(t, err) before := sessionRegister(t, store, session) policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour, DeleteAfterArchive: 12 * time.Hour} - if _, err := f.h.SweepSession(ctx, session, policy, first.LastActivity.Add(100*time.Hour)); err != nil { - t.Fatalf("sweep of a running session: %v", err) + { + deleted, err := f.h.SweepSession(ctx, session, policy, first.LastActivity.Add(100*time.Hour)) + testassert.Require(t, err == nil && !deleted, "sweep of the running session = (%v, %v), want the no-transition outcome", deleted, err) } still, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || still.Lifecycle != harness.LifecycleOpen || still.Revision != active.Revision { - t.Fatalf("sweep touched the running session: %+v err %v, want it unchanged", still, err) - } + testassert.Require(t, err == nil && still.Lifecycle == harness.LifecycleOpen && still.Revision == active.Revision, "sweep touched the running session: %+v err %v, want it unchanged", still, err) after := sessionRegister(t, store, session) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("sweep of a running session changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "sweep of a running session changed the durable register (revision %d -> %d)", before.Revision, after.Revision) script.releaseGate() <-f.prepare // the buffered item was not lost by the sweep: the drain admitted it <-script.arrived // the admitted item reached its model boundary: its admission committed - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } drained, err := f.h.ReadOperation(ctx, session, "op-2") - if err != nil || drained.State.Terminal == nil { - t.Fatalf("buffered item after sweep = %+v err %v, want it delivered and settled", drained, err) - } + testassert.Require(t, err == nil && drained.State.Terminal != nil, "buffered item after sweep = %+v err %v, want it delivered and settled", drained, err) }) } @@ -2530,55 +2386,38 @@ func TestPublicSweepCorruptSibling(t *testing.T) { // corrupt the register directly through the storage contract: syntactically // valid JSON whose state section violates the closed lifecycle enum reg := sessionRegister(t, store, corrupt) - var obj map[string]json.RawMessage - if err := json.Unmarshal(reg.Payload, &obj); err != nil { - t.Fatalf("unmarshal session payload: %v", err) - } - var state map[string]json.RawMessage - if err := json.Unmarshal(obj["state"], &state); err != nil { - t.Fatalf("unmarshal state section: %v", err) - } - state["lifecycle"] = json.RawMessage(`"bogus"`) - newState, err := json.Marshal(state) - if err != nil { - t.Fatalf("marshal state section: %v", err) - } - obj["state"] = newState - bad, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal session payload: %v", err) - } + bad := editStateObject(t, reg.Payload, func(state map[string]json.RawMessage) { + state["lifecycle"] = json.RawMessage(`"bogus"`) + }) key := harness.RegisterKey{SessionID: corrupt, Kind: harness.RegisterSession} - if err := store.Transact(ctx, func(tx harness.Transaction) error { - _, err := tx.ReplaceRegister(key, reg.Revision, bad) - return err - }); err != nil { - t.Fatalf("corrupt the register: %v", err) + { + err := store.Transact(ctx, func(tx harness.Transaction) error { + _, err := tx.ReplaceRegister(key, reg.Revision, bad) + return err + }) + testassert.NoError(t, err) } validBefore, err := f.h.ReadSessionHeader(ctx, valid) - if err != nil { - t.Fatalf("read valid sibling: %v", err) - } + testassert.NoError(t, err) policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour, DeleteAfterArchive: 12 * time.Hour} when := validBefore.LastActivity.Add(100 * time.Hour) - if _, err := f.h.SweepSession(ctx, corrupt, policy, when); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("sweep of the corrupt session = %v, want the corruption error and the row left in place", err) + { + _, err := f.h.SweepSession(ctx, corrupt, policy, when) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "sweep of the corrupt session = %v, want the corruption error and the row left in place", err) } - if _, err := f.h.SweepSession(ctx, valid, policy, when); err != nil { - t.Fatalf("sweep of the valid sibling = %v, want it archived", err) + { + _, err := f.h.SweepSession(ctx, valid, policy, when) + testassert.NoError(t, err) } untouched := sessionRegister(t, store, corrupt) // the corruption write itself took revision reg.Revision+1 - if !bytes.Equal(untouched.Payload, bad) || untouched.Revision != reg.Revision+1 { - t.Fatalf("sweep changed the corrupt register (revision %d -> %d)", reg.Revision+1, untouched.Revision) - } + testassert.Require(t, bytes.Equal(untouched.Payload, bad) && untouched.Revision == reg.Revision+1, "sweep changed the corrupt register (revision %d -> %d)", reg.Revision+1, untouched.Revision) swept, err := f.h.ReadSessionHeader(ctx, valid) - if err != nil || swept.Lifecycle != harness.LifecycleArchived { - t.Fatalf("valid sibling after sweep = %+v err %v, want it archived", swept, err) - } - if _, err := f.h.ReadSessionHeader(ctx, corrupt); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("corrupt session after sweep = err %v, want the corruption error", err) + testassert.Require(t, err == nil && swept.Lifecycle == harness.LifecycleArchived, "valid sibling after sweep = %+v err %v, want it archived", swept, err) + { + _, err := f.h.ReadSessionHeader(ctx, corrupt) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "corrupt session after sweep = err %v, want the corruption error", err) } }) } @@ -2637,8 +2476,9 @@ func (s *gatedEntryStore) ReadEntries(ctx context.Context, sessionID string, aft func seedArchivedSession(t *testing.T, store harness.Storage) string { t.Helper() var buf [16]byte - if _, err := rand.Read(buf[:]); err != nil { - t.Fatalf("random session id: %v", err) + { + _, err := rand.Read(buf[:]) + testassert.NoError(t, err) } id := hex.EncodeToString(buf[:]) now := time.Now().UTC().Format(time.RFC3339Nano) @@ -2653,9 +2493,7 @@ func seedArchivedSession(t *testing.T, store harness.Storage) string { }) return err }) - if err != nil { - t.Fatalf("seed archived session: %v", err) - } + testassert.NoError(t, err) return id } @@ -2672,23 +2510,18 @@ func TestPublicSweepZeroTime(t *testing.T) { defer f.close() policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour, DeleteAfterArchive: 12 * time.Hour} - if deleted, err := f.h.SweepSession(ctx, session, policy, time.Time{}); !errors.Is(err, harness.ErrInvalid) || deleted { - t.Fatalf("sweep with a zero time = (%v, %v), want ErrInvalid and no transition", deleted, err) - } - if counting.reads != 0 { - t.Fatalf("zero-time sweep performed %d materialization reads, want none before the rejection", counting.reads) + { + deleted, err := f.h.SweepSession(ctx, session, policy, time.Time{}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid) && !deleted, "sweep with a zero time = (%v, %v), want ErrInvalid and no transition", deleted, err) } + testassert.Equal(t, counting.reads, 0) // An ordinary valid read reaches the same counter through the cold // fixture, so the zero count above cannot pass vacuously, and shows // the seeded Session untouched by the rejection. header, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || header.Identity.SessionID != session || header.Lifecycle != harness.LifecycleArchived { - t.Fatalf("read after the rejected zero-time sweep = %+v err %v, want the seeded archived Session present and unchanged", header, err) - } - if counting.reads == 0 { - t.Fatalf("the cold fixture never reached the read counter: the zero-read assertion cannot pass vacuously") - } + testassert.Require(t, err == nil && header.Identity.SessionID == session && header.Lifecycle == harness.LifecycleArchived, "read after the rejected zero-time sweep = %+v err %v, want the seeded archived Session present and unchanged", header, err) + testassert.Require(t, counting.reads != 0, "the cold fixture never reached the read counter: the zero-read assertion cannot pass vacuously") }) } @@ -2708,9 +2541,7 @@ func TestPublicSweepSkipsBlockedPreparation(t *testing.T) { ctx := context.Background() session := createSession(t, f.h) // the only Session: the sweep reaches the reserved candidate first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } + testassert.NoError(t, err) prepStarted := make(chan struct{}, 1) releasePrep := make(chan struct{}) @@ -2751,35 +2582,29 @@ func TestPublicSweepSkipsBlockedPreparation(t *testing.T) { }() select { case err := <-sweepDone: - if err != nil { - t.Fatalf("sweep over a blocked preparation = %v, want the no-transition skip", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the sweep waited for the blocked preparation: busy candidates skip without waiting") } // The skip happened before the preparation released: nothing // transitioned while the reservation was still held. still, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || still.Lifecycle != harness.LifecycleOpen || still.CurrentOperationID != "" { - t.Fatalf("sweep transitioned the Session under a blocked preparation: %+v err %v", still, err) - } + testassert.Require(t, err == nil && still.Lifecycle == harness.LifecycleOpen && still.CurrentOperationID == "", "sweep transitioned the Session under a blocked preparation: %+v err %v", still, err) close(releasePrep) // the preparation completes: the admission commits and the Session becomes running out := <-submitDone - if out.err != nil || out.res.Disposition != harness.DispositionAdmitted { - t.Fatalf("first submit = %+v err %v, want admitted", out.res, out.err) - } + testassert.Require(t, out.err == nil && out.res.Disposition == harness.DispositionAdmitted, "first submit = %+v err %v, want admitted", out.res, out.err) settled, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || settled.Lifecycle != harness.LifecycleOpen || settled.CurrentOperationID != "op-1" { - t.Fatalf("post-commit header = %+v err %v, want the running Session unchanged", settled, err) - } - if _, err := f.h.ReadOperation(ctx, session, "op-1"); err != nil { - t.Fatalf("blocked-preparation admission = err %v, want it admitted after the sweep skipped", err) + testassert.Require(t, err == nil && settled.Lifecycle == harness.LifecycleOpen && settled.CurrentOperationID == "op-1", "post-commit header = %+v err %v, want the running Session unchanged", settled, err) + { + _, err := f.h.ReadOperation(ctx, session, "op-1") + testassert.NoError(t, err) } script.releaseGate() - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } }) } @@ -2812,23 +2637,22 @@ func TestPublicDeleteVsMaterialization(t *testing.T) { } close(gated.release) // validation now completes on the captured pre-deletion value - if err := <-readDone; err != nil && !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("load across a deletion = err %v, want the pre-deletion capture or ErrNotFound", err) + { + err := <-readDone + testassert.Require(t, err == nil || errors.Is(err, harness.ErrNotFound), "load across a deletion = err %v, want the pre-deletion capture or ErrNotFound", err) } - if err := <-deleteDone; err != nil { - t.Fatalf("delete after the load released the cell = %v, want nil", err) + { + err := <-deleteDone + testassert.NoError(t, err) } - if _, err := f.h.ReadSessionHeader(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("later materialization installed a stale coordinator: err %v, want ErrNotFound", err) + { + _, err := f.h.ReadSessionHeader(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "later materialization installed a stale coordinator: err %v, want ErrNotFound", err) } listed, err := f.h.ListSessions(ctx) - if err != nil { - t.Fatalf("ListSessions: %v", err) - } + testassert.NoError(t, err) for _, row := range listed { - if row.Identity.SessionID == session { - t.Fatalf("the deleted Session stayed listed: %+v", row) - } + testassert.Require(t, row.Identity.SessionID != session, "the deleted Session stayed listed: %+v", row) } }) } @@ -2901,22 +2725,10 @@ func (t *rollbackProbeTransaction) InsertRegister(draft harness.RegisterDraft) ( func assertRollback(t *testing.T, store harness.Storage, h *harness.Harness, session string, before harness.Register, want harness.SessionHeader) { t.Helper() after := sessionRegister(t, store, session) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("failed transition changed the durable register (revision %d -> %d)", before.Revision, after.Revision) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "failed transition changed the durable register (revision %d -> %d)", before.Revision, after.Revision) got, err := h.ReadSessionHeader(context.Background(), session) - if err != nil { - t.Fatalf("read after failed transition: %v", err) - } - if got.Revision != want.Revision || got.Lifecycle != want.Lifecycle || - !got.LastActivity.Equal(want.LastActivity) || - got.CurrentAgentType != want.CurrentAgentType { - t.Fatalf("cached view after failed transition = %+v, want the pre-attempt record", got) - } - if (got.ArchivedAt == nil) != (want.ArchivedAt == nil) || - (got.ArchivedAt != nil && !got.ArchivedAt.Equal(*want.ArchivedAt)) { - t.Fatalf("cached view archived_at after failed transition = %v, want %v", got.ArchivedAt, want.ArchivedAt) - } + testassert.NoError(t, err) + testassert.Require(t, got.Revision == want.Revision && got.Lifecycle == want.Lifecycle && got.LastActivity.Equal(want.LastActivity) && got.CurrentAgentType == want.CurrentAgentType && ((got.ArchivedAt == nil) == (want.ArchivedAt == nil) && (got.ArchivedAt == nil || got.ArchivedAt.Equal(*want.ArchivedAt))), "cached view after failed transition = %+v, want the pre-attempt record; cached view archived_at after failed transition = %v, want %v", got, got.ArchivedAt, want.ArchivedAt) } // TestPublicLifecycleRollbackOnTransactionFailure proves the lifecycle row's @@ -2936,196 +2748,171 @@ func TestPublicLifecycleRollbackOnTransactionFailure(t *testing.T) { {name: "memory", open: func(t *testing.T) harness.Storage { return storage.NewMemory() }}, {name: "sqlite", open: func(t *testing.T) harness.Storage { store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { store.Close() }) return store }}, } - cases := []struct { - name string - run func(t *testing.T, store harness.Storage) - }{ - {"reopen", rollbackReopenCase}, - {"archive", rollbackArchiveCase}, - {"delete", rollbackDeleteCase}, - {"sweep archive", rollbackSweepArchiveCase}, - {"sweep delete", rollbackSweepDeleteCase}, - } for _, variant := range variants { variant := variant - for _, c := range cases { + for _, c := range rollbackCases { c := c - t.Run(variant.name+"/"+c.name, func(t *testing.T) { c.run(t, variant.open(t)) }) - } - } -} - -// rollbackReopenCase proves Reopen's post-mutation rollback on one isolated store. -func rollbackReopenCase(t *testing.T, store harness.Storage) { - probe := &rollbackProbeStore{Storage: store} - f := newPublicFixture(t, probe, newScriptModel(), nil) - defer f.close() - ctx := context.Background() - session := createSession(t, f.h) - if _, err := f.h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("seed archive: %v", err) - } - archived, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read seed archive: %v", err) - } - before := sessionRegister(t, store, session) - - probe.failReplace = true - if _, err := f.h.ReopenSession(ctx, session); !errors.Is(err, errInjectedRollback) { - t.Fatalf("reopen with injected post-mutation failure = err %v, want the injected rollback", err) - } - assertRollback(t, store, f.h, session, before, archived) - - reopened, err := f.h.ReopenSession(ctx, session) - if err != nil || reopened.State.Lifecycle != harness.LifecycleOpen || reopened.State.ArchivedAt != nil || - reopened.Revision != before.Revision+1 { - t.Fatalf("reopen after rollback = %+v err %v, want open at the next revision", reopened, err) - } -} - -// rollbackArchiveCase proves Archive's post-mutation rollback on one isolated store. -func rollbackArchiveCase(t *testing.T, store harness.Storage) { - probe := &rollbackProbeStore{Storage: store} - f := newPublicFixture(t, probe, newScriptModel(), nil) - defer f.close() - ctx := context.Background() - session := createSession(t, f.h) - first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } - before := sessionRegister(t, store, session) - - probe.failReplace = true - if _, err := f.h.ArchiveSession(ctx, session); !errors.Is(err, errInjectedRollback) { - t.Fatalf("archive with injected post-mutation failure = err %v, want the injected rollback", err) - } - assertRollback(t, store, f.h, session, before, first) - - archived, err := f.h.ArchiveSession(ctx, session) - if err != nil || archived.State.Lifecycle != harness.LifecycleArchived || archived.State.ArchivedAt == nil || - archived.Revision != before.Revision+1 { - t.Fatalf("archive after rollback = %+v err %v, want archived at the next revision", archived, err) - } -} - -// rollbackDeleteCase proves Delete's post-mutation rollback on one isolated store. -func rollbackDeleteCase(t *testing.T, store harness.Storage) { - probe := &rollbackProbeStore{Storage: store} - f := newPublicFixture(t, probe, newScriptModel(), nil) - defer f.close() - ctx := context.Background() - session := createSession(t, f.h) - if _, err := f.h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("seed archive: %v", err) - } - archived, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read seed archive: %v", err) - } - before := sessionRegister(t, store, session) - - probe.failDelete = true - if err := f.h.DeleteSession(ctx, session); !errors.Is(err, errInjectedRollback) { - t.Fatalf("delete with injected post-mutation failure = err %v, want the injected rollback", err) - } - assertRollback(t, store, f.h, session, before, archived) - ids, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("list sessions: %v", err) - } - found := false - for _, id := range ids { // the rolled-back deletion is absence-free: the Session is still listed - if id == session { - found = true - } - } - if !found { - t.Fatalf("rolled-back delete removed the session from the listing") - } - - if err := f.h.DeleteSession(ctx, session); err != nil { - t.Fatalf("delete after rollback: %v", err) - } - key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} - if _, err := store.ReadRegister(ctx, key); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("register after delete = err %v, want ErrNotFound", err) - } + t.Run(variant.name+"/"+c.name, func(t *testing.T) { runRollbackCase(t, variant.open(t), c) }) + } + } +} + +// rollbackCase is one lifecycle transition's rollback row: archived seeds the +// transition's archived precondition (the shared runner archives and reads the +// pre-attempt cached view itself), arm selects the injected post-mutation +// failure point, run performs the transition, afterFailure observes the +// rolled-back state before the retry, and retry performs the same transition +// again and asserts its post-retry outcome. The shared runner owns the +// snapshot/failure/rollback/retry construction; the rows carry only their +// operation and their own expected values. +type rollbackCase struct { + name string + archived bool + arm func(probe *rollbackProbeStore) + run func(t *testing.T, h *harness.Harness, session string, before harness.SessionHeader) error + afterFailure func(t *testing.T, store harness.Storage, session string) + retry func(t *testing.T, store harness.Storage, h *harness.Harness, session string, before harness.SessionHeader) +} + +var rollbackCases = []rollbackCase{ + { + name: "reopen", + archived: true, + arm: func(probe *rollbackProbeStore) { probe.failReplace = true }, + run: func(t *testing.T, h *harness.Harness, session string, _ harness.SessionHeader) error { + _, err := h.ReopenSession(context.Background(), session) + return err + }, + retry: func(t *testing.T, _ harness.Storage, h *harness.Harness, session string, before harness.SessionHeader) { + reopened, err := h.ReopenSession(context.Background(), session) + testassert.Require(t, err == nil && reopened.State.Lifecycle == harness.LifecycleOpen && reopened.State.ArchivedAt == nil && reopened.Revision == before.Revision.DurableRevision+1, "reopen after rollback = %+v err %v, want open at the next revision", reopened, err) + }, + }, + { + name: "archive", + arm: func(probe *rollbackProbeStore) { probe.failReplace = true }, + run: func(t *testing.T, h *harness.Harness, session string, _ harness.SessionHeader) error { + _, err := h.ArchiveSession(context.Background(), session) + return err + }, + retry: func(t *testing.T, _ harness.Storage, h *harness.Harness, session string, before harness.SessionHeader) { + archived, err := h.ArchiveSession(context.Background(), session) + testassert.Require(t, err == nil && archived.State.Lifecycle == harness.LifecycleArchived && archived.State.ArchivedAt != nil && archived.Revision == before.Revision.DurableRevision+1, "archive after rollback = %+v err %v, want archived at the next revision", archived, err) + }, + }, + { + name: "delete", + archived: true, + arm: func(probe *rollbackProbeStore) { probe.failDelete = true }, + run: func(t *testing.T, h *harness.Harness, session string, _ harness.SessionHeader) error { + return h.DeleteSession(context.Background(), session) + }, + afterFailure: func(t *testing.T, store harness.Storage, session string) { + ids, err := store.ListSessionIDs(context.Background()) + testassert.NoError(t, err) + found := false + for _, id := range ids { // the rolled-back deletion is absence-free: the Session is still listed + if id == session { + found = true + } + } + testassert.Require(t, found, "rolled-back delete removed the session from the listing") + }, + retry: func(t *testing.T, store harness.Storage, h *harness.Harness, session string, _ harness.SessionHeader) { + { + err := h.DeleteSession(context.Background(), session) + testassert.NoError(t, err) + } + key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} + { + _, err := store.ReadRegister(context.Background(), key) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "register after delete = err %v, want ErrNotFound", err) + } + }, + }, + { + name: "sweep archive", + arm: func(probe *rollbackProbeStore) { probe.failReplace = true }, + run: func(t *testing.T, h *harness.Harness, session string, before harness.SessionHeader) error { + policy := harness.SweepPolicy{ArchiveAfter: time.Hour} + now := before.LastActivity.Add(100 * time.Hour) + _, err := h.SweepSession(context.Background(), session, policy, now) + return err + }, + retry: func(t *testing.T, _ harness.Storage, h *harness.Harness, session string, before harness.SessionHeader) { + policy := harness.SweepPolicy{ArchiveAfter: time.Hour} + now := before.LastActivity.Add(100 * time.Hour) + { + _, err := h.SweepSession(context.Background(), session, policy, now) + testassert.NoError(t, err) + } + archived, err := h.ReadSessionHeader(context.Background(), session) + testassert.Require(t, err == nil && archived.Lifecycle == harness.LifecycleArchived && archived.Revision.DurableRevision == before.Revision.DurableRevision+1 && archived.ArchivedAt != nil && archived.ArchivedAt.Equal(now), "sweep after rollback = %+v err %v, want archived at the sweep time", archived, err) + }, + }, + { + name: "sweep delete", + archived: true, + arm: func(probe *rollbackProbeStore) { probe.failDelete = true }, + run: func(t *testing.T, h *harness.Harness, session string, before harness.SessionHeader) error { + policy := harness.SweepPolicy{DeleteAfterArchive: time.Hour} + now := before.ArchivedAt.Add(100 * time.Hour) + _, err := h.SweepSession(context.Background(), session, policy, now) + return err + }, + retry: func(t *testing.T, store harness.Storage, h *harness.Harness, session string, before harness.SessionHeader) { + policy := harness.SweepPolicy{DeleteAfterArchive: time.Hour} + now := before.ArchivedAt.Add(100 * time.Hour) + { + _, err := h.SweepSession(context.Background(), session, policy, now) + testassert.NoError(t, err) + } + key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} + { + _, err := store.ReadRegister(context.Background(), key) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "register after swept delete = err %v, want ErrNotFound", err) + } + }, + }, } -// rollbackSweepArchiveCase proves the sweep archive transition's post-mutation -// rollback on one isolated store. -func rollbackSweepArchiveCase(t *testing.T, store harness.Storage) { +// runRollbackCase owns the shared rollback construction: one isolated store, +// the shared archived precondition and pre-attempt header read, the real +// transition, its injected post-mutation failure, the durable and cached +// rollback assertions, the row's own rolled-back observation, and the retry of +// the same transition with its post-retry observation. +func runRollbackCase(t *testing.T, store harness.Storage, row rollbackCase) { + t.Helper() probe := &rollbackProbeStore{Storage: store} f := newPublicFixture(t, probe, newScriptModel(), nil) defer f.close() - ctx := context.Background() session := createSession(t, f.h) - first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } - before := sessionRegister(t, store, session) - - policy := harness.SweepPolicy{ArchiveAfter: time.Hour} - now := first.LastActivity.Add(100 * time.Hour) - probe.failReplace = true - if _, err := f.h.SweepSession(ctx, session, policy, now); !errors.Is(err, errInjectedRollback) { - t.Fatalf("sweep with injected post-mutation failure = err %v, want the transition to return it", err) + var beforeHeader harness.SessionHeader + if row.archived { // the shared archived precondition: the transition rows and retries need the Session archived before the attempt. + archived, err := f.h.ArchiveSession(context.Background(), session) + testassert.Require(t, err == nil && archived.State.ArchivedAt != nil, "seed archive = %+v err %v", archived, err) } - assertRollback(t, store, f.h, session, before, first) - - if _, err := f.h.SweepSession(ctx, session, policy, now); err != nil { - t.Fatalf("sweep after rollback: %v", err) - } - archived, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || archived.Lifecycle != harness.LifecycleArchived || archived.Revision.DurableRevision != before.Revision+1 || - archived.ArchivedAt == nil || !archived.ArchivedAt.Equal(now) { - t.Fatalf("sweep after rollback = %+v err %v, want archived at the sweep time", archived, err) - } -} - -// rollbackSweepDeleteCase proves the sweep delete transition's post-mutation -// rollback on one isolated store. -func rollbackSweepDeleteCase(t *testing.T, store harness.Storage) { - probe := &rollbackProbeStore{Storage: store} - f := newPublicFixture(t, probe, newScriptModel(), nil) - defer f.close() - ctx := context.Background() - session := createSession(t, f.h) - archived, err := f.h.ArchiveSession(ctx, session) - if err != nil || archived.State.ArchivedAt == nil { - t.Fatalf("seed archive = %+v err %v", archived, err) + { + header, err := f.h.ReadSessionHeader(context.Background(), session) + testassert.NoError(t, err) + beforeHeader = header } before := sessionRegister(t, store, session) - - policy := harness.SweepPolicy{DeleteAfterArchive: time.Hour} - now := archived.State.ArchivedAt.Add(100 * time.Hour) - archivedHeader, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read seed archive: %v", err) - } - probe.failDelete = true - if _, err := f.h.SweepSession(ctx, session, policy, now); !errors.Is(err, errInjectedRollback) { - t.Fatalf("sweep with injected post-mutation failure = err %v, want the transition to return it", err) + row.arm(probe) + { + err := row.run(t, f.h, session, beforeHeader) + testassert.Require(t, errors.Is(err, errInjectedRollback), "%s with injected post-mutation failure = err %v, want the injected rollback", row.name, err) } - assertRollback(t, store, f.h, session, before, archivedHeader) - - if _, err := f.h.SweepSession(ctx, session, policy, now); err != nil { - t.Fatalf("sweep after rollback: %v", err) - } - key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} - if _, err := store.ReadRegister(ctx, key); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("register after swept delete = err %v, want ErrNotFound", err) + assertRollback(t, store, f.h, session, before, beforeHeader) + if row.afterFailure != nil { + row.afterFailure(t, store, session) } + row.retry(t, store, f.h, session, beforeHeader) } // TestPublicWaitConvergesWithCorruptingTransition proves the Wait and @@ -3145,44 +2932,33 @@ func TestPublicWaitConvergesWithCorruptingTransition(t *testing.T) { // corrupt the register directly through storage after materialization: // the transition discovers it in-transaction under c.mu reg := sessionRegister(t, store, session) - var obj map[string]json.RawMessage - if err := json.Unmarshal(reg.Payload, &obj); err != nil { - t.Fatalf("unmarshal session payload: %v", err) - } - var state map[string]json.RawMessage - if err := json.Unmarshal(obj["state"], &state); err != nil { - t.Fatalf("unmarshal state section: %v", err) - } - state["lifecycle"] = json.RawMessage(`"bogus"`) - newState, err := json.Marshal(state) - if err != nil { - t.Fatalf("marshal state section: %v", err) - } - obj["state"] = newState - bad, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal session payload: %v", err) - } + bad := editStateObject(t, reg.Payload, func(state map[string]json.RawMessage) { + state["lifecycle"] = json.RawMessage(`"bogus"`) + }) key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterSession} - if err := store.Transact(ctx, func(tx harness.Transaction) error { - _, err := tx.ReplaceRegister(key, reg.Revision, bad) - return err - }); err != nil { - t.Fatalf("corrupt the register: %v", err) + { + err := store.Transact(ctx, func(tx harness.Transaction) error { + _, err := tx.ReplaceRegister(key, reg.Revision, bad) + return err + }) + testassert.NoError(t, err) } f.cancel() // Harness loss before both calls waited := make(chan error, 1) go func() { waited <- f.h.Wait(ctx) }() // Wait begins while the transition is in flight - if _, err := f.h.ArchiveSession(ctx, session); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("archive over a corrupted register = err %v, want the corruption error", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "archive over a corrupted register = err %v, want the corruption error", err) } - if err := <-waited; err != nil { - t.Fatalf("Wait converging with the corrupting transition = %v, want nil", err) + { + err := <-waited + testassert.NoError(t, err) } - if _, err := f.h.ReadSessionHeader(ctx, session); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("corrupt session after convergence = err %v, want the sticky corruption error", err) + { + _, err := f.h.ReadSessionHeader(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "corrupt session after convergence = err %v, want the sticky corruption error", err) } }) } @@ -3221,8 +2997,9 @@ func recoverOpStateAt(t *testing.T, store harness.Storage, sessionID, operationI var wire struct { State recoverOpState `json:"state"` } - if err := json.Unmarshal(reg.Payload, &wire); err != nil { - t.Fatalf("decode operation register %q: %v", operationID, err) + { + err := json.Unmarshal(reg.Payload, &wire) + testassert.Require(t, err == nil, "decode operation register %q: %v", operationID, err) } return wire.State } @@ -3232,9 +3009,7 @@ func recoverOpStateAt(t *testing.T, store harness.Storage, sessionID, operationI func operationRegister(t *testing.T, store harness.Storage, sessionID, operationID string) harness.Register { t.Helper() reg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: operationID}) - if err != nil { - t.Fatalf("read operation register %q: %v", operationID, err) - } + testassert.Require(t, err == nil, "read operation register %q: %v", operationID, err) return reg } @@ -3247,8 +3022,9 @@ func recoverSessionCurrent(t *testing.T, store harness.Storage, sessionID string CurrentOperationID string `json:"current_operation_id"` } `json:"state"` } - if err := json.Unmarshal(reg.Payload, &wire); err != nil { - t.Fatalf("decode session register: %v", err) + { + err := json.Unmarshal(reg.Payload, &wire) + testassert.NoError(t, err) } return wire.State.CurrentOperationID } @@ -3257,9 +3033,7 @@ func recoverSessionCurrent(t *testing.T, store harness.Storage, sessionID string func recoverEntries(t *testing.T, store harness.Storage, sessionID string) []harness.Entry { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("read entries of session %q: %v", sessionID, err) - } + testassert.Require(t, err == nil, "read entries of session %q: %v", sessionID, err) return entries } @@ -3267,8 +3041,9 @@ func recoverEntries(t *testing.T, store harness.Storage, sessionID string) []har func newRawSessionID(t *testing.T) string { t.Helper() var buf [16]byte - if _, err := rand.Read(buf[:]); err != nil { - t.Fatalf("random id: %v", err) + { + _, err := rand.Read(buf[:]) + testassert.NoError(t, err) } return hex.EncodeToString(buf[:]) } @@ -3278,11 +3053,12 @@ func newRawSessionID(t *testing.T) string { // and entries. func insertRawRegister(t *testing.T, store harness.Storage, key harness.RegisterKey, payload string) { t.Helper() - if err := store.Transact(context.Background(), func(tx harness.Transaction) error { - _, err := tx.InsertRegister(harness.RegisterDraft{Key: key, Payload: json.RawMessage(payload)}) - return err - }); err != nil { - t.Fatalf("insert register %v: %v", key, err) + { + err := store.Transact(context.Background(), func(tx harness.Transaction) error { + _, err := tx.InsertRegister(harness.RegisterDraft{Key: key, Payload: json.RawMessage(payload)}) + return err + }) + testassert.Require(t, err == nil, "insert register %v: %v", key, err) } } @@ -3290,11 +3066,12 @@ func insertRawRegister(t *testing.T, store harness.Storage, key harness.Register // contract. The addressed Session must already exist. func insertRawEntry(t *testing.T, store harness.Storage, sessionID, id, operationID string, kind harness.EntryKind, payload string) { t.Helper() - if err := store.Transact(context.Background(), func(tx harness.Transaction) error { - _, err := tx.InsertEntry(harness.EntryDraft{SessionID: sessionID, ID: id, OperationID: operationID, Kind: kind, Payload: json.RawMessage(payload)}) - return err - }); err != nil { - t.Fatalf("insert entry %s: %v", id, err) + { + err := store.Transact(context.Background(), func(tx harness.Transaction) error { + _, err := tx.InsertEntry(harness.EntryDraft{SessionID: sessionID, ID: id, OperationID: operationID, Kind: kind, Payload: json.RawMessage(payload)}) + return err + }) + testassert.Require(t, err == nil, "insert entry %s: %v", id, err) } } @@ -3325,7 +3102,7 @@ func rawAdmission(sessionID, operationID, entryID string) string { `"execution":{"configuration_revision":"rev-1","model":{"provider":"prov","model":"gpt-x"},`+ `"context_window":4096,"output_reserve":2048,`+ `"system_prompt":"system","tools":[%s],"readonly":false,"write_dir":"",`+ - `"compact":{"model":{"provider":"cprov","model":"compact-x"},"context_window":2048,"output_reserve":1024,"system_prompt":"summarize"}},"admitted_at":%q}`, + `"compact":{"model":{"provider":"cprov","model":"compact-x"},"system_prompt":"summarize"}},"admitted_at":%q}`, sessionID, operationID, sessionID, entryID, rawToolDefinition, now) } @@ -3363,12 +3140,7 @@ func rawAssistantEntryPayload(sessionID, entryID, operationID string, calls [][2 // the pending reservations in order. func seedRunningOperationShape(t *testing.T, store harness.Storage, activeEffect, operationID string, calls []string) (sessionID, modelReserved string, pendingResultIDs []string) { t.Helper() - if activeEffect == "model" && len(calls) > 0 { - t.Fatalf("a model effect admits no pending calls") - } - if (activeEffect == "tool" || activeEffect == "hook") && len(calls) == 0 { - t.Fatalf("a %s effect requires a first pending call", activeEffect) - } + testassert.Require(t, (activeEffect != "model" || len(calls) <= 0) && (activeEffect != "tool" && activeEffect != "hook" || len(calls) != 0), "a model effect admits no pending calls; a %s effect requires a first pending call", activeEffect) sessionID = newRawSessionID(t) entryID, assistantID := newRawSessionID(t), newRawSessionID(t) now := time.Now().UTC().Format(time.RFC3339Nano) @@ -3429,13 +3201,9 @@ type sessionSnapshot struct { func snapshotSession(t *testing.T, store harness.Storage, sessionID string) sessionSnapshot { t.Helper() registers, err := store.ReadRegisters(context.Background(), sessionID) - if err != nil { - t.Fatalf("read registers of %q: %v", sessionID, err) - } + testassert.Require(t, err == nil, "read registers of %q: %v", sessionID, err) entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("read entries of %q: %v", sessionID, err) - } + testassert.Require(t, err == nil, "read entries of %q: %v", sessionID, err) return sessionSnapshot{registers: registers, entries: entries} } @@ -3445,21 +3213,14 @@ func snapshotSession(t *testing.T, store harness.Storage, sessionID string) sess func assertSessionUnchanged(t *testing.T, store harness.Storage, sessionID string, before sessionSnapshot) { t.Helper() after := snapshotSession(t, store, sessionID) - if len(after.registers) != len(before.registers) || len(after.entries) != len(before.entries) { - t.Fatalf("recovery changed the record count of corrupt session %q (registers %d -> %d, entries %d -> %d)", - sessionID, len(before.registers), len(after.registers), len(before.entries), len(after.entries)) - } + testassert.Require(t, len(after.registers) == len(before.registers) && len(after.entries) == len(before.entries), "recovery changed the record count of corrupt session %q (registers %d -> %d, entries %d -> %d)", sessionID, len(before.registers), len(after.registers), len(before.entries), len(after.entries)) for i := range before.registers { b, a := before.registers[i], after.registers[i] - if b.Key != a.Key || b.Revision != a.Revision || !bytes.Equal(b.Payload, a.Payload) { - t.Fatalf("recovery changed register %v of corrupt session %q (revision %d -> %d)", b.Key, sessionID, b.Revision, a.Revision) - } + testassert.Require(t, b.Key == a.Key && b.Revision == a.Revision && bytes.Equal(b.Payload, a.Payload), "recovery changed register %v of corrupt session %q (revision %d -> %d)", b.Key, sessionID, b.Revision, a.Revision) } for i := range before.entries { b, a := before.entries[i], after.entries[i] - if b.ID != a.ID || b.Kind != a.Kind || b.Sequence != a.Sequence || !bytes.Equal(b.Payload, a.Payload) { - t.Fatalf("recovery changed entry %s of corrupt session %q", b.ID, sessionID) - } + testassert.Require(t, b.ID == a.ID && b.Kind == a.Kind && b.Sequence == a.Sequence && bytes.Equal(b.Payload, a.Payload), "recovery changed entry %s of corrupt session %q", b.ID, sessionID) } } @@ -3470,9 +3231,7 @@ func harnessOver(t *testing.T, store harness.Storage) *harness.Harness { h, err := harness.New(context.Background(), harness.Dependencies{Storage: store, Prepare: func(context.Context, harness.PreparationRequest) (harness.PreparedExecution, error) { return harness.PreparedExecution{}, errors.New("the recovery suite never prepares") }}) - if err != nil { - t.Fatalf("New after recover: %v", err) - } + testassert.NoError(t, err) return h } @@ -3486,17 +3245,11 @@ func harnessOver(t *testing.T, store harness.Storage) *harness.Harness { func assertRecoveredOperation(t *testing.T, store harness.Storage, sessionID, operationID string, preEntries []harness.Entry, wantSettlementID string, wantInterruptedResultIDs []string) { t.Helper() state := recoverOpStateAt(t, store, sessionID, operationID) - if state.Status != "interruption" || state.ActiveEffect != nil || len(state.PendingToolCalls) != 0 || state.Terminal == nil { - t.Fatalf("recovered operation = %+v, want terminal interruption with no active or pending effects", state) - } - if state.Terminal.Detail != recoveredInterruptionDetail { - t.Fatalf("recovered detail = %q, want the exact recovery detail %q", state.Terminal.Detail, recoveredInterruptionDetail) - } + testassert.Require(t, state.Status == "interruption" && state.ActiveEffect == nil && len(state.PendingToolCalls) == 0 && state.Terminal != nil, "recovered operation = %+v, want terminal interruption with no active or pending effects", state) + testassert.Equal(t, state.Terminal.Detail, recoveredInterruptionDetail) settlementID := state.Terminal.SettlementEntry.EntryID if wantSettlementID != "" { - if settlementID != wantSettlementID { - t.Fatalf("settlement entry = %s, want the reserved identity %s", settlementID, wantSettlementID) - } + testassert.Equal(t, settlementID, wantSettlementID) } else { seen := map[string]bool{} for _, e := range preEntries { @@ -3505,12 +3258,11 @@ func assertRecoveredOperation(t *testing.T, store harness.Storage, sessionID, op for _, id := range wantInterruptedResultIDs { seen[id] = true } - if len(settlementID) != 32 || seen[settlementID] { - t.Fatalf("settlement entry = %q, want a fresh identity unused by the pre-recovery state", settlementID) - } + testassert.Require(t, len(settlementID) == 32 && !seen[settlementID], "settlement entry = %q, want a fresh identity unused by the pre-recovery state", settlementID) } - if current := recoverSessionCurrent(t, store, sessionID); current != "" { - t.Fatalf("session current operation = %q, want it cleared after recovery", current) + { + current := recoverSessionCurrent(t, store, sessionID) + testassert.Equal(t, current, "") } wantTail := make([]harness.EntryKind, 0, len(wantInterruptedResultIDs)+2) for range wantInterruptedResultIDs { @@ -3518,45 +3270,35 @@ func assertRecoveredOperation(t *testing.T, store harness.Storage, sessionID, op } wantTail = append(wantTail, harness.EntrySignal, harness.EntryOperationSettlement) entries := recoverEntries(t, store, sessionID) - if len(entries) != len(preEntries)+len(wantTail) { - t.Fatalf("recovered entries = %d, want the pre-recovery %d plus the %d recovery commits", len(entries), len(preEntries), len(wantTail)) - } + testassert.Require(t, len(entries) == len(preEntries)+len(wantTail), "recovered entries = %d, want the pre-recovery %d plus the %d recovery commits", len(entries), len(preEntries), len(wantTail)) for i, want := range preEntries { - if entries[i].ID != want.ID || entries[i].Kind != want.Kind || !bytes.Equal(entries[i].Payload, want.Payload) { - t.Fatalf("pre-recovery entry %d = %s/%s, want %s/%s unchanged", i, entries[i].ID, entries[i].Kind, want.ID, want.Kind) - } + testassert.Require(t, entries[i].ID == want.ID && entries[i].Kind == want.Kind && bytes.Equal(entries[i].Payload, want.Payload), "pre-recovery entry %d = %s/%s, want %s/%s unchanged", i, entries[i].ID, entries[i].Kind, want.ID, want.Kind) } for i, kind := range wantTail { entry := entries[len(preEntries)+i] - if entry.Kind != kind { - t.Fatalf("recovery commit %d is a %s entry, want %s", i, entry.Kind, kind) - } + testassert.Require(t, entry.Kind == kind, "recovery commit %d is a %s entry, want %s", i, entry.Kind, kind) switch kind { case harness.EntryToolResult: - if entry.ID != wantInterruptedResultIDs[i] { - t.Fatalf("interrupted result %d = %s, want the reserved identity %s", i, entry.ID, wantInterruptedResultIDs[i]) - } + testassert.Require(t, entry.ID == wantInterruptedResultIDs[i], "interrupted result %d = %s, want the reserved identity %s", i, entry.ID, wantInterruptedResultIDs[i]) var wire struct { Status model.ToolResultStatus `json:"status"` Content string `json:"content"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil || wire.Status != model.ResultInterrupted || wire.Content != "Tool call interrupted." { - t.Fatalf("interrupted tool payload = %s, want the ordinary interrupted result", entry.Payload) + { + err := json.Unmarshal(entry.Payload, &wire) + testassert.Require(t, err == nil && wire.Status == model.ResultInterrupted && wire.Content == "Tool call interrupted.", "interrupted tool payload = %s, want the ordinary interrupted result", entry.Payload) } case harness.EntryOperationSettlement: - if entry.ID != settlementID { - t.Fatalf("settlement entry %s disagrees with the operation's terminal reference %s", entry.ID, settlementID) - } + testassert.Equal(t, entry.ID, settlementID) var wire struct { Status string `json:"status"` Detail string `json:"detail"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode settlement entry: %v", err) - } - if wire.Status != "interruption" || wire.Detail != recoveredInterruptionDetail { - t.Fatalf("settlement payload = %+v, want the terminal interruption with the recovery detail", wire) + { + err := json.Unmarshal(entry.Payload, &wire) + testassert.NoError(t, err) } + testassert.Require(t, wire.Status == "interruption" && wire.Detail == recoveredInterruptionDetail, "settlement payload = %+v, want the terminal interruption with the recovery detail", wire) } } } @@ -3569,19 +3311,17 @@ func assertRecoverIdempotent(t *testing.T, store harness.Storage, sessionID, ope opBefore := operationRegister(t, store, sessionID, operationID) sessBefore := sessionRegister(t, store, sessionID) entriesBefore := recoverEntries(t, store, sessionID) - if err := harness.Recover(context.Background(), store); err != nil { - t.Fatalf("second recover: %v", err) + { + err := harness.Recover(context.Background(), store) + testassert.NoError(t, err) } opAfter := operationRegister(t, store, sessionID, operationID) sessAfter := sessionRegister(t, store, sessionID) - if opAfter.Revision != opBefore.Revision || !bytes.Equal(opAfter.Payload, opBefore.Payload) { - t.Fatalf("second recover changed the operation register (revision %d -> %d)", opBefore.Revision, opAfter.Revision) - } - if sessAfter.Revision != sessBefore.Revision || !bytes.Equal(sessAfter.Payload, sessBefore.Payload) { - t.Fatalf("second recover changed the session register (revision %d -> %d)", sessBefore.Revision, sessAfter.Revision) - } - if got := len(recoverEntries(t, store, sessionID)); got != len(entriesBefore) { - t.Fatalf("second recover committed %d entries, want none", got-len(entriesBefore)) + testassert.Require(t, opAfter.Revision == opBefore.Revision && bytes.Equal(opAfter.Payload, opBefore.Payload), "second recover changed the operation register (revision %d -> %d)", opBefore.Revision, opAfter.Revision) + testassert.Require(t, sessAfter.Revision == sessBefore.Revision && bytes.Equal(sessAfter.Payload, sessBefore.Payload), "second recover changed the session register (revision %d -> %d)", sessBefore.Revision, sessAfter.Revision) + { + got := len(recoverEntries(t, store, sessionID)) + testassert.Require(t, got == len(entriesBefore), "second recover committed %d entries, want none", got-len(entriesBefore)) } } @@ -3617,42 +3357,30 @@ func TestPublicRecoverRunningOperationShapes(t *testing.T) { sessionID, modelReserved, pendingResultIDs := seedRunningOperationShape(t, store, shape.activeEffect, "op-1", calls) state := recoverOpStateAt(t, store, sessionID, "op-1") - if state.Status != "running" || len(state.PendingToolCalls) != shape.calls { - t.Fatalf("seeded operation = %+v, want running with %d pending call(s)", state, shape.calls) - } + testassert.Require(t, state.Status == "running" && len(state.PendingToolCalls) == shape.calls, "seeded operation = %+v, want running with %d pending call(s)", state, shape.calls) switch shape.activeEffect { case "model": - if state.ActiveEffect == nil || state.ActiveEffect.Kind != "model" || state.ActiveEffect.ResultEntryID != modelReserved { - t.Fatalf("seeded operation = %+v, want the committed model effect intent", state) - } + testassert.Require(t, state.ActiveEffect != nil && state.ActiveEffect.Kind == "model" && state.ActiveEffect.ResultEntryID == modelReserved, "seeded operation = %+v, want the committed model effect intent", state) case "tool": - if state.ActiveEffect == nil || state.ActiveEffect.Kind != "tool" || state.ActiveEffect.ToolCallID != calls[0] || - state.ActiveEffect.ResultEntryID != pendingResultIDs[0] { - t.Fatalf("seeded operation = %+v, want the committed tool effect intent on %s", state, calls[0]) - } + testassert.Require(t, state.ActiveEffect != nil && state.ActiveEffect.Kind == "tool" && state.ActiveEffect.ToolCallID == calls[0] && state.ActiveEffect.ResultEntryID == pendingResultIDs[0], "seeded operation = %+v, want the committed tool effect intent on %s", state, calls[0]) default: - if state.ActiveEffect != nil { - t.Fatalf("seeded operation = %+v, want no active effect", state) - } + testassert.Require(t, state.ActiveEffect == nil, "seeded operation = %+v, want no active effect", state) } pre := recoverEntries(t, store, sessionID) - if err := harness.Recover(ctx, store); err != nil { // before any live Harness over the store - t.Fatalf("recover: %v", err) + // before any live Harness over the store + { + err := harness.Recover(ctx, store) + testassert.NoError(t, err) } assertRecoveredOperation(t, store, sessionID, "op-1", pre, modelReserved, pendingResultIDs) assertRecoverIdempotent(t, store, sessionID, "op-1") h := harnessOver(t, store) // one Harness after recovery: materialization verification only sess, err := h.ReadSessionHeader(ctx, sessionID) - if err != nil || sess.CurrentOperationID != "" { - t.Fatalf("repaired session = %+v err %v, want open with the current Operation cleared", sess, err) - } + testassert.Require(t, err == nil && sess.CurrentOperationID == "", "repaired session = %+v err %v, want open with the current Operation cleared", sess, err) rec, err := h.ReadOperation(ctx, sessionID, "op-1") - if err != nil || rec.State.Status != harness.OperationInterruption || rec.State.Terminal == nil || - rec.State.Terminal.Detail != recoveredInterruptionDetail { - t.Fatalf("repaired operation = %+v err %v, want the terminal interruption with the recovery detail", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == recoveredInterruptionDetail, "repaired operation = %+v err %v, want the terminal interruption with the recovery detail", rec, err) }) }) } @@ -3844,9 +3572,7 @@ func seedCorruptSiblingHookResult(t *testing.T, store harness.Storage) string { t.Helper() sessionID, _, _ := seedRunningOperationShape(t, store, "", "op-1", []string{"call-1"}) entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("read seeded entries: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.Kind == harness.EntryAssistant { hookID := newRawSessionID(t) @@ -3866,9 +3592,7 @@ func seedCorruptSiblingFailedHook(t *testing.T, store harness.Storage) string { t.Helper() sessionID, _, _ := seedRunningOperationShape(t, store, "", "op-1", []string{"call-1"}) entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("read seeded entries: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.Kind == harness.EntryAssistant { hookID := newRawSessionID(t) @@ -3971,14 +3695,13 @@ func snapAppendOperation(snap *sessionSnapshot, sessionID, operationID, payload func editPayloadObject(t *testing.T, raw json.RawMessage, edit func(map[string]json.RawMessage)) json.RawMessage { t.Helper() obj := map[string]json.RawMessage{} - if err := json.Unmarshal(raw, &obj); err != nil { - t.Fatalf("payload is not a JSON object: %v", err) + { + err := json.Unmarshal(raw, &obj) + testassert.NoError(t, err) } edit(obj) out, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal edited payload: %v", err) - } + testassert.NoError(t, err) return out } @@ -4003,14 +3726,13 @@ func editOperationAdmission(t *testing.T, raw json.RawMessage, edit func(map[str func editJSONArray(t *testing.T, raw json.RawMessage, edit func([]map[string]json.RawMessage)) json.RawMessage { t.Helper() items := []map[string]json.RawMessage{} - if err := json.Unmarshal(raw, &items); err != nil { - t.Fatalf("payload is not a JSON array of objects: %v", err) + { + err := json.Unmarshal(raw, &items) + testassert.NoError(t, err) } edit(items) out, err := json.Marshal(items) - if err != nil { - t.Fatalf("marshal edited array: %v", err) - } + testassert.NoError(t, err) return out } @@ -4024,8 +3746,9 @@ func snapReservedResultID(t *testing.T, snap *sessionSnapshot) string { ResultEntryID string `json:"result_entry_id"` } `json:"tool_calls"` } - if err := json.Unmarshal(assistant.Payload, &wire); err != nil || len(wire.ToolCalls) != 1 { - t.Fatalf("assistant payload has no single published call: %v", err) + { + err := json.Unmarshal(assistant.Payload, &wire) + testassert.Require(t, err == nil && len(wire.ToolCalls) == 1, "assistant payload has no single published call: %v", err) } return wire.ToolCalls[0].ResultEntryID } @@ -4474,8 +4197,9 @@ func TestPublicRecoverCorruptSibling(t *testing.T) { ResultEntryID string `json:"result_entry_id"` } `json:"tool_calls"` } - if err := json.Unmarshal(assistant.Payload, &wire); err != nil || len(wire.ToolCalls) != 2 { - t.Fatalf("seeded assistant has no two published calls: %v", err) + { + err := json.Unmarshal(assistant.Payload, &wire) + testassert.Require(t, err == nil && len(wire.ToolCalls) == 2, "seeded assistant has no two published calls: %v", err) } snapAppendEntry(snap, sessionID, wire.ToolCalls[0].ResultEntryID, "op-1", harness.EntryToolResult, rawToolResultPayload(sessionID, wire.ToolCalls[0].ResultEntryID, "op-1", assistant.ID, "call-1")) @@ -4949,8 +4673,9 @@ func TestPublicRecoverCorruptSibling(t *testing.T) { // The unmutated graph materializes on a separate fresh store. baseline := storage.NewMemory() baselineID := row.seed(t, baseline) - if _, err := harnessOver(t, baseline).ReadSessionHeader(ctx, baselineID); err != nil { - t.Fatalf("unmutated graph does not materialize: %v", err) + { + _, err := harnessOver(t, baseline).ReadSessionHeader(ctx, baselineID) + testassert.NoError(t, err) } targetID := row.seed(t, store) @@ -4961,21 +4686,20 @@ func TestPublicRecoverCorruptSibling(t *testing.T) { pre := recoverEntries(t, store, siblingID) wrapped := &corruptSiblingStore{Storage: store, session: targetID, registers: snap.registers, entries: snap.entries} - if err := harness.Recover(ctx, wrapped); err != nil { - t.Fatalf("recover with %q corruption = %v, want the corruption left in place and the pass completed", row.name, err) + { + err := harness.Recover(ctx, wrapped) + testassert.Require(t, err == nil, "recover with %q corruption = %v, want the corruption left in place and the pass completed", row.name, err) } assertSessionUnchanged(t, store, targetID, before) assertRecoveredOperation(t, store, siblingID, "op-sib", pre, "", nil) h := harnessOver(t, wrapped) // constructed only after recovery: materialization verification - if _, err := h.ReadSessionHeader(ctx, targetID); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("corrupt session after recovery = err %v, want the corruption error", err) + { + _, err := h.ReadSessionHeader(ctx, targetID) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "corrupt session after recovery = err %v, want the corruption error", err) } rec, err := h.ReadOperation(ctx, siblingID, "op-sib") - if err != nil || rec.State.Status != harness.OperationInterruption || rec.State.Terminal == nil || - rec.State.Terminal.Detail != recoveredInterruptionDetail { - t.Fatalf("repaired sibling after recovery = %+v err %v, want the terminal interruption with the recovery detail", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == recoveredInterruptionDetail, "repaired sibling after recovery = %+v err %v, want the terminal interruption with the recovery detail", rec, err) }) }) } @@ -5003,21 +4727,14 @@ func forkEntryOf(t *testing.T, store harness.Storage, sessionID string, kind har func assertForkSourceUnchanged(t *testing.T, store harness.Storage, sessionID string, before sessionSnapshot) { t.Helper() after := snapshotSession(t, store, sessionID) - if len(after.registers) != len(before.registers) || len(after.entries) != len(before.entries) { - t.Fatalf("fork changed the record count of source %q (registers %d -> %d, entries %d -> %d)", - sessionID, len(before.registers), len(after.registers), len(before.entries), len(after.entries)) - } + testassert.Require(t, len(after.registers) == len(before.registers) && len(after.entries) == len(before.entries), "fork changed the record count of source %q (registers %d -> %d, entries %d -> %d)", sessionID, len(before.registers), len(after.registers), len(before.entries), len(after.entries)) for i := range before.registers { b, a := before.registers[i], after.registers[i] - if b.Key != a.Key || b.Revision != a.Revision || !bytes.Equal(b.Payload, a.Payload) { - t.Fatalf("fork changed register %v of source %q (revision %d -> %d)", b.Key, sessionID, b.Revision, a.Revision) - } + testassert.Require(t, b.Key == a.Key && b.Revision == a.Revision && bytes.Equal(b.Payload, a.Payload), "fork changed register %v of source %q (revision %d -> %d)", b.Key, sessionID, b.Revision, a.Revision) } for i := range before.entries { b, a := before.entries[i], after.entries[i] - if b.ID != a.ID || b.Kind != a.Kind || b.Sequence != a.Sequence || b.OperationID != a.OperationID || !bytes.Equal(b.Payload, a.Payload) { - t.Fatalf("fork changed entry %s of source %q", b.ID, sessionID) - } + testassert.Require(t, b.ID == a.ID && b.Kind == a.Kind && b.Sequence == a.Sequence && b.OperationID == a.OperationID && bytes.Equal(b.Payload, a.Payload), "fork changed entry %s of source %q", b.ID, sessionID) } } @@ -5026,24 +4743,16 @@ func assertForkSourceUnchanged(t *testing.T, store harness.Storage, sessionID st func assertSessionIDs(t *testing.T, store harness.Storage, want ...string) { t.Helper() ids, err := store.ListSessionIDs(context.Background()) - if err != nil { - t.Fatalf("list sessions: %v", err) - } + testassert.NoError(t, err) got := append([]string(nil), ids...) - if len(got) != len(want) { - t.Fatalf("session listing = %v, want %v", got, want) - } + testassert.Require(t, len(got) == len(want), "session listing = %v, want %v", got, want) wantSet := make(map[string]bool, len(want)) for _, id := range want { - if wantSet[id] { - t.Fatalf("session listing expectation repeats %q", id) - } + testassert.Require(t, !wantSet[id], "session listing expectation repeats %q", id) wantSet[id] = true } for _, id := range got { - if !wantSet[id] { - t.Fatalf("session listing = %v, want %v", got, want) - } + testassert.Require(t, wantSet[id], "session listing = %v, want %v", got, want) } } @@ -5150,35 +4859,39 @@ func TestPublicForkCopiesPrefixAndAdmits(t *testing.T) { } source := createSession(t, f.h) - if _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // the published call <-script.arrived // the continued partial turn <-script.arrived // the terminal turn - if _, err := f.h.Submit(ctx, harness.SubmitRequest{ - SessionID: source, - OperationID: "op-2", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "second"}}, - Mode: harness.MessageModeQueued, - }); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := f.h.Submit(ctx, harness.SubmitRequest{ + SessionID: source, + OperationID: "op-2", + Origin: harness.InputOriginUser, + Content: []model.ContentPart{{Kind: model.PartText, Text: "second"}}, + Mode: harness.MessageModeQueued, + }) + testassert.NoError(t, err) } <-f.prepare // the drain admitted op-2: op-1's terminal committed <-script.arrived - if _, err := f.h.Submit(ctx, harness.SubmitRequest{ - SessionID: source, - OperationID: "op-3", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "third"}}, - Mode: harness.MessageModeQueued, - }); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := f.h.Submit(ctx, harness.SubmitRequest{ + SessionID: source, + OperationID: "op-3", + Origin: harness.InputOriginUser, + Content: []model.ContentPart{{Kind: model.PartText, Text: "third"}}, + Mode: harness.MessageModeQueued, + }) + testassert.NoError(t, err) } <-f.prepare // the drain admitted op-3: op-2's terminal committed - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } boundary := forkEntryOf(t, store, source, harness.EntryInput, "op-2").ID @@ -5196,44 +4909,25 @@ func TestPublicForkCopiesPrefixAndAdmits(t *testing.T) { OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) dest := res.Session.Identity.SessionID - if dest == source || res.Session.Identity.SessionID == "" { - t.Fatalf("fork session identity = %+v, want one fresh identity", res.Session.Identity) - } - if res.Session.Identity.Workspace != "/tmp/works" || res.Session.Identity.SourceSessionID != source || - res.Session.Identity.SourceBoundaryEntryID != boundary { - t.Fatalf("fork identity = %+v, want the inherited workspace and source/boundary lineage", res.Session.Identity) - } - if res.Session.State.Lifecycle != harness.LifecycleOpen || res.Session.State.CurrentAgentType != "coder" || - len(res.Session.State.Usage.ByModel) != 0 || res.Session.State.CurrentOperationID != "fork-1" { - t.Fatalf("fork session state = %+v, want open, the source type, zero usage, and the running fork", res.Session.State) - } - if res.Operation.State.Status != harness.OperationRunning || res.Operation.Admission.OperationID != "fork-1" || - res.Operation.Admission.RequestKind != harness.RequestKindMessage || res.Operation.Admission.AgentType != "coder" || - res.Operation.Admission.AdmittedEntry.SessionID != dest || - res.Operation.Admission.Execution.ConfigurationRevision != "rev-1" { - t.Fatalf("fork operation = %+v, want a running message admission with the fresh capture", res.Operation) - } + testassert.Require(t, dest != source && res.Session.Identity.SessionID != "", "fork session identity = %+v, want one fresh identity", res.Session.Identity) + testassert.Require(t, res.Session.Identity.Workspace == "/tmp/works" && res.Session.Identity.SourceSessionID == source && res.Session.Identity.SourceBoundaryEntryID == boundary && (res.Session.State.Lifecycle == harness.LifecycleOpen && res.Session.State.CurrentAgentType == "coder" && len(res.Session.State.Usage.ByModel) == 0 && res.Session.State.CurrentOperationID == "fork-1") && (res.Operation.State.Status == harness.OperationRunning && res.Operation.Admission.OperationID == "fork-1" && res.Operation.Admission.RequestKind == harness.RequestKindMessage && res.Operation.Admission.AgentType == "coder" && res.Operation.Admission.AdmittedEntry.SessionID == dest && res.Operation.Admission.Execution.ConfigurationRevision == "rev-1"), "fork identity = %+v, want the inherited workspace and source/boundary lineage; fork session state = %+v, want open, the source type, zero usage, and the running fork; fork operation = %+v, want a running message admission with the fresh capture", res.Session.Identity, res.Session.State, res.Operation) <-forkScript.arrived // the destination execution reached its model boundary - if got := texts(forkScript.seen()[0]); len(got) != 7 || - got[0] != "system" || got[1] != "hello" || got[2] != "done" || got[3] != "ran call-1" || got[4] != "partial" || - got[5] != "The previous model response failed after partial output. Continue from the retained response." || got[6] != "fork input" { - t.Fatalf("fork projection = %v, want the copied prefix before the fork input", got) + { + got := texts(forkScript.seen()[0]) + testassert.Require(t, len(got) == 7 && got[0] == "system" && got[1] == "hello" && got[2] == "done" && got[3] == "ran call-1" && got[4] == "partial" && got[5] == "The previous model response failed after partial output. Continue from the retained response." && got[6] == "fork input", "fork projection = %v, want the copied prefix before the fork input", got) } forkScript.releaseGate() awaitTerminal(t, f2.h, dest, "fork-1") // the fork turn's settlement commits before the convergence cancellation - if err := converge(t, f2); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f2) + testassert.NoError(t, err) } calls := f2.preparationCalls() - if len(calls) != 1 || calls[0].Session.Identity.Workspace != "/tmp/works" || calls[0].Session.AgentType != "coder" { - t.Fatalf("fork preparation calls = %+v, want one call with the source workspace and Agent type", calls) - } + testassert.Require(t, len(calls) == 1 && calls[0].Session.Identity.Workspace == "/tmp/works" && calls[0].Session.AgentType == "coder", "fork preparation calls = %+v, want one call with the source workspace and Agent type", calls) // the source is byte-identical after the fork assertForkSourceUnchanged(t, store, source, before) @@ -5242,41 +4936,27 @@ func TestPublicForkCopiesPrefixAndAdmits(t *testing.T) { // user-origin admission input and the fork's model-originated terminal entries := recoverEntries(t, store, dest) wantKinds := []harness.EntryKind{harness.EntryInput, harness.EntryAssistant, harness.EntryToolResult, harness.EntryAssistant, harness.EntrySignal, harness.EntryInput, harness.EntryOperationSettlement} - if len(entries) != len(wantKinds) { - t.Fatalf("destination entries = %d, want the %d copied prefix entries, the fork admission, and the fork's settled turn", len(entries), 5) - } + testassert.Require(t, len(entries) == len(wantKinds), "destination entries = %d, want the %d copied prefix entries, the fork admission, and the fork's settled turn", len(entries), 5) sourceIDs := map[string]bool{} for _, entry := range before.entries { sourceIDs[entry.ID] = true } for i, entry := range entries { - if entry.Kind != wantKinds[i] { - t.Fatalf("destination entry %d is a %s entry, want %s", i, entry.Kind, wantKinds[i]) - } - if entry.SessionID != dest { - t.Fatalf("destination entry %d addresses session %q", i, entry.SessionID) - } + testassert.Require(t, entry.Kind == wantKinds[i] && entry.SessionID == dest, "destination entry %d is a %s entry, want %s; destination entry %d addresses session %q", i, entry.Kind, wantKinds[i], i, entry.SessionID) if i < 5 { // copied prefix entries are operationless and freshly identified - if entry.OperationID != "" { - t.Fatalf("copied entry %s carries operation %q, want no source ownership", entry.ID, entry.OperationID) - } - if sourceIDs[entry.ID] { - t.Fatalf("copied entry %s reuses a source identity", entry.ID) - } + testassert.Require(t, entry.OperationID == "", "copied entry %s carries operation %q, want no source ownership", entry.ID, entry.OperationID) + testassert.Require(t, !sourceIDs[entry.ID], "copied entry %s reuses a source identity", entry.ID) } } - if entries[5].OperationID != "fork-1" { - t.Fatalf("fork admission entry = %+v, want it owned by fork-1", entries[5]) - } + testassert.Require(t, entries[5].OperationID == "fork-1", "fork admission entry = %+v, want it owned by fork-1", entries[5]) var copiedInput struct { Origin string `json:"origin"` } - if err := json.Unmarshal(entries[0].Payload, &copiedInput); err != nil { - t.Fatalf("decode copied input: %v", err) - } - if copiedInput.Origin != "user" { - t.Fatalf("copied input origin = %q, want the source origin preserved", copiedInput.Origin) + { + err := json.Unmarshal(entries[0].Payload, &copiedInput) + testassert.NoError(t, err) } + testassert.Equal(t, copiedInput.Origin, "user") var copiedAssistant struct { ToolCalls []struct { ID string `json:"id"` @@ -5285,19 +4965,13 @@ func TestPublicForkCopiesPrefixAndAdmits(t *testing.T) { } `json:"tool_calls"` Usage *struct{} `json:"usage"` } - if err := json.Unmarshal(entries[1].Payload, &copiedAssistant); err != nil { - t.Fatalf("decode copied assistant: %v", err) - } - if copiedAssistant.Usage != nil { - t.Fatalf("copied assistant carries source usage") - } - if len(copiedAssistant.ToolCalls) != 1 || copiedAssistant.ToolCalls[0].ID != "call-1" || - copiedAssistant.ToolCalls[0].ResultEntryID != entries[2].ID { - t.Fatalf("copied call reservation = %+v, want it rewritten to the copied result entry %s", copiedAssistant.ToolCalls, entries[2].ID) - } - if string(copiedAssistant.ToolCalls[0].NormalizedArguments) != `{"x":1}` { - t.Fatalf("copied assistant normalized arguments = %s, want the source's committed normalization preserved verbatim", copiedAssistant.ToolCalls[0].NormalizedArguments) + { + err := json.Unmarshal(entries[1].Payload, &copiedAssistant) + testassert.NoError(t, err) } + testassert.Require(t, copiedAssistant.Usage == nil, "copied assistant carries source usage") + testassert.Require(t, len(copiedAssistant.ToolCalls) == 1 && copiedAssistant.ToolCalls[0].ID == "call-1" && copiedAssistant.ToolCalls[0].ResultEntryID == entries[2].ID, "copied call reservation = %+v, want it rewritten to the copied result entry %s", copiedAssistant.ToolCalls, entries[2].ID) + testassert.Require(t, string(copiedAssistant.ToolCalls[0].NormalizedArguments) == `{"x":1}`, "copied assistant normalized arguments = %s, want the source's committed normalization preserved verbatim", copiedAssistant.ToolCalls[0].NormalizedArguments) var copiedResult struct { AssistantEntry struct { SessionID string `json:"session_id"` @@ -5306,52 +4980,38 @@ func TestPublicForkCopiesPrefixAndAdmits(t *testing.T) { ToolCallID string `json:"tool_call_id"` Metadata json.RawMessage `json:"metadata"` } - if err := json.Unmarshal(entries[2].Payload, &copiedResult); err != nil { - t.Fatalf("decode copied tool result: %v", err) - } - if copiedResult.AssistantEntry.SessionID != dest || copiedResult.AssistantEntry.EntryID != entries[1].ID || - copiedResult.ToolCallID != "call-1" { - t.Fatalf("copied result reference = %+v, want it rewritten inside the copied prefix", copiedResult.AssistantEntry) - } - if string(copiedResult.Metadata) != `{"n":[1,1.0,1e0,9007199254740993],"s":"\u003c\u003e\u0026"}` { - t.Fatalf("copied result metadata = %s, want the source's encoded metadata preserved verbatim", copiedResult.Metadata) + { + err := json.Unmarshal(entries[2].Payload, &copiedResult) + testassert.NoError(t, err) } + testassert.Require(t, copiedResult.AssistantEntry.SessionID == dest && copiedResult.AssistantEntry.EntryID == entries[1].ID && copiedResult.ToolCallID == "call-1" && string(copiedResult.Metadata) == `{"n":[1,1.0,1e0,9007199254740993],"s":"\u003c\u003e\u0026"}`, "copied result reference = %+v, want it rewritten inside the copied prefix; copied result metadata = %s, want the source's encoded metadata preserved verbatim", copiedResult.AssistantEntry, copiedResult.Metadata) var copiedSignal struct { RelatedOperation struct { SessionID string `json:"session_id"` OperationID string `json:"operation_id"` } `json:"related_operation"` } - if err := json.Unmarshal(entries[4].Payload, &copiedSignal); err != nil { - t.Fatalf("decode copied signal: %v", err) - } - if copiedSignal.RelatedOperation.SessionID != source || copiedSignal.RelatedOperation.OperationID != "op-1" { - t.Fatalf("copied signal related operation = %+v, want the source Operation kept as informational history", copiedSignal.RelatedOperation) + { + err := json.Unmarshal(entries[4].Payload, &copiedSignal) + testassert.NoError(t, err) } + testassert.Require(t, copiedSignal.RelatedOperation.SessionID == source && copiedSignal.RelatedOperation.OperationID == "op-1", "copied signal related operation = %+v, want the source Operation kept as informational history", copiedSignal.RelatedOperation) var admission struct { Origin string `json:"origin"` } - if err := json.Unmarshal(entries[5].Payload, &admission); err != nil { - t.Fatalf("decode fork admission input: %v", err) - } - if admission.Origin != "user" { - t.Fatalf("fork admission origin = %q, want the fixed user origin", admission.Origin) + { + err := json.Unmarshal(entries[5].Payload, &admission) + testassert.NoError(t, err) } + testassert.Equal(t, admission.Origin, "user") // the fork's own turn settled through its model-originated terminal destHeader, err := f2.h.ReadSessionHeader(ctx, dest) - if err != nil || destHeader.CurrentOperationID != "" { - t.Fatalf("fork session after convergence = %+v err %v, want it settled", destHeader, err) - } + testassert.Require(t, err == nil && destHeader.CurrentOperationID == "", "fork session after convergence = %+v err %v, want it settled", destHeader, err) destView, err := f2.h.ReadSessionView(ctx, dest) - if err != nil || len(destView.TotalUsage.ByModel) != 0 { - t.Fatalf("fork session usage = %+v err %v, want zero usage", destView.TotalUsage, err) - } + testassert.Require(t, err == nil && len(destView.TotalUsage.ByModel) == 0, "fork session usage = %+v err %v, want zero usage", destView.TotalUsage, err) rec, err := f2.h.ReadOperation(ctx, dest, "fork-1") - if err != nil || rec.State.Status != harness.OperationFailure || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "fork turn settled" { - t.Fatalf("fork operation after convergence = %+v err %v, want its model-originated terminal", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationFailure && rec.State.Terminal != nil && rec.State.Terminal.Detail == "fork turn settled", "fork operation after convergence = %+v err %v, want its model-originated terminal", rec, err) }) } @@ -5365,8 +5025,9 @@ func TestPublicForkArchivedSource(t *testing.T) { f2 := newPublicFixture(t, store, newScriptModel(), nil) defer f2.close() - if _, err := f2.h.ArchiveSession(ctx, source); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f2.h.ArchiveSession(ctx, source) + testassert.NoError(t, err) } boundary := forkEntryOf(t, store, source, harness.EntryInput, "op-1").ID res, err := f2.h.Fork(ctx, harness.ForkRequest{ @@ -5375,18 +5036,13 @@ func TestPublicForkArchivedSource(t *testing.T) { OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("Fork of an archived source: %v", err) - } - if res.Session.State.Lifecycle != harness.LifecycleOpen { - t.Fatalf("fork lifecycle = %s, want open", res.Session.State.Lifecycle) - } + testassert.NoError(t, err) + testassert.Equal(t, res.Session.State.Lifecycle, harness.LifecycleOpen) src, err := f2.h.ReadSessionHeader(ctx, source) - if err != nil || src.Lifecycle != harness.LifecycleArchived { - t.Fatalf("source after fork = %+v err %v, want it archived without reopening", src, err) - } - if err := converge(t, f2); err != nil { - t.Fatalf("Wait: %v", err) + testassert.Require(t, err == nil && src.Lifecycle == harness.LifecycleArchived, "source after fork = %+v err %v, want it archived without reopening", src, err) + { + err := converge(t, f2) + testassert.NoError(t, err) } }) } @@ -5404,48 +5060,56 @@ func TestPublicForkBoundaryValidation(t *testing.T) { defer f.close() source := createSession(t, f.h) - if _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived // parked: the source is running - if _, err := f.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: newRawSessionID(t), - OperationID: "fork-1", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork of a running source = err %v, want ErrInvalid", err) + { + _, err := f.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: newRawSessionID(t), + OperationID: "fork-1", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork of a running source = err %v, want ErrInvalid", err) } - if _, err := f.h.Fork(ctx, harness.ForkRequest{SourceSessionID: "short", BoundaryEntryID: newRawSessionID(t), OperationID: "fork-1"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork with a malformed source identity = err %v, want ErrInvalid", err) + { + _, err := f.h.Fork(ctx, harness.ForkRequest{SourceSessionID: "short", BoundaryEntryID: newRawSessionID(t), OperationID: "fork-1"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork with a malformed source identity = err %v, want ErrInvalid", err) } - if _, err := f.h.Fork(ctx, harness.ForkRequest{SourceSessionID: source, BoundaryEntryID: "short", OperationID: "fork-1"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork with a malformed boundary identity = err %v, want ErrInvalid", err) + { + _, err := f.h.Fork(ctx, harness.ForkRequest{SourceSessionID: source, BoundaryEntryID: "short", OperationID: "fork-1"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork with a malformed boundary identity = err %v, want ErrInvalid", err) } - if _, err := f.h.Fork(ctx, harness.ForkRequest{SourceSessionID: source, BoundaryEntryID: newRawSessionID(t), OperationID: ""}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork without an operation identity = err %v, want ErrInvalid", err) + { + _, err := f.h.Fork(ctx, harness.ForkRequest{SourceSessionID: source, BoundaryEntryID: newRawSessionID(t), OperationID: ""}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork without an operation identity = err %v, want ErrInvalid", err) } script.releaseGate() - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } // an idle source with one runtime-origin input: non-user boundaries are invalid runtimeScript := newScriptModel() f2 := newPublicFixture(t, store, runtimeScript, nil) defer f2.close() - if _, err := f2.h.Submit(ctx, harness.SubmitRequest{ - SessionID: source, - OperationID: "op-2", - Origin: harness.InputOriginRuntime, - Content: []model.ContentPart{{Kind: model.PartText, Text: "runtime input"}}, - Mode: harness.MessageModeRegular, - }); err != nil { - t.Fatalf("runtime submit: %v", err) + { + _, err := f2.h.Submit(ctx, harness.SubmitRequest{ + SessionID: source, + OperationID: "op-2", + Origin: harness.InputOriginRuntime, + Content: []model.ContentPart{{Kind: model.PartText, Text: "runtime input"}}, + Mode: harness.MessageModeRegular, + }) + testassert.NoError(t, err) } <-runtimeScript.arrived - if err := converge(t, f2); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f2) + testassert.NoError(t, err) } f3 := newPublicFixture(t, store, newScriptModel(), nil) @@ -5460,13 +5124,14 @@ func TestPublicForkBoundaryValidation(t *testing.T) { {"non-input boundary", assistantBoundary}, {"runtime-origin boundary", runtimeBoundary}, } { - if _, err := f3.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: tt.boundary, - OperationID: "fork-1", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork with %s = err %v, want ErrInvalid", tt.name, err) + { + _, err := f3.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: tt.boundary, + OperationID: "fork-1", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork with %s = err %v, want ErrInvalid", tt.name, err) } } assertSessionIDs(t, store, source) // nothing was published by any rejection @@ -5497,12 +5162,11 @@ func TestPublicForkExistingLookup(t *testing.T) { f2 := newPublicFixture(t, store, newScriptModel(), nil) defer f2.close() first, err := f2.h.Fork(ctx, forkReq) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) dest := first.Session.Identity.SessionID - if err := converge(t, f2); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f2) + testassert.NoError(t, err) } // one further live idle source for the later subtests @@ -5510,12 +5174,14 @@ func TestPublicForkExistingLookup(t *testing.T) { f3 := newPublicFixture(t, store, otherScript, nil) defer f3.close() other := createSession(t, f3.h) - if _, err := submit(t, f3.h, other, "fork-b", harness.MessageModeRegular, "reused"); err != nil { - t.Fatalf("cross-session submit: %v", err) + { + _, err := submit(t, f3.h, other, "fork-b", harness.MessageModeRegular, "reused") + testassert.NoError(t, err) } <-otherScript.arrived - if err := converge(t, f3); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f3) + testassert.NoError(t, err) } otherBoundary := forkEntryOf(t, store, other, harness.EntryInput, "fork-b").ID @@ -5525,15 +5191,11 @@ func TestPublicForkExistingLookup(t *testing.T) { retry := forkReq retry.Content = []model.ContentPart{{Kind: model.PartText, Text: "changed"}} res, err := f4.h.Fork(ctx, retry) - if err != nil { - t.Fatalf("retry Fork: %v", err) - } - if res.Session.Identity.SessionID != dest || - res.Operation.Admission.AdmittedEntry != first.Operation.Admission.AdmittedEntry { - t.Fatalf("retry = %+v, want the first destination", res) - } - if calls := f4.preparationCalls(); len(calls) != 0 { - t.Fatalf("retry prepared %d times, want the lookup to resolve before preparation", len(calls)) + testassert.NoError(t, err) + testassert.Require(t, res.Session.Identity.SessionID == dest && res.Operation.Admission.AdmittedEntry == first.Operation.Admission.AdmittedEntry, "retry = %+v, want the first destination", res) + { + calls := f4.preparationCalls() + testassert.Equal(t, len(calls), 0) } assertSessionIDs(t, store, dest, other, source) }) @@ -5541,31 +5203,30 @@ func TestPublicForkExistingLookup(t *testing.T) { t.Run("lookup precedes source validation", func(t *testing.T) { f5 := newPublicFixture(t, store, newScriptModel(), nil) defer f5.close() - if _, err := f5.h.ArchiveSession(ctx, source); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := f5.h.ArchiveSession(ctx, source) + testassert.NoError(t, err) } - if err := f5.h.DeleteSession(ctx, source); err != nil { - t.Fatalf("delete: %v", err) + { + err := f5.h.DeleteSession(ctx, source) + testassert.NoError(t, err) } res, err := f5.h.Fork(ctx, forkReq) - if err != nil { - t.Fatalf("retry after source deletion: %v", err) - } - if res.Session.Identity.SessionID != dest { - t.Fatalf("retry after source deletion = %+v, want the first destination", res) - } + testassert.NoError(t, err) + testassert.Require(t, res.Session.Identity.SessionID == dest, "retry after source deletion = %+v, want the first destination", res) }) t.Run("other reuse of the ID is invalid", func(t *testing.T) { f6 := newPublicFixture(t, store, newScriptModel(), nil) defer f6.close() - if _, err := f6.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: other, - BoundaryEntryID: otherBoundary, - OperationID: "fork-b", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork reusing a root Session's operation ID = err %v, want ErrInvalid", err) + { + _, err := f6.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: other, + BoundaryEntryID: otherBoundary, + OperationID: "fork-b", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork reusing a root Session's operation ID = err %v, want ErrInvalid", err) } }) @@ -5578,60 +5239,49 @@ func TestPublicForkExistingLookup(t *testing.T) { OperationID: "fork-c", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("Fork: %v", err) - } - if err := converge(t, f7); err != nil { - t.Fatalf("Wait: %v", err) + testassert.NoError(t, err) + { + err := converge(t, f7) + testassert.NoError(t, err) } // corrupt the destination register directly through storage reg := sessionRegister(t, store, res.Session.Identity.SessionID) - var obj map[string]json.RawMessage - if err := json.Unmarshal(reg.Payload, &obj); err != nil { - t.Fatalf("unmarshal session payload: %v", err) - } - var state map[string]json.RawMessage - if err := json.Unmarshal(obj["state"], &state); err != nil { - t.Fatalf("unmarshal state section: %v", err) - } - state["lifecycle"] = json.RawMessage(`"bogus"`) - newState, err := json.Marshal(state) - if err != nil { - t.Fatalf("marshal state section: %v", err) - } - obj["state"] = newState - bad, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal session payload: %v", err) - } + bad := editStateObject(t, reg.Payload, func(state map[string]json.RawMessage) { + state["lifecycle"] = json.RawMessage(`"bogus"`) + }) key := harness.RegisterKey{SessionID: res.Session.Identity.SessionID, Kind: harness.RegisterSession} f8 := newPublicFixture(t, store, newScriptModel(), nil) defer f8.close() // the affected destination is already materialized in the retry // Harness before the corruption is reported - if _, err := f8.h.ReadSessionHeader(ctx, res.Session.Identity.SessionID); err != nil { - t.Fatalf("read the destination before corruption: %v", err) - } - if err := store.Transact(ctx, func(tx harness.Transaction) error { - _, err := tx.ReplaceRegister(key, reg.Revision, bad) - return err - }); err != nil { - t.Fatalf("corrupt the register: %v", err) - } - if _, err := f8.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: other, - BoundaryEntryID: otherBoundary, - OperationID: "fork-c", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("retry over a corrupt located owner = err %v, want the corruption error", err) + { + _, err := f8.h.ReadSessionHeader(ctx, res.Session.Identity.SessionID) + testassert.NoError(t, err) + } + { + err := store.Transact(ctx, func(tx harness.Transaction) error { + _, err := tx.ReplaceRegister(key, reg.Revision, bad) + return err + }) + testassert.NoError(t, err) + } + { + _, err := f8.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: other, + BoundaryEntryID: otherBoundary, + OperationID: "fork-c", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "retry over a corrupt located owner = err %v, want the corruption error", err) } // the affected cached Session is sticky-corrupt while valid siblings remain usable - if _, err := f8.h.ReadSessionHeader(ctx, res.Session.Identity.SessionID); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("cached read of the corrupt located owner = err %v, want the sticky corruption error", err) + { + _, err := f8.h.ReadSessionHeader(ctx, res.Session.Identity.SessionID) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "cached read of the corrupt located owner = err %v, want the sticky corruption error", err) } - if _, err := f8.h.ReadSessionHeader(ctx, other); err != nil { - t.Fatalf("valid sibling after the corrupt located owner = err %v, want it usable", err) + { + _, err := f8.h.ReadSessionHeader(ctx, other) + testassert.NoError(t, err) } }) @@ -5642,22 +5292,21 @@ func TestPublicForkExistingLookup(t *testing.T) { defer f9.close() // the affected sibling is already materialized in this Harness // before the corruption is reported - if _, err := f9.h.ReadSessionHeader(ctx, forkSiblingID); err != nil { - t.Fatalf("read the sibling before corruption: %v", err) + { + _, err := f9.h.ReadSessionHeader(ctx, forkSiblingID) + testassert.NoError(t, err) } res, err := f9.h.Fork(ctx, forkReq) - if err != nil { - t.Fatalf("retry past a corrupt sibling: %v", err) - } - if res.Operation.Admission.OperationID != "fork-a" { - t.Fatalf("retry = %+v, want the existing destination", res) - } + testassert.NoError(t, err) + testassert.Require(t, res.Operation.Admission.OperationID == "fork-a", "retry = %+v, want the existing destination", res) // the skipped corruption sticks to the cached Session while valid siblings remain usable - if _, err := f9.h.ReadSessionHeader(ctx, forkSiblingID); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("cached read of the corrupt sibling = err %v, want the sticky corruption error", err) + { + _, err := f9.h.ReadSessionHeader(ctx, forkSiblingID) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "cached read of the corrupt sibling = err %v, want the sticky corruption error", err) } - if _, err := f9.h.ReadSessionHeader(ctx, other); err != nil { - t.Fatalf("valid sibling after the corrupt sibling = err %v, want it usable", err) + { + _, err := f9.h.ReadSessionHeader(ctx, other) + testassert.NoError(t, err) } }) }) @@ -5689,19 +5338,16 @@ func TestPublicForkFirstWriterRace(t *testing.T) { OperationID: "race-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("fork losing the race = err %v, want the matching first destination", err) - } + testassert.NoError(t, err) decoy := <-decoyCh - if res.Session.Identity.SessionID != decoy || res.Session.Identity.SourceSessionID != source || - res.Operation.Admission.OperationID != "race-1" { - t.Fatalf("race resolution = %+v, want the first destination session %q", res, decoy) + testassert.Require(t, res.Session.Identity.SessionID == decoy && res.Session.Identity.SourceSessionID == source && res.Operation.Admission.OperationID == "race-1", "race resolution = %+v, want the first destination session %q", res, decoy) + { + calls := race.preparationCalls() + testassert.Equal(t, len(calls), 1) } - if calls := race.preparationCalls(); len(calls) != 1 { - t.Fatalf("preparation calls = %d, want the preparation never rerun", len(calls)) - } - if got := race.opens.Load(); got != 0 { - t.Fatalf("opener calls = %d, want the first-writer loser to discard its prepared values without opening", got) + { + got := race.opens.Load() + testassert.Equal(t, got, 0) } assertSessionIDs(t, store, decoy, source) // the losing transaction published nothing }) @@ -5713,16 +5359,18 @@ func TestPublicForkFirstWriterRace(t *testing.T) { seedForkDecoy(t, store, source, boundary, "race-2", false) // a root Session wins the ID return racePrepared(race), nil } - if _, err := race.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: boundary, - OperationID: "race-2", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fork losing to a different use of the ID = err %v, want ErrInvalid", err) + { + _, err := race.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: boundary, + OperationID: "race-2", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fork losing to a different use of the ID = err %v, want ErrInvalid", err) } - if got := race.opens.Load(); got != 0 { - t.Fatalf("opener calls = %d, want an invalid fork resolution to discard the prepared values without opening", got) + { + got := race.opens.Load() + testassert.Equal(t, got, 0) } }) @@ -5736,13 +5384,14 @@ func TestPublicForkFirstWriterRace(t *testing.T) { insertRawRegister(t, store, harness.RegisterKey{SessionID: sid, Kind: harness.RegisterOperation, OperationID: "race-3"}, rawRunningOperationPayload(sid, "race-3", entryID)) return racePrepared(race), nil } - if _, err := race.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: boundary, - OperationID: "race-3", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("fork losing to an unreadable owner = err %v, want the original storage conflict", err) + { + _, err := race.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: boundary, + OperationID: "race-3", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrConflict), "fork losing to an unreadable owner = err %v, want the original storage conflict", err) } }) }) @@ -5776,23 +5425,22 @@ func TestPublicForkSourceRevisionRace(t *testing.T) { } return racePrepared(race), nil } - if _, err := race.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: boundary, - OperationID: "race-1", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("fork over a concurrently changed source = err %v, want ErrConflict", err) + { + _, err := race.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: boundary, + OperationID: "race-1", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrConflict), "fork over a concurrently changed source = err %v, want ErrConflict", err) } - if got := race.opens.Load(); got != 0 { - t.Fatalf("opener calls = %d, want a failed fork publication to discard the prepared values without opening", got) + { + got := race.opens.Load() + testassert.Equal(t, got, 0) } assertSessionIDs(t, store, source) // nothing was published after := sessionRegister(t, store, source) - if after.Revision != snapRegisterOf(&before, harness.RegisterSession, "").Revision+1 || - !bytes.Equal(after.Payload, mustForeignPayload(t, before)) { - t.Fatalf("fork disturbed the foreign change: register = %+v", after) - } + testassert.Require(t, after.Revision == snapRegisterOf(&before, harness.RegisterSession, "").Revision+1 && bytes.Equal(after.Payload, mustForeignPayload(t, before)), "fork disturbed the foreign change: register = %+v", after) }) } @@ -5829,55 +5477,60 @@ func TestPublicForkIndependentSourceDeletion(t *testing.T) { OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) dest := res.Session.Identity.SessionID - if err := converge(t, f2); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f2) + testassert.NoError(t, err) } // the source is archived and deleted; the fork stays usable destScript := newScriptModel() f3 := newPublicFixture(t, store, destScript, nil) defer f3.close() - if _, err := f3.h.ArchiveSession(ctx, source); err != nil { - t.Fatalf("archive source: %v", err) + { + _, err := f3.h.ArchiveSession(ctx, source) + testassert.NoError(t, err) } - if err := f3.h.DeleteSession(ctx, source); err != nil { - t.Fatalf("delete source: %v", err) + { + err := f3.h.DeleteSession(ctx, source) + testassert.NoError(t, err) } - if _, err := f3.h.ReadSessionHeader(ctx, dest); err != nil { - t.Fatalf("fork read after source deletion: %v", err) + { + _, err := f3.h.ReadSessionHeader(ctx, dest) + testassert.NoError(t, err) } - if _, err := f3.h.ReadOperation(ctx, dest, "fork-1"); err != nil { - t.Fatalf("fork operation read after source deletion: %v", err) + { + _, err := f3.h.ReadOperation(ctx, dest, "fork-1") + testassert.NoError(t, err) } - if _, err := submit(t, f3.h, dest, "fork-2", harness.MessageModeRegular, "after source loss"); err != nil { - t.Fatalf("fork admission after source deletion: %v", err) + { + _, err := submit(t, f3.h, dest, "fork-2", harness.MessageModeRegular, "after source loss") + testassert.NoError(t, err) } <-destScript.arrived - if _, err := submit(t, f3.h, dest, "fork-4", harness.MessageModeQueued, "settle barrier"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f3.h, dest, "fork-4", harness.MessageModeQueued, "settle barrier") + testassert.NoError(t, err) } <-f3.prepare // the drain admitted fork-4: fork-2's terminal committed <-destScript.arrived - if err := converge(t, f3); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f3) + testassert.NoError(t, err) } rec, err := f3.h.ReadOperation(ctx, dest, "fork-2") - if err != nil || rec.State.Status != harness.OperationSuccess { - t.Fatalf("fork admission after source deletion = %+v err %v, want success", rec, err) - } + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess, "fork admission after source deletion = %+v err %v, want success", rec, err) f4 := newPublicFixture(t, store, newScriptModel(), nil) defer f4.close() - if _, err := f4.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: boundary, - OperationID: "fork-3", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("fork from a deleted source = err %v, want ErrNotFound", err) + { + _, err := f4.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: boundary, + OperationID: "fork-3", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "fork from a deleted source = err %v, want ErrNotFound", err) } }) } @@ -5895,13 +5548,14 @@ func TestPublicForkRollbackOnTransactionFailure(t *testing.T) { probe := &rollbackProbeStore{Storage: store, failInsert: true, insertsUntilFail: 2} // the destination Operation-register insert fails after mutating race := newPublicFixture(t, probe, newScriptModel(), nil) defer race.close() - if _, err := race.h.Fork(ctx, harness.ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: boundary, - OperationID: "fork-1", - Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, - }); !errors.Is(err, errInjectedRollback) { - t.Fatalf("fork with injected post-mutation failure = err %v, want the injected rollback", err) + { + _, err := race.h.Fork(ctx, harness.ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: boundary, + OperationID: "fork-1", + Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, + }) + testassert.Require(t, errors.Is(err, errInjectedRollback), "fork with injected post-mutation failure = err %v, want the injected rollback", err) } assertSessionIDs(t, store, source) // the rolled-back fork left no destination assertForkSourceUnchanged(t, store, source, before) @@ -5924,29 +5578,23 @@ func TestPublicCompositionProductionIsolation(t *testing.T) { defer f.close() sessionID := createSession(t, f.h) res, err := submit(t, f.h, sessionID, "iso-1", harness.MessageModeRegular, "isolate") - if err != nil || res.Disposition != harness.DispositionAdmitted { - t.Fatalf("Submit = %+v err %v, want admitted", res, err) - } + testassert.Require(t, err == nil && res.Disposition == harness.DispositionAdmitted, "Submit = %+v err %v, want admitted", res, err) <-script.arrived // parked at the model boundary: the Session is active - if _, err := submit(t, f.h, sessionID, "iso-2", harness.MessageModeQueued, "drain"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, sessionID, "iso-2", harness.MessageModeQueued, "drain") + testassert.NoError(t, err) } script.releaseGate() <-script.arrived // the drain admitted iso-2: iso-1's terminal success committed - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } op, err := f.h.ReadOperation(context.Background(), sessionID, "iso-1") - if err != nil || op.State.Status != harness.OperationSuccess { - t.Fatalf("composed isolation turn = %+v err %v, want success", op, err) - } + testassert.Require(t, err == nil && op.State.Status == harness.OperationSuccess, "composed isolation turn = %+v err %v, want success", op, err) touched, err := os.ReadDir(state) - if err != nil { - t.Fatalf("read caller state directory: %v", err) - } - if len(touched) != 0 { - t.Fatalf("composed inactive Harness derived production state in the caller's working directory: %v", touched) - } + testassert.NoError(t, err) + testassert.Require(t, len(touched) == 0, "composed inactive Harness derived production state in the caller's working directory: %v", touched) }) } @@ -6004,20 +5652,20 @@ func TestPublicExecutionResourceLifetime(t *testing.T) { t.Helper() select { case got := <-events: - if got != want { - t.Fatalf("lifetime event = %q, want %q", got, want) - } + testassert.Equal(t, got, want) case <-time.After(30 * time.Second): t.Fatalf("timed out waiting for lifetime event %q", want) } } - if _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("root submit: %v", err) + { + _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } nextEvent("open:op-1") // the root execution opens after its commit - if _, err := submit(t, f.h, source, "op-2", harness.MessageModeQueued, "queued"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, source, "op-2", harness.MessageModeQueued, "queued") + testassert.NoError(t, err) } nextEvent("close:op-1:success") // closed once, with its own terminal settlement already durable nextEvent("open:op-2") // the drain's successor opens only after that close @@ -6032,31 +5680,22 @@ func TestPublicExecutionResourceLifetime(t *testing.T) { OperationID: "fork-op", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("fork: %v", err) - } + testassert.NoError(t, err) nextEvent("open:fork-op") nextEvent("close:fork-op:success") - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } openMu.Lock() defer openMu.Unlock() - if len(opens) != 3 { - t.Fatalf("%d opener calls, want exactly one per admitted execution", len(opens)) - } + testassert.Equal(t, len(opens), 3) for i, adm := range opens { - if !reflect.DeepEqual(adm.Execution, publicCapture()) { - t.Fatalf("open %d carries capture %+v, want the prepared capture", i, adm.Execution) - } - } - if opens[0].SessionID != source || opens[1].SessionID != source { - t.Fatalf("root/queued opens name sessions %q and %q, want the source", opens[0].SessionID, opens[1].SessionID) - } - if opens[2].SessionID != res.Session.Identity.SessionID { - t.Fatalf("fork open names session %q, want the destination", opens[2].SessionID) + testassert.Require(t, reflect.DeepEqual(adm.Execution, publicCapture()), "open %d carries capture %+v, want the prepared capture", i, adm.Execution) } + testassert.Require(t, opens[0].SessionID == source && opens[1].SessionID == source, "root/queued opens name sessions %q and %q, want the source", opens[0].SessionID, opens[1].SessionID) + testassert.Equal(t, opens[2].SessionID, res.Session.Identity.SessionID) }) } @@ -6076,45 +5715,37 @@ func TestPublicRecoverHookShapes(t *testing.T) { ctx := context.Background() sessionID, hookReserved, pending := seedRunningOperationShape(t, store, "hook", "op-1", []string{"call-1"}) state := recoverOpStateAt(t, store, sessionID, "op-1") - if state.ActiveEffect == nil || state.ActiveEffect.Kind != "hook" || - state.ActiveEffect.ResultEntryID != hookReserved || state.ActiveEffect.HookID != "cap.hook.one" || - state.ActiveEffect.ToolCallID != "call-1" { - t.Fatalf("seeded operation = %+v, want the committed hook effect intent", state) - } + testassert.Require(t, state.ActiveEffect != nil && state.ActiveEffect.Kind == "hook" && state.ActiveEffect.ResultEntryID == hookReserved && state.ActiveEffect.HookID == "cap.hook.one" && state.ActiveEffect.ToolCallID == "call-1", "seeded operation = %+v, want the committed hook effect intent", state) pre := recoverEntries(t, store, sessionID) - if err := harness.Recover(ctx, store); err != nil { - t.Fatalf("recover: %v", err) + { + err := harness.Recover(ctx, store) + testassert.NoError(t, err) } // Recovery adds its reserved hook result before the ordinary // interrupted-tool, signal and settlement tail. entries := recoverEntries(t, store, sessionID) - if len(entries) <= len(pre) { - t.Fatal("recovery committed no hook result") - } + testassert.Require(t, len(entries) > len(pre), "%v", "recovery committed no hook result") hook := entries[len(pre)] - if hook.Kind != harness.EntryHookResult || hook.ID != hookReserved { - t.Fatalf("recovery commit %s is %s, want the interrupted hook result under the reserved identity %s", hook.ID, hook.Kind, hookReserved) - } + testassert.Require(t, hook.Kind == harness.EntryHookResult && hook.ID == hookReserved, "recovery commit %s is %s, want the interrupted hook result under the reserved identity %s", hook.ID, hook.Kind, hookReserved) var hookWire struct { HookID string `json:"hook_id"` ToolCallID string `json:"tool_call_id"` Status string `json:"status"` Error string `json:"error"` } - if err := json.Unmarshal(hook.Payload, &hookWire); err != nil { - t.Fatalf("decode hook result: %v", err) - } - if hookWire.HookID != "cap.hook.one" || hookWire.ToolCallID != "call-1" || hookWire.Status != "interrupted" || - hookWire.Error != recoveredInterruptionDetail { - t.Fatalf("interrupted hook result = %+v, want the recovery-settled interrupted evidence", hookWire) + { + err := json.Unmarshal(hook.Payload, &hookWire) + testassert.NoError(t, err) } + testassert.Require(t, hookWire.HookID == "cap.hook.one" && hookWire.ToolCallID == "call-1" && hookWire.Status == "interrupted" && hookWire.Error == recoveredInterruptionDetail, "interrupted hook result = %+v, want the recovery-settled interrupted evidence", hookWire) assertRecoveredOperation(t, store, sessionID, "op-1", append(pre, hook), "", pending) assertRecoverIdempotent(t, store, sessionID, "op-1") h := harnessOver(t, store) - if _, err := h.ReadOperation(ctx, sessionID, "op-1"); err != nil { - t.Fatalf("repaired operation does not materialize: %v", err) + { + _, err := h.ReadOperation(ctx, sessionID, "op-1") + testassert.NoError(t, err) } }) }) @@ -6128,15 +5759,17 @@ func TestPublicRecoverHookShapes(t *testing.T) { rawFailedHookResultPayload(sessionID, hookID, "op-1", "cap.hook.one", "call-1", "error", "hook broke")) pre := recoverEntries(t, store, sessionID) - if err := harness.Recover(ctx, store); err != nil { - t.Fatalf("recover: %v", err) + { + err := harness.Recover(ctx, store) + testassert.NoError(t, err) } assertRecoveredOperation(t, store, sessionID, "op-1", pre, "", pending) assertRecoverIdempotent(t, store, sessionID, "op-1") h := harnessOver(t, store) - if _, err := h.ReadOperation(ctx, sessionID, "op-1"); err != nil { - t.Fatalf("repaired operation does not materialize: %v", err) + { + _, err := h.ReadOperation(ctx, sessionID, "op-1") + testassert.NoError(t, err) } }) }) @@ -6151,31 +5784,27 @@ func TestPublicRecoverHookShapes(t *testing.T) { // replacement for real, then fails the transaction: every recovery // commit must roll back together. probed := &rollbackProbeStore{Storage: store, failReplace: true} - if err := harness.Recover(ctx, probed); !errors.Is(err, errInjectedRollback) { - t.Fatalf("recover with the injected publication failure = %v, want the injected rollback", err) + { + err := harness.Recover(ctx, probed) + testassert.Require(t, errors.Is(err, errInjectedRollback), "recover with the injected publication failure = %v, want the injected rollback", err) } assertSessionUnchanged(t, store, targetID, before) - if err := harness.Recover(ctx, store); err != nil { - t.Fatalf("second recover: %v", err) + { + err := harness.Recover(ctx, store) + testassert.NoError(t, err) } state := recoverOpStateAt(t, store, targetID, "op-1") - if state.Status != "interruption" || state.ActiveEffect != nil || state.Terminal == nil { - t.Fatalf("recovered state = %+v, want the terminal interruption", state) - } + testassert.Require(t, state.Status == "interruption" && state.ActiveEffect == nil && state.Terminal != nil, "recovered state = %+v, want the terminal interruption", state) entries := recoverEntries(t, store, targetID) hookResults := 0 for _, entry := range entries { if entry.Kind == harness.EntryHookResult { hookResults++ - if entry.ID != hookReserved { - t.Fatalf("hook result = %+v, want it under the reserved identity", entry) - } + testassert.Require(t, entry.ID == hookReserved, "hook result = %+v, want it under the reserved identity", entry) } } - if hookResults != 1 { - t.Fatalf("%d hook results after the retried recovery, want exactly the reserved interrupted one", hookResults) - } + testassert.Equal(t, hookResults, 1) assertRecoverIdempotent(t, store, targetID, "op-1") }) }) @@ -6195,20 +5824,24 @@ func TestPublicForkAcrossCompaction(t *testing.T) { ctx := context.Background() conversation := newScriptModel(publicTurn(), publicTurn(), publicTurn()) // op-1, op-2, fork-1 compact := newScriptModel(summaryAttempt("summary one")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() source := createSession(t, f.h) - if _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "first question"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submit(t, f.h, source, "op-1", harness.MessageModeRegular, "first question") + testassert.NoError(t, err) } awaitTerminal(t, f.h, source, "op-1") compactWhenIdle(t, f.h, source, "c-1") - if rec := awaitTerminal(t, f.h, source, "c-1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("manual compact = %+v, want success", rec.State) + { + rec := awaitTerminal(t, f.h, source, "c-1") + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "manual compact = %+v, want success", rec.State) } - if _, err := submit(t, f.h, source, "op-2", harness.MessageModeRegular, "later question"); err != nil { - t.Fatalf("second submit: %v", err) + { + _, err := submit(t, f.h, source, "op-2", harness.MessageModeRegular, "later question") + testassert.NoError(t, err) } awaitSettled(t, f.h, source, "op-2") // submit may buffer during the compact run's retirement; admission is the rendezvous @@ -6222,9 +5855,7 @@ func TestPublicForkAcrossCompaction(t *testing.T) { sourceComp = &before.entries[i] } } - if sourceComp == nil { - t.Fatalf("no compaction entry in the source prefix") - } + testassert.Require(t, sourceComp != nil, "no compaction entry in the source prefix") // The wire compaction entry carries its model as the string // "provider/model" form; the copy observes the identity members only. type compactionWire struct { @@ -6235,12 +5866,11 @@ func TestPublicForkAcrossCompaction(t *testing.T) { Usage *harness.UsageCount `json:"usage"` } var sourceWire compactionWire - if err := json.Unmarshal(sourceComp.Payload, &sourceWire); err != nil { - t.Fatalf("decode source compaction entry: %v", err) - } - if sourceWire.OperationID != "c-1" || sourceWire.Summary != "summary one" || sourceWire.Usage == nil { - t.Fatalf("source compaction entry = %+v, want the c-1-owned committed summary with usage", sourceWire) + { + err := json.Unmarshal(sourceComp.Payload, &sourceWire) + testassert.NoError(t, err) } + testassert.Require(t, sourceWire.OperationID == "c-1" && sourceWire.Summary == "summary one" && sourceWire.Usage != nil, "source compaction entry = %+v, want the c-1-owned committed summary with usage", sourceWire) res, err := f.h.Fork(ctx, harness.ForkRequest{ SourceSessionID: source, @@ -6248,16 +5878,10 @@ func TestPublicForkAcrossCompaction(t *testing.T) { OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) dest := res.Session.Identity.SessionID - if dest == source { - t.Fatalf("fork session identity = %q, want one fresh identity", dest) - } - if len(res.Session.State.Usage.ByModel) != 0 { - t.Fatalf("fork session usage = %+v, want zero inherited usage", res.Session.State.Usage) - } + testassert.Require(t, dest != source, "fork session identity = %q, want one fresh identity", dest) + testassert.Require(t, len(res.Session.State.Usage.ByModel) == 0, "fork session usage = %+v, want zero inherited usage", res.Session.State.Usage) awaitTerminal(t, f.h, dest, "fork-1") destEntries := recoverEntries(t, store, dest) @@ -6279,9 +5903,7 @@ func TestPublicForkAcrossCompaction(t *testing.T) { } idMap := map[string]string{} for i, entry := range expectedCopied { - if destEntries[i].Kind != entry.Kind { - t.Fatalf("copied prefix %d = %s, want the source %s's %s", i, destEntries[i].Kind, entry.ID, entry.Kind) - } + testassert.Require(t, destEntries[i].Kind == entry.Kind, "copied prefix %d = %s, want the source %s's %s", i, destEntries[i].Kind, entry.ID, entry.Kind) idMap[entry.ID] = destEntries[i].ID } // The one copied compaction entry: fresh identity, no operation @@ -6290,78 +5912,45 @@ func TestPublicForkAcrossCompaction(t *testing.T) { var copied *harness.Entry for i := range destEntries { if destEntries[i].Kind == harness.EntryCompaction { - if copied != nil { - t.Fatalf("destination compaction entries: more than one copied") - } + testassert.Require(t, copied == nil, "destination compaction entries: more than one copied") copied = &destEntries[i] } } - if copied == nil { - t.Fatalf("no compaction entry copied into the destination") - } - if copied.ID == sourceComp.ID || copied.OperationID != "" { - t.Fatalf("copied compaction entry = %q/%q, want a fresh identity with no operation ownership", copied.ID, copied.OperationID) - } + testassert.Require(t, copied != nil, "no compaction entry copied into the destination") + testassert.Require(t, copied.ID != sourceComp.ID && copied.OperationID == "", "copied compaction entry = %q/%q, want a fresh identity with no operation ownership", copied.ID, copied.OperationID) var copiedWire compactionWire - if err := json.Unmarshal(copied.Payload, &copiedWire); err != nil { - t.Fatalf("decode copied compaction entry: %v", err) - } - if copiedWire.OperationID != "" { - t.Fatalf("copied compaction entry operation = %q, want no operation ownership", copiedWire.OperationID) - } - if copiedWire.Usage != nil { - t.Fatalf("copied compaction entry usage = %+v, want none", copiedWire.Usage) - } - if copiedWire.Summary != "summary one" { - t.Fatalf("copied summary = %q, want the source summary", copiedWire.Summary) + { + err := json.Unmarshal(copied.Payload, &copiedWire) + testassert.NoError(t, err) } + testassert.Require(t, copiedWire.OperationID == "" && copiedWire.Usage == nil && copiedWire.Summary == "summary one", "got %v, want %v; copied compaction entry usage = %+v, want none; got %v, want %v", copiedWire.OperationID, "", copiedWire.Usage, copiedWire.Summary, "summary one") expectedBoundary, ok := idMap[sourceWire.BoundaryEntryID] - if !ok { - t.Fatalf("the source boundary %q is not inside the copied prefix", sourceWire.BoundaryEntryID) - } - if copiedWire.BoundaryEntryID != expectedBoundary { - t.Fatalf("copied boundary %q != the mapped copy of the source boundary %q", copiedWire.BoundaryEntryID, expectedBoundary) - } - if copiedWire.ConfigurationRevision != sourceWire.ConfigurationRevision { - t.Fatalf("copied revision = %q, want the kept source revision %q", copiedWire.ConfigurationRevision, sourceWire.ConfigurationRevision) - } + testassert.Require(t, ok, "the source boundary %q is not inside the copied prefix", sourceWire.BoundaryEntryID) + testassert.Require(t, copiedWire.BoundaryEntryID == expectedBoundary && copiedWire.ConfigurationRevision == sourceWire.ConfigurationRevision, "got %v, want %v; got %v, want %v", copiedWire.BoundaryEntryID, expectedBoundary, copiedWire.ConfigurationRevision, sourceWire.ConfigurationRevision) // The destination projection is recreated at the copied summary and // inherits none of the source's usage: the source's compact-model // piece counts never reach the destination, whose only totals are its // own fork turn's conversation counts. destReg, err := f.store.ReadRegister(ctx, harness.RegisterKey{SessionID: dest, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("destination session register: %v", err) - } + testassert.NoError(t, err) var destState struct { State struct { CompactionEntryID string `json:"compaction_entry_id"` } `json:"state"` } - if err := json.Unmarshal(destReg.Payload, &destState); err != nil { - t.Fatalf("decode destination session register: %v", err) - } - if destState.State.CompactionEntryID != copied.ID { - t.Fatalf("destination compaction_entry_id = %q, want the copied entry %q", destState.State.CompactionEntryID, copied.ID) + { + err := json.Unmarshal(destReg.Payload, &destState) + testassert.NoError(t, err) } + testassert.Equal(t, destState.State.CompactionEntryID, copied.ID) destView, err := f.h.ReadSessionView(ctx, dest) - if err != nil { - t.Fatalf("ReadSessionView(dest): %v", err) - } - for _, mu := range destView.TotalUsage.ByModel { - if mu.Model == compactModelRef { - t.Fatalf("destination usage = %+v, want no inherited compact-model counts", destView.TotalUsage) - } - } + testassert.NoError(t, err) + testassert.Require(t, len(harness.UsageByModelForTest(destView.TotalUsage, compactModelRef).ByModel) == 0, "destination usage = %+v, want no inherited compact-model counts", destView.TotalUsage) // The fork's own turn projected at the copied summary. reqs := conversation.seen() - if len(reqs) == 0 { - t.Fatalf("the fork execution never reached its model boundary") - } + testassert.Require(t, len(reqs) != 0, "the fork execution never reached its model boundary") last := texts(reqs[len(reqs)-1]) - if len(last) != 3 || !strings.Contains(last[1], "[Previous conversation summary]") || !strings.Contains(last[1], "summary one") || last[2] != "fork input" { - t.Fatalf("fork projection = %q, want the system prompt, the copied summary, and the fork input", last) - } + testassert.Require(t, len(last) == 3 && strings.Contains(last[1], "[Previous conversation summary]") && strings.Contains(last[1], "summary one") && last[2] == "fork input", "fork projection = %q, want the system prompt, the copied summary, and the fork input", last) // The source is unchanged. assertForkSourceUnchanged(t, store, source, before) }) @@ -6397,7 +5986,7 @@ func rawCompactAdmission(sessionID, operationID string) string { `"execution":{"configuration_revision":"rev-1","model":{"provider":"prov","model":"gpt-x"},`+ `"context_window":4096,"output_reserve":2048,`+ `"system_prompt":"system","tools":[%s],"readonly":false,"write_dir":"",`+ - `"compact":{"model":{"provider":"cprov","model":"compact-x"},"context_window":2048,"output_reserve":1024,"system_prompt":"summarize"}},"admitted_at":%q}`, + `"compact":{"model":{"provider":"cprov","model":"compact-x"},"system_prompt":"summarize"}},"admitted_at":%q}`, sessionID, operationID, rawToolDefinition, now) } @@ -6461,14 +6050,13 @@ func TestPublicRecoverRunningCompactOperation(t *testing.T) { // The seeded shape: a running compact request with a live model intent. state := recoverOpStateAt(t, store, sessionID, "compact-op") - if state.Status != "running" || state.ActiveEffect == nil || state.ActiveEffect.Kind != "model" || - state.ActiveEffect.ResultEntryID != reserved { - t.Fatalf("seeded compact operation = %+v, want the running live model intent", state) - } + testassert.Require(t, state.Status == "running" && state.ActiveEffect != nil && state.ActiveEffect.Kind == "model" && state.ActiveEffect.ResultEntryID == reserved, "seeded compact operation = %+v, want the running live model intent", state) pre := recoverEntries(t, store, sessionID) - if err := harness.Recover(ctx, store); err != nil { // quiescent: before any live Harness - t.Fatalf("recover: %v", err) + // quiescent: before any live Harness + { + err := harness.Recover(ctx, store) + testassert.NoError(t, err) } // The compact Operation settles as the runtime-loss interruption with // no effect replay: the settlement consumes the reserved identity and @@ -6484,30 +6072,23 @@ func TestPublicRecoverRunningCompactOperation(t *testing.T) { for _, entry := range entries { if entry.Kind == harness.EntryCompaction { compactions++ - if entry.ID != compactionID { - t.Fatalf("recovery committed a new compaction entry %q, want no effect replay", entry.ID) - } + testassert.Equal(t, entry.ID, compactionID) } } - if compactions != 1 { - t.Fatalf("compaction entries = %d, want only the seeded one", compactions) - } + testassert.Equal(t, compactions, 1) sessReg, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("session register: %v", err) - } + testassert.NoError(t, err) var sessWire struct { State struct { CurrentOperationID string `json:"current_operation_id"` CompactionEntryID string `json:"compaction_entry_id"` } `json:"state"` } - if err := json.Unmarshal(sessReg.Payload, &sessWire); err != nil { - t.Fatalf("decode session register: %v", err) - } - if sessWire.State.CurrentOperationID != "" || sessWire.State.CompactionEntryID != compactionID { - t.Fatalf("recovered session state = %+v, want the cleared current Operation and the previous projection", sessWire.State) + { + err := json.Unmarshal(sessReg.Payload, &sessWire) + testassert.NoError(t, err) } + testassert.Require(t, sessWire.State.CurrentOperationID == "" && sessWire.State.CompactionEntryID == compactionID, "recovered session state = %+v, want the cleared current Operation and the previous projection", sessWire.State) // The next admission over the repaired state projects the prior // summary, the post-boundary entries, and the interruption signal — @@ -6515,14 +6096,13 @@ func TestPublicRecoverRunningCompactOperation(t *testing.T) { script := newScriptModel(publicTurn()) f := newPublicFixture(t, store, script, nil) defer f.close() - if _, err := submit(t, f.h, sessionID, "op-next", harness.MessageModeRegular, "next question"); err != nil { - t.Fatalf("submit after recovery: %v", err) + { + _, err := submit(t, f.h, sessionID, "op-next", harness.MessageModeRegular, "next question") + testassert.NoError(t, err) } awaitTerminal(t, f.h, sessionID, "op-next") reqs := script.seen() - if len(reqs) != 1 { - t.Fatalf("conversation transport calls = %d, want exactly the next admission's one request", len(reqs)) - } + testassert.Equal(t, len(reqs), 1) got := texts(reqs[0]) want := []string{ "system", @@ -6532,13 +6112,9 @@ func TestPublicRecoverRunningCompactOperation(t *testing.T) { "Operation interrupted.", "next question", } - if len(got) != len(want) { - t.Fatalf("next admission projection = %q, want %q", got, want) - } + testassert.Require(t, len(got) == len(want), "next admission projection = %q, want %q", got, want) for i := range want { - if got[i] != want[i] { - t.Fatalf("next admission projection[%d] = %q, want %q", i, got[i], want[i]) - } + testassert.Require(t, got[i] == want[i], "next admission projection[%d] = %q, want %q", i, got[i], want[i]) } }) } diff --git a/harness/recover.go b/harness/recover.go index e2291644..5f95befc 100644 --- a/harness/recover.go +++ b/harness/recover.go @@ -88,7 +88,7 @@ func recoverRunningOperation(ctx context.Context, store Storage, sessionID, oper if currentOp.State.Status != OperationRunning { // a second run finds no running Operation and writes nothing return nil } - _, _, _, err = commitTerminalSettlement(tx, sessionID, operationID, currentSession, currentOp, sreg.Revision, oreg.Revision, OperationInterruption, recoveryInterruptedDetail, nil, model.ModelRef{}, nil) + _, _, _, err = commitTerminalSettlement(tx, sessionID, operationID, currentSession, currentOp, sreg.Revision, oreg.Revision, OperationInterruption, recoveryInterruptedDetail, nil, model.ModelRef{}, nil, false) return err }) } diff --git a/harness/retention_test.go b/harness/retention_test.go index 74c6f337..96dcf9cc 100644 --- a/harness/retention_test.go +++ b/harness/retention_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -29,6 +30,20 @@ import ( // across the release window; a lease a fixture does hold is explicit and is // released by the fixture itself. +// assertMemberCount reports one coordinator's live member count: the count is +// read under the coordinator's own mutex and reported after the mutex is +// released, and the helper neither acquires a graph use nor reads storage. +func assertMemberCount(t *testing.T, c *coordinator, want int) { + t.Helper() + c.mu.Lock() + members := 0 + if c.group != nil { + members = len(c.group.members) + } + c.mu.Unlock() + testassert.Equal(t, members, want) +} + // TestFinalReleaseLeavesNoIdleGraph proves the rule and the survival of every // local fact: a released graph is gone while its cell, local revisions, // groups, and buffers stay, and the graphless cell remains fully usable @@ -45,19 +60,18 @@ func TestFinalReleaseLeavesNoIdleGraph(t *testing.T) { // The first subject carries a live job member. subject := createSession(t, h) var completionID string - if err := h.StartJob(ctx, subject, jobFixtureID, func(_ context.Context, id string) error { - completionID = id - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(ctx, subject, jobFixtureID, func(_ context.Context, id string) error { + completionID = id + return nil + }) + testassert.NoError(t, err) } subjectC := cachedCoordinator(t, h, subject) subjectC.mu.Lock() subjectRev, subjectMembers := subjectC.localRev, len(subjectC.group.members) subjectC.mu.Unlock() - if subjectMembers != 1 { - t.Fatalf("job member count = %d, want one", subjectMembers) - } + testassert.Equal(t, subjectMembers, 1) // The second subject carries a running Operation that owns no execution // and one buffered steering item beside it, driven under the fixture's own @@ -65,9 +79,7 @@ func TestFinalReleaseLeavesNoIdleGraph(t *testing.T) { buffered := createSession(t, h) bufferedC, bufferedLease := leaseCoordinatorNow(t, h, buffered) reservation, err := h.reserve(ctx, bufferedC) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) _, prepared, disposition, err := h.admitReserved(ctx, bufferedC, admissionRequest{ SessionID: buffered, OperationID: "op-running", @@ -76,27 +88,22 @@ func TestFinalReleaseLeavesNoIdleGraph(t *testing.T) { Content: admissionContent("first"), }) reservation() - if err != nil || disposition != DispositionAdmitted { - t.Fatalf("admission = %q err %v, want a new admission", disposition, err) - } - if prepared == nil { - t.Fatalf("the admission returned no prepared execution") - } - if _, err := h.Submit(ctx, SubmitRequest{ - SessionID: buffered, - OperationID: "op-steer", - Origin: InputOriginUser, - Content: admissionContent("steer"), - Mode: MessageModeRegular, - }); err != nil { - t.Fatalf("steering submit: %v", err) + testassert.Require(t, err == nil && disposition == DispositionAdmitted, "admission = %q err %v, want a new admission", disposition, err) + testassert.Require(t, prepared != nil, "the admission returned no prepared execution") + { + _, err := h.Submit(ctx, SubmitRequest{ + SessionID: buffered, + OperationID: "op-steer", + Origin: InputOriginUser, + Content: admissionContent("steer"), + Mode: MessageModeRegular, + }) + testassert.NoError(t, err) } bufferedC.mu.Lock() bufferedRev, bufferedSteering := bufferedC.localRev, len(bufferedC.steering) bufferedC.mu.Unlock() - if bufferedSteering != 1 { - t.Fatalf("steering items = %d, want one", bufferedSteering) - } + testassert.Equal(t, bufferedSteering, 1) bufferedLease() // the explicit lease ends: the graph releases with it // Every released graph is gone; the cells are the same objects and keep @@ -111,8 +118,9 @@ func TestFinalReleaseLeavesNoIdleGraph(t *testing.T) { {subject, subjectC, subjectRev, 1, 0}, {buffered, bufferedC, bufferedRev, 0, 1}, } { - if again := cachedCoordinator(t, h, row.id); again != row.cell { - t.Fatalf("the released cell of %q was replaced", row.id) + { + again := cachedCoordinator(t, h, row.id) + testassert.Require(t, again == row.cell, "the released cell of %q was replaced", row.id) } row.cell.mu.Lock() graph := row.cell.graph @@ -121,60 +129,42 @@ func TestFinalReleaseLeavesNoIdleGraph(t *testing.T) { members = len(row.cell.group.members) } row.cell.mu.Unlock() - if graph != nil { - t.Fatalf("the released graph of %q stayed resident", row.id) - } - if revision != row.revision { - t.Fatalf("local revision of %q = %d, want the retained %d", row.id, revision, row.revision) - } - if members != row.members || steering != row.steering { - t.Fatalf("local facts of %q = %d members, %d steering items, want %d and %d", - row.id, members, steering, row.members, row.steering) - } + testassert.Require(t, graph == nil, "the released graph of %q stayed resident", row.id) + testassert.Require(t, revision == row.revision, "local revision of %q = %d, want the retained %d", row.id, revision, row.revision) + testassert.Require(t, members == row.members && steering == row.steering, "local facts of %q = %d members, %d steering items, want %d and %d", row.id, members, steering, row.members, row.steering) } idleC := cachedCoordinator(t, h, idle) idleC.mu.Lock() idleGraph := idleC.graph idleC.mu.Unlock() - if idleGraph != nil { - t.Fatalf("the idle Session's released graph stayed resident") - } + testassert.Require(t, idleGraph == nil, "the idle Session's released graph stayed resident") // A graphless Session stays usable: a real history use re-materializes and // releases its graph, and a metadata archive adoption commits against the // same cell. - if _, err := h.ReadHistoryView(ctx, idle); err != nil { - t.Fatalf("ReadHistoryView of the graphless Session: %v", err) + { + _, err := h.ReadHistoryView(ctx, idle) + testassert.NoError(t, err) } idleC.mu.Lock() regrown := idleC.graph idleC.mu.Unlock() - if regrown != nil { - t.Fatalf("the real history use left its graph resident after the call") - } + testassert.Require(t, regrown == nil, "the real history use left its graph resident after the call") archived, err := h.ArchiveSession(ctx, idle) - if err != nil || archived.State.Lifecycle != LifecycleArchived { - t.Fatalf("ArchiveSession of the graphless Session = %+v err %v, want the committed archive", archived, err) - } + testassert.Require(t, err == nil && archived.State.Lifecycle == LifecycleArchived, "ArchiveSession of the graphless Session = %+v err %v, want the committed archive", archived, err) // The live member's completion still delivers from the graphless cell; the // delivered execution's own use releases as it settles, leaving no // resident graph behind. - if err := h.DeliverBackgroundCompletion(ctx, subject, completionID, "job done"); err != nil { - t.Fatalf("DeliverBackgroundCompletion from the graphless cell: %v", err) + { + err := h.DeliverBackgroundCompletion(ctx, subject, completionID, "job done") + testassert.NoError(t, err) } - if got := runtimeInputTextRaw(t, store, subject); got != "job done" { - t.Fatalf("delivered completion text = %q, want the job's own content", got) - } - subjectC.mu.Lock() - remaining := 0 - if subjectC.group != nil { - remaining = len(subjectC.group.members) - } - subjectC.mu.Unlock() - if remaining != 0 { - t.Fatalf("the finished job member stayed live: %d members", remaining) + { + got := runtimeInputTextRaw(t, store, subject) + testassert.Equal(t, got, "job done") } + assertMemberCount(t, subjectC, 0) deadline := time.Now().Add(10 * time.Second) // the delivered execution's own pin ends asynchronously for { subjectC.mu.Lock() @@ -183,9 +173,7 @@ func TestFinalReleaseLeavesNoIdleGraph(t *testing.T) { if graph == nil { break } - if time.Now().After(deadline) { - t.Fatalf("the delivered execution left its graph resident") - } + testassert.Require(t, !time.Now().After(deadline), "the delivered execution left its graph resident") time.Sleep(time.Millisecond) } } @@ -200,28 +188,24 @@ func TestOverlappingUsesRetainTheGraphUntilTheFinalRelease(t *testing.T) { ctx := context.Background() cell, lease := leaseCoordinatorNow(t, h, testSessionID) // the interval the test controls - if _, err := h.ReadHistoryView(ctx, testSessionID); err != nil { - t.Fatalf("ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, testSessionID) + testassert.NoError(t, err) } cell.mu.Lock() resident := cell.graph != nil cell.mu.Unlock() - if !resident { - t.Fatalf("the overlapping real use's release cleared the graph although the controlled use was still held") - } + testassert.Require(t, resident, "the overlapping real use's release cleared the graph although the controlled use was still held") lease() cell.mu.Lock() cleared, identity, revision := cell.graph == nil, cell.identity, cell.durableRevision cell.mu.Unlock() - if !cleared { - t.Fatalf("the final release left the graph resident") - } - if identity.SessionID != testSessionID || revision <= 0 { - t.Fatalf("the released cell = identity %+v revision %d, want the retained established values", identity, revision) - } - if _, pair, ok := h.ReadObservation(testSessionID); !ok || pair.DurableRevision != revision { - t.Fatalf("ReadObservation of the released cell = %+v %v, want the retained revision %d", pair, ok, revision) + testassert.Require(t, cleared, "the final release left the graph resident") + testassert.Require(t, identity.SessionID == testSessionID && revision > 0, "the released cell = identity %+v revision %d, want the retained established values", identity, revision) + { + _, pair, ok := h.ReadObservation(testSessionID) + testassert.Require(t, ok && pair.DurableRevision == revision, "ReadObservation of the released cell = %+v %v, want the retained revision %d", pair, ok, revision) } } @@ -241,28 +225,25 @@ func TestLatchedCellSurvivesItsGraphRelease(t *testing.T) { sourceCell.mu.Lock() residentBefore := sourceCell.graph != nil sourceCell.mu.Unlock() - if !residentBefore { - t.Fatalf("the source's graph was not resident before the discovery") - } + testassert.Require(t, residentBefore, "the source's graph was not resident before the discovery") // The real discovery is Fork's store-wide transaction failing on the // foreign-corrupted durable register, which latches the marker on the warm // cell without a load. foreignCorruptRegister(t, store, source) - if _, err := h.Fork(ctx, ForkRequest{ - SourceSessionID: source, - BoundaryEntryID: hexID(1), - OperationID: "op-fork-latched", - Content: admissionContent("fork"), - }); !isCorruption(err) { - t.Fatalf("Fork over the corrupt register = %v, want the typed corruption", err) + { + _, err := h.Fork(ctx, ForkRequest{ + SourceSessionID: source, + BoundaryEntryID: hexID(1), + OperationID: "op-fork-latched", + Content: admissionContent("fork"), + }) + testassert.Require(t, isCorruption(err), "Fork over the corrupt register = %v, want the typed corruption", err) } h.mu.Lock() latched := sourceCell.corru h.mu.Unlock() - if latched == nil { - t.Fatalf("the Fork discovery did not latch the source cell") - } + testassert.Require(t, latched != nil, "the Fork discovery did not latch the source cell") sourceLease() // the latched interval's genuine final release sourceCell.mu.Lock() @@ -271,14 +252,11 @@ func TestLatchedCellSurvivesItsGraphRelease(t *testing.T) { h.mu.Lock() registered := h.sessions[source] == sourceCell h.mu.Unlock() - if !registered || sourceCell.corru == nil { - t.Fatalf("the latched cell did not survive its release (registered %v)", registered) - } - if graph != nil { - t.Fatalf("the latched graph stayed resident after its release") - } - if _, err := h.ReadSessionHeader(ctx, source); !isCorruption(err) { - t.Fatalf("the latched Session read = %v, want the sticky corruption class", err) + testassert.Require(t, registered && sourceCell.corru != nil, "the latched cell did not survive its release (registered %v)", registered) + testassert.Require(t, graph == nil, "the latched graph stayed resident after its release") + { + _, err := h.ReadSessionHeader(ctx, source) + testassert.Require(t, isCorruption(err), "the latched Session read = %v, want the sticky corruption class", err) } // A metadata transition that latches a warm cell leaves it without a @@ -286,27 +264,26 @@ func TestLatchedCellSurvivesItsGraphRelease(t *testing.T) { borrowStore := emptyStore(t) borrowHarness := newTestHarness(t, borrowStore, nil) borrowedSource := createSession(t, borrowHarness) - if _, err := borrowHarness.ReadHistoryView(ctx, borrowedSource); err != nil { - t.Fatalf("ReadHistoryView: %v", err) + { + _, err := borrowHarness.ReadHistoryView(ctx, borrowedSource) + testassert.NoError(t, err) } foreignCorruptRegister(t, borrowStore, borrowedSource) - if _, err := borrowHarness.ArchiveSession(ctx, borrowedSource); !isCorruption(err) { - t.Fatalf("metadata transition over the corrupt register = %v, want the typed corruption", err) + { + _, err := borrowHarness.ArchiveSession(ctx, borrowedSource) + testassert.Require(t, isCorruption(err), "metadata transition over the corrupt register = %v, want the typed corruption", err) } borrowHarness.mu.Lock() borrowedCell := borrowHarness.sessions[borrowedSource] borrowHarness.mu.Unlock() - if borrowedCell == nil || borrowedCell.corru == nil { - t.Fatalf("the metadata transition did not latch the borrowed cell") - } + testassert.Require(t, borrowedCell != nil && borrowedCell.corru != nil, "the metadata transition did not latch the borrowed cell") borrowedCell.mu.Lock() borrowedGraph := borrowedCell.graph borrowedCell.mu.Unlock() - if borrowedGraph != nil { - t.Fatalf("the metadata transition left the latched graph resident") - } - if _, err := borrowHarness.ReadSessionHeader(ctx, borrowedSource); !isCorruption(err) { - t.Fatalf("the latched borrowed Session read = %v, want the sticky corruption class", err) + testassert.Require(t, borrowedGraph == nil, "the metadata transition left the latched graph resident") + { + _, err := borrowHarness.ReadSessionHeader(ctx, borrowedSource) + testassert.Require(t, isCorruption(err), "the latched borrowed Session read = %v, want the sticky corruption class", err) } } @@ -320,56 +297,46 @@ func TestStopAllExcludesNeverLoadedLatchedCells(t *testing.T) { h := newTestHarness(t, store, newPrepareStub(modelPrepared(quickModel())).prepare) ctx := context.Background() - if _, err := h.ReadSessionHeader(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("cold corrupt lookup = %v, want the corruption class", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "cold corrupt lookup = %v, want the corruption class", err) } h.mu.Lock() latchedCell := h.sessions[testSessionID] established := latchedCell != nil && latchedCell.everLoaded h.mu.Unlock() - if latchedCell == nil || latchedCell.corru == nil { - t.Fatalf("the cold corruption did not latch on a stable cell") - } - if established { - t.Fatalf("the latched never-loaded cell was marked established") - } + testassert.Require(t, latchedCell != nil && latchedCell.corru != nil, "the cold corruption did not latch on a stable cell") + testassert.Require(t, !established, "the latched never-loaded cell was marked established") completions := map[string]string{} h.deps.Jobs = &gatedJobStopper{deliver: func(sessionID, jobID string) { _ = h.DeliverBackgroundCompletion(context.Background(), sessionID, completions[jobID], "job done") }} root := createSession(t, h) - if err := h.StartJob(ctx, root, jobFixtureID, func(_ context.Context, completionID string) error { - completions[jobFixtureID] = completionID - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(ctx, root, jobFixtureID, func(_ context.Context, completionID string) error { + completions[jobFixtureID] = completionID + return nil + }) + testassert.NoError(t, err) } - if err := h.StopAll(ctx); err != nil { - t.Fatalf("StopAll = %v, want nil: the never-loaded latched cell owns no stop", err) + { + err := h.StopAll(ctx) + testassert.NoError(t, err) } - if got := h.deps.Jobs.(*gatedJobStopper).stopped; len(got) != 1 || got[0] != root+"/"+jobFixtureID { - t.Fatalf("StopJob calls = %v, want the established root's one member", got) + { + got := h.deps.Jobs.(*gatedJobStopper).stopped + testassert.Require(t, len(got) == 1 && got[0] == root+"/"+jobFixtureID, "StopJob calls = %v, want the established root's one member", got) } - if got := runtimeInputTextRaw(t, store, root); got != "job done" { - t.Fatalf("root completion text = %q, want the stopped member's delivery", got) + { + got := runtimeInputTextRaw(t, store, root) + testassert.Equal(t, got, "job done") } h.mu.Lock() survived := h.sessions[testSessionID] == latchedCell && latchedCell.corru != nil h.mu.Unlock() - if !survived { - t.Fatalf("StopAll dropped the latched never-loaded cell") - } - rootCell := cachedCoordinator(t, h, root) - rootCell.mu.Lock() - members := 0 - if rootCell.group != nil { - members = len(rootCell.group.members) - } - rootCell.mu.Unlock() - if members != 0 { - t.Fatalf("the established root's member stayed live: %d", members) - } + testassert.Require(t, survived, "StopAll dropped the latched never-loaded cell") + assertMemberCount(t, cachedCoordinator(t, h, root), 0) } // cancelledReadStore makes the private fake storage honor a canceled context @@ -414,11 +381,13 @@ func newCancelledCompletionFixture(t *testing.T, storage Storage, store *graphSt addRootSession(t, store, completionParentID) h, cancel := newCancelableHarness(t, storage, PreparedExecution{}, nil) t.Cleanup(cancel) - if _, err := h.ReadHistoryView(ctx, completionChildID); err != nil { - t.Fatalf("child ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, completionChildID) + testassert.NoError(t, err) } - if _, err := h.ReadHistoryView(ctx, completionParentID); err != nil { - t.Fatalf("parent ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, completionParentID) + testassert.NoError(t, err) } child := cachedCoordinator(t, h, completionChildID) parent := cachedCoordinator(t, h, completionParentID) @@ -429,13 +398,9 @@ func newCancelledCompletionFixture(t *testing.T, storage Storage, store *graphSt t.Cleanup(parentRelease) childLease, childRelease := leaseCoordinatorNow(t, h, completionChildID) parentMember, err := admitLocked(h, parentLease, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("child member admission: %v", err) - } + testassert.NoError(t, err) jobMember, err := admitLocked(h, childLease, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("job member admission: %v", err) - } + testassert.NoError(t, err) childLease.mu.Lock() childLease.pendingCompletion = &launchInfo{completionID: parentMember.completionID, outputLimit: 1024} childLease.mu.Unlock() @@ -474,8 +439,9 @@ func TestCancelledMemberFinishSettlesTheChildCompletion(t *testing.T) { // The observer is installed after the admissions, so the collected // facts are the finish-time ones. col := observeHarness(f.h) - if err := f.h.DeliverBackgroundCompletion(context.Background(), completionChildID, f.job.completionID, "job done"); err != nil { - t.Fatalf("job completion delivery: %v", err) + { + err := f.h.DeliverBackgroundCompletion(context.Background(), completionChildID, f.job.completionID, "job done") + testassert.NoError(t, err) } jobFacts := 0 for _, fact := range col.invalidations() { @@ -483,31 +449,18 @@ func TestCancelledMemberFinishSettlesTheChildCompletion(t *testing.T) { jobFacts++ } } - if jobFacts != 1 { - t.Fatalf("child job invalidations = %d, want the member finish's one fact from the graphless cell", jobFacts) - } + testassert.Equal(t, jobFacts, 1) signals := storedSignals(t, store, completionParentID) - if len(signals) != 1 || signals[0].Content != "child text" { - t.Fatalf("parent signals = %+v, want the child's rendered completion as the closed-path record", signals) - } - if childSignals := storedSignals(t, store, completionChildID); len(childSignals) != 1 || childSignals[0].Content != "job done" { - t.Fatalf("child signals = %+v, want the job's own completion record", childSignals) - } - f.parent.mu.Lock() - remaining := 0 - if f.parent.group != nil { - remaining = len(f.parent.group.members) - } - f.parent.mu.Unlock() - if remaining != 0 { - t.Fatalf("the child member stayed live after the settlement: %d members", remaining) + testassert.Require(t, len(signals) == 1 && signals[0].Content == "child text", "parent signals = %+v, want the child's rendered completion as the closed-path record", signals) + { + childSignals := storedSignals(t, store, completionChildID) + testassert.Require(t, len(childSignals) == 1 && childSignals[0].Content == "job done", "child signals = %+v, want the job's own completion record", childSignals) } + assertMemberCount(t, f.parent, 0) f.child.mu.Lock() pending := f.child.pendingCompletion f.child.mu.Unlock() - if pending != nil { - t.Fatalf("the pending completion was not consumed: %+v", pending) - } + testassert.Require(t, pending == nil, "the pending completion was not consumed: %+v", pending) }) t.Run("the settlement's storage failure is retained for Wait", func(t *testing.T) { @@ -554,16 +507,15 @@ func TestCancelledMemberFinishSettlesTheChildCompletion(t *testing.T) { // The storage cause is retained before the abandonment finishes the // parent member; the public wait reports it while the producer is // still parked after that finish. - if err := f.h.Wait(context.Background()); !errors.Is(err, ErrStorage) || !strings.Contains(err.Error(), "child register reload down") { - t.Fatalf("Wait while the producer is parked = %v, want the retained storage cause", err) + { + err := f.h.Wait(context.Background()) + testassert.Require(t, errors.Is(err, ErrStorage) && strings.Contains(err.Error(), "child register reload down"), "Wait while the producer is parked = %v, want the retained storage cause", err) } close(releaseObserver) select { case err := <-producer: - if err != nil { - t.Fatalf("job completion delivery: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the producer never joined") } @@ -571,18 +523,18 @@ func TestCancelledMemberFinishSettlesTheChildCompletion(t *testing.T) { // One attempt, no retry and no fallback write: the child's own signal // is durable, the obligation is abandoned, and the parent received // nothing. - if childSignals := storedSignals(t, store, completionChildID); len(childSignals) != 1 || childSignals[0].Content != "job done" { - t.Fatalf("child signals = %+v, want the job's own completion record", childSignals) + { + childSignals := storedSignals(t, store, completionChildID) + testassert.Require(t, len(childSignals) == 1 && childSignals[0].Content == "job done", "child signals = %+v, want the job's own completion record", childSignals) } - if parentSignals := storedSignals(t, store, completionParentID); len(parentSignals) != 0 { - t.Fatalf("parent signals = %+v, want none after the abandoned settlement", parentSignals) + { + parentSignals := storedSignals(t, store, completionParentID) + testassert.Require(t, len(parentSignals) == 0, "parent signals = %+v, want none after the abandoned settlement", parentSignals) } f.child.mu.Lock() pending := f.child.pendingCompletion f.child.mu.Unlock() - if pending != nil { - t.Fatalf("the abandoned obligation survived: %+v", pending) - } + testassert.Require(t, pending == nil, "the abandoned obligation survived: %+v", pending) }) } @@ -597,11 +549,13 @@ func TestStopAllClosesAnIdleGraphlessChildWithoutLoading(t *testing.T) { h := newTestHarness(t, counting, nil) ctx := context.Background() - if _, err := h.ReadHistoryView(ctx, completionParentID); err != nil { - t.Fatalf("parent ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, completionParentID) + testassert.NoError(t, err) } - if _, err := h.ReadHistoryView(ctx, completionChildID); err != nil { - t.Fatalf("child ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, completionChildID) + testassert.NoError(t, err) } h.mu.Lock() child := h.sessions[completionChildID] @@ -609,23 +563,18 @@ func TestStopAllClosesAnIdleGraphlessChildWithoutLoading(t *testing.T) { child.mu.Lock() graphless := child.graph == nil child.mu.Unlock() - if !graphless { - t.Fatalf("the idle child's graph stayed resident after its read") - } + testassert.Require(t, graphless, "the idle child's graph stayed resident after its read") before := counting.calls - if err := h.StopAll(ctx); err != nil { - t.Fatalf("StopAll: %v", err) - } - if counting.calls != before { - t.Fatalf("StopAll performed %d storage reads, want none: an idle child closes through its local handles", counting.calls-before) + { + err := h.StopAll(ctx) + testassert.NoError(t, err) } + testassert.Require(t, counting.calls == before, "StopAll performed %d storage reads, want none: an idle child closes through its local handles", counting.calls-before) child.mu.Lock() closed := child.bgState == bgClosed child.mu.Unlock() - if !closed { - t.Fatalf("the idle graphless child was not closed by StopAll") - } + testassert.Require(t, closed, "the idle graphless child was not closed by StopAll") } // TestMetadataListingLeavesNoResidentCorpus proves the metadata working set @@ -648,27 +597,22 @@ func TestMetadataListingLeavesNoResidentCorpus(t *testing.T) { } h.mu.Unlock() for i, c := range cells { - if c == nil { - t.Fatalf("%s dropped the established cell of %q", step, ids[i]) - } + testassert.Require(t, c != nil, "%s dropped the established cell of %q", step, ids[i]) c.mu.Lock() graph := c.graph c.mu.Unlock() - if graph != nil { - t.Fatalf("%s left the graph of %q resident", step, ids[i]) - } + testassert.Require(t, graph == nil, "%s left the graph of %q resident", step, ids[i]) } } assertNoResidentGraphs("the creation series") headers, err := h.ListSessions(ctx) - if err != nil || len(headers) != len(ids) { - t.Fatalf("ListSessions = %d rows err %v, want all %d", len(headers), err, len(ids)) - } + testassert.Require(t, err == nil && len(headers) == len(ids), "ListSessions = %d rows err %v, want all %d", len(headers), err, len(ids)) assertNoResidentGraphs("the metadata listing") - if _, err := h.ReadHistoryView(ctx, ids[0]); err != nil { - t.Fatalf("ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, ids[0]) + testassert.NoError(t, err) } assertNoResidentGraphs("the real history use") } @@ -684,42 +628,40 @@ func TestFailedFirstLookupLeavesNoCell(t *testing.T) { store := freshSessionStore(t) h := newTestHarness(t, store, nil) store.registersErr = fmt.Errorf("%w: storage down", ErrStorage) - if _, err := h.ReadSessionHeader(ctx, testSessionID); !errors.Is(err, ErrStorage) { - t.Fatalf("failed first lookup = %v, want the storage class", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.Require(t, errors.Is(err, ErrStorage), "failed first lookup = %v, want the storage class", err) } store.registersErr = nil h.mu.Lock() cell := h.sessions[testSessionID] h.mu.Unlock() - if cell != nil { - t.Fatalf("a failed never-loaded lookup left its cell behind") - } - if _, err := h.ReadSessionHeader(ctx, testSessionID); err != nil { - t.Fatalf("retry after the failed lookup: %v", err) + testassert.Require(t, cell == nil, "a failed never-loaded lookup left its cell behind") + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.NoError(t, err) } corrupt := freshSessionStore(t) foreignCorruptRegister(t, corrupt, testSessionID) counting := &observationCountingStorage{Storage: corrupt} latched := newTestHarness(t, counting, nil) - if _, err := latched.ReadSessionHeader(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("corrupt first lookup = %v, want the corruption class", err) + { + _, err := latched.ReadSessionHeader(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "corrupt first lookup = %v, want the corruption class", err) } latched.mu.Lock() stuck := latched.sessions[testSessionID] latched.mu.Unlock() - if stuck == nil { - t.Fatalf("the corruption did not latch on the stable cell") - } + testassert.Require(t, stuck != nil, "the corruption did not latch on the stable cell") after := counting.calls for i := 0; i < 2; i++ { - if _, err := latched.ReadSessionHeader(ctx, testSessionID); !isCorruption(err) { - t.Fatalf("latched corruption read %d = %v, want the corruption class", i, err) + { + _, err := latched.ReadSessionHeader(ctx, testSessionID) + testassert.Require(t, isCorruption(err), "latched corruption read %d = %v, want the corruption class", i, err) } } - if counting.calls != after { - t.Fatalf("the latched corruption re-read storage %d times, want none", counting.calls-after) - } + testassert.Require(t, counting.calls == after, "the latched corruption re-read storage %d times, want none", counting.calls-after) } // TestStopAllDoesNotLoadIdleCorpus proves the shutdown enumeration row: an @@ -735,12 +677,11 @@ func TestStopAllDoesNotLoadIdleCorpus(t *testing.T) { ids = append(ids, createSession(t, h)) } before := counting.calls - if err := h.StopAll(ctx); err != nil { - t.Fatalf("StopAll: %v", err) - } - if counting.calls != before { - t.Fatalf("StopAll over an idle corpus performed %d storage reads, want none", counting.calls-before) + { + err := h.StopAll(ctx) + testassert.NoError(t, err) } + testassert.Require(t, counting.calls == before, "StopAll over an idle corpus performed %d storage reads, want none", counting.calls-before) h.mu.Lock() cells := make([]*coordinator, 0, len(ids)) for _, id := range ids { @@ -748,18 +689,15 @@ func TestStopAllDoesNotLoadIdleCorpus(t *testing.T) { } h.mu.Unlock() for i, c := range cells { - if c == nil { - t.Fatalf("StopAll dropped the established cell of %q", ids[i]) - } + testassert.Require(t, c != nil, "StopAll dropped the established cell of %q", ids[i]) c.mu.Lock() graph := c.graph c.mu.Unlock() - if graph != nil { - t.Fatalf("StopAll left the graph of %q resident", ids[i]) - } + testassert.Require(t, graph == nil, "StopAll left the graph of %q resident", ids[i]) } - if _, err := h.ReadSessionHeader(ctx, ids[0]); err != nil { - t.Fatalf("read after StopAll: %v", err) + { + _, err := h.ReadSessionHeader(ctx, ids[0]) + testassert.NoError(t, err) } } @@ -780,35 +718,26 @@ func TestSweepCandidateScanOverAColdCorpus(t *testing.T) { ctx := context.Background() candidates, err := h.ListSessionCandidates(ctx) - if err != nil || !reflect.DeepEqual(candidates, ids) { - t.Fatalf("candidates = %v err %v, want the sorted %v", candidates, err, ids) - } - if counting.calls != 0 { - t.Fatalf("candidate enumeration performed %d storage reads, want none", counting.calls) - } + testassert.Require(t, err == nil && reflect.DeepEqual(candidates, ids), "candidates = %v err %v, want the sorted %v", candidates, err, ids) + testassert.Equal(t, counting.calls, 0) h.mu.Lock() cells := len(h.sessions) h.mu.Unlock() - if cells != 0 { - t.Fatalf("candidate enumeration created %d cells, want none", cells) - } + testassert.Equal(t, cells, 0) policy := SweepPolicy{ArchiveAfter: time.Hour, DeleteAfterArchive: time.Hour} now := time.Now().UTC() for _, id := range candidates { commit, err := h.SweepSession(ctx, id, policy, now) - if err != nil || commit { - t.Fatalf("sweep %q = commit %v err %v, want the archive without a deletion", id, commit, err) - } + testassert.Require(t, err == nil && !commit, "sweep %q = commit %v err %v, want the archive without a deletion", id, commit, err) } - if want := 2 * len(ids); counting.calls != want { - t.Fatalf("the candidate scan read storage %d times, want the one register and one entry read per candidate (%d)", counting.calls, want) + { + want := 2 * len(ids) + testassert.Equal(t, counting.calls, want) } for _, id := range ids { header, err := h.ReadSessionHeader(ctx, id) - if err != nil || header.Lifecycle != LifecycleArchived { - t.Fatalf("Session %q after its sweep = %+v err %v, want archived", id, header, err) - } + testassert.Require(t, err == nil && header.Lifecycle == LifecycleArchived, "Session %q after its sweep = %+v err %v, want archived", id, header, err) } } @@ -846,19 +775,19 @@ func TestExecutionUsePinsTheGraphThroughSuccessorDrain(t *testing.T) { h := newTestHarness(t, store, newPrepareStub(modelPrepared(modelFn)).prepare) session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "first"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "first") + testassert.NoError(t, err) } - if _, err := submitText(t, h, session, "op-2", MessageModeQueued, "queued"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeQueued, "queued") + testassert.NoError(t, err) } cell := cachedCoordinator(t, h, session) cell.mu.Lock() pinned := cell.graph cell.mu.Unlock() - if pinned == nil { - t.Fatalf("the running Session's graph was not resident") - } + testassert.Require(t, pinned != nil, "the running Session's graph was not resident") close(first) // the first turn settles and its drain admits the queued successor select { @@ -869,23 +798,20 @@ func TestExecutionUsePinsTheGraphThroughSuccessorDrain(t *testing.T) { cell.mu.Lock() successorGraph := cell.graph cell.mu.Unlock() - if successorGraph != pinned { - t.Fatalf("the successor continued on a different graph than the pinned one") - } + testassert.Equal(t, successorGraph, pinned) close(second) awaitHarnessQuiet(t, h, session) for _, id := range []string{"op-1", "op-2"} { - if rec := settledOperation(t, store, session, id); rec.State.Status != OperationSuccess { - t.Fatalf("operation %q = %+v, want the settled success", id, rec.State) + { + rec := settledOperation(t, store, session, id) + testassert.Require(t, rec.State.Status == OperationSuccess, "operation %q = %+v, want the settled success", id, rec.State) } } cell.mu.Lock() stillResident := cell.graph cell.mu.Unlock() - if stillResident != nil { - t.Fatalf("the Session's graph stayed resident after both Operations settled") - } + testassert.Require(t, stillResident == nil, "the Session's graph stayed resident after both Operations settled") } // TestChildCompletionRendersThroughAGraphlessChild proves the pending child @@ -899,40 +825,31 @@ func TestChildCompletionRendersThroughAGraphlessChild(t *testing.T) { ctx := context.Background() // Warm both Sessions with real reads; each graph is released with its call. - if _, err := h.ReadHistoryView(ctx, completionParentID); err != nil { - t.Fatalf("parent ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, completionParentID) + testassert.NoError(t, err) } - if _, err := h.ReadHistoryView(ctx, completionChildID); err != nil { - t.Fatalf("child ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, completionChildID) + testassert.NoError(t, err) } child := cachedCoordinator(t, h, completionChildID) parent := leaseCoordinator(t, h, completionParentID) // the fixture's explicit parent pin child.mu.Lock() childGraph := child.graph child.mu.Unlock() - if childGraph != nil { - t.Fatalf("the child's released graph stayed resident") - } + testassert.Require(t, childGraph == nil, "the child's released graph stayed resident") member, err := admitLocked(h, parent, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("child member admission: %v", err) - } + testassert.NoError(t, err) child.mu.Lock() child.pendingCompletion = &launchInfo{completionID: member.completionID, outputLimit: 1024} child.mu.Unlock() h.childCompletionSettled(child, "") - if got := runtimeInputTextRaw(t, store, completionParentID); got != "child text" { - t.Fatalf("parent completion text = %q, want the child's rendered settlement", got) - } - parent.mu.Lock() - remaining := 0 - if parent.group != nil { - remaining = len(parent.group.members) - } - parent.mu.Unlock() - if remaining != 0 { - t.Fatalf("the finished child member stayed live: %d members", remaining) + { + got := runtimeInputTextRaw(t, store, completionParentID) + testassert.Equal(t, got, "child text") } + assertMemberCount(t, parent, 0) } diff --git a/harness/schema.go b/harness/schema.go index 3f19bcb1..5c201ab7 100644 --- a/harness/schema.go +++ b/harness/schema.go @@ -134,8 +134,8 @@ type UsageTotals struct { // model identity, the system prompt, the advertised tool definitions in // preserved order, the selected capability names in preserved order, the // Agent definition's permission capability constraints, and the compaction -// configuration — the conversation context window, the output reserve, and -// the effective compact model's selection. Readonly and WriteDir +// configuration — the effective compact model's selection and its prompt. +// Readonly and WriteDir // are the configured lexical constraint copied from the one Harness-selected // definition; WriteDir is already trimmed at projection and stays unchanged in // the capture. The captured ConfigurationRevision also identifies the @@ -157,14 +157,11 @@ type ExecutionCapture struct { } // CompactCapture is the effective compaction configuration of one capture: -// the model the compaction summarizer runs on, its context window and output -// reserve under the same fallback rules as the conversation model, and the -// compact type's system prompt. Fully comparable. +// the model the compaction summarizer runs on and the compact type's system +// prompt. Fully comparable. type CompactCapture struct { - Model model.ModelRef `json:"model"` - ContextWindow int `json:"context_window"` - OutputReserve int `json:"output_reserve"` - SystemPrompt string `json:"system_prompt"` + Model model.ModelRef `json:"model"` + SystemPrompt string `json:"system_prompt"` } // SessionIdentity is the immutable identity section of one Session register. @@ -374,20 +371,25 @@ type signalEntry struct { // operationSettlementEntry publishes one terminal Operation outcome. It is // never operationless; the model identity is present exactly when the -// settlement carries usage. +// settlement carries usage. CompactionUsage marks that carried usage as the +// compaction summarizer's own: omitted or false identifies conversation usage, +// true identifies summary usage, and with no usage carried the marker is +// inert. type operationSettlementEntry struct { - SessionID string `json:"session_id"` - EntryID string `json:"entry_id"` - OperationID string `json:"operation_id"` - Status OperationState `json:"status"` - Detail string `json:"detail,omitempty"` - Model *model.ModelRef `json:"model,omitempty"` - Usage *UsageCount `json:"usage,omitempty"` + SessionID string `json:"session_id"` + EntryID string `json:"entry_id"` + OperationID string `json:"operation_id"` + Status OperationState `json:"status"` + Detail string `json:"detail,omitempty"` + Model *model.ModelRef `json:"model,omitempty"` + Usage *UsageCount `json:"usage,omitempty"` + CompactionUsage bool `json:"compaction_usage,omitempty"` } -// compactionEntry is one immutable compaction commit: the rolling summary of -// everything up to its boundary entry, the compact model identity that -// produced it, the governing configuration revision, and the commit's +// compactionEntry is one immutable compaction commit: the single-request +// summary of the frozen conversation up to its boundary entry, the compact +// model identity that produced it, the governing configuration revision, and +// the commit's // reported usage when present. Independently copied fork-prefix entries omit // the owning Operation identity and carry no source usage. type compactionEntry struct { diff --git a/harness/snapshot.go b/harness/snapshot.go index 0021bfc0..c5c6c965 100644 --- a/harness/snapshot.go +++ b/harness/snapshot.go @@ -338,21 +338,29 @@ func (v HistoryReadView) HistoryFacts(entryIDs []string) []HistoryFact { } // LatestContextUsage returns the newest owned usage measurement of the -// captured context: scanning backwards from the newest entry, the first -// usage-bearing assistant or settlement entry before the captured current -// compaction boundary. It returns nil when no measurement follows the -// boundary; compaction usage stays totals-only. +// captured context through the shared conversation-usage selector. func (v HistoryReadView) LatestContextUsage() *UsageCount { - for i := len(v.prefix) - 1; i >= 0; i-- { - entry := v.prefix[i] - if v.compactionEntryID != "" && entry.Envelope.ID == v.compactionEntryID { + return latestContextUsage(v.prefix, v.compactionEntryID) +} + +// latestContextUsage scans backwards from the newest entry for the newest +// reported conversation usage: a usage-bearing assistant entry, or a +// usage-bearing settlement entry whose CompactionUsage marker is false. A +// settlement marked as summary usage and a compaction entry's usage are never +// eligible — the scan skips them and keeps going. The scan stops at the +// current compaction boundary and returns nil there and on exhaustion; +// a returned measurement is an owned copy. +func latestContextUsage(entries []graphEntry, compactionEntryID string) *UsageCount { + for i := len(entries) - 1; i >= 0; i-- { + entry := entries[i] + if compactionEntryID != "" && entry.Envelope.ID == compactionEntryID { return nil } var usage *UsageCount switch { case entry.Assistant != nil: usage = entry.Assistant.Usage - case entry.Settlement != nil: + case entry.Settlement != nil && !entry.Settlement.CompactionUsage: usage = entry.Settlement.Usage } if usage != nil { diff --git a/harness/snapshot_public_test.go b/harness/snapshot_public_test.go index cfd52237..914de4b5 100644 --- a/harness/snapshot_public_test.go +++ b/harness/snapshot_public_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -36,9 +37,7 @@ func historyIDsOf(headers []harness.HistoryEntryHeader) []string { func readHistoryFacts(t *testing.T, h *harness.Harness, sessionID string) []harness.HistoryFact { t.Helper() view, err := h.ReadHistoryView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadHistoryView(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadHistoryView(%s): %v", sessionID, err) return view.HistoryFacts(historyIDsOf(view.HistoryHeaders())) } @@ -78,46 +77,28 @@ func TestPublicReadSessionHistoryOwnedCapture(t *testing.T) { return harness.PreparedExecution{}, errors.New("the narrow read fixture never prepares") }, }) - if err != nil { - t.Fatalf("New: %v", err) - } + testassert.NoError(t, err) defer func() { cancel() _ = h.Wait(context.Background()) }() view, err := h.ReadHistoryView(ctx, fixture.SessionID) - if err != nil { - t.Fatalf("ReadHistoryView: %v", err) - } + testassert.NoError(t, err) facts := view.HistoryFacts(historyIDsOf(view.HistoryHeaders())) - if fixture.HookEntryID == "" { - t.Fatal("the owning fixture lost its hook evidence entry") - } - if len(facts) != len(fixture.Identities) { - t.Fatalf("history = %d facts, want the %d non-hook entries", len(facts), len(fixture.Identities)) - } + testassert.Require(t, fixture.HookEntryID != "", "%v", "the owning fixture lost its hook evidence entry") + testassert.Equal(t, len(facts), len(fixture.Identities)) for i, want := range fixture.Identities { got := facts[i] - if got.EntryID != want.EntryID || got.OperationID != want.OperationID || got.Kind != want.Kind { - t.Fatalf("fact %d = (%s/%s/%s), want the fixture envelope (%s/%s/%s)", - i, got.EntryID, got.OperationID, got.Kind, want.EntryID, want.OperationID, want.Kind) - } - if got.EntryID == fixture.HookEntryID { - t.Fatalf("fact %d leaked the hook evidence entry", i) - } - } - if view.Revision.LocalRevision != 0 || view.Revision.DurableRevision <= 0 { - t.Fatalf("history revision pair = %+v, want the durable register revision with a fresh coordinator's zero local counter", view.Revision) + testassert.Require(t, got.EntryID == want.EntryID && got.OperationID == want.OperationID && got.Kind == want.Kind && got.EntryID != fixture.HookEntryID, "fact %d = (%s/%s/%s), want the fixture envelope (%s/%s/%s); fact %d leaked the hook evidence entry", i, got.EntryID, got.OperationID, got.Kind, want.EntryID, want.OperationID, want.Kind, i) } + testassert.Require(t, view.Revision.LocalRevision == 0 && view.Revision.DurableRevision > 0, "history revision pair = %+v, want the durable register revision with a fresh coordinator's zero local counter", view.Revision) // The serialized expectation is captured BEFORE any mutation: the // returned values are owned, so a fresh narrow read must still // serialize to it. expected, err := json.Marshal(facts) - if err != nil { - t.Fatalf("marshal the baseline history: %v", err) - } + testassert.NoError(t, err) for i := range facts { fact := &facts[i] if fact.Input != nil && len(fact.Input.Content) > 0 { @@ -165,16 +146,10 @@ func TestPublicReadSessionHistoryOwnedCapture(t *testing.T) { } } again, err := h.ReadHistoryView(ctx, fixture.SessionID) - if err != nil { - t.Fatalf("ReadHistoryView after mutation: %v", err) - } + testassert.NoError(t, err) got, err := json.Marshal(again.HistoryFacts(historyIDsOf(again.HistoryHeaders()))) - if err != nil { - t.Fatalf("marshal the fresh history: %v", err) - } - if !bytes.Equal(got, expected) { - t.Fatal("a mutation of the returned history reached the owner or another read") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(got, expected), "%v", "a mutation of the returned history reached the owner or another read") }) } @@ -197,74 +172,48 @@ func TestPublicSnapshotSessionReadsAndOwns(t *testing.T) { // op-z publishes one call and completes on its second turn; op-a is // admitted only after op-z's terminal committed, so its routing is // deterministic. - if _, err := submit(t, f.h, session, "op-z", harness.MessageModeRegular, "hello op-z"); err != nil { - t.Fatalf("submit op-z: %v", err) + { + _, err := submit(t, f.h, session, "op-z", harness.MessageModeRegular, "hello op-z") + testassert.NoError(t, err) } <-script.arrived // the published call's turn <-script.arrived // the completing turn awaitTerminal(t, f.h, session, "op-z") - if _, err := submit(t, f.h, session, "op-a", harness.MessageModeRegular, "hello op-a"); err != nil { - t.Fatalf("submit op-a: %v", err) + { + _, err := submit(t, f.h, session, "op-a", harness.MessageModeRegular, "hello op-a") + testassert.NoError(t, err) } <-script.arrived // op-a's completing turn awaitTerminal(t, f.h, session, "op-a") - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + err := converge(t, f) + testassert.NoError(t, err) } pristine, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if len(pristine.Operations) != 2 || - pristine.Operations[0].OperationID != "op-a" || - pristine.Operations[1].OperationID != "op-z" { - t.Fatalf("operations = %+v, want lexicographic order over the warm append order", pristine.Operations) - } - if pristine.History.ExecutionBusy || pristine.History.Revision.LocalRevision == 0 { - t.Fatalf("quiet view = busy %v local %d", pristine.History.ExecutionBusy, pristine.History.Revision.LocalRevision) - } - if pristine.Header.Identity.SessionID != session || pristine.Header.Lifecycle != harness.LifecycleOpen { - t.Fatalf("session header = %+v", pristine.Header) - } + testassert.NoError(t, err) + testassert.Require(t, len(pristine.Operations) == 2 && pristine.Operations[0].OperationID == "op-a" && pristine.Operations[1].OperationID == "op-z" && (!pristine.History.ExecutionBusy && pristine.History.Revision.LocalRevision != 0) && (pristine.Header.Identity.SessionID == session && pristine.Header.Lifecycle == harness.LifecycleOpen), "operations = %+v, want lexicographic order over the warm append order; quiet view = busy %v local %d; session header = %+v", pristine.Operations, pristine.History.ExecutionBusy, pristine.History.Revision.LocalRevision, pristine.Header) wantKinds := []harness.EntryKind{ harness.EntryInput, harness.EntryAssistant, harness.EntryToolResult, harness.EntryAssistant, harness.EntryOperationSettlement, harness.EntryInput, harness.EntryAssistant, harness.EntryOperationSettlement, } pristineFacts := pristine.History.HistoryFacts(historyIDsOf(pristine.History.HistoryHeaders())) - if len(pristineFacts) != len(wantKinds) { - t.Fatalf("facts = %d, want %d", len(pristineFacts), len(wantKinds)) - } + testassert.Equal(t, len(pristineFacts), len(wantKinds)) for i, fact := range pristineFacts { - if fact.Kind != wantKinds[i] { - t.Fatalf("fact[%d].Kind = %q, want %q", i, fact.Kind, wantKinds[i]) - } - if fact.OperationID == "" || fact.EntryID == "" || fact.Sequence == 0 || fact.CommittedAt.IsZero() { - t.Fatalf("fact[%d] loses its envelope identity: %+v", i, fact) - } + testassert.Require(t, fact.Kind == wantKinds[i] && (fact.OperationID != "" && fact.EntryID != "" && fact.Sequence != 0 && !fact.CommittedAt.IsZero()), "fact[%d].Kind = %q, want %q; fact[%d] loses its envelope identity: %+v", i, fact.Kind, wantKinds[i], i, fact) } assistant := pristineFacts[1].Assistant - if assistant == nil || len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].ID != "call-1" || - assistant.ToolCalls[0].ResultEntryID == "" { - t.Fatalf("assistant fact = %+v", assistant) - } + testassert.Require(t, assistant != nil && len(assistant.ToolCalls) == 1 && assistant.ToolCalls[0].ID == "call-1" && assistant.ToolCalls[0].ResultEntryID != "", "assistant fact = %+v", assistant) result := pristineFacts[2].ToolResult // the durable codec's JSON encoding escapes the raw metadata's markup - if result == nil || result.ToolCallID != "call-1" || - string(result.Metadata) != `{"n":[1,1.0],"s":"\u003c\u003e\u0026"}` { - t.Fatalf("tool result fact = %+v", result) - } + testassert.Require(t, result != nil && result.ToolCallID == "call-1" && string(result.Metadata) == `{"n":[1,1.0],"s":"\u003c\u003e\u0026"}`, "tool result fact = %+v", result) for i, seq := range []int64{1, 2, 3, 4, 5, 6, 7, 8} { - if pristineFacts[i].Sequence != seq { - t.Fatalf("fact[%d].Sequence = %d, want ascending %d", i, pristineFacts[i].Sequence, seq) - } + testassert.Require(t, pristineFacts[i].Sequence == seq, "fact[%d].Sequence = %d, want ascending %d", i, pristineFacts[i].Sequence, seq) } listed, err := f.h.ListSessions(ctx) - if err != nil || len(listed) != 1 || listed[0].Identity.SessionID != session { - t.Fatalf("ListSessions = %+v err %v", listed, err) - } + testassert.Require(t, err == nil && len(listed) == 1 && listed[0].Identity.SessionID == session, "ListSessions = %+v err %v", listed, err) }) } @@ -331,73 +280,53 @@ func TestPublicSnapshotSessionPendingPublications(t *testing.T) { session := createSession(t, f.h) baseline, err := f.h.ReadSessionView(ctx, session) - if err != nil || baseline.History.Revision.LocalRevision != 0 || baseline.History.ExecutionBusy { - t.Fatalf("baseline = %+v err %v", baseline, err) - } + testassert.Require(t, err == nil && baseline.History.Revision.LocalRevision == 0 && !baseline.History.ExecutionBusy, "baseline = %+v err %v", baseline, err) // The idle admission holds the reservation across its preparation. submitDone := make(chan struct{}) go func() { defer close(submitDone) - if _, err := f.h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, OperationID: "op-1", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }); err != nil { - t.Errorf("submit op-1: %v", err) + { + _, err := f.h.Submit(ctx, harness.SubmitRequest{ + SessionID: session, OperationID: "op-1", Origin: harness.InputOriginUser, + Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, + }) + testassert.Check(t, err == nil, "submit op-1: %v", err) } }() <-f.prepare // the preparation entered and parks on its gate; the reservation is held reserved, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read during preparation: %v", err) - } + testassert.NoError(t, err) if !reserved.History.ExecutionBusy || len(reserved.Operations) != 0 || len(reserved.History.HistoryHeaders()) != 0 { t.Fatalf("read during preparation = busy %v ops %d facts %d, want the entirely-old state with the busy reservation", reserved.History.ExecutionBusy, len(reserved.Operations), len(reserved.History.HistoryHeaders())) } - if reserved.History.Revision.LocalRevision != baseline.History.Revision.LocalRevision+1 { - t.Fatalf("reservation revision = %d, want %d", reserved.History.Revision.LocalRevision, baseline.History.Revision.LocalRevision+1) - } + testassert.Equal(t, reserved.History.Revision.LocalRevision, baseline.History.Revision.LocalRevision+1) releasePrep() // op-1's preparation proceeds <-submitDone <-script.arrived // the run is parked at its boundary installed, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after installation: %v", err) - } - if !installed.History.ExecutionBusy || len(installed.Operations) != 1 || installed.Operations[0].OperationID != "op-1" { - t.Fatalf("read after installation = %+v", installed) - } - if installed.History.Revision.LocalRevision != reserved.History.Revision.LocalRevision+2 { // the run slot's installation and the reservation release - t.Fatalf("installed revision = %d, want %d", installed.History.Revision.LocalRevision, reserved.History.Revision.LocalRevision+2) - } + testassert.NoError(t, err) + // the run slot's installation and the reservation release + testassert.Require(t, installed.History.ExecutionBusy && len(installed.Operations) == 1 && installed.Operations[0].OperationID == "op-1" && installed.History.Revision.LocalRevision == reserved.History.Revision.LocalRevision+2, "read after installation = %+v; got %v, want %v", installed, installed.History.Revision.LocalRevision, reserved.History.Revision.LocalRevision+2) // Active routing buffers by mode: each Submit's reservation, enqueue, // and release are three publications. - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } - if _, err := submit(t, f.h, session, "op-3", harness.MessageModeRegular, "steer-1"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submit(t, f.h, session, "op-3", harness.MessageModeRegular, "steer-1") + testassert.NoError(t, err) } buffered, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after buffering: %v", err) - } - if len(buffered.Steering) != 1 || buffered.Steering[0].OperationID != "op-3" || - len(buffered.Queued) != 1 || buffered.Queued[0].OperationID != "op-2" { - t.Fatalf("buffers after the submits = %+v / %+v", buffered.Steering, buffered.Queued) - } - if buffered.History.Revision.LocalRevision != installed.History.Revision.LocalRevision+6 { - t.Fatalf("buffered revision = %d, want %d", buffered.History.Revision.LocalRevision, installed.History.Revision.LocalRevision+6) - } + testassert.NoError(t, err) + testassert.Require(t, len(buffered.Steering) == 1 && buffered.Steering[0].OperationID == "op-3" && len(buffered.Queued) == 1 && buffered.Queued[0].OperationID == "op-2" && buffered.History.Revision.LocalRevision == installed.History.Revision.LocalRevision+6, "buffers after the submits = %+v / %+v; got %v, want %v", buffered.Steering, buffered.Queued, buffered.History.Revision.LocalRevision, installed.History.Revision.LocalRevision+6) buffered.Steering[0].Content[0].Text = "mutated" buffered.Queued[0].Content[0].Text = "mutated" reread, err := f.h.ReadSessionView(ctx, session) - if err != nil || reread.Steering[0].Content[0].Text != "steer-1" || reread.Queued[0].Content[0].Text != "queued-1" { - t.Fatalf("a pending input's content mutation reached the coordinator: %q / %q err %v", - reread.Steering[0].Content[0].Text, reread.Queued[0].Content[0].Text, err) - } + testassert.Require(t, err == nil && reread.Steering[0].Content[0].Text == "steer-1" && reread.Queued[0].Content[0].Text == "queued-1", "a pending input's content mutation reached the coordinator: %q / %q err %v", reread.Steering[0].Content[0].Text, reread.Queued[0].Content[0].Text, err) // Releasing the boundary settles op-1 quietly through the one // completion owner; its Close parks before the drain, so success is @@ -406,23 +335,15 @@ func TestPublicSnapshotSessionPendingPublications(t *testing.T) { script.releaseGate() <-closeArrived // the predecessor's Close parks parkedClose, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read at the parked Close: %v", err) - } - if parkedClose.Header.CurrentOperationID != "" { - t.Fatalf("current operation at the parked Close = %q, want the settled predecessor", parkedClose.Header.CurrentOperationID) - } + testassert.NoError(t, err) + testassert.Equal(t, parkedClose.Header.CurrentOperationID, "") for _, op := range parkedClose.Operations { - if op.OperationID == "op-1" && op.Status != harness.OperationSuccess { - t.Fatalf("op-1 = %q at the parked Close, want the quiet success readable", op.Status) - } - } - if len(parkedClose.Steering) != 1 || parkedClose.Steering[0].OperationID != "op-3" || - len(parkedClose.Queued) != 1 || parkedClose.Queued[0].OperationID != "op-2" { - t.Fatalf("heads at the parked Close = %+v / %+v, want both still pending", parkedClose.Steering, parkedClose.Queued) + testassert.Require(t, op.OperationID != "op-1" || op.Status == harness.OperationSuccess, "op-1 = %q at the parked Close, want the quiet success readable", op.Status) } - if got := preparedCalls.Load(); got != 1 { - t.Fatalf("preparations = %d, want no successor preparation/Open before the Close", got) + testassert.Require(t, len(parkedClose.Steering) == 1 && parkedClose.Steering[0].OperationID == "op-3" && len(parkedClose.Queued) == 1 && parkedClose.Queued[0].OperationID == "op-2", "heads at the parked Close = %+v / %+v, want both still pending", parkedClose.Steering, parkedClose.Queued) + { + got := preparedCalls.Load() + testassert.Equal(t, got, 1) } // Archive and Fork reach the held reservation's wait — observed // through each call's own waitContext barrier — and their own live @@ -448,9 +369,7 @@ func TestPublicSnapshotSessionPendingPublications(t *testing.T) { archiveCancel() select { case err := <-archiveDone: - if !errors.Is(err, context.Canceled) { - t.Fatalf("Archive at the held reservation = %v, want the canceled wait", err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "Archive at the held reservation = %v, want the canceled wait", err) case <-time.After(10 * time.Second): t.Fatal("Archive never returned after its cancellation") } @@ -461,9 +380,7 @@ func TestPublicSnapshotSessionPendingPublications(t *testing.T) { boundary = fact.EntryID } } - if boundary == "" { - t.Fatalf("the predecessor's admitted entry is missing from the parked read") - } + testassert.Require(t, boundary != "", "the predecessor's admitted entry is missing from the parked read") forkBase, forkCancel := context.WithCancel(ctx) defer forkCancel() forkWait := &waitContext{Context: forkBase, consulted: make(chan struct{})} @@ -488,80 +405,50 @@ func TestPublicSnapshotSessionPendingPublications(t *testing.T) { forkCancel() select { case err := <-forkDone: - if !errors.Is(err, context.Canceled) { - t.Fatalf("Fork at the held reservation = %v, want the canceled wait", err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "Fork at the held reservation = %v, want the canceled wait", err) case <-time.After(10 * time.Second): t.Fatal("Fork never returned after its cancellation") } releaseClose() // completion resumes: the drain selects the steering head and its admission parks <-f.prepare // the drain admission of the steering head (op-3) is preparing parked, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read during the parked delivery: %v", err) - } - if len(parked.Steering) != 1 || parked.Steering[0].OperationID != "op-3" { - t.Fatalf("steering buffer during the parked delivery = %+v, want the selected head still pending until adoption", parked.Steering) - } - if len(parked.Queued) != 1 || parked.Queued[0].OperationID != "op-2" { - t.Fatalf("queued buffer during the parked delivery = %+v, want the queued head pending behind the steering head", parked.Queued) - } - if parked.Header.CurrentOperationID != "" { - t.Fatalf("current operation during the parked delivery = %q, want the settled predecessor cleared", parked.Header.CurrentOperationID) - } + testassert.NoError(t, err) + testassert.Require(t, len(parked.Steering) == 1 && parked.Steering[0].OperationID == "op-3" && (len(parked.Queued) == 1 && parked.Queued[0].OperationID == "op-2") && parked.Header.CurrentOperationID == "", "steering buffer during the parked delivery = %+v, want the selected head still pending until adoption; queued buffer during the parked delivery = %+v, want the queued head pending behind the steering head; got %v, want %v", parked.Steering, parked.Queued, parked.Header.CurrentOperationID, "") for _, op := range parked.Operations { - if op.OperationID == "op-2" || op.OperationID == "op-3" { - t.Fatalf("the pending item %s committed before its delivery completed", op.OperationID) - } - if op.OperationID == "op-1" && op.Status != harness.OperationSuccess { - t.Fatalf("op-1 = %q during the parked delivery, want the quiet success", op.Status) - } + testassert.Require(t, op.OperationID != "op-2" && op.OperationID != "op-3" && (op.OperationID != "op-1" || op.Status == harness.OperationSuccess), "the pending item %s committed before its delivery completed; op-1 = %q during the parked delivery, want the quiet success", op.OperationID, op.Status) } // the one completion owner's reservation is the only local publication // between the buffering and the parked admission: one reservation and // one release span the whole delivery cycle - if parked.History.Revision.LocalRevision != buffered.History.Revision.LocalRevision+1 { - t.Fatalf("parked revision = %d, want %d", parked.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+1) - } + testassert.Equal(t, parked.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+1) releaseDrain() // the steering head admits, runs, and settles; the queued head follows <-script.arrived // the admitted steering Operation's execution started awaitTerminal(t, f.h, session, "op-3") <-f.prepare // the queued head's admission preparation <-script.arrived // the queued Operation's execution started awaitTerminal(t, f.h, session, "op-2") - if err := converge(t, f); err == nil || !strings.Contains(err.Error(), "cleanup broke") { - t.Fatalf("Wait = %v, want the retained cleanup failure", err) + { + err := converge(t, f) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "cleanup broke"), "Wait = %v, want the retained cleanup failure", err) } - if rec, err := f.h.ReadOperation(ctx, session, "op-1"); err != nil || rec.State.Status != harness.OperationSuccess { - t.Fatalf("op-1 after Wait = %+v err %v, want the settlement preserved", rec, err) + { + rec, err := f.h.ReadOperation(ctx, session, "op-1") + testassert.Require(t, err == nil && rec.State.Status == harness.OperationSuccess, "op-1 after Wait = %+v err %v, want the settlement preserved", rec, err) } final, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("final read: %v", err) - } - if final.History.ExecutionBusy || len(final.Steering) != 0 || len(final.Queued) != 0 { - t.Fatalf("final read = busy %v steering %d queued %d", final.History.ExecutionBusy, len(final.Steering), len(final.Queued)) - } - if len(final.Operations) != 3 { - t.Fatalf("final operations = %d, want the three split Operations", len(final.Operations)) - } + testassert.NoError(t, err) + testassert.Require(t, !final.History.ExecutionBusy && len(final.Steering) == 0 && len(final.Queued) == 0 && len(final.Operations) == 3, "final read = busy %v steering %d queued %d; got %v, want %v", final.History.ExecutionBusy, len(final.Steering), len(final.Queued), len(final.Operations), 3) for _, fact := range final.History.HistoryFacts(historyIDsOf(final.History.HistoryHeaders())) { if fact.Kind != harness.EntryInput || fact.Input == nil || len(fact.Input.Content) == 0 { continue } switch fact.Input.Content[0].Text { case "hello": - if fact.OperationID != "op-1" { - t.Fatalf("admitted input owned by %q, want op-1", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "op-1") case "steer-1": - if fact.OperationID != "op-3" { - t.Fatalf("steering input owned by %q, want its own Operation op-3", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "op-3") case "queued-1": - if fact.OperationID != "op-2" { - t.Fatalf("queued input owned by %q, want op-2", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "op-2") } } }) @@ -574,35 +461,30 @@ func TestPublicSnapshotSessionPendingPublications(t *testing.T) { f := newPublicFixture(t, store, script, nil) defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived - if _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submit(t, f.h, session, "op-2", harness.MessageModeQueued, "queued-1") + testassert.NoError(t, err) } buffered, err := f.h.ReadSessionView(ctx, session) - if err != nil || len(buffered.Queued) != 1 { - t.Fatalf("buffered read = %+v err %v", buffered, err) - } - - f.cancel() // the Harness loss closes admission and discards the buffers - go script.releaseGate() // the parked attempt unblocks; its effect settles and the run converges - if err := f.h.Wait(context.Background()); err != nil { // joins every in-flight execution - t.Fatalf("Wait: %v", err) + testassert.Require(t, err == nil && len(buffered.Queued) == 1, "buffered read = %+v err %v", buffered, err) + + f.cancel() // the Harness loss closes admission and discards the buffers + go script.releaseGate() // the parked attempt unblocks; its effect settles and the run converges + // joins every in-flight execution + { + err := f.h.Wait(context.Background()) + testassert.NoError(t, err) } after, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after the loss: %v", err) - } - if len(after.Queued) != 0 || len(after.Steering) != 0 { - t.Fatalf("buffers after the loss = %+v / %+v", after.Steering, after.Queued) - } + testassert.NoError(t, err) // the discard publication plus the loss path's reservation, // release, and run-slot retirement - if after.History.Revision.LocalRevision != buffered.History.Revision.LocalRevision+4 { - t.Fatalf("discard revision = %d, want %d", after.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+4) - } + testassert.Require(t, len(after.Queued) == 0 && len(after.Steering) == 0 && after.History.Revision.LocalRevision == buffered.History.Revision.LocalRevision+4, "buffers after the loss = %+v / %+v; got %v, want %v", after.Steering, after.Queued, after.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+4) }) }) } @@ -615,21 +497,21 @@ func TestPublicSnapshotSessionCompactAndDeletion(t *testing.T) { ctx := context.Background() conversation := newScriptModel(publicTurn()) compact := newScriptModel(summaryAttempt("Summary.")) - f := newCompactManualFixture(t, store, conversation, compact) + f := newPublicFixtureWithJobs(t, store, nil, conversation, nil, compact.effect) + f.defaultPrep.Capture = compactManualCapture() defer f.close() session := createSession(t, f.h) - if _, err := f.h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, OperationID: "op-1", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := f.h.Submit(ctx, harness.SubmitRequest{ + SessionID: session, OperationID: "op-1", Origin: harness.InputOriginUser, + Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, + }) + testassert.NoError(t, err) } awaitTerminal(t, f.h, session, "op-1") awaitQuiet(t, f.h, session) // the retiring run's drain publications land before the stable baseline before, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read before the compact: %v", err) - } + testassert.NoError(t, err) compactPrepStarted := make(chan struct{}) compactGate := make(chan struct{}) @@ -656,61 +538,49 @@ func TestPublicSnapshotSessionCompactAndDeletion(t *testing.T) { t.Fatalf("Compact returned before its preparation started: %v", err) } duringCompact, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read during the compact preparation: %v", err) - } - if !duringCompact.History.ExecutionBusy { - t.Fatalf("read during the compact preparation = busy %v, want the held reservation", duringCompact.History.ExecutionBusy) - } + testassert.NoError(t, err) + testassert.Require(t, duringCompact.History.ExecutionBusy, "read during the compact preparation = busy %v, want the held reservation", duringCompact.History.ExecutionBusy) for _, op := range duringCompact.Operations { - if op.OperationID == "comp-1" { - t.Fatalf("the compact Operation appeared before its commit") - } - } - if duringCompact.History.Revision.LocalRevision <= before.History.Revision.LocalRevision { - t.Fatalf("reservation revision = %d, want an advance past %d", duringCompact.History.Revision.LocalRevision, before.History.Revision.LocalRevision) + testassert.Require(t, op.OperationID != "comp-1", "the compact Operation appeared before its commit") } + testassert.Require(t, duringCompact.History.Revision.LocalRevision > before.History.Revision.LocalRevision, "reservation revision = %d, want an advance past %d", duringCompact.History.Revision.LocalRevision, before.History.Revision.LocalRevision) releaseCompactGate() - if err := <-compactDone; err != nil { - t.Fatalf("Compact: %v", err) + { + err := <-compactDone + testassert.NoError(t, err) } awaitTerminal(t, f.h, session, "comp-1") after, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after the compact: %v", err) - } + testassert.NoError(t, err) var compaction *harness.CompactionEntry for _, fact := range after.History.HistoryFacts(historyIDsOf(after.History.HistoryHeaders())) { if fact.Compaction != nil { compaction = fact.Compaction } } - if compaction == nil || compaction.Summary != "Summary." || compaction.Model != compactModelRef { - t.Fatalf("compaction fact = %+v", compaction) - } + testassert.Require(t, compaction != nil && compaction.Summary == "Summary." && compaction.Model == compactModelRef, "compaction fact = %+v", compaction) found := false for _, op := range after.Operations { if op.OperationID == "comp-1" { found = true } } - if !found { - t.Fatalf("the compact Operation is missing from the read") - } + testassert.Require(t, found, "the compact Operation is missing from the read") - if _, err := f.h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.NoError(t, err) } - if err := f.h.DeleteSession(ctx, session); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := f.h.DeleteSession(ctx, session) + testassert.NoError(t, err) } - if _, err := f.h.ReadSessionView(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read after deletion = %v, want the not-found class", err) + { + _, err := f.h.ReadSessionView(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read after deletion = %v, want the not-found class", err) } listed, err := f.h.ListSessions(ctx) - if err != nil || len(listed) != 0 { - t.Fatalf("ListSessions after deletion = %+v err %v", listed, err) - } + testassert.Require(t, err == nil && len(listed) == 0, "ListSessions after deletion = %+v err %v", listed, err) }) } @@ -794,31 +664,32 @@ func TestPublicListSessionsValidationAndFailures(t *testing.T) { // Corrupt one sibling's register directly through the storage contract. reg := sessionRegister(t, store, first) var obj map[string]json.RawMessage - if err := json.Unmarshal(reg.Payload, &obj); err != nil { - t.Fatalf("unmarshal session payload: %v", err) + { + err := json.Unmarshal(reg.Payload, &obj) + testassert.NoError(t, err) } obj["state"] = json.RawMessage(`{"lifecycle":"bogus"}`) bad, err := json.Marshal(obj) - if err != nil { - t.Fatalf("marshal session payload: %v", err) - } + testassert.NoError(t, err) key := harness.RegisterKey{SessionID: first, Kind: harness.RegisterSession} - if err := store.Transact(ctx, func(tx harness.Transaction) error { - _, err := tx.ReplaceRegister(key, reg.Revision, bad) - return err - }); err != nil { - t.Fatalf("corrupt the register: %v", err) + { + err := store.Transact(ctx, func(tx harness.Transaction) error { + _, err := tx.ReplaceRegister(key, reg.Revision, bad) + return err + }) + testassert.NoError(t, err) } // A malformed stored identity: non-empty, not the durable 32-hex shape. - if err := store.Transact(ctx, func(tx harness.Transaction) error { - _, err := tx.InsertRegister(harness.RegisterDraft{ - Key: harness.RegisterKey{SessionID: "corrupt-id", Kind: harness.RegisterSession}, - Payload: json.RawMessage(`{}`), + { + err := store.Transact(ctx, func(tx harness.Transaction) error { + _, err := tx.InsertRegister(harness.RegisterDraft{ + Key: harness.RegisterKey{SessionID: "corrupt-id", Kind: harness.RegisterSession}, + Payload: json.RawMessage(`{}`), + }) + return err }) - return err - }); err != nil { - t.Fatalf("plant the malformed row: %v", err) + testassert.NoError(t, err) } counting := &countingValidationStorage{Storage: store, register: map[string]int{}, entries: map[string]int{}} @@ -826,19 +697,17 @@ func TestPublicListSessionsValidationAndFailures(t *testing.T) { defer lister.close() listed, err := lister.h.ListSessions(ctx) - if err != nil { - t.Fatalf("ListSessions: %v", err) - } + testassert.NoError(t, err) want := []string{second, third} sort.Strings(want) // storage returns sorted identities; creation order is random - if len(listed) != 2 || listed[0].Identity.SessionID != want[0] || listed[1].Identity.SessionID != want[1] { - t.Fatalf("ListSessions = %+v, want the valid siblings in the sorted listing order", listed) - } - if _, err := lister.h.ReadSessionHeader(ctx, first); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("corrupt direct read = %v, want the corruption error", err) + testassert.Require(t, len(listed) == 2 && listed[0].Identity.SessionID == want[0] && listed[1].Identity.SessionID == want[1], "ListSessions = %+v, want the valid siblings in the sorted listing order", listed) + { + _, err := lister.h.ReadSessionHeader(ctx, first) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "corrupt direct read = %v, want the corruption error", err) } - if _, err := lister.h.ReadSessionHeader(ctx, "corrupt-id"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("malformed direct read = %v, want the invalid class", err) + { + _, err := lister.h.ReadSessionHeader(ctx, "corrupt-id") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "malformed direct read = %v, want the invalid class", err) } // A listing is metadata-only: each candidate's graph is validated once @@ -846,48 +715,47 @@ func TestPublicListSessionsValidationAndFailures(t *testing.T) { // so a repeat listing revalidates the corpus instead of retaining it, // and the latched corrupt candidate is never re-read at all. registersBefore, entriesBefore := counting.counts(second) - if _, err := lister.h.ListSessions(ctx); err != nil { - t.Fatalf("second ListSessions: %v", err) + { + _, err := lister.h.ListSessions(ctx) + testassert.NoError(t, err) } registersAfter, entriesAfter := counting.counts(second) - if registersAfter != registersBefore+1 || entriesAfter != entriesBefore+1 { - t.Fatalf("the listed valid Session graph reads = %d/%d -> %d/%d, want exactly one revalidation per listing", - registersBefore, entriesBefore, registersAfter, entriesAfter) - } + testassert.Require(t, registersAfter == registersBefore+1 && entriesAfter == entriesBefore+1, "the listed valid Session graph reads = %d/%d -> %d/%d, want exactly one revalidation per listing", registersBefore, entriesBefore, registersAfter, entriesAfter) corruptRegistersBefore, _ := counting.counts(first) - if _, err := lister.h.ListSessions(ctx); err != nil { - t.Fatalf("third ListSessions: %v", err) + { + _, err := lister.h.ListSessions(ctx) + testassert.NoError(t, err) } corruptRegistersAfter, _ := counting.counts(first) - if corruptRegistersAfter != corruptRegistersBefore { - t.Fatalf("the latched corrupt Session was revalidated %d times, want none: the corruption sticks to its cell", - corruptRegistersAfter-corruptRegistersBefore) - } - if malformedRegisters, malformedEntries := counting.counts("corrupt-id"); malformedRegisters != 0 || malformedEntries != 0 { - t.Fatalf("the malformed identity reached storage (%d/%d), want the shape filter to drop it before any read", - malformedRegisters, malformedEntries) + testassert.Require(t, corruptRegistersAfter == corruptRegistersBefore, "the latched corrupt Session was revalidated %d times, want none: the corruption sticks to its cell", corruptRegistersAfter-corruptRegistersBefore) + { + malformedRegisters, malformedEntries := counting.counts("corrupt-id") + testassert.Require(t, malformedRegisters == 0 && malformedEntries == 0, "the malformed identity reached storage (%d/%d), want the shape filter to drop it before any read", malformedRegisters, malformedEntries) } // Deletion removes the row from the listing while the direct read // reports the typed absence. - if _, err := lister.h.ArchiveSession(ctx, second); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := lister.h.ArchiveSession(ctx, second) + testassert.NoError(t, err) } - if err := lister.h.DeleteSession(ctx, second); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := lister.h.DeleteSession(ctx, second) + testassert.NoError(t, err) } - if _, err := lister.h.ArchiveSession(ctx, third); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := lister.h.ArchiveSession(ctx, third) + testassert.NoError(t, err) } - if err := lister.h.DeleteSession(ctx, third); err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := lister.h.DeleteSession(ctx, third) + testassert.NoError(t, err) } listed, err = lister.h.ListSessions(ctx) - if err != nil || len(listed) != 0 { - t.Fatalf("ListSessions after deletion = %+v err %v", listed, err) - } - if _, err := lister.h.ReadSessionHeader(ctx, second); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("deleted direct read = %v, want the not-found class", err) + testassert.Require(t, err == nil && len(listed) == 0, "ListSessions after deletion = %+v err %v", listed, err) + { + _, err := lister.h.ReadSessionHeader(ctx, second) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "deleted direct read = %v, want the not-found class", err) } }) @@ -901,20 +769,23 @@ func TestPublicListSessionsValidationAndFailures(t *testing.T) { failing.listErr = errors.New("enumeration failed") probe := newPublicFixture(t, failing, newScriptModel(), nil) defer probe.close() - if _, err := probe.h.ListSessions(ctx); err == nil || !strings.Contains(err.Error(), "enumeration failed") { - t.Fatalf("ListSessions with a failing enumeration = %v, want the propagated failure", err) + { + _, err := probe.h.ListSessions(ctx) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "enumeration failed"), "ListSessions with a failing enumeration = %v, want the propagated failure", err) } failing.listErr = nil failing.entriesErrFor[session] = errors.New("entry read failed") - if _, err := probe.h.ListSessions(ctx); err == nil || !strings.Contains(err.Error(), "entry read failed") { - t.Fatalf("ListSessions with a failing materialization = %v, want the propagated failure", err) + { + _, err := probe.h.ListSessions(ctx) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "entry read failed"), "ListSessions with a failing materialization = %v, want the propagated failure", err) } failing.entriesErrFor[session] = nil canceled, cancel := context.WithCancel(ctx) cancel() - if _, err := probe.h.ListSessions(canceled); err == nil { - t.Fatalf("ListSessions with a canceled context succeeded, want the propagated cancellation") + { + _, err := probe.h.ListSessions(canceled) + testassert.Require(t, err != nil, "ListSessions with a canceled context succeeded, want the propagated cancellation") } }) } @@ -931,28 +802,24 @@ func TestPublicSnapshotSessionDeletionRace(t *testing.T) { f := newPublicFixture(t, store, script, nil) defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived awaitTerminal(t, f.h, session, "op-1") awaitQuiet(t, f.h, session) - if _, err := f.h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.NoError(t, err) } baseline, err := f.h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("archived baseline read: %v", err) - } + testassert.NoError(t, err) expected, err := json.Marshal(baseline) - if err != nil { - t.Fatalf("marshal the archived baseline: %v", err) - } + testassert.NoError(t, err) expectedHeaders := baseline.History.HistoryHeaders() expectedFacts := baseline.History.HistoryFacts(historyIDsOf(expectedHeaders)) - if len(expectedHeaders) == 0 || len(expectedFacts) == 0 { - t.Fatal("the archived baseline carries no committed history") - } + testassert.Require(t, len(expectedHeaders) != 0 && len(expectedFacts) != 0, "%v", "the archived baseline carries no committed history") const readers = 4 const reads = 50 @@ -1053,7 +920,7 @@ func backgroundPrepared(gate, arrived chan struct{}) harness.PreparedExecution { // on the later gate; every model invocation signals arrived. func gatedBackgroundFixture(t *testing.T, store harness.Storage, stopper harness.JobStopper, script *scriptModel, first, later, arrived chan struct{}) *publicFixture { t.Helper() - f := newPublicFixtureWithJobs(t, store, stopper, script, nil) + f := newPublicFixtureWithJobs(t, store, stopper, script, nil, nil) calls := 0 f.prepareHook = func(_ int, _ harness.PreparationRequest) (harness.PreparedExecution, error) { calls++ @@ -1084,9 +951,7 @@ func TestPublicSnapshotJobMemberRevisions(t *testing.T) { session := createSession(t, h) baseline, err := h.ReadSessionView(ctx, session) - if err != nil || baseline.History.Revision.LocalRevision != 0 || len(baseline.Background) != 0 { - t.Fatalf("baseline = %+v err %v", baseline, err) - } + testassert.Require(t, err == nil && baseline.History.Revision.LocalRevision == 0 && len(baseline.Background) == 0, "baseline = %+v err %v", baseline, err) // A spawn that fails still cleans its member up: one admission // publication and one finish publication. @@ -1102,84 +967,57 @@ func TestPublicSnapshotJobMemberRevisions(t *testing.T) { }() <-spawnEntered admitted, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read during the spawn: %v", err) - } - if len(admitted.Background) != 1 || admitted.Background[0] != (harness.BackgroundMemberView{Kind: "job", ID: "000000aa"}) { - t.Fatalf("background during the spawn = %+v", admitted.Background) - } - if admitted.History.Revision.LocalRevision != baseline.History.Revision.LocalRevision+1 { - t.Fatalf("admission revision = %d, want %d", admitted.History.Revision.LocalRevision, baseline.History.Revision.LocalRevision+1) - } + testassert.NoError(t, err) + testassert.Require(t, len(admitted.Background) == 1 && admitted.Background[0] == (harness.BackgroundMemberView{Kind: "job", ID: "000000aa"}) && admitted.History.Revision.LocalRevision == baseline.History.Revision.LocalRevision+1, "background during the spawn = %+v; got %v, want %v", admitted.Background, admitted.History.Revision.LocalRevision, baseline.History.Revision.LocalRevision+1) close(spawnRelease) - if err := <-startErr; err == nil || !strings.Contains(err.Error(), "spawn down") { - t.Fatalf("StartJob error = %v, want the spawn failure", err) + { + err := <-startErr + testassert.Require(t, err != nil && strings.Contains(err.Error(), "spawn down"), "StartJob error = %v, want the spawn failure", err) } cleaned, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after the failed start: %v", err) - } - if len(cleaned.Background) != 0 { - t.Fatalf("background after the failed start = %+v", cleaned.Background) - } - if cleaned.History.Revision.LocalRevision != admitted.History.Revision.LocalRevision+1 { - t.Fatalf("cleanup revision = %d, want %d", cleaned.History.Revision.LocalRevision, admitted.History.Revision.LocalRevision+1) - } + testassert.NoError(t, err) + testassert.Require(t, len(cleaned.Background) == 0 && cleaned.History.Revision.LocalRevision == admitted.History.Revision.LocalRevision+1, "background after the failed start = %+v; got %v, want %v", cleaned.Background, cleaned.History.Revision.LocalRevision, admitted.History.Revision.LocalRevision+1) // An active Session steers a delivered completion under the // completion identity; the member finishes after the delivery. - if _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-arrived // the admitted Operation reached its parked boundary active, err := h.ReadSessionView(ctx, session) - if err != nil || !active.History.ExecutionBusy { - t.Fatalf("active read = %+v err %v", active, err) - } + testassert.Require(t, err == nil && active.History.ExecutionBusy, "active read = %+v err %v", active, err) secondCompletion := "" - if err := h.StartJob(ctx, session, "000000bb", func(_ context.Context, completionID string) error { - secondCompletion = completionID - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(ctx, session, "000000bb", func(_ context.Context, completionID string) error { + secondCompletion = completionID + return nil + }) + testassert.NoError(t, err) } withMember, err := h.ReadSessionView(ctx, session) - if err != nil || len(withMember.Background) != 1 || withMember.Background[0].ID != "000000bb" { - t.Fatalf("background after the start = %+v err %v", withMember.Background, err) - } + testassert.Require(t, err == nil && len(withMember.Background) == 1 && withMember.Background[0].ID == "000000bb", "background after the start = %+v err %v", withMember.Background, err) beforeDelivery := withMember.History.Revision.LocalRevision - if err := h.DeliverBackgroundCompletion(ctx, session, secondCompletion, "bg answer"); err != nil { - t.Fatalf("DeliverBackgroundCompletion: %v", err) + { + err := h.DeliverBackgroundCompletion(ctx, session, secondCompletion, "bg answer") + testassert.NoError(t, err) } delivered, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after the delivery: %v", err) - } - if len(delivered.Background) != 0 { - t.Fatalf("background after the delivery = %+v", delivered.Background) - } - if len(delivered.Steering) != 1 || delivered.Steering[0].OperationID != secondCompletion || - delivered.Steering[0].Origin != harness.InputOriginRuntime || delivered.Steering[0].Content[0].Text != "bg answer" { - t.Fatalf("steering after the delivery = %+v", delivered.Steering) - } - if delivered.History.Revision.LocalRevision != beforeDelivery+2 { // the enqueue and the finish - t.Fatalf("delivery revision = %d, want %d", delivered.History.Revision.LocalRevision, beforeDelivery+2) - } + testassert.NoError(t, err) + // the enqueue and the finish + testassert.Require(t, len(delivered.Background) == 0 && (len(delivered.Steering) == 1 && delivered.Steering[0].OperationID == secondCompletion && delivered.Steering[0].Origin == harness.InputOriginRuntime && delivered.Steering[0].Content[0].Text == "bg answer") && delivered.History.Revision.LocalRevision == beforeDelivery+2, "background after the delivery = %+v; steering after the delivery = %+v; got %v, want %v", delivered.Background, delivered.Steering, delivered.History.Revision.LocalRevision, beforeDelivery+2) // Converge: the cancellation settles the parked Operation and // discards the buffered completion. f.cancel() go close(parentGate) - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } final, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("final read: %v", err) - } - if final.History.ExecutionBusy || len(final.Steering) != 0 || len(final.Background) != 0 { - t.Fatalf("final read = busy %v steering %d background %d", final.History.ExecutionBusy, len(final.Steering), len(final.Background)) - } + testassert.NoError(t, err) + testassert.Require(t, !final.History.ExecutionBusy && len(final.Steering) == 0 && len(final.Background) == 0, "final read = busy %v steering %d background %d", final.History.ExecutionBusy, len(final.Steering), len(final.Background)) }) } @@ -1199,14 +1037,13 @@ func TestPublicSnapshotChildStopRevisions(t *testing.T) { h := f.h root := createSession(t, h) - if _, err := submit(t, h, root, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, root, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-arrived // the parent's Operation is parked at its boundary parentBefore, err := h.ReadSessionView(ctx, root) - if err != nil || parentBefore.History.Revision.LocalRevision != 3 { - t.Fatalf("parent before the launch = %+v err %v", parentBefore, err) - } + testassert.Require(t, err == nil && parentBefore.History.Revision.LocalRevision == 3, "parent before the launch = %+v err %v", parentBefore, err) res, err := h.LaunchChildSession(ctx, harness.LaunchChildRequest{ ParentSessionID: root, @@ -1216,91 +1053,54 @@ func TestPublicSnapshotChildStopRevisions(t *testing.T) { MaxConcurrent: 1, OutputLimit: 1024, }) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) <-arrived // the child's Operation is parked at its boundary parent, err := h.ReadSessionView(ctx, root) - if err != nil { - t.Fatalf("parent read: %v", err) - } - if len(parent.Background) != 1 || parent.Background[0] != (harness.BackgroundMemberView{Kind: "child", ID: res.ChildSessionID}) { - t.Fatalf("parent background = %+v", parent.Background) - } - if parent.History.Revision.LocalRevision != parentBefore.History.Revision.LocalRevision+1 { - t.Fatalf("admission revision = %d, want %d", parent.History.Revision.LocalRevision, parentBefore.History.Revision.LocalRevision+1) - } + testassert.NoError(t, err) + testassert.Require(t, len(parent.Background) == 1 && parent.Background[0] == (harness.BackgroundMemberView{Kind: "child", ID: res.ChildSessionID}) && parent.History.Revision.LocalRevision == parentBefore.History.Revision.LocalRevision+1, "parent background = %+v; got %v, want %v", parent.Background, parent.History.Revision.LocalRevision, parentBefore.History.Revision.LocalRevision+1) child, err := h.ReadSessionView(ctx, res.ChildSessionID) - if err != nil { - t.Fatalf("child read: %v", err) - } - if !child.History.ExecutionBusy || child.History.Revision.LocalRevision != 1 || len(child.Operations) != 1 || - child.Operations[0].OperationID != "child-op-1" { - t.Fatalf("child read = busy %v rev %d ops %+v", child.History.ExecutionBusy, child.History.Revision.LocalRevision, child.Operations) - } + testassert.NoError(t, err) + testassert.Require(t, child.History.ExecutionBusy && child.History.Revision.LocalRevision == 1 && len(child.Operations) == 1 && child.Operations[0].OperationID == "child-op-1", "child read = busy %v rev %d ops %+v", child.History.ExecutionBusy, child.History.Revision.LocalRevision, child.Operations) childFacts := child.History.HistoryFacts(historyIDsOf(child.History.HistoryHeaders())) - if len(childFacts) != 1 || childFacts[0].Kind != harness.EntryInput { - t.Fatalf("child facts = %+v", childFacts) - } + testassert.Require(t, len(childFacts) == 1 && childFacts[0].Kind == harness.EntryInput, "child facts = %+v", childFacts) - if _, err := submit(t, h, res.ChildSessionID, "op-2", harness.MessageModeRegular, "steer-me"); err != nil { - t.Fatalf("child steering submit: %v", err) + { + _, err := submit(t, h, res.ChildSessionID, "op-2", harness.MessageModeRegular, "steer-me") + testassert.NoError(t, err) } buffered, err := h.ReadSessionView(ctx, res.ChildSessionID) - if err != nil || len(buffered.Steering) != 1 || buffered.History.Revision.LocalRevision != child.History.Revision.LocalRevision+3 { - t.Fatalf("child buffered read = %+v err %v", buffered, err) - } + testassert.Require(t, err == nil && len(buffered.Steering) == 1 && buffered.History.Revision.LocalRevision == child.History.Revision.LocalRevision+3, "child buffered read = %+v err %v", buffered, err) - if err := h.Stop(ctx, res.ChildSessionID); err != nil { - t.Fatalf("Stop(child): %v", err) + { + err := h.Stop(ctx, res.ChildSessionID) + testassert.NoError(t, err) } closed, err := h.ReadSessionView(ctx, res.ChildSessionID) - if err != nil { - t.Fatalf("child read after the stop: %v", err) - } - if len(closed.Steering) != 0 || closed.History.ExecutionBusy { - t.Fatalf("closed child = steering %d busy %v", len(closed.Steering), closed.History.ExecutionBusy) - } + testassert.NoError(t, err) // the closure publication plus the canceled run's retirement drain - if closed.History.Revision.LocalRevision != buffered.History.Revision.LocalRevision+4 { - t.Fatalf("closure revision = %d, want %d", closed.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+4) - } + testassert.Require(t, len(closed.Steering) == 0 && !closed.History.ExecutionBusy && closed.History.Revision.LocalRevision == buffered.History.Revision.LocalRevision+4, "closed child = steering %d busy %v; got %v, want %v", len(closed.Steering), closed.History.ExecutionBusy, closed.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+4) closedFacts := closed.History.HistoryFacts(historyIDsOf(closed.History.HistoryHeaders())) - if len(closedFacts) != 3 || closedFacts[0].Kind != harness.EntryInput || - closedFacts[1].Kind != harness.EntrySignal || - closedFacts[2].Kind != harness.EntryOperationSettlement || - closedFacts[2].Settlement.Status != harness.OperationInterruption { - t.Fatalf("closed child facts = %+v", closedFacts) - } - if err := h.Stop(ctx, res.ChildSessionID); err != nil { // the repeat stop is a no-op - t.Fatalf("repeat Stop(child): %v", err) + testassert.Require(t, len(closedFacts) == 3 && closedFacts[0].Kind == harness.EntryInput && closedFacts[1].Kind == harness.EntrySignal && closedFacts[2].Kind == harness.EntryOperationSettlement && closedFacts[2].Settlement.Status == harness.OperationInterruption, "closed child facts = %+v", closedFacts) + // the repeat stop is a no-op + { + err := h.Stop(ctx, res.ChildSessionID) + testassert.NoError(t, err) } repeated, err := h.ReadSessionView(ctx, res.ChildSessionID) - if err != nil || repeated.History.Revision.LocalRevision != closed.History.Revision.LocalRevision { - t.Fatalf("repeat stop revision = %d err %v, want %d", repeated.History.Revision.LocalRevision, err, closed.History.Revision.LocalRevision) - } + testassert.Require(t, err == nil && repeated.History.Revision.LocalRevision == closed.History.Revision.LocalRevision, "repeat stop revision = %d err %v, want %d", repeated.History.Revision.LocalRevision, err, closed.History.Revision.LocalRevision) // The child's completion steered into the active parent and finished // the parent's member. parent, err = h.ReadSessionView(ctx, root) - if err != nil { - t.Fatalf("parent read after the child stop: %v", err) - } - if len(parent.Background) != 0 { - t.Fatalf("parent background after the finish = %+v", parent.Background) - } - if len(parent.Steering) != 1 || parent.Steering[0].Origin != harness.InputOriginRuntime || - parent.Steering[0].Content[0].Text != "Task interrupted." { - t.Fatalf("parent steering after the delivery = %+v", parent.Steering) - } - if parent.History.Revision.LocalRevision != parentBefore.History.Revision.LocalRevision+3 { // admit, enqueue, finish - t.Fatalf("parent revision after the child stop = %d, want %d", parent.History.Revision.LocalRevision, parentBefore.History.Revision.LocalRevision+3) - } + testassert.NoError(t, err) + // admit, enqueue, finish + testassert.Require(t, len(parent.Background) == 0 && (len(parent.Steering) == 1 && parent.Steering[0].Origin == harness.InputOriginRuntime && parent.Steering[0].Content[0].Text == "Task interrupted.") && parent.History.Revision.LocalRevision == parentBefore.History.Revision.LocalRevision+3, "parent background after the finish = %+v; parent steering after the delivery = %+v; got %v, want %v", parent.Background, parent.Steering, parent.History.Revision.LocalRevision, parentBefore.History.Revision.LocalRevision+3) f.cancel() go close(parentGate) - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } }) } @@ -1333,62 +1133,49 @@ func TestPublicSnapshotRootStopRevisions(t *testing.T) { hh = h session := createSession(t, h) - if _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-arrived before, err := h.ReadSessionView(ctx, session) - if err != nil || before.History.Revision.LocalRevision != 3 { - t.Fatalf("before the start = %+v err %v", before, err) - } + testassert.Require(t, err == nil && before.History.Revision.LocalRevision == 3, "before the start = %+v err %v", before, err) - if err := h.StartJob(ctx, session, "deadbeef", func(_ context.Context, completionID string) error { - mu.Lock() - completions["deadbeef"] = completionID - mu.Unlock() - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(ctx, session, "deadbeef", func(_ context.Context, completionID string) error { + mu.Lock() + completions["deadbeef"] = completionID + mu.Unlock() + return nil + }) + testassert.NoError(t, err) } started, err := h.ReadSessionView(ctx, session) - if err != nil || len(started.Background) != 1 || started.Background[0] != (harness.BackgroundMemberView{Kind: "job", ID: "deadbeef"}) { - t.Fatalf("after the start = %+v err %v", started, err) - } - if started.History.Revision.LocalRevision != before.History.Revision.LocalRevision+1 { - t.Fatalf("admission revision = %d, want %d", started.History.Revision.LocalRevision, before.History.Revision.LocalRevision+1) - } + testassert.Require(t, err == nil && len(started.Background) == 1 && started.Background[0] == (harness.BackgroundMemberView{Kind: "job", ID: "deadbeef"}), "after the start = %+v err %v", started, err) + testassert.Equal(t, started.History.Revision.LocalRevision, before.History.Revision.LocalRevision+1) - if err := h.Stop(ctx, session); err != nil { - t.Fatalf("Stop: %v", err) + { + err := h.Stop(ctx, session) + testassert.NoError(t, err) } stopped, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("read after the stop: %v", err) - } - if len(stopped.Background) != 0 { - t.Fatalf("background after the stop = %+v", stopped.Background) - } - if len(stopped.Steering) != 1 || stopped.Steering[0].Origin != harness.InputOriginRuntime || - stopped.Steering[0].Content[0].Text != "job done" { - t.Fatalf("steering after the stop = %+v", stopped.Steering) - } + testassert.NoError(t, err) // the stopping publication, the completion enqueue, the member finish, // and the reopen publication - if stopped.History.Revision.LocalRevision != started.History.Revision.LocalRevision+4 { - t.Fatalf("stop revision = %d, want %d", stopped.History.Revision.LocalRevision, started.History.Revision.LocalRevision+4) - } - if err := h.Stop(ctx, session); err != nil { // the repeat stop is a no-op - t.Fatalf("repeat Stop: %v", err) + testassert.Require(t, len(stopped.Background) == 0 && (len(stopped.Steering) == 1 && stopped.Steering[0].Origin == harness.InputOriginRuntime && stopped.Steering[0].Content[0].Text == "job done") && stopped.History.Revision.LocalRevision == started.History.Revision.LocalRevision+4, "background after the stop = %+v; steering after the stop = %+v; got %v, want %v", stopped.Background, stopped.Steering, stopped.History.Revision.LocalRevision, started.History.Revision.LocalRevision+4) + // the repeat stop is a no-op + { + err := h.Stop(ctx, session) + testassert.NoError(t, err) } repeated, err := h.ReadSessionView(ctx, session) - if err != nil || repeated.History.Revision.LocalRevision != stopped.History.Revision.LocalRevision { - t.Fatalf("repeat stop revision = %d err %v, want %d", repeated.History.Revision.LocalRevision, err, stopped.History.Revision.LocalRevision) - } + testassert.Require(t, err == nil && repeated.History.Revision.LocalRevision == stopped.History.Revision.LocalRevision, "repeat stop revision = %d err %v, want %d", repeated.History.Revision.LocalRevision, err, stopped.History.Revision.LocalRevision) f.cancel() go close(parentGate) - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } }) } @@ -1437,16 +1224,16 @@ func TestPublicListSessionsStaleEnumeratedRow(t *testing.T) { defer f.close() victim := createSession(t, f.h) sibling := createSession(t, f.h) - if _, err := f.h.ArchiveSession(ctx, victim); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := f.h.ArchiveSession(ctx, victim) + testassert.NoError(t, err) } - if _, err := f.h.ArchiveSession(ctx, sibling); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := f.h.ArchiveSession(ctx, sibling) + testassert.NoError(t, err) } survivor, err := f.h.ReadSessionHeader(ctx, sibling) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } + testassert.NoError(t, err) // Arm the one-shot gate: the store has already returned both // identities, and the listing parks before consuming them. @@ -1462,22 +1249,17 @@ func TestPublicListSessionsStaleEnumeratedRow(t *testing.T) { listDone <- listResult{rows, err} }() <-wrapped.listed - if err := f.h.DeleteSession(ctx, victim); err != nil { - t.Fatalf("DeleteSession across the parked enumeration: %v", err) + { + err := f.h.DeleteSession(ctx, victim) + testassert.NoError(t, err) } close(wrapped.listRelease) result := <-listDone - if result.err != nil { - t.Fatalf("ListSessions: %v", result.err) - } - if len(result.rows) != 1 || result.rows[0].Identity.SessionID != sibling { - t.Fatalf("stale listing = %+v, want only the surviving sibling", result.rows) - } - if !reflect.DeepEqual(result.rows[0], survivor) { - t.Fatalf("the surviving row = %+v, want the complete archived record %+v", result.rows[0], survivor) - } - if _, err := f.h.ReadSessionHeader(ctx, victim); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("the disappeared row's direct read = %v, want the typed not-found class", err) + testassert.Require(t, result.err == nil && (len(result.rows) == 1 && result.rows[0].Identity.SessionID == sibling), "ListSessions: %v; stale listing = %+v, want only the surviving sibling", result.err, result.rows) + testassert.Require(t, reflect.DeepEqual(result.rows[0], survivor), "the surviving row = %+v, want the complete archived record %+v", result.rows[0], survivor) + { + _, err := f.h.ReadSessionHeader(ctx, victim) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "the disappeared row's direct read = %v, want the typed not-found class", err) } }) } @@ -1498,23 +1280,15 @@ func TestPublicSnapshotSessionColdReadFailures(t *testing.T) { entriesFailure := errors.New("entry read failed") wrapped.entriesErrFor[cold] = entriesFailure view, err := f.h.ReadSessionView(ctx, cold) - if !errors.Is(err, entriesFailure) { - t.Fatalf("cold read over a failing entry read = %v, want the exact injected storage failure", err) - } - if !reflect.DeepEqual(view, harness.SessionReadView{}) { - t.Fatalf("failing cold read = %+v, want no partial body", view) - } + testassert.Require(t, errors.Is(err, entriesFailure), "cold read over a failing entry read = %v, want the exact injected storage failure", err) + testassert.Require(t, reflect.DeepEqual(view, harness.SessionReadView{}), "failing cold read = %+v, want no partial body", view) delete(wrapped.entriesErrFor, cold) canceled, cancel := context.WithCancel(ctx) cancel() view, err = f.h.ReadSessionView(canceled, cold) - if !errors.Is(err, context.Canceled) { - t.Fatalf("cold read over a canceled context = %v, want the exact context error", err) - } - if !reflect.DeepEqual(view, harness.SessionReadView{}) { - t.Fatalf("canceled cold read = %+v, want no partial body", view) - } + testassert.Require(t, errors.Is(err, context.Canceled), "cold read over a canceled context = %v, want the exact context error", err) + testassert.Require(t, reflect.DeepEqual(view, harness.SessionReadView{}), "canceled cold read = %+v, want no partial body", view) }) } @@ -1526,15 +1300,11 @@ func awaitQuiet(t *testing.T, h *harness.Harness, session string) { deadline := time.Now().Add(10 * time.Second) for { view, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) if !view.History.ExecutionBusy { return } - if time.Now().After(deadline) { - t.Fatalf("session %s never went quiet within the wait bound", session) - } + testassert.Require(t, !time.Now().After(deadline), "session %s never went quiet within the wait bound", session) time.Sleep(time.Millisecond) } } @@ -1560,7 +1330,7 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { ) parkEntered := make(chan struct{}) parkRelease := make(chan struct{}) - f := newPublicFixtureWithJobs(t, store, snapshotJobStopper{}, script, nil) + f := newPublicFixtureWithJobs(t, store, snapshotJobStopper{}, script, nil, nil) f.prepareHook = func(call int, _ harness.PreparationRequest) (harness.PreparedExecution, error) { capture := publicCapture() capture.Capabilities = []string{"cap.a", "cap.b"} @@ -1610,11 +1380,12 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { } submitExtra := func(operationID, text string, mode harness.MessageMode) { t.Helper() - if _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, OperationID: operationID, Origin: harness.InputOriginUser, - Content: extraPart(text), Mode: mode, - }); err != nil { - t.Fatalf("submit %s: %v", operationID, err) + { + _, err := h.Submit(ctx, harness.SubmitRequest{ + SessionID: session, OperationID: operationID, Origin: harness.InputOriginUser, + Content: extraPart(text), Mode: mode, + }) + testassert.Require(t, err == nil, "submit %s: %v", operationID, err) } } @@ -1635,32 +1406,17 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { submitExtra("op-4", "queue-me", harness.MessageModeQueued) parked, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("parked read: %v", err) - } + testassert.NoError(t, err) parkedExpected, err := json.Marshal(parked) - if err != nil { - t.Fatalf("marshal the parked baseline: %v", err) - } - if !parked.History.ExecutionBusy || len(parked.Operations) != 2 { - t.Fatalf("parked read = busy %v ops %d", parked.History.ExecutionBusy, len(parked.Operations)) - } + testassert.NoError(t, err) + testassert.Require(t, parked.History.ExecutionBusy && len(parked.Operations) == 2, "parked read = busy %v ops %d", parked.History.ExecutionBusy, len(parked.Operations)) parkedOp, err := h.ReadOperation(ctx, session, "op-2") - if err != nil { - t.Fatalf("parked ReadOperation: %v", err) - } - if parkedOp.State.ActiveEffect == nil || parkedOp.State.ActiveEffect.Kind != harness.EffectTool || - len(parkedOp.State.PendingToolCalls) != 1 { - t.Fatalf("parked operation state = %+v, want the active tool effect and its pending call", parkedOp.State) - } - if len(parked.Steering) != 1 || len(parked.Queued) != 1 { - t.Fatalf("parked buffers = %+v / %+v", parked.Steering, parked.Queued) - } + testassert.NoError(t, err) + testassert.Require(t, parkedOp.State.ActiveEffect != nil && parkedOp.State.ActiveEffect.Kind == harness.EffectTool && len(parkedOp.State.PendingToolCalls) == 1, "parked operation state = %+v, want the active tool effect and its pending call", parkedOp.State) + testassert.Require(t, len(parked.Steering) == 1 && len(parked.Queued) == 1, "parked buffers = %+v / %+v", parked.Steering, parked.Queued) mutatedParked, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("parked mutation read: %v", err) - } + testassert.NoError(t, err) mutatedParked.Steering[0].Content[0].Text = "mutated" mutatedParked.Steering[0].Content[0].Extra["q"][6] = '3' mutatedParked.Steering[0].Content[0].Extra["p"] = json.RawMessage(`"mutated"`) @@ -1671,9 +1427,7 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { mutatedParked.Queued[0].Content[0].Extra["p"] = json.RawMessage(`"mutated"`) mutatedParked.Queued = append(mutatedParked.Queued, harness.PendingInput{OperationID: "forged"}) mutatedParkedOp, err := h.ReadOperation(ctx, session, "op-2") - if err != nil { - t.Fatalf("parked mutation ReadOperation: %v", err) - } + testassert.NoError(t, err) mutatedParkedOp.State.ActiveEffect.Kind = "mutated" mutatedParkedOp.State.ActiveEffect.ResultEntryID = "mutated" mutatedParkedOp.State.ActiveEffect.ToolCallID = "mutated" @@ -1682,24 +1436,13 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { mutatedParkedOp.State.PendingToolCalls = append(mutatedParkedOp.State.PendingToolCalls, harness.PendingToolCall{CallID: "forged"}) freshParked, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("parked fresh read: %v", err) - } + testassert.NoError(t, err) freshParkedBytes, err := json.Marshal(freshParked) - if err != nil { - t.Fatalf("marshal the parked fresh read: %v", err) - } - if !bytes.Equal(parkedExpected, freshParkedBytes) { - t.Fatalf("a mutation of the parked read reached the coordinator or another read") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(parkedExpected, freshParkedBytes), "a mutation of the parked read reached the coordinator or another read") freshParkedOp, err := h.ReadOperation(ctx, session, "op-2") - if err != nil { - t.Fatalf("parked fresh ReadOperation: %v", err) - } - if freshParkedOp.State.ActiveEffect.Kind != harness.EffectTool || len(freshParkedOp.State.PendingToolCalls) != 1 || - freshParkedOp.State.ActiveEffect.ToolCallID == "mutated" { - t.Fatalf("a mutation of the parked Operation reached the coordinator: %+v", freshParkedOp.State) - } + testassert.NoError(t, err) + testassert.Require(t, freshParkedOp.State.ActiveEffect.Kind == harness.EffectTool && len(freshParkedOp.State.PendingToolCalls) == 1 && freshParkedOp.State.ActiveEffect.ToolCallID != "mutated", "a mutation of the parked Operation reached the coordinator: %+v", freshParkedOp.State) // Release the parked tool: op-2's completed turn continues for the // waiting steering item, settles, and the queued item drains. @@ -1721,8 +1464,9 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { script.gate = make(chan struct{}) submitExtra("op-5", "fifth", harness.MessageModeRegular) <-script.arrived - if err := h.Interrupt(ctx, session); err != nil { - t.Fatalf("Interrupt: %v", err) + { + err := h.Interrupt(ctx, session) + testassert.NoError(t, err) } script.releaseGate() awaitTerminal(t, h, session, "op-5") @@ -1731,119 +1475,80 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { // A live job member whose completion is delivered under harness loss // commits the operationless background_completion signal. completionID := "" - if err := h.StartJob(ctx, session, "deadbeef", func(_ context.Context, id string) error { - completionID = id - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(ctx, session, "deadbeef", func(_ context.Context, id string) error { + completionID = id + return nil + }) + testassert.NoError(t, err) } f.cancel() - if err := h.DeliverBackgroundCompletion(ctx, session, completionID, "lost work"); err != nil { - t.Fatalf("DeliverBackgroundCompletion: %v", err) + { + err := h.DeliverBackgroundCompletion(ctx, session, completionID, "lost work") + testassert.NoError(t, err) } // Archive stamps the Session's archived time. - if _, err := h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := h.ArchiveSession(ctx, session) + testassert.NoError(t, err) } baseline, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("final read: %v", err) - } + testassert.NoError(t, err) expected, err := json.Marshal(baseline) - if err != nil { - t.Fatalf("marshal the baseline: %v", err) - } + testassert.NoError(t, err) // Every applicable reference must be present in the fixture. - if baseline.Header.ArchivedAt == nil || len(baseline.TotalUsage.ByModel) == 0 { - t.Fatalf("session fixture lost its archived time or usage: %+v", baseline.Header) - } + testassert.Require(t, baseline.Header.ArchivedAt != nil && len(baseline.TotalUsage.ByModel) != 0, "session fixture lost its archived time or usage: %+v", baseline.Header) baselineFacts := baseline.History.HistoryFacts(historyIDsOf(baseline.History.HistoryHeaders())) expectedFacts, err := json.Marshal(baselineFacts) - if err != nil { - t.Fatalf("marshal the baseline facts: %v", err) - } + testassert.NoError(t, err) kinds := map[harness.EntryKind]int{} var relatedOperation, relatedMember, settlementWithUsage, assistantReferences, resultMetadata bool for _, fact := range baselineFacts { kinds[fact.Kind]++ switch fact.Kind { case harness.EntryInput: - if fact.Input == nil || len(fact.Input.Content) == 0 || len(fact.Input.Content[0].Extra) == 0 { - t.Fatalf("input fact fixture lost its extra-bearing content: %+v", fact) - } + testassert.Require(t, fact.Input != nil && len(fact.Input.Content) != 0 && len(fact.Input.Content[0].Extra) != 0, "input fact fixture lost its extra-bearing content: %+v", fact) case harness.EntryAssistant: - if fact.Assistant == nil { - t.Fatalf("assistant fact fixture lost its payload: %+v", fact) - } + testassert.Require(t, fact.Assistant != nil, "assistant fact fixture lost its payload: %+v", fact) assistantReferences = assistantReferences || (len(fact.Assistant.Extra) > 0 && len(fact.Assistant.ToolCalls) > 0 && len(fact.Assistant.ToolCalls[0].Extra) > 0 && len(fact.Assistant.ToolCalls[0].NormalizedArguments) > 0 && fact.Assistant.Usage != nil) case harness.EntryToolResult: - if fact.ToolResult == nil { - t.Fatalf("tool result fact fixture lost its payload: %+v", fact) - } + testassert.Require(t, fact.ToolResult != nil, "tool result fact fixture lost its payload: %+v", fact) resultMetadata = resultMetadata || len(fact.ToolResult.Metadata) > 0 case harness.EntrySignal: - if fact.Signal == nil { - t.Fatalf("signal fact fixture lost its payload: %+v", fact) - } + testassert.Require(t, fact.Signal != nil, "signal fact fixture lost its payload: %+v", fact) relatedOperation = relatedOperation || fact.Signal.RelatedOperation != nil relatedMember = relatedMember || fact.Signal.RelatedMember != nil case harness.EntryCompaction: - if fact.Compaction == nil || fact.Compaction.Usage == nil { - t.Fatalf("compaction fact fixture lost its usage: %+v", fact.Compaction) - } + testassert.Require(t, fact.Compaction != nil && fact.Compaction.Usage != nil, "compaction fact fixture lost its usage: %+v", fact.Compaction) case harness.EntryOperationSettlement: - if fact.Settlement == nil { - t.Fatalf("settlement fact fixture lost its payload: %+v", fact) - } + testassert.Require(t, fact.Settlement != nil, "settlement fact fixture lost its payload: %+v", fact) settlementWithUsage = settlementWithUsage || (fact.Settlement.Model != nil && fact.Settlement.Usage != nil) } } - if !assistantReferences { - t.Fatalf("fixture never produced an assistant fact with its full nested reference set") - } - if !resultMetadata { - t.Fatalf("fixture never produced a tool result carrying tool-owned metadata") - } + testassert.Require(t, assistantReferences, "fixture never produced an assistant fact with its full nested reference set") + testassert.Require(t, resultMetadata, "fixture never produced a tool result carrying tool-owned metadata") for _, kind := range []harness.EntryKind{ harness.EntryInput, harness.EntryAssistant, harness.EntryToolResult, harness.EntrySignal, harness.EntryCompaction, harness.EntryOperationSettlement, } { - if kinds[kind] == 0 { - t.Fatalf("fixture never produced a %s fact", kind) - } - } - if !relatedOperation || !relatedMember { - t.Fatalf("fixture never produced both signal reference shapes (related operation %v, related member %v)", relatedOperation, relatedMember) - } - if !settlementWithUsage { - t.Fatalf("fixture never produced a settlement carrying its model and usage pointers") + testassert.Require(t, kinds[kind] != 0, "fixture never produced a %s fact", kind) } + testassert.Require(t, relatedOperation && relatedMember, "fixture never produced both signal reference shapes (related operation %v, related member %v)", relatedOperation, relatedMember) + testassert.Require(t, settlementWithUsage, "fixture never produced a settlement carrying its model and usage pointers") var recordExpected []byte for _, op := range baseline.Operations { - if op.SettledAt == nil { - t.Fatalf("operation fixture lost its settlement pointer: %+v", op) - } + testassert.Require(t, op.SettledAt != nil, "operation fixture lost its settlement pointer: %+v", op) record, err := h.ReadOperation(ctx, session, op.OperationID) - if err != nil { - t.Fatalf("ReadOperation(%s): %v", op.OperationID, err) - } - if record.State.SettledAt == nil || record.State.Terminal == nil { - t.Fatalf("operation fixture lost its terminal pointers: %+v", record.State) - } - if len(record.Admission.Execution.Tools) == 0 || len(record.Admission.Execution.Tools[0].Parameters) == 0 || - len(record.Admission.Execution.Capabilities) == 0 { - t.Fatalf("operation fixture lost its execution references: %+v", record.Admission.Execution) - } + testassert.Require(t, err == nil, "ReadOperation(%s): %v", op.OperationID, err) + testassert.Require(t, record.State.SettledAt != nil && record.State.Terminal != nil && (len(record.Admission.Execution.Tools) != 0 && len(record.Admission.Execution.Tools[0].Parameters) != 0 && len(record.Admission.Execution.Capabilities) != 0), "operation fixture lost its terminal pointers: %+v; operation fixture lost its execution references: %+v", record.State, record.Admission.Execution) if recordExpected == nil { recordExpected, err = json.Marshal(record) - if err != nil { - t.Fatalf("marshal the baseline operation: %v", err) - } + testassert.NoError(t, err) } } @@ -1851,9 +1556,7 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { // groups ride the composed view; full execution captures stay at // ReadOperation. mutated, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("mutation read: %v", err) - } + testassert.NoError(t, err) mutated.Header.Identity.Workspace = "mutated" *mutated.Header.ArchivedAt = baseline.Header.ArchivedAt.Add(time.Hour) mutated.TotalUsage.ByModel[0].Model.Provider = "mutated" @@ -1871,9 +1574,7 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { mutated.Operations = append(mutated.Operations, harness.OperationSummary{}) mutatedRecord, err := h.ReadOperation(ctx, session, baseline.Operations[0].OperationID) - if err != nil { - t.Fatalf("ReadOperation mutation: %v", err) - } + testassert.NoError(t, err) mutatedRecord.Admission.Execution.Model.Provider = "mutated" mutatedRecord.Admission.Execution.Tools[0].Parameters[9] = 'p' mutatedRecord.Admission.Execution.Tools = append(mutatedRecord.Admission.Execution.Tools, model.ToolDefinition{}) @@ -1888,16 +1589,10 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { mutatedRecord.State.PendingToolCalls = append(mutatedRecord.State.PendingToolCalls, harness.PendingToolCall{CallID: "forged"}) freshRecord, err := h.ReadOperation(ctx, session, baseline.Operations[0].OperationID) - if err != nil { - t.Fatalf("ReadOperation fresh read: %v", err) - } + testassert.NoError(t, err) freshRecordBytes, err := json.Marshal(freshRecord) - if err != nil { - t.Fatalf("marshal the fresh operation: %v", err) - } - if !bytes.Equal(recordExpected, freshRecordBytes) { - t.Fatalf("a mutation of one returned Operation reached the coordinator or another read") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(recordExpected, freshRecordBytes), "a mutation of one returned Operation reached the coordinator or another read") for i := range baselineFacts { fact := &baselineFacts[i] @@ -1966,43 +1661,26 @@ func TestPublicSnapshotOwnershipMatrix(t *testing.T) { baselineFacts = append(baselineFacts, harness.HistoryFact{EntryID: "forged"}) again, err := h.ReadSessionView(ctx, session) - if err != nil { - t.Fatalf("fresh read: %v", err) - } + testassert.NoError(t, err) freshBytes, err := json.Marshal(again) - if err != nil { - t.Fatalf("marshal the fresh read: %v", err) - } - if !bytes.Equal(expected, freshBytes) { - t.Fatalf("a mutation of one read reached the coordinator or another read") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(expected, freshBytes), "a mutation of one read reached the coordinator or another read") freshFacts := readHistoryFacts(t, h, session) freshFactsBytes, err := json.Marshal(freshFacts) - if err != nil { - t.Fatalf("marshal the fresh facts: %v", err) - } - if !bytes.Equal(expectedFacts, freshFactsBytes) { - t.Fatalf("a mutation of one returned fact set reached the coordinator or another read") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(expectedFacts, freshFactsBytes), "a mutation of one returned fact set reached the coordinator or another read") // The mutated read and fact set must actually differ from the // expectations, so the comparisons above cannot pass on untouched data. mutatedBytes, err := json.Marshal(mutated) - if err != nil { - t.Fatalf("marshal the mutated read: %v", err) - } - if bytes.Equal(expected, mutatedBytes) { - t.Fatalf("the mutation matrix changed nothing; the ownership comparison is vacuous") - } + testassert.NoError(t, err) + testassert.Require(t, !bytes.Equal(expected, mutatedBytes), "the mutation matrix changed nothing; the ownership comparison is vacuous") mutatedFactsBytes, err := json.Marshal(baselineFacts) - if err != nil { - t.Fatalf("marshal the mutated facts: %v", err) - } - if bytes.Equal(expectedFacts, mutatedFactsBytes) { - t.Fatalf("the fact mutation matrix changed nothing; the ownership comparison is vacuous") - } - if err := h.Wait(context.Background()); err != nil { - t.Fatalf("Wait: %v", err) + testassert.NoError(t, err) + testassert.Require(t, !bytes.Equal(expectedFacts, mutatedFactsBytes), "the fact mutation matrix changed nothing; the ownership comparison is vacuous") + { + err := h.Wait(context.Background()) + testassert.NoError(t, err) } }) } @@ -2016,15 +1694,11 @@ func awaitStoreQuiet(t *testing.T, h *harness.Harness, session string) { deadline := time.Now().Add(10 * time.Second) for { view, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) if !view.History.ExecutionBusy && view.Header.CurrentOperationID == "" { return } - if time.Now().After(deadline) { - t.Fatalf("session %s never reached the quiet pair", session) - } + testassert.Require(t, !time.Now().After(deadline), "session %s never reached the quiet pair", session) time.Sleep(time.Millisecond) } } @@ -2041,32 +1715,26 @@ func TestPublicListSessionsRowOwnership(t *testing.T) { f := newPublicFixture(t, store, script, nil) defer f.close() session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived awaitTerminal(t, f.h, session, "op-1") - if _, err := f.h.ArchiveSession(ctx, session); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := f.h.ArchiveSession(ctx, session) + testassert.NoError(t, err) } awaitStoreQuiet(t, f.h, session) rows, err := f.h.ListSessions(ctx) - if err != nil || len(rows) != 1 { - t.Fatalf("ListSessions = %+v err %v", rows, err) - } - if rows[0].ArchivedAt == nil { - t.Fatalf("row fixture lost its archived time: %+v", rows[0]) - } + testassert.Require(t, err == nil && len(rows) == 1, "ListSessions = %+v err %v", rows, err) + testassert.Require(t, rows[0].ArchivedAt != nil, "row fixture lost its archived time: %+v", rows[0]) expected, err := json.Marshal(rows) - if err != nil { - t.Fatalf("marshal the baseline rows: %v", err) - } + testassert.NoError(t, err) mutated, err := f.h.ListSessions(ctx) - if err != nil || len(mutated) != 1 { - t.Fatalf("second ListSessions = %+v err %v", mutated, err) - } + testassert.Require(t, err == nil && len(mutated) == 1, "second ListSessions = %+v err %v", mutated, err) *mutated[0].ArchivedAt = mutated[0].ArchivedAt.Add(time.Hour) mutated[0].Identity.Workspace = "mutated" mutated[0].Lifecycle = "mutated" @@ -2074,23 +1742,13 @@ func TestPublicListSessionsRowOwnership(t *testing.T) { mutated = append(mutated, harness.SessionHeader{}) fresh, err := f.h.ListSessions(ctx) - if err != nil || len(fresh) != 1 { - t.Fatalf("fresh ListSessions = %+v err %v", fresh, err) - } + testassert.Require(t, err == nil && len(fresh) == 1, "fresh ListSessions = %+v err %v", fresh, err) freshBytes, err := json.Marshal(fresh) - if err != nil { - t.Fatalf("marshal the fresh rows: %v", err) - } - if !bytes.Equal(expected, freshBytes) { - t.Fatalf("a mutation of one listing reached the coordinator or another listing") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(expected, freshBytes), "a mutation of one listing reached the coordinator or another listing") mutatedBytes, err := json.Marshal(mutated) - if err != nil { - t.Fatalf("marshal the mutated rows: %v", err) - } - if bytes.Equal(expected, mutatedBytes) { - t.Fatalf("the row mutation changed nothing; the ownership comparison is vacuous") - } + testassert.NoError(t, err) + testassert.Require(t, !bytes.Equal(expected, mutatedBytes), "the row mutation changed nothing; the ownership comparison is vacuous") }) } @@ -2140,23 +1798,21 @@ func TestPublicSnapshotDeleteCoherence(t *testing.T) { t.Helper() f := newPublicFixture(t, wrapped, script, nil) session := createSession(t, f.h) - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello") + testassert.NoError(t, err) } <-script.arrived awaitTerminal(t, f.h, session, "op-1") awaitQuiet(t, f.h, session) - if _, err := f.h.ArchiveSession(context.Background(), session); err != nil { - t.Fatalf("ArchiveSession: %v", err) + { + _, err := f.h.ArchiveSession(context.Background(), session) + testassert.NoError(t, err) } baseline, err := f.h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("archived baseline read: %v", err) - } + testassert.NoError(t, err) expected, err := json.Marshal(baseline) - if err != nil { - t.Fatalf("marshal the archived baseline: %v", err) - } + testassert.NoError(t, err) return f.h, session, expected, f.close } @@ -2183,19 +1839,20 @@ func TestPublicSnapshotDeleteCoherence(t *testing.T) { reads <- err }() close(gate.release) - if err := <-deleteDone; err != nil { - t.Fatalf("DeleteSession: %v", err) + { + err := <-deleteDone + testassert.NoError(t, err) } - if err := <-reads; !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read across the held delete = %v, want the typed not-found class", err) + { + err := <-reads + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read across the held delete = %v, want the typed not-found class", err) } - if _, err := h.ReadSessionView(context.Background(), session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("post-delete read = %v, want the typed not-found class", err) + { + _, err := h.ReadSessionView(context.Background(), session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "post-delete read = %v, want the typed not-found class", err) } listed, err := h.ListSessions(context.Background()) - if err != nil || len(listed) != 0 { - t.Fatalf("post-delete listing = %+v err %v", listed, err) - } + testassert.Require(t, err == nil && len(listed) == 0, "post-delete listing = %+v err %v", listed, err) }) }) @@ -2213,25 +1870,18 @@ func TestPublicSnapshotDeleteCoherence(t *testing.T) { }() <-gate.started close(gate.release) - if err := <-deleteDone; err == nil || !strings.Contains(err.Error(), "delete aborted") { - t.Fatalf("DeleteSession = %v, want the injected rollback failure", err) + { + err := <-deleteDone + testassert.Require(t, err != nil && strings.Contains(err.Error(), "delete aborted"), "DeleteSession = %v, want the injected rollback failure", err) } after, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("read after the rollback: %v", err) - } + testassert.NoError(t, err) fresh, err := json.Marshal(after) - if err != nil { - t.Fatalf("marshal the post-rollback read: %v", err) - } - if !bytes.Equal(expected, fresh) { - t.Fatalf("the rolled-back deletion changed the readable state") - } + testassert.NoError(t, err) + testassert.Require(t, bytes.Equal(expected, fresh), "the rolled-back deletion changed the readable state") listed, err := h.ListSessions(context.Background()) - if err != nil || len(listed) != 1 || listed[0].Identity.SessionID != session { - t.Fatalf("post-rollback listing = %+v err %v", listed, err) - } + testassert.Require(t, err == nil && len(listed) == 1 && listed[0].Identity.SessionID == session, "post-rollback listing = %+v err %v", listed, err) }) }) } diff --git a/harness/snapshot_test.go b/harness/snapshot_test.go index a9fb3c21..fb3c1942 100644 --- a/harness/snapshot_test.go +++ b/harness/snapshot_test.go @@ -4,12 +4,14 @@ import ( "context" "encoding/json" "errors" + "io" "reflect" "strings" "sync" "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -126,31 +128,6 @@ func factFixtureGraph() *testGraph { } } -// onlyMember asserts exactly one payload member of one fact is present and -// returns the EntryKind that member's presence implies. -func onlyMember(t *testing.T, fact HistoryFact) EntryKind { - t.Helper() - present := 0 - var kind EntryKind - for member, isKind := range map[EntryKind]bool{ - EntryInput: fact.Input != nil, - EntryAssistant: fact.Assistant != nil, - EntryToolResult: fact.ToolResult != nil, - EntrySignal: fact.Signal != nil, - EntryCompaction: fact.Compaction != nil, - EntryOperationSettlement: fact.Settlement != nil, - } { - if isKind { - present++ - kind = member - } - } - if present != 1 { - t.Fatalf("fact %s carries %d payload members, want exactly one", fact.EntryID, present) - } - return kind -} - // FactIdentity is one expected committed fact's envelope identity. type FactIdentity struct { EntryID string @@ -196,9 +173,7 @@ func seedTestGraph(t *testing.T, store Storage, g *testGraph) { } return nil }) - if err != nil { - t.Fatalf("seed fixture graph: %v", err) - } + testassert.NoError(t, err) } // SeedFactFixture writes the owning fact fixture graph into any Storage and @@ -236,22 +211,26 @@ func SeedFactFixture(t *testing.T, store Storage) FactFixture { func TestNarrowReadsCopyOnlyTheirOwnState(t *testing.T) { h := newTestHarness(t, factFixtureGraph().storage(t), nil) ctx := context.Background() - if _, err := h.ReadSessionHeader(ctx, testSessionID); err != nil { - t.Fatalf("warm ReadSessionHeader: %v", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.NoError(t, err) } - if _, err := h.ReadHistoryView(ctx, testSessionID); err != nil { - t.Fatalf("warm ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, testSessionID) + testassert.NoError(t, err) } c := leaseCoordinator(t, h, testSessionID) // the fixture's explicit warm-view lease measure := func() (header, history float64) { header = testing.AllocsPerRun(200, func() { - if _, err := h.ReadSessionHeader(ctx, testSessionID); err != nil { - t.Fatalf("ReadSessionHeader: %v", err) + { + _, err := h.ReadSessionHeader(ctx, testSessionID) + testassert.NoError(t, err) } }) history = testing.AllocsPerRun(200, func() { - if _, err := h.ReadHistoryView(ctx, testSessionID); err != nil { - t.Fatalf("ReadHistoryView: %v", err) + { + _, err := h.ReadHistoryView(ctx, testSessionID) + testassert.NoError(t, err) } }) return header, history @@ -303,12 +282,11 @@ func TestNarrowReadsCopyOnlyTheirOwnState(t *testing.T) { } factsMeasure := func() float64 { view, err := h.ReadHistoryView(ctx, testSessionID) - if err != nil { - t.Fatalf("ReadHistoryView: %v", err) - } + testassert.NoError(t, err) return testing.AllocsPerRun(200, func() { - if got := view.HistoryFacts(selectedIDs); len(got) != len(selectedIDs) { - t.Fatalf("selected facts = %d, want the fixed %d", len(got), len(selectedIDs)) + { + got := view.HistoryFacts(selectedIDs) + testassert.Equal(t, len(got), len(selectedIDs)) } }) } @@ -321,18 +299,9 @@ func TestNarrowReadsCopyOnlyTheirOwnState(t *testing.T) { addUnselectedHistory(2000) headerLarge, historyLarge := measure() factsLarge := factsMeasure() - if headerMid != headerSmall || headerLarge != headerSmall { - t.Fatalf("header allocations = %v after the small group and %v after the large group, want the unchanged %v", - headerMid, headerLarge, headerSmall) - } - if historyMid != historySmall || historyLarge != historySmall { - t.Fatalf("history allocations = %v after the small group and %v after the large group, want the unchanged %v", - historyMid, historyLarge, historySmall) - } - if factsMid != factsSmall || factsLarge != factsSmall { - t.Fatalf("selected-facts allocations = %v after the small group and %v after the large group, want the unchanged %v", - factsMid, factsLarge, factsSmall) - } + testassert.Require(t, headerMid == headerSmall && headerLarge == headerSmall, "header allocations = %v after the small group and %v after the large group, want the unchanged %v", headerMid, headerLarge, headerSmall) + testassert.Require(t, historyMid == historySmall && historyLarge == historySmall, "history allocations = %v after the small group and %v after the large group, want the unchanged %v", historyMid, historyLarge, historySmall) + testassert.Require(t, factsMid == factsSmall && factsLarge == factsSmall, "selected-facts allocations = %v after the small group and %v after the large group, want the unchanged %v", factsMid, factsLarge, factsSmall) // Added history entries may grow the callers that request facts, but // neither the metadata header nor the history capture copies them. @@ -342,21 +311,15 @@ func TestNarrowReadsCopyOnlyTheirOwnState(t *testing.T) { } c.mu.Unlock() headerWithHistory, historyWithHistory := measure() - if headerWithHistory != headerSmall { - t.Fatalf("header allocations = %v after adding history entries, want the unchanged %v", headerWithHistory, headerSmall) - } - if historyWithHistory != historySmall { - t.Fatalf("history capture allocations = %v after adding history entries, want the unchanged %v", historyWithHistory, historySmall) - } + testassert.Equal(t, headerWithHistory, headerSmall) + testassert.Equal(t, historyWithHistory, historySmall) } // readSessionView reads one Session's composed hydration view. func readSessionView(t *testing.T, h *Harness, sessionID string) SessionReadView { t.Helper() view, err := h.ReadSessionView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadSessionView(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadSessionView(%s): %v", sessionID, err) return view } @@ -364,9 +327,7 @@ func readSessionView(t *testing.T, h *Harness, sessionID string) SessionReadView func readSessionFacts(t *testing.T, h *Harness, sessionID string) []HistoryFact { t.Helper() view, err := h.ReadHistoryView(context.Background(), sessionID) - if err != nil { - t.Fatalf("ReadHistoryView(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadHistoryView(%s): %v", sessionID, err) return view.HistoryFacts(historyHeaderIDs(view.HistoryHeaders())) } @@ -389,17 +350,10 @@ func TestHistoryViewFactUnionAndOwnership(t *testing.T) { t.Run("every payload kind with hook evidence excluded", func(t *testing.T) { h := newTestHarness(t, factFixtureGraph().storage(t), nil) view, err := h.ReadHistoryView(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadHistoryView: %v", err) - } + testassert.NoError(t, err) headers := view.HistoryHeaders() for i, header := range headers { - if header.EntryID == "" { - t.Fatalf("header[%d] = %+v, want the committed envelope identity", i, header) - } - if header.Kind != EntryInput && header.InputOrigin != "" { - t.Fatalf("header[%d] = %+v, want no input origin on a non-input kind", i, header) - } + testassert.Require(t, header.EntryID != "" && (header.Kind == EntryInput || header.InputOrigin == ""), "header[%d] = %+v, want the committed envelope identity; header[%d] = %+v, want no input origin on a non-input kind", i, header, i, header) } facts := view.HistoryFacts(historyHeaderIDs(headers)) @@ -407,84 +361,53 @@ func TestHistoryViewFactUnionAndOwnership(t *testing.T) { EntryInput, EntryAssistant, EntryToolResult, EntryAssistant, EntrySignal, EntryOperationSettlement, EntryCompaction, EntryOperationSettlement, EntrySignal, } - if len(facts) != len(wantKinds) { - t.Fatalf("facts = %d entries, want %d (hook_result excluded)", len(facts), len(wantKinds)) - } + testassert.Equal(t, len(facts), len(wantKinds)) for i, fact := range facts { - if fact.Kind != wantKinds[i] { - t.Fatalf("fact[%d].Kind = %q, want %q", i, fact.Kind, wantKinds[i]) - } - if kind := onlyMember(t, fact); kind != fact.Kind { - t.Fatalf("fact[%d]'s single payload member implies kind %q, want its Kind %q", i, kind, fact.Kind) - } - if fact.EntryID == "" || fact.Sequence == 0 || fact.CommittedAt.IsZero() { - t.Fatalf("fact[%d] loses its envelope identity: %+v", i, fact) + testassert.Require(t, fact.Kind == wantKinds[i], "fact[%d].Kind = %q, want %q", i, fact.Kind, wantKinds[i]) + { + present := 0 + var kind EntryKind + for member, isKind := range map[EntryKind]bool{ + EntryInput: fact.Input != nil, + EntryAssistant: fact.Assistant != nil, + EntryToolResult: fact.ToolResult != nil, + EntrySignal: fact.Signal != nil, + EntryCompaction: fact.Compaction != nil, + EntryOperationSettlement: fact.Settlement != nil, + } { + if isKind { + present++ + kind = member + } + } + testassert.Require(t, present == 1, "fact %s carries %d payload members, want exactly one", fact.EntryID, present) + testassert.Require(t, kind == fact.Kind, "fact[%d]'s single payload member implies kind %q, want its Kind %q", i, kind, fact.Kind) } + testassert.Require(t, fact.EntryID != "" && fact.Sequence != 0 && !fact.CommittedAt.IsZero(), "fact[%d] loses its envelope identity: %+v", i, fact) } - if facts[0].Input == nil || facts[0].Input.Content[0].Extra["part"] == nil { - t.Fatalf("input fact lost its content part extra") - } + testassert.Require(t, facts[0].Input != nil && facts[0].Input.Content[0].Extra["part"] != nil, "input fact lost its content part extra") firstAssistant := facts[1].Assistant - if firstAssistant == nil || firstAssistant.Extra["reasoning"] == nil || - len(firstAssistant.ToolCalls) != 1 { - t.Fatalf("assistant fact lost its extra or call: %+v", firstAssistant) - } + testassert.Require(t, firstAssistant != nil && firstAssistant.Extra["reasoning"] != nil && len(firstAssistant.ToolCalls) == 1, "assistant fact lost its extra or call: %+v", firstAssistant) call := firstAssistant.ToolCalls[0] - if call.ID != "call-1" || call.Ordinal != 0 || call.Name != "echo" || - call.ResultEntryID != testResultID || call.Extra["call"] == nil || - string(call.NormalizedArguments) != `{"x":1}` { - t.Fatalf("tool call record = %+v", call) - } - if facts[2].ToolResult == nil || string(facts[2].ToolResult.Metadata) != `{"n":1}` || - facts[2].ToolResult.AssistantEntry.EntryID != hexID(2) { - t.Fatalf("tool result fact = %+v", facts[2]) - } - if facts[3].Assistant == nil || facts[3].Assistant.Usage == nil || - *facts[3].Assistant.Usage != (UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2}) { - t.Fatalf("completing assistant fact = %+v", facts[3]) - } - if facts[4].Signal == nil || facts[4].Signal.Signal != SignalInterruption || - facts[4].Signal.RelatedOperation == nil || - facts[4].Signal.RelatedOperation.OperationID != testOpID { - t.Fatalf("owned signal fact = %+v", facts[4]) - } - if facts[5].Settlement == nil || facts[5].Settlement.Status != OperationSuccess { - t.Fatalf("settlement fact = %+v", facts[5]) - } - if facts[6].Compaction == nil || facts[6].Compaction.BoundaryEntryID != hexID(2) || - facts[6].Compaction.Usage == nil { - t.Fatalf("compaction fact = %+v", facts[6]) - } - if facts[8].Signal == nil || facts[8].Signal.RelatedMember == nil || - facts[8].Signal.OperationID != "" { - t.Fatalf("background completion fact = %+v", facts[8]) - } + testassert.Require(t, call.ID == "call-1" && call.Ordinal == 0 && call.Name == "echo" && call.ResultEntryID == testResultID && call.Extra["call"] != nil && string(call.NormalizedArguments) == `{"x":1}`, "tool call record = %+v", call) + testassert.Require(t, facts[2].ToolResult != nil && string(facts[2].ToolResult.Metadata) == `{"n":1}` && facts[2].ToolResult.AssistantEntry.EntryID == hexID(2), "tool result fact = %+v", facts[2]) + testassert.Require(t, facts[3].Assistant != nil && facts[3].Assistant.Usage != nil && *facts[3].Assistant.Usage == (UsageCount{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 2}), "completing assistant fact = %+v", facts[3]) + testassert.Require(t, facts[4].Signal != nil && facts[4].Signal.Signal == SignalInterruption && facts[4].Signal.RelatedOperation != nil && facts[4].Signal.RelatedOperation.OperationID == testOpID, "owned signal fact = %+v", facts[4]) + testassert.Require(t, facts[5].Settlement != nil && facts[5].Settlement.Status == OperationSuccess, "settlement fact = %+v", facts[5]) + testassert.Require(t, facts[6].Compaction != nil && facts[6].Compaction.BoundaryEntryID == hexID(2) && facts[6].Compaction.Usage != nil, "compaction fact = %+v", facts[6]) + testassert.Require(t, facts[8].Signal != nil && facts[8].Signal.RelatedMember != nil && facts[8].Signal.OperationID == "", "background completion fact = %+v", facts[8]) // The composed view is coherent: sorted Operation summaries, no busy // state, a fresh coordinator's zero local revision, and the owned // register totals. sessionView, err := h.ReadSessionView(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } - if len(sessionView.Operations) != 2 || - sessionView.Operations[0].OperationID != testOpID || - sessionView.Operations[1].OperationID != "op-compact" { - t.Fatalf("operations = %+v", sessionView.Operations) - } - if sessionView.History.ExecutionBusy || sessionView.History.Revision.LocalRevision != 0 || - len(sessionView.Steering) != 0 || len(sessionView.Queued) != 0 || len(sessionView.Background) != 0 { - t.Fatalf("session view state = busy %v local %d steering %d queued %d background %d", - sessionView.History.ExecutionBusy, sessionView.History.Revision.LocalRevision, - len(sessionView.Steering), len(sessionView.Queued), len(sessionView.Background)) - } + testassert.NoError(t, err) + testassert.Require(t, len(sessionView.Operations) == 2 && sessionView.Operations[0].OperationID == testOpID && sessionView.Operations[1].OperationID == "op-compact" && (!sessionView.History.ExecutionBusy && sessionView.History.Revision.LocalRevision == 0 && len(sessionView.Steering) == 0 && len(sessionView.Queued) == 0 && len(sessionView.Background) == 0), "operations = %+v; session view state = busy %v local %d steering %d queued %d background %d", sessionView.Operations, sessionView.History.ExecutionBusy, sessionView.History.Revision.LocalRevision, len(sessionView.Steering), len(sessionView.Queued), len(sessionView.Background)) wantUsage := UsageTotals{ByModel: []ModelUsage{{ Model: testModelRef(), Usage: UsageCount{InputTokens: 4, CachedInputTokens: 2, OutputTokens: 3}, }}} - if !reflect.DeepEqual(sessionView.TotalUsage, wantUsage) { - t.Fatalf("total usage = %+v, want the register totals %+v", sessionView.TotalUsage, wantUsage) - } + testassert.Require(t, reflect.DeepEqual(sessionView.TotalUsage, wantUsage), "total usage = %+v, want the register totals %+v", sessionView.TotalUsage, wantUsage) }) t.Run("copied fork prefix facts carry no operation identity", func(t *testing.T) { @@ -529,26 +452,16 @@ func TestHistoryViewFactUnionAndOwnership(t *testing.T) { } h := newTestHarness(t, fork.storage(t), nil) view, err := h.ReadHistoryView(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadHistoryView: %v", err) - } + testassert.NoError(t, err) facts := view.HistoryFacts(historyHeaderIDs(view.HistoryHeaders())) - if len(facts) != 5 { - t.Fatalf("fork facts = %d, want 5", len(facts)) - } + testassert.Equal(t, len(facts), 5) for i := 0; i < 3; i++ { - if facts[i].OperationID != "" { - t.Fatalf("copied fact[%d] carries operation %q, want none", i, facts[i].OperationID) - } + testassert.Require(t, facts[i].OperationID == "", "copied fact[%d] carries operation %q, want none", i, facts[i].OperationID) } for i := 3; i < 5; i++ { - if facts[i].OperationID != testOpID { - t.Fatalf("owned fact[%d] operation = %q, want %q", i, facts[i].OperationID, testOpID) - } - } - if facts[2].ToolResult == nil || facts[2].ToolResult.ToolCallID != "call-1" { - t.Fatalf("copied tool result fact = %+v", facts[2]) + testassert.Require(t, facts[i].OperationID == testOpID, "owned fact[%d] operation = %q, want %q", i, facts[i].OperationID, testOpID) } + testassert.Require(t, facts[2].ToolResult != nil && facts[2].ToolResult.ToolCallID == "call-1", "copied tool result fact = %+v", facts[2]) }) t.Run("hook evidence is validated, never skipped", func(t *testing.T) { @@ -557,9 +470,7 @@ func TestHistoryViewFactUnionAndOwnership(t *testing.T) { h := newTestHarness(t, graph.storage(t), nil) _, err := h.ReadHistoryView(context.Background(), testSessionID) var corrupt *CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != testSessionID { - t.Fatalf("history read over a corrupt hook entry = %v, want the owning Session's corruption error", err) - } + testassert.Require(t, errors.As(err, &corrupt) && corrupt.SessionID == testSessionID, "history read over a corrupt hook entry = %v, want the owning Session's corruption error", err) }) } @@ -593,23 +504,22 @@ func TestHistoryViewAccessorsPure(t *testing.T) { {EntryID: "input-1", Kind: EntryInput, InputOrigin: InputOriginUser}, {EntryID: "assistant-1", Kind: EntryAssistant}, } - if headers := view.HistoryHeaders(); !reflect.DeepEqual(headers, wantHeaders) { - t.Fatalf("headers = %+v, want %+v (hook evidence excluded)", headers, wantHeaders) + { + headers := view.HistoryHeaders() + testassert.Require(t, reflect.DeepEqual(headers, wantHeaders), "headers = %+v, want %+v (hook evidence excluded)", headers, wantHeaders) } facts := view.HistoryFacts([]string{"assistant-1", "missing", "input-1", "input-1", "hook-1"}) - if len(facts) != 2 || facts[0].EntryID != "input-1" || facts[1].EntryID != "assistant-1" { - t.Fatalf("facts = %+v, want the deduplicated sequence-ordered input and assistant", facts) - } - if facts[0].Input == nil || facts[0].Input.Content[0].Text != "hi" || facts[0].Kind != EntryInput { - t.Fatalf("input fact = %+v, want the requested owned input payload", facts[0]) - } + testassert.Require(t, len(facts) == 2 && facts[0].EntryID == "input-1" && facts[1].EntryID == "assistant-1", "facts = %+v, want the deduplicated sequence-ordered input and assistant", facts) + testassert.Require(t, facts[0].Input != nil && facts[0].Input.Content[0].Text == "hi" && facts[0].Kind == EntryInput, "input fact = %+v, want the requested owned input payload", facts[0]) facts[0].Input.Content[0].Text = "mutated" - if again := view.HistoryFacts([]string{"input-1"}); again[0].Input.Content[0].Text != "hi" { - t.Fatal("a mutation of the returned fact reached the captured entry") + { + again := view.HistoryFacts([]string{"input-1"}) + testassert.Require(t, again[0].Input.Content[0].Text == "hi", "%v", "a mutation of the returned fact reached the captured entry") } - if empty := view.HistoryFacts(nil); empty == nil || len(empty) != 0 { - t.Fatalf("empty facts = %+v, want the non-nil empty result", empty) + { + empty := view.HistoryFacts(nil) + testassert.Require(t, empty != nil && len(empty) == 0, "empty facts = %+v, want the non-nil empty result", empty) } assistantAt := func(id string, seq int64, usage *UsageCount) graphEntry { @@ -619,9 +529,24 @@ func TestHistoryViewAccessorsPure(t *testing.T) { } } settlementAt := func(id string, seq int64, usage *UsageCount) graphEntry { + identity := testCompactCapture().Model return graphEntry{ Envelope: Entry{ID: id, Sequence: seq, Kind: EntryOperationSettlement, CommittedAt: testTime}, - Settlement: &operationSettlementEntry{EntryID: id, Status: OperationSuccess, Usage: usage}, + Settlement: &operationSettlementEntry{EntryID: id, Status: OperationSuccess, Usage: usage, Model: &identity}, + } + } + settlementMarked := func(id string, seq int64, usage *UsageCount) graphEntry { + identity := testModelRef() + return graphEntry{ + Envelope: Entry{ID: id, Sequence: seq, Kind: EntryOperationSettlement, CommittedAt: testTime}, + Settlement: &operationSettlementEntry{EntryID: id, Status: OperationFailure, Detail: "summary failure", Usage: usage, Model: &identity, CompactionUsage: true}, + } + } + settlementFailureAt := func(id string, seq int64, usage *UsageCount) graphEntry { + identity := testModelRef() + return graphEntry{ + Envelope: Entry{ID: id, Sequence: seq, Kind: EntryOperationSettlement, CommittedAt: testTime}, + Settlement: &operationSettlementEntry{EntryID: id, Status: OperationFailure, Detail: "summary failure", Usage: usage, Model: &identity}, } } compactionAt := func(id string, seq int64) graphEntry { @@ -680,6 +605,36 @@ func TestHistoryViewAccessorsPure(t *testing.T) { []graphEntry{compactionAt("c1", 1)}, nil, }, + { + "equal-identity pair: marked newest skipped, older unmarked measured", + "", + []graphEntry{settlementFailureAt("s1", 1, count(500)), settlementMarked("s2", 2, count(300))}, + count(500), + }, + { + "marked settlement skipped, scan continues", + "", + []graphEntry{assistantAt("a1", 1, count(100)), settlementMarked("s1", 2, count(300))}, + count(100), + }, + { + "marked settlement without usage is inert", + "", + []graphEntry{assistantAt("a1", 1, count(100)), settlementMarked("s1", 2, nil)}, + count(100), + }, + { + "unmarked settlement eligible beside compact identity", + "", + []graphEntry{settlementAt("s1", 1, count(300))}, + count(300), + }, + { + "marked settlement latest leaves none", + "", + []graphEntry{settlementMarked("s1", 1, count(300)), settlementMarked("s2", 2, count(400))}, + nil, + }, } for _, tc := range measurement { scanned := HistoryReadView{prefix: tc.entries, compactionEntryID: tc.compactionID} @@ -694,8 +649,9 @@ func TestHistoryViewAccessorsPure(t *testing.T) { scanned := HistoryReadView{prefix: []graphEntry{assistantAt("a1", 1, count(100))}} owned := scanned.LatestContextUsage() owned.InputTokens = 999 - if fresh := scanned.LatestContextUsage(); fresh.InputTokens != 100 { - t.Fatalf("a mutation of the returned measurement reached the captured entry: %+v", fresh) + { + fresh := scanned.LatestContextUsage() + testassert.Require(t, fresh.InputTokens == 100, "a mutation of the returned measurement reached the captured entry: %+v", fresh) } } @@ -709,17 +665,13 @@ func TestHistoryViewRetainedCaptureStable(t *testing.T) { h := newTestHarness(t, factFixtureGraph().storage(t), nil) ctx := context.Background() view, err := h.ReadHistoryView(ctx, testSessionID) - if err != nil { - t.Fatalf("ReadHistoryView: %v", err) - } + testassert.NoError(t, err) beforeHeaders := view.HistoryHeaders() beforeIDs := historyHeaderIDs(beforeHeaders) beforeFacts := view.HistoryFacts(beforeIDs) beforeUsage := view.LatestContextUsage() beforeRevision := view.Revision - if len(beforeHeaders) == 0 || len(beforeFacts) == 0 { - t.Fatal("fixture has no committed history") - } + testassert.Require(t, len(beforeHeaders) != 0 && len(beforeFacts) != 0, "%v", "fixture has no committed history") const futureAssistantID = "fffffffffffffffffffffffffffffff1" const futureResultID = "fffffffffffffffffffffffffffffff2" @@ -749,36 +701,33 @@ func TestHistoryViewRetainedCaptureStable(t *testing.T) { ) c.mu.Unlock() - if after := view.HistoryHeaders(); !reflect.DeepEqual(after, beforeHeaders) { - t.Fatalf("retained headers changed after the live appends: %+v vs %+v", after, beforeHeaders) + { + after := view.HistoryHeaders() + testassert.Require(t, reflect.DeepEqual(after, beforeHeaders), "retained headers changed after the live appends: %+v vs %+v", after, beforeHeaders) } - if after := view.HistoryFacts(beforeIDs); !reflect.DeepEqual(after, beforeFacts) { - t.Fatalf("retained facts changed after the live appends: %+v vs %+v", after, beforeFacts) + { + after := view.HistoryFacts(beforeIDs) + testassert.Require(t, reflect.DeepEqual(after, beforeFacts), "retained facts changed after the live appends: %+v vs %+v", after, beforeFacts) } - if future := view.HistoryFacts([]string{futureAssistantID, futureResultID}); len(future) != 0 { - t.Fatalf("retained facts returned appended entries: %+v", future) + { + future := view.HistoryFacts([]string{futureAssistantID, futureResultID}) + testassert.Require(t, len(future) == 0, "retained facts returned appended entries: %+v", future) } - if got := view.LatestContextUsage(); !reflect.DeepEqual(got, beforeUsage) { - t.Fatalf("retained context measurement = %+v, want the captured %+v", got, beforeUsage) - } - if view.Revision != beforeRevision { - t.Fatalf("retained revision = %+v, want the captured %+v", view.Revision, beforeRevision) + { + got := view.LatestContextUsage() + testassert.Require(t, reflect.DeepEqual(got, beforeUsage), "retained context measurement = %+v, want the captured %+v", got, beforeUsage) } + testassert.Equal(t, view.Revision, beforeRevision) // The same live state seen fresh: the appended entries are real. fresh, err := h.ReadHistoryView(ctx, testSessionID) - if err != nil { - t.Fatalf("fresh ReadHistoryView: %v", err) - } + testassert.NoError(t, err) freshHeaders := fresh.HistoryHeaders() - if len(freshHeaders) != len(beforeHeaders)+2 { - t.Fatalf("fresh headers = %d, want the %d captured plus the two appended", len(freshHeaders), len(beforeHeaders)) - } - if fresh.Revision.DurableRevision != beforeRevision.DurableRevision+1 { - t.Fatalf("fresh revision = %+v, want the appended advance over %+v", fresh.Revision, beforeRevision) - } - if facts := fresh.HistoryFacts([]string{futureAssistantID, futureResultID}); len(facts) != 2 { - t.Fatalf("fresh facts = %+v, want the two appended entries", facts) + testassert.Require(t, len(freshHeaders) == len(beforeHeaders)+2, "fresh headers = %d, want the %d captured plus the two appended", len(freshHeaders), len(beforeHeaders)) + testassert.Require(t, fresh.Revision.DurableRevision == beforeRevision.DurableRevision+1, "fresh revision = %+v, want the appended advance over %+v", fresh.Revision, beforeRevision) + { + facts := fresh.HistoryFacts([]string{futureAssistantID, futureResultID}) + testassert.Require(t, len(facts) == 2, "fresh facts = %+v, want the two appended entries", facts) } } @@ -792,46 +741,26 @@ func TestSessionReadViewMemberPublications(t *testing.T) { read := func() SessionReadView { t.Helper() view, err := h.ReadSessionView(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) return view } before := read() - if len(before.Background) != 0 || before.History.Revision.LocalRevision != 0 { - t.Fatalf("baseline = %+v", before) - } + testassert.Require(t, len(before.Background) == 0 && before.History.Revision.LocalRevision == 0, "baseline = %+v", before) member, err := admitLocked(h, c, memberJob, "job-1", 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) afterAdmit := read() - if len(afterAdmit.Background) != 1 || afterAdmit.Background[0] != (BackgroundMemberView{Kind: "job", ID: "job-1"}) { - t.Fatalf("background after admission = %+v", afterAdmit.Background) - } - if afterAdmit.History.Revision.LocalRevision != before.History.Revision.LocalRevision+1 { - t.Fatalf("local revision after admission = %d, want %d", afterAdmit.History.Revision.LocalRevision, before.History.Revision.LocalRevision+1) - } + testassert.Require(t, len(afterAdmit.Background) == 1 && afterAdmit.Background[0] == (BackgroundMemberView{Kind: "job", ID: "job-1"}) && afterAdmit.History.Revision.LocalRevision == before.History.Revision.LocalRevision+1, "background after admission = %+v; got %v, want %v", afterAdmit.Background, afterAdmit.History.Revision.LocalRevision, before.History.Revision.LocalRevision+1) claimed, ok := claimLocked(h, c, member.completionID) - if !ok || claimed != member { - t.Fatalf("claim failed") - } + testassert.Require(t, ok && claimed == member, "claim failed") h.finishBackgroundMember(c, claimed) afterFinish := read() - if len(afterFinish.Background) != 0 { - t.Fatalf("background after finish = %+v", afterFinish.Background) - } - if afterFinish.History.Revision.LocalRevision != afterAdmit.History.Revision.LocalRevision+1 { - t.Fatalf("local revision after finish = %d, want %d", afterFinish.History.Revision.LocalRevision, afterAdmit.History.Revision.LocalRevision+1) - } + testassert.Require(t, len(afterFinish.Background) == 0 && afterFinish.History.Revision.LocalRevision == afterAdmit.History.Revision.LocalRevision+1, "background after finish = %+v; got %v, want %v", afterFinish.Background, afterFinish.History.Revision.LocalRevision, afterAdmit.History.Revision.LocalRevision+1) h.finishBackgroundMember(c, claimed) // already removed: a no-op publication afterRepeat := read() - if afterRepeat.History.Revision.LocalRevision != afterFinish.History.Revision.LocalRevision { - t.Fatalf("repeat finish advanced the revision to %d, want %d", afterRepeat.History.Revision.LocalRevision, afterFinish.History.Revision.LocalRevision) - } + testassert.Equal(t, afterRepeat.History.Revision.LocalRevision, afterFinish.History.Revision.LocalRevision) } // TestSnapshotSessionSteeringDeliveryPublications proves the steering @@ -853,30 +782,22 @@ func TestSnapshotSessionSteeringDeliveryPublications(t *testing.T) { defer script.releaseGate() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, script.arrived, "first model boundary") baseline := readSessionView(t, h, session) - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer-me"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer-me") + testassert.NoError(t, err) } buffered := readSessionView(t, h, session) - if len(buffered.Steering) != 1 || buffered.Steering[0].OperationID != "op-2" || - buffered.Steering[0].Content[0].Text != "steer-me" { - t.Fatalf("steering buffer after enqueue = %+v", buffered.Steering) - } // Producer-level pending fact: the steering FIFO's own nonemptiness // alone raises HasPending (no queued member was submitted). - if !buffered.History.HasPending || len(buffered.Queued) != 0 { - t.Fatalf("steering-only pending fact = %v (steering %d, queued %d), want true from the steering FIFO alone", - buffered.History.HasPending, len(buffered.Steering), len(buffered.Queued)) - } // one Submit's publications: the reservation, the enqueue, and its release - if buffered.History.Revision.LocalRevision != baseline.History.Revision.LocalRevision+3 { - t.Fatalf("enqueue revision = %d, want %d", buffered.History.Revision.LocalRevision, baseline.History.Revision.LocalRevision+3) - } + testassert.Require(t, len(buffered.Steering) == 1 && buffered.Steering[0].OperationID == "op-2" && buffered.Steering[0].Content[0].Text == "steer-me" && (buffered.History.HasPending && len(buffered.Queued) == 0) && buffered.History.Revision.LocalRevision == baseline.History.Revision.LocalRevision+3, "steering buffer after enqueue = %+v; steering-only pending fact = %v (steering %d, queued %d), want true from the steering FIFO alone; got %v, want %v", buffered.Steering, buffered.History.HasPending, len(buffered.Steering), len(buffered.Queued), buffered.History.Revision.LocalRevision, baseline.History.Revision.LocalRevision+3) // The successor admission's entry insert fails once: the selected head // stays buffered through the attempt, is dropped exactly once as the @@ -897,45 +818,29 @@ func TestSnapshotSessionSteeringDeliveryPublications(t *testing.T) { awaitHarnessQuiet(t, h, session) // the drop settled and the drain converged after := readSessionView(t, h, session) - if len(after.Steering) != 0 { - t.Fatalf("steering buffer after the failed attempt = %+v, want the dropped head gone", after.Steering) - } // Both FIFOs empty: the pending fact is false. - if after.History.HasPending || len(after.Queued) != 0 { - t.Fatalf("empty pending fact = %v (steering %d, queued %d), want false with both FIFOs empty", - after.History.HasPending, len(after.Steering), len(after.Queued)) - } + testassert.Require(t, len(after.Steering) == 0 && (!after.History.HasPending && len(after.Queued) == 0), "steering buffer after the failed attempt = %+v, want the dropped head gone; empty pending fact = %v (steering %d, queued %d), want false with both FIFOs empty", after.Steering, after.History.HasPending, len(after.Steering), len(after.Queued)) for _, fact := range readSessionFacts(t, h, session) { - if fact.Input != nil && fact.Input.Content[0].Text == "steer-me" { - t.Fatalf("the failed delivery committed entry %s", fact.EntryID) - } + testassert.Require(t, fact.Input == nil || fact.Input.Content[0].Text != "steer-me", "the failed delivery committed entry %s", fact.EntryID) } // the completion owner's reservation, the drop, the retirement and // the release: the predecessor's settlement is the one durable advance - if after.History.Revision.LocalRevision != buffered.History.Revision.LocalRevision+4 { - t.Fatalf("local revision after the failed attempt = %d, want %d (the reservation, the drop, the retirement and the release)", - after.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+4) - } + testassert.Equal(t, after.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+4) sessionKey := RegisterKey{SessionID: session, Kind: RegisterSession} if after.Header.Revision.DurableRevision != registerRevision(t, store, session, sessionKey) { t.Fatalf("read revision %d disagrees with the durable register %d", after.Header.Revision.DurableRevision, registerRevision(t, store, session, sessionKey)) } entries, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("read entries: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { - if strings.Contains(string(entry.Payload), "steer-me") { - t.Fatalf("the failed delivery's entry %s reached storage", entry.ID) - } - } - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationSuccess { - t.Fatalf("op-1 = %q, want the quiet success preserved through the failed delivery", rec.State.Status) + testassert.Require(t, !strings.Contains(string(entry.Payload), "steer-me"), "the failed delivery's entry %s reached storage", entry.ID) } - if storedOperationExists(store, session, "op-2") { - t.Fatalf("the failed delivery admitted the successor Operation") + { + rec := settledOperation(t, store, session, "op-1") + testassert.Equal(t, rec.State.Status, OperationSuccess) } + testassert.Require(t, !storedOperationExists(store, session, "op-2"), "the failed delivery admitted the successor Operation") }) t.Run("the pending fact follows each FIFO independently of the busy fact", func(t *testing.T) { @@ -947,29 +852,25 @@ func TestSnapshotSessionSteeringDeliveryPublications(t *testing.T) { defer script.releaseGate() session := createSession(t, h) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, script.arrived, "first model boundary") // Busy with both FIFOs empty: the run's own busy fact is not pending // work, so HasPending stays false while ExecutionBusy is true. busy := readSessionView(t, h, session) - if !busy.History.ExecutionBusy || busy.History.HasPending || len(busy.Steering) != 0 || len(busy.Queued) != 0 { - t.Fatalf("busy empty view = busy %v pending %v steering %d queued %d, want busy with no pending work", - busy.History.ExecutionBusy, busy.History.HasPending, len(busy.Steering), len(busy.Queued)) - } + testassert.Require(t, busy.History.ExecutionBusy && !busy.History.HasPending && len(busy.Steering) == 0 && len(busy.Queued) == 0, "busy empty view = busy %v pending %v steering %d queued %d, want busy with no pending work", busy.History.ExecutionBusy, busy.History.HasPending, len(busy.Steering), len(busy.Queued)) // Queued-only: the queued FIFO's own nonemptiness alone raises the // pending fact with the steering FIFO still empty. - if _, err := submitText(t, h, session, "op-2", MessageModeQueued, "queue-me"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeQueued, "queue-me") + testassert.NoError(t, err) } queued := readSessionView(t, h, session) - if len(queued.Steering) != 0 || len(queued.Queued) != 1 || !queued.History.HasPending { - t.Fatalf("queued-only view = pending %v steering %d queued %d, want true from the queued FIFO alone", - queued.History.HasPending, len(queued.Steering), len(queued.Queued)) - } + testassert.Require(t, len(queued.Steering) == 0 && len(queued.Queued) == 1 && queued.History.HasPending, "queued-only view = pending %v steering %d queued %d, want true from the queued FIFO alone", queued.History.HasPending, len(queued.Steering), len(queued.Queued)) }) t.Run("a parked delivery keeps snapshots coherent until the adoption", func(t *testing.T) { @@ -978,32 +879,42 @@ func TestSnapshotSessionSteeringDeliveryPublications(t *testing.T) { // state stays stable until the subtest's assertions complete: no // terminal commit and no successor drain can publish while reading release2 := make(chan struct{}) - defer close(release2) + release := make(chan struct{}) + releaseDelivery := sync.OnceFunc(func() { close(release) }) script := newModelScript(turn(testToolCall("call-1")), modelAttempt{stream: &parkingStream{release: release2}}) script.gate = make(chan struct{}) h, cancel := newCancelableHarness(t, store, steeringPrepared(script), nil) - defer cancel() - defer script.releaseGate() + // Cleanup releases the storage publication gate and the script model + // gate, loses the Harness, then opens the parked stream read and joins + // the execution with a bounded Wait: the stream's gated delta and EOF + // let the assembly terminate instead of leaking an executor into + // later allocation windows. + defer func() { + releaseDelivery() + script.releaseGate() + cancel() + close(release2) + waitCtx, stopWait := context.WithTimeout(context.Background(), 10*time.Second) + defer stopWait() + testassert.NoError(t, h.Wait(waitCtx)) + }() session := createSession(t, h) c := cachedCoordinator(t, h, session) - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, script.arrived, "first model boundary") baseline := readSessionView(t, h, session) - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer-me"); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := submitText(t, h, session, "op-2", MessageModeRegular, "steer-me") + testassert.NoError(t, err) } buffered := readSessionView(t, h, session) - if len(buffered.Steering) != 1 || buffered.History.Revision.LocalRevision != baseline.History.Revision.LocalRevision+3 { - t.Fatalf("buffered view = %+v, want one steering item advanced by the Submit's three publications", buffered) - } + testassert.Require(t, len(buffered.Steering) == 1 && buffered.History.Revision.LocalRevision == baseline.History.Revision.LocalRevision+3, "buffered view = %+v, want one steering item advanced by the Submit's three publications", buffered) deliveryStarted := make(chan struct{}) - release := make(chan struct{}) - releaseDelivery := sync.OnceFunc(func() { close(release) }) - defer releaseDelivery() deliverySettled := make(chan struct{}) var once sync.Once store.entryHook = func(draft EntryDraft) error { @@ -1049,19 +960,9 @@ func TestSnapshotSessionSteeringDeliveryPublications(t *testing.T) { parkedSnap <- snapResult{view, err} }() p := receiveBounded(t, parkedSnap, "mid-admission read") - if p.err != nil { - t.Fatalf("read during the parked admission: %v", p.err) - } - if len(p.view.Steering) != 1 || p.view.Steering[0].OperationID != "op-2" { - t.Fatalf("mid-admission steering = %+v, want the selected head still pending", p.view.Steering) - } - if p.view.Header.CurrentOperationID != "" { - t.Fatalf("mid-admission current operation = %q, want the settled predecessor cleared", p.view.Header.CurrentOperationID) - } + testassert.Require(t, p.err == nil && (len(p.view.Steering) == 1 && p.view.Steering[0].OperationID == "op-2") && p.view.Header.CurrentOperationID == "", "read during the parked admission: %v; mid-admission steering = %+v, want the selected head still pending; got %v, want %v", p.err, p.view.Steering, p.view.Header.CurrentOperationID, "") for _, op := range p.view.Operations { - if op.OperationID == "op-2" { - t.Fatalf("the parked admission exposed a half-applied successor Operation") - } + testassert.Require(t, op.OperationID != "op-2", "the parked admission exposed a half-applied successor Operation") } releaseDelivery() receiveBounded(t, script.arrived, "post-adoption model boundary") @@ -1071,42 +972,27 @@ func TestSnapshotSessionSteeringDeliveryPublications(t *testing.T) { // reads only after that release. releaseWait, stopWait := context.WithTimeout(context.Background(), 10*time.Second) defer stopWait() - if err := c.waitIdle(releaseWait); err != nil { - t.Fatalf("waiting for the completion owner's release: %v", err) + { + err := c.waitIdle(releaseWait) + testassert.NoError(t, err) } post := readSessionView(t, h, session) - if len(post.Steering) != 0 { - t.Fatalf("steering buffer after the adoption = %+v", post.Steering) - } + testassert.Require(t, len(post.Steering) == 0, "steering buffer after the adoption = %+v", post.Steering) found := false for _, fact := range readSessionFacts(t, h, session) { if fact.Input != nil && fact.Input.Content[0].Text == "steer-me" { found = true - if fact.OperationID != "op-2" { - t.Fatalf("the adopted delivery's input is owned by %q, want its own Operation op-2", fact.OperationID) - } + testassert.Equal(t, fact.OperationID, "op-2") } } - if !found { - t.Fatalf("the adopted delivery's input fact is missing") - } - if post.Header.Revision.DurableRevision <= beforeAdoption { - t.Fatalf("read revision %d did not advance past the pre-adoption register %d", - post.Header.Revision.DurableRevision, beforeAdoption) - } + testassert.Require(t, found, "the adopted delivery's input fact is missing") // The selected head stayed buffered through the parked attempt; the // durable adoption advanced only the register — the removal rode it, // publishing nothing of its own. The completion owner's reservation, // the successor installation and its release are the delivery cycle's // local publications, the last visible only after the release waited // for above. - if post.History.Revision.LocalRevision != buffered.History.Revision.LocalRevision+3 { - t.Fatalf("post-adoption local revision = %d, want %d (the reservation, the installation and the release)", - post.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+3) - } - if !post.History.ExecutionBusy { - t.Fatalf("post-adoption read = busy %v, want the admitted successor Operation", post.History.ExecutionBusy) - } + testassert.Require(t, post.Header.Revision.DurableRevision > beforeAdoption && post.History.Revision.LocalRevision == buffered.History.Revision.LocalRevision+3 && post.History.ExecutionBusy, "read revision %d did not advance past the pre-adoption register %d; got %v, want %v; post-adoption read = busy %v, want the admitted successor Operation", post.Header.Revision.DurableRevision, beforeAdoption, post.History.Revision.LocalRevision, buffered.History.Revision.LocalRevision+3, post.History.ExecutionBusy) }) } @@ -1124,30 +1010,23 @@ func TestSnapshotOwnerWaitsForReservationRelease(t *testing.T) { c := cachedCoordinator(t, h, session) release, err := h.reserve(context.Background(), c) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) held, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("read under the held reservation: %v", err) - } + testassert.NoError(t, err) bounded, stopWait := context.WithTimeout(context.Background(), 50*time.Millisecond) defer stopWait() - if err := c.waitIdle(bounded); !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("waitIdle under a held reservation = %v, want the bounded caller deadline", err) + { + err := c.waitIdle(bounded) + testassert.Require(t, errors.Is(err, context.DeadlineExceeded), "waitIdle under a held reservation = %v, want the bounded caller deadline", err) } release() - if err := c.waitIdle(context.Background()); err != nil { - t.Fatalf("waitIdle after the release: %v", err) + { + err := c.waitIdle(context.Background()) + testassert.NoError(t, err) } free, err := h.ReadSessionView(context.Background(), session) - if err != nil { - t.Fatalf("read after the release: %v", err) - } - if free.History.Revision.LocalRevision != held.History.Revision.LocalRevision+1 { - t.Fatalf("local revision across the release = %d, want %d: the release is one publication, so counts read after it", - free.History.Revision.LocalRevision, held.History.Revision.LocalRevision+1) - } + testassert.NoError(t, err) + testassert.Equal(t, free.History.Revision.LocalRevision, held.History.Revision.LocalRevision+1) } func TestBufferedDeliverySelectionIdentity(t *testing.T) { @@ -1163,21 +1042,13 @@ func TestBufferedDeliverySelectionIdentity(t *testing.T) { selected := &pendingMessage{operationID: "same-id", content: admissionContent("same-content")} next := &pendingMessage{operationID: "same-id", content: admissionContent("same-content")} *fifo = []*pendingMessage{selected, next} - if c.dropBufferedLocked(nil) || len(*fifo) != 2 { - t.Fatal("ordinary admission removed a pending head") - } - if !c.dropBufferedLocked(selected) || len(*fifo) != 1 || (*fifo)[0] != next { - t.Fatal("delivery failed to preserve its appended sibling") - } - if c.dropBufferedLocked(selected) { - t.Fatal("repeated completion removed the next head") - } + testassert.Require(t, !c.dropBufferedLocked(nil) && len(*fifo) == 2, "%v", "ordinary admission removed a pending head") + testassert.Require(t, c.dropBufferedLocked(selected) && len(*fifo) == 1 && (*fifo)[0] == next, "%v", "delivery failed to preserve its appended sibling") + testassert.Require(t, !c.dropBufferedLocked(selected), "%v", "repeated completion removed the next head") *fifo = []*pendingMessage{selected} c.discardBuffers() *fifo = []*pendingMessage{next} - if c.dropBufferedLocked(selected) || len(*fifo) != 1 || (*fifo)[0] != next { - t.Fatal("discarded selection removed a replacement with the same payload") - } + testassert.Require(t, !c.dropBufferedLocked(selected) && len(*fifo) == 1 && (*fifo)[0] == next, "%v", "discarded selection removed a replacement with the same payload") }) } } @@ -1198,23 +1069,21 @@ func TestBufferedDuplicateDeliveryFinal(t *testing.T) { defer cancel() defer script.releaseGate() session := createSession(t, h) - if _, err := submitText(t, h, session, "initial", MessageModeRegular, "hello"); err != nil { - t.Fatal(err) + { + _, err := submitText(t, h, session, "initial", MessageModeRegular, "hello") + testassert.NoError(t, err) } receiveBounded(t, script.arrived, "initial model boundary") for _, text := range []string{"first", "duplicate"} { result, err := submitText(t, h, session, "buffered", tc.mode, text) - if err != nil || result.Disposition != tc.want { - t.Fatalf("buffer %q = %+v, %v, want disposition %s", text, result, err, tc.want) - } + testassert.Require(t, err == nil && result.Disposition == tc.want, "buffer %q = %+v, %v, want disposition %s", text, result, err, tc.want) } script.releaseGate() quiet := awaitHarnessQuiet(t, h, session) - if len(quiet.Steering) != 0 || len(quiet.Queued) != 0 || len(quiet.Operations) != 2 { - t.Fatalf("duplicate did not retire exactly once: %+v", quiet) - } - if got := strings.Join(entryTexts(t, store, session), ","); got != "hello,first" { - t.Fatalf("committed inputs = %q, want only the first buffered payload", got) + testassert.Require(t, len(quiet.Steering) == 0 && len(quiet.Queued) == 0 && len(quiet.Operations) == 2, "duplicate did not retire exactly once: %+v", quiet) + { + got := strings.Join(entryTexts(t, store, session), ",") + testassert.Equal(t, got, "hello,first") } }) } @@ -1234,7 +1103,8 @@ func steeringPrepared(script *modelScript) PreparedExecution { } // parkingStream is one accepted stream whose first read parks until its -// release channel closes, then completes one turn. +// release channel closes, then yields its existing delta once; every later +// read is EOF, so an assembly consuming it terminates instead of leaking. type parkingStream struct { release chan struct{} done bool @@ -1244,13 +1114,27 @@ func (s *parkingStream) Recv() (model.StreamDelta, error) { if !s.done { <-s.release s.done = true - } - return model.StreamDelta{ - HasChoice: true, - Role: "assistant", - ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "done"}}, - FinishReason: "stop", - }, nil + return model.StreamDelta{ + HasChoice: true, + Role: "assistant", + ContentFragments: []model.ContentFragment{{Position: 0, Kind: model.PartText, Text: "done"}}, + FinishReason: "stop", + }, nil + } + return model.StreamDelta{}, io.EOF } func (s *parkingStream) Close() error { return nil } + +// TestParkingStreamGatedDeltaThenEOF pins the parked fixture's own stream +// contract: after release, the first Recv carries the existing delta and the +// next Recv is EOF, so an assembly consuming the stream terminates rather than +// leaking an executor into later allocation windows. +func TestParkingStreamGatedDeltaThenEOF(t *testing.T) { + s := &parkingStream{release: make(chan struct{})} + close(s.release) + delta, err := s.Recv() + testassert.Require(t, err == nil && delta.HasChoice && delta.FinishReason == "stop" && len(delta.ContentFragments) == 1 && delta.ContentFragments[0].Text == "done", "first Recv after release = %+v, %v; want the existing delta", delta, err) + _, err = s.Recv() + testassert.Require(t, errors.Is(err, io.EOF), "second Recv = %v, want EOF", err) +} diff --git a/harness/stop_test.go b/harness/stop_test.go index ac6cbbec..e405b438 100644 --- a/harness/stop_test.go +++ b/harness/stop_test.go @@ -6,12 +6,14 @@ import ( "errors" "fmt" "reflect" + goruntime "runtime" "sort" "strings" "sync" "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -136,9 +138,7 @@ func awaitStopInterval(t *testing.T, c *coordinator, done <-chan error) { if waiting { return } - if time.Now().After(deadline) { - t.Fatalf("Stop never entered its stop interval") - } + testassert.Require(t, !time.Now().After(deadline), "Stop never entered its stop interval") time.Sleep(2 * time.Millisecond) } } @@ -155,6 +155,66 @@ func stopResult(t *testing.T, done <-chan error) error { } } +// awaitStopJoiners waits until count distinct current-test goroutines are +// parked in the concurrent-join receive: each must be blocked in a channel +// receive with (*Harness).stopCoordinator as its top application frame and +// must carry this test's wrapper. The owner parked in its gated StopJob — +// its stopCoordinator frame sits deeper — and a goroutine merely started by +// the test do not match, so the wait proves real joining before release. +func awaitStopJoiners(t *testing.T, count int) { + t.Helper() + deadline := time.Now().Add(5 * time.Second) + buf := make([]byte, 1<<20) + for { + stacks := string(buf[:goruntime.Stack(buf, true)]) + seen := 0 + for _, stack := range strings.Split(stacks, "\n\n") { + if stopJoinerStack(stack, t.Name()) { + seen++ + } + } + if seen >= count { + return + } + testassert.Require(t, !time.Now().After(deadline), "%d concurrent Stop callers: only %d parked in the join receive", count, seen) + goruntime.Gosched() + } +} + +// stopJoinerStack reports whether one goroutine dump is a concurrent joiner +// of the current test's Stop: parked in a channel receive with +// stopCoordinator as the top application frame and created by the named test. +// No source line is consulted; a deeper stopCoordinator frame (the owner +// under gatedJobStopper) and a caller that has only started never match. +func stopJoinerStack(stack, testName string) bool { + lines := strings.Split(stack, "\n") + if len(lines) < 2 { + return false + } + if !strings.Contains(lines[0], "chan receive") { // the join receive itself, not a started caller + return false + } + if !strings.Contains(lines[1], "(*Harness).stopCoordinator") { // the top application frame + return false + } + return strings.Contains(stack, testName) // this test's wrapper, not another Stop stack +} + +// waitStopCallers bounds the cleanup join of every started Stop caller: the +// fixture gate is released before this runs, so each unblocked caller +// converges and Done fires; one held past the bound is reported, never hung +// on, and the owner is never left parked. +func waitStopCallers(t *testing.T, wg *sync.WaitGroup) { + t.Helper() + done := make(chan struct{}) + go func() { wg.Wait(); close(done) }() + select { + case <-done: + case <-time.After(5 * time.Second): + t.Errorf("a Stop caller did not converge during cleanup") + } +} + // runtimeInputText reads the first runtime-origin input entry's text straight // from durable storage. Graph validation is deliberately skipped: its // registers-then-entries reads can tear against a concurrent settlement @@ -168,9 +228,7 @@ func runtimeInputTextRaw(t *testing.T, store *graphStorage, sessionID string) st continue } ie, err := decodeInputEntry(env) - if err != nil { - t.Fatalf("decode input entry %s: %v", env.ID, err) - } + testassert.Require(t, err == nil, "decode input entry %s: %v", env.ID, err) if ie.Origin == InputOriginRuntime { return ie.Content[0].Text } @@ -191,9 +249,7 @@ func inputTexts(t *testing.T, store *graphStorage, sessionID string) []string { continue } ie, err := decodeInputEntry(env) - if err != nil { - t.Fatalf("decode input entry %s: %v", env.ID, err) - } + testassert.Require(t, err == nil, "decode input entry %s: %v", env.ID, err) out = append(out, ie.Content[0].Text) } return out @@ -214,9 +270,7 @@ func waitQuiet(t *testing.T, c *coordinator) { if quiet { return } - if time.Now().After(deadline) { - t.Fatalf("the session never went quiet") - } + testassert.Require(t, !time.Now().After(deadline), "the session never went quiet") time.Sleep(5 * time.Millisecond) } } @@ -305,25 +359,29 @@ func TestStopRootStopsMembersAndReopens(t *testing.T) { tree := newStopTree(t) defer tree.cancel() watch := watchSettlements(tree.store) // armed before the stop's child interruption commits - if _, err := submitText(t, tree.h, tree.root, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, tree.h, tree.root, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } - if _, err := submitText(t, tree.h, tree.root, "op-2", MessageModeQueued, "queued-msg"); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := submitText(t, tree.h, tree.root, "op-2", MessageModeQueued, "queued-msg") + testassert.NoError(t, err) } - if err := tree.h.Stop(context.Background(), tree.root); err != nil { - t.Fatalf("Stop: %v", err) + { + err := tree.h.Stop(context.Background(), tree.root) + testassert.NoError(t, err) } - if got := tree.stopper.stopped; len(got) != 1 || got[0] != tree.root+"/"+jobFixtureID { - t.Fatalf("StopJob calls = %v, want the tree's job killed", got) + { + got := tree.stopper.stopped + testassert.Require(t, len(got) == 1 && got[0] == tree.root+"/"+jobFixtureID, "StopJob calls = %v, want the tree's job killed", got) } waitMemberDone(t, tree.childMember) waitMemberDone(t, tree.jobMember) - if rec := settledOperation(t, tree.store, tree.child, "child-op-1"); rec.State.Status != OperationInterruption || - rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("child operation state = %+v, want terminal interruption with the contract detail", rec.State) + { + rec := settledOperation(t, tree.store, tree.child, "child-op-1") + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "child operation state = %+v, want terminal interruption with the contract detail", rec.State) } rootC := cachedCoordinator(t, tree.h, tree.root) rootC.mu.Lock() @@ -333,15 +391,9 @@ func TestStopRootStopsMembersAndReopens(t *testing.T) { current := rootC.graph.Session.State.CurrentOperationID running := rootC.run != nil rootC.mu.Unlock() - if !reopened || !groupEmpty { - t.Fatalf("after Stop: reopened=%v groupEmpty=%v, want the group reopened and empty", reopened, groupEmpty) - } - if queued != 1 { - t.Fatalf("root queued buffer after Stop = %d items, want the queued message intact", queued) - } - if current != "op-1" || !running { - t.Fatalf("root operation after Stop = %q (run %v), want op-1 still running", current, running) - } + testassert.Require(t, reopened && groupEmpty, "after Stop: reopened=%v groupEmpty=%v, want the group reopened and empty", reopened, groupEmpty) + testassert.Equal(t, queued, 1) + testassert.Require(t, current == "op-1" && running, "root operation after Stop = %q (run %v), want op-1 still running", current, running) childC := cachedCoordinator(t, tree.h, tree.child) childC.mu.Lock() closed := childC.bgState == bgClosed @@ -356,11 +408,13 @@ func TestStopRootStopsMembersAndReopens(t *testing.T) { watch.next() // the first steered completion's own Operation settles watch.next() // the second steered completion's own Operation settles watch.next() // the queued message admitted after the reopen - if rec := settledOperation(t, tree.store, tree.root, "op-2"); rec.State.Status != OperationSuccess { - t.Fatalf("queued operation status = %s, want the preserved queued message admitted and successful", rec.State.Status) + { + rec := settledOperation(t, tree.store, tree.root, "op-2") + testassert.Equal(t, rec.State.Status, OperationSuccess) } - if rec := settledOperation(t, tree.store, tree.root, "op-1"); rec.State.Status != OperationSuccess { - t.Fatalf("root operation status = %s, want the quiet success", rec.State.Status) + { + rec := settledOperation(t, tree.store, tree.root, "op-1") + testassert.Equal(t, rec.State.Status, OperationSuccess) } // The member-stop order is unspecified (snapshot map iteration), so the // two completions may commit in either order; the set is what survives. @@ -368,28 +422,20 @@ func TestStopRootStopsMembersAndReopens(t *testing.T) { sort.Strings(texts) want := []string{"Task interrupted.", "hello", "job done", "queued-msg"} sort.Strings(want) - if !reflect.DeepEqual(texts, want) { - t.Fatalf("root entries = %q, want the surviving buffers delivered", texts) - } + testassert.Require(t, reflect.DeepEqual(texts, want), "root entries = %q, want the surviving buffers delivered", texts) // Each delivered completion owns its own Operation: op-1 keeps exactly its // admitted input and the completions arrive through ordinary admission. rootGraph, err := validateFixture(t, tree.store, tree.root) - if err != nil { - t.Fatalf("root graph: %v", err) - } + testassert.NoError(t, err) for _, entry := range rootGraph.Entries { if entry.Input == nil || len(entry.Input.Content) == 0 { continue } switch entry.Input.Content[0].Text { case "hello": - if entry.Envelope.OperationID != "op-1" { - t.Fatalf("admitted input owned by %q, want op-1", entry.Envelope.OperationID) - } + testassert.Equal(t, entry.Envelope.OperationID, "op-1") case "queued-msg": - if entry.Envelope.OperationID != "op-2" { - t.Fatalf("queued input owned by %q, want op-2", entry.Envelope.OperationID) - } + testassert.Equal(t, entry.Envelope.OperationID, "op-2") default: if entry.Envelope.OperationID == "op-1" || entry.Input.Origin != InputOriginRuntime { t.Fatalf("completion input %q owned by %q (%q), want its own successor Operation with runtime origin", entry.Input.Content[0].Text, entry.Envelope.OperationID, entry.Input.Origin) @@ -410,24 +456,25 @@ func TestStopChildClosesPermanently(t *testing.T) { defer close(release) root := createSession(t, h) res, err := h.LaunchChildSession(context.Background(), launchRequestFor(root)) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) child := res.ChildSessionID childC := cachedCoordinator(t, h, child) childC.mu.Lock() run := childC.run childC.mu.Unlock() - if _, err := submitText(t, h, child, "op-2", MessageModeRegular, "steer-me"); err != nil { - t.Fatalf("steering submit to the child: %v", err) + { + _, err := submitText(t, h, child, "op-2", MessageModeRegular, "steer-me") + testassert.NoError(t, err) } - if err := h.Stop(context.Background(), child); err != nil { - t.Fatalf("Stop(child): %v", err) + { + err := h.Stop(context.Background(), child) + testassert.NoError(t, err) } - if rec := settledOperation(t, store, child, "child-op-1"); rec.State.Status != OperationInterruption { - t.Fatalf("child operation status = %s, want the interrupted run", rec.State.Status) + { + rec := settledOperation(t, store, child, "child-op-1") + testassert.Equal(t, rec.State.Status, OperationInterruption) } select { case <-run.done: @@ -441,18 +488,15 @@ func TestStopChildClosesPermanently(t *testing.T) { if !closed { t.Fatalf("child bgState after Stop = %q, want permanently closed", childC.bgState) } - if buffers != 0 { - t.Fatalf("closed child kept %d buffered items, want the buffers discarded", buffers) - } - if storedOperationExists(store, child, "op-2") { - t.Fatalf("the closed child's discarded head admitted a successor") + testassert.Equal(t, buffers, 0) + testassert.Require(t, !storedOperationExists(store, child, "op-2"), "the closed child's discarded head admitted a successor") + { + _, err := submitText(t, h, child, "op-3", MessageModeRegular, "nope") + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "session is closed to agent work"), "Submit on the closed child = %v, want the closed-to-agent-work rejection", err) } - if _, err := submitText(t, h, child, "op-3", MessageModeRegular, "nope"); !errors.Is(err, ErrInvalid) || - !strings.Contains(err.Error(), "session is closed to agent work") { - t.Fatalf("Submit on the closed child = %v, want the closed-to-agent-work rejection", err) - } - if got := runtimeInputTextRaw(t, store, root); got != "Task interrupted." { - t.Fatalf("parent runtime input = %q, want the child completion delivered to the idle parent", got) + { + got := runtimeInputTextRaw(t, store, root) + testassert.Equal(t, got, "Task interrupted.") } } @@ -465,9 +509,9 @@ func TestSubmitRejectsClosedActiveChild(t *testing.T) { h := newTestHarness(t, store, nil) childC := leaseCoordinator(t, h, completionChildID) closeBackgroundMode(childC) - if _, err := submitText(t, h, completionChildID, "op-x", MessageModeRegular, "hi"); !errors.Is(err, ErrInvalid) || - !strings.Contains(err.Error(), "session is closed to agent work") { - t.Fatalf("Submit on the closed active child = %v, want the closed-to-agent-work rejection", err) + { + _, err := submitText(t, h, completionChildID, "op-x", MessageModeRegular, "hi") + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "session is closed to agent work"), "Submit on the closed active child = %v, want the closed-to-agent-work rejection", err) } } @@ -509,9 +553,7 @@ func TestStopGatedLaunchWaitsForMaterialization(t *testing.T) { childID = id } } - if childID == "" { - t.Fatalf("the committed child register is missing") - } + testassert.Require(t, childID != "", "the committed child register is missing") stopDone := make(chan error, 1) go func() { stopDone <- h.Stop(context.Background(), childID) }() @@ -521,28 +563,31 @@ func TestStopGatedLaunchWaitsForMaterialization(t *testing.T) { case <-time.After(200 * time.Millisecond): } sharedLoads := gate.readCount() // the parent's load plus the parked child's: the stop must not add a third - if sharedLoads != 2 { - t.Fatalf("register reads while the stop waited = %d, want the parent's and the parked child's two", sharedLoads) - } + testassert.Equal(t, sharedLoads, 2) close(gate.readRelease) - if err := stopResult(t, stopDone); err != nil { - t.Fatalf("Stop(child) = %v, want nil", err) + { + err := stopResult(t, stopDone) + testassert.NoError(t, err) } - if err := <-launchDone; err != nil { - t.Fatalf("LaunchChildSession = %v, want the launch to succeed", err) + { + err := <-launchDone + testassert.NoError(t, err) } // The stop's own acquisition joins the launch's just-published graph when // it arrives in time, or — under the one release rule — loads the child // once itself; it never loads more than that one. - if got := gate.readCount(); got != sharedLoads && got != sharedLoads+1 { - t.Fatalf("register reads after the stop joined = %d, want at most the stop's own one load beyond %d", got, sharedLoads) + { + got := gate.readCount() + testassert.Require(t, got == sharedLoads || got == sharedLoads+1, "register reads after the stop joined = %d, want at most the stop's own one load beyond %d", got, sharedLoads) } - if rec := settledOperation(t, store, childID, "child-op-1"); rec.State.Status == OperationRunning || rec.State.Terminal == nil { - t.Fatalf("child operation state = %+v, want a terminal settlement", rec.State) + { + rec := settledOperation(t, store, childID, "child-op-1") + testassert.Require(t, rec.State.Status != OperationRunning && rec.State.Terminal != nil, "child operation state = %+v, want a terminal settlement", rec.State) } - if got := runtimeInputTextRaw(t, store, testSessionID); got == "" { - t.Fatalf("parent runtime input = %q, want the child completion delivered", got) + { + got := runtimeInputTextRaw(t, store, testSessionID) + testassert.Require(t, got != "", "parent runtime input = %q, want the child completion delivered", got) } if len(storedSignals(t, store, testSessionID)) != 0 { t.Fatalf("parent signals = %+v, want the idle parent's admission delivery, not a signal", storedSignals(t, store, testSessionID)) @@ -594,9 +639,7 @@ func TestStopRacingStartSettlesWithoutStarting(t *testing.T) { childID = id } } - if childID == "" { - t.Fatalf("the committed child register is missing") - } + testassert.Require(t, childID != "", "the committed child register is missing") childC := leaseCoordinator(t, h, childID) stopDone := make(chan error, 1) @@ -604,11 +647,13 @@ func TestStopRacingStartSettlesWithoutStarting(t *testing.T) { awaitStopInterval(t, childC, stopDone) // the parent membership holds the stop close(gate.txRelease) - if err := stopResult(t, stopDone); err != nil { - t.Fatalf("Stop(child) = %v, want nil", err) + { + err := stopResult(t, stopDone) + testassert.NoError(t, err) } - if err := <-launchDone; err != nil { - t.Fatalf("gated launch = %v, want the launch to succeed with the entry interruption", err) + { + err := <-launchDone + testassert.NoError(t, err) } mu.Lock() childOpened := false @@ -618,15 +663,14 @@ func TestStopRacingStartSettlesWithoutStarting(t *testing.T) { } } mu.Unlock() - if childOpened { - t.Fatalf("the racing child execution opened; want it settled without starting") - } - if rec := settledOperation(t, store, childID, "child-op-1"); rec.State.Status != OperationInterruption || - rec.State.Terminal == nil || rec.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("child operation state = %+v, want terminal interruption with the contract detail", rec.State) + testassert.Require(t, !childOpened, "the racing child execution opened; want it settled without starting") + { + rec := settledOperation(t, store, childID, "child-op-1") + testassert.Require(t, rec.State.Status == OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == executionInterruptedDetail, "child operation state = %+v, want terminal interruption with the contract detail", rec.State) } - if got := runtimeInputTextRaw(t, store, testSessionID); got != "Task interrupted." { - t.Fatalf("parent runtime input = %q, want the racing start's completion delivered", got) + { + got := runtimeInputTextRaw(t, store, testSessionID) + testassert.Equal(t, got, "Task interrupted.") } } @@ -641,9 +685,7 @@ func TestStopChildJoinsReservationAndRun(t *testing.T) { defer cancel() childC := leaseCoordinator(t, h, completionChildID) release, err := h.reserve(context.Background(), childC) - if err != nil { - t.Fatalf("reserve: %v", err) - } + testassert.NoError(t, err) rec, prepared, _, err := h.admitReserved(context.Background(), childC, admissionRequest{ SessionID: completionChildID, OperationID: "op-2", @@ -651,9 +693,7 @@ func TestStopChildJoinsReservationAndRun(t *testing.T) { Origin: InputOriginUser, Content: admissionContent("later"), }) - if err != nil || prepared == nil { - t.Fatalf("admission = (%+v, %+v, %v), want a published new Operation", rec, prepared, err) - } + testassert.Require(t, err == nil && prepared != nil, "admission = (%+v, %+v, %v), want a published new Operation", rec, prepared, err) stopDone := make(chan error, 1) go func() { stopDone <- h.Stop(context.Background(), completionChildID) }() @@ -661,16 +701,17 @@ func TestStopChildJoinsReservationAndRun(t *testing.T) { h.startExecution(childC, "op-2", *prepared) // the ordinary admission installs before releasing release() - if err := stopResult(t, stopDone); err != nil { - t.Fatalf("Stop(child) = %v, want nil", err) + { + err := stopResult(t, stopDone) + testassert.NoError(t, err) } - if settled := settledOperation(t, store, completionChildID, "op-2"); settled.State.Status != OperationInterruption || - settled.State.Terminal == nil || settled.State.Terminal.Detail != executionInterruptedDetail { - t.Fatalf("installed operation state = %+v, want the closed-entry interruption", settled.State) + { + settled := settledOperation(t, store, completionChildID, "op-2") + testassert.Require(t, settled.State.Status == OperationInterruption && settled.State.Terminal != nil && settled.State.Terminal.Detail == executionInterruptedDetail, "installed operation state = %+v, want the closed-entry interruption", settled.State) } - if _, err := submitText(t, h, completionChildID, "op-3", MessageModeRegular, "nope"); !errors.Is(err, ErrInvalid) || - !strings.Contains(err.Error(), "session is closed to agent work") { - t.Fatalf("Submit on the closed child = %v, want the closed-to-agent-work rejection", err) + { + _, err := submitText(t, h, completionChildID, "op-3", MessageModeRegular, "nope") + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "session is closed to agent work"), "Submit on the closed child = %v, want the closed-to-agent-work rejection", err) } } @@ -686,9 +727,7 @@ func TestStopChildWaitsForLiveJobDelivery(t *testing.T) { defer close(release) root := createSession(t, h) res, err := h.LaunchChildSession(context.Background(), launchRequestFor(root)) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) child := res.ChildSessionID gate := make(chan struct{}) @@ -697,11 +736,12 @@ func TestStopChildWaitsForLiveJobDelivery(t *testing.T) { h.DeliverBackgroundCompletion(context.Background(), sessionID, completions[jobID], "job done") }} h.deps.Jobs = stopper - if err := h.StartJob(context.Background(), child, jobFixtureID, func(_ context.Context, completionID string) error { - completions[jobFixtureID] = completionID - return nil - }); err != nil { - t.Fatalf("StartJob on the child: %v", err) + { + err := h.StartJob(context.Background(), child, jobFixtureID, func(_ context.Context, completionID string) error { + completions[jobFixtureID] = completionID + return nil + }) + testassert.NoError(t, err) } rootC := cachedCoordinator(t, h, root) rootC.mu.Lock() @@ -712,15 +752,14 @@ func TestStopChildWaitsForLiveJobDelivery(t *testing.T) { } } rootC.mu.Unlock() - if membership == nil { - t.Fatalf("the child's parent membership is missing") - } + testassert.Require(t, membership != nil, "the child's parent membership is missing") stopDone := make(chan error, 1) go func() { stopDone <- h.Stop(context.Background(), child) }() awaitStopInterval(t, cachedCoordinator(t, h, child), stopDone) - if signals := storedSignals(t, store, root); len(signals) != 0 { - t.Fatalf("parent signals while the job is live = %d, want none", len(signals)) + { + signals := storedSignals(t, store, root) + testassert.Equal(t, len(signals), 0) } select { case <-membership: @@ -729,25 +768,29 @@ func TestStopChildWaitsForLiveJobDelivery(t *testing.T) { } close(gate) // the job finishes: its kill-completion closes the child, then the child delivers - if err := stopResult(t, stopDone); err != nil { - t.Fatalf("Stop(child) = %v, want nil", err) + { + err := stopResult(t, stopDone) + testassert.NoError(t, err) } - if got := runtimeInputTextRaw(t, store, root); got != "Task interrupted." { - t.Fatalf("parent runtime input = %q, want the child completion delivered after the job finished", got) + { + got := runtimeInputTextRaw(t, store, root) + testassert.Equal(t, got, "Task interrupted.") } if len(storedSignals(t, store, root)) != 0 { t.Fatalf("parent signals = %+v, want the idle parent's admission delivery, not a signal", storedSignals(t, store, root)) } - if signals := storedSignals(t, store, child); len(signals) != 1 || signals[0].Content != "job done" { - t.Fatalf("child signals = %+v, want the job's closed-delivery signal", signals) + { + signals := storedSignals(t, store, child) + testassert.Require(t, len(signals) == 1 && signals[0].Content == "job done", "child signals = %+v, want the job's closed-delivery signal", signals) } select { case <-membership: default: t.Fatalf("the parent membership did not close") } - if rec := settledOperation(t, store, child, "child-op-1"); rec.State.Status != OperationInterruption { - t.Fatalf("child operation status = %s, want interruption", rec.State.Status) + { + rec := settledOperation(t, store, child, "child-op-1") + testassert.Equal(t, rec.State.Status, OperationInterruption) } } @@ -755,23 +798,26 @@ func TestStopChildWaitsForLiveJobDelivery(t *testing.T) { // the corruption-class fixture both error-path stop tests resolve. func insertMalformedChildSession(t *testing.T, store *graphStorage, sessionID string) { t.Helper() - if err := store.Transact(context.Background(), func(tx Transaction) error { - _, err := tx.InsertRegister(RegisterDraft{ - Key: RegisterKey{SessionID: sessionID, Kind: RegisterSession}, - Payload: json.RawMessage(`{"bogus":true}`), + { + err := store.Transact(context.Background(), func(tx Transaction) error { + _, err := tx.InsertRegister(RegisterDraft{ + Key: RegisterKey{SessionID: sessionID, Kind: RegisterSession}, + Payload: json.RawMessage(`{"bogus":true}`), + }) + return err }) - return err - }); err != nil { - t.Fatalf("insert corrupt child register: %v", err) + testassert.NoError(t, err) } } // TestStopConcurrentCallersJoinSameError proves the join row: concurrent -// stops share one interval and every caller receives the same stored error — -// here the corruption-class child failure that Stop claim-finishes without -// delivery. The stopper stops the job member exactly once, the corrupt child -// register stays byte-identical, and the root admits and completes ordinary -// work after convergence. +// stops share one interval and every joining caller receives the same stored +// error — here the corruption-class child failure that Stop claim-finishes +// without delivery — while a later public Stop after retirement is not a +// joiner and observes the reopened empty root as a nil no-op. The stopper +// stops the job member exactly once, the corrupt child register stays +// byte-identical, and the root admits and completes ordinary work after +// convergence. func TestStopConcurrentCallersJoinSameError(t *testing.T) { store := freshSessionStore(t) childID := hexID(9) @@ -780,64 +826,77 @@ func TestStopConcurrentCallersJoinSameError(t *testing.T) { defer cancel() c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, childID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) corruptKey := RegisterKey{SessionID: childID, Kind: RegisterSession} corruptBefore, err := store.ReadRegister(context.Background(), corruptKey) - if err != nil { - t.Fatalf("read the corrupt child register: %v", err) - } + testassert.NoError(t, err) gate := make(chan struct{}) completions := map[string]string{} stopper := &gatedJobStopper{gate: gate, deliver: func(sessionID, jobID string) { h.DeliverBackgroundCompletion(context.Background(), sessionID, completions[jobID], "job done") }} h.deps.Jobs = stopper - if err := h.StartJob(context.Background(), testSessionID, jobFixtureID, func(_ context.Context, completionID string) error { - completions[jobFixtureID] = completionID - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(context.Background(), testSessionID, jobFixtureID, func(_ context.Context, completionID string) error { + completions[jobFixtureID] = completionID + return nil + }) + testassert.NoError(t, err) } - // The first stop owns the interval and parks at the gated job. The two - // joiners signal entry immediately before their Stop calls while that - // interval is provably open; the rendezvous receives both entries, then - // the gate releases — the joiners have joined and cannot return before - // the owner converges. + // The first stop owns the interval and parks at the gated job. Every + // concurrent caller below starts while that interval is provably open. + // The bounded stack wait then requires the two joiners actually parked + // in the join receive — not merely announced — before the gate releases. + // A later public Stop runs synchronously after retirement: it is not a + // joiner and observes the reopened empty root as a nil no-op — + // goroutine start is not a join. ownerDone := make(chan error, 1) - go func() { ownerDone <- h.Stop(context.Background(), testSessionID) }() - awaitStopInterval(t, cachedCoordinator(t, h, testSessionID), ownerDone) - joinerEntered := make(chan struct{}, 2) joinerDone := make(chan error, 2) + var ( + releaseGate sync.Once + stopWG sync.WaitGroup + ) + stopWG.Add(1) + go func() { + defer stopWG.Done() + ownerDone <- h.Stop(context.Background(), testSessionID) + }() + defer func() { // a failed phase still releases the fixture gate once and joins the started Stop callers + releaseGate.Do(func() { close(gate) }) + waitStopCallers(t, &stopWG) + }() + awaitStopInterval(t, cachedCoordinator(t, h, testSessionID), ownerDone) for i := 0; i < 2; i++ { + stopWG.Add(1) go func() { - joinerEntered <- struct{}{} + defer stopWG.Done() joinerDone <- h.Stop(context.Background(), testSessionID) }() } - <-joinerEntered - <-joinerEntered + awaitStopJoiners(t, 2) - close(gate) // the job finishes: the owner collects the child error and stores it + releaseGate.Do(func() { close(gate) }) // the job finishes: the owner collects the child error and stores it err = stopResult(t, ownerDone) - if err2 := stopResult(t, joinerDone); err2 != err { - t.Fatalf("joiner = %v, want the same stored error as the owner (%v)", err2, err) + { + err2 := stopResult(t, joinerDone) + testassert.Equal(t, err2, err) } - if err3 := stopResult(t, joinerDone); err3 != err { - t.Fatalf("joiner = %v, want the same stored error as the owner (%v)", err3, err) + { + err3 := stopResult(t, joinerDone) + testassert.Equal(t, err3, err) } - if err == nil { - t.Fatalf("the joined error = nil, want the stored child error") + { + errLate := h.Stop(context.Background(), testSessionID) // a post-retirement public Stop is not a joiner + testassert.NoError(t, errLate) // the empty reopened root stops as a nil no-op } + testassert.Require(t, err != nil, "the joined error = nil, want the stored child error") var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("joined error = %v, want the corruption class", err) - } + testassert.Require(t, errors.As(err, &corrupt), "joined error = %v, want the corruption class", err) waitMemberDone(t, member) // claim-finished without delivery - if signals := storedSignals(t, store, testSessionID); len(signals) != 0 { - t.Fatalf("root signals = %+v, want no delivery from the claim-finished child", signals) + { + signals := storedSignals(t, store, testSessionID) + testassert.Require(t, len(signals) == 0, "root signals = %+v, want no delivery from the claim-finished child", signals) } c.mu.Lock() reopened := c.bgState == bgOpen @@ -845,23 +904,21 @@ func TestStopConcurrentCallersJoinSameError(t *testing.T) { if !reopened { t.Fatalf("root bgState after Stop = %q, want reopened", c.bgState) } - if stopped := stopper.stopped; len(stopped) != 1 || stopped[0] != testSessionID+"/"+jobFixtureID { - t.Fatalf("StopJob calls = %q, want exactly the one job member's stop", stopped) + { + stopped := stopper.stopped + testassert.Require(t, len(stopped) == 1 && stopped[0] == testSessionID+"/"+jobFixtureID, "StopJob calls = %q, want exactly the one job member's stop", stopped) } corruptAfter, err := store.ReadRegister(context.Background(), corruptKey) - if err != nil { - t.Fatalf("reread the corrupt child register: %v", err) - } - if !reflect.DeepEqual(corruptBefore, corruptAfter) { - t.Fatalf("the corrupt child register changed: before %+v after %+v", corruptBefore, corruptAfter) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(corruptBefore, corruptAfter), "the corrupt child register changed: before %+v after %+v", corruptBefore, corruptAfter) // The root lifecycle is usable after convergence: once the delivered // job completion's own Operation retires, the next ordinary input // admits and completes. waitQuiet(t, c) - if _, err := submitText(t, h, testSessionID, "op-root", MessageModeRegular, "after stop"); err != nil { - t.Fatalf("post-stop submit: %v", err) + { + _, err := submitText(t, h, testSessionID, "op-root", MessageModeRegular, "after stop") + testassert.NoError(t, err) } awaitOperationTerminal(t, store, testSessionID, "op-root", OperationSuccess) } @@ -878,17 +935,14 @@ func TestStopReportsChildErrorAndWaitsForNaturalDelivery(t *testing.T) { h := newTestHarness(t, store, nil) c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, childID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) err = h.Stop(context.Background(), testSessionID) var corrupt *CorruptionError - if !errors.As(err, &corrupt) { - t.Fatalf("Stop = %v, want the collected corruption error", err) - } + testassert.Require(t, errors.As(err, &corrupt), "Stop = %v, want the collected corruption error", err) waitMemberDone(t, member) - if signals := storedSignals(t, store, testSessionID); len(signals) != 0 { - t.Fatalf("root signals = %+v, want no delivery", signals) + { + signals := storedSignals(t, store, testSessionID) + testassert.Require(t, len(signals) == 0, "root signals = %+v, want no delivery", signals) } }) @@ -900,9 +954,7 @@ func TestStopReportsChildErrorAndWaitsForNaturalDelivery(t *testing.T) { defer cancel() c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberChild, completionChildID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } + testassert.NoError(t, err) store.mu.Lock() store.registersErr = fmt.Errorf("%w: storage down", ErrStorage) // the child resolution fails @@ -913,9 +965,7 @@ func TestStopReportsChildErrorAndWaitsForNaturalDelivery(t *testing.T) { c.mu.Lock() claimed := member.claimed c.mu.Unlock() - if claimed { - t.Fatalf("Stop claim-finished a member whose failure does not prevent natural delivery") - } + testassert.Require(t, !claimed, "Stop claim-finished a member whose failure does not prevent natural delivery") store.mu.Lock() store.registersErr = nil // the transient failure clears: the natural path converges @@ -927,12 +977,11 @@ func TestStopReportsChildErrorAndWaitsForNaturalDelivery(t *testing.T) { h.childCompletionSettled(childC, "") // the settled child's completion delivers on its own err = stopResult(t, stopDone) - if !errors.Is(err, ErrStorage) { - t.Fatalf("Stop = %v, want the reported storage-class child error", err) - } + testassert.Require(t, errors.Is(err, ErrStorage), "Stop = %v, want the reported storage-class child error", err) waitMemberDone(t, member) - if got := runtimeInputTextRaw(t, store, testSessionID); got != "Task completed." { - t.Fatalf("root runtime input = %q, want the natural delivery", got) + { + got := runtimeInputTextRaw(t, store, testSessionID) + testassert.Equal(t, got, "Task completed.") } c.mu.Lock() reopened := c.bgState == bgOpen @@ -959,11 +1008,12 @@ func TestStopRootRejectsStartWhileStopping(t *testing.T) { h.DeliverBackgroundCompletion(context.Background(), sessionID, completions[jobID], "job done") }} h.deps.Jobs = stopper - if err := h.StartJob(context.Background(), root, jobFixtureID, func(_ context.Context, completionID string) error { - completions[jobFixtureID] = completionID - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(context.Background(), root, jobFixtureID, func(_ context.Context, completionID string) error { + completions[jobFixtureID] = completionID + return nil + }) + testassert.NoError(t, err) } stopDone := make(chan error, 1) @@ -971,20 +1021,20 @@ func TestStopRootRejectsStartWhileStopping(t *testing.T) { awaitStopInterval(t, cachedCoordinator(t, h, root), stopDone) // the parked kill-completion holds the stop spawns := 0 - if err := h.StartJob(context.Background(), root, "feedface", func(context.Context, string) error { - spawns++ - return nil - }); err == nil || err.Error() != "harness: invalid storage request: background group is closed or stopping" { - t.Fatalf("start while stopping = %v, want the closed-or-stopping rejection", err) - } - if spawns != 0 { - t.Fatalf("the rejected start spawned %d times", spawns) + { + err := h.StartJob(context.Background(), root, "feedface", func(context.Context, string) error { + spawns++ + return nil + }) + testassert.Require(t, err != nil && err.Error() == "harness: invalid storage request: background group is closed or stopping", "start while stopping = %v, want the closed-or-stopping rejection", err) } + testassert.Equal(t, spawns, 0) watch := watchSettlements(store) // both kill-completion admissions settle close(gate) - if err := stopResult(t, stopDone); err != nil { - t.Fatalf("Stop(root) = %v, want nil", err) + { + err := stopResult(t, stopDone) + testassert.NoError(t, err) } c := cachedCoordinator(t, h, root) c.mu.Lock() @@ -996,19 +1046,22 @@ func TestStopRootRejectsStartWhileStopping(t *testing.T) { watch.next() // the first kill-completion admission settled var completion2 string - if err := h.StartJob(context.Background(), root, "feedface", func(_ context.Context, completionID string) error { - completion2 = completionID - return nil - }); err != nil { - t.Fatalf("StartJob after reopen: %v", err) + { + err := h.StartJob(context.Background(), root, "feedface", func(_ context.Context, completionID string) error { + completion2 = completionID + return nil + }) + testassert.NoError(t, err) } waitQuiet(t, c) // the retiring run releases before the second delivery resolves its mode - if err := h.DeliverBackgroundCompletion(context.Background(), root, completion2, "job done"); err != nil { - t.Fatalf("second delivery: %v", err) + { + err := h.DeliverBackgroundCompletion(context.Background(), root, completion2, "job done") + testassert.NoError(t, err) } watch.next() // the second kill-completion admission - if rec := settledOperation(t, store, root, completion2); rec.State.Status != OperationSuccess { - t.Fatalf("new work status = %s, want the reopened group completing it normally", rec.State.Status) + { + rec := settledOperation(t, store, root, completion2) + testassert.Equal(t, rec.State.Status, OperationSuccess) } } @@ -1035,8 +1088,9 @@ func TestStopHeldPreparationRegistersAfterReopen(t *testing.T) { defer cancel() defer close(release) root := createSession(t, h) - if _, err := submitText(t, h, root, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) + { + _, err := submitText(t, h, root, "op-1", MessageModeRegular, "hello") + testassert.NoError(t, err) } gate := make(chan struct{}) @@ -1045,11 +1099,12 @@ func TestStopHeldPreparationRegistersAfterReopen(t *testing.T) { h.DeliverBackgroundCompletion(context.Background(), sessionID, completions[jobID], "job done") }} h.deps.Jobs = stopper - if err := h.StartJob(context.Background(), root, jobFixtureID, func(_ context.Context, completionID string) error { - completions[jobFixtureID] = completionID - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := h.StartJob(context.Background(), root, jobFixtureID, func(_ context.Context, completionID string) error { + completions[jobFixtureID] = completionID + return nil + }) + testassert.NoError(t, err) } stopDone := make(chan error, 1) @@ -1068,12 +1123,14 @@ func TestStopHeldPreparationRegistersAfterReopen(t *testing.T) { } close(gate) // the stop completes and reopens; the kill-completion steers without a register write - if err := stopResult(t, stopDone); err != nil { - t.Fatalf("Stop(root) = %v, want nil", err) + { + err := stopResult(t, stopDone) + testassert.NoError(t, err) } close(hold) // the held preparation registers against the reopened group - if err := <-launchDone; err != nil { - t.Fatalf("held launch after reopen = %v, want success", err) + { + err := <-launchDone + testassert.NoError(t, err) } c := cachedCoordinator(t, h, root) c.mu.Lock() @@ -1083,9 +1140,7 @@ func TestStopHeldPreparationRegistersAfterReopen(t *testing.T) { if !reopened { t.Fatalf("root bgState after the held registration = %q, want reopened", c.bgState) } - if current != "op-1" { - t.Fatalf("root current operation = %q, want the untouched running Operation", current) - } + testassert.Equal(t, current, "op-1") } // TestArchiveGateRejectsLiveMembers proves the archive gate: a Session with a @@ -1096,35 +1151,27 @@ func TestArchiveGateRejectsLiveMembers(t *testing.T) { h := newTestHarness(t, store, nil) c := leaseCoordinator(t, h, testSessionID) member, err := admitLocked(h, c, memberJob, jobFixtureID, 0) - if err != nil { - t.Fatalf("admission: %v", err) - } - if _, ok := claimLocked(h, c, member.completionID); !ok { - t.Fatalf("the fixture member was not claimed") + testassert.NoError(t, err) + { + _, ok := claimLocked(h, c, member.completionID) + testassert.Require(t, ok, "the fixture member was not claimed") } - if _, err := h.ArchiveSession(context.Background(), testSessionID); !errors.Is(err, ErrInvalid) || - !strings.Contains(err.Error(), "session has live background work; stop it first") { - t.Fatalf("ArchiveSession = %v, want the live-background rejection", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.Require(t, errors.Is(err, ErrInvalid) && strings.Contains(err.Error(), "session has live background work; stop it first"), "ArchiveSession = %v, want the live-background rejection", err) } deleted, err := h.SweepSession(context.Background(), testSessionID, SweepPolicy{ArchiveAfter: time.Hour}, testTime.Add(2*time.Hour)) - if err != nil { - t.Fatalf("SweepSession: %v", err) - } - if deleted { - t.Fatalf("SweepSession deleted the Session, want the live member to skip it") - } + testassert.NoError(t, err) + testassert.Require(t, !deleted, "SweepSession deleted the Session, want the live member to skip it") rec, err := h.ReadSessionHeader(context.Background(), testSessionID) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if rec.Lifecycle != LifecycleOpen { - t.Fatalf("swept lifecycle = %q, want the skipped open Session", rec.Lifecycle) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.Lifecycle, LifecycleOpen) h.finishBackgroundMember(c, member) // the member finishes: both paths proceed - if _, err := h.ArchiveSession(context.Background(), testSessionID); err != nil { - t.Fatalf("ArchiveSession after the member finished: %v", err) + { + _, err := h.ArchiveSession(context.Background(), testSessionID) + testassert.NoError(t, err) } } @@ -1137,14 +1184,17 @@ func TestStopAllMixedTree(t *testing.T) { defer close(tree.release) extra := createSession(t, tree.h) - if err := tree.h.StopAll(context.Background()); err != nil { - t.Fatalf("StopAll: %v", err) + { + err := tree.h.StopAll(context.Background()) + testassert.NoError(t, err) } - if got := tree.stopper.stopped; len(got) != 1 || got[0] != tree.root+"/"+jobFixtureID { - t.Fatalf("StopJob calls = %v, want the tree's job killed", got) + { + got := tree.stopper.stopped + testassert.Require(t, len(got) == 1 && got[0] == tree.root+"/"+jobFixtureID, "StopJob calls = %v, want the tree's job killed", got) } - if rec := settledOperation(t, tree.store, tree.child, "child-op-1"); rec.State.Status != OperationInterruption { - t.Fatalf("child operation status = %s, want interruption", rec.State.Status) + { + rec := settledOperation(t, tree.store, tree.child, "child-op-1") + testassert.Equal(t, rec.State.Status, OperationInterruption) } childC := cachedCoordinator(t, tree.h, tree.child) childC.mu.Lock() @@ -1156,14 +1206,12 @@ func TestStopAllMixedTree(t *testing.T) { rootEmpty := rootC.group == nil || len(rootC.group.members) == 0 extraOpen := tree.h.sessions[extra] != nil rootC.mu.Unlock() - if !childClosed || !rootOpen || !rootEmpty { - t.Fatalf("after StopAll: childClosed=%v rootOpen=%v rootEmpty=%v", childClosed, rootOpen, rootEmpty) - } - if !extraOpen { - t.Fatalf("the idle root vanished from the registry") - } + testassert.Require(t, childClosed && rootOpen && rootEmpty, "after StopAll: childClosed=%v rootOpen=%v rootEmpty=%v", childClosed, rootOpen, rootEmpty) + testassert.Require(t, extraOpen, "the idle root vanished from the registry") - if err := tree.h.StopAll(context.Background()); err != nil { // idempotent - t.Fatalf("second StopAll = %v, want nil", err) + // idempotent + { + err := tree.h.StopAll(context.Background()) + testassert.NoError(t, err) } } diff --git a/harness/storage_test.go b/harness/storage_test.go index fde4d9b6..03257d96 100644 --- a/harness/storage_test.go +++ b/harness/storage_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestClosedKinds proves every entry and register kind constant carries its exact contract string and that no constant collapses onto a sibling or the zero value, so the kind set stays closed to the declared non-zero values. @@ -21,63 +22,43 @@ func TestClosedKinds(t *testing.T) { harness.EntryCompaction: "compaction", harness.EntryOperationSettlement: "operation_settlement", } - if len(entryKinds) != 7 { - t.Fatalf("entry kind constants collapse to %d distinct values, want 7", len(entryKinds)) - } + testassert.Equal(t, len(entryKinds), 7) for kind, want := range entryKinds { - if string(kind) != want { - t.Errorf("EntryKind %q, want %q", kind, want) - } - if kind == "" { - t.Errorf("EntryKind zero value is a declared constant; the kind set must be closed to non-zero kinds") - } + testassert.Check(t, string(kind) == want, "EntryKind %q, want %q", kind, want) + testassert.Check(t, kind != "", "EntryKind zero value is a declared constant; the kind set must be closed to non-zero kinds") } registerKinds := map[harness.RegisterKind]string{ harness.RegisterSession: "session", harness.RegisterOperation: "operation", } - if len(registerKinds) != 2 { - t.Fatalf("register kind constants collapse to %d distinct values, want 2", len(registerKinds)) - } + testassert.Equal(t, len(registerKinds), 2) for kind, want := range registerKinds { - if string(kind) != want { - t.Errorf("RegisterKind %q, want %q", kind, want) - } - if kind == "" { - t.Errorf("RegisterKind zero value is a declared constant; the kind set must be closed to non-zero kinds") - } + testassert.Check(t, string(kind) == want, "RegisterKind %q, want %q", kind, want) + testassert.Check(t, kind != "", "RegisterKind zero value is a declared constant; the kind set must be closed to non-zero kinds") } } // TestTypedErrorClassification proves the typed errors carry their owning sentinel through errors.Is and their fields through errors.As across arbitrary wrapping, without cross-classifying into an unrelated sentinel. func TestTypedErrorClassification(t *testing.T) { wrapped := fmt.Errorf("read entries: %w", &harness.CorruptionError{SessionID: "s1", Detail: "malformed envelope"}) - if !errors.Is(wrapped, harness.ErrCorrupt) { - t.Errorf("CorruptionError does not classify as ErrCorrupt: %v", wrapped) - } + testassert.Check(t, errors.Is(wrapped, harness.ErrCorrupt), "CorruptionError does not classify as ErrCorrupt: %v", wrapped) var gotCorrupt *harness.CorruptionError if !errors.As(wrapped, &gotCorrupt) { t.Errorf("CorruptionError not reachable through errors.As: %v", wrapped) } else if gotCorrupt == nil || gotCorrupt.SessionID != "s1" || gotCorrupt.Detail != "malformed envelope" { t.Errorf("errors.As recovered %+v, want the stored fields", gotCorrupt) } - if errors.Is(wrapped, harness.ErrInvalid) { - t.Error("CorruptionError cross-classifies as ErrInvalid") - } + testassert.Check(t, !errors.Is(wrapped, harness.ErrInvalid), "%v", "CorruptionError cross-classifies as ErrInvalid") wrappedSchema := fmt.Errorf("open: %w", &harness.IncompatibleSchemaError{Found: 3, Supported: 1, Detail: "extra table"}) - if !errors.Is(wrappedSchema, harness.ErrIncompatible) { - t.Errorf("IncompatibleSchemaError does not classify as ErrIncompatible: %v", wrappedSchema) - } + testassert.Check(t, errors.Is(wrappedSchema, harness.ErrIncompatible), "IncompatibleSchemaError does not classify as ErrIncompatible: %v", wrappedSchema) var gotSchema *harness.IncompatibleSchemaError if !errors.As(wrappedSchema, &gotSchema) { t.Errorf("IncompatibleSchemaError not reachable through errors.As: %v", wrappedSchema) } else if gotSchema == nil || gotSchema.Found != 3 || gotSchema.Supported != 1 || gotSchema.Detail != "extra table" { t.Errorf("errors.As recovered %+v, want the stored fields", gotSchema) } - if errors.Is(wrappedSchema, harness.ErrCorrupt) { - t.Error("IncompatibleSchemaError cross-classifies as ErrCorrupt") - } + testassert.Check(t, !errors.Is(wrappedSchema, harness.ErrCorrupt), "%v", "IncompatibleSchemaError cross-classifies as ErrCorrupt") } // TestErrorSentinels proves all six outcome sentinels exist and stay pairwise distinct, so no implementation can alias two outcome classes. @@ -91,13 +72,9 @@ func TestErrorSentinels(t *testing.T) { harness.ErrStorage, } for i, s := range sentinels { - if s == nil { - t.Fatalf("sentinel %d is nil", i) - } + testassert.Require(t, s != nil, "sentinel %d is nil", i) for _, other := range sentinels[i+1:] { - if errors.Is(s, other) || errors.Is(other, s) { - t.Errorf("sentinels %q and %q alias each other", s, other) - } + testassert.Check(t, !errors.Is(s, other) && !errors.Is(other, s), "sentinels %q and %q alias each other", s, other) } } } @@ -152,7 +129,5 @@ var ( func TestInterfaceShape(t *testing.T) { var storage harness.Storage = shapeStorage{} var txn harness.Transaction = shapeTransaction{} - if storage == nil || txn == nil { - t.Fatal("interface assignments lost their implementations") - } + testassert.Require(t, storage != nil && txn != nil, "%v", "interface assignments lost their implementations") } diff --git a/harness/sweep_session_test.go b/harness/sweep_session_test.go index d9681f56..d33322f8 100644 --- a/harness/sweep_session_test.go +++ b/harness/sweep_session_test.go @@ -9,51 +9,31 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) -// TestPublicSweepSessionSkipsBusyCandidate proves the one per-Session sweep -// transition: a running candidate takes the no-transition outcome without -// waiting on the Session's work; an unknown -// identity reports the not-found class; a zero time is invalid input; and an -// eligible archived candidate transitions inside the call. -func TestPublicSweepSessionSkipsBusyCandidate(t *testing.T) { +// TestPublicSweepSessionRejectsUnknownOrZeroTime proves the per-Session sweep +// transition's remaining rejection rows: an unknown identity reports the +// not-found class and a zero time is invalid input. The busy candidate's +// no-transition outcome is observed with its stronger register and buffer +// evidence by TestPublicSweepRunningLeftUnchanged. +func TestPublicSweepSessionRejectsUnknownOrZeroTime(t *testing.T) { eachStore(t, func(t *testing.T, store harness.Storage) { - script := newScriptModel() - script.gate = make(chan struct{}) - f := newPublicFixture(t, store, script, nil) + f := newPublicFixture(t, store, newScriptModel(), nil) defer f.close() ctx := context.Background() session := createSession(t, f.h) first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } - - if _, err := submit(t, f.h, session, "op-1", harness.MessageModeRegular, "hello"); err != nil { - t.Fatalf("submit: %v", err) - } - <-script.arrived // parked at the model boundary: the Session is running + testassert.NoError(t, err) policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour, DeleteAfterArchive: 12 * time.Hour} - deleted, err := f.h.SweepSession(ctx, session, policy, first.LastActivity.Add(100*time.Hour)) - if err != nil || deleted { - t.Fatalf("SweepSession of a running candidate = (%v, %v), want the no-transition outcome", deleted, err) - } - still, err := f.h.ReadSessionHeader(ctx, session) - if err != nil || still.Lifecycle != harness.LifecycleOpen || still.CurrentOperationID != "op-1" { - t.Fatalf("SweepSession touched the running candidate: %+v err %v", still, err) + { + _, err := f.h.SweepSession(ctx, "0123456789abcdef0123456789abcdef", policy, first.LastActivity.Add(100*time.Hour)) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "SweepSession of an unknown identity = %v, want harness.ErrNotFound", err) } - - if _, err := f.h.SweepSession(ctx, "0123456789abcdef0123456789abcdef", policy, first.LastActivity.Add(100*time.Hour)); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("SweepSession of an unknown identity = %v, want harness.ErrNotFound", err) - } - if _, err := f.h.SweepSession(ctx, session, policy, time.Time{}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("SweepSession with a zero time = %v, want harness.ErrInvalid", err) - } - - script.releaseGate() - if err := converge(t, f); err != nil { - t.Fatalf("Wait: %v", err) + { + _, err := f.h.SweepSession(ctx, session, policy, time.Time{}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "SweepSession with a zero time = %v, want harness.ErrInvalid", err) } }) } @@ -70,20 +50,18 @@ func TestPublicSweepSessionDeletesEligibleCandidate(t *testing.T) { ctx := context.Background() session := createSession(t, f.h) first, err := f.h.ReadSessionHeader(ctx, session) - if err != nil { - t.Fatalf("read created session: %v", err) - } + testassert.NoError(t, err) policy := harness.SweepPolicy{ArchiveAfter: 24 * time.Hour, DeleteAfterArchive: 12 * time.Hour} - if _, err := f.h.SweepSession(ctx, session, policy, first.LastActivity.Add(100*time.Hour)); err != nil { - t.Fatalf("archive SweepSession: %v", err) + { + _, err := f.h.SweepSession(ctx, session, policy, first.LastActivity.Add(100*time.Hour)) + testassert.NoError(t, err) } deleted, err := f.h.SweepSession(ctx, session, policy, first.LastActivity.Add(200*time.Hour)) - if err != nil || !deleted { - t.Fatalf("delete SweepSession = (%v, %v), want the committed deletion", deleted, err) - } - if _, err := f.h.ReadSessionHeader(ctx, session); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read after SweepSession deletion = %v, want harness.ErrNotFound", err) + testassert.Require(t, err == nil && deleted, "delete SweepSession = (%v, %v), want the committed deletion", deleted, err) + { + _, err := f.h.ReadSessionHeader(ctx, session) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read after SweepSession deletion = %v, want harness.ErrNotFound", err) } }) } diff --git a/harness/tokens.go b/harness/tokens.go deleted file mode 100644 index b939cf4d..00000000 --- a/harness/tokens.go +++ /dev/null @@ -1,106 +0,0 @@ -package harness - -import ( - _ "embed" - "encoding/base64" - "strconv" - "strings" - "sync" - - "github.com/MMinasyan/lightcode/model" - "github.com/pkoukk/tiktoken-go" -) - -// cl100kBaseData is the bundled cl100k_base mergeable-rank table; see -// data/LICENSE for its notice. -// -//go:embed data/cl100k_base.tiktoken -var cl100kBaseData []byte - -// localEncoding memoizes the cl100k_base construction over the bundled data. -// The data is fixed at build time, so a failed construction cannot repair -// itself during a process lifetime: success and failure are both latched. -var localEncoding = sync.OnceValues(func() (*tiktoken.Tiktoken, error) { - return newLocalEncoding(cl100kBaseData) -}) - -// newLocalEncoding constructs the pinned tiktoken-go cl100k_base encoding from -// one rank table: every nonempty line is split into its base64 token and -// decimal rank, decoding and conversion failures are returned as they come, -// and the pinned library's construction rejects an inconsistent table. The -// bundled digest-pinned asset is the only production input, so no further -// table validation is imposed. -func newLocalEncoding(data []byte) (*tiktoken.Tiktoken, error) { - ranks := make(map[string]int) - for _, line := range strings.Split(string(data), "\n") { - if line == "" { - continue - } - tokenText, rankText, _ := strings.Cut(line, " ") - token, err := base64.StdEncoding.DecodeString(tokenText) - if err != nil { - return nil, err - } - rank, err := strconv.Atoi(rankText) - if err != nil { - return nil, err - } - ranks[string(token)] = rank - } - encoding := &tiktoken.Encoding{ - Name: tiktoken.MODEL_CL100K_BASE, - PatStr: `(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+`, - MergeableRanks: ranks, - SpecialTokens: map[string]int{ - tiktoken.ENDOFTEXT: 100257, - tiktoken.FIM_PREFIX: 100258, - tiktoken.FIM_MIDDLE: 100259, - tiktoken.FIM_SUFFIX: 100260, - tiktoken.ENDOFPROMPT: 100276, - }, - } - bpe, err := tiktoken.NewCoreBPE(encoding.MergeableRanks, encoding.SpecialTokens, encoding.PatStr) - if err != nil { - return nil, err - } - specialTokensSet := make(map[string]any, len(encoding.SpecialTokens)) - for token := range encoding.SpecialTokens { - specialTokensSet[token] = true - } - return tiktoken.NewTiktoken(bpe, encoding, specialTokensSet), nil -} - -// estimateTokensWithEncoding owns the token arithmetic over one encoder: per -// message, the message's text content plus each tool call's name and raw -// arguments, plus 4; the system prompt is estimated as plain text. A nil -// encoder selects the characters/3 fallback over the same accumulated text. -func estimateTokensWithEncoding(systemPrompt string, messages []model.Message, enc *tiktoken.Tiktoken) int { - if enc == nil { - chars := len(systemPrompt) - for _, m := range messages { - chars += len(m.TextContent()) - for _, tc := range m.ToolCalls { - chars += len(tc.Name) + len(tc.Arguments) - } - } - return chars / 3 - } - total := len(enc.Encode(systemPrompt, nil, nil)) - for _, m := range messages { - total += len(enc.Encode(m.TextContent(), nil, nil)) - for _, tc := range m.ToolCalls { - total += len(enc.Encode(tc.Name, nil, nil)) - total += len(enc.Encode(string(tc.Arguments), nil, nil)) - } - total += 4 // per-message overhead (role, separators) - } - return total -} - -// estimateTokens estimates the token count of the assembled context with the -// local cl100k_base encoding; a construction failure yields a nil encoder, -// which selects the characters/3 fallback. -func estimateTokens(systemPrompt string, messages []model.Message) int { - enc, _ := localEncoding() - return estimateTokensWithEncoding(systemPrompt, messages, enc) -} diff --git a/harness/tokens_test.go b/harness/tokens_test.go deleted file mode 100644 index 1ce7a6ed..00000000 --- a/harness/tokens_test.go +++ /dev/null @@ -1,333 +0,0 @@ -package harness - -import ( - "context" - "crypto/sha256" - "encoding/base64" - "errors" - "fmt" - "net/http" - "os" - "os/exec" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/MMinasyan/lightcode/model" - "github.com/pkoukk/tiktoken-go" -) - -// tokenizerColdChildEnv switches this test binary into the child mode of the -// cold tokenizer regression. -const tokenizerColdChildEnv = "LIGHTCODE_COLD_TOKENIZER_CHILD" - -// tokenizerColdChildDone is the child's completion marker: the parent requires -// it so a child that ran no assertion cannot pass by exiting zero. -const tokenizerColdChildDone = "cold tokenizer child: done" - -// refusedTransport fails every attempt after recording it, so the child can -// prove that no tokenizer acquisition escaped to an external transport. -type refusedTransport struct{ attempts atomic.Int64 } - -func (f *refusedTransport) RoundTrip(*http.Request) (*http.Response, error) { - f.attempts.Add(1) - return nil, errors.New("external transport is unavailable in this process") -} - -// TestTokenizerColdChildNoAcquisitionIO runs one real Operation, a mid-flight -// Interrupt and a Wait in a fresh child process with an empty tokenizer cache -// environment and a transport that refuses (and records) every attempt. All -// three must finish without contacting that transport. -func TestTokenizerColdChildNoAcquisitionIO(t *testing.T) { - if os.Getenv(tokenizerColdChildEnv) == "1" { - runTokenizerColdChild(t) - return - } - cmd := exec.Command(os.Args[0], "-test.run=^"+t.Name()+"$", "-test.timeout=120s") - cmd.Env = append(tokenizerFreeEnv(), tokenizerColdChildEnv+"=1", "TIKTOKEN_CACHE_DIR="+t.TempDir()) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("cold tokenizer child: %v\n%s", err, out) - } - if !strings.Contains(string(out), tokenizerColdChildDone) { - t.Fatalf("the cold child ran no completed work:\n%s", out) - } -} - -// tokenizerFreeEnv is the parent environment with the tokenizer cache -// variables removed: the child's acquisition sees an empty cache directory, -// never a prewarmed developer copy. -func tokenizerFreeEnv() []string { - env := os.Environ() - kept := env[:0] - for _, entry := range env { - if strings.HasPrefix(entry, "TIKTOKEN_CACHE_DIR=") || strings.HasPrefix(entry, "DATA_GYM_CACHE_DIR=") { - continue - } - kept = append(kept, entry) - } - return kept -} - -// runTokenizerColdChild is the child body: a completed Operation, a parked -// Operation interrupted mid-flight, and the canceled Harness's Wait join. -func runTokenizerColdChild(t *testing.T) { - transport := &refusedTransport{} - http.DefaultTransport = transport - - store := emptyStore(t) - started := make(chan struct{}) - var requests atomic.Int64 - modelFn := func(ctx context.Context, _ model.Request) (model.Stream, error) { - if requests.Add(1) == 1 { - return completedTurnStream(), nil - } - close(started) // the second Operation parks inside its model attempt - <-ctx.Done() - return nil, ctx.Err() - } - h, cancel := newCancelableHarness(t, store, modelPrepared(modelFn), nil) - session := createSession(t, h) - watch := watchSettlements(store) - - if _, err := submitText(t, h, session, "op-1", MessageModeRegular, "hello"); err != nil { - t.Fatalf("first submit: %v", err) - } - watch.next() - if rec := settledOperation(t, store, session, "op-1"); rec.State.Status != OperationSuccess { - t.Fatalf("op-1 status = %s, want success", rec.State.Status) - } - if _, err := submitText(t, h, session, "op-2", MessageModeRegular, "park"); err != nil { - t.Fatalf("second submit: %v", err) - } - <-started - if err := h.Interrupt(context.Background(), session); err != nil { - t.Fatalf("Interrupt: %v", err) - } - watch.next() - if rec := settledOperation(t, store, session, "op-2"); rec.State.Status != OperationInterruption { - t.Fatalf("op-2 status = %s, want interruption", rec.State.Status) - } - cancel() - waitCtx, waitCancel := context.WithTimeout(context.Background(), 30*time.Second) - defer waitCancel() - if err := h.Wait(waitCtx); err != nil { - t.Fatalf("Wait: %v", err) - } - if attempts := transport.attempts.Load(); attempts != 0 { - t.Fatalf("the cold child contacted the external transport %d time(s)", attempts) - } - fmt.Println(tokenizerColdChildDone) -} - -// tokenizerVectors pins the cl100k_base token count of each fixture text over -// the bundled rank data. They are the oracle for the arithmetic below, and the -// multibyte rows are the encoding's UTF-8 vectors. -var tokenizerVectors = map[string]int{ - "You are a helpful assistant.": 6, - "hello world": 2, - "hi there": 2, - "hi": 1, - "You are Lightcode, a coding agent.": 9, - "calling": 1, - "read_file": 2, - `{"path":"a.go"}`: 6, - "edit": 1, - `{"x":1}`: 5, - "Привет, мир!": 7, - "你好世界": 5, - "🙂 emoji": 3, - "héllo wörld": 6, - "日本語のテキスト": 8, - "hello Привет 🙂 世界": 9, -} - -// pinnedTokens returns one fixture text's pinned token count. -func pinnedTokens(t *testing.T, text string) int { - t.Helper() - tokens, ok := tokenizerVectors[text] - if !ok { - t.Fatalf("no pinned token vector for %q", text) - } - return tokens -} - -// TestLocalEncodingAsset pins the provenance of the bundled rank table: its -// size and SHA-256 digest are the accepted copy's. -func TestLocalEncodingAsset(t *testing.T) { - const wantDigest = "223921b76ee99bde995b7ff738513eef100fb51d18c93597a113bcffe865b2a7" - const wantSize = 1681126 - if got := fmt.Sprintf("%x", sha256.Sum256(cl100kBaseData)); got != wantDigest { - t.Fatalf("bundled cl100k_base digest = %s, want %s", got, wantDigest) - } - if got := len(cl100kBaseData); got != wantSize { - t.Fatalf("bundled cl100k_base size = %d, want %d", got, wantSize) - } -} - -// TestLocalEncodingVectors pins the bundled encoding itself, including its -// UTF-8 vectors: every fixture text must encode to exactly its pinned count. -func TestLocalEncodingVectors(t *testing.T) { - enc, err := localEncoding() - if err != nil { - t.Fatalf("localEncoding: %v", err) - } - for text, want := range tokenizerVectors { - if got := len(enc.Encode(text, nil, nil)); got != want { - t.Errorf("cl100k_base tokens of %q = %d, want %d", text, got, want) - } - } -} - -// TestNewLocalEncodingDuplicateRankIsConstructionError pins the construction -// contract's one rejection row: a rank table whose tokens carry duplicate rank -// identities is rejected by the pinned library's construction (its reverse-map -// size check), and the memoized construction failure selects the characters/3 -// fallback on the next estimate. -func TestNewLocalEncodingDuplicateRankIsConstructionError(t *testing.T) { - tokenA := base64.StdEncoding.EncodeToString([]byte("ab")) - tokenB := base64.StdEncoding.EncodeToString([]byte("cd")) - table := []byte(tokenA + " 0\n" + tokenB + " 0\n") - if _, err := newLocalEncoding(table); err == nil { - t.Fatal("duplicate rank identities: construction succeeded, want the library's rejection") - } - - old := localEncoding - localEncoding = sync.OnceValues(func() (*tiktoken.Tiktoken, error) { return newLocalEncoding(table) }) - t.Cleanup(func() { localEncoding = old }) - // The latched failure is the fallback's input: 6 characters / 3. - if got := estimateTokens("abcdef", nil); got != 2 { - t.Fatalf("estimateTokens over the failed construction = %d, want the characters/3 fallback 2", got) - } -} - -// mustEstimateMessage validates the fixture message into its owned form. -func mustEstimateMessage(t *testing.T, in model.Message) model.Message { - t.Helper() - msg, err := model.NewMessage(in) - if err != nil { - t.Fatalf("NewMessage: %v", err) - } - return msg -} - -// TestEstimateTokensTextOnlyMessages pins the rule over text-only messages: -// per message, the encoded text content (all text parts joined without -// separators) plus the flat +4. -func TestEstimateTokensTextOnlyMessages(t *testing.T) { - messages := []model.Message{ - mustEstimateMessage(t, model.Message{ - Role: model.RoleSystem, - Content: []model.ContentPart{{Kind: model.PartText, Text: "You are a helpful assistant."}}, - }), - mustEstimateMessage(t, model.Message{ - Role: model.RoleUser, - Content: []model.ContentPart{ - {Kind: model.PartText, Text: "hello "}, - {Kind: model.PartText, Text: "world"}, - }, - }), - mustEstimateMessage(t, model.Message{ - Role: model.RoleAssistant, - Source: model.ModelRef{Provider: "p", Model: "m"}, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hi there"}}, - }), - } - want := pinnedTokens(t, "You are a helpful assistant.") + pinnedTokens(t, "hello world") + pinnedTokens(t, "hi there") - want += 4 * len(messages) - if got := estimateTokens("", messages); got != want { - t.Fatalf("estimateTokens = %d, want %d", got, want) - } -} - -// TestEstimateTokensToolCalls pins the rule for assistant tool calls: each -// call contributes its name and raw argument bytes as encoded text. -func TestEstimateTokensToolCalls(t *testing.T) { - messages := []model.Message{ - mustEstimateMessage(t, model.Message{ - Role: model.RoleAssistant, - Source: model.ModelRef{Provider: "p", Model: "m"}, - Content: []model.ContentPart{{Kind: model.PartText, Text: "calling"}}, - ToolCalls: []model.ToolCall{ - {ID: "c1", Name: "read_file", Arguments: []byte(`{"path":"a.go"}`)}, - {ID: "c2", Name: "edit", Arguments: []byte(`{"x":1}`)}, - }, - }), - } - want := pinnedTokens(t, "calling") - for _, call := range []struct{ name, args string }{ - {"read_file", `{"path":"a.go"}`}, - {"edit", `{"x":1}`}, - } { - want += pinnedTokens(t, call.name) - want += pinnedTokens(t, call.args) - } - want += 4 * len(messages) - if got := estimateTokens("", messages); got != want { - t.Fatalf("estimateTokens = %d, want %d", got, want) - } -} - -// TestEstimateTokensFlatOverheadPerMessage pins the flat +4 per message: an -// empty conversation estimates zero, and each empty-text message adds exactly -// four. -func TestEstimateTokensFlatOverheadPerMessage(t *testing.T) { - if got := estimateTokens("", nil); got != 0 { - t.Fatalf("estimateTokens of no messages = %d, want 0", got) - } - one := []model.Message{mustEstimateMessage(t, model.Message{Role: model.RoleUser})} - if got := estimateTokens("", one); got != 4 { - t.Fatalf("estimateTokens of one empty message = %d, want 4", got) - } - two := append(one, mustEstimateMessage(t, model.Message{Role: model.RoleUser})) - if got := estimateTokens("", two); got != 8 { - t.Fatalf("estimateTokens of two empty messages = %d, want 8", got) - } -} - -// TestEstimateTokensSystemPromptPlainText pins the system-prompt rule: the -// prompt is estimated as plain text with no per-message overhead, once, on -// top of the messages. -func TestEstimateTokensSystemPromptPlainText(t *testing.T) { - prompt := "You are Lightcode, a coding agent." - if got, want := estimateTokens(prompt, nil), pinnedTokens(t, prompt); got != want { - t.Fatalf("estimateTokens(prompt, nil) = %d, want %d", got, want) - } - messages := []model.Message{mustEstimateMessage(t, model.Message{ - Role: model.RoleUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hi"}}, - })} - want := pinnedTokens(t, prompt) + pinnedTokens(t, "hi") + 4 - if got := estimateTokens(prompt, messages); got != want { - t.Fatalf("estimateTokens(prompt, messages) = %d, want %d", got, want) - } -} - -// TestEstimateTokensCharsFallback pins the fallback a nil encoder selects: the -// same accumulated text (system prompt, message texts, tool call names and -// arguments) divided by three. -func TestEstimateTokensCharsFallback(t *testing.T) { - prompt := "abcdef" // 6 chars - messages := []model.Message{ - mustEstimateMessage(t, model.Message{ - Role: model.RoleUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "gh"}}, - }), - mustEstimateMessage(t, model.Message{ - Role: model.RoleAssistant, - Source: model.ModelRef{Provider: "p", Model: "m"}, - ToolCalls: []model.ToolCall{ - {ID: "c1", Name: "f", Arguments: []byte(`{}`)}, - }, - }), - } - // 6 + 2 + 1 + 2 = 11 chars -> 11/3 = 3 - if got := estimateTokensWithEncoding(prompt, messages, nil); got != 3 { - t.Fatalf("fallback estimateTokens = %d, want 3", got) - } - if got := estimateTokensWithEncoding("", nil, nil); got != 0 { - t.Fatalf("fallback estimateTokens of no messages = %d, want 0", got) - } -} diff --git a/internal/acpclient/mounted_test.go b/internal/acpclient/mounted_test.go index b7e7994f..dc82986c 100644 --- a/internal/acpclient/mounted_test.go +++ b/internal/acpclient/mounted_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/clientops" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" ) @@ -32,11 +33,10 @@ const mountedAgentsDocument = `{"integrated":{"model":"prov/m","system_prompt":" func writeServiceFile(t *testing.T, path, content string) { t.Helper() - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatalf("create config directory: %v", err) - } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatalf("write %s: %v", path, err) + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + { + err := os.WriteFile(path, []byte(content), 0o600) + testassert.Require(t, err == nil, "write %s: %v", path, err) } } @@ -46,9 +46,7 @@ func writeServiceFile(t *testing.T, path, content string) { func isolateBundledCredentials(t *testing.T) { t.Helper() entries, err := os.ReadDir(filepath.Join("..", "..", "internal", "catalog", "builtin")) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue @@ -83,12 +81,11 @@ func newMountedRuntime(t *testing.T, baseURL string) (*runtime.Runtime, string, writeServiceFile(t, configPath, fmt.Sprintf(mountedConfigDocument, baseURL)) writeServiceFile(t, agents.PathForConfig(configPath), mountedAgentsDocument) r, err := runtime.Open(context.Background(), runtime.Options{DataDir: dataDir, ConfigPath: configPath, Plugins: builtin.Plugins()}) - if err != nil { - t.Fatalf("runtime.Open: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("runtime.Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "runtime.Close: %v", err) } }) return r, dataDir, configPath @@ -162,27 +159,19 @@ func TestACPMountedRuntimeDepartureKeepsAcceptedWork(t *testing.T) { model := newParkedModel(t) owner, dataDir, configPath := newMountedRuntime(t, model.URL) ps, err := owner.OpenProtocol(context.Background()) - if err != nil { - t.Fatalf("OpenProtocol: %v", err) - } + testassert.NoError(t, err) record := filepath.Join(dataDir, "runtime.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(record)) workspace := t.TempDir() direct, err := client.Connect(context.Background(), record) - if err != nil { - t.Fatalf("direct connect: %v", err) - } + testassert.NoError(t, err) b := startMountedBridge(t, dataDir, configPath, workspace) initialized := b.call(t, "1", "initialize", "") var description struct { Runtime protocol.Health `json:"runtime"` } - if err := json.Unmarshal([]byte(resultOf(t, initialized)), &description); err != nil { - t.Fatalf("initialize result: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(resultOf(t, initialized)), &description)) if description.Runtime.InstanceId != direct.InstanceID() { t.Fatalf("initialize runtime instance = %q, want the mounted owner %q", description.Runtime.InstanceId, direct.InstanceID()) } @@ -203,18 +192,12 @@ func TestACPMountedRuntimeDepartureKeepsAcceptedWork(t *testing.T) { // The host departs while its accepted work is parked in the model. b.eof() - if err := b.awaitRun(t); err != nil { - t.Fatalf("Run after EOF = %v, want nil", err) - } - if model.wasCanceled() { - t.Fatal("the departing bridge canceled the accepted model work") - } + testassert.NoError(t, b.awaitRun(t)) + testassert.Require(t, !model.wasCanceled(), "the departing bridge canceled the accepted model work") model.unblock() awaitOperationSuccess(t, direct, session.SessionID, "op-depart") - if model.wasCanceled() { - t.Fatal("the accepted model work was canceled during settlement") - } + testassert.Require(t, !model.wasCanceled(), "the accepted model work was canceled during settlement") } // awaitOperationSuccess polls the real owner's hydration until the operation @@ -225,21 +208,15 @@ func awaitOperationSuccess(t *testing.T, c *client.Client, sessionID, operationI deadline := time.Now().Add(30 * time.Second) for { raw, err := clientops.Call(context.Background(), c, "GetSessionHydration", params) - if err != nil { - t.Fatalf("GetSessionHydration: %v", err) - } + testassert.NoError(t, err) var hydration protocol.Hydration - if err := json.Unmarshal(raw, &hydration); err != nil { - t.Fatalf("hydration: %v", err) - } + testassert.NoError(t, json.Unmarshal(raw, &hydration)) for _, operation := range hydration.Operations { if operation.OperationId == operationID && operation.Status == protocol.OperationStatusSuccess { return } } - if time.Now().After(deadline) { - t.Fatalf("operation %s never reached success: %s", operationID, raw) - } + testassert.Require(t, !time.Now().After(deadline), "operation %s never reached success: %s", operationID, raw) time.Sleep(10 * time.Millisecond) } } diff --git a/internal/acpclient/repair_test.go b/internal/acpclient/repair_test.go index f8580901..44878721 100644 --- a/internal/acpclient/repair_test.go +++ b/internal/acpclient/repair_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/clienthost" + "github.com/MMinasyan/lightcode/internal/testassert" ) // The in-package frame fixtures drive the real Runner helpers and queues: @@ -268,9 +269,7 @@ func (f *frameFixture) writeRecord(t *testing.T, dataDir string) { t.Helper() record := fmt.Sprintf(`{"endpoint":%q,"instance_id":%q,"protocol_version":"2","credential":%q}`, f.server.URL, f.instance, frameCredential) - if err := os.WriteFile(filepath.Join(dataDir, "runtime.json"), []byte(record), 0o600); err != nil { - t.Fatalf("write discovery record: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dataDir, "runtime.json"), []byte(record), 0o600)) } // newFrameRunner builds one Runner over this fixture's discovery record. @@ -421,14 +420,10 @@ func TestNotificationFramesFollowTheCurrentConnection(t *testing.T) { writer := startParkedWriter(t, r, ctx) first, err := r.establish(ctx) - if err != nil { - t.Fatalf("establish: %v", err) - } + testassert.NoError(t, err) r.retire(first) r.pumps.Wait() - if r.currentConnection() != nil { - t.Fatal("the retired connection generation repaired itself") - } + testassert.Require(t, r.currentConnection() == nil, "the retired connection generation repaired itself") markerFixture := f markerOpen := 2 if tc.newInstance { @@ -438,12 +433,8 @@ func TestNotificationFramesFollowTheCurrentConnection(t *testing.T) { markerOpen = 1 } second, err := r.establish(ctx) - if err != nil { - t.Fatalf("replacement establish: %v", err) - } - if second == first { - t.Fatal("the replacement connection is the retired one") - } + testassert.NoError(t, err) + testassert.Require(t, second != first, "the replacement connection is the retired one") markerFixture.marker(markerOpen) <- eventDocument(markerFixture.instance, "7") writer.release() writer.awaitContains(t, `"durable_revision":"7"`) @@ -481,16 +472,13 @@ func TestRepairSnapshotFramesFollowTheCurrentSelection(t *testing.T) { r, f, writer, ctx, cancel := setup(t) defer cancel() first, err := r.establish(ctx) - if err != nil { - t.Fatalf("establish: %v", err) - } + testassert.NoError(t, err) r.selection = selection{id: "s1", generation: 1} r.repair(first) - if r.currentConnection() == nil { - t.Fatal("repair left no connection") - } - if _, rpcErr := r.workspaceSelect(json.RawMessage(`{"workspace":"/other"}`)); rpcErr != nil { - t.Fatalf("workspace/select: %v", rpcErr) + testassert.Require(t, r.currentConnection() != nil, "repair left no connection") + { + _, rpcErr := r.workspaceSelect(json.RawMessage(`{"workspace":"/other"}`)) + testassert.Require(t, rpcErr == nil, "workspace/select: %v", rpcErr) } f.marker(2) <- eventDocument(f.instance, "7") writer.release() @@ -504,9 +492,7 @@ func TestRepairSnapshotFramesFollowTheCurrentSelection(t *testing.T) { r, f, writer, ctx, cancel := setup(t) defer cancel() first, err := r.establish(ctx) - if err != nil { - t.Fatalf("establish: %v", err) - } + testassert.NoError(t, err) r.selection = selection{id: "s1", generation: 1} r.repair(first) f.marker(2) <- eventDocument(f.instance, "7") @@ -524,15 +510,11 @@ func TestRepairSnapshotFramesFollowTheCurrentSelection(t *testing.T) { r, f, writer, ctx, cancel := setup(t) defer cancel() first, err := r.establish(ctx) - if err != nil { - t.Fatalf("establish: %v", err) - } + testassert.NoError(t, err) r.selection = selection{id: "s1", generation: 1} r.repair(first) second := r.currentConnection() - if second == nil { - t.Fatal("the first repair left no connection") - } + testassert.Require(t, second != nil, "the first repair left no connection") r.selection = selection{id: "s2", generation: 2} r.repair(second) f.marker(3) <- eventDocument(f.instance, "7") @@ -587,9 +569,7 @@ func TestRetiredConnectionCancelsItsInFlightCall(t *testing.T) { defer cancel() r.ctx = ctx conn, err := r.establish(ctx) - if err != nil { - t.Fatalf("establish: %v", err) - } + testassert.NoError(t, err) handled := make(chan struct{}) go func() { @@ -611,34 +591,18 @@ func TestRetiredConnectionCancelsItsInFlightCall(t *testing.T) { var frame response select { case data := <-r.responses: - if err := json.Unmarshal(data, &frame); err != nil { - t.Fatalf("response frame: %v", err) - } + testassert.NoError(t, json.Unmarshal(data, &frame)) case <-time.After(15 * time.Second): t.Fatal("the canceled call produced no response") } - if string(frame.ID) != tc.id { - t.Fatalf("response ID = %s, want the original request ID %s", frame.ID, tc.id) - } - if frame.Error == nil { - t.Fatalf("frame = %+v, want the canceled call's transport error", frame) - } - if frame.Error.Code != codeTransport { - t.Fatalf("error code = %d, want %d", frame.Error.Code, codeTransport) - } - if !strings.Contains(frame.Error.Message, "context canceled") { - t.Fatalf("error message = %q, want the real transport cancellation", frame.Error.Message) - } - if len(frame.Result) != 0 { - t.Fatalf("frame carries a result beside the error: %s", frame.Result) - } - if got := r.selectionSnapshot(); got.id != "" { - t.Fatalf("selection = %+v, want no adoption from a cancelled call", got) + testassert.Require(t, string(frame.ID) == tc.id && frame.Error != nil, "response ID = %s, want the original request ID %s; frame = %+v, want the canceled call's transport error", frame.ID, tc.id, frame) + testassert.Require(t, frame.Error.Code == codeTransport && strings.Contains(frame.Error.Message, "context canceled") && len(frame.Result) == 0, "got %v, want %v; error message = %q, want the real transport cancellation; frame carries a result beside the error: %s", frame.Error.Code, codeTransport, frame.Error.Message, frame.Result) + { + got := r.selectionSnapshot() + testassert.Require(t, got.id == "", "selection = %+v, want no adoption from a cancelled call", got) } for _, path := range f.paths() { - if strings.Contains(path, "stop") || strings.Contains(path, "interrupt") || strings.Contains(path, "shutdown") { - t.Fatalf("cancellation issued owner control %s", path) - } + testassert.Require(t, !strings.Contains(path, "stop") && !strings.Contains(path, "interrupt") && !strings.Contains(path, "shutdown"), "cancellation issued owner control %s", path) } }) } @@ -658,9 +622,7 @@ func TestCompletedResponseSurvivesRetirement(t *testing.T) { r.out = writer go r.writeLoop(ctx) conn, err := r.establish(ctx) - if err != nil { - t.Fatalf("establish: %v", err) - } + testassert.NoError(t, err) handled := make(chan struct{}) go func() { @@ -678,24 +640,19 @@ func TestCompletedResponseSurvivesRetirement(t *testing.T) { t.Fatal("the completed addressed call never returned") } stamp := r.selectionSnapshot() - if stamp.id != "s1" { - t.Fatalf("selection = %+v, want the completed request's adoption", stamp) - } + testassert.Require(t, stamp.id == "s1", "selection = %+v, want the completed request's adoption", stamp) // Retirement after the completed response: the local adoption rejects the // retired connection and adopts the current one. r.retire(conn) replacement, err := r.establish(ctx) - if err != nil { - t.Fatalf("replacement establish: %v", err) - } + testassert.NoError(t, err) r.adoptSelection(conn, stamp, "s2", "/ws/s2") - if got := r.selectionSnapshot(); got != stamp { - t.Fatalf("selection = %+v, want the retired connection's adoption rejected", got) - } + testassert.Equal(t, r.selectionSnapshot(), stamp) r.adoptSelection(replacement, stamp, "s2", "/ws/s2") - if got := r.selectionSnapshot(); got.id != "s2" { - t.Fatalf("selection = %+v, want the current connection's adoption", got) + { + got := r.selectionSnapshot() + testassert.Require(t, got.id == "s2", "selection = %+v, want the current connection's adoption", got) } } @@ -708,18 +665,16 @@ func TestSelectionAdoptionFollowsTheCurrentSelection(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() r.ctx = ctx - if _, err := r.establish(ctx); err != nil { - t.Fatalf("establish: %v", err) + { + _, err := r.establish(ctx) + testassert.NoError(t, err) } result, rpcErr := r.sessionSelect(ctx, json.RawMessage(`{"session_id":"s1"}`)) - if rpcErr != nil { - t.Fatalf("session/select = %+v", rpcErr) - } - if !strings.Contains(string(result), `"workspace":"/ws/s1"`) { - t.Fatalf("session/select result = %s", result) - } - if got := r.selectionSnapshot(); got.id != "s1" { - t.Fatalf("selection = %+v, want the adopted explicit session", got) + testassert.Require(t, rpcErr == nil, "session/select = %+v", rpcErr) + testassert.Require(t, strings.Contains(string(result), `"workspace":"/ws/s1"`), "session/select result = %s", result) + { + got := r.selectionSnapshot() + testassert.Require(t, got.id == "s1", "selection = %+v, want the adopted explicit session", got) } } @@ -740,30 +695,22 @@ func TestEstablishInstallsOnlyAHealthyStream(t *testing.T) { defer cancel() r.ctx = ctx - if _, rpcErr := r.initialize(ctx, nil); rpcErr == nil || rpcErr.Code != codeTransport { - t.Fatalf("initialize = %+v, want the unavailable transport error", rpcErr) + { + _, rpcErr := r.initialize(ctx, nil) + testassert.Require(t, rpcErr != nil && rpcErr.Code == codeTransport, "initialize = %+v, want the unavailable transport error", rpcErr) } attempts := f.countPath("/v1/events") - if attempts == 0 { - t.Fatal("the failed establish never attempted the event stream") - } + testassert.Require(t, attempts != 0, "the failed establish never attempted the event stream") r.pumps.Wait() - if r.currentConnection() != nil { - t.Fatal("a known failed stream was installed as healthy") - } - if r.initialized { - t.Fatal("a failed stream marked the bridge initialized") - } - if frame := drainEventFrames(r); len(frame) != 0 { - t.Fatalf("a failed stream published %v", frame) - } - if got := f.countPath("/v1/events"); got != attempts { - t.Fatalf("event stream attempts grew from %d to %d without a loss", attempts, got) + testassert.Require(t, r.currentConnection() == nil, "a known failed stream was installed as healthy") + testassert.Require(t, !r.initialized, "a failed stream marked the bridge initialized") + { + frame := drainEventFrames(r) + testassert.Require(t, len(frame) == 0, "a failed stream published %v", frame) } + testassert.Equal(t, f.countPath("/v1/events"), attempts) for _, path := range f.paths() { - if strings.HasPrefix(path, "/v1/sessions") || strings.HasPrefix(path, "/v1/configuration") { - t.Fatalf("a failed stream performed %s", path) - } + testassert.Require(t, !strings.HasPrefix(path, "/v1/sessions") && !strings.HasPrefix(path, "/v1/configuration"), "a failed stream performed %s", path) } }) } @@ -778,19 +725,20 @@ func TestEstablishOpensTheStreamBeforeTheDescriptiveRead(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() r.ctx = ctx - if _, err := r.establish(ctx); err != nil { - t.Fatalf("establish: %v", err) + { + _, err := r.establish(ctx) + testassert.NoError(t, err) } want := []string{"/v1/health", "/v1/events", "/v1/health"} - if got := f.paths(); !slices.Equal(got, want) { - t.Fatalf("establish requests = %v, want the stream established before the descriptive read %v", got, want) - } - if _, rpcErr := r.initialize(ctx, nil); rpcErr != nil { - t.Fatalf("initialize: %+v", rpcErr) + { + got := f.paths() + testassert.Require(t, slices.Equal(got, want), "establish requests = %v, want the stream established before the descriptive read %v", got, want) } - if got := f.countPath("/v1/events"); got != 1 { - t.Fatalf("repeated initialize opened %d event streams, want one", got) + { + _, rpcErr := r.initialize(ctx, nil) + testassert.Require(t, rpcErr == nil, "initialize: %+v", rpcErr) } + testassert.Equal(t, f.countPath("/v1/events"), 1) } // TestFailedRepairLeavesNoConnection proves a lost established stream whose @@ -806,8 +754,9 @@ func TestFailedRepairLeavesNoConnection(t *testing.T) { defer cancel() r.ctx = ctx endFirst := f.end(1) - if _, rpcErr := r.initialize(ctx, nil); rpcErr != nil { - t.Fatalf("initialize: %+v", rpcErr) + { + _, rpcErr := r.initialize(ctx, nil) + testassert.Require(t, rpcErr == nil, "initialize: %+v", rpcErr) } f.awaitEventsOpen(t) drainEventFrames(r) // the initial connection's announcement @@ -817,54 +766,30 @@ func TestFailedRepairLeavesNoConnection(t *testing.T) { close(endFirst) f.awaitEventsOpen(t) // the failed reconnect's stream request // Stop any further startup attempt so quiescence is deterministic. - if err := os.Remove(filepath.Join(dataDir, "runtime.json")); err != nil { - t.Fatalf("remove discovery record: %v", err) - } + testassert.NoError(t, os.Remove(filepath.Join(dataDir, "runtime.json"))) r.pumps.Wait() - if r.currentConnection() != nil { - t.Fatal("a failed repair left a connection installed") - } - if !r.initialized { - t.Fatal("the established-once flag was lost with the connection") - } - if _, rpcErr := r.requireConnection(); rpcErr == nil { - t.Fatal("a lost connection was reported available") + testassert.Require(t, r.currentConnection() == nil, "a failed repair left a connection installed") + testassert.Require(t, r.initialized, "the established-once flag was lost with the connection") + { + _, rpcErr := r.requireConnection() + testassert.Require(t, rpcErr != nil, "a lost connection was reported available") } for _, frame := range drainEventFrames(r) { - if strings.Contains(frame, `"runtime/connected"`) || - strings.Contains(frame, `"runtime/resync_required"`) || - strings.Contains(frame, `"session/snapshot"`) { - t.Fatalf("a failed repair published %s", frame) - } - } - if got := f.countPath("/v1/events"); got != 2 { - t.Fatalf("event stream attempts = %d, want one loss and one failed reconnect", got) + testassert.Require(t, !strings.Contains(frame, `"runtime/connected"`) && !strings.Contains(frame, `"runtime/resync_required"`) && !strings.Contains(frame, `"session/snapshot"`), "a failed repair published %s", frame) } + testassert.Equal(t, f.countPath("/v1/events"), 2) f.setEventsFailure("") f.writeRecord(t, dataDir) - if _, rpcErr := r.initialize(ctx, nil); rpcErr != nil { - t.Fatalf("explicit initialize after recovery: %+v", rpcErr) - } - if r.currentConnection() == nil { - t.Fatal("explicit initialize left no connection") - } - if got := f.countPath("/v1/events"); got != 3 { - t.Fatalf("event streams = %d, want the recovery stream", got) + { + _, rpcErr := r.initialize(ctx, nil) + testassert.Require(t, rpcErr == nil, "explicit initialize after recovery: %+v", rpcErr) } + testassert.Require(t, r.currentConnection() != nil, "explicit initialize left no connection") + testassert.Equal(t, f.countPath("/v1/events"), 3) frames := drainEventFrames(r) - if !containsFrame(frames, `"runtime/connected"`) { - t.Fatalf("the recovered connection was not announced: %v", frames) - } - if !containsFrame(frames, `"runtime/resync_required"`) { - t.Fatalf("the recovery did not record the repair: %v", frames) - } - if !containsFrame(frames, `"workspace":"/ws/s1"`) { - t.Fatalf("the recovery re-snapshotted no retained selection: %v", frames) - } - if got := f.countPath("/v1/sessions/s1/hydration"); got != 1 { - t.Fatalf("snapshot hydration reads = %d, want one", got) - } + testassert.Require(t, containsFrame(frames, `"runtime/connected"`) && containsFrame(frames, `"runtime/resync_required"`) && containsFrame(frames, `"workspace":"/ws/s1"`), "the recovered connection was not announced: %v; the recovery did not record the repair: %v; the recovery re-snapshotted no retained selection: %v", frames, frames, frames) + testassert.Equal(t, f.countPath("/v1/sessions/s1/hydration"), 1) } // TestResponseAdmissionWaitsCancellably proves the actual response admission @@ -878,9 +803,7 @@ func TestResponseAdmissionWaitsCancellably(t *testing.T) { for i := 0; i < responseQueueSize; i++ { r.send(response{JSONRPC: jsonRPCVersion, ID: json.RawMessage(fmt.Sprintf("%d", i)), Result: json.RawMessage("null")}) } - if got := len(r.responses); got != responseQueueSize { - t.Fatalf("queue length = %d, want the full %d", got, responseQueueSize) - } + testassert.Equal(t, len(r.responses), responseQueueSize) started := make(chan struct{}) admitted := make(chan struct{}) go func() { @@ -895,9 +818,7 @@ func TestResponseAdmissionWaitsCancellably(t *testing.T) { case <-time.After(15 * time.Second): t.Fatal("a full response queue admission did not return on caller cancellation") } - if got := len(r.responses); got != responseQueueSize { - t.Fatalf("queue length = %d, want the queue unchanged after a cancelled admission", got) - } + testassert.Equal(t, len(r.responses), responseQueueSize) } // drainEventFrames empties the queued notification frames. diff --git a/internal/acpclient/runner_test.go b/internal/acpclient/runner_test.go index 7f30fead..cc774720 100644 --- a/internal/acpclient/runner_test.go +++ b/internal/acpclient/runner_test.go @@ -22,6 +22,7 @@ import ( "github.com/MMinasyan/lightcode/internal/acpclient" "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/clientops" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/version" ) @@ -54,7 +55,7 @@ func newFixture(t *testing.T, routes map[string]http.HandlerFunc) *fixture { f := &fixture{handlers: map[string]http.HandlerFunc{}, eventsOpen: make(chan struct{}, 16)} f.handlers["/v1/health"] = func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, `{"instance_id":%q,"protocol_version":"2"}`, testInstance) + fmt.Fprint(w, healthBody()) } f.handlers["/v1/events"] = f.idleEvents // Startup composition defaults: an empty open-root list and one created @@ -67,9 +68,7 @@ func newFixture(t *testing.T, routes map[string]http.HandlerFunc) *fixture { fmt.Fprint(w, acpclient.SessionHeader(startupSessionID, "/ws", "2026-10-01T00:00:00Z")) return } - if r.Method != http.MethodGet || r.URL.Query().Get("lifecycle") != "open" || r.URL.Query().Get("workspace") == "" { - t.Errorf("startup list = %s %s, want the selected Workspace's open lifecycle", r.Method, r.URL.RawQuery) - } + testassert.Check(t, r.Method == http.MethodGet && r.URL.Query().Get("lifecycle") == "open" && r.URL.Query().Get("workspace") != "", "startup list = %s %s, want the selected Workspace's open lifecycle", r.Method, r.URL.RawQuery) fmt.Fprint(w, "[]") } f.handlers["/v1/sessions/"+startupSessionID+"/hydration"] = jsonHandler(200, hydrationBody(startupSessionID, "/ws")) @@ -269,9 +268,7 @@ func startBridgeWith(t *testing.T, f *fixture, workspace string, writer io.Write dataDir := t.TempDir() record := fmt.Sprintf(`{"endpoint":%q,"instance_id":%q,"protocol_version":"2","credential":%q}`, f.server.URL, testInstance, testCredential) - if err := os.WriteFile(filepath.Join(dataDir, "runtime.json"), []byte(record), 0o600); err != nil { - t.Fatalf("write discovery record: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dataDir, "runtime.json"), []byte(record), 0o600)) return startRunner(t, clienthost.Options{ DataDir: dataDir, ConfigPath: filepath.Join(dataDir, "config.json"), @@ -318,8 +315,9 @@ func startRunner(t *testing.T, startup clienthost.Options, workspace string, wri func (b *bridge) send(t *testing.T, line string) { t.Helper() - if _, err := io.WriteString(b.in, line+"\n"); err != nil { - t.Fatalf("write input: %v", err) + { + _, err := io.WriteString(b.in, line+"\n") + testassert.NoError(t, err) } } @@ -342,8 +340,9 @@ func (b *bridge) recv(t *testing.T) map[string]json.RawMessage { t.Helper() line := b.recvLine(t) var frame map[string]json.RawMessage - if err := json.Unmarshal([]byte(line), &frame); err != nil { - t.Fatalf("output frame %q: %v", line, err) + { + err := json.Unmarshal([]byte(line), &frame) + testassert.Require(t, err == nil, "output frame %q: %v", line, err) } return frame } @@ -353,9 +352,7 @@ func (b *bridge) recvLine(t *testing.T) string { t.Helper() select { case line, ok := <-b.lines: - if !ok { - t.Fatal("bridge output ended") - } + testassert.Require(t, ok, "bridge output ended") return line case <-time.After(15 * time.Second): t.Fatal("timed out waiting for one output frame") @@ -380,12 +377,11 @@ func (b *bridge) collectFrames(t *testing.T, stop func(map[string]json.RawMessag for { select { case line, ok := <-b.lines: - if !ok { - t.Fatal("bridge output ended before the expected frame") - } + testassert.Require(t, ok, "bridge output ended before the expected frame") var frame map[string]json.RawMessage - if err := json.Unmarshal([]byte(line), &frame); err != nil { - t.Fatalf("output frame %q: %v", line, err) + { + err := json.Unmarshal([]byte(line), &frame) + testassert.Require(t, err == nil, "output frame %q: %v", line, err) } frames = append(frames, frame) if stop(frame) { @@ -462,16 +458,15 @@ func (b *bridge) call(t *testing.T, id, method, params string) map[string]json.R func errorFrame(t *testing.T, frame map[string]json.RawMessage) (int, string, json.RawMessage) { t.Helper() raw, ok := frame["error"] - if !ok { - t.Fatalf("frame carries no error: %s", frame) - } + testassert.Require(t, ok, "frame carries no error: %s", frame) var rpcErr struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data"` } - if err := json.Unmarshal(raw, &rpcErr); err != nil { - t.Fatalf("error frame %s: %v", raw, err) + { + err := json.Unmarshal(raw, &rpcErr) + testassert.Require(t, err == nil, "error frame %s: %v", raw, err) } return rpcErr.Code, rpcErr.Message, rpcErr.Data } @@ -485,9 +480,7 @@ func errorCode(t *testing.T, frame map[string]json.RawMessage) int { func resultOf(t *testing.T, frame map[string]json.RawMessage) string { t.Helper() raw, ok := frame["result"] - if !ok { - t.Fatalf("frame carries no result: %s", frame) - } + testassert.Require(t, ok, "frame carries no result: %s", frame) return string(raw) } @@ -517,39 +510,25 @@ func TestACPFraming(t *testing.T) { b.send(t, "not json") line := b.recvLine(t) want := `{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"parse error"}}` - if line != want { - t.Fatalf("frame = %s, want %s", line, want) - } + testassert.Equal(t, line, want) }) t.Run("invalid version", func(t *testing.T) { b.send(t, `{"jsonrpc":"1.0","id":7,"method":"workspace/current"}`) frame := b.recv(t) - if code := errorCode(t, frame); code != -32600 { - t.Fatalf("code = %d, want -32600", code) - } - if string(frame["id"]) != "7" { - t.Fatalf("id = %s, want 7", frame["id"]) - } + testassert.Equal(t, errorCode(t, frame), -32600) + testassert.Require(t, string(frame["id"]) == "7", "id = %s, want 7", frame["id"]) }) t.Run("missing method", func(t *testing.T) { b.send(t, `{"jsonrpc":"2.0","id":"a"}`) frame := b.recv(t) - if code := errorCode(t, frame); code != -32600 { - t.Fatalf("code = %d, want -32600", code) - } - if string(frame["id"]) != `"a"` { - t.Fatalf("id = %s, want \"a\"", frame["id"]) - } + testassert.Equal(t, errorCode(t, frame), -32600) + testassert.Require(t, string(frame["id"]) == `"a"`, "id = %s, want \"a\"", frame["id"]) }) t.Run("invalid id", func(t *testing.T) { b.send(t, `{"jsonrpc":"2.0","id":true,"method":"workspace/current"}`) frame := b.recv(t) - if code := errorCode(t, frame); code != -32600 { - t.Fatalf("code = %d, want -32600", code) - } - if string(frame["id"]) != "null" { - t.Fatalf("id = %s, want null", frame["id"]) - } + testassert.Equal(t, errorCode(t, frame), -32600) + testassert.Require(t, string(frame["id"]) == "null", "id = %s, want null", frame["id"]) }) t.Run("valid JSON with a non-object root", func(t *testing.T) { for _, row := range []struct{ name, line string }{ @@ -562,12 +541,8 @@ func TestACPFraming(t *testing.T) { t.Run(row.name, func(t *testing.T) { b.send(t, row.line) frame := b.recv(t) - if code := errorCode(t, frame); code != -32600 { - t.Fatalf("code = %d, want -32600 for valid JSON outside the request object shape", code) - } - if string(frame["id"]) != "null" { - t.Fatalf("id = %s, want null", frame["id"]) - } + testassert.Equal(t, errorCode(t, frame), -32600) + testassert.Require(t, string(frame["id"]) == "null", "id = %s, want null", frame["id"]) }) } }) @@ -582,12 +557,8 @@ func TestACPFraming(t *testing.T) { t.Run(row.name, func(t *testing.T) { b.send(t, row.line) frame := b.recv(t) - if code := errorCode(t, frame); code != -32600 { - t.Fatalf("code = %d, want -32600", code) - } - if string(frame["id"]) != row.id { - t.Fatalf("id = %s, want the available caller ID %s", frame["id"], row.id) - } + testassert.Equal(t, errorCode(t, frame), -32600) + testassert.Require(t, string(frame["id"]) == row.id, "id = %s, want the available caller ID %s", frame["id"], row.id) }) } }) @@ -595,67 +566,48 @@ func TestACPFraming(t *testing.T) { b.send(t, `{"jsonrpc":"1.0","method":"workspace/current"}`) b.send(t, requestLine("1", "workspace/current", "")) frame := b.recv(t) - if string(frame["id"]) != "null" { - t.Fatalf("first frame after an invalid notification-shaped envelope = %s, want the null-id invalid request", frame) - } - if code := errorCode(t, frame); code != -32600 { - t.Fatalf("code = %d, want -32600", code) - } - if marker := b.recv(t); string(marker["id"]) != "1" { - t.Fatalf("marker response = %s, want the drained caller request", marker) + testassert.Require(t, string(frame["id"]) == "null", "first frame after an invalid notification-shaped envelope = %s, want the null-id invalid request", frame) + testassert.Equal(t, errorCode(t, frame), -32600) + { + marker := b.recv(t) + testassert.Require(t, string(marker["id"]) == "1", "marker response = %s, want the drained caller request", marker) } }) t.Run("truncated JSON is a parse error", func(t *testing.T) { b.send(t, `{"jsonrpc":`) frame := b.recv(t) - if code := errorCode(t, frame); code != -32700 { - t.Fatalf("code = %d, want -32700 for invalid JSON", code) - } - if string(frame["id"]) != "null" { - t.Fatalf("id = %s, want null", frame["id"]) - } + testassert.Equal(t, errorCode(t, frame), -32700) + testassert.Require(t, string(frame["id"]) == "null", "id = %s, want null", frame["id"]) }) t.Run("unknown method", func(t *testing.T) { frame := b.call(t, "7", "session/new", "") code, message, _ := errorFrame(t, frame) - if code != -32601 || message != "method not found: session/new" { - t.Fatalf("error = (%d, %q), want -32601 method not found", code, message) - } + testassert.Require(t, code == -32601 && message == "method not found: session/new", "error = (%d, %q), want -32601 method not found", code, message) }) t.Run("notification produces no response", func(t *testing.T) { b.send(t, notificationLine("workspace/current", "")) b.send(t, requestLine("1", "workspace/current", "")) frame := b.recv(t) - if string(frame["id"]) != "1" { - t.Fatalf("first frame after a notification = %s, want the marker response", frame) - } + testassert.Require(t, string(frame["id"]) == "1", "first frame after a notification = %s, want the marker response", frame) }) t.Run("explicit null id responds", func(t *testing.T) { frame := b.call(t, "null", "workspace/current", "") - if string(frame["id"]) != "null" { - t.Fatalf("id = %s, want null", frame["id"]) - } + testassert.Require(t, string(frame["id"]) == "null", "id = %s, want null", frame["id"]) if resultOf(t, frame) != `"/ws"` { t.Fatalf("result = %s, want the initial workspace", resultOf(t, frame)) } }) t.Run("large number id stays exact", func(t *testing.T) { frame := b.call(t, "9007199254740993", "workspace/current", "") - if string(frame["id"]) != "9007199254740993" { - t.Fatalf("id = %s, want the exact caller number", frame["id"]) - } + testassert.Require(t, string(frame["id"]) == "9007199254740993", "id = %s, want the exact caller number", frame["id"]) }) t.Run("string id stays exact", func(t *testing.T) { frame := b.call(t, `"caller-id"`, "workspace/current", "") - if string(frame["id"]) != `"caller-id"` { - t.Fatalf("id = %s", frame["id"]) - } + testassert.Require(t, string(frame["id"]) == `"caller-id"`, "id = %s", frame["id"]) }) t.Run("negative number id", func(t *testing.T) { frame := b.call(t, "-5", "workspace/current", "") - if string(frame["id"]) != "-5" { - t.Fatalf("id = %s", frame["id"]) - } + testassert.Require(t, string(frame["id"]) == "-5", "id = %s", frame["id"]) }) } @@ -668,19 +620,14 @@ func TestACPStartupFailureIsUnavailable(t *testing.T) { ConfigPath: filepath.Join(dataDir, "config.json"), Executable: filepath.Join(dataDir, "absent-runtime"), }, "/ws", nil) - if code, _, _ := errorFrame(t, b.call(t, "1", "initialize", "")); code != -32001 { - t.Fatalf("initialize = %d, want -32001 for a failed startup", code) - } - if code := errorCode(t, b.call(t, "2", "runtime/GetHealth", "")); code != -32001 { - t.Fatalf("runtime method after a failed initialize = %d, want -32001", code) - } - if result := resultOf(t, b.call(t, "3", "workspace/current", "")); result != `"/ws"` { - t.Fatalf("workspace/current = %s, want the local workspace after startup failure", result) + { + code, _, _ := errorFrame(t, b.call(t, "1", "initialize", "")) + testassert.Equal(t, code, -32001) } + testassert.Equal(t, errorCode(t, b.call(t, "2", "runtime/GetHealth", "")), -32001) + testassert.Equal(t, resultOf(t, b.call(t, "3", "workspace/current", "")), `"/ws"`) b.eof() - if err := b.awaitRun(t); err != nil { - t.Fatalf("Run = %v, want nil", err) - } + testassert.NoError(t, b.awaitRun(t)) } // TestACPInitializeRequiresAHealthyStream proves a candidate whose event @@ -708,26 +655,14 @@ func TestACPInitializeRequiresAHealthyStream(t *testing.T) { t.Run(tc.name, func(t *testing.T) { f := newFixture(t, map[string]http.HandlerFunc{"/v1/events": tc.events}) b := startBridge(t, f, "/ws") - if code := errorCode(t, b.call(t, "1", "initialize", "")); code != -32001 { - t.Fatalf("initialize = %d, want -32001", code) - } - if code := errorCode(t, b.call(t, "2", "runtime/GetHealth", "")); code != -32001 { - t.Fatalf("runtime after a failed initialize = %d, want -32001", code) - } + testassert.Equal(t, errorCode(t, b.call(t, "1", "initialize", "")), -32001) + testassert.Equal(t, errorCode(t, b.call(t, "2", "runtime/GetHealth", "")), -32001) attempts := f.count("/v1/events") - if attempts == 0 { - t.Fatal("initialize never attempted the event stream") - } - if result := resultOf(t, b.call(t, "3", "workspace/current", "")); result != `"/ws"` { - t.Fatalf("workspace/current = %s, want the local workspace", result) - } - if got := f.count("/v1/events"); got != attempts { - t.Fatalf("event stream attempts grew from %d to %d without a loss", attempts, got) - } + testassert.Require(t, attempts != 0, "initialize never attempted the event stream") + testassert.Equal(t, resultOf(t, b.call(t, "3", "workspace/current", "")), `"/ws"`) + testassert.Equal(t, f.count("/v1/events"), attempts) for _, path := range f.paths() { - if strings.HasPrefix(path, "/v1/sessions") || strings.HasPrefix(path, "/v1/configuration") { - t.Fatalf("a failed initialize performed %s", path) - } + testassert.Require(t, !strings.HasPrefix(path, "/v1/sessions") && !strings.HasPrefix(path, "/v1/configuration"), "a failed initialize performed %s", path) } }) } @@ -741,12 +676,8 @@ func TestACPOversizedLine(t *testing.T) { _, _ = io.WriteString(b.in, strings.Repeat("x", (1<<20)+1)+"\n") }() err := b.awaitRun(t) - if err == nil { - t.Fatal("an oversized line ended Run without an error") - } - if !strings.Contains(err.Error(), "token too long") { - t.Fatalf("error = %v, want the scanner's line-bound error", err) - } + testassert.Require(t, err != nil, "an oversized line ended Run without an error") + testassert.Require(t, strings.Contains(err.Error(), "token too long"), "error = %v, want the scanner's line-bound error", err) } // TestACPInitializeBeforeAndAfter pins the initializing gate, the exact @@ -756,27 +687,13 @@ func TestACPInitializeBeforeAndAfter(t *testing.T) { b := startBridge(t, f, "/ws") // Local methods and method classification operate before initialization. - if result := resultOf(t, b.call(t, "1", "workspace/current", "")); result != `"/ws"` { - t.Fatalf("workspace/current = %s, want the initial normalized workspace", result) - } - if result := resultOf(t, b.call(t, "2", "workspace/select", `{"workspace":"/other"}`)); result != `"/other"` { - t.Fatalf("workspace/select = %s, want the normalized selection", result) - } - if code := errorCode(t, b.call(t, "3", "runtime/GetHealth", "")); code != -32001 { - t.Fatalf("pre-initialize runtime/GetHealth = %d, want -32001", code) - } - if code := errorCode(t, b.call(t, "4", "session/current", "")); code != -32001 { - t.Fatalf("pre-initialize session/current = %d, want -32001", code) - } - if code := errorCode(t, b.call(t, "5", "session/select", `{"session_id":"s1"}`)); code != -32001 { - t.Fatalf("pre-initialize session/select = %d, want -32001", code) - } - if code := errorCode(t, b.call(t, "6", "runtime/Nope", "{}")); code != -32001 { - t.Fatalf("unknown operation before a connection = %d, want -32001", code) - } - if requests := f.total(); requests != 0 { - t.Fatalf("pre-initialize requests reached the Runtime: %d", requests) - } + testassert.Equal(t, resultOf(t, b.call(t, "1", "workspace/current", "")), `"/ws"`) + testassert.Equal(t, resultOf(t, b.call(t, "2", "workspace/select", `{"workspace":"/other"}`)), `"/other"`) + testassert.Equal(t, errorCode(t, b.call(t, "3", "runtime/GetHealth", "")), -32001) + testassert.Equal(t, errorCode(t, b.call(t, "4", "session/current", "")), -32001) + testassert.Equal(t, errorCode(t, b.call(t, "5", "session/select", `{"session_id":"s1"}`)), -32001) + testassert.Equal(t, errorCode(t, b.call(t, "6", "runtime/Nope", "{}")), -32001) + testassert.Equal(t, f.total(), 0) b.send(t, requestLine("10", "initialize", "")) var sawResponse, sawConnected bool @@ -804,35 +721,20 @@ func TestACPInitializeBeforeAndAfter(t *testing.T) { Methods []string `json:"methods"` } var members map[string]json.RawMessage - if err := json.Unmarshal([]byte(resultOf(t, response)), &members); err != nil { - t.Fatalf("initialize result: %v", err) - } - if len(members) != 3 { - t.Fatalf("initialize result members = %v, want exactly serverInfo, runtime, and methods", members) - } - if err := json.Unmarshal([]byte(resultOf(t, response)), &result); err != nil { - t.Fatalf("initialize result: %v", err) - } - if result.ServerInfo.Name != "lightcode" || result.ServerInfo.Version != version.String() { - t.Fatalf("serverInfo = %+v, want the native identity", result.ServerInfo) - } - if string(result.Runtime) != healthBody() { - t.Fatalf("runtime = %s, want the generated Health", result.Runtime) - } + testassert.NoError(t, json.Unmarshal([]byte(resultOf(t, response)), &members)) + testassert.Require(t, len(members) == 3, "initialize result members = %v, want exactly serverInfo, runtime, and methods", members) + testassert.NoError(t, json.Unmarshal([]byte(resultOf(t, response)), &result)) + testassert.Require(t, result.ServerInfo.Name == "lightcode" && result.ServerInfo.Version == version.String() && string(result.Runtime) == healthBody(), "serverInfo = %+v, want the native identity; runtime = %s, want the generated Health", result.ServerInfo, result.Runtime) wantMethods := clientops.OperationNames() slices.Sort(wantMethods) - if !reflect.DeepEqual(result.Methods, wantMethods) { - t.Fatalf("methods = %v, want the sorted supported invocation names", result.Methods) - } + testassert.Require(t, reflect.DeepEqual(result.Methods, wantMethods), "methods = %v, want the sorted supported invocation names", result.Methods) if f.count("/v1/events") != 1 { t.Fatalf("initialize opened %d event streams, want one", f.count("/v1/events")) } // The one startup composition created one primary Session in the captured // Workspace (the pre-initialize selection); the scripted list handler // checked its query and method. - if got := f.lastBody(t, "/v1/sessions"); got != `{"agent_type":"primary","workspace":"/other"}` { - t.Fatalf("startup create body = %s", got) - } + testassert.Equal(t, f.lastBody(t, "/v1/sessions"), `{"agent_type":"primary","workspace":"/other"}`) // A repeated initialize describes the current connection and never // composes or creates again. @@ -842,20 +744,14 @@ func TestACPInitializeBeforeAndAfter(t *testing.T) { if resultOf(t, again) != resultOf(t, response) { t.Fatalf("repeated initialize = %s, want the current description", resultOf(t, again)) } - if f.count("/v1/events") != before { - t.Fatal("repeated initialize opened a second event stream") - } - if f.count("/v1/sessions") != startupReads { - t.Fatal("a repeated initialize composed again") - } + testassert.Require(t, f.count("/v1/events") == before, "repeated initialize opened a second event stream") + testassert.Require(t, f.count("/v1/sessions") == startupReads, "a repeated initialize composed again") health := b.call(t, "12", "runtime/GetHealth", "") if resultOf(t, health) != healthBody() { t.Fatalf("runtime/GetHealth = %s, want the exact health document", resultOf(t, health)) } - if code := errorCode(t, b.call(t, "13", "runtime/GetHealth", `{"bogus":true}`)); code != -32602 { - t.Fatalf("malformed envelope = %d, want -32602", code) - } + testassert.Equal(t, errorCode(t, b.call(t, "13", "runtime/GetHealth", `{"bogus":true}`)), -32602) } // TestACPSelection pins explicit session selection, its Workspace adoption, @@ -887,54 +783,38 @@ func TestACPSelection(t *testing.T) { // The startup composition adopted the newest root by last activity and ID; // an older root and a newer child header never win. - if result := resultOf(t, b.call(t, "2", "session/current", "")); result != hydrationBody("s-a", "/ws") { - t.Fatalf("startup selection = %s, want the newest root's fresh hydration", result) + { + result := resultOf(t, b.call(t, "2", "session/current", "")) + testassert.Require(t, result == hydrationBody("s-a", "/ws"), "startup selection = %s, want the newest root's fresh hydration", result) } // The local workspace operation clears the selection so the null result is // observable. - if result := resultOf(t, b.call(t, "3", "workspace/select", `{"workspace":"/ws"}`)); result != `"/ws"` { - t.Fatalf("workspace/select = %s, want the normalized selection", result) - } - if result := resultOf(t, b.call(t, "4", "session/current", "")); result != "null" { - t.Fatalf("session/current with no selection = %s, want null", result) - } - if f.count("/v1/sessions/s1/hydration") != 0 { - t.Fatal("session/current without a selection performed a hydration read") - } + testassert.Equal(t, resultOf(t, b.call(t, "3", "workspace/select", `{"workspace":"/ws"}`)), `"/ws"`) + testassert.Equal(t, resultOf(t, b.call(t, "4", "session/current", "")), "null") + testassert.Require(t, f.count("/v1/sessions/s1/hydration") == 0, "session/current without a selection performed a hydration read") selected := b.call(t, "5", "session/select", `{"session_id":"s1"}`) if resultOf(t, selected) != hydrationBody("s1", "/ws/s1") { t.Fatalf("session/select = %s, want the exact generated hydration", resultOf(t, selected)) } - if result := resultOf(t, b.call(t, "6", "workspace/current", "")); result != `"/ws/s1"` { - t.Fatalf("workspace/current after selection = %s, want the selected Session's workspace", result) - } + testassert.Equal(t, resultOf(t, b.call(t, "6", "workspace/current", "")), `"/ws/s1"`) b.call(t, "7", "session/current", "") if f.count("/v1/sessions/s1/hydration") != 2 { t.Fatalf("hydration reads = %d, want the selection read and one fresh current read", f.count("/v1/sessions/s1/hydration")) } - if result := resultOf(t, b.call(t, "8", "workspace/select", `{"workspace":"/other"}`)); result != `"/other"` { - t.Fatalf("workspace/select = %s", result) - } - if result := resultOf(t, b.call(t, "9", "session/current", "")); result != "null" { - t.Fatalf("session/current after clearing = %s, want null", result) - } - if result := resultOf(t, b.call(t, "10", "session/select", `{"session_id":"s2"}`)); result != hydrationBody("s2", "/ws/s2") { - t.Fatalf("session/select s2 = %s", result) - } - if result := resultOf(t, b.call(t, "11", "workspace/current", "")); result != `"/ws/s2"` { - t.Fatalf("workspace/current = %s, want the second Session's workspace", result) + testassert.Equal(t, resultOf(t, b.call(t, "8", "workspace/select", `{"workspace":"/other"}`)), `"/other"`) + testassert.Equal(t, resultOf(t, b.call(t, "9", "session/current", "")), "null") + { + result := resultOf(t, b.call(t, "10", "session/select", `{"session_id":"s2"}`)) + testassert.Require(t, result == hydrationBody("s2", "/ws/s2"), "session/select s2 = %s", result) } + testassert.Equal(t, resultOf(t, b.call(t, "11", "workspace/current", "")), `"/ws/s2"`) rejected := b.call(t, "12", "session/select", `{"session_id":"s3"}`) code, message, data := errorFrame(t, rejected) - if code != -32000 || message != "no such session" { - t.Fatalf("received error = (%d, %q), want -32000 with the returned message", code, message) - } - if string(data) != `{"code":"not_found","message":"no such session"}` { - t.Fatalf("error.data = %s, want the generated Error document", data) - } + testassert.Require(t, code == -32000 && message == "no such session", "received error = (%d, %q), want -32000 with the returned message", code, message) + testassert.Require(t, string(data) == `{"code":"not_found","message":"no such session"}`, "error.data = %s, want the generated Error document", data) for _, row := range []struct { name string @@ -952,9 +832,7 @@ func TestACPSelection(t *testing.T) { {"initialize unknown member", "initialize", `{"x":1}`}, } { t.Run(row.name, func(t *testing.T) { - if code := errorCode(t, b.call(t, `"`+row.name+`"`, row.method, row.params)); code != -32602 { - t.Fatalf("code = %d, want -32602", code) - } + testassert.Equal(t, errorCode(t, b.call(t, `"`+row.name+`"`, row.method, row.params)), -32602) }) } } @@ -1008,35 +886,24 @@ func TestACPEventsAndRepair(t *testing.T) { return sawResponse && sawConnected && sawEvent }) var forwarded, original map[string]any - if err := json.Unmarshal(eventParams, &forwarded); err != nil { - t.Fatalf("runtime/event params: %v", err) - } - if err := json.Unmarshal([]byte(event), &original); err != nil { - t.Fatalf("fixture event: %v", err) - } - if !reflect.DeepEqual(forwarded, original) { - t.Fatalf("forwarded event = %v, want the generated event %v", forwarded, original) - } + testassert.NoError(t, json.Unmarshal(eventParams, &forwarded)) + testassert.NoError(t, json.Unmarshal([]byte(event), &original)) + testassert.Require(t, reflect.DeepEqual(forwarded, original), "forwarded event = %v, want the generated event %v", forwarded, original) b.call(t, "2", "session/select", `{"session_id":"s1"}`) close(release) connected := b.awaitFrame(t, methodFrame("runtime/connected")) var health map[string]any - if err := json.Unmarshal(connected["params"], &health); err != nil { - t.Fatalf("runtime/connected params: %v", err) - } - if health["instance_id"] != testInstance || health["protocol_version"] != "2" { - t.Fatalf("runtime/connected params = %v, want the generated Health", health) - } + testassert.NoError(t, json.Unmarshal(connected["params"], &health)) + testassert.Require(t, health["instance_id"] == testInstance && health["protocol_version"] == "2", "runtime/connected params = %v, want the generated Health", health) resync := b.awaitFrame(t, methodFrame("runtime/resync_required")) - if params, ok := resync["params"]; ok { - t.Fatalf("runtime/resync_required carries params %s, want none", params) + { + params, ok := resync["params"] + testassert.Require(t, !ok, "runtime/resync_required carries params %s, want none", params) } snapshot := b.awaitFrame(t, methodFrame("session/snapshot")) - if string(snapshot["params"]) != hydrationBody("s1", "/ws/s1") { - t.Fatalf("session/snapshot = %s, want the selected Session's fresh hydration", snapshot["params"]) - } + testassert.Require(t, string(snapshot["params"]) == hydrationBody("s1", "/ws/s1"), "session/snapshot = %s, want the selected Session's fresh hydration", snapshot["params"]) if f.count("/v1/events") != 2 { t.Fatalf("event streams opened = %d, want one repair reconnect", f.count("/v1/events")) } @@ -1054,45 +921,30 @@ func TestACPNoParameterParams(t *testing.T) { b := startBridge(t, f, "/ws") // initialize rejects a supplied null before any connection is made. - if code := errorCode(t, b.call(t, "1", "initialize", "null")); code != -32602 { - t.Fatalf("initialize with null params = %d, want -32602", code) - } + testassert.Equal(t, errorCode(t, b.call(t, "1", "initialize", "null")), -32602) if f.total() != 0 { t.Fatalf("a rejected initialize started %d Runtime requests", f.total()) } // initialize accepts the empty object. initialized := b.call(t, "2", "initialize", "{}") - if _, ok := initialized["result"]; !ok { - t.Fatalf("initialize with {} = %s, want the connection description", initialized) + { + _, ok := initialized["result"] + testassert.Require(t, ok, "initialize with {} = %s, want the connection description", initialized) } // A repeated initialize accepts absent params. - if resultOf(t, b.call(t, "3", "initialize", "")) != resultOf(t, initialized) { - t.Fatal("initialize with absent params did not describe the connection") - } + testassert.Require(t, resultOf(t, b.call(t, "3", "initialize", "")) == resultOf(t, initialized), "initialize with absent params did not describe the connection") // workspace/current accepts absent and {}, and rejects null. - if result := resultOf(t, b.call(t, "4", "workspace/current", "")); result != `"/ws"` { - t.Fatalf("workspace/current with absent params = %s", result) - } - if result := resultOf(t, b.call(t, "5", "workspace/current", "{}")); result != `"/ws"` { - t.Fatalf("workspace/current with {} = %s", result) - } - if code := errorCode(t, b.call(t, "6", "workspace/current", "null")); code != -32602 { - t.Fatalf("workspace/current with null params = %d, want -32602", code) - } + testassert.Equal(t, resultOf(t, b.call(t, "4", "workspace/current", "")), `"/ws"`) + testassert.Equal(t, resultOf(t, b.call(t, "5", "workspace/current", "{}")), `"/ws"`) + testassert.Equal(t, errorCode(t, b.call(t, "6", "workspace/current", "null")), -32602) // With the startup selection cleared, session/current accepts absent and // {}, and rejects null. b.call(t, "61", "workspace/select", `{"workspace":"/ws"}`) - if result := resultOf(t, b.call(t, "7", "session/current", "")); result != "null" { - t.Fatalf("session/current with absent params = %s, want null", result) - } - if result := resultOf(t, b.call(t, "8", "session/current", "{}")); result != "null" { - t.Fatalf("session/current with {} = %s, want null", result) - } - if code := errorCode(t, b.call(t, "9", "session/current", "null")); code != -32602 { - t.Fatalf("session/current with null params = %d, want -32602", code) - } + testassert.Equal(t, resultOf(t, b.call(t, "7", "session/current", "")), "null") + testassert.Equal(t, resultOf(t, b.call(t, "8", "session/current", "{}")), "null") + testassert.Equal(t, errorCode(t, b.call(t, "9", "session/current", "null")), -32602) } // TestACPProgressOverflowDisconnectsAndResyncs pins that a full progress @@ -1183,15 +1035,9 @@ func TestACPInputPressureCancelsParkedCalls(t *testing.T) { t.Fatal("EOF did not cancel the parked Runtime call") } writer.release() - if err := b.awaitRun(t); err != nil { - t.Fatalf("Run after EOF = %v, want nil", err) - } - if got := f.count("/v1/health"); got != healthBefore { - t.Fatalf("the queued line was dispatched after EOF: health requests %d -> %d", healthBefore, got) - } - if f.count("/v1/sessions/s1/stop") != 1 { - t.Fatal("the bridge issued an implicit control beyond the explicit call") - } + testassert.NoError(t, b.awaitRun(t)) + testassert.Equal(t, f.count("/v1/health"), healthBefore) + testassert.Require(t, f.count("/v1/sessions/s1/stop") == 1, "the bridge issued an implicit control beyond the explicit call") }) t.Run("EOF behind a capacity-blocked line is observed in order", func(t *testing.T) { @@ -1230,9 +1076,7 @@ func TestACPInputPressureCancelsParkedCalls(t *testing.T) { t.Fatalf("the line admitted before EOF never progressed: health requests %d -> %d, calls=%v", healthBefore, got, f.paths()) } // The ordered EOF then ends the run normally, never as an overflow. - if err := b.awaitRun(t); err != nil { - t.Fatalf("Run after an ordered EOF = %v, want nil", err) - } + testassert.NoError(t, b.awaitRun(t)) // Every line that reached dispatch kept its admission order; the racy // tail behind the observed EOF may be released, never reordered. want := []string{"/v1/health", "/v1/warnings", "/v1/health"} @@ -1267,13 +1111,12 @@ func TestACPInputPressureCancelsParkedCalls(t *testing.T) { case <-time.After(15 * time.Second): t.Fatal("out-of-band cancellation did not cancel the parked Runtime call") } - if err := b.awaitRun(t); err == nil { - t.Fatal("Run returned nil after its context was canceled") + { + err := b.awaitRun(t) + testassert.Require(t, err != nil, "Run returned nil after its context was canceled") } writer.release() - if got := f.total(); got != before { - t.Fatalf("a capacity-blocked line was dispatched after cancellation: requests %d -> %d", before, got) - } + testassert.Equal(t, f.total(), before) }) t.Run("an uninterruptible output write stays bounded", func(t *testing.T) { @@ -1285,11 +1128,10 @@ func TestACPInputPressureCancelsParkedCalls(t *testing.T) { writer.awaitBlocked(t) b.eof() start := time.Now() - if err := b.awaitRun(t); err != nil { - t.Fatalf("Run after EOF with a parked writer = %v, want nil", err) - } - if elapsed := time.Since(start); elapsed > 10*time.Second { - t.Fatalf("Run waited %s for the parked writer, want the bounded join", elapsed) + testassert.NoError(t, b.awaitRun(t)) + { + elapsed := time.Since(start) + testassert.Require(t, elapsed <= 10*time.Second, "Run waited %s for the parked writer, want the bounded join", elapsed) } }) } @@ -1378,15 +1220,12 @@ func TestACPFailedWriterEndsRun(t *testing.T) { if tc.short { wantErr = io.ErrShortWrite } - if err := b.awaitRun(t); !errors.Is(err, wantErr) { - t.Fatalf("Run = %v, want %v", err, wantErr) - } - if got := f.count("/v1/health"); got != healthBefore { - t.Fatalf("a request was dispatched after the output failure: health %d -> %d", healthBefore, got) - } - if f.count("/v1/sessions/s1/stop") != 1 { - t.Fatal("the output failure issued an implicit Runtime control") + { + err := b.awaitRun(t) + testassert.Require(t, errors.Is(err, wantErr), "Run = %v, want %v", err, wantErr) } + testassert.Equal(t, f.count("/v1/health"), healthBefore) + testassert.Require(t, f.count("/v1/sessions/s1/stop") == 1, "the output failure issued an implicit Runtime control") }) } } @@ -1417,54 +1256,34 @@ func TestACPRuntimeForwarding(t *testing.T) { if resultOf(t, created) != `{"session_id":"s1","durable_revision":"9007199254740993"}` { t.Fatalf("CreateSession result = %s, want the exact raw body", resultOf(t, created)) } - if got := f.lastBody(t, "/v1/sessions"); got != body { - t.Fatalf("Runtime received %s, want the caller's exact body %s", got, body) - } - if resultOf(t, b.call(t, "3", "runtime/DiscoverProviderCandidates", `{"body":{"base_url":"x","api_key_env":"K"}}`)) != "[]" { - t.Fatalf("DiscoverProviderCandidates result did not pass through raw") - } + testassert.Equal(t, f.lastBody(t, "/v1/sessions"), body) + testassert.Require(t, resultOf(t, b.call(t, "3", "runtime/DiscoverProviderCandidates", `{"body":{"base_url":"x","api_key_env":"K"}}`)) == "[]", "DiscoverProviderCandidates result did not pass through raw") reverted := b.call(t, "4", "runtime/RevertSessionCode", `{"session_id":"s1","body":{"operation_id":"op"}}`) code, message, data := errorFrame(t, reverted) - if code != -32000 || message != "partial restore" { - t.Fatalf("partial error = (%d, %q)", code, message) - } + testassert.Require(t, code == -32000 && message == "partial restore", "partial error = (%d, %q)", code, message) var gotPartial, wantPartial any - if err := json.Unmarshal(data, &gotPartial); err != nil { - t.Fatalf("partial error.data %s: %v", data, err) - } - if err := json.Unmarshal([]byte(`{"error":{"code":"conflict","message":"partial restore"},"restored":["a.go"],"skipped":[{"path":"b.go","reason":"denied"}]}`), &wantPartial); err != nil { - t.Fatalf("fixture partial: %v", err) - } - if !reflect.DeepEqual(gotPartial, wantPartial) { - t.Fatalf("error.data = %s, want the complete generated result", data) + { + err := json.Unmarshal(data, &gotPartial) + testassert.Require(t, err == nil, "partial error.data %s: %v", data, err) } + testassert.NoError(t, json.Unmarshal([]byte(`{"error":{"code":"conflict","message":"partial restore"},"restored":["a.go"],"skipped":[{"path":"b.go","reason":"denied"}]}`), &wantPartial)) + testassert.Require(t, reflect.DeepEqual(gotPartial, wantPartial), "error.data = %s, want the complete generated result", data) malformed := b.call(t, "5", "runtime/GetSessionHydration", `{"session_id":"s1"}`) - if code, _, _ := errorFrame(t, malformed); code != -32001 { - t.Fatalf("malformed Runtime response = %d, want the separate transport code", code) + { + code, _, _ := errorFrame(t, malformed) + testassert.Equal(t, code, -32001) } unknownBefore := f.total() - if code := errorCode(t, b.call(t, "6", "runtime/NoSuchOperation", `{}`)); code != -32602 { - t.Fatalf("unknown operation = %d, want -32602", code) - } - if f.total() != unknownBefore { - t.Fatal("an unknown operation reached the Runtime") - } - if code := errorCode(t, b.call(t, "7", "runtime/CreateSession", `{"body":[]}`)); code != -32602 { - t.Fatalf("local envelope failure = %d, want -32602", code) - } - if code := errorCode(t, b.call(t, "8", "runtime/GetHealth", `{"query":{}}`)); code != -32602 { - t.Fatalf("unrelated envelope member = %d, want -32602", code) - } + testassert.Equal(t, errorCode(t, b.call(t, "6", "runtime/NoSuchOperation", `{}`)), -32602) + testassert.Require(t, f.total() == unknownBefore, "an unknown operation reached the Runtime") + testassert.Equal(t, errorCode(t, b.call(t, "7", "runtime/CreateSession", `{"body":[]}`)), -32602) + testassert.Equal(t, errorCode(t, b.call(t, "8", "runtime/GetHealth", `{"query":{}}`)), -32602) healthBefore := f.count("/v1/health") - if code := errorCode(t, b.call(t, "9", "runtime/GetHealth", "null")); code != -32602 { - t.Fatalf("null invocation params = %d, want -32602", code) - } - if f.count("/v1/health") != healthBefore { - t.Fatal("a rejected null envelope reached the Runtime") - } + testassert.Equal(t, errorCode(t, b.call(t, "9", "runtime/GetHealth", "null")), -32602) + testassert.Require(t, f.count("/v1/health") == healthBefore, "a rejected null envelope reached the Runtime") } // A composition failure ends that attempt through the RPC error mapping and @@ -1475,10 +1294,9 @@ func TestACPStartupCompositionFailureLeavesTransportAvailable(t *testing.T) { }) b := startBridge(t, f, "/ws") code, message, _ := errorFrame(t, b.call(t, "1", "initialize", "")) - if code != -32000 || message != "list failed" { - t.Fatalf("composition failure = (%d, %q), want the received Runtime error mapping", code, message) - } - if result := resultOf(t, b.call(t, "2", "runtime/GetHealth", "")); result != healthBody() { - t.Fatalf("runtime/GetHealth after a composition failure = %s", result) + testassert.Require(t, code == -32000 && message == "list failed", "composition failure = (%d, %q), want the received Runtime error mapping", code, message) + { + result := resultOf(t, b.call(t, "2", "runtime/GetHealth", "")) + testassert.Require(t, result == healthBody(), "runtime/GetHealth after a composition failure = %s", result) } } diff --git a/internal/acpclient/signal_test.go b/internal/acpclient/signal_test.go index 95d24341..ad57b64f 100644 --- a/internal/acpclient/signal_test.go +++ b/internal/acpclient/signal_test.go @@ -16,6 +16,7 @@ import ( "github.com/MMinasyan/lightcode/internal/acpclient" "github.com/MMinasyan/lightcode/internal/clienthost" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestACPSignalHelper is the subprocess entry: one bridge parked on open @@ -53,17 +54,11 @@ func TestACPSignalCancelsRun(t *testing.T) { cmd := exec.Command(os.Args[0], "-test.run=TestACPSignalHelper", "-test.v") cmd.Env = append(os.Environ(), "ACP_SIGNAL_HELPER=1", "ACP_SIGNAL_DATADIR="+dataDir) stdin, err := cmd.StdinPipe() - if err != nil { - t.Fatalf("stdin pipe: %v", err) - } + testassert.NoError(t, err) defer stdin.Close() stderr, err := cmd.StderrPipe() - if err != nil { - t.Fatalf("stderr pipe: %v", err) - } - if err := cmd.Start(); err != nil { - t.Fatalf("start helper: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, cmd.Start()) joined := false defer func() { if !joined { @@ -87,17 +82,13 @@ func TestACPSignalCancelsRun(t *testing.T) { case <-time.After(30 * time.Second): t.Fatal("the helper never reported readiness") } - if err := cmd.Process.Signal(syscall.SIGTERM); err != nil { - t.Fatalf("signal helper: %v", err) - } + testassert.NoError(t, cmd.Process.Signal(syscall.SIGTERM)) waited := make(chan error, 1) go func() { waited <- cmd.Wait() }() select { case err := <-waited: joined = true - if err != nil { - t.Fatalf("helper exit: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the helper did not join within the bounded wait") } diff --git a/internal/adaptation/bundled_test.go b/internal/adaptation/bundled_test.go index 82a862d3..aedaf181 100644 --- a/internal/adaptation/bundled_test.go +++ b/internal/adaptation/bundled_test.go @@ -8,6 +8,8 @@ import ( "slices" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // loadFixture reads the fixture bundled data into a bundledDoc the tests @@ -16,13 +18,9 @@ func loadFixture(t *testing.T) bundledDoc { t.Helper() path := filepath.Join("testdata", "bundled.json") raw, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read fixture %s: %v", path, err) - } + testassert.Require(t, err == nil, "read fixture %s: %v", path, err) var doc bundledDoc - if err := json.Unmarshal(raw, &doc); err != nil { - t.Fatalf("parse fixture: %v", err) - } + testassert.NoError(t, json.Unmarshal(raw, &doc)) return doc } @@ -44,29 +42,19 @@ func fixtureRow(doc bundledDoc, want string) bundledEntry { // before they reach the matcher. func TestProductionBundledDataIsValid(t *testing.T) { tbl := loadBundledTable() - if len(tbl) == 0 { - t.Fatal("loadBundledTable returned an empty table") - } + testassert.Require(t, len(tbl) != 0, "loadBundledTable returned an empty table") // The production data file is the authoritative source of truth. // Compare its loaded form against its parsed JSON form to ensure // the loader and the file agree. doc := loadFixture(t) - if len(doc.Entries) != len(tbl) { - t.Fatalf("loaded %d rows, fixture has %d", len(tbl), len(doc.Entries)) - } + testassert.Equal(t, len(doc.Entries), len(tbl)) for i, e := range doc.Entries { - if e.Name == "" { - t.Errorf("row %d (%q): fixture row has no authoring name", i, e.Pattern) - } - if tbl[i].pattern != e.Pattern { - t.Errorf("row %d: loader pattern %q != fixture %q", i, tbl[i].pattern, e.Pattern) - } - if !maps.Equal(tbl[i].adapt.Additions, e.Additions) || - !slices.Equal(tbl[i].adapt.ExcludeTools, e.ExcludeTools) || - !slices.Equal(tbl[i].adapt.IncludeTools, e.IncludeTools) || - !maps.Equal(tbl[i].adapt.ToolDescriptionReplacements, e.ToolDescriptionReplacements) { - t.Errorf("row %d (%q): loaded adaptation outputs %+v != fixture row %+v", i, e.Pattern, tbl[i].adapt, e) - } + testassert.Check(t, e.Name != "", "row %d (%q): fixture row has no authoring name", i, e.Pattern) + testassert.Check(t, tbl[i].pattern == e.Pattern, "row %d: loader pattern %q != fixture %q", i, tbl[i].pattern, e.Pattern) + testassert.Check(t, maps.Equal(tbl[i].adapt.Additions, e.Additions) && + slices.Equal(tbl[i].adapt.ExcludeTools, e.ExcludeTools) && + slices.Equal(tbl[i].adapt.IncludeTools, e.IncludeTools) && + maps.Equal(tbl[i].adapt.ToolDescriptionReplacements, e.ToolDescriptionReplacements), "row %d (%q): loaded adaptation outputs %+v != fixture row %+v", i, e.Pattern, tbl[i].adapt, e) } } @@ -93,9 +81,7 @@ func TestProductionBundledDataHasNoDuplicateRow(t *testing.T) { seen := make(map[string]bool) for _, e := range doc.Entries { key := e.Pattern + "\x00" + e.Name - if seen[key] { - t.Errorf("duplicate row %q / %q", e.Pattern, e.Name) - } + testassert.Check(t, !seen[key], "duplicate row %q / %q", e.Pattern, e.Name) seen[key] = true } } @@ -107,21 +93,18 @@ func TestProductionBundledDataAdditionsUseValidSectionIDs(t *testing.T) { doc := loadFixture(t) for _, e := range doc.Entries { for k := range e.Additions { - if !validSectionIDs[k] { - t.Errorf("entry %q has additions key %q outside {safety, tone, task_execution, language}", e.Pattern, k) - } + testassert.Check(t, validSectionIDs[k], "entry %q has additions key %q outside {safety, tone, task_execution, language}", e.Pattern, k) } } } func TestProductionBundledDataHasToolDescriptionDefaults(t *testing.T) { doc := loadFixture(t) - if len(doc.Defaults.ToolDescriptionReplacements) == 0 { - t.Fatal("fixture defaults.tool_description_replacements is empty") - } + testassert.Require(t, len(doc.Defaults.ToolDescriptionReplacements) != 0, "fixture defaults.tool_description_replacements is empty") for _, key := range []string{""} { - if _, ok := doc.Defaults.ToolDescriptionReplacements[key]; !ok { - t.Fatalf("fixture missing default tool description replacement %q", key) + { + _, ok := doc.Defaults.ToolDescriptionReplacements[key] + testassert.Require(t, ok, "fixture missing default tool description replacement %q", key) } } } @@ -136,9 +119,7 @@ func TestBundledEntryRejectsUnknownToolDescriptionReplacement(t *testing.T) { }, bundledDefaults{ToolDescriptionReplacements: map[string]string{ "": "value", }}) - if err == nil || !strings.Contains(err.Error(), "has no default replacement") { - t.Fatalf("validateBundledEntry err = %v, want unknown replacement key error", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "has no default replacement"), "validateBundledEntry err = %v, want unknown replacement key error", err) } func indexOf(doc bundledDoc, want bundledEntry) int { @@ -172,9 +153,7 @@ var modelFamilyNames = []string{ // stays intact. func TestProductionGoContainsNoModelFamilyNames(t *testing.T) { entries, err := os.ReadDir(".") - if err != nil { - t.Fatalf("read package dir: %v", err) - } + testassert.NoError(t, err) for _, e := range entries { name := e.Name() if e.IsDir() { @@ -199,17 +178,13 @@ func TestProductionGoContainsNoModelFamilyNames(t *testing.T) { func scanFileForModelFamilyNames(t *testing.T, path string) { t.Helper() raw, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s: %v", path, err) - } + testassert.Require(t, err == nil, "read %s: %v", path, err) // Match Go string literals (double-quoted) and backtick raw strings. // We are intentionally not parsing Go; a token-level scan is good // enough for the audit's purpose. src := string(raw) for _, fam := range modelFamilyNames { - if containsStringLiteral(src, fam) { - t.Errorf("production Go file %q contains model family name %q as a string literal; production bindings must live in the bundled data file", path, fam) - } + testassert.Check(t, !containsStringLiteral(src, fam), "production Go file %q contains model family name %q as a string literal; production bindings must live in the bundled data file", path, fam) } } diff --git a/internal/adaptation/match_test.go b/internal/adaptation/match_test.go index 2c3cc4bb..33eed37c 100644 --- a/internal/adaptation/match_test.go +++ b/internal/adaptation/match_test.go @@ -5,6 +5,8 @@ import ( "slices" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestMatchInOrderingMostSpecificFirst(t *testing.T) { @@ -25,12 +27,8 @@ func TestMatchInOrderingMostSpecificFirst(t *testing.T) { for _, tc := range cases { t.Run(tc.id, func(t *testing.T) { got := matchIn(tbl, tc.id) - if got == nil { - t.Fatalf("matchIn(%q) = nil, want %q", tc.id, tc.want) - } - if got.Additions["tone"] != tc.want { - t.Fatalf("matchIn(%q).Additions[tone] = %q, want %q", tc.id, got.Additions["tone"], tc.want) - } + testassert.Require(t, got != nil, "matchIn(%q) = nil, want %q", tc.id, tc.want) + testassert.Require(t, got.Additions["tone"] == tc.want, "matchIn(%q).Additions[tone] = %q, want %q", tc.id, got.Additions["tone"], tc.want) }) } } @@ -38,8 +36,9 @@ func TestMatchInOrderingMostSpecificFirst(t *testing.T) { func TestMatchInAbstention(t *testing.T) { tbl := []entry{{pattern: "gpt-*", adapt: Adaptation{Additions: map[string]string{"tone": "gpt-addition"}}}} for _, id := range []string{"claude-opus-4-8", "qwen3.7-plus", "", "gp"} { - if got := matchIn(tbl, id); got != nil { - t.Fatalf("matchIn(%q) = %+v, want nil", id, got) + { + got := matchIn(tbl, id) + testassert.Require(t, got == nil, "matchIn(%q) = %+v, want nil", id, got) } } } @@ -49,9 +48,7 @@ func TestMatchInCaseInsensitive(t *testing.T) { // Both the pattern (upper) and the input (mixed) are folded before matching. for _, id := range []string{"gpt-oss-20b", "GPT-OSS-20B", "Gpt-Oss-20b"} { got := matchIn(tbl, id) - if got == nil || got.Additions["tone"] != "gpt-oss-addition" { - t.Fatalf("matchIn(%q) = %+v, want the gpt-oss row", id, got) - } + testassert.Require(t, got != nil && got.Additions["tone"] == "gpt-oss-addition", "matchIn(%q) = %+v, want the gpt-oss row", id, got) } } @@ -60,20 +57,20 @@ func TestMatchInSlashContainingBareID(t *testing.T) { // first '/'), so the wildcard must cross '/'. tbl := []entry{{pattern: "deepseek*", adapt: Adaptation{Additions: map[string]string{"tone": "deepseek-addition"}}}} got := matchIn(tbl, "deepseek/deepseek-chat") - if got == nil || got.Additions["tone"] != "deepseek-addition" { - t.Fatalf("matchIn(deepseek/deepseek-chat) = %+v, want the deepseek row", got) - } + testassert.Require(t, got != nil && got.Additions["tone"] == "deepseek-addition", "matchIn(deepseek/deepseek-chat) = %+v, want the deepseek row", got) } func TestMatchInIsAnchoredNotSubstring(t *testing.T) { // The resolver is always fed the bare model id; a provider-prefixed string // must not match a pattern that the bare id matches (documents "feed ref.Model"). tbl := []entry{{pattern: "gpt-5*", adapt: Adaptation{Additions: map[string]string{"tone": "gpt5-addition"}}}} - if got := matchIn(tbl, "gpt-5.4"); got == nil || got.Additions["tone"] != "gpt5-addition" { - t.Fatalf("matchIn(gpt-5.4) = %+v, want the gpt-5 row", got) + { + got := matchIn(tbl, "gpt-5.4") + testassert.Require(t, got != nil && got.Additions["tone"] == "gpt5-addition", "matchIn(gpt-5.4) = %+v, want the gpt-5 row", got) } - if got := matchIn(tbl, "openai/gpt-5.4"); got != nil { - t.Fatalf("matchIn(openai/gpt-5.4) = %+v, want nil (anchored, not substring)", got) + { + got := matchIn(tbl, "openai/gpt-5.4") + testassert.Require(t, got == nil, "matchIn(openai/gpt-5.4) = %+v, want nil (anchored, not substring)", got) } } @@ -86,12 +83,8 @@ func TestMatchInReturnsCopyNotAlias(t *testing.T) { ToolDescriptionReplacements: map[string]string{"": "apply_patch"}, }}} got := matchIn(tbl, "gpt-5.4") - if got == nil { - t.Fatal("matchIn returned nil") - } - if got == &tbl[0].adapt { - t.Fatal("matchIn returned a pointer aliasing the table entry") - } + testassert.Require(t, got != nil, "matchIn returned nil") + testassert.Require(t, got != &tbl[0].adapt, "matchIn returned a pointer aliasing the table entry") // Mutating every field of the result must not touch the table entry. got.ExcludeTools[0] = "mutated" got.IncludeTools[0] = "mutated" @@ -100,21 +93,11 @@ func TestMatchInReturnsCopyNotAlias(t *testing.T) { got.Additions["tone"] = "mutated" got.Additions["safety"] = "mutated" got.ToolDescriptionReplacements[""] = "mutated" - if tbl[0].adapt.ExcludeTools[0] != "edit_file" { - t.Fatalf("ExcludeTools leaked into the table: %q", tbl[0].adapt.ExcludeTools[0]) - } - if tbl[0].adapt.IncludeTools[0] != "apply_patch" { - t.Fatalf("IncludeTools leaked into the table: %q", tbl[0].adapt.IncludeTools[0]) - } - if len(tbl[0].adapt.Blocks) != 1 || tbl[0].adapt.Blocks[0] != "coach" { - t.Fatalf("Blocks leaked into the table: %v", tbl[0].adapt.Blocks) - } - if len(tbl[0].adapt.Additions) != 1 || tbl[0].adapt.Additions["tone"] != "tone-add" { - t.Fatalf("Additions leaked into the table: %v", tbl[0].adapt.Additions) - } - if len(tbl[0].adapt.ToolDescriptionReplacements) != 1 || tbl[0].adapt.ToolDescriptionReplacements[""] != "apply_patch" { - t.Fatalf("ToolDescriptionReplacements leaked into the table: %v", tbl[0].adapt.ToolDescriptionReplacements) - } + testassert.Equal(t, tbl[0].adapt.ExcludeTools[0], "edit_file") + testassert.Equal(t, tbl[0].adapt.IncludeTools[0], "apply_patch") + testassert.Require(t, len(tbl[0].adapt.Blocks) == 1 && tbl[0].adapt.Blocks[0] == "coach", "Blocks leaked into the table: %v", tbl[0].adapt.Blocks) + testassert.Require(t, len(tbl[0].adapt.Additions) == 1 && tbl[0].adapt.Additions["tone"] == "tone-add", "Additions leaked into the table: %v", tbl[0].adapt.Additions) + testassert.Require(t, len(tbl[0].adapt.ToolDescriptionReplacements) == 1 && tbl[0].adapt.ToolDescriptionReplacements[""] == "apply_patch", "ToolDescriptionReplacements leaked into the table: %v", tbl[0].adapt.ToolDescriptionReplacements) } func TestSectionAdditionResolution(t *testing.T) { @@ -132,9 +115,7 @@ func TestSectionAdditionResolution(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := SectionAddition(tc.adapt, tc.section) - if got != tc.want { - t.Fatalf("SectionAddition(%v, %q) = %q, want %q", tc.adapt, tc.section, got, tc.want) - } + testassert.Require(t, got == tc.want, "SectionAddition(%v, %q) = %q, want %q", tc.adapt, tc.section, got, tc.want) }) } } @@ -144,8 +125,9 @@ func TestShippedToolDescriptionDefaults(t *testing.T) { "": "edit_file or write_file", } for key, value := range want { - if got := defaultToolDescriptionReplacements[key]; got != value { - t.Fatalf("defaultToolDescriptionReplacements[%q] = %q, want %q", key, got, value) + { + got := defaultToolDescriptionReplacements[key] + testassert.Require(t, got == value, "defaultToolDescriptionReplacements[%q] = %q, want %q", key, got, value) } } } @@ -165,9 +147,7 @@ func TestRenderToolDescriptionReplacements(t *testing.T) { }, "\n") baseline := RenderToolDescription(description, nil) - if strings.Contains(baseline, "<") || !strings.Contains(baseline, "use edit_file or write_file") || !strings.Contains(baseline, "- removed by adaptation") { - t.Fatalf("baseline RenderToolDescription = %q", baseline) - } + testassert.Require(t, !strings.Contains(baseline, "<") && strings.Contains(baseline, "use edit_file or write_file") && strings.Contains(baseline, "- removed by adaptation"), "baseline RenderToolDescription = %q", baseline) adapted := RenderToolDescription(description, &Adaptation{ ToolDescriptionReplacements: map[string]string{ @@ -175,11 +155,10 @@ func TestRenderToolDescriptionReplacements(t *testing.T) { "": "", }, }) - if strings.Contains(adapted, "<") || strings.Contains(adapted, "removed by adaptation") { - t.Fatalf("adapted RenderToolDescription = %q", adapted) - } - if want := "use apply_patch\ndone"; adapted != want { - t.Fatalf("adapted RenderToolDescription = %q, want %q", adapted, want) + testassert.Require(t, !strings.Contains(adapted, "<") && !strings.Contains(adapted, "removed by adaptation"), "adapted RenderToolDescription = %q", adapted) + { + want := "use apply_patch\ndone" + testassert.Equal(t, adapted, want) } } @@ -191,26 +170,19 @@ func TestShippedTableMatchesBundledGPTCodex(t *testing.T) { fx := loadFixture(t) codex := fixtureRow(fx, "gpt-codex") want := "Use more tool calls when they can improve the response, provide useful context, or help plan or complete the task.\n\nIf you encounter a new or unexpected challenge before the active task is complete, try to resolve it yourself first. If a tool call fails or returns incomplete or unhelpful output, try another tool or a different approach." - if codex.Additions["task_execution"] != want { - t.Fatalf("fixture gpt-codex additions text differs from canonical Decision-1 text") - } + testassert.Equal(t, codex.Additions["task_execution"], want) for _, id := range []string{ "gpt-5.4", "gpt-5.4-mini", "gpt-5.5", "openai/gpt-5.4-mini", "openai/gpt-5.5-pro", } { t.Run(id, func(t *testing.T) { got := Match(id) - if got == nil { - t.Fatalf("Match(%q) = nil, want gpt-codex", id) - } - if !reflect.DeepEqual(got.Additions, codex.Additions) { - t.Fatalf("Match(%q).Additions = %v, want the %s row additions", id, got.Additions, codex.Name) - } - if add := SectionAddition(got, "task_execution"); add != want { - t.Fatalf("SectionAddition(Match(%q), task_execution) differs from canonical text", id) - } - if !slices.Contains(got.IncludeTools, "apply_patch") { - t.Errorf("Match(%q).IncludeTools = %v, want apply_patch", id, got.IncludeTools) + testassert.Require(t, got != nil, "Match(%q) = nil, want gpt-codex", id) + testassert.Require(t, reflect.DeepEqual(got.Additions, codex.Additions), "Match(%q).Additions = %v, want the %s row additions", id, got.Additions, codex.Name) + { + add := SectionAddition(got, "task_execution") + testassert.Require(t, add == want, "SectionAddition(Match(%q), task_execution) differs from canonical text", id) } + testassert.Check(t, slices.Contains(got.IncludeTools, "apply_patch"), "Match(%q).IncludeTools = %v, want apply_patch", id, got.IncludeTools) }) } } @@ -223,17 +195,12 @@ func TestShippedTableMatchesBundledGPTApplyPatchBroad(t *testing.T) { } { t.Run(id, func(t *testing.T) { got := Match(id) - if got == nil { - t.Fatalf("Match(%q) = nil, want gpt-apply-patch", id) - } - if !reflect.DeepEqual(got.IncludeTools, ap.IncludeTools) { - t.Fatalf("Match(%q).IncludeTools = %v, want the %s row include_tools", id, got.IncludeTools, ap.Name) - } - if !slices.Contains(got.IncludeTools, "apply_patch") { - t.Errorf("Match(%q).IncludeTools = %v, want apply_patch", id, got.IncludeTools) - } - if v, ok := got.Additions["task_execution"]; ok && v != "" { - t.Errorf("Match(%q).Additions[task_execution] = %q, want absent (broader gpt-5 has no prompt addition)", id, v) + testassert.Require(t, got != nil, "Match(%q) = nil, want gpt-apply-patch", id) + testassert.Require(t, reflect.DeepEqual(got.IncludeTools, ap.IncludeTools), "Match(%q).IncludeTools = %v, want the %s row include_tools", id, got.IncludeTools, ap.Name) + testassert.Check(t, slices.Contains(got.IncludeTools, "apply_patch"), "Match(%q).IncludeTools = %v, want apply_patch", id, got.IncludeTools) + { + v, ok := got.Additions["task_execution"] + testassert.Check(t, !ok || v == "", "Match(%q).Additions[task_execution] = %q, want absent (broader gpt-5 has no prompt addition)", id, v) } }) } @@ -245,15 +212,12 @@ func TestShippedTableMatchesBundledGrok(t *testing.T) { for _, id := range []string{"grok-build-0.1", "x-ai/grok-build-0.1"} { t.Run(id, func(t *testing.T) { got := Match(id) - if got == nil { - t.Fatalf("Match(%q) = nil, want adaptation", id) - } - if add := SectionAddition(got, "task_execution"); add == "" || add != ge.Additions["task_execution"] { - t.Fatalf("Match(%q).Additions[task_execution] = %q, want the nonempty %s row addition", id, add, ge.Name) - } - if slices.Contains(got.IncludeTools, "apply_patch") { - t.Errorf("grok should not include apply_patch (not V4A): %v", got.IncludeTools) + testassert.Require(t, got != nil, "Match(%q) = nil, want adaptation", id) + { + add := SectionAddition(got, "task_execution") + testassert.Require(t, add != "" && add == ge.Additions["task_execution"], "Match(%q).Additions[task_execution] = %q, want the nonempty %s row addition", id, add, ge.Name) } + testassert.Check(t, !slices.Contains(got.IncludeTools, "apply_patch"), "grok should not include apply_patch (not V4A): %v", got.IncludeTools) }) } } @@ -267,11 +231,10 @@ func TestShippedTableMatchesBundledGoogle(t *testing.T) { } { t.Run(id, func(t *testing.T) { got := Match(id) - if got == nil { - t.Fatalf("Match(%q) = nil, want google-task-execution", id) - } - if add := SectionAddition(got, "task_execution"); add == "" || add != ge.Additions["task_execution"] { - t.Fatalf("Match(%q).Additions[task_execution] = %q, want the nonempty %s row addition", id, add, ge.Name) + testassert.Require(t, got != nil, "Match(%q) = nil, want google-task-execution", id) + { + add := SectionAddition(got, "task_execution") + testassert.Require(t, add != "" && add == ge.Additions["task_execution"], "Match(%q).Additions[task_execution] = %q, want the nonempty %s row addition", id, add, ge.Name) } }) } @@ -292,8 +255,9 @@ func TestShippedTableLeavesOtherModelsBaseline(t *testing.T) { "", } { t.Run(id, func(t *testing.T) { - if got := Match(id); got != nil { - t.Fatalf("Match(%q) = %+v, want nil", id, got) + { + got := Match(id) + testassert.Require(t, got == nil, "Match(%q) = %+v, want nil", id, got) } }) } @@ -322,8 +286,9 @@ func TestGlobMatch(t *testing.T) { } for _, tc := range cases { t.Run(tc.glob+"~"+tc.s, func(t *testing.T) { - if got := globMatch(tc.glob, tc.s); got != tc.want { - t.Fatalf("globMatch(%q, %q) = %v, want %v", tc.glob, tc.s, got, tc.want) + { + got := globMatch(tc.glob, tc.s) + testassert.Require(t, got == tc.want, "globMatch(%q, %q) = %v, want %v", tc.glob, tc.s, got, tc.want) } }) } diff --git a/internal/agents/loader_test.go b/internal/agents/loader_test.go index 7690eb7d..63fb0564 100644 --- a/internal/agents/loader_test.go +++ b/internal/agents/loader_test.go @@ -5,6 +5,8 @@ import ( "reflect" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // retainedSummarizerPrompt is the compact built-in's retained summarizer @@ -33,63 +35,36 @@ Be specific — include file paths, function names, error messages, line numbers func TestResolveBuiltinsInheritThroughPrimary(t *testing.T) { cfg, err := ParseWithCapabilities([]byte(`{"primary":{"model":"test/base-model"},"secondary":{"model":"test/secondary-model"}}`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) secondary, err := cfg.Resolve("secondary") - if err != nil { - t.Fatalf("Resolve(secondary): %v", err) - } - if secondary.Model != "test/secondary-model" || secondary.SystemPrompt != SystemPromptSimple { - t.Fatalf("secondary = model:%q system_prompt:%q, want its own overlay and simple prompt", secondary.Model, secondary.SystemPrompt) - } + testassert.NoError(t, err) + testassert.Require(t, secondary.Model == "test/secondary-model" && secondary.SystemPrompt == SystemPromptSimple, "secondary = model:%q system_prompt:%q, want its own overlay and simple prompt", secondary.Model, secondary.SystemPrompt) explore, err := cfg.Resolve("explore") - if err != nil { - t.Fatalf("Resolve(explore): %v", err) - } - if explore.Model != "test/secondary-model" { - t.Fatalf("explore model = %q, want secondary inheritance", explore.Model) - } - if explore.SystemPrompt != SystemPromptSimple { - t.Fatalf("explore system prompt = %q, want simple", explore.SystemPrompt) - } - if !explore.LSP || !explore.Subagent || !explore.Readonly { - t.Fatalf("explore booleans = lsp:%v subagent:%v readonly:%v", explore.LSP, explore.Subagent, explore.Readonly) - } + testassert.NoError(t, err) + testassert.Require(t, explore.Model == "test/secondary-model" && explore.SystemPrompt == SystemPromptSimple && explore.LSP && explore.Subagent && explore.Readonly, "got %v, want %v; got %v, want %v; explore booleans = lsp:%v subagent:%v readonly:%v", explore.Model, "test/secondary-model", explore.SystemPrompt, SystemPromptSimple, explore.LSP, explore.Subagent, explore.Readonly) review, err := cfg.Resolve("review") - if err != nil { - t.Fatalf("Resolve(review): %v", err) - } - if !reflect.DeepEqual(review.Tools, StandardTools) || review.Model != "test/secondary-model" || review.SystemPrompt != SystemPromptSimple { - t.Fatalf("review = model:%q system_prompt:%q tools:%v, want secondary's resolved values inherited unmodified", review.Model, review.SystemPrompt, review.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(review.Tools, StandardTools) && review.Model == "test/secondary-model" && review.SystemPrompt == SystemPromptSimple, "review = model:%q system_prompt:%q tools:%v, want secondary's resolved values inherited unmodified", review.Model, review.SystemPrompt, review.Tools) } func TestRetainedBuiltinRosterOrderAndResolution(t *testing.T) { cfg, err := ParseWithCapabilities([]byte(`{}`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) wantOrder := []string{"primary", "secondary", "explore", "review", "compact"} got := namesOf(cfg.All()) - if !reflect.DeepEqual(got, wantOrder) { - t.Fatalf("builtin roster = %v, want %v in order", got, wantOrder) - } + testassert.Require(t, reflect.DeepEqual(got, wantOrder), "builtin roster = %v, want %v in order", got, wantOrder) for _, name := range wantOrder { resolved, err := cfg.Resolve(name) - if err != nil { - t.Fatalf("Resolve(%s): %v", name, err) - } - if !resolved.Builtin { - t.Fatalf("%s Builtin = false, want true", name) - } + testassert.Require(t, err == nil, "Resolve(%s): %v", name, err) + testassert.Require(t, resolved.Builtin, "%s Builtin = false, want true", name) } - if _, err := cfg.Resolve("plan"); err == nil || !strings.Contains(err.Error(), `unknown agent type "plan"`) { - t.Fatalf("Resolve(plan) error = %v, want unknown agent type before any resolution", err) + { + _, err := cfg.Resolve("plan") + testassert.Require(t, err != nil && strings.Contains(err.Error(), `unknown agent type "plan"`), "Resolve(plan) error = %v, want unknown agent type before any resolution", err) } } @@ -98,38 +73,21 @@ func TestCustomTypeNamedPlanFollowsOrdinaryRules(t *testing.T) { "primary": {"model":"test/base-model"}, "plan": {"prompt":"Write design notes.","tools":["read_file"],"readonly":true,"write_dir":"docs","description":"Notes writer."} }`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) resolved, err := cfg.Resolve("plan") - if err != nil { - t.Fatalf("Resolve(plan): %v", err) - } - if resolved.Builtin { - t.Fatal(`custom type "plan" marked as builtin`) - } - if resolved.Model != "test/base-model" || resolved.SystemPrompt != SystemPromptSimple { - t.Fatalf(`custom plan = model:%q system_prompt:%q, want ordinary secondary inheritance of unset values`, resolved.Model, resolved.SystemPrompt) - } - if !reflect.DeepEqual(resolved.Tools, []string{"read_file"}) { - t.Fatalf("plan tools = %v, want the custom set only", resolved.Tools) - } - if got := strings.TrimSpace(resolved.Prompt); got != "Write design notes." || contains(resolved.Tools, "submit_plan") { - t.Fatalf(`custom plan prompt/tools = %q/%v, want its own prompt without a built-in tool`, resolved.Prompt, resolved.Tools) - } - if !resolved.Readonly || resolved.WriteDir != "docs" { - t.Fatalf("plan readonly/write_dir = %v/%q, want the literal custom values", resolved.Readonly, resolved.WriteDir) - } - if resolved.Description != "Notes writer." || !resolved.Subagent { - t.Fatalf("custom plan description/subagent = %q/%v, want its own description and the inherited subagent flag", resolved.Description, resolved.Subagent) + testassert.NoError(t, err) + testassert.Require(t, !resolved.Builtin, `custom type "plan" marked as builtin`) + testassert.Require(t, resolved.Model == "test/base-model" && resolved.SystemPrompt == SystemPromptSimple && reflect.DeepEqual(resolved.Tools, []string{"read_file"}), "custom plan = model:%q system_prompt:%q, want ordinary secondary inheritance of unset values; plan tools = %v, want the custom set only", resolved.Model, resolved.SystemPrompt, resolved.Tools) + { + got := strings.TrimSpace(resolved.Prompt) + testassert.Require(t, got == "Write design notes." && !contains(resolved.Tools, "submit_plan"), `custom plan prompt/tools = %q/%v, want its own prompt without a built-in tool`, resolved.Prompt, resolved.Tools) } + testassert.Require(t, resolved.Readonly && resolved.WriteDir == "docs" && resolved.Description == "Notes writer." && resolved.Subagent, "plan readonly/write_dir = %v/%q, want the literal custom values; custom plan description/subagent = %q/%v, want its own description and the inherited subagent flag", resolved.Readonly, resolved.WriteDir, resolved.Description, resolved.Subagent) all := namesOf(cfg.All()) wantOrder := []string{"primary", "secondary", "explore", "review", "compact", "plan"} - if !reflect.DeepEqual(all, wantOrder) { - t.Fatalf("All = %v, want builtins in order then the custom type", all) - } + testassert.Require(t, reflect.DeepEqual(all, wantOrder), "All = %v, want builtins in order then the custom type", all) } func TestSubmitPlanToolIsRejectedInCustomDefinitions(t *testing.T) { @@ -137,28 +95,19 @@ func TestSubmitPlanToolIsRejectedInCustomDefinitions(t *testing.T) { "planner": {"tools":["read_file","submit_plan"]}, "reader": {"tools":["read_file"]} }`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) - if _, err := cfg.Resolve("planner"); err == nil || !strings.Contains(err.Error(), `unknown agent type`) { - t.Fatalf(`Resolve(planner with submit_plan) error = %v, want the entry dropped as unknown`, err) + { + _, err := cfg.Resolve("planner") + testassert.Require(t, err != nil && strings.Contains(err.Error(), `unknown agent type`), `Resolve(planner with submit_plan) error = %v, want the entry dropped as unknown`, err) } reader, err := cfg.Resolve("reader") - if err != nil { - t.Fatalf("Resolve(reader): %v", err) - } - if !reflect.DeepEqual(reader.Tools, []string{"read_file"}) { - t.Fatalf("known-tool custom definition tools = %v, want it retained", reader.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(reader.Tools, []string{"read_file"}), "known-tool custom definition tools = %v, want it retained", reader.Tools) warnings := cfg.Warnings() - if len(warnings) != 1 || warnings[0].Kind != "invalid_agent_type" || warnings[0].Name != "planner" { - t.Fatalf("warnings = %#v, want one invalid custom warning naming planner", warnings) - } - if !strings.Contains(warnings[0].Message, `unknown tool "submit_plan"`) { - t.Fatalf(`warning message = %q, want the unknown submit_plan tool named`, warnings[0].Message) - } + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "invalid_agent_type" && warnings[0].Name == "planner", "warnings = %#v, want one invalid custom warning naming planner", warnings) + testassert.Require(t, strings.Contains(warnings[0].Message, `unknown tool "submit_plan"`), `warning message = %q, want the unknown submit_plan tool named`, warnings[0].Message) } func TestExecutePendingToolIsRejectedInCustomDefinitions(t *testing.T) { @@ -166,57 +115,32 @@ func TestExecutePendingToolIsRejectedInCustomDefinitions(t *testing.T) { "stager": {"tools":["read_file","execute_pending"]}, "reader": {"tools":["read_file"]} }`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) - if _, err := cfg.Resolve("stager"); err == nil || !strings.Contains(err.Error(), `unknown agent type`) { - t.Fatalf(`Resolve(stager with execute_pending) error = %v, want the entry dropped as unknown`, err) + { + _, err := cfg.Resolve("stager") + testassert.Require(t, err != nil && strings.Contains(err.Error(), `unknown agent type`), `Resolve(stager with execute_pending) error = %v, want the entry dropped as unknown`, err) } reader, err := cfg.Resolve("reader") - if err != nil { - t.Fatalf("Resolve(reader): %v", err) - } - if !reflect.DeepEqual(reader.Tools, []string{"read_file"}) { - t.Fatalf("known-tool custom definition tools = %v, want it retained", reader.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(reader.Tools, []string{"read_file"}), "known-tool custom definition tools = %v, want it retained", reader.Tools) warnings := cfg.Warnings() - if len(warnings) != 1 || warnings[0].Kind != "invalid_agent_type" || warnings[0].Name != "stager" { - t.Fatalf("warnings = %#v, want one invalid custom warning naming stager", warnings) - } - if !strings.Contains(warnings[0].Message, `unknown tool "execute_pending"`) { - t.Fatalf(`warning message = %q, want the unknown execute_pending tool named`, warnings[0].Message) - } + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "invalid_agent_type" && warnings[0].Name == "stager", "warnings = %#v, want one invalid custom warning naming stager", warnings) + testassert.Require(t, strings.Contains(warnings[0].Message, `unknown tool "execute_pending"`), `warning message = %q, want the unknown execute_pending tool named`, warnings[0].Message) } func TestBuiltinsExposeConcreteRootAndCodeOnlyCompact(t *testing.T) { cfg, err := ParseWithCapabilities([]byte(`{}`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) primary, err := cfg.Resolve("primary") - if err != nil { - t.Fatalf("Resolve(primary): %v", err) - } - if primary.SystemPrompt != SystemPromptFull || !reflect.DeepEqual(primary.Tools, StandardTools) { - t.Fatalf("primary = %+v, want full prompt and standard tools", primary) - } - if !primary.LSP || primary.Readonly || primary.Subagent { - t.Fatalf("primary booleans = lsp:%v readonly:%v subagent:%v", primary.LSP, primary.Readonly, primary.Subagent) - } + testassert.NoError(t, err) + testassert.Require(t, primary.SystemPrompt == SystemPromptFull && reflect.DeepEqual(primary.Tools, StandardTools) && primary.LSP && !primary.Readonly && !primary.Subagent, "primary = %+v, want full prompt and standard tools; primary booleans = lsp:%v readonly:%v subagent:%v", primary, primary.LSP, primary.Readonly, primary.Subagent) compactAgent, err := cfg.Resolve("compact") - if err != nil { - t.Fatalf("Resolve(compact): %v", err) - } - if compactAgent.SystemPrompt != SystemPromptNone || compactAgent.Prompt != retainedSummarizerPrompt { - t.Fatalf("compact prompt = system:%q body:%q", compactAgent.SystemPrompt, compactAgent.Prompt) - } - if len(compactAgent.Tools) != 0 || compactAgent.LSP || !compactAgent.Readonly || compactAgent.Subagent { - t.Fatalf("compact capability fields = %+v", compactAgent) - } + testassert.NoError(t, err) + testassert.Require(t, compactAgent.SystemPrompt == SystemPromptNone && compactAgent.Prompt == retainedSummarizerPrompt && len(compactAgent.Tools) == 0 && !compactAgent.LSP && compactAgent.Readonly && !compactAgent.Subagent, "compact prompt = system:%q body:%q; compact capability fields = %+v", compactAgent.SystemPrompt, compactAgent.Prompt, compactAgent) } func TestToolsPresenceControlsInheritanceEmptyAndSubset(t *testing.T) { @@ -224,31 +148,17 @@ func TestToolsPresenceControlsInheritanceEmptyAndSubset(t *testing.T) { "empty": {"tools": []}, "reader": {"tools": ["read_file", "run_command"]} }`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) empty, err := cfg.Resolve("empty") - if err != nil { - t.Fatalf("Resolve(empty): %v", err) - } - if len(empty.Tools) != 0 { - t.Fatalf("empty tools = %v, want explicit empty set", empty.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, len(empty.Tools) == 0, "empty tools = %v, want explicit empty set", empty.Tools) reader, err := cfg.Resolve("reader") - if err != nil { - t.Fatalf("Resolve(reader): %v", err) - } - if !reflect.DeepEqual(reader.Tools, []string{"read_file", "run_command"}) { - t.Fatalf("reader tools = %v, want exact subset", reader.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(reader.Tools, []string{"read_file", "run_command"}), "reader tools = %v, want exact subset", reader.Tools) review, err := cfg.Resolve("review") - if err != nil { - t.Fatalf("Resolve(review): %v", err) - } - if !reflect.DeepEqual(review.Tools, StandardTools) { - t.Fatalf("review tools = %v, want inherited standard tools", review.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(review.Tools, StandardTools), "review tools = %v, want inherited standard tools", review.Tools) } func TestBuiltinLockedFieldsIgnoredAndModelHonored(t *testing.T) { @@ -261,19 +171,10 @@ func TestBuiltinLockedFieldsIgnoredAndModelHonored(t *testing.T) { "description": "override" } }`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) explore, err := cfg.Resolve("explore") - if err != nil { - t.Fatalf("Resolve(explore): %v", err) - } - if explore.Model != "test/explore-model" { - t.Fatalf("explore model = %q, want override", explore.Model) - } - if explore.SystemPrompt != SystemPromptSimple || !explore.Readonly || explore.Description == "override" || len(explore.Tools) == 0 { - t.Fatalf("locked fields were not ignored: %+v", explore) - } + testassert.NoError(t, err) + testassert.Require(t, explore.Model == "test/explore-model" && explore.SystemPrompt == SystemPromptSimple && explore.Readonly && explore.Description != "override" && len(explore.Tools) != 0, "got %v, want %v; locked fields were not ignored: %+v", explore.Model, "test/explore-model", explore) if len(cfg.Warnings()) != 0 { t.Fatalf("warnings = %#v, want malformed locked built-in fields ignored", cfg.Warnings()) } @@ -288,48 +189,36 @@ func TestInvalidCustomDropsOnlyBadEntryWithWarning(t *testing.T) { "bad_shape": "not an object", "bad_tools_shape": {"tools": "read_file"} }`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } - if _, err := cfg.Resolve("good"); err != nil { - t.Fatalf("Resolve(good): %v", err) + testassert.NoError(t, err) + { + _, err := cfg.Resolve("good") + testassert.NoError(t, err) } for _, name := range []string{"bad_tool", "bad_prompt", "bad_model", "bad_shape", "bad_tools_shape"} { - if _, err := cfg.Resolve(name); err == nil { - t.Fatalf("Resolve(%s) succeeded, want dropped", name) + { + _, err := cfg.Resolve(name) + testassert.Require(t, err != nil, "Resolve(%s) succeeded, want dropped", name) } } warnings := cfg.Warnings() - if len(warnings) != 5 { - t.Fatalf("warnings = %#v, want 5 invalid custom warnings", warnings) - } + testassert.Require(t, len(warnings) == 5, "warnings = %#v, want 5 invalid custom warnings", warnings) for _, warning := range warnings { - if warning.Kind != "invalid_agent_type" { - t.Fatalf("warning kind = %q, want invalid_agent_type", warning.Kind) - } + testassert.Equal(t, warning.Kind, "invalid_agent_type") } } func TestBuiltinNameWinsOverUserLockedFields(t *testing.T) { cfg, err := ParseWithCapabilities([]byte(`{"explore":{"prompt":"custom prompt","description":"custom","subagent":false}}`), nil, StandardTools, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) explore, err := cfg.Resolve("explore") - if err != nil { - t.Fatalf("Resolve(explore): %v", err) - } - if explore.Prompt == "custom prompt" || explore.Description == "custom" || !explore.Subagent { - t.Fatalf("builtin was overridden by colliding user entry: %+v", explore) - } + testassert.NoError(t, err) + testassert.Require(t, explore.Prompt != "custom prompt" && explore.Description != "custom" && explore.Subagent, "builtin was overridden by colliding user entry: %+v", explore) } func TestPathForConfigUsesConfigDirectory(t *testing.T) { got := PathForConfig(filepath.Join("/tmp", "lightcode-test", "config.json")) want := filepath.Join("/tmp", "lightcode-test", "agents.json") - if got != want { - t.Fatalf("PathForConfig = %q, want %q", got, want) - } + testassert.Equal(t, got, want) } func contains(values []string, want string) bool { @@ -365,60 +254,48 @@ func TestParseWithCapabilitiesSelection(t *testing.T) { "broken": {"capabilities": "not-a-list"}, "custom_tools": {"tools": ["own_tool", "own_tool", "read_file"]} }`), []string{"cap-a", "cap-b"}, []string{"own_tool", "read_file"}, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) for _, drop := range []string{"unknown_cap", "duplicate_cap", "broken"} { - if _, err := cfg.Resolve(drop); err == nil { - t.Fatalf("Resolve(%s) succeeded, want dropped", drop) + { + _, err := cfg.Resolve(drop) + testassert.Require(t, err != nil, "Resolve(%s) succeeded, want dropped", drop) } } warnings := cfg.Warnings() - if len(warnings) != 3 { - t.Fatalf("warnings = %#v, want three invalid_agent_type drops", warnings) - } + testassert.Require(t, len(warnings) == 3, "warnings = %#v, want three invalid_agent_type drops", warnings) for _, warning := range warnings { - if warning.Kind != "invalid_agent_type" { - t.Fatalf("warning kind = %q, want invalid_agent_type", warning.Kind) - } + testassert.Equal(t, warning.Kind, "invalid_agent_type") } plan, err := cfg.Resolve("plan") - if err != nil { - t.Fatalf("Resolve(plan): %v", err) - } - if want := []string{"cap-b", "cap-a"}; !reflect.DeepEqual(plan.Capabilities, want) { - t.Fatalf("plan capabilities = %q, want the selected order", plan.Capabilities) + testassert.NoError(t, err) + { + want := []string{"cap-b", "cap-a"} + testassert.Require(t, reflect.DeepEqual(plan.Capabilities, want), "plan capabilities = %q, want the selected order", plan.Capabilities) } - if want := []string{"own_tool"}; !reflect.DeepEqual(plan.Tools, want) { - t.Fatalf("plan tools = %q, want the declared selection", plan.Tools) + { + want := []string{"own_tool"} + testassert.Require(t, reflect.DeepEqual(plan.Tools, want), "plan tools = %q, want the declared selection", plan.Tools) } // A custom type inherits through secondary and primary; the built-ins // keep their locked fields and an empty capability default because this // caller supplies no default list. for _, name := range []string{"sweeper", "plain", "secondary"} { resolved, err := cfg.Resolve(name) - if err != nil { - t.Fatalf("Resolve(%s): %v", name, err) - } - if resolved.Capabilities != nil { - t.Fatalf("%s capabilities = %q, want absent, cleared, or inherited-empty", name, resolved.Capabilities) - } + testassert.Require(t, err == nil, "Resolve(%s): %v", name, err) + testassert.Require(t, resolved.Capabilities == nil, "%s capabilities = %q, want absent, cleared, or inherited-empty", name, resolved.Capabilities) } for _, resolved := range cfg.All() { - if resolved.Builtin && resolved.Capabilities != nil { - t.Fatalf("builtin %s capabilities = %q, want the empty default", resolved.Name, resolved.Capabilities) - } + testassert.Require(t, !resolved.Builtin || resolved.Capabilities == nil, "builtin %s capabilities = %q, want the empty default", resolved.Name, resolved.Capabilities) } // Configured tool names are retained without an available-tool list, // duplicates included; a repeated name neither drops the definition nor // changes the resolved order. worker, err := cfg.Resolve("custom_tools") - if err != nil { - t.Fatalf("Resolve(custom_tools): %v", err) - } - if want := []string{"own_tool", "own_tool", "read_file"}; !reflect.DeepEqual(worker.Tools, want) { - t.Fatalf("custom_tools = %q, want %q retained", worker.Tools, want) + testassert.NoError(t, err) + { + want := []string{"own_tool", "own_tool", "read_file"} + testassert.Require(t, reflect.DeepEqual(worker.Tools, want), "custom_tools = %q, want %q retained", worker.Tools, want) } } @@ -426,19 +303,14 @@ func TestParseWithCapabilitiesRejectsEmptyToolNames(t *testing.T) { // A list containing only the empty name: the empty-name rule is the // only one that can fire. cfg, err := ParseWithCapabilities([]byte(`{"bad": {"tools": [""]}}`), nil, nil, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } - if _, err := cfg.Resolve("bad"); err == nil { - t.Fatal("Resolve(bad) succeeded, want dropped for an empty tool name") + testassert.NoError(t, err) + { + _, err := cfg.Resolve("bad") + testassert.Require(t, err != nil, "Resolve(bad) succeeded, want dropped for an empty tool name") } warnings := cfg.Warnings() - if len(warnings) != 1 || warnings[0].Kind != "invalid_agent_type" { - t.Fatalf("warnings = %#v, want one invalid_agent_type drop", warnings) - } - if warnings[0].Message != "tool name is empty" { - t.Fatalf("warning message = %q, want the empty-tool-name rejection text", warnings[0].Message) - } + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "invalid_agent_type", "warnings = %#v, want one invalid_agent_type drop", warnings) + testassert.Equal(t, warnings[0].Message, "tool name is empty") } // TestParseWithCapabilitiesToolUniverseValidation proves the compiled-tool @@ -455,84 +327,63 @@ func TestParseWithCapabilitiesToolUniverseValidation(t *testing.T) { "empty": {"tools": []} }`) cfg, err := ParseWithCapabilities(doc, nil, []string{"own_tool", "read_file", "write_file"}, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } - if _, err := cfg.Resolve("unknown_tool"); err == nil { - t.Fatal("Resolve(unknown_tool) succeeded, want dropped for an uncompiled tool name") + testassert.NoError(t, err) + { + _, err := cfg.Resolve("unknown_tool") + testassert.Require(t, err != nil, "Resolve(unknown_tool) succeeded, want dropped for an uncompiled tool name") } warnings := cfg.Warnings() - if len(warnings) != 1 || warnings[0].Kind != "invalid_agent_type" || warnings[0].Name != "unknown_tool" { - t.Fatalf("warnings = %#v, want one invalid_agent_type drop for unknown_tool", warnings) - } - if !strings.Contains(warnings[0].Message, "not_compiled") { - t.Fatalf("warning message = %q, want the unknown tool name", warnings[0].Message) - } + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "invalid_agent_type" && warnings[0].Name == "unknown_tool", "warnings = %#v, want one invalid_agent_type drop for unknown_tool", warnings) + testassert.Require(t, strings.Contains(warnings[0].Message, "not_compiled"), "warning message = %q, want the unknown tool name", warnings[0].Message) // Duplicates remain valid and keep their resolved order and repetition. worker, err := cfg.Resolve("duplicate_tools") - if err != nil { - t.Fatalf("Resolve(duplicate_tools): %v", err) - } - if want := []string{"own_tool", "own_tool", "read_file"}; !reflect.DeepEqual(worker.Tools, want) { - t.Fatalf("duplicate_tools = %q, want %q retained", worker.Tools, want) + testassert.NoError(t, err) + { + want := []string{"own_tool", "own_tool", "read_file"} + testassert.Require(t, reflect.DeepEqual(worker.Tools, want), "duplicate_tools = %q, want %q retained", worker.Tools, want) } // Omitted stays omitted at the definition level (its resolved value still // inherits) and an explicit empty list stays empty. - if cfg.defs["omitted"].Tools != nil { - t.Fatalf("omitted definition tools = %v, want nil", cfg.defs["omitted"].Tools) - } + testassert.Require(t, cfg.defs["omitted"].Tools == nil, "omitted definition tools = %v, want nil", cfg.defs["omitted"].Tools) // The explicit empty list stays empty at the definition level (its // resolved projection normalizes zero-length slices to nil, the package's // empty form). - if emptyDef := cfg.defs["empty"]; emptyDef.Tools == nil || len(*emptyDef.Tools) != 0 { - t.Fatalf("empty definition tools = %#v, want an explicit empty list", emptyDef.Tools) + { + emptyDef := cfg.defs["empty"] + testassert.Require(t, emptyDef.Tools != nil && len(*emptyDef.Tools) == 0, "empty definition tools = %#v, want an explicit empty list", emptyDef.Tools) } - if empty, err := cfg.Resolve("empty"); err != nil || empty.Tools != nil { - t.Fatalf("resolved empty tools = %#v (%v), want the normalized empty form", empty.Tools, err) + { + empty, err := cfg.Resolve("empty") + testassert.Require(t, err == nil && empty.Tools == nil, "resolved empty tools = %#v (%v), want the normalized empty form", empty.Tools, err) } } func TestParseWithCapabilitiesIntersectsDefaultTools(t *testing.T) { cfg, err := ParseWithCapabilities([]byte(`{}`), nil, []string{"read_file", "run_command", "sleep"}, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) primary, err := cfg.Resolve("primary") - if err != nil { - t.Fatalf("Resolve(primary): %v", err) - } + testassert.NoError(t, err) // Default and inherited tools are intersected with the compiled universe: // process and task stay in the retained roster input but are not // fabricated as executable placeholders, and nothing outside the universe // survives. - if want := []string{"read_file", "run_command", "sleep"}; !reflect.DeepEqual(primary.Tools, want) { - t.Fatalf("primary default tools = %q, want the intersected roster %q", primary.Tools, want) + { + want := []string{"read_file", "run_command", "sleep"} + testassert.Require(t, reflect.DeepEqual(primary.Tools, want), "primary default tools = %q, want the intersected roster %q", primary.Tools, want) } secondary, err := cfg.Resolve("secondary") - if err != nil { - t.Fatalf("Resolve(secondary): %v", err) - } - if !reflect.DeepEqual(secondary.Tools, primary.Tools) { - t.Fatalf("secondary inherited tools = %q, want the intersected default %q", secondary.Tools, primary.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(secondary.Tools, primary.Tools), "secondary inherited tools = %q, want the intersected default %q", secondary.Tools, primary.Tools) // The retained roster input itself is unchanged. for _, name := range []string{"process", "task", "edit_file", "apply_patch"} { - if !contains(StandardTools, name) { - t.Fatalf("StandardTools lost %q: %q", name, StandardTools) - } + testassert.Require(t, contains(StandardTools, name), "StandardTools lost %q: %q", name, StandardTools) } // An empty compiled universe leaves the defaults empty, not fabricated. none, err := ParseWithCapabilities([]byte(`{}`), nil, nil, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) nonePrimary, err := none.Resolve("primary") - if err != nil { - t.Fatalf("Resolve(primary): %v", err) - } - if len(nonePrimary.Tools) != 0 { - t.Fatalf("primary tools under an empty universe = %q, want none fabricated", nonePrimary.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, len(nonePrimary.Tools) == 0, "primary tools under an empty universe = %q, want none fabricated", nonePrimary.Tools) } func TestParseWithCapabilitiesDefaultProjection(t *testing.T) { @@ -543,63 +394,40 @@ func TestParseWithCapabilitiesDefaultProjection(t *testing.T) { "model_a": {"model": "prov/a"}, "model_b": {"model": "prov/b"} }`), []string{"cap-a", "cap-b", "model_adaptation"}, []string{"read_file"}, []string{"model_adaptation"}) - if err != nil { - t.Fatalf("ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) // The default list installs on the built-in primary definition alone; // ordinary inheritance propagates it through secondary to customs. want := []string{"model_adaptation"} primary, err := cfg.Resolve("primary") - if err != nil { - t.Fatalf("Resolve(primary): %v", err) - } - if !reflect.DeepEqual(primary.Capabilities, want) { - t.Fatalf("primary capabilities = %q, want the default %q", primary.Capabilities, want) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(primary.Capabilities, want), "primary capabilities = %q, want the default %q", primary.Capabilities, want) for _, name := range []string{"secondary", "explore", "review", "compact", "plain", "model_a", "model_b"} { resolved, err := cfg.Resolve(name) - if err != nil { - t.Fatalf("Resolve(%s): %v", name, err) - } - if !reflect.DeepEqual(resolved.Capabilities, want) { - t.Fatalf("%s capabilities = %q, want the inherited default %q", name, resolved.Capabilities, want) - } + testassert.Require(t, err == nil, "Resolve(%s): %v", name, err) + testassert.Require(t, reflect.DeepEqual(resolved.Capabilities, want), "%s capabilities = %q, want the inherited default %q", name, resolved.Capabilities, want) } // An explicit selection overrides the default and an explicit empty list // clears it. plan, err := cfg.Resolve("plan") - if err != nil { - t.Fatalf("Resolve(plan): %v", err) - } - if want := []string{"cap-a"}; !reflect.DeepEqual(plan.Capabilities, want) { - t.Fatalf("plan capabilities = %q, want the explicit selection %q", plan.Capabilities, want) + testassert.NoError(t, err) + { + want := []string{"cap-a"} + testassert.Require(t, reflect.DeepEqual(plan.Capabilities, want), "plan capabilities = %q, want the explicit selection %q", plan.Capabilities, want) } sweeper, err := cfg.Resolve("sweeper") - if err != nil { - t.Fatalf("Resolve(sweeper): %v", err) - } - if sweeper.Capabilities != nil { - t.Fatalf("sweeper capabilities = %q, want the explicit empty clear", sweeper.Capabilities) - } + testassert.NoError(t, err) + testassert.Require(t, sweeper.Capabilities == nil, "sweeper capabilities = %q, want the explicit empty clear", sweeper.Capabilities) } func TestParseWithCapabilitiesEmptyDefaultsLeaveBaseline(t *testing.T) { const doc = `{"plain": {}}` nilDefaults, err := ParseWithCapabilities([]byte(doc), []string{"cap-a"}, nil, nil) - if err != nil { - t.Fatalf("ParseWithCapabilities(nil defaults): %v", err) - } + testassert.NoError(t, err) emptyDefaults, err := ParseWithCapabilities([]byte(doc), []string{"cap-a"}, nil, []string{}) - if err != nil { - t.Fatalf("ParseWithCapabilities(empty defaults): %v", err) - } - if !reflect.DeepEqual(nilDefaults.All(), emptyDefaults.All()) { - t.Fatal("an empty default list changed the resolved definitions") - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(nilDefaults.All(), emptyDefaults.All()), "an empty default list changed the resolved definitions") // Both stay byte-identical to the built-ins' empty capability default. for _, resolved := range nilDefaults.All() { - if resolved.Capabilities != nil { - t.Fatalf("%s capabilities = %q, want the empty default", resolved.Name, resolved.Capabilities) - } + testassert.Require(t, resolved.Capabilities == nil, "%s capabilities = %q, want the empty default", resolved.Name, resolved.Capabilities) } } diff --git a/internal/atomicfs/atomicfs_test.go b/internal/atomicfs/atomicfs_test.go index 670fea45..bef3d01f 100644 --- a/internal/atomicfs/atomicfs_test.go +++ b/internal/atomicfs/atomicfs_test.go @@ -13,41 +13,29 @@ import ( "sync" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestWriteReplacesAtomicallyWithMode(t *testing.T) { dir := t.TempDir() sub := filepath.Join(dir, "sub") - if err := os.MkdirAll(sub, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(sub, 0o700)) path := filepath.Join(sub, "rec.json") - if err := Write(path, []byte("first"), 0o600); err != nil { - t.Fatalf("first write: %v", err) - } + testassert.NoError(t, Write(path, []byte("first"), 0o600)) got, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read: %v", err) - } - if string(got) != "first" { - t.Fatalf("content = %q, want first", got) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == "first", "content = %q, want first", got) info, err := os.Stat(path) - if err != nil { - t.Fatalf("stat: %v", err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o600 { t.Fatalf("mode = %v, want 0600", info.Mode().Perm()) } - if err := Write(path, []byte("second"), 0o600); err != nil { - t.Fatalf("second write: %v", err) - } + testassert.NoError(t, Write(path, []byte("second"), 0o600)) got, _ = os.ReadFile(path) - if string(got) != "second" { - t.Fatalf("content after replace = %q, want second", got) - } + testassert.Require(t, string(got) == "second", "content after replace = %q, want second", got) // No temp file is left behind in the destination directory. entries, _ := os.ReadDir(filepath.Dir(path)) @@ -64,11 +52,13 @@ func TestWriteReplacesAtomicallyWithMode(t *testing.T) { func TestWriteDoesNotCreateDestinationDir(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "gone", "rec.json") - if err := Write(path, []byte("data"), 0o600); err == nil { - t.Fatal("Write into a missing directory succeeded; want failure") + { + err := Write(path, []byte("data"), 0o600) + testassert.Require(t, err != nil, "Write into a missing directory succeeded; want failure") } - if _, err := os.Stat(filepath.Join(dir, "gone")); !os.IsNotExist(err) { - t.Fatalf("Write recreated the missing directory: %v", err) + { + _, err := os.Stat(filepath.Join(dir, "gone")) + testassert.Require(t, os.IsNotExist(err), "Write recreated the missing directory: %v", err) } } @@ -77,30 +67,18 @@ func TestCreateExclusiveRefusesOverwrite(t *testing.T) { path := filepath.Join(dir, "config.json") created, err := CreateExclusive(path, []byte("mine"), 0o600) - if err != nil { - t.Fatalf("create: %v", err) - } - if !created { - t.Fatal("first CreateExclusive: created = false, want true") - } + testassert.NoError(t, err) + testassert.Require(t, created, "first CreateExclusive: created = false, want true") created, err = CreateExclusive(path, []byte("theirs"), 0o600) - if err != nil { - t.Fatalf("second create: %v", err) - } - if created { - t.Fatal("second CreateExclusive: created = true, want false") - } + testassert.NoError(t, err) + testassert.Require(t, !created, "second CreateExclusive: created = true, want false") got, _ := os.ReadFile(path) - if string(got) != "mine" { - t.Fatalf("existing file overwritten: %q, want mine", got) - } + testassert.Require(t, string(got) == "mine", "existing file overwritten: %q, want mine", got) // No temp file is left behind after either call. entries, _ := os.ReadDir(dir) - if len(entries) != 1 || entries[0].Name() != "config.json" { - t.Fatalf("unexpected dir entries: %v", entries) - } + testassert.Require(t, len(entries) == 1 && entries[0].Name() == "config.json", "unexpected dir entries: %v", entries) } // TestPrePublicationSyncFailureRows covers both publishers with absent and @@ -132,9 +110,7 @@ func TestPrePublicationSyncFailureRows(t *testing.T) { if tc.present { created, err := tc.publish(path, []byte("original"), 0o600) - if err != nil || !created { - t.Fatalf("seed publish: (%v, %v)", created, err) - } + testassert.Require(t, err == nil && created, "seed publish: (%v, %v)", created, err) } injected := errors.New("injected temp sync failure") @@ -142,32 +118,20 @@ func TestPrePublicationSyncFailureRows(t *testing.T) { defer func() { SyncFileFunc = nil }() created, err := tc.publish(path, []byte("replacement"), 0o600) - if err == nil { - t.Fatal("publish succeeded; want the injected temp sync failure") - } - if created { - t.Fatal("created = true on a failed write") - } - if tc.wantErrPart != "" && !strings.Contains(err.Error(), tc.wantErrPart) { - t.Fatalf("error = %v, want a %s error", err, tc.wantErrPart) - } + testassert.Require(t, err != nil, "publish succeeded; want the injected temp sync failure") + testassert.Require(t, !created, "created = true on a failed write") + testassert.Require(t, tc.wantErrPart == "" || strings.Contains(err.Error(), tc.wantErrPart), "error = %v, want a %s error", err, tc.wantErrPart) if tc.present { got, readErr := os.ReadFile(path) - if readErr != nil { - t.Fatalf("read destination: %v", readErr) - } - if string(got) != "original" { - t.Fatalf("destination = %q after the failed write, want original", got) - } + testassert.Require(t, readErr == nil, "read destination: %v", readErr) + testassert.Require(t, string(got) == "original", "destination = %q after the failed write, want original", got) } else if _, statErr := os.Stat(path); !os.IsNotExist(statErr) { t.Fatalf("path exists after the aborted write: %v", statErr) } entries, _ := os.ReadDir(dir) if tc.present { - if len(entries) != 1 || entries[0].Name() != "rec.json" { - t.Fatalf("leftover entries after the aborted write: %v", entries) - } + testassert.Require(t, len(entries) == 1 && entries[0].Name() == "rec.json", "leftover entries after the aborted write: %v", entries) } else if len(entries) != 0 { t.Fatalf("leftover entries after the aborted write: %v", entries) } @@ -189,21 +153,14 @@ func TestWriteDirSyncFailureAfterPublicationReturnsSuccess(t *testing.T) { defer func() { SyncDirFunc = nil }() stderr := captureStderr(t) - if err := Write(path, []byte("data"), 0o600); err != nil { - t.Fatalf("Write returned %v; want nil after publication", err) - } + testassert.NoError(t, Write(path, []byte("data"), 0o600)) got, err := os.ReadFile(path) - if err != nil { - t.Fatalf("published file unreadable: %v", err) - } - if string(got) != "data" { - t.Fatalf("published content = %q, want data", got) - } - if len(synced) != 1 || synced[0] != dir { - t.Fatalf("dir syncs = %v, want [%s]", synced, dir) - } - if out := stderr(); !strings.Contains(out, "injected dir sync failure") { - t.Fatalf("stderr = %q, want the dir sync diagnostic", out) + testassert.NoError(t, err) + testassert.Require(t, string(got) == "data", "published content = %q, want data", got) + testassert.Require(t, len(synced) == 1 && synced[0] == dir, "dir syncs = %v, want [%s]", synced, dir) + { + out := stderr() + testassert.Require(t, strings.Contains(out, "injected dir sync failure"), "stderr = %q, want the dir sync diagnostic", out) } } @@ -229,23 +186,16 @@ func TestCreateExclusiveDirSyncFailureAfterPublicationReturnsSuccess(t *testing. stderr := captureStderr(t) created, err := CreateExclusive(path, []byte("data"), 0o600) - if err != nil || !created { - t.Fatalf("CreateExclusive = (%v, %v); want (true, nil)", created, err) - } + testassert.Require(t, err == nil && created, "CreateExclusive = (%v, %v); want (true, nil)", created, err) got, err := os.ReadFile(path) - if err != nil { - t.Fatalf("published file unreadable: %v", err) - } - if string(got) != "data" { - t.Fatalf("published content = %q, want data", got) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == "data", "published content = %q, want data", got) // The ancestors are synced before publication; the destination directory // is synced after it, last. - if len(synced) == 0 || synced[len(synced)-1] != dir { - t.Fatalf("dir syncs = %v, want destination dir %s synced last", synced, dir) - } - if out := stderr(); !strings.Contains(out, "injected dir sync failure") { - t.Fatalf("stderr = %q, want the dir sync diagnostic", out) + testassert.Require(t, len(synced) != 0 && synced[len(synced)-1] == dir, "dir syncs = %v, want destination dir %s synced last", synced, dir) + { + out := stderr() + testassert.Require(t, strings.Contains(out, "injected dir sync failure"), "stderr = %q, want the dir sync diagnostic", out) } } @@ -258,9 +208,7 @@ func TestCreateExclusiveDirSyncFailureAfterPublicationReturnsSuccess(t *testing. func TestCreateExclusiveExistingPathPerformsNoDirSync(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "rec.json") - if err := os.WriteFile(path, []byte("mine"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("mine"), 0o600)) dirSyncs := 0 SyncDirFunc = func(d string) error { @@ -275,23 +223,13 @@ func TestCreateExclusiveExistingPathPerformsNoDirSync(t *testing.T) { defer func() { SyncFileFunc = nil }() created, err := CreateExclusive(path, []byte("theirs"), 0o600) - if err != nil || created { - t.Fatalf("CreateExclusive = (%v, %v); want (false, nil)", created, err) - } - if dirSyncs != 0 { - t.Fatalf("destination dir syncs = %d, want 0 (nothing was published)", dirSyncs) - } - if tempSyncs != 1 { - t.Fatalf("temp syncs = %d, want 1 (the temp is always synced before the link attempt)", tempSyncs) - } + testassert.Require(t, err == nil && !created, "CreateExclusive = (%v, %v); want (false, nil)", created, err) + testassert.Equal(t, dirSyncs, 0) + testassert.Equal(t, tempSyncs, 1) got, _ := os.ReadFile(path) - if string(got) != "mine" { - t.Fatalf("existing file changed: %q, want mine", got) - } + testassert.Require(t, string(got) == "mine", "existing file changed: %q, want mine", got) entries, _ := os.ReadDir(dir) - if len(entries) != 1 || entries[0].Name() != "rec.json" { - t.Fatalf("unexpected dir entries: %v", entries) - } + testassert.Require(t, len(entries) == 1 && entries[0].Name() == "rec.json", "unexpected dir entries: %v", entries) } // TestCreateExclusiveSyncsAncestorChainWhenCallerMadeTheDirectory proves a @@ -305,9 +243,7 @@ func TestCreateExclusiveExistingPathPerformsNoDirSync(t *testing.T) { func TestCreateExclusiveSyncsAncestorChainWhenCallerMadeTheDirectory(t *testing.T) { dir := t.TempDir() configDir := filepath.Join(dir, "config") - if err := os.MkdirAll(configDir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(configDir, 0o700)) path := filepath.Join(configDir, ".env") var synced []string @@ -318,9 +254,7 @@ func TestCreateExclusiveSyncsAncestorChainWhenCallerMadeTheDirectory(t *testing. defer func() { SyncDirFunc = nil }() created, err := CreateExclusive(path, []byte("#OPENAI_API_KEY=\n"), 0o600) - if err != nil || !created { - t.Fatalf("CreateExclusive = (%v, %v); want (true, nil)", created, err) - } + testassert.Require(t, err == nil && created, "CreateExclusive = (%v, %v); want (true, nil)", created, err) // Every level from the destination directory's parent upward must have // been synced before publication. @@ -332,17 +266,13 @@ func TestCreateExclusiveSyncsAncestorChainWhenCallerMadeTheDirectory(t *testing. break } } - if !found { - t.Fatalf("ancestor %s was not synced; synced = %v", d, synced) - } + testassert.Require(t, found, "ancestor %s was not synced; synced = %v", d, synced) if d == filepath.Dir(d) { break } } // The destination directory's own sync follows publication, last. - if len(synced) == 0 || synced[len(synced)-1] != configDir { - t.Fatalf("synced = %v, want destination dir %s synced last", synced, configDir) - } + testassert.Require(t, len(synced) != 0 && synced[len(synced)-1] == configDir, "synced = %v, want destination dir %s synced last", synced, configDir) } // TestCreateExclusiveAncestorSyncFailurePublishesNothing proves an injected @@ -368,22 +298,15 @@ func TestCreateExclusiveAncestorSyncFailurePublishesNothing(t *testing.T) { defer func() { SyncDirFunc = nil }() created, err := CreateExclusive(path, []byte("#OPENAI_API_KEY=\n"), 0o600) - if err == nil { - t.Fatal("CreateExclusive succeeded; want the injected ancestor sync failure") - } - if !strings.Contains(err.Error(), "injected ancestor sync failure") { - t.Fatalf("error = %v, want the injected ancestor sync failure", err) - } - if created { - t.Fatal("created = true on an aborted publication") - } - if _, statErr := os.Stat(path); !os.IsNotExist(statErr) { - t.Fatalf("path exists after the aborted publication: %v", statErr) + testassert.Require(t, err != nil, "CreateExclusive succeeded; want the injected ancestor sync failure") + testassert.Require(t, strings.Contains(err.Error(), "injected ancestor sync failure"), "error = %v, want the injected ancestor sync failure", err) + testassert.Require(t, !created, "created = true on an aborted publication") + { + _, statErr := os.Stat(path) + testassert.Require(t, os.IsNotExist(statErr), "path exists after the aborted publication: %v", statErr) } entries, _ := os.ReadDir(filepath.Join(dir, "a", "b")) - if len(entries) != 0 { - t.Fatalf("leftover entries after the aborted publication: %v", entries) - } + testassert.Require(t, len(entries) == 0, "leftover entries after the aborted publication: %v", entries) // The walk started at the destination directory's parent and stopped at // the next level up, where the failure was injected. if len(synced) != 2 || synced[0] != filepath.Join(dir, "a") || synced[1] != dir { @@ -397,9 +320,7 @@ func captureStderr(t *testing.T) func() string { t.Helper() old := os.Stderr f, err := os.CreateTemp(t.TempDir(), "stderr-*") - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) os.Stderr = f t.Cleanup(func() { os.Stderr = old @@ -416,27 +337,19 @@ func TestTryAcquireReportsContention(t *testing.T) { lockPath := filepath.Join(dir, ".locks", "rec.lock") held, ok, err := TryAcquire(lockPath) - if err != nil || !ok { - t.Fatalf("first TryAcquire: ok=%v err=%v, want true/nil", ok, err) - } + testassert.Require(t, err == nil && ok, "first TryAcquire: ok=%v err=%v, want true/nil", ok, err) contender, ok, err := TryAcquire(lockPath) - if err != nil { - t.Fatalf("second TryAcquire: %v", err) - } + testassert.NoError(t, err) if ok { contender.Release() t.Fatal("second TryAcquire acquired while first held; want contention") } - if err := held.Release(); err != nil { - t.Fatalf("release: %v", err) - } + testassert.NoError(t, held.Release()) regained, ok, err := TryAcquire(lockPath) - if err != nil || !ok { - t.Fatalf("TryAcquire after release: ok=%v err=%v, want true/nil", ok, err) - } + testassert.Require(t, err == nil && ok, "TryAcquire after release: ok=%v err=%v, want true/nil", ok, err) regained.Release() } @@ -447,9 +360,7 @@ func TestWithLockSerializesReadModifyWrite(t *testing.T) { dir := t.TempDir() counter := filepath.Join(dir, "counter") lockPath := filepath.Join(dir, ".locks", "counter.lock") - if err := os.WriteFile(counter, []byte("0"), 0o600); err != nil { - t.Fatalf("seed: %v", err) - } + testassert.NoError(t, os.WriteFile(counter, []byte("0"), 0o600)) const workers = 24 var wg sync.WaitGroup @@ -468,18 +379,14 @@ func TestWithLockSerializesReadModifyWrite(t *testing.T) { } return Write(counter, []byte(strconv.Itoa(n+1)), 0o600) }) - if err != nil { - t.Errorf("withlock: %v", err) - } + testassert.Check(t, err == nil, "withlock: %v", err) }() } wg.Wait() raw, _ := os.ReadFile(counter) n, _ := strconv.Atoi(string(raw)) - if n != workers { - t.Fatalf("counter = %d, want %d (lost updates)", n, workers) - } + testassert.Equal(t, n, workers) } // TestWithLockReportsReleaseFailureToStderr proves WithLock retains the @@ -497,12 +404,10 @@ func TestWithLockReportsReleaseFailureToStderr(t *testing.T) { stderr := captureStderr(t) callbackErr := errors.New("callback failure") - err := WithLock(lockPath, func() error { return callbackErr }) - if err != callbackErr { - t.Fatalf("WithLock = %v, want the callback result unchanged", err) - } - if out := stderr(); out != "lightcode: release lock "+lockPath+": "+injected.Error()+"\n" { - t.Fatalf("stderr = %q, want the one exact release diagnostic", out) + testassert.Equal(t, WithLock(lockPath, func() error { return callbackErr }), callbackErr) + { + out := stderr() + testassert.Require(t, out == "lightcode: release lock "+lockPath+": "+injected.Error()+"\n", "stderr = %q, want the one exact release diagnostic", out) } } @@ -517,20 +422,17 @@ func TestWithLockReleasesOnCallbackPanic(t *testing.T) { func() { defer func() { - if r := recover(); r == nil { - t.Fatal("WithLock callback did not panic") + { + r := recover() + testassert.Require(t, r != nil, "WithLock callback did not panic") } }() _ = WithLock(lockPath, func() error { panic("callback panic") }) }() regained, ok, err := TryAcquire(lockPath) - if err != nil { - t.Fatalf("TryAcquire after the recovered panic: %v", err) - } - if !ok { - t.Fatal("lock still held after a panicking WithLock callback; the release did not run") - } + testassert.NoError(t, err) + testassert.Require(t, ok, "lock still held after a panicking WithLock callback; the release did not run") regained.Release() } @@ -548,44 +450,30 @@ func TestTryWithLockRows(t *testing.T) { called = true return nil }) - if !ok || err != nil { - t.Fatalf("TryWithLock = (%v, %v), want (true, nil)", ok, err) - } - if !called { - t.Fatal("callback was not invoked") - } + testassert.Require(t, ok && err == nil, "TryWithLock = (%v, %v), want (true, nil)", ok, err) + testassert.Require(t, called, "callback was not invoked") }) t.Run("contention_skips_callback", func(t *testing.T) { dir := t.TempDir() lockPath := filepath.Join(dir, ".locks", "rec.lock") held, ok, err := TryAcquire(lockPath) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer held.Release() called := false got, err := TryWithLock(lockPath, func() error { called = true return nil }) - if got { - t.Fatal("TryWithLock acquired while the lock was held") - } - if err != nil { - t.Fatalf("TryWithLock contention err = %v, want nil", err) - } - if called { - t.Fatal("callback ran despite contention") - } + testassert.Require(t, !got, "TryWithLock acquired while the lock was held") + testassert.NoError(t, err) + testassert.Require(t, !called, "callback ran despite contention") }) t.Run("callback_error_preserved", func(t *testing.T) { dir := t.TempDir() lockPath := filepath.Join(dir, ".locks", "rec.lock") callbackErr := errors.New("callback failure") ok, err := TryWithLock(lockPath, func() error { return callbackErr }) - if !ok || err != callbackErr { - t.Fatalf("TryWithLock = (%v, %v), want (true, callbackErr)", ok, err) - } + testassert.Require(t, ok && err == callbackErr, "TryWithLock = (%v, %v), want (true, callbackErr)", ok, err) }) t.Run("release_error_diagnostic_only", func(t *testing.T) { dir := t.TempDir() @@ -596,11 +484,10 @@ func TestTryWithLockRows(t *testing.T) { stderr := captureStderr(t) callbackErr := errors.New("callback failure") ok, err := TryWithLock(lockPath, func() error { return callbackErr }) - if !ok || err != callbackErr { - t.Fatalf("TryWithLock = (%v, %v), want (true, callbackErr)", ok, err) - } - if out := stderr(); out != "lightcode: release lock "+lockPath+": "+injected.Error()+"\n" { - t.Fatalf("stderr = %q, want the one exact release diagnostic", out) + testassert.Require(t, ok && err == callbackErr, "TryWithLock = (%v, %v), want (true, callbackErr)", ok, err) + { + out := stderr() + testassert.Require(t, out == "lightcode: release lock "+lockPath+": "+injected.Error()+"\n", "stderr = %q, want the one exact release diagnostic", out) } }) t.Run("panic_releases", func(t *testing.T) { @@ -608,19 +495,16 @@ func TestTryWithLockRows(t *testing.T) { lockPath := filepath.Join(dir, ".locks", "rec.lock") func() { defer func() { - if r := recover(); r == nil { - t.Fatal("callback did not panic") + { + r := recover() + testassert.Require(t, r != nil, "callback did not panic") } }() _, _ = TryWithLock(lockPath, func() error { panic("callback panic") }) }() regained, ok, err := TryAcquire(lockPath) - if err != nil { - t.Fatalf("TryAcquire after the recovered panic: %v", err) - } - if !ok { - t.Fatal("lock still held after a panicking TryWithLock callback") - } + testassert.NoError(t, err) + testassert.Require(t, ok, "lock still held after a panicking TryWithLock callback") regained.Release() }) } @@ -672,33 +556,24 @@ func TestCrashBeforeRenameLeavesReadableOrphanTemp(t *testing.T) { t.Run("write", func(t *testing.T) { dir := t.TempDir() dest := filepath.Join(dir, "rec.json") - if err := Write(dest, []byte("original"), 0o600); err != nil { - t.Fatalf("seed: %v", err) - } + testassert.NoError(t, Write(dest, []byte("original"), 0o600)) runCrashChild(t, dir, dest, false) got, err := os.ReadFile(dest) - if err != nil { - t.Fatalf("read destination: %v", err) - } - if string(got) != "original" { - t.Fatalf("destination = %q after the crash, want original", got) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == "original", "destination = %q after the crash, want original", got) orphan := readOrphan(t, dir) - if string(orphan) != "replacement" { - t.Fatalf("orphan = %q, want the complete replacement data", orphan) - } + testassert.Require(t, string(orphan) == "replacement", "orphan = %q, want the complete replacement data", orphan) }) t.Run("create", func(t *testing.T) { dir := t.TempDir() dest := filepath.Join(dir, "rec.json") runCrashChild(t, dir, dest, true) - if _, err := os.Stat(dest); !os.IsNotExist(err) { - t.Fatalf("destination exists after the crash: %v", err) + { + _, err := os.Stat(dest) + testassert.Require(t, os.IsNotExist(err), "destination exists after the crash: %v", err) } orphan := readOrphan(t, dir) - if string(orphan) != "replacement" { - t.Fatalf("orphan = %q, want the complete replacement data", orphan) - } + testassert.Require(t, string(orphan) == "replacement", "orphan = %q, want the complete replacement data", orphan) }) } @@ -713,14 +588,10 @@ func runCrashChild(t *testing.T, dir, dest string, create bool) { cmd.Env = append(cmd.Env, crashChildCreateEnv+"=1") } stdout, err := cmd.StdoutPipe() - if err != nil { - t.Fatalf("child stdout pipe: %v", err) - } + testassert.NoError(t, err) var stderr bytes.Buffer cmd.Stderr = &stderr - if err := cmd.Start(); err != nil { - t.Fatalf("start child: %v", err) - } + testassert.NoError(t, cmd.Start()) ready := make(chan struct{}) scannerDone := make(chan struct{}) go func() { @@ -749,9 +620,7 @@ func runCrashChild(t *testing.T, dir, dest string, create bool) { killErr := cmd.Process.Kill() _ = cmd.Wait() <-scannerDone - if killErr != nil { - t.Fatalf("kill child: %v", killErr) - } + testassert.Require(t, killErr == nil, "kill child: %v", killErr) } // readOrphan returns the single *.tmp-* orphan in dir, failing the test when @@ -759,15 +628,9 @@ func runCrashChild(t *testing.T, dir, dest string, create bool) { func readOrphan(t *testing.T, dir string) []byte { t.Helper() orphans, err := filepath.Glob(filepath.Join(dir, "*.tmp-*")) - if err != nil { - t.Fatalf("glob orphans: %v", err) - } - if len(orphans) != 1 { - t.Fatalf("orphans = %v, want exactly one", orphans) - } + testassert.NoError(t, err) + testassert.Require(t, len(orphans) == 1, "orphans = %v, want exactly one", orphans) data, err := os.ReadFile(orphans[0]) - if err != nil { - t.Fatalf("read orphan %s: %v", orphans[0], err) - } + testassert.Require(t, err == nil, "read orphan %s: %v", orphans[0], err) return data } diff --git a/internal/catalog/build_test.go b/internal/catalog/build_test.go index ca73da22..d11c23fe 100644 --- a/internal/catalog/build_test.go +++ b/internal/catalog/build_test.go @@ -6,6 +6,8 @@ import ( "fmt" "sort" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestBuildCatalogFromBundledOnly(t *testing.T) { @@ -24,25 +26,12 @@ func TestBuildCatalogFromBundledOnly(t *testing.T) { }`), }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) provider, model, err := result.Catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if provider.Transport.BaseURL != "https://api.openai.com/v1" { - t.Fatalf("BaseURL = %q", provider.Transport.BaseURL) - } - if model.Name != "GPT-5.4 mini" || model.ContextWindow != 400000 || model.MaxOutputTokens != 128000 { - t.Fatalf("model = %#v", model) - } - if len(model.InputModalities) != 1 || model.InputModalities[0] != ModalityText { - t.Fatalf("InputModalities = %#v, want default [text]", model.InputModalities) - } - if !model.UsageInStream { - t.Fatalf("UsageInStream default/flattening was false") - } + testassert.NoError(t, err) + testassert.Equal(t, provider.Transport.BaseURL, "https://api.openai.com/v1") + testassert.Require(t, model.Name == "GPT-5.4 mini" && model.ContextWindow == 400000 && model.MaxOutputTokens == 128000 && len(model.InputModalities) == 1 && model.InputModalities[0] == ModalityText, "model = %#v; InputModalities = %#v, want default [text]", model, model.InputModalities) + testassert.Require(t, model.UsageInStream, "UsageInStream default/flattening was false") } func TestBuildCatalogFromUserOnlyCustomProvider(t *testing.T) { @@ -56,19 +45,11 @@ func TestBuildCatalogFromUserOnlyCustomProvider(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) provider, model, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "qwen2.5-coder:32b"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if provider.ID != "local" || provider.Name != "local" { - t.Fatalf("provider defaults = %#v", provider) - } - if model.Name != "qwen2.5-coder:32b" { - t.Fatalf("model name = %q, want id fallback", model.Name) - } + testassert.NoError(t, err) + testassert.Require(t, provider.ID == "local" && provider.Name == "local", "provider defaults = %#v", provider) + testassert.Equal(t, model.Name, "qwen2.5-coder:32b") } func TestBuildKeepsDiscoveryOnlyProviderWithEmptyModels(t *testing.T) { @@ -81,19 +62,11 @@ func TestBuildKeepsDiscoveryOnlyProviderWithEmptyModels(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) provider, ok := result.Catalog.Providers["local"] - if !ok || provider == nil { - t.Fatalf("provider local missing from catalog: %#v", result.Catalog.Providers) - } - if !provider.Discovery { - t.Fatalf("provider discovery = false, want true") - } - if len(provider.Models) != 0 { - t.Fatalf("provider models = %#v, want empty discovery-only model map", provider.Models) - } + testassert.Require(t, ok && provider != nil, "provider local missing from catalog: %#v", result.Catalog.Providers) + testassert.Require(t, provider.Discovery, "provider discovery = false, want true") + testassert.Require(t, len(provider.Models) == 0, "provider models = %#v, want empty discovery-only model map", provider.Models) } func TestBuildConfigOnlyProviderDoesNotAddDiscoveredModelsFromCache(t *testing.T) { @@ -114,11 +87,10 @@ func TestBuildConfigOnlyProviderDoesNotAddDiscoveredModelsFromCache(t *testing.T }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("Lookup error = %v, want ErrUnknownModel for config-only discovered model", err) + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "Lookup error = %v, want ErrUnknownModel for config-only discovered model", err) } } @@ -140,11 +112,10 @@ func TestBuildDoesNotMaskInvalidModelsWithDiscoveryCache(t *testing.T) { }, }, }) - if len(result.Warnings) != 1 || result.Warnings[0].Kind != "user_config_skip" || result.Warnings[0].Provider != "local" { - t.Fatalf("warnings = %#v, want user_config_skip for invalid models", result.Warnings) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}); !errors.Is(err, ErrUnknownProvider) { - t.Fatalf("Lookup error = %v, want ErrUnknownProvider", err) + testassert.Require(t, len(result.Warnings) == 1 && result.Warnings[0].Kind == "user_config_skip" && result.Warnings[0].Provider == "local", "warnings = %#v, want user_config_skip for invalid models", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}) + testassert.Require(t, errors.Is(err, ErrUnknownProvider), "Lookup error = %v, want ErrUnknownProvider", err) } } @@ -161,15 +132,15 @@ func TestBuildMalformedTransportSkipsOnlyInvalidProvider(t *testing.T) { "models": map[string]any{"m": map[string]any{"context_window": 1}}, }, }}) - if _, ok := result.Catalog.Providers["invalid"]; ok { - t.Fatal("invalid provider was retained") - } - if _, ok := result.Catalog.Providers["valid"]; !ok { - t.Fatalf("valid sibling was omitted: %#v", result.Warnings) + { + _, ok := result.Catalog.Providers["invalid"] + testassert.Require(t, !ok, "invalid provider was retained") } - if !hasWarningForProvider(result.Warnings, "user_config_skip", "invalid") { - t.Fatalf("warnings = %#v, want user_config_skip for invalid", result.Warnings) + { + _, ok := result.Catalog.Providers["valid"] + testassert.Require(t, ok, "valid sibling was omitted: %#v", result.Warnings) } + testassert.Require(t, hasWarningForProvider(result.Warnings, "user_config_skip", "invalid"), "warnings = %#v, want user_config_skip for invalid", result.Warnings) }) } t.Run("missing", func(t *testing.T) { @@ -180,15 +151,15 @@ func TestBuildMalformedTransportSkipsOnlyInvalidProvider(t *testing.T) { "models": map[string]any{"m": map[string]any{"context_window": 1}}, }, }}) - if _, ok := result.Catalog.Providers["invalid"]; ok { - t.Fatal("provider with missing transport was retained") + { + _, ok := result.Catalog.Providers["invalid"] + testassert.Require(t, !ok, "provider with missing transport was retained") } - if _, ok := result.Catalog.Providers["valid"]; !ok { - t.Fatalf("valid sibling was omitted: %#v", result.Warnings) - } - if !hasWarningForProvider(result.Warnings, "user_config_skip", "invalid") { - t.Fatalf("warnings = %#v, want user_config_skip for invalid", result.Warnings) + { + _, ok := result.Catalog.Providers["valid"] + testassert.Require(t, ok, "valid sibling was omitted: %#v", result.Warnings) } + testassert.Require(t, hasWarningForProvider(result.Warnings, "user_config_skip", "invalid"), "warnings = %#v, want user_config_skip for invalid", result.Warnings) }) } @@ -197,15 +168,15 @@ func TestBuildRejectsTrailingBundledProviderData(t *testing.T) { "invalid": json.RawMessage(`{"id":"invalid","transport":{"base_url":"http://invalid.test/v1","api_key_env":""},"models":{}} {"extra":1}`), "valid": rawProviderJSON(`{"id":"valid","transport":{"base_url":"http://valid.test/v1","api_key_env":""},"models":{"m":{"context_window":1}}}`), }}) - if _, ok := result.Catalog.Providers["invalid"]; ok { - t.Fatal("bundled provider with trailing data was retained") - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "valid", Model: "m"}); err != nil { - t.Fatalf("valid bundled sibling missing: %v", err) + { + _, ok := result.Catalog.Providers["invalid"] + testassert.Require(t, !ok, "bundled provider with trailing data was retained") } - if len(result.Warnings) != 1 || result.Warnings[0].Kind != "bundled_config_skip" { - t.Fatalf("warnings = %#v, want one bundled_config_skip", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "valid", Model: "m"}) + testassert.NoError(t, err) } + testassert.Require(t, len(result.Warnings) == 1 && result.Warnings[0].Kind == "bundled_config_skip", "warnings = %#v, want one bundled_config_skip", result.Warnings) } // TestBuildLabelsModelSourcesFromSameAcceptedInputs pins the source labels @@ -267,20 +238,23 @@ func TestBuildLabelsModelSourcesFromSameAcceptedInputs(t *testing.T) { t.Fatalf("provider %q effective models = %d, want %d", providerID, len(provider.Models), len(models)) } for modelID, source := range models { - if got := provider.Models[modelID].Source; got != source { - t.Fatalf("source of %s/%s = %q, want %q", providerID, modelID, got, source) + { + got := provider.Models[modelID].Source + testassert.Require(t, got == source, "source of %s/%s = %q, want %q", providerID, modelID, got, source) } } } - if _, ok := result.Catalog.Providers["bundled"].Models["unadopted"]; ok { - t.Fatal("custom-provider discovery model was adopted into the bundled-provider catalog") + { + _, ok := result.Catalog.Providers["bundled"].Models["unadopted"] + testassert.Require(t, !ok, "custom-provider discovery model was adopted into the bundled-provider catalog") } for _, absent := range []struct{ provider, model string }{ {"custom", "unadopted"}, {"bundled", "unbound"}, } { - if _, present := result.Catalog.Providers[absent.provider].Models[absent.model]; present { - t.Fatalf("model %s/%s carries an effective entry outside the accepted layers", absent.provider, absent.model) + { + _, present := result.Catalog.Providers[absent.provider].Models[absent.model] + testassert.Require(t, !present, "model %s/%s carries an effective entry outside the accepted layers", absent.provider, absent.model) } } } @@ -308,8 +282,9 @@ func TestBuildIgnoresForeignDiscoveryRecord(t *testing.T) { "local": {TransportFingerprint: transportFingerprint(foreign), Models: map[string]DiscoveredModel{"stale": {ContextWindow: 4096}}}, }, }) - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "stale"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("foreign discovery model lookup = %v, want unknown model", err) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "stale"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "foreign discovery model lookup = %v, want unknown model", err) } } @@ -350,26 +325,17 @@ func TestBuildDeepMergesUserOverridesAndPassesSidecarsThrough(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) provider, model, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "openai/gpt-5.4-mini"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if provider.Transport.Headers["OpenAI-Beta"] != "assistants=v2" || provider.Transport.Headers["HTTP-Referer"] != "https://lightcode.local" { - t.Fatalf("headers = %#v", provider.Transport.Headers) - } + testassert.NoError(t, err) + testassert.Require(t, provider.Transport.Headers["OpenAI-Beta"] == "assistants=v2" && provider.Transport.Headers["HTTP-Referer"] == "https://lightcode.local", "headers = %#v", provider.Transport.Headers) body := ShallowMergeBody(provider.ExtraBody, model.ExtraBody) - if body["reasoning_effort"] != "high" { - t.Fatalf("sidecar body = %#v", body) - } - if _, ok := body["thinking"]; !ok { - t.Fatalf("thinking sidecar missing: %#v", body) - } - if len(model.InputModalities) != 1 || model.InputModalities[0] != ModalityText { - t.Fatalf("InputModalities = %#v, want user replacement", model.InputModalities) + testassert.Require(t, body["reasoning_effort"] == "high", "sidecar body = %#v", body) + { + _, ok := body["thinking"] + testassert.Require(t, ok, "thinking sidecar missing: %#v", body) } + testassert.Require(t, len(model.InputModalities) == 1 && model.InputModalities[0] == ModalityText, "InputModalities = %#v, want user replacement", model.InputModalities) } func TestBuildProtocolMetadataInheritsAndOverrides(t *testing.T) { @@ -401,33 +367,16 @@ func TestBuildProtocolMetadataInheritsAndOverrides(t *testing.T) { }`), }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) _, inherited, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "anthropic/a"}) - if err != nil { - t.Fatalf("Lookup inherited returned error: %v", err) - } - if inherited.ProtocolMetadata == nil || inherited.ProtocolMetadata.Family != "anthropic-thinking" { - t.Fatalf("inherited protocol metadata = %#v", inherited.ProtocolMetadata) - } - if len(inherited.ProtocolMetadata.MustPreserve) != 1 || inherited.ProtocolMetadata.MustPreserve[0] != "reasoning_details" { - t.Fatalf("inherited must_preserve = %#v", inherited.ProtocolMetadata.MustPreserve) - } + testassert.NoError(t, err) + testassert.Require(t, inherited.ProtocolMetadata != nil && inherited.ProtocolMetadata.Family == "anthropic-thinking", "inherited protocol metadata = %#v", inherited.ProtocolMetadata) + testassert.Require(t, len(inherited.ProtocolMetadata.MustPreserve) == 1 && inherited.ProtocolMetadata.MustPreserve[0] == "reasoning_details", "inherited must_preserve = %#v", inherited.ProtocolMetadata.MustPreserve) _, overridden, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "anthropic/b"}) - if err != nil { - t.Fatalf("Lookup overridden returned error: %v", err) - } - if overridden.ProtocolMetadata == nil || overridden.ProtocolMetadata.Family != "anthropic-thinking-v2" { - t.Fatalf("overridden protocol metadata = %#v", overridden.ProtocolMetadata) - } - if len(overridden.ProtocolMetadata.MustPreserve) != 1 || overridden.ProtocolMetadata.MustPreserve[0] != "reasoning_details" { - t.Fatalf("overridden must_preserve = %#v", overridden.ProtocolMetadata.MustPreserve) - } - if len(overridden.ProtocolMetadata.Drop) != 1 || overridden.ProtocolMetadata.Drop[0] != "model_drop" { - t.Fatalf("overridden drop = %#v", overridden.ProtocolMetadata.Drop) - } + testassert.NoError(t, err) + testassert.Require(t, overridden.ProtocolMetadata != nil && overridden.ProtocolMetadata.Family == "anthropic-thinking-v2", "overridden protocol metadata = %#v", overridden.ProtocolMetadata) + testassert.Require(t, len(overridden.ProtocolMetadata.MustPreserve) == 1 && overridden.ProtocolMetadata.MustPreserve[0] == "reasoning_details" && len(overridden.ProtocolMetadata.Drop) == 1 && overridden.ProtocolMetadata.Drop[0] == "model_drop", "overridden must_preserve = %#v; overridden drop = %#v", overridden.ProtocolMetadata.MustPreserve, overridden.ProtocolMetadata.Drop) } func TestBuildConfigOnlyDiscoveryCacheOnlyUpdatesKnownModelCost(t *testing.T) { @@ -450,21 +399,13 @@ func TestBuildConfigOnlyDiscoveryCacheOnlyUpdatesKnownModelCost(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) _, known, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "known"}) - if err != nil { - t.Fatalf("Lookup known returned error: %v", err) - } - if known.Name != "Configured" || known.ContextWindow != 1000 || known.MaxOutputTokens != 100 { - t.Fatalf("known metadata = %#v, want configured metadata unchanged", known) - } - if known.Cost == nil || known.Cost.Input == nil || *known.Cost.Input != 0.2 { - t.Fatalf("known cost = %#v, want discovered input cost", known.Cost) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "new"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("Lookup new error = %v, want ErrUnknownModel", err) + testassert.NoError(t, err) + testassert.Require(t, known.Name == "Configured" && known.ContextWindow == 1000 && known.MaxOutputTokens == 100 && known.Cost != nil && known.Cost.Input != nil && *known.Cost.Input == 0.2, "known metadata = %#v, want configured metadata unchanged; known cost = %#v, want discovered input cost", known, known.Cost) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "new"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "Lookup new error = %v, want ErrUnknownModel", err) } } @@ -493,27 +434,15 @@ func TestBuildConfigOnlyDiscoveryDoesNotOverrideUserCostFields(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) _, known, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "known"}) - if err != nil { - t.Fatalf("Lookup known returned error: %v", err) - } - if known.Name != "Configured" || known.ContextWindow != 1000 || known.MaxOutputTokens != 100 { - t.Fatalf("known metadata = %#v, want configured metadata unchanged", known) - } - if known.Cost == nil { - t.Fatalf("known cost = nil") - } - if known.Cost.Input == nil || *known.Cost.Input != 99 { - t.Fatalf("known cost input = %v, want user value 99", known.Cost.Input) - } - if known.Cost.Output == nil || *known.Cost.Output != 0.4 { - t.Fatalf("known cost output = %v, want discovered value 0.4", known.Cost.Output) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "new"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("Lookup new error = %v, want ErrUnknownModel", err) + testassert.NoError(t, err) + testassert.Require(t, known.Name == "Configured" && known.ContextWindow == 1000 && known.MaxOutputTokens == 100, "known metadata = %#v, want configured metadata unchanged", known) + testassert.Require(t, known.Cost != nil, "known cost = nil") + testassert.Require(t, known.Cost.Input != nil && *known.Cost.Input == 99 && known.Cost.Output != nil && *known.Cost.Output == 0.4, "known cost input = %v, want user value 99; known cost output = %v, want discovered value 0.4", known.Cost.Input, known.Cost.Output) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "new"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "Lookup new error = %v, want ErrUnknownModel", err) } } @@ -537,9 +466,7 @@ func TestBuildBundledDiscoveryCacheCanAddModels(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) if _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "remote"}); err != nil { t.Fatalf("Lookup remote returned error: %v", err) } else if model.Name != "Remote" || model.ContextWindow != 32768 { @@ -583,22 +510,21 @@ func TestBuildFiltersRejectedDiscoveredOnlyModels(t *testing.T) { }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "image-only"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "Lookup image-only error = %v, want ErrUnknownModel", err) } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "image-only"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("Lookup image-only error = %v, want ErrUnknownModel", err) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "old-cache"}) + testassert.NoError(t, err) } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "old-cache"}); err != nil { - t.Fatalf("Lookup old-cache returned error: %v", err) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "vision-chat"}); err != nil { - t.Fatalf("Lookup vision-chat returned error: %v", err) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "vision-chat"}) + testassert.NoError(t, err) } for _, ref := range result.Catalog.AllModels() { - if ref.String() == "openrouter/image-only" { - t.Fatalf("rejected discovered model appeared in AllModels") - } + testassert.Require(t, ref.String() != "openrouter/image-only", "rejected discovered model appeared in AllModels") } } @@ -629,19 +555,10 @@ func TestBuildDoesNotDeleteConfiguredModelRejectedByDiscoveryMetadata(t *testing }, }, }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v, want none", result.Warnings) _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openrouter", Model: "configured"}) - if err != nil { - t.Fatalf("Lookup configured returned error: %v", err) - } - if model.Name != "Configured" || model.ContextWindow != 1000 { - t.Fatalf("configured model = %#v, want bundled metadata preserved", model) - } - if model.Cost == nil || model.Cost.Input == nil || *model.Cost.Input != 0.5 { - t.Fatalf("configured cost = %#v, want discovery cost still merged", model.Cost) - } + testassert.NoError(t, err) + testassert.Require(t, model.Name == "Configured" && model.ContextWindow == 1000 && model.Cost != nil && model.Cost.Input != nil && *model.Cost.Input == 0.5, "configured model = %#v, want bundled metadata preserved; configured cost = %#v, want discovery cost still merged", model, model.Cost) } func TestBuildKeepsIncompleteModelsAndWarns(t *testing.T) { @@ -653,24 +570,17 @@ func TestBuildKeepsIncompleteModelsAndWarns(t *testing.T) { }, }, }) - if len(result.Warnings) != 1 || result.Warnings[0].Kind != "incomplete_model" { - t.Fatalf("warnings = %#v, want incomplete_model", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 1 && result.Warnings[0].Kind == "incomplete_model", "warnings = %#v, want incomplete_model", result.Warnings) _, _, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "qwen"}) - if !errors.Is(err, ErrIncomplete) { - t.Fatalf("Lookup error = %v, want ErrIncomplete", err) - } + testassert.Require(t, errors.Is(err, ErrIncomplete), "Lookup error = %v, want ErrIncomplete", err) _, model, err := result.Catalog.LookupOrIncomplete(ModelRef{Provider: "local", Model: "qwen"}) - if err != nil { - t.Fatalf("LookupOrIncomplete error = %v", err) - } - if _, ok := model.Incomplete(); !ok { - t.Fatalf("model incomplete = false") + testassert.NoError(t, err) + { + _, ok := model.Incomplete() + testassert.Require(t, ok, "model incomplete = false") } incomplete := result.Catalog.IncompleteModels() - if len(incomplete) != 1 || incomplete[0].Ref.String() != "local/qwen" { - t.Fatalf("IncompleteModels = %#v", incomplete) - } + testassert.Require(t, len(incomplete) == 1 && incomplete[0].Ref.String() == "local/qwen", "IncompleteModels = %#v", incomplete) } func TestBuildSkipsReservedKeyViolationWithWarning(t *testing.T) { @@ -688,11 +598,10 @@ func TestBuildSkipsReservedKeyViolationWithWarning(t *testing.T) { }, }, }) - if len(result.Warnings) != 1 || result.Warnings[0].Kind != "user_config_skip" || result.Warnings[0].Provider != "bad" { - t.Fatalf("warnings = %#v, want user_config_skip for bad", result.Warnings) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "bad", Model: "bad-model"}); !errors.Is(err, ErrUnknownProvider) { - t.Fatalf("Lookup error = %v, want ErrUnknownProvider", err) + testassert.Require(t, len(result.Warnings) == 1 && result.Warnings[0].Kind == "user_config_skip" && result.Warnings[0].Provider == "bad", "warnings = %#v, want user_config_skip for bad", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "bad", Model: "bad-model"}) + testassert.Require(t, errors.Is(err, ErrUnknownProvider), "Lookup error = %v, want ErrUnknownProvider", err) } } @@ -713,94 +622,43 @@ func TestBuildSkipsRetiredProviderControlsWithWarning(t *testing.T) { }, }, }) - if !hasWarningForProvider(result.Warnings, "user_config_skip", "retired") { - t.Fatalf("warnings = %#v, want user_config_skip for the retired controls", result.Warnings) + testassert.Require(t, hasWarningForProvider(result.Warnings, "user_config_skip", "retired"), "warnings = %#v, want user_config_skip for the retired controls", result.Warnings) + { + _, ok := result.Catalog.Providers["retired"] + testassert.Require(t, !ok, "a provider carrying retired controls stayed effective") } - if _, ok := result.Catalog.Providers["retired"]; ok { - t.Fatal("a provider carrying retired controls stayed effective") - } - if _, ok := result.Catalog.Providers["valid"]; !ok { - t.Fatalf("valid sibling was omitted: %#v", result.Warnings) + { + _, ok := result.Catalog.Providers["valid"] + testassert.Require(t, ok, "valid sibling was omitted: %#v", result.Warnings) } } -func TestBuildDiscoveryUpdatesBundledCostFields(t *testing.T) { - // Bundled model has cost with input=10, output=20. - // Discovery cache has updated cost with input=15, output=30. - // Discovery should update the cost fields. - in15 := 15.0 - out30 := 30.0 - result := Build(BuildInputs{ - Bundled: map[string]json.RawMessage{ - "openai": rawProviderJSON(`{ - "id": "openai", - "name": "OpenAI", - "transport": {"base_url": "https://api.openai.com/v1", "api_key_env": "OPENAI_API_KEY"}, - "discovery": true, - "models": { - "gpt-5.4-mini": { - "name": "GPT-5.4 mini", - "context_window": 400000, - "max_output_tokens": 128000, - "cost": {"input": 10, "output": 20} - } - } - }`), - }, - Records: map[string]DiscoveryRecord{ - "openai": { - TransportFingerprint: transportFingerprint(Transport{BaseURL: "https://api.openai.com/v1", APIKeyEnv: "OPENAI_API_KEY"}), - Models: map[string]DiscoveredModel{ - "gpt-5.4-mini": { - Name: "GPT-5.4 mini", - ContextWindow: 400000, - Cost: &Cost{Input: &in15, Output: &out30}, - }, - }, - }, - }, - }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v", result.Warnings) - } - _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if model.Cost == nil { - t.Fatalf("model.Cost = nil, want cost from discovery") - } - if model.Cost.Input == nil || *model.Cost.Input != 15 { - t.Fatalf("model.Cost.Input = %v, want 15 from discovery", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 30 { - t.Fatalf("model.Cost.Output = %v, want 30 from discovery", model.Cost.Output) - } +// buildCostProviderDocument is the raw bundled provider for the +// discovery-cost rows: one OpenAI provider whose single model carries the +// row's cost object. +func buildCostProviderDocument(cost string) json.RawMessage { + return rawProviderJSON(fmt.Sprintf(`{ + "id": "openai", + "name": "OpenAI", + "transport": {"base_url": "https://api.openai.com/v1", "api_key_env": "OPENAI_API_KEY"}, + "discovery": true, + "models": { + "gpt-5.4-mini": { + "name": "GPT-5.4 mini", + "context_window": 400000, + "max_output_tokens": 128000, + "cost": %s + } + } + }`, cost)) } -func TestBuildDiscoveryMergesCostSubfieldsOverBundled(t *testing.T) { - // Bundled has input/output. Discovery has input/output/cache_read. - // Discovery should merge: update input/output, add cache_read. - in5 := 5.0 - out12 := 12.0 - cacheRead := 1.5 - result := Build(BuildInputs{ - Bundled: map[string]json.RawMessage{ - "openai": rawProviderJSON(`{ - "id": "openai", - "name": "OpenAI", - "transport": {"base_url": "https://api.openai.com/v1", "api_key_env": "OPENAI_API_KEY"}, - "discovery": true, - "models": { - "gpt-5.4-mini": { - "name": "GPT-5.4 mini", - "context_window": 400000, - "max_output_tokens": 128000, - "cost": {"input": 10, "output": 20} - } - } - }`), - }, +// buildCostInputs is one raw Build input over the nominal provider: the +// bundled cost, an optional user override for the same model's cost, and the +// matching discovery record. +func buildCostInputs(bundledCost string, userCost map[string]any, discovered *Cost) BuildInputs { + inputs := BuildInputs{ + Bundled: map[string]json.RawMessage{"openai": buildCostProviderDocument(bundledCost)}, Records: map[string]DiscoveryRecord{ "openai": { TransportFingerprint: transportFingerprint(Transport{BaseURL: "https://api.openai.com/v1", APIKeyEnv: "OPENAI_API_KEY"}), @@ -808,158 +666,119 @@ func TestBuildDiscoveryMergesCostSubfieldsOverBundled(t *testing.T) { "gpt-5.4-mini": { Name: "GPT-5.4 mini", ContextWindow: 400000, - Cost: &Cost{Input: &in5, Output: &out12, CacheRead: &cacheRead}, + Cost: discovered, }, }, }, }, - }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v", result.Warnings) - } - _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if model.Cost == nil { - t.Fatalf("model.Cost = nil") } - if model.Cost.Input == nil || *model.Cost.Input != 5 { - t.Fatalf("model.Cost.Input = %v, want 5 (updated by discovery)", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 12 { - t.Fatalf("model.Cost.Output = %v, want 12 (updated by discovery)", model.Cost.Output) - } - if model.Cost.CacheRead == nil || *model.Cost.CacheRead != 1.5 { - t.Fatalf("model.Cost.CacheRead = %v, want 1.5 (added by discovery)", model.Cost.CacheRead) - } -} - -func TestBuildDiscoveryDoesNotOverrideUserCostFields(t *testing.T) { - // User cost is an explicit override. Discovery may update bundled prices, but - // must not overwrite fields the user set. - result := Build(BuildInputs{ - Bundled: map[string]json.RawMessage{ - "openai": rawProviderJSON(`{ - "id": "openai", - "name": "OpenAI", - "transport": {"base_url": "https://api.openai.com/v1", "api_key_env": "OPENAI_API_KEY"}, - "discovery": true, - "models": { - "gpt-5.4-mini": { - "name": "GPT-5.4 mini", - "context_window": 400000, - "max_output_tokens": 128000, - "cost": {"input": 10, "output": 20, "cache_read": 2} - } - } - }`), - }, - UserRaw: map[string]any{ + if userCost != nil { + inputs.UserRaw = map[string]any{ "openai": map[string]any{ "models": map[string]any{ - "gpt-5.4-mini": map[string]any{ - "cost": map[string]any{"input": 99.0}, - }, + "gpt-5.4-mini": map[string]any{"cost": userCost}, }, }, - }, - Records: map[string]DiscoveryRecord{ - "openai": { - TransportFingerprint: transportFingerprint(Transport{BaseURL: "https://api.openai.com/v1", APIKeyEnv: "OPENAI_API_KEY"}), - Models: map[string]DiscoveredModel{ - "gpt-5.4-mini": { - Name: "GPT-5.4 mini", - ContextWindow: 400000, - Cost: &Cost{Input: ptrFloat64(8), Output: ptrFloat64(12), CacheRead: ptrFloat64(1)}, - }, - }, - }, - }, - }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v", result.Warnings) - } - _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if model.Cost == nil { - t.Fatalf("model.Cost = nil") - } - if model.Cost.Input == nil || *model.Cost.Input != 99 { - t.Fatalf("model.Cost.Input = %v, want 99 from user override", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 12 { - t.Fatalf("model.Cost.Output = %v, want 12 from discovery", model.Cost.Output) - } - if model.Cost.CacheRead == nil || *model.Cost.CacheRead != 1 { - t.Fatalf("model.Cost.CacheRead = %v, want 1 from discovery", model.Cost.CacheRead) + } } + return inputs } -func TestBuildDiscoveryPreservesExistingCostFieldsItDoesNotProvide(t *testing.T) { - // Bundled has input/output/cache_read. Discovery only has input. - // Discovery should update input, but keep output and cache_read from bundled. - cacheRead := 3.0 - result := Build(BuildInputs{ - Bundled: map[string]json.RawMessage{ - "openai": rawProviderJSON(`{ - "id": "openai", - "name": "OpenAI", - "transport": {"base_url": "https://api.openai.com/v1", "api_key_env": "OPENAI_API_KEY"}, - "discovery": true, - "models": { - "gpt-5.4-mini": { - "name": "GPT-5.4 mini", - "context_window": 400000, - "max_output_tokens": 128000, - "cost": {"input": 10, "output": 20, "cache_read": 3} - } - } - }`), - }, - Records: map[string]DiscoveryRecord{ - "openai": { - TransportFingerprint: transportFingerprint(Transport{BaseURL: "https://api.openai.com/v1", APIKeyEnv: "OPENAI_API_KEY"}), - Models: map[string]DiscoveredModel{ - "gpt-5.4-mini": { - Name: "GPT-5.4 mini", - ContextWindow: 400000, - Cost: &Cost{Input: ptrFloat64(8)}, - }, - }, - }, - }, - }) - if len(result.Warnings) != 0 { - t.Fatalf("Build warnings = %#v", result.Warnings) - } - _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}) - if err != nil { - t.Fatalf("Lookup returned error: %v", err) - } - if model.Cost == nil { - t.Fatalf("model.Cost = nil") - } - if model.Cost.Input == nil || *model.Cost.Input != 8 { - t.Fatalf("model.Cost.Input = %v, want 8 (updated by discovery)", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 20 { - t.Fatalf("model.Cost.Output = %v, want 20 (preserved from bundled)", model.Cost.Output) - } - if model.Cost.CacheRead == nil || *model.Cost.CacheRead != cacheRead { - t.Fatalf("model.Cost.CacheRead = %v, want 3 (preserved from bundled)", model.Cost.CacheRead) +func TestBuildDiscoveryMergesCostFields(t *testing.T) { + rows := []struct { + name string + bundledCost string + userCost map[string]any + discovered *Cost + wantInput, wantOutput *float64 + wantCacheRead, wantCacheWrite *float64 + }{ + { + name: "updates bundled input and output", + bundledCost: `{"input": 10, "output": 20}`, + discovered: &Cost{Input: ptrFloat64(15), Output: ptrFloat64(30)}, + wantInput: ptrFloat64(15), + wantOutput: ptrFloat64(30), + }, + { + name: "adds discovered cache_read", + bundledCost: `{"input": 10, "output": 20}`, + discovered: &Cost{Input: ptrFloat64(5), Output: ptrFloat64(12), CacheRead: ptrFloat64(1.5)}, + wantInput: ptrFloat64(5), + wantOutput: ptrFloat64(12), + wantCacheRead: ptrFloat64(1.5), + }, + { + name: "user field wins over discovery", + bundledCost: `{"input": 10, "output": 20, "cache_read": 2}`, + userCost: map[string]any{"input": 99.0}, + discovered: &Cost{Input: ptrFloat64(8), Output: ptrFloat64(12), CacheRead: ptrFloat64(1)}, + wantInput: ptrFloat64(99), + wantOutput: ptrFloat64(12), + wantCacheRead: ptrFloat64(1), + }, + { + name: "preserves bundled fields discovery omits", + bundledCost: `{"input": 10, "output": 20, "cache_read": 3}`, + discovered: &Cost{Input: ptrFloat64(8)}, + wantInput: ptrFloat64(8), + wantOutput: ptrFloat64(20), + wantCacheRead: ptrFloat64(3), + }, + { + name: "user-owned zero input is preserved", + bundledCost: `{"input": 10, "output": 20}`, + userCost: map[string]any{"input": 0.0}, + discovered: &Cost{Input: ptrFloat64(8), Output: ptrFloat64(12)}, + wantInput: ptrFloat64(0), + wantOutput: ptrFloat64(12), + }, + } + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + result := Build(buildCostInputs(row.bundledCost, row.userCost, row.discovered)) + testassert.Require(t, len(result.Warnings) == 0, "Build warnings = %#v", result.Warnings) + _, model, err := result.Catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}) + testassert.NoError(t, err) + assertCostFields(t, model.Cost, row.wantInput, row.wantOutput, row.wantCacheRead, row.wantCacheWrite) + }) } } func ptrFloat64(v float64) *float64 { return &v } +// assertCostFields checks each cost subfield's independent presence and value. +// A nil want is an absent field, not a zero. +func assertCostFields(t *testing.T, cost *Cost, input, output, cacheRead, cacheWrite *float64) { + t.Helper() + testassert.Require(t, cost != nil, "model.Cost = nil") + for _, field := range []struct { + name string + got, want *float64 + }{ + {"input", cost.Input, input}, + {"output", cost.Output, output}, + {"cache_read", cost.CacheRead, cacheRead}, + {"cache_write", cost.CacheWrite, cacheWrite}, + } { + if field.want == nil { + testassert.Require(t, field.got == nil, "%s = %s, want absent", field.name, costFieldValue(field.got)) + continue + } + testassert.Require(t, field.got != nil && *field.got == *field.want, "%s = %s, want %g", field.name, costFieldValue(field.got), *field.want) + } +} + +func costFieldValue(v *float64) string { + if v == nil { + return "absent" + } + return fmt.Sprintf("%g", *v) +} + func BenchmarkBuildCatalog(b *testing.B) { bundled, err := readBundledProviders(bundledFS) - if err != nil { - b.Fatalf("read bundled providers: %v", err) - } + testassert.Require(b, err == nil, "read bundled providers: %v", err) b.ResetTimer() for i := 0; i < b.N; i++ { result := Build(BuildInputs{Bundled: bundled}) diff --git a/internal/catalog/builtin_test.go b/internal/catalog/builtin_test.go index 6deffe02..41af0e7b 100644 --- a/internal/catalog/builtin_test.go +++ b/internal/catalog/builtin_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestBundledCatalogFilesValidateAndBuild(t *testing.T) { @@ -26,40 +28,35 @@ func TestBundledCatalogFilesValidateAndBuild(t *testing.T) { seen[providerID] = true var raw map[string]any - if err := json.Unmarshal(data, &raw); err != nil { - t.Fatalf("%s: unmarshal: %v", path, err) + { + err := json.Unmarshal(data, &raw) + testassert.Require(t, err == nil, "%s: unmarshal: %v", path, err) } - if errs := ValidateRaw(providerID, raw, true); len(errs) != 0 { - t.Fatalf("%s: ValidateRaw(strict=true) errors = %#v", path, errs) + { + errs := ValidateRaw(providerID, raw, true) + testassert.Require(t, len(errs) == 0, "%s: ValidateRaw(strict=true) errors = %#v", path, errs) } result := Build(BuildInputs{Bundled: map[string]json.RawMessage{providerID: data}}) - if len(result.Warnings) != 0 { - t.Fatalf("%s: Build warnings = %#v, want none", path, result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "%s: Build warnings = %#v, want none", path, result.Warnings) provider := result.Catalog.Providers[providerID] - if provider == nil { - t.Fatalf("%s: provider %q missing after Build", path, providerID) - } - if errs := ValidateEffective(provider); len(errs) != 0 { - t.Fatalf("%s: ValidateEffective errors = %#v", path, errs) + testassert.Require(t, provider != nil, "%s: provider %q missing after Build", path, providerID) + { + errs := ValidateEffective(provider) + testassert.Require(t, len(errs) == 0, "%s: ValidateEffective errors = %#v", path, errs) } return nil }) - if err != nil { - t.Fatalf("walk bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, providerID := range []string{"openai", "openrouter"} { - if !seen[providerID] { - t.Fatalf("bundled provider %q was not found", providerID) - } + testassert.Require(t, seen[providerID], "bundled provider %q was not found", providerID) } } func TestBrokenBundledCatalogFixtureFailsStrictValidation(t *testing.T) { var raw map[string]any - if err := json.Unmarshal([]byte(`{ + testassert.NoError(t, json.Unmarshal([]byte(`{ "id": "broken", "name": "Broken", "transport": {"base_url": "https://example.com/v1", "api_key_env": "BROKEN_API_KEY"}, @@ -70,14 +67,10 @@ func TestBrokenBundledCatalogFixtureFailsStrictValidation(t *testing.T) { "extra_body": {"model": "not allowed"} } } - }`), &raw); err != nil { - t.Fatalf("broken fixture unmarshal: %v", err) - } + }`), &raw)) errs := ValidateRaw("broken", raw, true) - if len(errs) == 0 { - t.Fatalf("ValidateRaw accepted deliberately broken fixture") - } + testassert.Require(t, len(errs) != 0, "ValidateRaw accepted deliberately broken fixture") for _, err := range errs { if err.Field == "models.bad.extra_body.model" && err.Reason == "reserved request body key" { return diff --git a/internal/catalog/catalog_test.go b/internal/catalog/catalog_test.go index 7fe59e03..50110a4a 100644 --- a/internal/catalog/catalog_test.go +++ b/internal/catalog/catalog_test.go @@ -3,6 +3,8 @@ package catalog import ( "errors" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestCatalogLookupReturnsUnknownErrors(t *testing.T) { @@ -18,76 +20,35 @@ func TestCatalogLookupReturnsUnknownErrors(t *testing.T) { }, }} - if _, _, err := cat.Lookup(ModelRef{Provider: "missing", Model: "gpt"}); !errors.Is(err, ErrUnknownProvider) { - t.Fatalf("missing provider err = %v, want ErrUnknownProvider", err) + { + _, _, err := cat.Lookup(ModelRef{Provider: "missing", Model: "gpt"}) + testassert.Require(t, errors.Is(err, ErrUnknownProvider), "missing provider err = %v, want ErrUnknownProvider", err) } - if _, _, err := cat.Lookup(ModelRef{Provider: "openai", Model: "missing"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("missing model err = %v, want ErrUnknownModel", err) + { + _, _, err := cat.Lookup(ModelRef{Provider: "openai", Model: "missing"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "missing model err = %v, want ErrUnknownModel", err) } } -func TestMergeDiscoveredProviderUpdatesExistingCost(t *testing.T) { - // Model already has cost with input=10, output=20. - // Discovery provides input=15, output=30, cache_read=5. - // Discovery should update all cost subfields. - in15 := 15.0 - out30 := 30.0 - cacheRead := 5.0 - cat := &Catalog{Providers: map[string]*Provider{ - "openai": { - ID: "openai", - Name: "OpenAI", - Transport: Transport{BaseURL: "https://api.openai.com/v1"}, - SystemRole: RoleSystem, - UsageInStream: true, - ExtraBody: map[string]any{}, - Models: map[string]*Model{ - "gpt-5.4-mini": { - ID: "gpt-5.4-mini", - Name: "GPT-5.4 mini", - ContextWindow: 400000, - MaxOutputTokens: 128000, - InputModalities: []Modality{ModalityText}, - SystemRole: RoleSystem, - UsageInStream: true, - ExtraBody: map[string]any{}, - Cost: &Cost{Input: ptrFloat64(10), Output: ptrFloat64(20)}, - }, - }, - }, - }} - err := cat.MergeDiscoveredProvider("openai", DiscoveredProvider{ - Models: map[string]DiscoveredModel{ - "gpt-5.4-mini": { - Name: "GPT-5.4 mini", - ContextWindow: 400000, - Cost: &Cost{Input: &in15, Output: &out30, CacheRead: &cacheRead}, - }, - }, - }) - if err != nil { - t.Fatalf("MergeDiscoveredProvider error: %v", err) - } - model := cat.Providers["openai"].Models["gpt-5.4-mini"] - if model.Cost == nil { - t.Fatalf("model.Cost = nil") - } - if model.Cost.Input == nil || *model.Cost.Input != 15 { - t.Fatalf("model.Cost.Input = %v, want 15", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 30 { - t.Fatalf("model.Cost.Output = %v, want 30", model.Cost.Output) - } - if model.Cost.CacheRead == nil || *model.Cost.CacheRead != 5 { - t.Fatalf("model.Cost.CacheRead = %v, want 5", model.Cost.CacheRead) +// mergeCostModel is the nominal typed merge model stem with the row's +// existing cost; nil is the absent-cost state. +func mergeCostModel(cost *Cost) *Model { + return &Model{ + ID: "gpt-5.4-mini", + Name: "GPT-5.4 mini", + ContextWindow: 400000, + MaxOutputTokens: 128000, + InputModalities: []Modality{ModalityText}, + SystemRole: RoleSystem, + UsageInStream: true, + ExtraBody: map[string]any{}, + Cost: cost, } } -func TestMergeDiscoveredProviderPreservesCostFieldsDiscoveryDoesNotProvide(t *testing.T) { - // Model has input/output/cache_read/cache_write. - // Discovery only provides input/output. - // Should update input/output but keep cache_read and cache_write. - cat := &Catalog{Providers: map[string]*Provider{ +// mergeCostProvider wraps one model in the nominal typed merge provider. +func mergeCostProvider(model *Model) *Catalog { + return &Catalog{Providers: map[string]*Provider{ "openai": { ID: "openai", Name: "OpenAI", @@ -95,48 +56,53 @@ func TestMergeDiscoveredProviderPreservesCostFieldsDiscoveryDoesNotProvide(t *te SystemRole: RoleSystem, UsageInStream: true, ExtraBody: map[string]any{}, - Models: map[string]*Model{ - "gpt-5.4-mini": { - ID: "gpt-5.4-mini", - Name: "GPT-5.4 mini", - ContextWindow: 400000, - MaxOutputTokens: 128000, - InputModalities: []Modality{ModalityText}, - SystemRole: RoleSystem, - UsageInStream: true, - ExtraBody: map[string]any{}, - Cost: &Cost{Input: ptrFloat64(10), Output: ptrFloat64(20), CacheRead: ptrFloat64(3), CacheWrite: ptrFloat64(7)}, - }, - }, + Models: map[string]*Model{model.ID: model}, }, }} - err := cat.MergeDiscoveredProvider("openai", DiscoveredProvider{ - Models: map[string]DiscoveredModel{ - "gpt-5.4-mini": { - Name: "GPT-5.4 mini", - ContextWindow: 400000, - Cost: &Cost{Input: ptrFloat64(12), Output: ptrFloat64(25)}, - }, +} + +func TestMergeDiscoveredProviderCostFields(t *testing.T) { + rows := []struct { + name string + existing *Cost + discovered *Cost + wantInput, wantOutput *float64 + wantCacheRead, wantCacheWrite *float64 + }{ + { + name: "updates existing cost", + existing: &Cost{Input: ptrFloat64(10), Output: ptrFloat64(20)}, + discovered: &Cost{Input: ptrFloat64(15), Output: ptrFloat64(30), CacheRead: ptrFloat64(5)}, + wantInput: ptrFloat64(15), + wantOutput: ptrFloat64(30), + wantCacheRead: ptrFloat64(5), + }, + { + name: "preserves fields discovery omits", + existing: &Cost{Input: ptrFloat64(10), Output: ptrFloat64(20), CacheRead: ptrFloat64(3), CacheWrite: ptrFloat64(7)}, + discovered: &Cost{Input: ptrFloat64(12), Output: ptrFloat64(25)}, + wantInput: ptrFloat64(12), + wantOutput: ptrFloat64(25), + wantCacheRead: ptrFloat64(3), + wantCacheWrite: ptrFloat64(7), }, - }) - if err != nil { - t.Fatalf("MergeDiscoveredProvider error: %v", err) - } - model := cat.Providers["openai"].Models["gpt-5.4-mini"] - if model.Cost == nil { - t.Fatalf("model.Cost = nil") - } - if model.Cost.Input == nil || *model.Cost.Input != 12 { - t.Fatalf("model.Cost.Input = %v, want 12 (updated by discovery)", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 25 { - t.Fatalf("model.Cost.Output = %v, want 25 (updated by discovery)", model.Cost.Output) - } - if model.Cost.CacheRead == nil || *model.Cost.CacheRead != 3 { - t.Fatalf("model.Cost.CacheRead = %v, want 3 (preserved from existing)", model.Cost.CacheRead) } - if model.Cost.CacheWrite == nil || *model.Cost.CacheWrite != 7 { - t.Fatalf("model.Cost.CacheWrite = %v, want 7 (preserved from existing)", model.Cost.CacheWrite) + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + cat := mergeCostProvider(mergeCostModel(row.existing)) + err := cat.MergeDiscoveredProvider("openai", DiscoveredProvider{ + Models: map[string]DiscoveredModel{ + "gpt-5.4-mini": { + Name: "GPT-5.4 mini", + ContextWindow: 400000, + Cost: row.discovered, + }, + }, + }) + testassert.NoError(t, err) + model := cat.Providers["openai"].Models["gpt-5.4-mini"] + assertCostFields(t, model.Cost, row.wantInput, row.wantOutput, row.wantCacheRead, row.wantCacheWrite) + }) } } @@ -169,6 +135,15 @@ func TestMergeDiscoveredProviderWithCostProtectionPreservesUserFields(t *testing ExtraBody: map[string]any{}, Cost: &Cost{Input: ptrFloat64(10), Output: ptrFloat64(11), CacheRead: ptrFloat64(12), CacheWrite: ptrFloat64(13)}, }, + "zero-cost": { + ID: "zero-cost", + Name: "Zero Cost", + InputModalities: []Modality{ModalityText}, + SystemRole: RoleSystem, + UsageInStream: true, + ExtraBody: map[string]any{}, + Cost: &Cost{Input: ptrFloat64(0), Output: ptrFloat64(20)}, + }, }, }, }} @@ -185,41 +160,24 @@ func TestMergeDiscoveredProviderWithCostProtectionPreservesUserFields(t *testing "omitted-costs": { Cost: &Cost{Input: ptrFloat64(15)}, }, + "zero-cost": { + Cost: &Cost{Input: ptrFloat64(15), Output: ptrFloat64(30)}, + }, }, }, map[string]map[string]bool{ "gpt-5.4-mini": {"input": true}, + "zero-cost": {"input": true}, }) - if err != nil { - t.Fatalf("MergeDiscoveredProviderWithCostProtection error: %v", err) - } + testassert.NoError(t, err) protected := cat.Providers["openai"].Models["gpt-5.4-mini"].Cost - if protected.Input == nil || *protected.Input != 99 { - t.Fatalf("protected input = %v, want 99", protected.Input) - } - if protected.Output == nil || *protected.Output != 2 { - t.Fatalf("unprotected output = %v, want 2", protected.Output) - } - if protected.CacheRead == nil || *protected.CacheRead != 4 { - t.Fatalf("unprotected cache_read = %v, want 4", protected.CacheRead) - } - if protected.CacheWrite == nil || *protected.CacheWrite != 5 { - t.Fatalf("unprotected cache_write = %v, want 5", protected.CacheWrite) - } + assertCostFields(t, protected, ptrFloat64(99), ptrFloat64(2), ptrFloat64(4), ptrFloat64(5)) omitted := cat.Providers["openai"].Models["omitted-costs"].Cost - if omitted.Input == nil || *omitted.Input != 15 { - t.Fatalf("omitted input = %v, want updated 15", omitted.Input) - } - if omitted.Output == nil || *omitted.Output != 11 { - t.Fatalf("omitted output = %v, want preserved 11", omitted.Output) - } - if omitted.CacheRead == nil || *omitted.CacheRead != 12 { - t.Fatalf("omitted cache_read = %v, want preserved 12", omitted.CacheRead) - } - if omitted.CacheWrite == nil || *omitted.CacheWrite != 13 { - t.Fatalf("omitted cache_write = %v, want preserved 13", omitted.CacheWrite) - } + assertCostFields(t, omitted, ptrFloat64(15), ptrFloat64(11), ptrFloat64(12), ptrFloat64(13)) + + zero := cat.Providers["openai"].Models["zero-cost"].Cost + assertCostFields(t, zero, ptrFloat64(0), ptrFloat64(30), nil, nil) } func TestMergeDiscoveredProviderAddsCostWhenPreviouslyNil(t *testing.T) { @@ -253,19 +211,9 @@ func TestMergeDiscoveredProviderAddsCostWhenPreviouslyNil(t *testing.T) { }, }, }) - if err != nil { - t.Fatalf("MergeDiscoveredProvider error: %v", err) - } + testassert.NoError(t, err) model := cat.Providers["local"].Models["qwen3"] - if model.Cost == nil { - t.Fatalf("model.Cost = nil, want cost from discovery") - } - if model.Cost.Input == nil || *model.Cost.Input != 5 { - t.Fatalf("model.Cost.Input = %v, want 5", model.Cost.Input) - } - if model.Cost.Output == nil || *model.Cost.Output != 10 { - t.Fatalf("model.Cost.Output = %v, want 10", model.Cost.Output) - } + assertCostFields(t, model.Cost, ptrFloat64(5), ptrFloat64(10), nil, nil) } func TestMergeDiscoveredProviderLeavesMaxOutputUnsetWhenDiscoveryOmits(t *testing.T) { @@ -290,19 +238,10 @@ func TestMergeDiscoveredProviderLeavesMaxOutputUnsetWhenDiscoveryOmits(t *testin }, }, }) - if err != nil { - t.Fatalf("MergeDiscoveredProvider error: %v", err) - } + testassert.NoError(t, err) m := cat.Providers["openrouter"].Models["free-model"] - if m == nil { - t.Fatal("model not found in catalog") - } - if m.ContextWindow != 200000 { - t.Fatalf("ContextWindow = %d, want 200000", m.ContextWindow) - } - if m.MaxOutputTokens != 0 { - t.Fatalf("MaxOutputTokens = %d, want 0 (no fallback to context_window)", m.MaxOutputTokens) - } + testassert.Require(t, m != nil, "model not found in catalog") + testassert.Require(t, m.ContextWindow == 200000 && m.MaxOutputTokens == 0, "got %v, want %v; got %v, want %v", m.ContextWindow, 200000, m.MaxOutputTokens, 0) } func TestMergeDiscoveredProviderFiltersRejectedDiscoveredOnlyModels(t *testing.T) { @@ -331,14 +270,14 @@ func TestMergeDiscoveredProviderFiltersRejectedDiscoveredOnlyModels(t *testing.T }, }, }) - if err != nil { - t.Fatalf("MergeDiscoveredProvider error: %v", err) - } - if _, ok := cat.Providers["openrouter"].Models["image-only"]; ok { - t.Fatalf("rejected discovered model was added: %#v", cat.Providers["openrouter"].Models["image-only"]) + testassert.NoError(t, err) + { + _, ok := cat.Providers["openrouter"].Models["image-only"] + testassert.Require(t, !ok, "rejected discovered model was added: %#v", cat.Providers["openrouter"].Models["image-only"]) } - if _, ok := cat.Providers["openrouter"].Models["unknown"]; !ok { - t.Fatalf("unknown discovered model without metadata was not added") + { + _, ok := cat.Providers["openrouter"].Models["unknown"] + testassert.Require(t, ok, "unknown discovered model without metadata was not added") } } @@ -359,11 +298,10 @@ func TestMergeDiscoveredProviderConfigOnlyDoesNotAddModels(t *testing.T) { "qwen3": {Name: "Qwen 3", ContextWindow: 32768, MaxOutputTokens: 8192, Cost: &Cost{Input: ptrFloat64(1)}}, }, }) - if err != nil { - t.Fatalf("MergeDiscoveredProvider error: %v", err) - } - if _, ok := cat.Providers["local"].Models["qwen3"]; ok { - t.Fatalf("config-only provider gained discovered model: %#v", cat.Providers["local"].Models["qwen3"]) + testassert.NoError(t, err) + { + _, ok := cat.Providers["local"].Models["qwen3"] + testassert.Require(t, !ok, "config-only provider gained discovered model: %#v", cat.Providers["local"].Models["qwen3"]) } } @@ -386,17 +324,11 @@ func TestCatalogVisibleModelsFiltersHiddenAndSorts(t *testing.T) { visible := cat.VisibleModels() want := []string{"local/qwen", "openai/z"} - if len(visible) != len(want) { - t.Fatalf("VisibleModels = %#v, want %v", visible, want) - } + testassert.Require(t, len(visible) == len(want), "VisibleModels = %#v, want %v", visible, want) for i := range want { - if visible[i].String() != want[i] { - t.Fatalf("VisibleModels = %#v, want %v", visible, want) - } + testassert.Require(t, visible[i].String() == want[i], "VisibleModels = %#v, want %v", visible, want) } all := cat.AllModels() - if len(all) != 4 { - t.Fatalf("AllModels length = %d, want 4", len(all)) - } + testassert.Equal(t, len(all), 4) } diff --git a/internal/catalog/discovery_filter_test.go b/internal/catalog/discovery_filter_test.go index 6261d9e3..5b875d71 100644 --- a/internal/catalog/discovery_filter_test.go +++ b/internal/catalog/discovery_filter_test.go @@ -1,6 +1,9 @@ package catalog -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) func TestDiscoveredModelAllowedClassifier(t *testing.T) { tests := []struct { @@ -67,10 +70,7 @@ func TestDiscoveredModelAllowedClassifier(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := discoveredModelAllowed(DiscoveredModel{metadata: tt.meta}) - if got != tt.allow { - t.Fatalf("discoveredModelAllowed = %v, want %v", got, tt.allow) - } + testassert.Equal(t, discoveredModelAllowed(DiscoveredModel{metadata: tt.meta}), tt.allow) }) } } diff --git a/internal/catalog/discovery_test.go b/internal/catalog/discovery_test.go index 98b6218a..23181944 100644 --- a/internal/catalog/discovery_test.go +++ b/internal/catalog/discovery_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/atomicfs" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestFetchDiscoveryParsesModelsAndSendsHeaders(t *testing.T) { @@ -29,15 +30,9 @@ func TestFetchDiscoveryParsesModelsAndSendsHeaders(t *testing.T) { seenRequest := false server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { seenRequest = true - if r.URL.Path != "/v1/models" { - t.Fatalf("discovery path = %q, want /v1/models", r.URL.Path) - } - if got := r.Header.Get("Authorization"); got != "Bearer secret-token" { - t.Fatalf("Authorization header = %q", got) - } - if got := r.Header.Get("X-Test-Header"); got != "yes" { - t.Fatalf("X-Test-Header = %q", got) - } + testassert.Equal(t, r.URL.Path, "/v1/models") + testassert.Equal(t, r.Header.Get("Authorization"), "Bearer secret-token") + testassert.Equal(t, r.Header.Get("X-Test-Header"), "yes") w.Header().Set("Content-Type", "application/json") // One successful shape: the valid document may be followed by JSON // whitespace (space, tab, CR, LF). Non-whitespace trailing data is @@ -75,29 +70,13 @@ func TestFetchDiscoveryParsesModelsAndSendsHeaders(t *testing.T) { }, } discovered, err := FetchDiscovery(context.Background(), server.Client(), provider) - if err != nil { - t.Fatalf("FetchDiscovery returned error: %v", err) - } - if !seenRequest { - t.Fatalf("test server was not called") - } + testassert.NoError(t, err) + testassert.Require(t, seenRequest, "test server was not called") rich := discovered.Models["rich"] - if rich.Name != "Rich Model" || rich.ContextWindow != 128000 || rich.MaxOutputTokens != 16384 { - t.Fatalf("rich model = %#v", rich) - } - if rich.Cost == nil || rich.Cost.Input == nil || rich.Cost.Output == nil { - t.Fatalf("rich cost = %#v, want input/output", rich.Cost) - } - if rich.Cost.CacheRead == nil || rich.Cost.CacheWrite == nil { - t.Fatalf("rich cost = %#v, want cache read/write", rich.Cost) - } - if !floatNear(*rich.Cost.Input, 0.15) || !floatNear(*rich.Cost.Output, 0.60) || !floatNear(*rich.Cost.CacheRead, 0.03) || !floatNear(*rich.Cost.CacheWrite, 0.20) { - t.Fatalf("rich cost = %#v, want 0.15/0.60/0.03/0.20 per million", rich.Cost) - } + testassert.Require(t, rich.Name == "Rich Model" && rich.ContextWindow == 128000 && rich.MaxOutputTokens == 16384 && rich.Cost != nil && rich.Cost.Input != nil && rich.Cost.Output != nil, "rich model = %#v; rich cost = %#v, want input/output", rich, rich.Cost) + testassert.Require(t, rich.Cost.CacheRead != nil && rich.Cost.CacheWrite != nil && floatNear(*rich.Cost.Input, 0.15) && floatNear(*rich.Cost.Output, 0.60) && floatNear(*rich.Cost.CacheRead, 0.03) && floatNear(*rich.Cost.CacheWrite, 0.20), "rich cost = %#v, want cache read/write; rich cost = %#v, want 0.15/0.60/0.03/0.20 per million", rich.Cost, rich.Cost) fallback := discovered.Models["fallback"] - if fallback.ContextWindow != 32000 || fallback.MaxOutputTokens != 4096 { - t.Fatalf("fallback model = %#v", fallback) - } + testassert.Require(t, fallback.ContextWindow == 32000 && fallback.MaxOutputTokens == 4096, "fallback model = %#v", fallback) } func TestFetchDiscoveryReturnsBoundedFailures(t *testing.T) { @@ -123,9 +102,7 @@ func TestFetchDiscoveryReturnsBoundedFailures(t *testing.T) { defer server.Close() _, err := FetchDiscovery(context.Background(), server.Client(), &Provider{ID: "test", Transport: Transport{BaseURL: server.URL + "/v1"}}) - if err == nil || !strings.Contains(err.Error(), tt.wantError) { - t.Fatalf("FetchDiscovery error = %v, want substring %q", err, tt.wantError) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), tt.wantError), "FetchDiscovery error = %v, want substring %q", err, tt.wantError) }) } } @@ -168,13 +145,9 @@ func TestFetchDiscoveryBoundsResponseBytes(t *testing.T) { })) defer server.Close() discovered, err := FetchDiscovery(context.Background(), server.Client(), &Provider{ID: "test", Transport: Transport{BaseURL: server.URL + "/v1"}}) - if err != nil { - t.Fatalf("FetchDiscovery at the exact limit: %v", err) - } + testassert.NoError(t, err) entry := discovered.Models["padded"] - if entry.ContextWindow != 8 { - t.Fatalf("padded model = %#v, want the padded document's own projection", entry) - } + testassert.Require(t, entry.ContextWindow == 8, "padded model = %#v, want the padded document's own projection", entry) }) t.Run("over_limit_known_length", func(t *testing.T) { @@ -183,8 +156,9 @@ func TestFetchDiscoveryBoundsResponseBytes(t *testing.T) { _, _ = io.WriteString(w, oversized) })) defer server.Close() - if err := fetch(t, server.Client(), server.URL+"/v1"); err == nil || !strings.Contains(err.Error(), "byte maximum") { - t.Fatalf("FetchDiscovery over the limit = %v, want the byte-maximum refusal", err) + { + err := fetch(t, server.Client(), server.URL+"/v1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "byte maximum"), "FetchDiscovery over the limit = %v, want the byte-maximum refusal", err) } }) @@ -193,8 +167,9 @@ func TestFetchDiscoveryBoundsResponseBytes(t *testing.T) { _, _ = io.WriteString(w, oversized) })) defer server.Close() - if err := fetch(t, server.Client(), server.URL+"/v1"); err == nil || !strings.Contains(err.Error(), "byte maximum") { - t.Fatalf("FetchDiscovery over the limit = %v, want the byte-maximum refusal", err) + { + err := fetch(t, server.Client(), server.URL+"/v1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "byte maximum"), "FetchDiscovery over the limit = %v, want the byte-maximum refusal", err) } }) @@ -207,8 +182,9 @@ func TestFetchDiscoveryBoundsResponseBytes(t *testing.T) { Body: io.NopCloser(strings.NewReader(oversized)), }, nil })} - if err := fetch(t, client, "https://bounds.test/v1"); err == nil || !strings.Contains(err.Error(), "byte maximum") { - t.Fatalf("FetchDiscovery over the limit = %v, want the byte-maximum refusal despite the misleading length", err) + { + err := fetch(t, client, "https://bounds.test/v1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "byte maximum"), "FetchDiscovery over the limit = %v, want the byte-maximum refusal despite the misleading length", err) } }) } @@ -217,7 +193,7 @@ func TestDiscoveryCacheWriteReadRoundTripLoadsAttempt(t *testing.T) { home := t.TempDir() fetchedAt := time.Now().Add(-48 * time.Hour).UTC() transport := discoveryTestTransport() - if err := WriteDiscoveryCache(home, "local", transport, DiscoveredProvider{Models: map[string]DiscoveredModel{ + testassert.NoError(t, WriteDiscoveryCache(home, "local", transport, DiscoveredProvider{Models: map[string]DiscoveredModel{ "qwen3": { Name: "Qwen 3", ContextWindow: 40960, @@ -230,82 +206,54 @@ func TestDiscoveryCacheWriteReadRoundTripLoadsAttempt(t *testing.T) { SupportedParameters: []string{"tools"}, }, }, - }}, fetchedAt); err != nil { - t.Fatalf("WriteDiscoveryCache returned error: %v", err) - } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "local.json")); err != nil { - t.Fatalf("discovery cache file missing: %v", err) + }}, fetchedAt)) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "local.json")) + testassert.NoError(t, err) } cache, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 { - t.Fatalf("ReadDiscoveryCache warnings = %#v, want none", warnings) - } - if !cache["local"].AttemptedAt.Equal(fetchedAt) { - t.Fatalf("attempt = %v, want %v", cache["local"].AttemptedAt, fetchedAt) - } + testassert.Require(t, len(warnings) == 0, "ReadDiscoveryCache warnings = %#v, want none", warnings) + testassert.Require(t, cache["local"].AttemptedAt.Equal(fetchedAt), "attempt = %v, want %v", cache["local"].AttemptedAt, fetchedAt) model := cache["local"].Models["qwen3"] - if model.Name != "Qwen 3" || model.ContextWindow != 40960 || model.MaxOutputTokens != 8192 { - t.Fatalf("round-tripped model = %#v", model) - } - if model.Cost == nil || model.Cost.Input == nil || !floatNear(*model.Cost.Input, 0.20) { - t.Fatalf("round-tripped cost = %#v", model.Cost) - } - if model.metadata == nil { - t.Fatalf("round-tripped metadata = nil") + testassert.Require(t, model.Name == "Qwen 3" && model.ContextWindow == 40960 && model.MaxOutputTokens == 8192 && model.Cost != nil && model.Cost.Input != nil && floatNear(*model.Cost.Input, 0.20), "round-tripped model = %#v; round-tripped cost = %#v", model, model.Cost) + testassert.Require(t, model.metadata != nil, "round-tripped metadata = nil") + testassert.Require(t, model.metadata.Type == "chat" && reflect.DeepEqual(model.metadata.ArchitectureOutputModalities, []string{"text"}), "round-tripped metadata = %#v", model.metadata) + { + got := model.metadata.Capabilities["chat_completion"] + testassert.Require(t, got, "round-tripped capabilities = %#v, want chat_completion=true", model.metadata.Capabilities) } - if model.metadata.Type != "chat" || !reflect.DeepEqual(model.metadata.ArchitectureOutputModalities, []string{"text"}) { - t.Fatalf("round-tripped metadata = %#v", model.metadata) - } - if got := model.metadata.Capabilities["chat_completion"]; !got { - t.Fatalf("round-tripped capabilities = %#v, want chat_completion=true", model.metadata.Capabilities) - } - if got := model.metadata.Capabilities["embeddings"]; got { - t.Fatalf("round-tripped capabilities = %#v, want embeddings=false", model.metadata.Capabilities) + { + got := model.metadata.Capabilities["embeddings"] + testassert.Require(t, !got, "round-tripped capabilities = %#v, want embeddings=false", model.metadata.Capabilities) } } func TestReadDiscoveryCacheSkipsMalformedFilesWithWarning(t *testing.T) { home := t.TempDir() path := filepath.Join(home, ".lightcode", "cache", "discovery", "bad.json") - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatalf("MkdirAll: %v", err) - } - if err := os.WriteFile(path, []byte(`{not json`), 0o600); err != nil { - t.Fatalf("WriteFile: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + testassert.NoError(t, os.WriteFile(path, []byte(`{not json`), 0o600)) cache, warnings := ReadDiscoveryCache(home) - if len(cache) != 0 { - t.Fatalf("cache = %#v, want empty", cache) - } - if len(warnings) != 1 || warnings[0].Kind != "discovery_failure" || warnings[0].Provider != "bad" { - t.Fatalf("warnings = %#v, want discovery_failure for bad", warnings) - } + testassert.Require(t, len(cache) == 0, "cache = %#v, want empty", cache) + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "discovery_failure" && warnings[0].Provider == "bad", "warnings = %#v, want discovery_failure for bad", warnings) } func TestReadDiscoveryCacheOldShapeWithoutMetadataKeepsUnknown(t *testing.T) { home := t.TempDir() path := filepath.Join(home, ".lightcode", "cache", "discovery", "local.json") - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatalf("MkdirAll: %v", err) - } - if err := os.WriteFile(path, []byte(`{ + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + testassert.NoError(t, os.WriteFile(path, []byte(`{ "fetched_at": "2026-05-30T00:00:00Z", "models": { "old": {"id": "old", "name": "Old", "context_window": 8192} } - }`), 0o600); err != nil { - t.Fatalf("WriteFile: %v", err) - } + }`), 0o600)) cache, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 { - t.Fatalf("ReadDiscoveryCache warnings = %#v", warnings) - } - if len(cache["local"].Models) != 0 || !cache["local"].AttemptedAt.IsZero() { - t.Fatalf("legacy cache record = %#v, want unbound empty record", cache["local"]) - } + testassert.Require(t, len(warnings) == 0, "ReadDiscoveryCache warnings = %#v", warnings) + testassert.Require(t, len(cache["local"].Models) == 0 && cache["local"].AttemptedAt.IsZero(), "legacy cache record = %#v, want unbound empty record", cache["local"]) } func TestWriteDiscoveryAttemptPreservesCachedModels(t *testing.T) { @@ -313,31 +261,22 @@ func TestWriteDiscoveryAttemptPreservesCachedModels(t *testing.T) { fetchedAt := time.Now().Add(-48 * time.Hour).UTC() attemptedAt := time.Now().UTC() transport := discoveryTestTransport() - if err := WriteDiscoveryCache(home, "local", transport, DiscoveredProvider{Models: map[string]DiscoveredModel{ + testassert.NoError(t, WriteDiscoveryCache(home, "local", transport, DiscoveredProvider{Models: map[string]DiscoveredModel{ "qwen3": {Name: "Qwen 3", ContextWindow: 40960, MaxOutputTokens: 8192}, - }}, fetchedAt); err != nil { - t.Fatalf("WriteDiscoveryCache returned error: %v", err) - } - if err := WriteDiscoveryAttempt(home, "local", transport, attemptedAt); err != nil { - t.Fatalf("WriteDiscoveryAttempt returned error: %v", err) - } + }}, fetchedAt)) + testassert.NoError(t, WriteDiscoveryAttempt(home, "local", transport, attemptedAt)) cache, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 { - t.Fatalf("ReadDiscoveryCache warnings = %#v", warnings) - } - if _, ok := cache["local"].Models["qwen3"]; !ok { - t.Fatalf("cached model missing after attempt write: %#v", cache) - } - if !cache["local"].AttemptedAt.Equal(attemptedAt) { - t.Fatalf("attempt = %v, want %v", cache["local"].AttemptedAt, attemptedAt) + testassert.Require(t, len(warnings) == 0, "ReadDiscoveryCache warnings = %#v", warnings) + { + _, ok := cache["local"].Models["qwen3"] + testassert.Require(t, ok, "cached model missing after attempt write: %#v", cache) } + testassert.Require(t, cache["local"].AttemptedAt.Equal(attemptedAt), "attempt = %v, want %v", cache["local"].AttemptedAt, attemptedAt) } func TestWriteDiscoveryCacheRejectsUnsafeProviderID(t *testing.T) { err := WriteDiscoveryCache(t.TempDir(), "../bad", discoveryTestTransport(), DiscoveredProvider{}, time.Now()) - if err == nil || !errors.Is(err, ErrInvalidModelRef) { - t.Fatalf("WriteDiscoveryCache error = %v, want ErrInvalidModelRef", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrInvalidModelRef), "WriteDiscoveryCache error = %v, want ErrInvalidModelRef", err) } func floatPtr(v float64) *float64 { @@ -367,35 +306,23 @@ func TestTransportFingerprintCanonicalIdentity(t *testing.T) { "X-B": "b-value", }, } - if !SameTransport(base, equivalent) { - t.Fatal("equivalent transports did not share a fingerprint") - } + testassert.Require(t, SameTransport(base, equivalent), "equivalent transports did not share a fingerprint") changedConfiguredAuthorization := equivalent changedConfiguredAuthorization.Headers = map[string]string{ "X-A": "a-value", "Authorization": "Bearer changed-secret", "X-B": "b-value", } - if SameTransport(base, changedConfiguredAuthorization) { - t.Fatal("different configured Authorization values shared a fingerprint") - } - if SameTransport(base, Transport{BaseURL: base.BaseURL, APIKeyEnv: "OTHER_KEY", Headers: base.Headers}) { - t.Fatal("different API key environment names shared a fingerprint") - } - if SameTransport(base, Transport{BaseURL: "https://other.test/v1", APIKeyEnv: base.APIKeyEnv, Headers: base.Headers}) { - t.Fatal("different base URLs shared a fingerprint") - } + testassert.Require(t, !SameTransport(base, changedConfiguredAuthorization), "different configured Authorization values shared a fingerprint") + testassert.Require(t, !SameTransport(base, Transport{BaseURL: base.BaseURL, APIKeyEnv: "OTHER_KEY", Headers: base.Headers}), "different API key environment names shared a fingerprint") + testassert.Require(t, !SameTransport(base, Transport{BaseURL: "https://other.test/v1", APIKeyEnv: base.APIKeyEnv, Headers: base.Headers}), "different base URLs shared a fingerprint") changedHeader := equivalent changedHeader.Headers = map[string]string{"X-A": "changed"} - if SameTransport(base, changedHeader) { - t.Fatal("different header values shared a fingerprint") - } - if !SameTransport( + testassert.Require(t, !SameTransport(base, changedHeader), "different header values shared a fingerprint") + testassert.Require(t, SameTransport( Transport{BaseURL: base.BaseURL, APIKeyEnv: base.APIKeyEnv}, Transport{BaseURL: base.BaseURL, APIKeyEnv: base.APIKeyEnv, Headers: map[string]string{}}, - ) { - t.Fatal("nil and empty transport headers did not share a fingerprint") - } + ), "nil and empty transport headers did not share a fingerprint") if len(transportFingerprint(base)) != sha256.Size*2 || strings.ToLower(transportFingerprint(base)) != transportFingerprint(base) { t.Fatalf("transport fingerprint = %q, want lowercase SHA-256 hex", transportFingerprint(base)) } @@ -410,12 +337,8 @@ func TestTransportFingerprintIgnoresResolvedAPIKey(t *testing.T) { t.Setenv(apiKeyEnv, "resolved-secret-b") secondFingerprint := transportFingerprint(transport) secondSame := SameTransport(transport, transport) - if firstFingerprint != secondFingerprint { - t.Fatalf("resolved secret changed fingerprint: first=%q second=%q", firstFingerprint, secondFingerprint) - } - if !firstSame || firstSame != secondSame { - t.Fatalf("SameTransport changed with resolved secret: before=%v after=%v", firstSame, secondSame) - } + testassert.Equal(t, firstFingerprint, secondFingerprint) + testassert.Require(t, firstSame && firstSame == secondSame, "SameTransport changed with resolved secret: before=%v after=%v", firstSame, secondSame) } func TestDiscoveryRecordsAreBoundForModelsAndTTL(t *testing.T) { @@ -423,45 +346,29 @@ func TestDiscoveryRecordsAreBoundForModelsAndTTL(t *testing.T) { transportA := Transport{BaseURL: "http://a.test/v1"} transportB := Transport{BaseURL: "http://b.test/v1"} fetchedAt := time.Now().Add(-time.Hour).UTC() - if err := WriteDiscoveryCache(home, "p", transportA, DiscoveredProvider{Models: map[string]DiscoveredModel{"a": {ContextWindow: 4096}}}, fetchedAt); err != nil { - t.Fatal(err) - } + testassert.NoError(t, WriteDiscoveryCache(home, "p", transportA, DiscoveredProvider{Models: map[string]DiscoveredModel{"a": {ContextWindow: 4096}}}, fetchedAt)) records, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 || !records["p"].BoundTo(transportA) { - t.Fatalf("records = %#v, warnings = %#v", records, warnings) - } - if !records["p"].FetchedAt.Equal(fetchedAt) || records["p"].Models["a"].ContextWindow != 4096 { - t.Fatalf("matching record = %#v", records["p"]) - } - if recent, err := DiscoveryAttemptRecent(home, "p", transportA, time.Now().UTC()); err != nil || !recent { - t.Fatalf("matching transport recent = (%v, %v), want (true, nil)", recent, err) - } - if recent, err := DiscoveryAttemptRecent(home, "p", transportB, time.Now().UTC()); err != nil || recent { - t.Fatalf("foreign transport recent = (%v, %v), want (false, nil)", recent, err) + testassert.Require(t, len(warnings) == 0 && records["p"].BoundTo(transportA), "records = %#v, warnings = %#v", records, warnings) + testassert.Require(t, records["p"].FetchedAt.Equal(fetchedAt) && records["p"].Models["a"].ContextWindow == 4096, "matching record = %#v", records["p"]) + { + recent, err := DiscoveryAttemptRecent(home, "p", transportA, time.Now().UTC()) + testassert.Require(t, err == nil && recent, "matching transport recent = (%v, %v), want (true, nil)", recent, err) } - if err := WriteDiscoveryAttempt(home, "p", transportA, time.Now().UTC()); err != nil { - t.Fatal(err) + { + recent, err := DiscoveryAttemptRecent(home, "p", transportB, time.Now().UTC()) + testassert.Require(t, err == nil && !recent, "foreign transport recent = (%v, %v), want (false, nil)", recent, err) } + testassert.NoError(t, WriteDiscoveryAttempt(home, "p", transportA, time.Now().UTC())) records, _ = ReadDiscoveryCache(home) - if records["p"].Models["a"].ContextWindow != 4096 || !records["p"].FetchedAt.Equal(fetchedAt) { - t.Fatalf("matching attempt did not preserve record fields: %#v", records["p"]) - } - if err := WriteDiscoveryAttempt(home, "p", transportB, time.Now().UTC()); err != nil { - t.Fatal(err) - } + testassert.Require(t, records["p"].Models["a"].ContextWindow == 4096 && records["p"].FetchedAt.Equal(fetchedAt), "matching attempt did not preserve record fields: %#v", records["p"]) + testassert.NoError(t, WriteDiscoveryAttempt(home, "p", transportB, time.Now().UTC())) records, _ = ReadDiscoveryCache(home) - if !records["p"].BoundTo(transportB) || len(records["p"].Models) != 0 || !records["p"].FetchedAt.IsZero() { - t.Fatalf("foreign attempt retained stale fields: %#v", records["p"]) - } - if err := WriteDiscoveryCache(home, "p", transportA, DiscoveredProvider{Models: map[string]DiscoveredModel{"stale": {}}}, time.Now().UTC()); err != nil { - t.Fatal(err) - } + testassert.Require(t, records["p"].BoundTo(transportB) && len(records["p"].Models) == 0 && records["p"].FetchedAt.IsZero(), "foreign attempt retained stale fields: %#v", records["p"]) + testassert.NoError(t, WriteDiscoveryCache(home, "p", transportA, DiscoveredProvider{Models: map[string]DiscoveredModel{"stale": {}}}, time.Now().UTC())) records, _ = ReadDiscoveryCache(home) cat := &Catalog{Providers: map[string]*Provider{"p": {ID: "p", Discovery: true, Transport: transportB}}} candidates := DiscoveryRefreshCandidates(cat, records, time.Now().UTC()) - if !reflect.DeepEqual(candidates, []string{"p"}) { - t.Fatalf("foreign physical overwrite candidates = %v, want [p]", candidates) - } + testassert.Require(t, reflect.DeepEqual(candidates, []string{"p"}), "foreign physical overwrite candidates = %v, want [p]", candidates) } func floatNear(a, b float64) bool { @@ -480,15 +387,8 @@ func TestParseDiscoveredModelReadsMaxTokensFromTopProvider(t *testing.T) { "max_completion_tokens": float64(16384), }, }) - if id != "test-model" { - t.Fatalf("id = %q, want test-model", id) - } - if model.ContextWindow != 128000 { - t.Fatalf("ContextWindow = %d, want 128000", model.ContextWindow) - } - if model.MaxOutputTokens != 16384 { - t.Fatalf("MaxOutputTokens = %d, want 16384", model.MaxOutputTokens) - } + testassert.Equal(t, id, "test-model") + testassert.Require(t, model.ContextWindow == 128000 && model.MaxOutputTokens == 16384, "got %v, want %v; got %v, want %v", model.ContextWindow, 128000, model.MaxOutputTokens, 16384) } func TestParseDiscoveredModelPreservesGenericMetadata(t *testing.T) { @@ -510,37 +410,10 @@ func TestParseDiscoveredModelPreservesGenericMetadata(t *testing.T) { }, "supported_parameters": []any{"tools", "response-format"}, }) - if id != "metadata-model" { - t.Fatalf("id = %q, want metadata-model", id) - } + testassert.Equal(t, id, "metadata-model") meta := model.metadata - if meta == nil { - t.Fatal("Metadata = nil") - } - if meta.Type != "chat" || meta.Task != "text_generation" { - t.Fatalf("Type/Task = %q/%q, want chat/text_generation", meta.Type, meta.Task) - } - if !reflect.DeepEqual(meta.InputModalities, []string{"image", "text"}) { - t.Fatalf("InputModalities = %#v", meta.InputModalities) - } - if !reflect.DeepEqual(meta.OutputModalities, []string{"text"}) { - t.Fatalf("OutputModalities = %#v", meta.OutputModalities) - } - if !reflect.DeepEqual(meta.ArchitectureInputModalities, []string{"audio", "text"}) { - t.Fatalf("ArchitectureInputModalities = %#v", meta.ArchitectureInputModalities) - } - if !reflect.DeepEqual(meta.ArchitectureOutputModalities, []string{"text"}) { - t.Fatalf("ArchitectureOutputModalities = %#v", meta.ArchitectureOutputModalities) - } - if meta.ArchitectureModality != "text+image->text" { - t.Fatalf("ArchitectureModality = %q", meta.ArchitectureModality) - } - if !meta.Capabilities["chat_completion"] || meta.Capabilities["embeddings"] { - t.Fatalf("Capabilities = %#v", meta.Capabilities) - } - if !reflect.DeepEqual(meta.SupportedParameters, []string{"response_format", "tools"}) { - t.Fatalf("SupportedParameters = %#v", meta.SupportedParameters) - } + testassert.Require(t, meta != nil, "Metadata = nil") + testassert.Require(t, meta.Type == "chat" && meta.Task == "text_generation" && reflect.DeepEqual(meta.InputModalities, []string{"image", "text"}) && reflect.DeepEqual(meta.OutputModalities, []string{"text"}) && reflect.DeepEqual(meta.ArchitectureInputModalities, []string{"audio", "text"}) && reflect.DeepEqual(meta.ArchitectureOutputModalities, []string{"text"}) && meta.ArchitectureModality == "text+image->text" && meta.Capabilities["chat_completion"] && !meta.Capabilities["embeddings"] && reflect.DeepEqual(meta.SupportedParameters, []string{"response_format", "tools"}), "Type/Task = %q/%q, want chat/text_generation; InputModalities = %#v; OutputModalities = %#v; ArchitectureInputModalities = %#v; ArchitectureOutputModalities = %#v; got %v, want %v; Capabilities = %#v; SupportedParameters = %#v", meta.Type, meta.Task, meta.InputModalities, meta.OutputModalities, meta.ArchitectureInputModalities, meta.ArchitectureOutputModalities, meta.ArchitectureModality, "text+image->text", meta.Capabilities, meta.SupportedParameters) } func TestParseDiscoveredModelPrefersNestedTopProviderOverTopLevel(t *testing.T) { @@ -552,12 +425,8 @@ func TestParseDiscoveredModelPrefersNestedTopProviderOverTopLevel(t *testing.T) "max_completion_tokens": float64(128000), }, }) - if id != "aggregator-route" { - t.Fatalf("id = %q, want aggregator-route", id) - } - if model.MaxOutputTokens != 128000 { - t.Fatalf("MaxOutputTokens = %d, want 128000 (per-route cap from top_provider, not the model-wide aggregate)", model.MaxOutputTokens) - } + testassert.Equal(t, id, "aggregator-route") + testassert.Equal(t, model.MaxOutputTokens, 128000) } func TestParseDiscoveredModelSkipsNullMaxTokensInTopProvider(t *testing.T) { @@ -568,15 +437,8 @@ func TestParseDiscoveredModelSkipsNullMaxTokensInTopProvider(t *testing.T) { "max_completion_tokens": nil, }, }) - if id != "free-model" { - t.Fatalf("id = %q, want free-model", id) - } - if model.ContextWindow != 200000 { - t.Fatalf("ContextWindow = %d, want 200000", model.ContextWindow) - } - if model.MaxOutputTokens != 0 { - t.Fatalf("MaxOutputTokens = %d, want 0 (null in API, fallback happens in merge)", model.MaxOutputTokens) - } + testassert.Equal(t, id, "free-model") + testassert.Require(t, model.ContextWindow == 200000 && model.MaxOutputTokens == 0, "got %v, want %v; got %v, want %v", model.ContextWindow, 200000, model.MaxOutputTokens, 0) } // smallStale returns a small stale discovery cache. The assertions only need @@ -629,9 +491,7 @@ func recordDiscoveryLockAcquisitions(t *testing.T) func() []string { // serialize and the fresh discovery survives. func TestConcurrentAttemptWritesLoseNoDiscovery(t *testing.T) { home := t.TempDir() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), smallStale(), time.Now().Add(-48*time.Hour).UTC()); err != nil { - t.Fatalf("seed WriteDiscoveryCache: %v", err) - } + testassert.NoError(t, WriteDiscoveryCache(home, "p", discoveryTestTransport(), smallStale(), time.Now().Add(-48*time.Hour).UTC())) fresh := DiscoveredProvider{Models: map[string]DiscoveredModel{ "fresh-a": {Name: "Fresh A", ContextWindow: 4096}, "fresh-b": {Name: "Fresh B", ContextWindow: 8192}, @@ -643,20 +503,23 @@ func TestConcurrentAttemptWritesLoseNoDiscovery(t *testing.T) { wg.Add(3) go func() { // attempt writer 1 defer wg.Done() - if err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryAttempt 1: %v", err) + { + err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryAttempt 1: %v", err) } }() go func() { // attempt writer 2 defer wg.Done() - if err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryAttempt 2: %v", err) + { + err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryAttempt 2: %v", err) } }() go func() { // successful refresh: publishes fresh discovery defer wg.Done() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), fresh, time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryCache: %v", err) + { + err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), fresh, time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryCache: %v", err) } }() wg.Wait() @@ -666,24 +529,16 @@ func TestConcurrentAttemptWritesLoseNoDiscovery(t *testing.T) { // this assertion deterministically. want := discoveryLockPath(home, "p") got := acquisitions() - if len(got) != 3 { - t.Fatalf("discovery lock acquisitions = %v, want 3 acquisitions of %s (a writer skipped the lock)", got, want) - } + testassert.Require(t, len(got) == 3, "discovery lock acquisitions = %v, want 3 acquisitions of %s (a writer skipped the lock)", got, want) for _, p := range got { - if p != want { - t.Fatalf("discovery lock acquisition path = %q, want %q (writers did not share one lock)", p, want) - } + testassert.Equal(t, p, want) } // Nothing was lost: the fresh discovery survives the concurrent attempts. cache, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 { - t.Fatalf("ReadDiscoveryCache warnings = %#v, want none", warnings) - } + testassert.Require(t, len(warnings) == 0, "ReadDiscoveryCache warnings = %#v, want none", warnings) gotModels := cache["p"].Models - if len(gotModels) != 2 || gotModels["fresh-a"].Name != "Fresh A" || gotModels["fresh-b"].Name != "Fresh B" { - t.Fatalf("fresh discovery lost by concurrent attempt writes: got %d models, want [fresh-a fresh-b]", len(gotModels)) - } + testassert.Require(t, len(gotModels) == 2 && gotModels["fresh-a"].Name == "Fresh A" && gotModels["fresh-b"].Name == "Fresh B", "fresh discovery lost by concurrent attempt writes: got %d models, want [fresh-a fresh-b]", len(gotModels)) } // TestConcurrentAttemptAndDiscoveryWriteLoseNeither proves a concurrent @@ -695,9 +550,7 @@ func TestConcurrentAttemptWritesLoseNoDiscovery(t *testing.T) { // fresh discovery survives. func TestConcurrentAttemptAndDiscoveryWriteLoseNeither(t *testing.T) { home := t.TempDir() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), smallStale(), time.Now().Add(-48*time.Hour).UTC()); err != nil { - t.Fatalf("seed WriteDiscoveryCache: %v", err) - } + testassert.NoError(t, WriteDiscoveryCache(home, "p", discoveryTestTransport(), smallStale(), time.Now().Add(-48*time.Hour).UTC())) fresh := DiscoveredProvider{Models: map[string]DiscoveredModel{ "fresh-a": {Name: "Fresh A", ContextWindow: 4096}, "fresh-b": {Name: "Fresh B", ContextWindow: 8192}, @@ -709,14 +562,16 @@ func TestConcurrentAttemptAndDiscoveryWriteLoseNeither(t *testing.T) { wg.Add(2) go func() { // failed refresh: only its attempt write lands defer wg.Done() - if err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryAttempt: %v", err) + { + err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryAttempt: %v", err) } }() go func() { // successful refresh: publishes fresh discovery defer wg.Done() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), fresh, time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryCache: %v", err) + { + err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), fresh, time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryCache: %v", err) } }() wg.Wait() @@ -726,33 +581,21 @@ func TestConcurrentAttemptAndDiscoveryWriteLoseNeither(t *testing.T) { // fails this assertion deterministically. want := discoveryLockPath(home, "p") got := acquisitions() - if len(got) != 2 { - t.Fatalf("discovery lock acquisitions = %v, want 2 acquisitions of %s (a writer skipped the lock)", got, want) - } + testassert.Require(t, len(got) == 2, "discovery lock acquisitions = %v, want 2 acquisitions of %s (a writer skipped the lock)", got, want) for _, p := range got { - if p != want { - t.Fatalf("discovery lock acquisition path = %q, want %q (writers did not share one lock)", p, want) - } + testassert.Equal(t, p, want) } // Nothing was lost: the fresh discovery survives the concurrent attempt // write, and the published file is complete, parseable JSON. cache, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 { - t.Fatalf("ReadDiscoveryCache warnings = %#v, want none", warnings) - } + testassert.Require(t, len(warnings) == 0, "ReadDiscoveryCache warnings = %#v, want none", warnings) gotModels := cache["p"].Models - if len(gotModels) != 2 || gotModels["fresh-a"].Name != "Fresh A" || gotModels["fresh-b"].Name != "Fresh B" { - t.Fatalf("fresh discovery lost by concurrent attempt write: got %d models, want [fresh-a fresh-b]", len(gotModels)) - } + testassert.Require(t, len(gotModels) == 2 && gotModels["fresh-a"].Name == "Fresh A" && gotModels["fresh-b"].Name == "Fresh B", "fresh discovery lost by concurrent attempt write: got %d models, want [fresh-a fresh-b]", len(gotModels)) data, err := os.ReadFile(filepath.Join(discoveryCacheDir(home), "p.json")) - if err != nil { - t.Fatalf("read cache file: %v", err) - } + testassert.NoError(t, err) var raw discoveryCacheFile - if err := json.Unmarshal(data, &raw); err != nil { - t.Fatalf("published cache not complete JSON: %v", err) - } + testassert.NoError(t, json.Unmarshal(data, &raw)) } func TestConcurrentStaleTransportWriteAfterFreshTransport(t *testing.T) { @@ -769,9 +612,7 @@ func TestConcurrentStaleTransportWriteAfterFreshTransport(t *testing.T) { aAcquired := make(chan struct{}) originalHook := discoveryLockAcquiredHook discoveryLockAcquiredHook = func(lockPath string) { - if lockPath != discoveryLockPath(home, "p") { - t.Errorf("lock path = %q, want provider p lock", lockPath) - } + testassert.Check(t, lockPath == discoveryLockPath(home, "p"), "lock path = %q, want provider p lock", lockPath) acquisitionMu.Lock() acquisitions++ count := acquisitions @@ -790,15 +631,17 @@ func TestConcurrentStaleTransportWriteAfterFreshTransport(t *testing.T) { wg.Add(2) go func() { defer wg.Done() - if err := WriteDiscoveryCache(home, "p", transportB, freshB, time.Now().UTC()); err != nil { - t.Errorf("fresh B WriteDiscoveryCache: %v", err) + { + err := WriteDiscoveryCache(home, "p", transportB, freshB, time.Now().UTC()) + testassert.Check(t, err == nil, "fresh B WriteDiscoveryCache: %v", err) } }() <-bAcquired go func() { defer wg.Done() - if err := WriteDiscoveryCache(home, "p", transportA, staleA, time.Now().Add(-48*time.Hour).UTC()); err != nil { - t.Errorf("stale A WriteDiscoveryCache: %v", err) + { + err := WriteDiscoveryCache(home, "p", transportA, staleA, time.Now().Add(-48*time.Hour).UTC()) + testassert.Check(t, err == nil, "stale A WriteDiscoveryCache: %v", err) } }() close(allowB) @@ -810,25 +653,18 @@ func TestConcurrentStaleTransportWriteAfterFreshTransport(t *testing.T) { wg.Wait() records, warnings := ReadDiscoveryCache(home) - if len(warnings) != 0 { - t.Fatalf("ReadDiscoveryCache warnings = %#v", warnings) - } + testassert.Require(t, len(warnings) == 0, "ReadDiscoveryCache warnings = %#v", warnings) record := records["p"] - if record.TransportFingerprint != transportFingerprint(transportA) || record.Models["stale-a"].Name != "Stale A" { - t.Fatalf("final stale A record = %#v, want stale A after fresh B", record) - } - if record.BoundTo(transportB) { - t.Fatal("B reader treated stale A record as bound") - } - if recent, err := DiscoveryAttemptRecent(home, "p", transportB, time.Now().UTC()); err != nil || recent { - t.Fatalf("B attempt recency = (%v, %v), want (false, nil) for due stale record", recent, err) + testassert.Require(t, record.TransportFingerprint == transportFingerprint(transportA) && record.Models["stale-a"].Name == "Stale A", "final stale A record = %#v, want stale A after fresh B", record) + testassert.Require(t, !record.BoundTo(transportB), "B reader treated stale A record as bound") + { + recent, err := DiscoveryAttemptRecent(home, "p", transportB, time.Now().UTC()) + testassert.Require(t, err == nil && !recent, "B attempt recency = (%v, %v), want (false, nil) for due stale record", recent, err) } candidates := DiscoveryRefreshCandidates(&Catalog{Providers: map[string]*Provider{ "p": {ID: "p", Transport: transportB, Discovery: true, Models: map[string]*Model{}}, }}, records, time.Now().UTC()) - if !reflect.DeepEqual(candidates, []string{"p"}) { - t.Fatalf("B refresh candidates = %#v, want [p]", candidates) - } + testassert.Require(t, reflect.DeepEqual(candidates, []string{"p"}), "B refresh candidates = %#v, want [p]", candidates) result := Build(BuildInputs{ UserRaw: map[string]any{"p": map[string]any{ "transport": map[string]any{"base_url": transportB.BaseURL, "api_key_env": transportB.APIKeyEnv}, @@ -837,8 +673,9 @@ func TestConcurrentStaleTransportWriteAfterFreshTransport(t *testing.T) { }}, Records: records, }) - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "p", Model: "stale-a"}); !errors.Is(err, ErrUnknownModel) { - t.Fatalf("B catalog stale model lookup = %v, want ErrUnknownModel", err) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "p", Model: "stale-a"}) + testassert.Require(t, errors.Is(err, ErrUnknownModel), "B catalog stale model lookup = %v, want ErrUnknownModel", err) } } @@ -865,34 +702,33 @@ func TestConcurrentDiscoveryWritesDoNotTearFile(t *testing.T) { wg.Add(2) go func() { defer wg.Done() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), setA, time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryCache A: %v", err) + { + err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), setA, time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryCache A: %v", err) } }() go func() { defer wg.Done() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), setB, time.Now().UTC()); err != nil { - t.Errorf("WriteDiscoveryCache B: %v", err) + { + err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), setB, time.Now().UTC()) + testassert.Check(t, err == nil, "WriteDiscoveryCache B: %v", err) } }() wg.Wait() data, err := os.ReadFile(filepath.Join(discoveryCacheDir(home), "p.json")) - if err != nil { - t.Fatalf("round %d: read cache file: %v", round, err) - } + testassert.Require(t, err == nil, "round %d: read cache file: %v", round, err) var raw discoveryCacheFile - if err := json.Unmarshal(data, &raw); err != nil { - t.Fatalf("round %d: torn cache file, not complete JSON: %v", round, err) + { + err := json.Unmarshal(data, &raw) + testassert.Require(t, err == nil, "round %d: torn cache file, not complete JSON: %v", round, err) } got := make([]string, 0, len(raw.Models)) for id := range raw.Models { got = append(got, id) } sort.Strings(got) - if !reflect.DeepEqual(got, keysA) && !reflect.DeepEqual(got, keysB) { - t.Fatalf("round %d: torn cache file mixing payloads: %v", round, got) - } + testassert.Require(t, reflect.DeepEqual(got, keysA) || reflect.DeepEqual(got, keysB), "round %d: torn cache file mixing payloads: %v", round, got) } } @@ -902,73 +738,58 @@ func TestConcurrentDiscoveryWritesDoNotTearFile(t *testing.T) { func TestTryDiscoveryWriterRows(t *testing.T) { t.Run("attempt_success", func(t *testing.T) { home := t.TempDir() - if err := WriteDiscoveryCache(home, "p", discoveryTestTransport(), smallStale(), time.Now().Add(-48*time.Hour).UTC()); err != nil { - t.Fatalf("seed: %v", err) - } + testassert.NoError(t, WriteDiscoveryCache(home, "p", discoveryTestTransport(), smallStale(), time.Now().Add(-48*time.Hour).UTC())) ok, err := TryWriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()) - if !ok || err != nil { - t.Fatalf("TryWriteDiscoveryAttempt = (%v, %v), want (true, nil)", ok, err) - } + testassert.Require(t, ok && err == nil, "TryWriteDiscoveryAttempt = (%v, %v), want (true, nil)", ok, err) records, _ := ReadDiscoveryCache(home) - if records["p"].AttemptedAt.IsZero() { - t.Fatal("attempted_at not recorded by TryWriteDiscoveryAttempt") - } + testassert.Require(t, !records["p"].AttemptedAt.IsZero(), "attempted_at not recorded by TryWriteDiscoveryAttempt") }) t.Run("attempt_contention", func(t *testing.T) { home := t.TempDir() holder, ok, err := atomicfs.TryAcquire(discoveryLockPath(home, "p")) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() got, err := TryWriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()) - if got { - t.Fatal("TryWriteDiscoveryAttempt acquired while the lock was held") - } - if err != nil { - t.Fatalf("contention err = %v, want nil", err) - } - if _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(statErr) { - t.Fatalf("contended attempt write created the cache file: %v", statErr) + testassert.Require(t, !got, "TryWriteDiscoveryAttempt acquired while the lock was held") + testassert.NoError(t, err) + { + _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(statErr), "contended attempt write created the cache file: %v", statErr) } }) t.Run("cache_success", func(t *testing.T) { home := t.TempDir() discovered := DiscoveredProvider{Models: map[string]DiscoveredModel{"m": {Name: "M", ContextWindow: 4096}}} ok, err := TryWriteDiscoveryCache(home, "p", discoveryTestTransport(), discovered, time.Now().UTC()) - if !ok || err != nil { - t.Fatalf("TryWriteDiscoveryCache = (%v, %v), want (true, nil)", ok, err) - } + testassert.Require(t, ok && err == nil, "TryWriteDiscoveryCache = (%v, %v), want (true, nil)", ok, err) cache, _ := ReadDiscoveryCache(home) - if _, ok := cache["p"].Models["m"]; !ok { - t.Fatalf("cache after TryWriteDiscoveryCache = %#v, want model m", cache["p"].Models) + { + _, ok := cache["p"].Models["m"] + testassert.Require(t, ok, "cache after TryWriteDiscoveryCache = %#v, want model m", cache["p"].Models) } }) t.Run("cache_contention", func(t *testing.T) { home := t.TempDir() holder, ok, err := atomicfs.TryAcquire(discoveryLockPath(home, "p")) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() got, err := TryWriteDiscoveryCache(home, "p", discoveryTestTransport(), DiscoveredProvider{Models: map[string]DiscoveredModel{"m": {}}}, time.Now().UTC()) - if got { - t.Fatal("TryWriteDiscoveryCache acquired while the lock was held") - } - if err != nil { - t.Fatalf("contention err = %v, want nil", err) - } - if _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(statErr) { - t.Fatalf("contended cache write created the cache file: %v", statErr) + testassert.Require(t, !got, "TryWriteDiscoveryCache acquired while the lock was held") + testassert.NoError(t, err) + { + _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(statErr), "contended cache write created the cache file: %v", statErr) } }) t.Run("unsafe_id", func(t *testing.T) { home := t.TempDir() - if _, err := TryWriteDiscoveryAttempt(home, "a/b", discoveryTestTransport(), time.Now().UTC()); err == nil { - t.Fatal("TryWriteDiscoveryAttempt accepted an unsafe provider id") + { + _, err := TryWriteDiscoveryAttempt(home, "a/b", discoveryTestTransport(), time.Now().UTC()) + testassert.Require(t, err != nil, "TryWriteDiscoveryAttempt accepted an unsafe provider id") } - if _, err := TryWriteDiscoveryCache(home, "a/b", discoveryTestTransport(), DiscoveredProvider{}, time.Now().UTC()); err == nil { - t.Fatal("TryWriteDiscoveryCache accepted an unsafe provider id") + { + _, err := TryWriteDiscoveryCache(home, "a/b", discoveryTestTransport(), DiscoveredProvider{}, time.Now().UTC()) + testassert.Require(t, err != nil, "TryWriteDiscoveryCache accepted an unsafe provider id") } }) } @@ -986,17 +807,12 @@ func TestFetchDiscoveryIfDueRows(t *testing.T) { defer server.Close() provider := &Provider{ID: "p", Discovery: true, Transport: Transport{BaseURL: server.URL + "/v1"}} attempted, discovered, warnings := FetchDiscoveryIfDue(context.Background(), home, provider, time.Now().UTC()) - if !attempted { - t.Fatal("FetchDiscoveryIfDue did not mark the network attempt") - } - if discovered == nil || len(discovered.Models) != 1 { - t.Fatalf("discovered = %#v, want one model", discovered) - } - if len(warnings) != 0 { - t.Fatalf("warnings = %#v, want none", warnings) - } - if _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(statErr) { - t.Fatalf("fetch-only core wrote the cache: %v", statErr) + testassert.Require(t, attempted, "FetchDiscoveryIfDue did not mark the network attempt") + testassert.Require(t, discovered != nil && len(discovered.Models) == 1, "discovered = %#v, want one model", discovered) + testassert.Require(t, len(warnings) == 0, "warnings = %#v, want none", warnings) + { + _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(statErr), "fetch-only core wrote the cache: %v", statErr) } }) t.Run("valid_request_failure_marks_attempt", func(t *testing.T) { @@ -1006,17 +822,12 @@ func TestFetchDiscoveryIfDueRows(t *testing.T) { home := t.TempDir() provider := &Provider{ID: "p", Discovery: true, Transport: Transport{BaseURL: "http://127.0.0.1:9/v1"}} attempted, discovered, warnings := FetchDiscoveryIfDue(context.Background(), home, provider, time.Now().UTC()) - if !attempted { - t.Fatal("FetchDiscoveryIfDue did not mark the network attempt on a valid-request failure") - } - if discovered != nil { - t.Fatalf("discovered = %#v, want nil on failure", discovered) - } - if len(warnings) != 1 || warnings[0].Kind != "discovery_failure" { - t.Fatalf("warnings = %#v, want one discovery_failure", warnings) - } - if _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(statErr) { - t.Fatalf("fetch-only core wrote the attempt on failure: %v", statErr) + testassert.Require(t, attempted, "FetchDiscoveryIfDue did not mark the network attempt on a valid-request failure") + testassert.Require(t, discovered == nil, "discovered = %#v, want nil on failure", discovered) + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "discovery_failure", "warnings = %#v, want one discovery_failure", warnings) + { + _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(statErr), "fetch-only core wrote the attempt on failure: %v", statErr) } }) t.Run("invalid_url_refuses_before_attempt", func(t *testing.T) { @@ -1027,29 +838,20 @@ func TestFetchDiscoveryIfDueRows(t *testing.T) { home := t.TempDir() provider := &Provider{ID: "p", Discovery: true, Transport: Transport{BaseURL: "not an absolute url"}} attempted, discovered, warnings := FetchDiscoveryIfDue(context.Background(), home, provider, time.Now().UTC()) - if attempted { - t.Fatal("FetchDiscoveryIfDue marked an attempt for a pre-network URL validation failure") - } - if discovered != nil { - t.Fatalf("discovered = %#v, want nil for an invalid request", discovered) - } - if len(warnings) != 1 || warnings[0].Kind != "discovery_failure" { - t.Fatalf("warnings = %#v, want one discovery_failure", warnings) - } - if _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(statErr) { - t.Fatalf("invalid-request core created an attempt/cache marker: %v", statErr) + testassert.Require(t, !attempted, "FetchDiscoveryIfDue marked an attempt for a pre-network URL validation failure") + testassert.Require(t, discovered == nil, "discovered = %#v, want nil for an invalid request", discovered) + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "discovery_failure", "warnings = %#v, want one discovery_failure", warnings) + { + _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(statErr), "invalid-request core created an attempt/cache marker: %v", statErr) } }) t.Run("recent_attempt_suppresses", func(t *testing.T) { home := t.TempDir() - if err := WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC()); err != nil { - t.Fatal(err) - } + testassert.NoError(t, WriteDiscoveryAttempt(home, "p", discoveryTestTransport(), time.Now().UTC())) provider := &Provider{ID: "p", Discovery: true, Transport: Transport{BaseURL: "http://127.0.0.1:9/v1"}} attempted, discovered, warnings := FetchDiscoveryIfDue(context.Background(), home, provider, time.Now().UTC()) - if attempted || discovered != nil || len(warnings) != 0 { - t.Fatalf("FetchDiscoveryIfDue = (%v, %#v, %#v), want (false, nil, none) for a recent attempt", attempted, discovered, warnings) - } + testassert.Require(t, !attempted && discovered == nil && len(warnings) == 0, "FetchDiscoveryIfDue = (%v, %#v, %#v), want (false, nil, none) for a recent attempt", attempted, discovered, warnings) }) t.Run("missing_key_suppresses", func(t *testing.T) { home := t.TempDir() @@ -1057,17 +859,13 @@ func TestFetchDiscoveryIfDueRows(t *testing.T) { _ = os.Unsetenv("LIGHTCODE_FETCH_DUE_KEY") provider := &Provider{ID: "p", Discovery: true, Transport: Transport{BaseURL: "http://127.0.0.1:9/v1", APIKeyEnv: "LIGHTCODE_FETCH_DUE_KEY"}} attempted, discovered, warnings := FetchDiscoveryIfDue(context.Background(), home, provider, time.Now().UTC()) - if attempted || discovered != nil || len(warnings) != 0 { - t.Fatalf("FetchDiscoveryIfDue = (%v, %#v, %#v), want (false, nil, none) without a key", attempted, discovered, warnings) - } + testassert.Require(t, !attempted && discovered == nil && len(warnings) == 0, "FetchDiscoveryIfDue = (%v, %#v, %#v), want (false, nil, none) without a key", attempted, discovered, warnings) }) t.Run("disabled_provider_suppresses", func(t *testing.T) { home := t.TempDir() provider := &Provider{ID: "p", Discovery: false, Transport: Transport{BaseURL: "http://127.0.0.1:9/v1"}} attempted, discovered, warnings := FetchDiscoveryIfDue(context.Background(), home, provider, time.Now().UTC()) - if attempted || discovered != nil || len(warnings) != 0 { - t.Fatalf("FetchDiscoveryIfDue = (%v, %#v, %#v), want (false, nil, none) for a disabled provider", attempted, discovered, warnings) - } + testassert.Require(t, !attempted && discovered == nil && len(warnings) == 0, "FetchDiscoveryIfDue = (%v, %#v, %#v), want (false, nil, none) for a disabled provider", attempted, discovered, warnings) }) } @@ -1084,24 +882,17 @@ func TestRefreshProviderDiscoveryTryContention(t *testing.T) { "p": {ID: "p", Builtin: true, Discovery: true, Models: map[string]*Model{}, Transport: Transport{BaseURL: server.URL + "/v1"}}, }} holder, ok, err := atomicfs.TryAcquire(discoveryLockPath(home, "p")) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() changed, warnings := RefreshProviderDiscoveryTryWithConfigPath(context.Background(), home, "", cat, "p") - if changed { - t.Fatal("Try refresh reported a change under foreign lock contention") - } - if len(warnings) == 0 || !strings.Contains(warnings[0].Message, "lock") { - t.Fatalf("warnings = %#v, want a discovery lock contention warning", warnings) - } - if _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(statErr) { - t.Fatalf("contended Try refresh wrote the cache: %v", statErr) - } - if len(cat.Providers["p"].Models) != 0 { - t.Fatalf("contended Try refresh merged models into the catalog: %#v", cat.Providers["p"].Models) + testassert.Require(t, !changed, "Try refresh reported a change under foreign lock contention") + testassert.Require(t, len(warnings) != 0 && strings.Contains(warnings[0].Message, "lock"), "warnings = %#v, want a discovery lock contention warning", warnings) + { + _, statErr := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(statErr), "contended Try refresh wrote the cache: %v", statErr) } + testassert.Require(t, len(cat.Providers["p"].Models) == 0, "contended Try refresh merged models into the catalog: %#v", cat.Providers["p"].Models) } // discoveryBlockHolderEnv selects the child half of @@ -1213,15 +1004,11 @@ func TestRefreshProviderDiscoveryBlocksUntilRelease(t *testing.T) { } select { case res := <-done: - if !res.changed || len(res.warnings) != 0 { - t.Fatalf("blocking refresh after release = (%v, %#v), want (true, none)", res.changed, res.warnings) - } + testassert.Require(t, res.changed && len(res.warnings) == 0, "blocking refresh after release = (%v, %#v), want (true, none)", res.changed, res.warnings) case <-time.After(5 * time.Second): t.Fatal("blocking refresh did not complete after the holder released") } - if len(cat.Providers["p"].Models) != 1 { - t.Fatalf("merged models = %#v, want the fetched model", cat.Providers["p"].Models) - } + testassert.Require(t, len(cat.Providers["p"].Models) == 1, "merged models = %#v, want the fetched model", cat.Providers["p"].Models) } // TestRefreshWrappersDoNotMarkInvalidURLAttempt proves a pre-network URL @@ -1248,14 +1035,11 @@ func TestRefreshWrappersDoNotMarkInvalidURLAttempt(t *testing.T) { "p": {ID: "p", Discovery: true, Transport: Transport{BaseURL: "not an absolute url"}}, }} changed, warnings := tc.run(home, cat) - if changed { - t.Fatal("invalid-URL refresh reported a change") - } - if len(warnings) != 1 || warnings[0].Kind != "discovery_failure" { - t.Fatalf("warnings = %#v, want one discovery_failure", warnings) - } - if _, err := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")); !os.IsNotExist(err) { - t.Fatalf("invalid-URL %s wrapper created an attempt/cache marker: %v", tc.name, err) + testassert.Require(t, !changed, "invalid-URL refresh reported a change") + testassert.Require(t, len(warnings) == 1 && warnings[0].Kind == "discovery_failure", "warnings = %#v, want one discovery_failure", warnings) + { + _, err := os.Stat(filepath.Join(discoveryCacheDir(home), "p.json")) + testassert.Require(t, os.IsNotExist(err), "invalid-URL %s wrapper created an attempt/cache marker: %v", tc.name, err) } }) } diff --git a/internal/catalog/loader_captured_test.go b/internal/catalog/loader_captured_test.go index 73a8d9dd..cc636f1c 100644 --- a/internal/catalog/loader_captured_test.go +++ b/internal/catalog/loader_captured_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/atomicfs" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestLoaderLoadCapturedUsesInputWithoutReadingConfig(t *testing.T) { @@ -30,22 +31,20 @@ func TestLoaderLoadCapturedUsesInputWithoutReadingConfig(t *testing.T) { }} result, err := NewLoader(home, fsys).LoadCaptured(context.Background(), userRaw) - if err != nil { - t.Fatalf("LoadCaptured: %v", err) - } - if len(result.Warnings) != 0 { - t.Fatalf("warnings = %#v, want none: the captured layer must not report config problems", result.Warnings) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "base", Model: "known"}); err != nil { - t.Fatalf("bundled provider missing from the captured build: %v", err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Warnings) == 0, "warnings = %#v, want none: the captured layer must not report config problems", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "base", Model: "known"}) + testassert.NoError(t, err) } if _, model, err := result.Catalog.Lookup(ModelRef{Provider: "local", Model: "known"}); err != nil { t.Fatalf("captured provider missing: %v", err) } else if model.ContextWindow != 9007199254740993 { t.Fatalf("captured model = %#v, want the exact number preserved from the captured input", model) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")); !os.IsNotExist(err) { - t.Fatalf("LoadCaptured read or created the main configuration: %v", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")) + testassert.Require(t, os.IsNotExist(err), "LoadCaptured read or created the main configuration: %v", err) } } @@ -69,31 +68,28 @@ func TestLoaderLoadCapturedCostProtectionUsesCapturedInput(t *testing.T) { loader := NewLoader(home, fsys) result, err := loader.LoadCaptured(context.Background(), userRaw) - if err != nil { - t.Fatalf("LoadCaptured: %v", err) - } - if calls != 1 { - t.Fatalf("discovery calls = %d, want the due provider to be fetched", calls) - } + testassert.NoError(t, err) + testassert.Equal(t, calls, 1) if _, model, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "m"}); err != nil { t.Fatalf("discovered model missing after the one-attempt publication: %v", err) } else if model.Cost == nil || model.Cost.Input == nil || *model.Cost.Input != 0.5 || model.Cost.Output == nil || *model.Cost.Output != 1.2 { t.Fatalf("discovered model cost = %#v, want the captured-declared input protected and the discovered output filled", model.Cost) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); err != nil { - t.Fatalf("discovery cache was not published: %v", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.NoError(t, err) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")); !os.IsNotExist(err) { - t.Fatalf("cost protection reread the main configuration: %v", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")) + testassert.Require(t, os.IsNotExist(err), "cost protection reread the main configuration: %v", err) } // The retained 24h attempted-at TTL suppresses a duplicate fetch. - if _, err := loader.LoadCaptured(context.Background(), userRaw); err != nil { - t.Fatalf("second LoadCaptured: %v", err) - } - if calls != 1 { - t.Fatalf("discovery calls = %d, want the recent attempt TTL retained", calls) + { + _, err := loader.LoadCaptured(context.Background(), userRaw) + testassert.NoError(t, err) } + testassert.Equal(t, calls, 1) } func TestLoaderLoadCapturedCancellationSkipsPublicationWithoutUndoingWrites(t *testing.T) { @@ -134,17 +130,15 @@ func TestLoaderLoadCapturedCancellationSkipsPublicationWithoutUndoingWrites(t *t } result, err := NewLoader(home, fsys).LoadCaptured(ctx, nil) - if !errors.Is(err, context.Canceled) { - t.Fatalf("LoadCaptured error = %v, want the caller's context error unwrapped", err) - } - if result.Catalog != nil || result.Warnings != nil { - t.Fatalf("canceled LoadCaptured published result %#v, want the zero BuildResult", result) + testassert.Require(t, errors.Is(err, context.Canceled), "LoadCaptured error = %v, want the caller's context error unwrapped", err) + testassert.Require(t, result.Catalog == nil && result.Warnings == nil, "canceled LoadCaptured published result %#v, want the zero BuildResult", result) + { + _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "aa.json")) + testassert.Require(t, statErr == nil, "the cache write that had started was undone: %v", statErr) } - if _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "aa.json")); statErr != nil { - t.Fatalf("the cache write that had started was undone: %v", statErr) - } - if _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "bb.json")); !os.IsNotExist(statErr) { - t.Fatalf("the canceled fetch's publication was not skipped: %v", statErr) + { + _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "bb.json")) + testassert.Require(t, os.IsNotExist(statErr), "the canceled fetch's publication was not skipped: %v", statErr) } } @@ -189,17 +183,15 @@ func TestLoaderLoadCapturedFailedFetchKeepsWarningAndAttempt(t *testing.T) { }`)}} result, err := NewLoader(home, fsys).LoadCaptured(context.Background(), nil) - if err != nil { - t.Fatalf("a failed fetch must stay a warning, not an error: %v", err) - } - if !hasWarningForProvider(result.Warnings, "discovery_failure", "remote") { - t.Fatalf("warnings = %#v, want the discovery_failure warning", result.Warnings) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "known"}); err != nil { - t.Fatalf("cached input lost after a failed fetch: %v", err) + testassert.NoError(t, err) + testassert.Require(t, hasWarningForProvider(result.Warnings, "discovery_failure", "remote"), "warnings = %#v, want the discovery_failure warning", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "known"}) + testassert.NoError(t, err) } - if _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); statErr != nil { - t.Fatalf("the failed fetch's attempt marker was not written: %v", statErr) + { + _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.Require(t, statErr == nil, "the failed fetch's attempt marker was not written: %v", statErr) } } @@ -210,15 +202,11 @@ func TestLoaderLoadCapturedContentionWarnsAndKeepsCachedInput(t *testing.T) { })) t.Cleanup(server.Close) transport := Transport{BaseURL: server.URL + "/v1"} - if err := WriteDiscoveryCache(home, "remote", transport, DiscoveredProvider{ + testassert.NoError(t, WriteDiscoveryCache(home, "remote", transport, DiscoveredProvider{ Models: map[string]DiscoveredModel{"cached-model": {ContextWindow: 4096}}, - }, time.Now().Add(-48*time.Hour).UTC()); err != nil { - t.Fatal(err) - } + }, time.Now().Add(-48*time.Hour).UTC())) holder, ok, err := atomicfs.TryAcquire(discoveryLockPath(home, "remote")) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() fsys := fstest.MapFS{"builtin/remote.json": {Data: []byte(`{ @@ -228,17 +216,15 @@ func TestLoaderLoadCapturedContentionWarnsAndKeepsCachedInput(t *testing.T) { "models": {} }`)}} result, err := NewLoader(home, fsys).LoadCaptured(context.Background(), nil) - if err != nil { - t.Fatalf("LoadCaptured under discovery contention = %v, want a result plus warnings", err) + testassert.NoError(t, err) + testassert.Require(t, hasWarningForProvider(result.Warnings, "discovery_failure", "remote"), "warnings = %#v, want the retained contention warning", result.Warnings) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "cached-model"}) + testassert.NoError(t, err) } - if !hasWarningForProvider(result.Warnings, "discovery_failure", "remote") { - t.Fatalf("warnings = %#v, want the retained contention warning", result.Warnings) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "cached-model"}); err != nil { - t.Fatalf("cached input lost under contention: %v", err) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}); err == nil { - t.Fatal("the contended publication merged anyway") + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}) + testassert.Require(t, err != nil, "the contended publication merged anyway") } } @@ -250,11 +236,9 @@ func TestLoaderLoadCapturedAllowRefreshFilterRetained(t *testing.T) { _, _ = w.Write([]byte(`{"data":[{"id":"remote-model","context_window":1000}]}`)) })) t.Cleanup(server.Close) - if err := WriteDiscoveryCache(home, "remote", Transport{BaseURL: server.URL + "/v1"}, DiscoveredProvider{ + testassert.NoError(t, WriteDiscoveryCache(home, "remote", Transport{BaseURL: server.URL + "/v1"}, DiscoveredProvider{ Models: map[string]DiscoveredModel{"cached-model": {ContextWindow: 4096}}, - }, time.Now().Add(-48*time.Hour).UTC()); err != nil { - t.Fatal(err) - } + }, time.Now().Add(-48*time.Hour).UTC())) fsys := fstest.MapFS{"builtin/remote.json": {Data: []byte(`{ "id": "remote", "transport": {"base_url": "` + server.URL + `/v1", "api_key_env": ""}, @@ -265,14 +249,11 @@ func TestLoaderLoadCapturedAllowRefreshFilterRetained(t *testing.T) { loader := NewLoader(home, fsys) loader.AllowRefresh = func(_ string, _ *Provider) bool { return false } result, err := loader.LoadCaptured(context.Background(), nil) - if err != nil { - t.Fatalf("LoadCaptured: %v", err) - } - if calls != 0 { - t.Fatalf("discovery calls = %d, want the AllowRefresh filter retained", calls) - } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "cached-model"}); err != nil { - t.Fatalf("cached input lost under the filter: %v", err) + testassert.NoError(t, err) + testassert.Equal(t, calls, 0) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "remote", Model: "cached-model"}) + testassert.NoError(t, err) } } @@ -323,58 +304,52 @@ func TestLoaderLoadCapturedConnectionOverlaysOneProviderWithoutRefresh(t *testin transport := Transport{BaseURL: server.URL + "/v1", APIKeyEnv: "REMOTE_KEY"} result, err := loader.LoadCapturedConnection(context.Background(), userRaw, "remote", transport, &DiscoveredProvider{Models: fetched}) - if err != nil { - t.Fatalf("LoadCapturedConnection: %v", err) - } - if fetches != 0 { - t.Fatalf("connection candidate performed %d network fetches, want none", fetches) - } + testassert.NoError(t, err) + testassert.Equal(t, fetches, 0) prov := result.Catalog.Providers["remote"] - if prov == nil { - t.Fatal("remote provider missing from the connection candidate") - } - if entry := prov.Models["fresh"]; entry == nil || entry.ContextWindow != 4096 { - t.Fatalf("overlay record missing the fetched model: %#v", prov.Models) + testassert.Require(t, prov != nil, "remote provider missing from the connection candidate") + { + entry := prov.Models["fresh"] + testassert.Require(t, entry != nil && entry.ContextWindow == 4096, "overlay record missing the fetched model: %#v", prov.Models) } if entry := prov.Models["fresh"]; entry != nil && entry.Source != SourceDiscovered { t.Fatalf("overlay model source = %q, want discovered", entry.Source) } - if entry := prov.Models["plain"]; entry == nil || entry.ContextWindow != 4096 || entry.Source != SourceDiscovered { - t.Fatalf("nil-metadata overlay sibling = %#v, want the allowed discovered model", prov.Models["plain"]) + { + entry := prov.Models["plain"] + testassert.Require(t, entry != nil && entry.ContextWindow == 4096 && entry.Source == SourceDiscovered, "nil-metadata overlay sibling = %#v, want the allowed discovered model", prov.Models["plain"]) } - if entry, ok := prov.Models["blocked"]; ok { - t.Fatalf("filtered metadata model reached the catalog: %#v", entry) + { + entry, ok := prov.Models["blocked"] + testassert.Require(t, !ok, "filtered metadata model reached the catalog: %#v", entry) } - if _, ok := result.Catalog.Providers["local"]; !ok { - t.Fatal("the non-connection sibling disappeared from the candidate") + { + _, ok := result.Catalog.Providers["local"] + testassert.Require(t, ok, "the non-connection sibling disappeared from the candidate") } // Nil discovery: the ordinary cached inputs, still no fetch and no // cache write. result, err = loader.LoadCapturedConnection(context.Background(), userRaw, "remote", transport, nil) - if err != nil { - t.Fatalf("LoadCapturedConnection nil discovery: %v", err) - } - if fetches != 0 { - t.Fatalf("nil-discovery candidate performed %d network fetches, want none", fetches) - } + testassert.NoError(t, err) + testassert.Equal(t, fetches, 0) if prov = result.Catalog.Providers["remote"]; prov == nil || len(prov.Models) != 0 { t.Fatalf("nil-discovery candidate carried %v, want the empty cached model set", prov.Models) } - if _, _, err := result.Catalog.Lookup(ModelRef{Provider: "base", Model: "known"}); err != nil { - t.Fatalf("bundled provider missing: %v", err) + { + _, _, err := result.Catalog.Lookup(ModelRef{Provider: "base", Model: "known"}) + testassert.NoError(t, err) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); !os.IsNotExist(err) { - t.Fatalf("the connection candidate wrote a cache file: %v", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.Require(t, os.IsNotExist(err), "the connection candidate wrote a cache file: %v", err) } // A fingerprint over a different transport never binds the overlay: the // same Build producer computes it inside catalog. other := Transport{BaseURL: "http://other.test/v1", APIKeyEnv: "REMOTE_KEY"} result, err = loader.LoadCapturedConnection(context.Background(), userRaw, "remote", other, &DiscoveredProvider{Models: fetched}) - if err != nil { - t.Fatalf("LoadCapturedConnection other transport: %v", err) - } + testassert.NoError(t, err) if prov = result.Catalog.Providers["remote"]; prov != nil && len(prov.Models) != 0 { t.Fatalf("misbound overlay contributed %v, want no models", prov.Models) } diff --git a/internal/catalog/loader_test.go b/internal/catalog/loader_test.go index 8fbb8b0f..3aada652 100644 --- a/internal/catalog/loader_test.go +++ b/internal/catalog/loader_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/atomicfs" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestLoaderReadsBundledUserConfigAndDiscoveryCache(t *testing.T) { @@ -40,14 +41,11 @@ func TestLoaderReadsBundledUserConfigAndDiscoveryCache(t *testing.T) { }`) catalog, warnings, err := NewLoader(home, bundledFS).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if hasNonDiscoveryWarning(warnings) { - t.Fatalf("Load warnings = %#v, want no non-discovery warnings", warnings) - } - if _, _, err := catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5-mini"}); err != nil { - t.Fatalf("bundled openai model missing after Load: %v", err) + testassert.NoError(t, err) + testassert.Require(t, !hasNonDiscoveryWarning(warnings), "Load warnings = %#v, want no non-discovery warnings", warnings) + { + _, _, err := catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5-mini"}) + testassert.NoError(t, err) } if _, model, err := catalog.Lookup(ModelRef{Provider: "local", Model: "known"}); err != nil { t.Fatalf("user-config model missing after Load: %v", err) @@ -56,8 +54,9 @@ func TestLoaderReadsBundledUserConfigAndDiscoveryCache(t *testing.T) { } else if model.Cost == nil || model.Cost.Input == nil || *model.Cost.Input != 0.2 { t.Fatalf("user-config model cost = %#v, want discovery price", model.Cost) } - if _, _, err := catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}); err == nil { - t.Fatalf("config-only provider gained discovered model") + { + _, _, err := catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}) + testassert.Require(t, err != nil, "config-only provider gained discovered model") } } @@ -66,17 +65,15 @@ func TestLoaderCreatesEmptyConfigAndFallsBackToBundled(t *testing.T) { home := t.TempDir() catalog, warnings, err := NewLoader(home, bundledFS).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) + testassert.NoError(t, err) + testassert.Require(t, !hasNonDiscoveryWarning(warnings), "Load warnings = %#v, want no non-discovery warnings", warnings) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")) + testassert.NoError(t, err) } - if hasNonDiscoveryWarning(warnings) { - t.Fatalf("Load warnings = %#v, want no non-discovery warnings", warnings) - } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")); err != nil { - t.Fatalf("config skeleton was not created: %v", err) - } - if _, _, err := catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5-mini"}); err != nil { - t.Fatalf("bundled openai model missing after missing config fallback: %v", err) + { + _, _, err := catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5-mini"}) + testassert.NoError(t, err) } } @@ -86,14 +83,11 @@ func TestLoaderMalformedConfigFallsBackToBundledWithWarning(t *testing.T) { writeLoaderFile(t, filepath.Join(home, ".lightcode", "config.json"), `{not json`) catalog, warnings, err := NewLoader(home, bundledFS).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if !hasWarning(warnings, "user_config_skip") { - t.Fatalf("Load warnings = %#v, want user_config_skip", warnings) - } - if _, _, err := catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5-mini"}); err != nil { - t.Fatalf("bundled openai model missing after malformed config fallback: %v", err) + testassert.NoError(t, err) + testassert.Require(t, hasWarning(warnings, "user_config_skip"), "Load warnings = %#v, want user_config_skip", warnings) + { + _, _, err := catalog.Lookup(ModelRef{Provider: "openai", Model: "gpt-5-mini"}) + testassert.NoError(t, err) } } @@ -111,14 +105,11 @@ func TestLoaderSkipsMalformedDiscoveryCacheWithWarning(t *testing.T) { writeLoaderFile(t, filepath.Join(home, ".lightcode", "cache", "discovery", "local.json"), `{not json`) catalog, warnings, err := NewLoader(home, bundledFS).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if !hasWarning(warnings, "discovery_failure") { - t.Fatalf("Load warnings = %#v, want discovery_failure", warnings) - } - if _, _, err := catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}); err == nil || !strings.Contains(err.Error(), "unknown") { - t.Fatalf("Lookup local/qwen3 error = %v, want unknown model/provider", err) + testassert.NoError(t, err) + testassert.Require(t, hasWarning(warnings, "discovery_failure"), "Load warnings = %#v, want discovery_failure", warnings) + { + _, _, err := catalog.Lookup(ModelRef{Provider: "local", Model: "qwen3"}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "unknown"), "Lookup local/qwen3 error = %v, want unknown model/provider", err) } } @@ -136,17 +127,15 @@ func TestLoaderMalformedTransportKeepsValidSibling(t *testing.T) { "discovery": false, "models": {} }`)}}).Load() - if err != nil { - t.Fatal(err) - } - if !hasWarningForProvider(warnings, "user_config_skip", "invalid") { - t.Fatalf("warnings = %#v, want user_config_skip for invalid malformed transport", warnings) + testassert.NoError(t, err) + testassert.Require(t, hasWarningForProvider(warnings, "user_config_skip", "invalid"), "warnings = %#v, want user_config_skip for invalid malformed transport", warnings) + { + _, _, err := cat.Lookup(ModelRef{Provider: "invalid", Model: "m"}) + testassert.Require(t, err != nil, "malformed transport provider was retained") } - if _, _, err := cat.Lookup(ModelRef{Provider: "invalid", Model: "m"}); err == nil { - t.Fatal("malformed transport provider was retained") - } - if _, _, err := cat.Lookup(ModelRef{Provider: "valid", Model: "m"}); err != nil { - t.Fatalf("valid sibling missing: %v", err) + { + _, _, err := cat.Lookup(ModelRef{Provider: "valid", Model: "m"}) + testassert.NoError(t, err) } } @@ -174,31 +163,28 @@ func TestLoaderTransportBoundRecentFailedAndLegacyRows(t *testing.T) { serverA, callsA := newServer(t, "a-model") serverB, callsB := newServer(t, "b-model") transportA := Transport{BaseURL: serverA.URL + "/v1"} - if err := WriteDiscoveryCache(home, "remote", transportA, DiscoveredProvider{Models: map[string]DiscoveredModel{"a-cached": {ContextWindow: 4096}}}, time.Now().UTC()); err != nil { - t.Fatal(err) - } + testassert.NoError(t, WriteDiscoveryCache(home, "remote", transportA, DiscoveredProvider{Models: map[string]DiscoveredModel{"a-cached": {ContextWindow: 4096}}}, time.Now().UTC())) catA, _, err := NewLoader(home, providerFS(transportA.BaseURL)).Load() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) if *callsA != 0 { t.Fatalf("recent A discovery calls = %d, want 0", *callsA) } - if _, _, err := catA.Lookup(ModelRef{Provider: "remote", Model: "a-cached"}); err != nil { - t.Fatalf("matching A cache model missing: %v", err) + { + _, _, err := catA.Lookup(ModelRef{Provider: "remote", Model: "a-cached"}) + testassert.NoError(t, err) } catB, _, err := NewLoader(home, providerFS(serverB.URL+"/v1")).Load() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) if *callsB != 1 { t.Fatalf("B discovery calls = %d, want 1 after foreign recent A", *callsB) } - if _, _, err := catB.Lookup(ModelRef{Provider: "remote", Model: "b-model"}); err != nil { - t.Fatalf("B discovery model missing: %v", err) + { + _, _, err := catB.Lookup(ModelRef{Provider: "remote", Model: "b-model"}) + testassert.NoError(t, err) } - if _, _, err := catB.Lookup(ModelRef{Provider: "remote", Model: "a-cached"}); err == nil { - t.Fatal("foreign A model survived the B rebuild") + { + _, _, err := catB.Lookup(ModelRef{Provider: "remote", Model: "a-cached"}) + testassert.Require(t, err != nil, "foreign A model survived the B rebuild") } }) t.Run("failed A then B", func(t *testing.T) { @@ -206,11 +192,10 @@ func TestLoaderTransportBoundRecentFailedAndLegacyRows(t *testing.T) { serverA, _ := newServer(t, "unused-a") serverB, callsB := newServer(t, "b-model") transportA := Transport{BaseURL: serverA.URL + "/v1"} - if err := WriteDiscoveryAttempt(home, "remote", transportA, time.Now().UTC()); err != nil { - t.Fatal(err) - } - if _, _, err := NewLoader(home, providerFS(serverB.URL+"/v1")).Load(); err != nil { - t.Fatal(err) + testassert.NoError(t, WriteDiscoveryAttempt(home, "remote", transportA, time.Now().UTC())) + { + _, _, err := NewLoader(home, providerFS(serverB.URL+"/v1")).Load() + testassert.NoError(t, err) } if *callsB != 1 { t.Fatalf("B discovery calls after failed A = %d, want 1", *callsB) @@ -224,14 +209,13 @@ func TestLoaderTransportBoundRecentFailedAndLegacyRows(t *testing.T) { "models": {"legacy": {"context_window": 4096}} }`) cat, _, err := NewLoader(home, providerFS(server.URL+"/v1")).Load() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) if *calls != 1 { t.Fatalf("legacy cache discovery calls = %d, want 1", *calls) } - if _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "legacy"}); err == nil { - t.Fatal("legacy model was used as bound discovery") + { + _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "legacy"}) + testassert.Require(t, err != nil, "legacy model was used as bound discovery") } }) } @@ -241,9 +225,7 @@ func TestLoaderFetchesDiscoveryWhenAttemptDueForBundledProvider(t *testing.T) { calls := 0 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls++ - if r.URL.Path != "/v1/models" { - t.Fatalf("unexpected discovery path %s", r.URL.Path) - } + testassert.Equal(t, r.URL.Path, "/v1/models") _, _ = w.Write([]byte(`{"data":[{"id":"remote-model","name":"Remote Model","context_window":16384,"max_output_tokens":2048}]}`)) })) t.Cleanup(server.Close) @@ -259,22 +241,17 @@ func TestLoaderFetchesDiscoveryWhenAttemptDueForBundledProvider(t *testing.T) { } catalog, warnings, err := NewLoader(home, fsys).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if hasNonDiscoveryWarning(warnings) { - t.Fatalf("Load warnings = %#v, want no non-discovery warnings", warnings) - } - if calls != 1 { - t.Fatalf("discovery calls = %d, want 1", calls) - } + testassert.NoError(t, err) + testassert.Require(t, !hasNonDiscoveryWarning(warnings), "Load warnings = %#v, want no non-discovery warnings", warnings) + testassert.Equal(t, calls, 1) if _, model, err := catalog.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}); err != nil { t.Fatalf("discovered model missing after live Load discovery: %v", err) } else if model.Name != "Remote Model" || model.ContextWindow != 16384 || model.MaxOutputTokens != 2048 { t.Fatalf("discovered model = %#v", model) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); err != nil { - t.Fatalf("discovery cache was not written: %v", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.NoError(t, err) } } @@ -302,17 +279,12 @@ func TestLoaderSkipsDiscoveryWhenAttemptRecent(t *testing.T) { } catalog, warnings, err := NewLoader(home, fsys).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if hasNonDiscoveryWarning(warnings) { - t.Fatalf("Load warnings = %#v, want no non-discovery warnings", warnings) - } - if calls != 0 { - t.Fatalf("discovery calls = %d, want 0", calls) - } - if _, _, err := catalog.Lookup(ModelRef{Provider: "remote", Model: "known"}); err != nil { - t.Fatalf("known model missing: %v", err) + testassert.NoError(t, err) + testassert.Require(t, !hasNonDiscoveryWarning(warnings), "Load warnings = %#v, want no non-discovery warnings", warnings) + testassert.Equal(t, calls, 0) + { + _, _, err := catalog.Lookup(ModelRef{Provider: "remote", Model: "known"}) + testassert.NoError(t, err) } } @@ -334,8 +306,9 @@ func TestLoaderSkipsMissingAPIKeySilently(t *testing.T) { } else if len(warnings) != 0 { t.Fatalf("Load warnings = %#v, want none", warnings) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); !os.IsNotExist(err) { - t.Fatalf("discovery cache stat = %v, want missing cache", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.Require(t, os.IsNotExist(err), "discovery cache stat = %v, want missing cache", err) } } @@ -358,19 +331,11 @@ func TestLoaderDiscoveryFailureWarnsAndDoesNotBlockStartup(t *testing.T) { }`) catalog, warnings, err := NewLoader(home, bundledFS).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if catalog == nil { - t.Fatal("Load returned nil catalog after discovery failure") - } - if !hasWarning(warnings, "discovery_failure") { - t.Fatalf("Load warnings = %#v, want discovery_failure", warnings) - } + testassert.NoError(t, err) + testassert.Require(t, catalog != nil, "Load returned nil catalog after discovery failure") + testassert.Require(t, hasWarning(warnings, "discovery_failure"), "Load warnings = %#v, want discovery_failure", warnings) _, err = os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "local.json")) - if err != nil { - t.Fatalf("discovery attempt cache was not written: %v", err) - } + testassert.NoError(t, err) } func TestLoaderFailedDiscoveryAttemptDoesNotRetryWithinTTL(t *testing.T) { @@ -403,9 +368,7 @@ func TestLoaderFailedDiscoveryAttemptDoesNotRetryWithinTTL(t *testing.T) { } else if hasWarning(warnings, "discovery_failure") { t.Fatalf("second Load warnings = %#v, want no retry failure", warnings) } - if calls != 1 { - t.Fatalf("discovery calls = %d, want 1", calls) - } + testassert.Equal(t, calls, 1) } func writeLoaderFile(t *testing.T, path, content string) { @@ -413,8 +376,9 @@ func writeLoaderFile(t *testing.T, path, content string) { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { t.Fatalf("MkdirAll(%s): %v", filepath.Dir(path), err) } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatalf("WriteFile(%s): %v", path, err) + { + err := os.WriteFile(path, []byte(content), 0o600) + testassert.Require(t, err == nil, "WriteFile(%s): %v", path, err) } } @@ -479,14 +443,11 @@ func TestLoaderAllowRefreshGateExcludesUnconnected(t *testing.T) { loader := NewLoader(home, fsys) loader.AllowRefresh = func(_ string, _ *Provider) bool { return false } cat, _, err := loader.Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if calls != 0 { - t.Fatalf("discovery calls = %d, want 0 (gate rejected unconnected)", calls) - } - if _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}); err == nil { - t.Fatal("discovered model appeared despite AllowRefresh=false") + testassert.NoError(t, err) + testassert.Equal(t, calls, 0) + { + _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}) + testassert.Require(t, err != nil, "discovered model appeared despite AllowRefresh=false") } } @@ -517,14 +478,11 @@ func TestLoaderAllowRefreshGateIncludesConnected(t *testing.T) { loader := NewLoader(home, fsys) loader.AllowRefresh = func(_ string, _ *Provider) bool { return true } cat, _, err := loader.Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if calls != 1 { - t.Fatalf("discovery calls = %d, want 1 (gate allowed connected)", calls) - } - if _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}); err != nil { - t.Fatalf("discovered model missing after allowed refresh: %v", err) + testassert.NoError(t, err) + testassert.Equal(t, calls, 1) + { + _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}) + testassert.NoError(t, err) } } @@ -554,14 +512,11 @@ func TestLoaderNoAllowRefreshRefreshesAllCandidates(t *testing.T) { // No AllowRefresh set — should behave as before and refresh all candidates. cat, _, err := NewLoader(home, fsys).Load() - if err != nil { - t.Fatalf("Load returned error: %v", err) - } - if calls != 1 { - t.Fatalf("discovery calls = %d, want 1 (no gate = refresh all)", calls) - } - if _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}); err != nil { - t.Fatalf("discovered model missing: %v", err) + testassert.NoError(t, err) + testassert.Equal(t, calls, 1) + { + _, _, err := cat.Lookup(ModelRef{Provider: "remote", Model: "remote-model"}) + testassert.NoError(t, err) } } @@ -596,13 +551,9 @@ func TestDiscoveryRefreshCandidatesIsMechanical(t *testing.T) { got[id] = true } for id := range want { - if !got[id] { - t.Fatalf("candidate %q missing; got %v", id, candidates) - } - } - if got["no-discovery"] { - t.Fatalf("no-discovery provider should not be a candidate; got %v", candidates) + testassert.Require(t, got[id], "candidate %q missing; got %v", id, candidates) } + testassert.Require(t, !got["no-discovery"], "no-discovery provider should not be a candidate; got %v", candidates) } // TestLoaderLoadTryContentionWarnsAndDoesNotHang proves the Try Loader routes @@ -622,9 +573,7 @@ func TestLoaderLoadTryContentionWarnsAndDoesNotHang(t *testing.T) { } }`) holder, ok, err := atomicfs.TryAcquire(discoveryLockPath(home, "local")) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() done := make(chan struct { @@ -642,15 +591,9 @@ func TestLoaderLoadTryContentionWarnsAndDoesNotHang(t *testing.T) { }() select { case res := <-done: - if res.err != nil { - t.Fatalf("LoadTry under discovery contention = %v, want a catalog plus warnings", res.err) - } - if res.cat == nil { - t.Fatal("LoadTry returned a nil catalog under discovery contention") - } - if !hasWarning(res.warnings, "discovery_failure") { - t.Fatalf("LoadTry warnings = %#v, want a discovery_failure warning", res.warnings) - } + testassert.Require(t, res.err == nil, "LoadTry under discovery contention = %v, want a catalog plus warnings", res.err) + testassert.Require(t, res.cat != nil, "LoadTry returned a nil catalog under discovery contention") + testassert.Require(t, hasWarning(res.warnings, "discovery_failure"), "LoadTry warnings = %#v, want a discovery_failure warning", res.warnings) case <-time.After(5 * time.Second): t.Fatal("LoadTry hung on the foreign discovery lock; Try publication must not block shutdown") } @@ -676,16 +619,14 @@ func TestLoaderLoadTrySucceedsLikeLoad(t *testing.T) { }`)}, } cat, warnings, err := NewLoader(home, fsys).LoadTry() - if err != nil { - t.Fatalf("LoadTry: %v", err) - } - if hasWarning(warnings, "discovery_failure") { - t.Fatalf("LoadTry warnings = %#v, want no discovery_failure", warnings) - } - if _, ok := cat.Providers["remote"].Models["remote-model"]; !ok { - t.Fatalf("LoadTry did not merge the fetched model: %#v", cat.Providers["remote"].Models) - } - if _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); statErr != nil { - t.Fatalf("LoadTry did not write the discovery cache: %v", statErr) + testassert.NoError(t, err) + testassert.Require(t, !hasWarning(warnings, "discovery_failure"), "LoadTry warnings = %#v, want no discovery_failure", warnings) + { + _, ok := cat.Providers["remote"].Models["remote-model"] + testassert.Require(t, ok, "LoadTry did not merge the fetched model: %#v", cat.Providers["remote"].Models) + } + { + _, statErr := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.Require(t, statErr == nil, "LoadTry did not write the discovery cache: %v", statErr) } } diff --git a/internal/catalog/merge_fuzz_test.go b/internal/catalog/merge_fuzz_test.go index 43c1f21c..fbcf1dca 100644 --- a/internal/catalog/merge_fuzz_test.go +++ b/internal/catalog/merge_fuzz_test.go @@ -3,6 +3,8 @@ package catalog import ( "encoding/json" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func FuzzDeepMergeCatalog(f *testing.F) { @@ -57,13 +59,9 @@ func FuzzDeepMergeCatalog(f *testing.F) { } for _, pair := range seedPairs { bundled, err := json.Marshal(pair[0]) - if err != nil { - f.Fatalf("marshal bundled seed: %v", err) - } + testassert.Require(f, err == nil, "marshal bundled seed: %v", err) user, err := json.Marshal(pair[1]) - if err != nil { - f.Fatalf("marshal user seed: %v", err) - } + testassert.Require(f, err == nil, "marshal user seed: %v", err) f.Add(bundled, user) } for _, invalid := range []struct { @@ -88,11 +86,10 @@ func FuzzDeepMergeCatalog(f *testing.F) { } merged := DeepMergeCatalog(bundled, user) - if merged == nil { - t.Fatal("DeepMergeCatalog returned nil") - } - if _, err := json.Marshal(merged); err != nil { - t.Fatalf("merged catalog is not valid JSON: %v", err) + testassert.Require(t, merged != nil, "DeepMergeCatalog returned nil") + { + _, err := json.Marshal(merged) + testassert.NoError(t, err) } }) } diff --git a/internal/catalog/merge_test.go b/internal/catalog/merge_test.go index a8d1143e..50258562 100644 --- a/internal/catalog/merge_test.go +++ b/internal/catalog/merge_test.go @@ -1,6 +1,9 @@ package catalog -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) func TestDeepMergeCatalogMergesObjectsAndReplacesArrays(t *testing.T) { bundled := map[string]any{ @@ -42,26 +45,18 @@ func TestDeepMergeCatalogMergesObjectsAndReplacesArrays(t *testing.T) { merged := DeepMergeCatalog(bundled, user) headers := merged["transport"].(map[string]any)["headers"].(map[string]any) - if headers["OpenAI-Beta"] != "assistants=v2" { - t.Fatalf("bundled header missing after merge: %#v", headers) - } - if headers["HTTP-Referer"] != "https://lightcode.local" { - t.Fatalf("user header missing after merge: %#v", headers) - } + testassert.Require(t, headers["OpenAI-Beta"] == "assistants=v2" && headers["HTTP-Referer"] == "https://lightcode.local", "bundled header missing after merge: %#v; user header missing after merge: %#v", headers, headers) model := merged["models"].(map[string]any)["gpt-5.4-mini"].(map[string]any) extra := model["extra_body"].(map[string]any) - if extra["reasoning_effort"] != "high" { - t.Fatalf("reasoning_effort = %v, want high", extra["reasoning_effort"]) - } - if _, ok := extra["thinking"].(map[string]any); !ok { - t.Fatalf("thinking sidecar missing after merge: %#v", extra) + testassert.Equal(t, extra["reasoning_effort"], "high") + { + _, ok := extra["thinking"].(map[string]any) + testassert.Require(t, ok, "thinking sidecar missing after merge: %#v", extra) } modalities := model["input_modalities"].([]any) - if len(modalities) != 1 || modalities[0] != "text" { - t.Fatalf("input_modalities = %#v, want replacement with [text]", modalities) - } + testassert.Require(t, len(modalities) == 1 && modalities[0] == "text", "input_modalities = %#v, want replacement with [text]", modalities) } func TestDeepMergeCatalogDoesNotMutateInputs(t *testing.T) { @@ -71,10 +66,7 @@ func TestDeepMergeCatalogDoesNotMutateInputs(t *testing.T) { merged := DeepMergeCatalog(bundled, user) merged["transport"].(map[string]any)["headers"].(map[string]any)["A"] = "changed" - got := bundled["transport"].(map[string]any)["headers"].(map[string]any)["A"] - if got != "b" { - t.Fatalf("bundled input mutated: %v", got) - } + testassert.Equal(t, bundled["transport"].(map[string]any)["headers"].(map[string]any)["A"], "b") } func TestDeepMergeCatalogPreservesNullValues(t *testing.T) { @@ -93,15 +85,9 @@ func TestDeepMergeCatalogPreservesNullValues(t *testing.T) { merged := DeepMergeCatalog(bundled, user) extra := merged["extra_body"].(map[string]any) value, ok := extra["nullable"] - if !ok { - t.Fatalf("nullable key missing after merge: %#v", extra) - } - if value != nil { - t.Fatalf("nullable = %#v, want nil passthrough", value) - } - if extra["keep"] != "bundled" { - t.Fatalf("lower-layer sibling key missing after null passthrough: %#v", extra) - } + testassert.Require(t, ok, "nullable key missing after merge: %#v", extra) + testassert.Require(t, value == nil, "nullable = %#v, want nil passthrough", value) + testassert.Require(t, extra["keep"] == "bundled", "lower-layer sibling key missing after null passthrough: %#v", extra) } func TestShallowMergeBodyMergesTopLevelOnly(t *testing.T) { @@ -121,16 +107,13 @@ func TestShallowMergeBodyMergesTopLevelOnly(t *testing.T) { runtime := map[string]any{"reasoning_effort": "low"} merged := ShallowMergeBody(provider, model, runtime) - if merged["reasoning_effort"] != "low" { - t.Fatalf("reasoning_effort = %v, want runtime override", merged["reasoning_effort"]) - } - if _, ok := merged["transforms"]; !ok { - t.Fatalf("provider key missing: %#v", merged) + testassert.Equal(t, merged["reasoning_effort"], "low") + { + _, ok := merged["transforms"] + testassert.Require(t, ok, "provider key missing: %#v", merged) } thinking := merged["thinking"].(map[string]any) - if len(thinking) != 1 || thinking["budget_tokens"] != float64(4000) { - t.Fatalf("thinking = %#v, want shallow replacement", thinking) - } + testassert.Require(t, len(thinking) == 1 && thinking["budget_tokens"] == float64(4000), "thinking = %#v, want shallow replacement", thinking) } func TestShallowMergeBodyPreservesNullValues(t *testing.T) { @@ -144,13 +127,7 @@ func TestShallowMergeBodyPreservesNullValues(t *testing.T) { merged := ShallowMergeBody(provider, model) value, ok := merged["nullable"] - if !ok { - t.Fatalf("nullable key missing after merge: %#v", merged) - } - if value != nil { - t.Fatalf("nullable = %#v, want nil passthrough", value) - } - if merged["keep"] != "provider" { - t.Fatalf("lower-layer sibling key missing after null passthrough: %#v", merged) - } + testassert.Require(t, ok, "nullable key missing after merge: %#v", merged) + testassert.Require(t, value == nil, "nullable = %#v, want nil passthrough", value) + testassert.Require(t, merged["keep"] == "provider", "lower-layer sibling key missing after null passthrough: %#v", merged) } diff --git a/internal/catalog/model_bridge_test.go b/internal/catalog/model_bridge_test.go index 3bace51e..49533572 100644 --- a/internal/catalog/model_bridge_test.go +++ b/internal/catalog/model_bridge_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -13,23 +14,16 @@ import ( // alias is the public type itself (its String/JSON contracts are owned by // model/model_ref_test.go). func TestModelRefBridgeForwardsPublicIdentity(t *testing.T) { - if ErrInvalidModelRef != model.ErrInvalidModelRef { - t.Fatalf("ErrInvalidModelRef = %v, want the model sentinel %v", ErrInvalidModelRef, model.ErrInvalidModelRef) - } + testassert.Equal(t, ErrInvalidModelRef, model.ErrInvalidModelRef) _, err := ParseModelRef("no-slash") - if err == nil { - t.Fatal("ParseModelRef(no-slash) = nil, want error") - } - if !errors.Is(err, model.ErrInvalidModelRef) { - t.Fatalf("ParseModelRef error = %v, want errors.Is against the model sentinel", err) - } + testassert.Require(t, err != nil, "ParseModelRef(no-slash) = nil, want error") + testassert.Require(t, errors.Is(err, model.ErrInvalidModelRef), "ParseModelRef error = %v, want errors.Is against the model sentinel", err) ref, err := ParseModelRef("prov/name/extra") - if err != nil { - t.Fatalf("ParseModelRef: %v", err) - } - if want := (model.ModelRef{Provider: "prov", Model: "name/extra"}); ref != want { - t.Fatalf("ParseModelRef = %+v, want %+v", ref, want) + testassert.NoError(t, err) + { + want := (model.ModelRef{Provider: "prov", Model: "name/extra"}) + testassert.Equal(t, ref, want) } } diff --git a/internal/catalog/offline_test.go b/internal/catalog/offline_test.go index ae0d6f09..4d16b783 100644 --- a/internal/catalog/offline_test.go +++ b/internal/catalog/offline_test.go @@ -7,6 +7,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // snapshotTree records every entry under root with its size and mtime, so a @@ -26,9 +28,7 @@ func snapshotTree(t *testing.T, root string) map[string]string { snap[path] = fmt.Sprintf("%d:%d:%v", info.Size(), info.ModTime().UnixNano(), info.IsDir()) return nil }) - if err != nil && !os.IsNotExist(err) { - t.Fatalf("snapshot %s: %v", root, err) - } + testassert.Require(t, err == nil || os.IsNotExist(err), "snapshot %s: %v", root, err) return snap } @@ -36,27 +36,23 @@ func TestBuildOfflineZeroWrites(t *testing.T) { t.Run("absent data dir", func(t *testing.T) { home := t.TempDir() before := snapshotTree(t, home) - if _, err := BuildOffline(home, nil); err != nil { - t.Fatalf("BuildOffline: %v", err) + { + _, err := BuildOffline(home, nil) + testassert.NoError(t, err) } after := snapshotTree(t, home) - if !reflect.DeepEqual(before, after) { - t.Fatalf("filesystem changed:\nbefore: %v\nafter: %v", before, after) - } - if _, statErr := os.Stat(filepath.Join(home, ".lightcode")); !os.IsNotExist(statErr) { - t.Fatalf(".lightcode was created: stat err = %v", statErr) + testassert.Require(t, reflect.DeepEqual(before, after), "filesystem changed:\nbefore: %v\nafter: %v", before, after) + { + _, statErr := os.Stat(filepath.Join(home, ".lightcode")) + testassert.Require(t, os.IsNotExist(statErr), ".lightcode was created: stat err = %v", statErr) } }) t.Run("populated cache dir", func(t *testing.T) { home := t.TempDir() dir := filepath.Join(home, ".lightcode", "cache", "discovery") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "corrupt.json"), []byte("{not json"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "corrupt.json"), []byte("{not json"), 0o600)) before := snapshotTree(t, home) userRaw := map[string]any{ "local": map[string]any{ @@ -65,42 +61,31 @@ func TestBuildOfflineZeroWrites(t *testing.T) { "models": map[string]any{"m1": map[string]any{"context_window": float64(8192)}}, }, } - if _, err := BuildOffline(home, userRaw); err != nil { - t.Fatalf("BuildOffline: %v", err) + { + _, err := BuildOffline(home, userRaw) + testassert.NoError(t, err) } after := snapshotTree(t, home) - if !reflect.DeepEqual(before, after) { - t.Fatalf("filesystem changed:\nbefore: %v\nafter: %v", before, after) - } + testassert.Require(t, reflect.DeepEqual(before, after), "filesystem changed:\nbefore: %v\nafter: %v", before, after) }) } func TestBuildOfflineSurfacesCorruptCacheWarning(t *testing.T) { home := t.TempDir() dir := filepath.Join(home, ".lightcode", "cache", "discovery") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "someprovider.json"), []byte("{not json"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "someprovider.json"), []byte("{not json"), 0o600)) result, err := BuildOffline(home, nil) - if err != nil { - t.Fatalf("BuildOffline: %v", err) - } + testassert.NoError(t, err) found := false for _, w := range result.Warnings { if w.Kind == "discovery_failure" && w.Provider == "someprovider" { found = true } } - if !found { - t.Fatalf("no discovery_failure warning for the corrupt cache file; warnings = %+v", result.Warnings) - } - if result.Catalog == nil || len(result.Catalog.Providers) == 0 { - t.Fatal("bundled providers missing from the offline catalog") - } + testassert.Require(t, found, "no discovery_failure warning for the corrupt cache file; warnings = %+v", result.Warnings) + testassert.Require(t, result.Catalog != nil && len(result.Catalog.Providers) != 0, "bundled providers missing from the offline catalog") } func TestProviderConnected(t *testing.T) { @@ -150,9 +135,7 @@ func TestProviderConnected(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - if got := ProviderConnected(tc.prov, tc.isSet); got != tc.want { - t.Fatalf("ProviderConnected = %v, want %v", got, tc.want) - } + testassert.Equal(t, ProviderConnected(tc.prov, tc.isSet), tc.want) }) } } @@ -176,9 +159,7 @@ func TestDiscoveryTransportReadyDoesNotRequireModels(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - if got := DiscoveryTransportReady(tc.prov, tc.set); got != tc.want { - t.Fatalf("DiscoveryTransportReady = %v, want %v", got, tc.want) - } + testassert.Equal(t, DiscoveryTransportReady(tc.prov, tc.set), tc.want) }) } } @@ -199,13 +180,10 @@ func TestProviderConnectedDoctorContext(t *testing.T) { Transport: Transport{APIKeyEnv: "DOTENV_ONLY_KEY", BaseURL: "https://x"}, Models: map[string]*Model{"m": {ID: "m", ContextWindow: 8192}}, } - if _, set := os.LookupEnv("DOTENV_ONLY_KEY"); set { - t.Fatal("test precondition: DOTENV_ONLY_KEY must not be in the process env") - } - if !ProviderConnected(prov, isSet) { - t.Fatal("a .env-only key must resolve through the dotenv key set") - } - if ProviderConnected(prov, func(name string) bool { _, ok := os.LookupEnv(name); return ok }) { - t.Fatal("without the dotenv set the key must not resolve (the false-disconnected regression)") + { + _, set := os.LookupEnv("DOTENV_ONLY_KEY") + testassert.Require(t, !set, "test precondition: DOTENV_ONLY_KEY must not be in the process env") } + testassert.Require(t, ProviderConnected(prov, isSet), "a .env-only key must resolve through the dotenv key set") + testassert.Require(t, !ProviderConnected(prov, func(name string) bool { _, ok := os.LookupEnv(name); return ok }), "without the dotenv set the key must not resolve (the false-disconnected regression)") } diff --git a/internal/catalog/refresh_test.go b/internal/catalog/refresh_test.go index b8f8e986..3db2131c 100644 --- a/internal/catalog/refresh_test.go +++ b/internal/catalog/refresh_test.go @@ -4,38 +4,29 @@ import ( "os" "path/filepath" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestUserCostProtectionUsesConfiguredConfigPath(t *testing.T) { home := t.TempDir() defaultConfig := filepath.Join(home, ".lightcode", "config.json") - if err := os.MkdirAll(filepath.Dir(defaultConfig), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(defaultConfig, []byte(`{ + testassert.NoError(t, os.MkdirAll(filepath.Dir(defaultConfig), 0o700)) + testassert.NoError(t, os.WriteFile(defaultConfig, []byte(`{ "providers": { "test": { "models": { "model": { "cost": { "input": 1 } } } } }, "default_model": "" -}`), 0o600); err != nil { - t.Fatal(err) - } +}`), 0o600)) customConfig := filepath.Join(t.TempDir(), "custom-config.json") - if err := os.WriteFile(customConfig, []byte(`{ + testassert.NoError(t, os.WriteFile(customConfig, []byte(`{ "providers": { "test": { "models": { "model": { "cost": { "output": 2, "cache_read": 3 } } } } }, "default_model": "" -}`), 0o600); err != nil { - t.Fatal(err) - } +}`), 0o600)) protected := userCostProtectionForProviderAt(home, customConfig, "test") - if protected["model"]["input"] { - t.Fatalf("protected input from default config; want custom config only: %#v", protected) - } - if !protected["model"]["output"] || !protected["model"]["cache_read"] { - t.Fatalf("protected = %#v, want custom config cost fields", protected) - } + testassert.Require(t, !protected["model"]["input"] && protected["model"]["output"] && protected["model"]["cache_read"], "protected input from default config; want custom config only: %#v; protected = %#v, want custom config cost fields", protected, protected) } diff --git a/internal/catalog/reserved_test.go b/internal/catalog/reserved_test.go index d2990ea9..422570ba 100644 --- a/internal/catalog/reserved_test.go +++ b/internal/catalog/reserved_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -24,14 +25,10 @@ func TestReservedKeyValidationCoversEveryOwnerKey(t *testing.T) { "n", } for _, name := range want { - if !protocolFieldReserved(name) { - t.Errorf("protocolFieldReserved(%q) = false, want the shared reserved key rejected", name) - } + testassert.Check(t, protocolFieldReserved(name), "protocolFieldReserved(%q) = false, want the shared reserved key rejected", name) } for _, name := range []string{"temperature", "reasoning_effort"} { - if protocolFieldReserved(name) { - t.Errorf("protocolFieldReserved(%q) = true, want a clean unknown field accepted", name) - } + testassert.Check(t, !protocolFieldReserved(name), "protocolFieldReserved(%q) = true, want a clean unknown field accepted", name) } } @@ -40,9 +37,7 @@ func TestCheckReservedKeysReturnsEmptyForCleanBody(t *testing.T) { "temperature": 0.2, "reasoning_effort": "high", }) - if len(got) != 0 { - t.Fatalf("CheckReservedKeys returned %#v, want empty", got) - } + testassert.Require(t, len(got) == 0, "CheckReservedKeys returned %#v, want empty", got) } func TestCheckReservedKeysReturnsReservedKeysInStableOrder(t *testing.T) { @@ -53,13 +48,9 @@ func TestCheckReservedKeysReturnsReservedKeysInStableOrder(t *testing.T) { "temperature": 0.2, }) want := []string{"model", "stream_options", "n"} - if len(got) != len(want) { - t.Fatalf("reserved keys = %#v, want %#v", got, want) - } + testassert.Require(t, len(got) == len(want), "reserved keys = %#v, want %#v", got, want) for i := range want { - if got[i] != want[i] { - t.Fatalf("reserved keys = %#v, want %#v", got, want) - } + testassert.Require(t, got[i] == want[i], "reserved keys = %#v, want %#v", got, want) } } @@ -75,15 +66,7 @@ func TestReservedKeyPresenceIsDiagnosticAndRejection(t *testing.T) { delete(raw["transport"].(map[string]any), "base_url") errs := ValidateRaw("openai", raw, true) - if !hasValidationError(errs, "extra_body.model") { - t.Fatalf("ValidateRaw errors = %#v, want the reserved extra_body.model diagnostic for any value shape", errs) - } - if hasValidationError(errs, "extra_body.temperature") { - t.Fatalf("ValidateRaw errors = %#v, want the clean sibling value to stay unrestricted", errs) - } - if !hasValidationError(errs, "transport.base_url") { - t.Fatalf("ValidateRaw errors = %#v, want the unrelated violation beside the reserved-key diagnostic", errs) - } + testassert.Require(t, hasValidationError(errs, "extra_body.model") && !hasValidationError(errs, "extra_body.temperature") && hasValidationError(errs, "transport.base_url"), "ValidateRaw errors = %#v, want the reserved extra_body.model diagnostic for any value shape; ValidateRaw errors = %#v, want the clean sibling value to stay unrestricted; ValidateRaw errors = %#v, want the unrelated violation beside the reserved-key diagnostic", errs, errs, errs) rt := model.ResolvedTransport{ Model: model.ModelRef{Provider: "openai", Model: "gpt-test"}, @@ -91,12 +74,8 @@ func TestReservedKeyPresenceIsDiagnosticAndRejection(t *testing.T) { } body, _, err := model.Encode(rt, model.Request{}, nil) var reserved *model.ReservedKeyError - if !errors.As(err, &reserved) || !errors.Is(err, model.ErrReservedKeys) { - t.Fatalf("Encode error = %v, want the typed reserved-key rejection", err) - } - if len(reserved.Keys) != 1 || reserved.Keys[0] != "model" { - t.Fatalf("rejected keys = %#v, want [model]", reserved.Keys) - } + testassert.Require(t, errors.As(err, &reserved) && errors.Is(err, model.ErrReservedKeys), "Encode error = %v, want the typed reserved-key rejection", err) + testassert.Require(t, len(reserved.Keys) == 1 && reserved.Keys[0] == "model", "rejected keys = %#v, want [model]", reserved.Keys) if len(body) != 0 { t.Fatalf("rejection produced a partial body: %s", string(body)) } diff --git a/internal/catalog/step7_structure_test.go b/internal/catalog/step7_structure_test.go index 1943bfa4..0724032c 100644 --- a/internal/catalog/step7_structure_test.go +++ b/internal/catalog/step7_structure_test.go @@ -5,49 +5,35 @@ import ( "path/filepath" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestStep7DiscoveryMigrationStructure(t *testing.T) { files, err := filepath.Glob("*.go") - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) for _, path := range files { if path == "step7_structure_test.go" { continue } data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) source := string(data) - if strings.Contains(source, "map[string]time.Time") { - t.Fatalf("%s retains a parallel provider-ID attempt map", path) - } - if strings.Contains(source, "map[string]DiscoveredProvider") { - t.Fatalf("%s retains a parallel provider-ID cache map", path) - } + testassert.Require(t, !strings.Contains(source, "map[string]time.Time"), "%s retains a parallel provider-ID attempt map", path) + testassert.Require(t, !strings.Contains(source, "map[string]DiscoveredProvider"), "%s retains a parallel provider-ID cache map", path) } doctorSource := readStep7Source(t, filepath.Join("..", "doctor", "doctor.go")) - if !strings.Contains(doctorSource, "catalog.ProviderConnected(") { - t.Fatal("Doctor no longer retains ProviderConnected") - } + testassert.Require(t, strings.Contains(doctorSource, "catalog.ProviderConnected("), "Doctor no longer retains ProviderConnected") refreshSource := readStep7Source(t, "refresh.go") - if !strings.Contains(refreshSource, "record.BoundTo(provider.Transport)") { - t.Fatal("refresh candidate selection is not transport-bound") - } - if strings.Contains(refreshSource, "attempts[") { - t.Fatal("refresh candidate selection retains provider-ID-only TTL") - } + testassert.Require(t, strings.Contains(refreshSource, "record.BoundTo(provider.Transport)"), "refresh candidate selection is not transport-bound") + testassert.Require(t, !strings.Contains(refreshSource, "attempts["), "refresh candidate selection retains provider-ID-only TTL") } func readStep7Source(t *testing.T, path string) string { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return string(data) } diff --git a/internal/catalog/types_test.go b/internal/catalog/types_test.go index cdfdc736..0b86a75c 100644 --- a/internal/catalog/types_test.go +++ b/internal/catalog/types_test.go @@ -3,19 +3,15 @@ package catalog import ( "encoding/json" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestParseModelRefSplitsOnFirstSlash(t *testing.T) { ref, err := ParseModelRef("openrouter/openai/gpt-5.4-mini") - if err != nil { - t.Fatalf("ParseModelRef returned error: %v", err) - } - if ref.Provider != "openrouter" || ref.Model != "openai/gpt-5.4-mini" { - t.Fatalf("unexpected ref: %#v", ref) - } - if got := ref.String(); got != "openrouter/openai/gpt-5.4-mini" { - t.Fatalf("String() = %q", got) - } + testassert.NoError(t, err) + testassert.Require(t, ref.Provider == "openrouter" && ref.Model == "openai/gpt-5.4-mini", "unexpected ref: %#v", ref) + testassert.Equal(t, ref.String(), "openrouter/openai/gpt-5.4-mini") } func TestModelRefStringIsParseableWhenNonEmpty(t *testing.T) { @@ -32,19 +28,13 @@ func TestModelRefStringIsParseableWhenNonEmpty(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := tc.ref.String() - if got != tc.want { - t.Fatalf("String() = %q, want %q", got, tc.want) - } + testassert.Equal(t, got, tc.want) if got == "" { return } parsed, err := ParseModelRef(got) - if err != nil { - t.Fatalf("ParseModelRef(%q) returned error: %v", got, err) - } - if parsed != tc.ref { - t.Fatalf("parsed = %#v, want %#v", parsed, tc.ref) - } + testassert.Require(t, err == nil, "ParseModelRef(%q) returned error: %v", got, err) + testassert.Equal(t, parsed, tc.ref) }) } } @@ -52,26 +42,19 @@ func TestModelRefStringIsParseableWhenNonEmpty(t *testing.T) { func TestModelRefJSONUsesPrefixString(t *testing.T) { ref := ModelRef{Provider: "openai", Model: "gpt-5.4-mini"} b, err := json.Marshal(ref) - if err != nil { - t.Fatalf("Marshal returned error: %v", err) - } - if string(b) != `"openai/gpt-5.4-mini"` { - t.Fatalf("Marshal = %s", b) - } + testassert.NoError(t, err) + testassert.Require(t, string(b) == `"openai/gpt-5.4-mini"`, "Marshal = %s", b) var parsed ModelRef - if err := json.Unmarshal(b, &parsed); err != nil { - t.Fatalf("Unmarshal returned error: %v", err) - } - if parsed != ref { - t.Fatalf("parsed = %#v, want %#v", parsed, ref) - } + testassert.NoError(t, json.Unmarshal(b, &parsed)) + testassert.Equal(t, parsed, ref) } func TestParseModelRefRejectsMissingParts(t *testing.T) { for _, input := range []string{"", "openai", "/gpt-5.4-mini", "openai/"} { - if _, err := ParseModelRef(input); err == nil { - t.Fatalf("ParseModelRef(%q) succeeded, want error", input) + { + _, err := ParseModelRef(input) + testassert.Require(t, err != nil, "ParseModelRef(%q) succeeded, want error", input) } } } diff --git a/internal/catalog/validate_test.go b/internal/catalog/validate_test.go index 7e322450..d0dfe53e 100644 --- a/internal/catalog/validate_test.go +++ b/internal/catalog/validate_test.go @@ -1,11 +1,15 @@ package catalog -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) func TestValidateRawAcceptsValidProvider(t *testing.T) { raw := validRawProvider() - if errs := ValidateRaw("openai", raw, true); len(errs) != 0 { - t.Fatalf("ValidateRaw returned errors: %#v", errs) + { + errs := ValidateRaw("openai", raw, true) + testassert.Require(t, len(errs) == 0, "ValidateRaw returned errors: %#v", errs) } } @@ -13,8 +17,9 @@ func TestValidateRawAcceptsDiscoveryOnlyProviderWithoutModels(t *testing.T) { raw := validRawProvider() delete(raw, "models") - if errs := ValidateRaw("openai", raw, true); len(errs) != 0 { - t.Fatalf("ValidateRaw returned errors: %#v, want discovery-only provider without models to validate", errs) + { + errs := ValidateRaw("openai", raw, true) + testassert.Require(t, len(errs) == 0, "ValidateRaw returned errors: %#v, want discovery-only provider without models to validate", errs) } } @@ -22,8 +27,9 @@ func TestValidateRawAcceptsDiscoveryOnlyProviderWithEmptyModels(t *testing.T) { raw := validRawProvider() raw["models"] = map[string]any{} - if errs := ValidateRaw("openai", raw, true); len(errs) != 0 { - t.Fatalf("ValidateRaw returned errors: %#v, want discovery-only provider with empty models to validate", errs) + { + errs := ValidateRaw("openai", raw, true) + testassert.Require(t, len(errs) == 0, "ValidateRaw returned errors: %#v, want discovery-only provider with empty models to validate", errs) } } @@ -32,9 +38,7 @@ func TestValidateRawStrictRejectsUnknownProviderKey(t *testing.T) { raw["base_uri"] = "https://api.openai.com/v1" errs := ValidateRaw("openai", raw, true) - if !hasValidationError(errs, "base_uri") { - t.Fatalf("ValidateRaw errors = %#v, want base_uri error", errs) - } + testassert.Require(t, hasValidationError(errs, "base_uri"), "ValidateRaw errors = %#v, want base_uri error", errs) } // TestValidateRawStrictRejectsRetiredProviderControls pins the retired @@ -44,14 +48,16 @@ func TestValidateRawStrictRejectsUnknownProviderKey(t *testing.T) { func TestValidateRawStrictRejectsRetiredProviderControls(t *testing.T) { raw := validRawProvider() raw["max_tokens_field"] = "max_completion_tokens" - if errs := ValidateRaw("openai", raw, true); !hasValidationError(errs, "max_tokens_field") { - t.Fatalf("ValidateRaw errors = %#v, want the retired max_tokens_field refused as unknown", errs) + { + errs := ValidateRaw("openai", raw, true) + testassert.Require(t, hasValidationError(errs, "max_tokens_field"), "ValidateRaw errors = %#v, want the retired max_tokens_field refused as unknown", errs) } raw = validRawProvider() raw["transport"].(map[string]any)["options"] = map[string]any{"retries": float64(3)} - if errs := ValidateRaw("openai", raw, true); !hasValidationError(errs, "transport.options") { - t.Fatalf("ValidateRaw errors = %#v, want the retired transport.options refused as unknown", errs) + { + errs := ValidateRaw("openai", raw, true) + testassert.Require(t, hasValidationError(errs, "transport.options"), "ValidateRaw errors = %#v, want the retired transport.options refused as unknown", errs) } } @@ -60,9 +66,7 @@ func TestValidateRawReportsMissingRequiredTransportBaseURL(t *testing.T) { delete(raw["transport"].(map[string]any), "base_url") errs := ValidateRaw("openai", raw, true) - if !hasValidationError(errs, "transport.base_url") { - t.Fatalf("ValidateRaw errors = %#v, want transport.base_url error", errs) - } + testassert.Require(t, hasValidationError(errs, "transport.base_url"), "ValidateRaw errors = %#v, want transport.base_url error", errs) } func TestValidateRawRejectsReservedProviderExtraBodyKey(t *testing.T) { @@ -70,9 +74,7 @@ func TestValidateRawRejectsReservedProviderExtraBodyKey(t *testing.T) { raw["extra_body"] = map[string]any{"model": "bad"} errs := ValidateRaw("openai", raw, true) - if !hasValidationError(errs, "extra_body.model") { - t.Fatalf("ValidateRaw errors = %#v, want reserved extra_body.model error", errs) - } + testassert.Require(t, hasValidationError(errs, "extra_body.model"), "ValidateRaw errors = %#v, want reserved extra_body.model error", errs) } func TestValidateRawRejectsInvalidProtocolMetadata(t *testing.T) { @@ -85,9 +87,7 @@ func TestValidateRawRejectsInvalidProtocolMetadata(t *testing.T) { errs := ValidateRaw("openai", raw, true) for _, field := range []string{"protocol_metadata.family", "protocol_metadata.must_preserve[1]", "protocol_metadata.drop[0]"} { - if !hasValidationError(errs, field) { - t.Fatalf("ValidateRaw errors = %#v, want %s error", errs, field) - } + testassert.Require(t, hasValidationError(errs, field), "ValidateRaw errors = %#v, want %s error", errs, field) } } @@ -97,12 +97,7 @@ func TestValidateRawRejectsIllegalEnums(t *testing.T) { raw["models"].(map[string]any)["gpt-5.4-mini"].(map[string]any)["input_modalities"] = []any{"text", "video"} errs := ValidateRaw("openai", raw, true) - if !hasValidationError(errs, "system_role") { - t.Fatalf("ValidateRaw errors = %#v, want system_role error", errs) - } - if !hasValidationError(errs, "models.gpt-5.4-mini.input_modalities[1]") { - t.Fatalf("ValidateRaw errors = %#v, want input modality error", errs) - } + testassert.Require(t, hasValidationError(errs, "system_role") && hasValidationError(errs, "models.gpt-5.4-mini.input_modalities[1]"), "ValidateRaw errors = %#v, want system_role error; ValidateRaw errors = %#v, want input modality error", errs, errs) } func TestValidateEffectiveRejectsReservedModelExtraBodyKey(t *testing.T) { @@ -128,9 +123,7 @@ func TestValidateEffectiveRejectsReservedModelExtraBodyKey(t *testing.T) { } errs := ValidateEffective(p) - if !hasValidationError(errs, "models.gpt-5.4-mini.extra_body.messages") { - t.Fatalf("ValidateEffective errors = %#v, want reserved model extra_body error", errs) - } + testassert.Require(t, hasValidationError(errs, "models.gpt-5.4-mini.extra_body.messages"), "ValidateEffective errors = %#v, want reserved model extra_body error", errs) } func TestValidateEffectiveAllowsIncompleteCapacity(t *testing.T) { @@ -153,8 +146,9 @@ func TestValidateEffectiveAllowsIncompleteCapacity(t *testing.T) { }, } - if errs := ValidateEffective(p); len(errs) != 0 { - t.Fatalf("ValidateEffective errors = %#v, want incomplete model to remain loadable", errs) + { + errs := ValidateEffective(p) + testassert.Require(t, len(errs) == 0, "ValidateEffective errors = %#v, want incomplete model to remain loadable", errs) } } diff --git a/internal/cliclient/commands_test.go b/internal/cliclient/commands_test.go index d2cc6e7a..fc3af3a5 100644 --- a/internal/cliclient/commands_test.go +++ b/internal/cliclient/commands_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/terminal" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -91,9 +92,7 @@ func setDraft(c *CLI, text string) { c.mu.Lock(); c.draft.Set(text); c.draftRevi func attemptBody(t *testing.T, r request) map[string]json.RawMessage { t.Helper() var body map[string]json.RawMessage - if err := json.Unmarshal(r.body, &body); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal(r.body, &body)) return body } @@ -113,38 +112,26 @@ func TestUnknownAttemptIdentityDraftRefreshRetryAndAbandon(t *testing.T) { beforeRevision := c.draftRevision a, err := c.formAttemptLocked(operation, minted) c.mu.Unlock() - if err != nil { - t.Fatal(err) - } - if a.draftRevision != beforeRevision { - t.Fatal("forming a fork edited the draft") - } + testassert.NoError(t, err) + testassert.Require(t, a.draftRevision == beforeRevision, "forming a fork edited the draft") c.task(func() { c.runAttempt(a.id) }) first := receive(t, transport.requests) body := attemptBody(t, first) var id string _ = json.Unmarshal(body["operation_id"], &id) - if id != a.id || len(id) != 32 || strings.ToLower(id) != id { - t.Fatal("caller ID missing") - } + testassert.Require(t, id == a.id && len(id) == 32 && strings.ToLower(id) == id, "caller ID missing") first.lost() fresh := receive(t, transport.requests) fresh.respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() - if a.outcome != "unknown" || c.draft.String() != "immutable replacement" || c.queued { - t.Fatal("unknown lost attempt/draft or queue mode") - } + testassert.Require(t, a.outcome == "unknown" && c.draft.String() == "immutable replacement" && !c.queued, "unknown lost attempt/draft or queue mode") c.mu.Unlock() - if !strings.Contains(outputs(c), "Outcome unknown") { - t.Fatal("unknown outcome not displayed") - } + testassert.Require(t, strings.Contains(outputs(c), "Outcome unknown"), "unknown outcome not displayed") setDraft(c, "newer edit") c.retryAttempt(a.id) retry := receive(t, transport.requests) - if !reflect.DeepEqual(attemptBody(t, retry), body) { - t.Fatal("retry changed immutable payload") - } + testassert.Require(t, reflect.DeepEqual(attemptBody(t, retry), body), "retry changed immutable payload") if operation == "ForkSession" { retry.respond(protocol.ForkResult{Session: protocol.Session{SessionId: rootB}, Operation: protocol.Operation{OperationId: id}}) } else if operation == "CompactSession" { @@ -160,13 +147,9 @@ func TestUnknownAttemptIdentityDraftRefreshRetryAndAbandon(t *testing.T) { refresh.respond(hydration(selected, "3", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() - if c.draft.String() != "newer edit" || a.outcome != "accepted" { - t.Fatal("accepted old attempt cleared a newer edit") - } + testassert.Require(t, c.draft.String() == "newer edit" && a.outcome == "accepted", "accepted old attempt cleared a newer edit") c.mu.Unlock() - if !strings.Contains(outputs(c), "buffered input can be duplicated") { - t.Fatal("retry warning missing") - } + testassert.Require(t, strings.Contains(outputs(c), "buffered input can be duplicated"), "retry warning missing") // Unknown is an explicit /session item, addressed to this root only. setDraft(c, "abandon draft") press(c, terminal.KeyMsg{Special: terminal.KeyEnter}) @@ -191,9 +174,7 @@ func TestUnknownAttemptIdentityDraftRefreshRetryAndAbandon(t *testing.T) { c.mu.Lock() _, exists := c.attempts[id] c.mu.Unlock() - if exists { - t.Fatal("explicit abandon retained attempt") - } + testassert.Require(t, !exists, "explicit abandon retained attempt") }) } } @@ -213,9 +194,7 @@ func TestUnknownAcceptanceByPendingIDNotAbsenceOrContent(t *testing.T) { receive(t, transport.requests).respond(fresh) settleTasks(t, c) c.mu.Lock() - if c.attempts[id].outcome != "unknown" || c.draft.String() != "draft" { - t.Fatal("content matched or absence treated as rejection") - } + testassert.Require(t, c.attempts[id].outcome == "unknown" && c.draft.String() == "draft", "content matched or absence treated as rejection") c.mu.Unlock() done := c.refreshReader(r) fresh.SessionRevision = revision("3") @@ -224,9 +203,7 @@ func TestUnknownAcceptanceByPendingIDNotAbsenceOrContent(t *testing.T) { barrier(t, done) c.mu.Lock() defer c.mu.Unlock() - if c.attempts[id].outcome != "accepted" || c.draft.String() != "" { - t.Fatal("pending caller ID did not reconcile acceptance") - } + testassert.Require(t, c.attempts[id].outcome == "accepted" && c.draft.String() == "", "pending caller ID did not reconcile acceptance") } func TestForkRestoreOrderedPartialRetainedAcrossRetryAndNavigation(t *testing.T) { @@ -243,14 +220,10 @@ func TestForkRestoreOrderedPartialRetainedAcrossRetryAndNavigation(t *testing.T) a, err := c.formAttemptLocked("ForkSession", minted) initiating := a.stamp c.mu.Unlock() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) c.task(func() { c.runAttempt(a.id) }) restore := receive(t, transport.requests) - if !strings.HasSuffix(restore.req.URL.Path, "/revert-code") || !strings.Contains(string(restore.body), *input.OperationId) { - t.Fatalf("wrong restore address %s %s", restore.req.URL.Path, restore.body) - } + testassert.Require(t, strings.HasSuffix(restore.req.URL.Path, "/revert-code") && strings.Contains(string(restore.body), *input.OperationId), "wrong restore address %s %s", restore.req.URL.Path, restore.body) partial := map[string]any{"restored": []string{"one.go"}, "skipped": []map[string]string{{"path": "two.go", "reason": "changed"}}, "error": protocol.Error{Code: "conflict", Message: "partial"}} switch restoreKind { case "success": @@ -266,31 +239,23 @@ func TestForkRestoreOrderedPartialRetainedAcrossRetryAndNavigation(t *testing.T) receive(t, transport.requests).respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() - if a.outcome != "unknown restore" || a.restoreHandled { - t.Fatal("unknown rewind continued") - } + testassert.Require(t, a.outcome == "unknown restore" && !a.restoreHandled, "unknown rewind continued") c.mu.Unlock() transport.mu.Lock() for _, route := range transport.calls { - if strings.Contains(route, "/fork") { - t.Fatal("Fork preceded unknown restore completion") - } + testassert.Require(t, !strings.Contains(route, "/fork"), "Fork preceded unknown restore completion") } transport.mu.Unlock() return } fork := receive(t, transport.requests) - if !strings.HasSuffix(fork.req.URL.Path, "/fork") { - t.Fatal("known restore did not precede Fork") - } + testassert.Require(t, strings.HasSuffix(fork.req.URL.Path, "/fork"), "known restore did not precede Fork") forkBody := append([]byte(nil), fork.body...) fork.lost() receive(t, transport.requests).respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) out := outputs(c) - if restoreKind == "partial" && (!strings.Contains(out, "one.go") || !strings.Contains(out, "two.go") || !strings.Contains(out, "partial")) { - t.Fatal("partial restore dropped") - } + testassert.Require(t, restoreKind != "partial" || strings.Contains(out, "one.go") && strings.Contains(out, "two.go") && strings.Contains(out, "partial"), "partial restore dropped") c.selectRoot(rootB) receive(t, transport.requests).respond(hydration(rootB, "1", nil, nil)) fresh := receive(t, transport.requests) @@ -298,16 +263,12 @@ func TestForkRestoreOrderedPartialRetainedAcrossRetryAndNavigation(t *testing.T) settleTasks(t, c) c.retryAttempt(a.id) retry := receive(t, transport.requests) - if !strings.HasSuffix(retry.req.URL.Path, "/fork") || string(retry.body) != string(forkBody) { - t.Fatal("retry reran restore or changed Fork") - } + testassert.Require(t, strings.HasSuffix(retry.req.URL.Path, "/fork") && string(retry.body) == string(forkBody), "retry reran restore or changed Fork") retry.respond(protocol.ForkResult{Session: protocol.Session{SessionId: childA}, Operation: protocol.Operation{OperationId: a.id}}) settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if a.stamp != initiating || c.rootID != rootB || a.restoreHandled == false { - t.Fatal("retry replaced initiating stamp/adopted unrelated destination") - } + testassert.Require(t, a.stamp == initiating && c.rootID == rootB && a.restoreHandled != false, "retry replaced initiating stamp/adopted unrelated destination") }) } } @@ -322,43 +283,31 @@ func TestForkAndRevertMenusWaitRealOlderReader(t *testing.T) { c.command(command) settleTasks(t, c) c.mu.Lock() - if len(c.menu.items) != 2 || c.menu.items[0].Label != "input 10" { - t.Fatal("recent input missing") - } + testassert.Require(t, len(c.menu.items) == 2 && c.menu.items[0].Label == "input 10", "recent input missing") c.mu.Unlock() press(c, terminal.KeyMsg{Special: terminal.KeyDown}) press(c, terminal.KeyMsg{Special: terminal.KeyEnter}) page := receive(t, transport.requests) - if page.req.URL.Query().Get("cursor") != "unchanged-oldest" { - t.Fatal("older menu decoded/replaced cursor") - } + testassert.Require(t, page.req.URL.Query().Get("cursor") == "unchanged-oldest", "older menu decoded/replaced cursor") c.mu.Lock() chain := r.chain - if c.menu != nil { - t.Fatal("menu reopened before real completion") - } + testassert.Require(t, c.menu == nil, "menu reopened before real completion") c.mu.Unlock() page.respond(protocol.HistoryPage{SessionRevision: revision("1"), Items: inputs(1, 9)}) barrier(t, chain.done) settleTasks(t, c) c.mu.Lock() - if c.menu == nil || len(c.menu.items) != 10 { - t.Fatal("real reader completion did not reopen full menu") - } + testassert.Require(t, c.menu != nil && len(c.menu.items) == 10, "real reader completion did not reopen full menu") c.mu.Unlock() press(c, terminal.KeyMsg{Special: terminal.KeyEnter}) if command == "/revert" { restore := receive(t, transport.requests) - if !strings.Contains(string(restore.body), "operation-1") { - t.Fatal("display position used as rewind boundary") - } + testassert.Require(t, strings.Contains(string(restore.body), "operation-1"), "display position used as rewind boundary") restore.respond(map[string]any{"restored": []any{}, "skipped": []any{}}) settleTasks(t, c) } else { c.mu.Lock() - if c.draft.String() != "input 1" || c.forkBoundary.ItemId != "item-1" || c.menu.kind != "fork-choice" { - t.Fatal("fork not editable") - } + testassert.Require(t, c.draft.String() == "input 1" && c.forkBoundary.ItemId == "item-1" && c.menu.kind == "fork-choice", "fork not editable") c.mu.Unlock() } }) @@ -375,9 +324,7 @@ func TestLifecycleInvalidatesStaleListArchiveFirstDelete(t *testing.T) { parked := receive(t, transport.requests) c.archiveDelete(s, rootA, true) archive := receive(t, transport.requests) - if !strings.HasSuffix(archive.req.URL.Path, "/archive") { - t.Fatal("DELETE before Archive") - } + testassert.Require(t, strings.HasSuffix(archive.req.URL.Path, "/archive"), "DELETE before Archive") archive.respond(protocol.Session{SessionId: rootA, Lifecycle: "archived"}) var deleteReq, listReq request for i := 0; i < 2; i++ { @@ -389,9 +336,7 @@ func TestLifecycleInvalidatesStaleListArchiveFirstDelete(t *testing.T) { } } c.mu.Lock() - if c.rootID != "" || c.childID != "" { - t.Fatal("known archive did not clear selection immediately") - } + testassert.Require(t, c.rootID == "" && c.childID == "", "known archive did not clear selection immediately") c.mu.Unlock() deleteReq.reject(204, nil) final := receive(t, transport.requests) @@ -401,9 +346,7 @@ func TestLifecycleInvalidatesStaleListArchiveFirstDelete(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.rootID != "" || c.menu != nil || len(c.sessions) != 1 || c.sessions[0].SessionId != rootB { - t.Fatal("stale list resurrected root or unrelated auto-adoption") - } + testassert.Require(t, c.rootID == "" && c.menu == nil && len(c.sessions) == 1 && c.sessions[0].SessionId == rootB, "stale list resurrected root or unrelated auto-adoption") } func TestLifecycleUnknownRejectedArchiveStopsDelete(t *testing.T) { @@ -424,9 +367,7 @@ func TestLifecycleUnknownRejectedArchiveStopsDelete(t *testing.T) { transport.mu.Lock() defer transport.mu.Unlock() for _, call := range transport.calls { - if strings.HasPrefix(call, "DELETE") { - t.Fatal("unknown/rejected archive continued DELETE") - } + testassert.Require(t, !strings.HasPrefix(call, "DELETE"), "unknown/rejected archive continued DELETE") } }) } @@ -453,22 +394,16 @@ func TestChildrenListsAndNavigationTaskMenusSuperseded(t *testing.T) { } settleTasks(t, c) c.mu.Lock() - if c.menu != nil || c.rootID != rootB { - t.Fatal("superseded child-list menu published") - } + testassert.Require(t, c.menu == nil && c.rootID == rootB, "superseded child-list menu published") c.mu.Unlock() - if strings.Contains(outputs(c), "stale child error") { - t.Fatal("stale task error published") - } + testassert.Require(t, !strings.Contains(outputs(c), "stale child error"), "stale task error published") c.command("/children") open := receive(t, transport.requests) open.respond([]protocol.Session{{SessionId: childA, ParentSessionId: pointer(rootA)}, {SessionId: rootA, ParentSessionId: pointer(rootB)}}) receive(t, transport.requests).respond([]protocol.Session{}) settleTasks(t, c) c.mu.Lock() - if len(c.menu.items) != 1 || c.menu.items[0].Label != rootA { - t.Fatal("new root children were not filtered by parent") - } + testassert.Require(t, len(c.menu.items) == 1 && c.menu.items[0].Label == rootA, "new root children were not filtered by parent") c.mu.Unlock() }) } @@ -515,36 +450,24 @@ func TestExplicitNewWorkIsNotRetransmission(t *testing.T) { c.mu.Lock() formed := len(c.attempts) c.mu.Unlock() - if formed != 2 { - t.Fatal("explicit new work was blocked while a response is held") - } + testassert.Require(t, formed == 2, "explicit new work was blocked while a response is held") second := receive(t, transport.requests) firstBody, secondBody := attemptBody(t, first), attemptBody(t, second) var firstID, secondID string _ = json.Unmarshal(firstBody["operation_id"], &firstID) _ = json.Unmarshal(secondBody["operation_id"], &secondID) - if len(firstID) != 32 || len(secondID) != 32 || firstID == secondID { - t.Fatalf("independent work identities = %q %q, want distinct caller IDs", firstID, secondID) - } + testassert.Require(t, len(firstID) == 32 && len(secondID) == 32 && firstID != secondID, "independent work identities = %q %q, want distinct caller IDs", firstID, secondID) secondMode := `"regular"` if test.secondQueued { secondMode = `"queued"` } switch test.operation { case "SubmitSession": - if string(firstBody["mode"]) != `"regular"` || string(firstBody["content"]) != wantContent || - string(secondBody["mode"]) != secondMode || string(secondBody["content"]) != wantContent { - t.Fatalf("per-attempt payloads = %s %s, want the exact immutable captures", first.body, second.body) - } + testassert.Require(t, string(firstBody["mode"]) == `"regular"` && string(firstBody["content"]) == wantContent && string(secondBody["mode"]) == secondMode && string(secondBody["content"]) == wantContent, "per-attempt payloads = %s %s, want the exact immutable captures", first.body, second.body) case "CompactSession": - if len(firstBody) != 1 || len(secondBody) != 1 { - t.Fatalf("compact payloads = %s %s, want the exact caller IDs", first.body, second.body) - } + testassert.Require(t, len(firstBody) == 1 && len(secondBody) == 1, "compact payloads = %s %s, want the exact caller IDs", first.body, second.body) case "ForkSession": - if string(firstBody["content"]) != wantContent || string(secondBody["content"]) != wantContent || - string(firstBody["boundary_item_id"]) != `"item-1"` || string(secondBody["boundary_item_id"]) != `"item-1"` { - t.Fatalf("fork payloads = %s %s, want the exact immutable captures", first.body, second.body) - } + testassert.Require(t, string(firstBody["content"]) == wantContent && string(secondBody["content"]) == wantContent && string(firstBody["boundary_item_id"]) == `"item-1"` && string(secondBody["boundary_item_id"]) == `"item-1"`, "fork payloads = %s %s, want the exact immutable captures", first.body, second.body) } first.lost() second.lost() @@ -571,9 +494,7 @@ func TestExplicitNewWorkIsNotRetransmission(t *testing.T) { } } transport.mu.Unlock() - if sends != 1 { - t.Fatalf("the lost response alone sent %d work requests, want exactly the one attempt", sends) - } + testassert.Equal(t, sends, 1) oldConn := c.conn c.mu.Lock() c.opts.Startup = live.opts @@ -586,9 +507,7 @@ func TestExplicitNewWorkIsNotRetransmission(t *testing.T) { live.mu.Lock() defer live.mu.Unlock() for _, route := range live.calls { - if strings.Contains(route, "/submit") || strings.Contains(route, "/fork") || strings.Contains(route, "/compact") { - t.Fatalf("connection repair resent work: %s", route) - } + testassert.Require(t, !strings.Contains(route, "/submit") && !strings.Contains(route, "/fork") && !strings.Contains(route, "/compact"), "connection repair resent work: %s", route) } }) } @@ -621,15 +540,11 @@ func TestRootlessRepairNeverComposes(t *testing.T) { c.mu.Lock() selected := c.rootID c.mu.Unlock() - if selected != "" { - t.Fatalf("rootless repair adopted %q", selected) - } + testassert.Equal(t, selected, "") live.mu.Lock() defer live.mu.Unlock() for _, route := range live.calls { - if strings.Contains(route, "/v1/sessions") { - t.Fatalf("rootless repair composed or created: %s", route) - } + testassert.Require(t, !strings.Contains(route, "/v1/sessions"), "rootless repair composed or created: %s", route) } } @@ -644,23 +559,17 @@ func TestKnownForkFailureRetainsHandledRestoreAndSourceSelection(t *testing.T) { c.forkRewind = true a, err := c.formAttemptLocked("ForkSession", minted) c.mu.Unlock() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) c.task(func() { c.runAttempt(a.id) }) receive(t, transport.requests).respond(protocol.CodeRevertResult{Restored: []string{"one.go"}, Skipped: []protocol.SkippedFile{}}) receive(t, transport.requests).reject(409, protocol.Error{Code: "conflict", Message: "Fork rejected"}) receive(t, transport.requests).respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() - if c.root != r || c.rootID != rootA || c.draft.String() != "replacement" || a.outcome != "rejected" || !a.restoreHandled || !strings.Contains(string(a.restore), "one.go") { - t.Fatal("Fork failure lost source/draft/known earlier restore") - } + testassert.Require(t, c.root == r && c.rootID == rootA && c.draft.String() == "replacement" && a.outcome == "rejected" && a.restoreHandled && strings.Contains(string(a.restore), "one.go"), "Fork failure lost source/draft/known earlier restore") c.mu.Unlock() text := outputs(c) - if !strings.Contains(text, "one.go") || !strings.Contains(text, "Fork rejected") || strings.Contains(text, "rollback") { - t.Fatal("known restore effects hidden or rollback claimed") - } + testassert.Require(t, strings.Contains(text, "one.go") && strings.Contains(text, "Fork rejected") && !strings.Contains(text, "rollback"), "known restore effects hidden or rollback claimed") } func TestUnselectedArchiveInvalidatesListSuccessAndErrorWithoutClearingRoot(t *testing.T) { @@ -683,13 +592,9 @@ func TestUnselectedArchiveInvalidatesListSuccessAndErrorWithoutClearingRoot(t *t } settleTasks(t, c) c.mu.Lock() - if c.root != root || c.rootID != rootA || c.menu != nil || len(c.sessions) != 1 || c.sessions[0].SessionId != rootA { - t.Fatal("unselected lifecycle mutation lost current root or accepted stale list") - } + testassert.Require(t, c.root == root && c.rootID == rootA && c.menu == nil && len(c.sessions) == 1 && c.sessions[0].SessionId == rootA, "unselected lifecycle mutation lost current root or accepted stale list") c.mu.Unlock() - if strings.Contains(outputs(c), "late list error") { - t.Fatal("late list error survived mutation invalidation") - } + testassert.Require(t, !strings.Contains(outputs(c), "late list error"), "late list error survived mutation invalidation") }) } } diff --git a/internal/cliclient/contracts_test.go b/internal/cliclient/contracts_test.go index e1352627..c46285f0 100644 --- a/internal/cliclient/contracts_test.go +++ b/internal/cliclient/contracts_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/terminal" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -38,26 +39,18 @@ func TestStaleOlderPageRetriesThroughTheSameReader(t *testing.T) { c.mu.Unlock() receive(t, transport.requests).respond(protocol.HistoryPage{SessionRevision: revision("4"), Items: inputs(1, 50)}) fresh := receive(t, transport.requests) - if !strings.HasSuffix(fresh.req.URL.Path, "/hydration") { - t.Fatal("stale page did not dirty hydration") - } + testassert.Require(t, strings.HasSuffix(fresh.req.URL.Path, "/hydration"), "stale page did not dirty hydration") fresh.respond(h) retry := receive(t, transport.requests) - if retry.req.URL.Query().Get("cursor") != "real-oldest" { - t.Fatal("stale older request lost or cursor changed") - } + testassert.Require(t, retry.req.URL.Query().Get("cursor") == "real-oldest", "stale older request lost or cursor changed") c.mu.Lock() - if r.chain != chain || len(r.items) != 50 || r.hydration.SessionRevision.DurableRevision != "5" { - t.Fatal("stale page advanced view or spawned second chain") - } + testassert.Require(t, r.chain == chain && len(r.items) == 50 && r.hydration.SessionRevision.DurableRevision == "5", "stale page advanced view or spawned second chain") c.mu.Unlock() retry.respond(protocol.HistoryPage{SessionRevision: revision("5"), Items: inputs(1, 50)}) barrier(t, chain.done) c.mu.Lock() defer c.mu.Unlock() - if len(r.items) != 100 { - t.Fatal("valid disjoint older page rejected") - } + testassert.Require(t, len(r.items) == 100, "valid disjoint older page rejected") } func TestMalformedGlobalsAndLostConversationContinuityRepairActualConnection(t *testing.T) { @@ -116,9 +109,7 @@ func TestMalformedGlobalsAndLostConversationContinuityRepairActualConnection(t * frameContaining(t, screen, "input 5") } write.Close() - if err := receive(t, done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, receive(t, done)) assertNoImplicitControls(t, live) }) } @@ -153,9 +144,7 @@ func TestQueuedTaskMenuAndErrorCannotPublishAfterNavigation(t *testing.T) { settleTasks(t, c) close(blocked.release) c.mu.Lock() - if c.menu != nil { - t.Fatal("queued menu survived newer selection") - } + testassert.Require(t, c.menu == nil, "queued menu survived newer selection") current := c.stampLocked() c.mu.Unlock() c.report(current, terminal.RenderSystemMsg("current command marker"), false) @@ -175,9 +164,7 @@ func TestRuntimeFileSuccessAndErrorAreStampedAndUseGeneratedAddress(t *testing.T settledReader(t, c, transport) c.command(`/runtime ReadWorkspaceFile {"body":{"workspace":"/workspace","path":"file.go"}}`) old := receive(t, transport.requests) - if !strings.Contains(string(old.body), `"workspace":"/workspace"`) || !strings.Contains(string(old.body), `"path":"file.go"`) { - t.Fatal("file read omitted explicit address") - } + testassert.Require(t, strings.Contains(string(old.body), `"workspace":"/workspace"`) && strings.Contains(string(old.body), `"path":"file.go"`), "file read omitted explicit address") c.selectRoot(rootB) receive(t, transport.requests).respond(hydration(rootB, "1", nil, nil)) receive(t, transport.requests).respond(hydration(rootB, "1", nil, nil)) @@ -187,21 +174,18 @@ func TestRuntimeFileSuccessAndErrorAreStampedAndUseGeneratedAddress(t *testing.T old.respond(protocol.ReadFileResult{CanonicalPath: "/workspace/stale.go", Content: "stale content", Truncated: true}) } settleTasks(t, c) - if strings.Contains(outputs(c), "stale") { - t.Fatal("superseded viewer success/error printed") - } + testassert.Require(t, !strings.Contains(outputs(c), "stale"), "superseded viewer success/error printed") c.command(`/runtime ReadWorkspaceFile {"body":{"workspace":"/workspace","path":"current.go"}}`) receive(t, transport.requests).respond(protocol.ReadFileResult{CanonicalPath: "/workspace/current.go", Content: "current", Truncated: true}) settleTasks(t, c) text := outputs(c) - if !strings.Contains(text, "/workspace/current.go") || !strings.Contains(text, `"truncated":true`) { - t.Fatal("canonical path/truncation lost") - } + testassert.Require(t, strings.Contains(text, "/workspace/current.go") && strings.Contains(text, `"truncated":true`), "canonical path/truncation lost") c.command(`/runtime ReadWorkspaceFile {"body":{"workspace":"/workspace","path":"../denied"}}`) receive(t, transport.requests).reject(400, protocol.Error{Code: "invalid", Message: "denied workspace path"}) settleTasks(t, c) - if text := outputs(c); !strings.Contains(text, "denied workspace path") || strings.Contains(text, "Outcome unknown") { - t.Fatal("denied read hidden or treated as unknown mutation") + { + text := outputs(c) + testassert.Require(t, strings.Contains(text, "denied workspace path") && !strings.Contains(text, "Outcome unknown"), "denied read hidden or treated as unknown mutation") } }) } @@ -225,25 +209,19 @@ func TestForkBoundaryMenusUserOwnedCopiedRuntimeAndPlugin(t *testing.T) { c.command("/fork") settleTasks(t, c) c.mu.Lock() - if len(c.menu.items) != 2 { - t.Fatal("non-user input became a fork boundary") - } + testassert.Require(t, len(c.menu.items) == 2, "non-user input became a fork boundary") c.mu.Unlock() press(c, terminal.KeyMsg{Special: terminal.KeyDown}) press(c, terminal.KeyMsg{Special: terminal.KeyEnter}) c.mu.Lock() - if c.menu != nil || c.forkBoundary.OperationId != nil || c.forkRewind { - t.Fatal("copied input invented a source restore boundary") - } + testassert.Require(t, c.menu == nil && c.forkBoundary.OperationId == nil && !c.forkRewind, "copied input invented a source restore boundary") c.forkBoundary = nil c.mu.Unlock() c.command("/revert") settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if len(c.menu.items) != 1 || c.menu.items[0].Extra.(protocol.InputItem).OperationId == nil || r != c.root { - t.Fatal("copied/non-user rewind sibling admitted") - } + testassert.Require(t, len(c.menu.items) == 1 && c.menu.items[0].Extra.(protocol.InputItem).OperationId != nil && r == c.root, "copied/non-user rewind sibling admitted") } func TestRejectedSubmissionKeepsDraftAndDoesNotBecomeEmptySuccess(t *testing.T) { @@ -255,14 +233,10 @@ func TestRejectedSubmissionKeepsDraftAndDoesNotBecomeEmptySuccess(t *testing.T) receive(t, transport.requests).respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() - if c.draft.String() != "rejected draft" { - t.Fatal("rejected draft cleared") - } + testassert.Require(t, c.draft.String() == "rejected draft", "rejected draft cleared") c.mu.Unlock() text := outputs(c) - if !strings.Contains(text, "server gate rejection") || strings.Contains(text, "Outcome unknown") { - t.Fatal("known rejection hidden/reclassified") - } + testassert.Require(t, strings.Contains(text, "server gate rejection") && !strings.Contains(text, "Outcome unknown"), "known rejection hidden/reclassified") } func TestChildListOldConnectionSuccessAndErrorAreSuperseded(t *testing.T) { @@ -289,16 +263,12 @@ func TestChildListOldConnectionSuccessAndErrorAreSuperseded(t *testing.T) { } barrier(t, oldDone) c.mu.Lock() - if c.menu != nil || c.conn == s.conn { - t.Fatal("old connection child menu published") - } + testassert.Require(t, c.menu == nil && c.conn != s.conn, "old connection child menu published") fresh := c.stampLocked() c.mu.Unlock() c.report(fresh, terminal.RenderSystemMsg("new owner marker"), false) frameContaining(t, screen, "new owner marker") - if strings.Contains(screen.text(), "old owner child error") { - t.Fatal("old-owner error published") - } + testassert.Require(t, !strings.Contains(screen.text(), "old owner child error"), "old-owner error published") c.mu.Lock() c.closed = true c.cancel() @@ -315,9 +285,7 @@ func TestFutureAgentAndTypeWideModelUseGeneratedScope(t *testing.T) { settledReader(t, c, transport) c.command("/agent planner") req := receive(t, transport.requests) - if !strings.Contains(req.req.URL.Path, rootA) || string(req.body) != `{"agent_type":"planner"}` { - t.Fatalf("wrong Session Agent edit: %s %s", req.req.URL, req.body) - } + testassert.Require(t, strings.Contains(req.req.URL.Path, rootA) && string(req.body) == `{"agent_type":"planner"}`, "wrong Session Agent edit: %s %s", req.req.URL, req.body) req.respond(protocol.Session{SessionId: rootA, AgentType: "planner"}) next := hydration(rootA, "2", inputs(1, 1), nil) next.Session.AgentType = "planner" @@ -326,9 +294,7 @@ func TestFutureAgentAndTypeWideModelUseGeneratedScope(t *testing.T) { settleTasks(t, c) c.command("/model provider/next") req = receive(t, transport.requests) - if req.req.URL.Query().Get("agent_type") != "planner" || string(req.body) != `{"model":"provider/next"}` || strings.Contains(req.req.URL.Path, rootA) { - t.Fatalf("model was not a type-wide future edit: %s %s", req.req.URL, req.body) - } + testassert.Require(t, req.req.URL.Query().Get("agent_type") == "planner" && string(req.body) == `{"model":"provider/next"}` && !strings.Contains(req.req.URL.Path, rootA), "model was not a type-wide future edit: %s %s", req.req.URL, req.body) req.respond(protocol.RevisionResponse{ConfigurationRevision: configuration("2").ConfigurationRevision}) next.SessionRevision = revision("3") next.SelectedModel = pointer("provider/next") @@ -342,9 +308,7 @@ func TestFutureAgentAndTypeWideModelUseGeneratedScope(t *testing.T) { c.mu.Unlock() frameContaining(t, screen, "captured-old") text := strings.ReplaceAll(screen.text(), "\n", "") - if !strings.Contains(text, "Future: planner / provider/next") || !strings.Contains(text, "captured-old / captured/model") { - t.Fatal("future selection overwrote active capture") - } + testassert.Require(t, strings.Contains(text, "Future: planner / provider/next") && strings.Contains(text, "captured-old / captured/model"), "future selection overwrote active capture") c.requestExit(nil) } @@ -359,21 +323,21 @@ func TestCreateFailureCapturedAddressAndExplicitWorkspaceIntent(t *testing.T) { c.task(func() { c.create(s) }) old := receive(t, transport.requests) var pending protocol.CreateSessionRequest - if err := json.Unmarshal(old.body, &pending); err != nil || pending.Workspace != "/workspace" { - t.Fatalf("pending create captured %q (%v)", pending.Workspace, err) + { + err := json.Unmarshal(old.body, &pending) + testassert.Require(t, err == nil && pending.Workspace == "/workspace", "pending create captured %q (%v)", pending.Workspace, err) } c.selectWorkspace("/workspace/../different") list := receive(t, transport.requests) - if list.req.URL.Query().Get("workspace") != "/different" { - t.Fatal("Workspace did not use lexical Abs") - } + testassert.Require(t, list.req.URL.Query().Get("workspace") == "/different", "Workspace did not use lexical Abs") // A child-only header list leaves no eligible root: the next request is // still the Create for the captured Workspace. list.respond([]protocol.Session{{SessionId: childA, ParentSessionId: pointer(rootB)}}) newCreate := receive(t, transport.requests) var sent protocol.CreateSessionRequest - if err := json.Unmarshal(newCreate.body, &sent); err != nil || sent.Workspace != "/different" { - t.Fatalf("explicit Workspace composition create = %s (%v)", newCreate.body, err) + { + err := json.Unmarshal(newCreate.body, &sent) + testassert.Require(t, err == nil && sent.Workspace == "/different", "explicit Workspace composition create = %s (%v)", newCreate.body, err) } newCreate.reject(201, protocol.Session{SessionId: rootB, Workspace: "/different", AgentType: "primary"}) fresh := hydration(rootB, "1", nil, nil) @@ -384,12 +348,8 @@ func TestCreateFailureCapturedAddressAndExplicitWorkspaceIntent(t *testing.T) { c.mu.Lock() adopted := c.rootID == rootB c.mu.Unlock() - if !adopted { - t.Fatal("an old-Workspace completion or a failed intent blocked the new explicit composition") - } - if strings.Contains(outputs(c), "Outcome unknown") { - t.Fatal("an old-Workspace completion published a stale local error") - } + testassert.Require(t, adopted, "an old-Workspace completion or a failed intent blocked the new explicit composition") + testassert.Require(t, !strings.Contains(outputs(c), "Outcome unknown"), "an old-Workspace completion published a stale local error") } func TestExplicitResumeCannotPromoteChildOrSubmitToItBeforeAdoption(t *testing.T) { @@ -403,17 +363,13 @@ func TestExplicitResumeCannotPromoteChildOrSubmitToItBeforeAdoption(t *testing.T c.mu.Unlock() c.command("/resume " + childA) destination := receive(t, transport.requests) - if !strings.HasSuffix(destination.req.URL.Path, "/hydration") { - t.Fatal("named resume did not read its destination globally") - } + testassert.Require(t, strings.HasSuffix(destination.req.URL.Path, "/hydration"), "named resume did not read its destination globally") // The retained source keeps accepting explicit work while the destination is // resolved, and nothing is addressed to the unadopted child. setDraft(c, "forged child follow up") press(c, terminal.KeyMsg{Special: terminal.KeyEnter}) submit := receive(t, transport.requests) - if !strings.Contains(submit.req.URL.Path, sourceID) { - t.Fatalf("follow-up addressed outside the accepted source: %s", submit.req.URL.Path) - } + testassert.Require(t, strings.Contains(submit.req.URL.Path, sourceID), "follow-up addressed outside the accepted source: %s", submit.req.URL.Path) submit.respond(protocol.SubmitResult{Disposition: "admitted", Operation: &protocol.Operation{OperationId: "forged"}}) // The destination read's own Session record is the eligibility authority. child := hydration(childA, "1", nil, nil) @@ -421,16 +377,12 @@ func TestExplicitResumeCannotPromoteChildOrSubmitToItBeforeAdoption(t *testing.T destination.respond(child) settleAnswering(t, c, transport) c.mu.Lock() - if c.rootID != sourceID || c.root != source || c.childID != "" { - t.Fatal("child resume promoted the child or erased the source") - } + testassert.Require(t, c.rootID == sourceID && c.root == source && c.childID == "", "child resume promoted the child or erased the source") c.mu.Unlock() transport.mu.Lock() defer transport.mu.Unlock() for _, route := range transport.calls { - if strings.Contains(route, "/reopen") || strings.Contains(route, childA+"/submit") { - t.Fatalf("child root bypass %s", route) - } + testassert.Require(t, !strings.Contains(route, "/reopen") && !strings.Contains(route, childA+"/submit"), "child root bypass %s", route) } } @@ -446,9 +398,7 @@ func TestNamedResumeAdoptsCrossWorkspaceRootAndReopensArchived(t *testing.T) { receive(t, transport.requests).respond(destination) settleTasks(t, c) c.mu.Lock() - if c.rootID != rootB || c.workspace != "/different" || c.root == nil { - t.Fatalf("cross-Workspace root resume was refused or not adopted: root=%q workspace=%q", c.rootID, c.workspace) - } + testassert.Require(t, c.rootID == rootB && c.workspace == "/different" && c.root != nil, "cross-Workspace root resume was refused or not adopted: root=%q workspace=%q", c.rootID, c.workspace) c.mu.Unlock() // An archived root is reopened first and its fresh hydration is then adopted. c.command("/resume " + childA) @@ -456,22 +406,16 @@ func TestNamedResumeAdoptsCrossWorkspaceRootAndReopensArchived(t *testing.T) { archived.Session.Lifecycle = protocol.Archived receive(t, transport.requests).respond(archived) reopen := receive(t, transport.requests) - if !strings.HasSuffix(reopen.req.URL.Path, "/reopen") { - t.Fatal("archived destination did not reopen before work") - } + testassert.Require(t, strings.HasSuffix(reopen.req.URL.Path, "/reopen"), "archived destination did not reopen before work") reopen.respond(protocol.Session{SessionId: childA, Lifecycle: protocol.Open}) // The adopted reader owns the one fresh hydration after the reopen. fresh := receive(t, transport.requests) - if !strings.HasSuffix(fresh.req.URL.Path, "/hydration") { - t.Fatal("reopen did not precede the fresh destination read") - } + testassert.Require(t, strings.HasSuffix(fresh.req.URL.Path, "/hydration"), "reopen did not precede the fresh destination read") fresh.respond(hydration(childA, "2", nil, nil)) settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.rootID != childA || c.root == nil || c.root.hydration == nil || c.root.hydration.SessionRevision.DurableRevision != "2" { - t.Fatal("reopened root was not adopted with its fresh read") - } + testassert.Require(t, c.rootID == childA && c.root != nil && c.root.hydration != nil && c.root.hydration.SessionRevision.DurableRevision == "2", "reopened root was not adopted with its fresh read") } func TestBareResumeSelectsNewestOpenRootAndKeepsSelectionWithoutAny(t *testing.T) { @@ -487,9 +431,7 @@ func TestBareResumeSelectsNewestOpenRootAndKeepsSelectionWithoutAny(t *testing.T receive(t, transport.requests).respond(hydration(rootB, "2", nil, nil)) settleTasks(t, c) c.mu.Lock() - if c.rootID != rootB || c.menu != nil { - t.Fatalf("bare resume did not select the newest open root: root=%q menu=%v", c.rootID, c.menu) - } + testassert.Require(t, c.rootID == rootB && c.menu == nil, "bare resume did not select the newest open root: root=%q menu=%v", c.rootID, c.menu) c.mu.Unlock() // No available root reports the retained no-session outcome and keeps the // current selection; no Session is created. @@ -498,19 +440,13 @@ func TestBareResumeSelectsNewestOpenRootAndKeepsSelectionWithoutAny(t *testing.T // The retained source stays live: its own refresh follows the failed intent. settleAnsweringRequested(t, c, transport) c.mu.Lock() - if c.rootID != rootB { - t.Fatalf("empty bare resume moved the selection: %q", c.rootID) - } + testassert.Equal(t, c.rootID, rootB) c.mu.Unlock() - if !strings.Contains(outputs(c), "no active sessions") { - t.Fatal("no-session outcome was not reported") - } + testassert.Require(t, strings.Contains(outputs(c), "no active sessions"), "no-session outcome was not reported") transport.mu.Lock() defer transport.mu.Unlock() for _, route := range transport.calls { - if strings.Contains(route, "/create") || strings.Contains(route, "/archived") { - t.Fatalf("bare resume created a Session or consulted archived Sessions: %s", route) - } + testassert.Require(t, !strings.Contains(route, "/create") && !strings.Contains(route, "/archived"), "bare resume created a Session or consulted archived Sessions: %s", route) } } @@ -540,20 +476,14 @@ func TestFailedRootNavigationKeepsTheAcceptedSelection(t *testing.T) { test.fail(receive(t, transport.requests)) // The retained source readers resume under the current selection. resumed := receive(t, transport.requests) - if !strings.Contains(resumed.req.URL.Path, rootA+"/hydration") { - t.Fatalf("failed navigation did not resume the source read: %s", resumed.req.URL) - } + testassert.Require(t, strings.Contains(resumed.req.URL.Path, rootA+"/hydration"), "failed navigation did not resume the source read: %s", resumed.req.URL) resumed.respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() kept := c.root == source && c.rootID == sourceID && c.workspace == workspace && c.child == nil && c.childID == "" && c.menu == nil c.mu.Unlock() - if !kept { - t.Fatal("failed navigation changed the accepted selection") - } - if !strings.Contains(outputs(c), test.note) { - t.Fatal("failed navigation hid its outcome") - } + testassert.Require(t, kept, "failed navigation changed the accepted selection") + testassert.Require(t, strings.Contains(outputs(c), test.note), "failed navigation hid its outcome") press(c, terminal.KeyMsg{Rune: 'z'}) press(c, terminal.KeyMsg{Special: terminal.KeyUp}) if c.draft.String() != "input 1" { @@ -578,12 +508,8 @@ func TestFailedRootNavigationKeepsTheAcceptedSelection(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.rootID != rootB { - t.Fatal("a superseded completion adopted over newer navigation") - } - if strings.Contains(outputs(c), "stale destination error") { - t.Fatal("superseded destination error printed") - } + testassert.Require(t, c.rootID == rootB, "a superseded completion adopted over newer navigation") + testassert.Require(t, !strings.Contains(outputs(c), "stale destination error"), "superseded destination error printed") }) } @@ -599,9 +525,7 @@ func TestSourceAgentMenuCannotPublishAfterDestinationAdoption(t *testing.T) { destination := receive(t, transport.requests) c.command("/agent") menuRead := receive(t, transport.requests) - if !strings.HasSuffix(menuRead.req.URL.Path, "/configuration") { - t.Fatalf("agent menu did not read the configuration: %s", menuRead.req.URL.Path) - } + testassert.Require(t, strings.HasSuffix(menuRead.req.URL.Path, "/configuration"), "agent menu did not read the configuration: %s", menuRead.req.URL.Path) // The adopted reader's own read proves the destination published, then the // menu task completes afterwards. destination.respond(hydration(rootB, "2", nil, nil)) @@ -611,12 +535,7 @@ func TestSourceAgentMenuCannotPublishAfterDestinationAdoption(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.menu != nil { - t.Fatalf("source agent menu published after destination adoption: %+v", c.menu) - } - if c.rootID != rootB || c.root == nil { - t.Fatalf("destination was not adopted: root=%q", c.rootID) - } + testassert.Require(t, c.menu == nil && c.rootID == rootB && c.root != nil, "source agent menu published after destination adoption: %+v; destination was not adopted: root=%q", c.menu, c.rootID) } func TestSourceForkCompletionCannotReplaceAdoptedDestination(t *testing.T) { @@ -629,14 +548,10 @@ func TestSourceForkCompletionCannotReplaceAdoptedDestination(t *testing.T) { c.forkBoundary = &protocol.InputItem{ItemId: "boundary"} a, err := c.formAttemptLocked("ForkSession", "fork-op") c.mu.Unlock() - if err != nil { - t.Fatalf("fork attempt: %v", err) - } + testassert.NoError(t, err) c.task(func() { c.runAttempt(a.id) }) fork := receive(t, transport.requests) - if !strings.HasSuffix(fork.req.URL.Path, "/fork") { - t.Fatalf("fork attempt did not fork: %s", fork.req.URL.Path) - } + testassert.Require(t, strings.HasSuffix(fork.req.URL.Path, "/fork"), "fork attempt did not fork: %s", fork.req.URL.Path) // The adopted reader's own read proves the destination published, then the // source Fork completes afterwards. destination.respond(hydration(rootB, "2", nil, nil)) @@ -646,14 +561,10 @@ func TestSourceForkCompletionCannotReplaceAdoptedDestination(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.rootID != rootB || c.root == nil { - t.Fatalf("source Fork completion replaced the adopted destination: root=%q", c.rootID) - } + testassert.Require(t, c.rootID == rootB && c.root != nil, "source Fork completion replaced the adopted destination: root=%q", c.rootID) // The attempt's own outcome and identity survive. settled := c.attempts["fork-op"] - if settled == nil || settled.outcome != "accepted" || settled.session != rootA { - t.Fatalf("fork outcome/identity lost: %+v", settled) - } + testassert.Require(t, settled != nil && settled.outcome == "accepted" && settled.session == rootA, "fork outcome/identity lost: %+v", settled) } func TestFailedResumeLeavesTheSourceLiveUnderAHeldHydration(t *testing.T) { @@ -674,16 +585,12 @@ func TestFailedResumeLeavesTheSourceLiveUnderAHeldHydration(t *testing.T) { destination.reject(404, protocol.Error{Code: "not_found", Message: "no such session"}) held.respond(hydration(rootA, "2", inputs(1, 1), nil)) fresh := receive(t, transport.requests) - if !strings.Contains(fresh.req.URL.Path, rootA) || !strings.HasSuffix(fresh.req.URL.Path, "/hydration") { - t.Fatalf("the retained source did not resume reading: %s", fresh.req.URL.Path) - } + testassert.Require(t, strings.Contains(fresh.req.URL.Path, rootA) && strings.HasSuffix(fresh.req.URL.Path, "/hydration"), "the retained source did not resume reading: %s", fresh.req.URL.Path) fresh.respond(hydration(rootA, "3", inputs(1, 1), nil)) settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.rootID != rootA || c.root == nil || c.root.hydration == nil || c.root.hydration.SessionRevision.DurableRevision != "3" { - t.Fatal("the retained source was left stale after a failed resume") - } + testassert.Require(t, c.rootID == rootA && c.root != nil && c.root.hydration != nil && c.root.hydration.SessionRevision.DurableRevision == "3", "the retained source was left stale after a failed resume") } func TestRuntimeCurrentRetainedSnapshotsAndPartialRestoreRemainReachable(t *testing.T) { @@ -691,34 +598,22 @@ func TestRuntimeCurrentRetainedSnapshotsAndPartialRestoreRemainReachable(t *test settledReader(t, c, transport) c.command(`/runtime GetSessionCodeSnapshots {"session_id":"` + rootA + `"}`) current := receive(t, transport.requests) - if !strings.Contains(current.req.URL.Path, rootA) { - t.Fatal("current snapshots did not address Session") - } + testassert.Require(t, strings.Contains(current.req.URL.Path, rootA), "current snapshots did not address Session") current.respond([]protocol.CodeSnapshotGroup{{OperationId: "owned-operation", GroupItemId: "group", Files: []protocol.SnapshotFile{}}}) settleTasks(t, c) - if !strings.Contains(outputs(c), "owned-operation") { - t.Fatal("Operation snapshot boundary dropped") - } + testassert.Require(t, strings.Contains(outputs(c), "owned-operation"), "Operation snapshot boundary dropped") c.command(`/runtime GetRetainedCodeSnapshots {"query":{"workspace":"/workspace","session_id":"1234abcd"}}`) retained := receive(t, transport.requests) - if retained.req.URL.Query().Get("workspace") != "/workspace" || retained.req.URL.Query().Get("session_id") != "1234abcd" { - t.Fatal("retained snapshot address changed") - } + testassert.Require(t, retained.req.URL.Query().Get("workspace") == "/workspace" && retained.req.URL.Query().Get("session_id") == "1234abcd", "retained snapshot address changed") retained.respond(protocol.RetainedTurns{Turns: []protocol.RetainedTurn{{Turn: 7, Files: []protocol.SnapshotFile{}}}}) settleTasks(t, c) - if !strings.Contains(outputs(c), "retained snapshot boundaries") { - t.Fatal("retained boundary labelled as conversation turn") - } + testassert.Require(t, strings.Contains(outputs(c), "retained snapshot boundaries"), "retained boundary labelled as conversation turn") c.command(`/runtime RevertRetainedCode {"body":{"workspace":"/workspace","session_id":"1234abcd","after_turn":7}}`) restore := receive(t, transport.requests) - if !strings.Contains(string(restore.body), `"after_turn":7`) { - t.Fatal("retained restore boundary rewritten") - } + testassert.Require(t, strings.Contains(string(restore.body), `"after_turn":7`), "retained restore boundary rewritten") restore.reject(409, protocol.CodeRevertResult{Restored: []string{"one.go"}, Skipped: []protocol.SkippedFile{{Path: "two.go", Reason: "changed"}}, Error: &protocol.Error{Code: "conflict", Message: "partial restore"}}) receive(t, transport.requests).respond(hydration(rootA, "2", inputs(1, 1), nil)) settleTasks(t, c) text := outputs(c) - if !strings.Contains(text, "one.go") || !strings.Contains(text, "two.go") || !strings.Contains(text, "partial restore") { - t.Fatal("/runtime dropped complete partial restore") - } + testassert.Require(t, strings.Contains(text, "one.go") && strings.Contains(text, "two.go") && strings.Contains(text, "partial restore"), "/runtime dropped complete partial restore") } diff --git a/internal/cliclient/globals_test.go b/internal/cliclient/globals_test.go index c433088c..c039de43 100644 --- a/internal/cliclient/globals_test.go +++ b/internal/cliclient/globals_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/clienthost" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -37,9 +38,7 @@ func TestGlobalsNoReaderCoalescesAndCoveredHintsDoNotRead(t *testing.T) { c.acceptEvent(eventDelivery{c.conn, warningHint("6")}) } c.mu.Lock() - if !c.globalsBusy || !c.globalsDirty || c.root != nil { - t.Fatal("hints created an unintended Session reader") - } + testassert.Require(t, c.globalsBusy && c.globalsDirty && c.root == nil, "hints created an unintended Session reader") c.mu.Unlock() config := configuration("6") config.Agents[0].Name = "payload-six" @@ -58,16 +57,12 @@ func TestGlobalsNoReaderCoalescesAndCoveredHintsDoNotRead(t *testing.T) { c.acceptEvent(eventDelivery{c.conn, warningHint("5")}) } c.mu.Lock() - if c.globalsBusy || c.globalsDirty || c.configuration.Agents[0].Name != "payload-six" || c.warnings.Warnings[0].Message != "payload-six" { - t.Fatal("covered hints refreshed or payload not installed atomically") - } + testassert.Require(t, !c.globalsBusy && !c.globalsDirty && c.configuration.Agents[0].Name == "payload-six" && c.warnings.Warnings[0].Message == "payload-six", "covered hints refreshed or payload not installed atomically") c.mu.Unlock() transport.mu.Lock() after := len(transport.calls) transport.mu.Unlock() - if before != 4 || after != before { - t.Fatalf("refresh count %d -> %d", before, after) - } + testassert.Require(t, before == 4 && after == before, "refresh count %d -> %d", before, after) } func TestGlobalClockPayloadNonregressionAndHydrationFloors(t *testing.T) { @@ -84,9 +79,7 @@ func TestGlobalClockPayloadNonregressionAndHydrationFloors(t *testing.T) { receive(t, transport.requests).respond(warnings("8")) configReq := receive(t, transport.requests) c.mu.Lock() - if c.configuration.Agents[0].Name != "accepted-nine" || c.warnings.WarningsRevision.Revision != "9" { - t.Fatal("older payload replaced accepted clock") - } + testassert.Require(t, c.configuration.Agents[0].Name == "accepted-nine" && c.warnings.WarningsRevision.Revision == "9", "older payload replaced accepted clock") c.mu.Unlock() configReq.respond(configuration("10")) receive(t, transport.requests).respond(warnings("10")) @@ -98,9 +91,7 @@ func TestGlobalClockPayloadNonregressionAndHydrationFloors(t *testing.T) { receive(t, transport.requests).respond(stale) fresh := receive(t, transport.requests) c.mu.Lock() - if r.hydration != nil { - t.Fatal("hydration installed below independent floors") - } + testassert.Require(t, r.hydration == nil, "hydration installed below independent floors") c.mu.Unlock() stale.ConfigurationRevision = configuration("10").ConfigurationRevision stale.WarningsRevision = warnings("10").WarningsRevision @@ -112,9 +103,7 @@ func TestGlobalClockPayloadNonregressionAndHydrationFloors(t *testing.T) { receive(t, transport.requests).respond(stale) barrier(t, done) c.mu.Lock() - if r.hydration.ConfigurationRevision.Generation != "11" || r.hydration.WarningsRevision.Revision != "10" { - t.Fatal("configuration-only delivery was suppressed") - } + testassert.Require(t, r.hydration.ConfigurationRevision.Generation == "11" && r.hydration.WarningsRevision.Revision == "10", "configuration-only delivery was suppressed") c.mu.Unlock() done = c.refreshReader(r) stale.WarningsRevision = warnings("11").WarningsRevision @@ -122,9 +111,7 @@ func TestGlobalClockPayloadNonregressionAndHydrationFloors(t *testing.T) { barrier(t, done) c.mu.Lock() defer c.mu.Unlock() - if r.hydration.ConfigurationRevision.Generation != "11" || r.hydration.WarningsRevision.Revision != "11" { - t.Fatal("equal Session discarded independent clocks") - } + testassert.Require(t, r.hydration.ConfigurationRevision.Generation == "11" && r.hydration.WarningsRevision.Revision == "11", "equal Session discarded independent clocks") } // TestGlobalReadsStayValidAcrossSelectionChanges pins the Runtime-global @@ -154,12 +141,8 @@ func TestGlobalReadsStayValidAcrossSelectionChanges(t *testing.T) { receive(t, transport.requests).lost() settleTasks(t, c) c.mu.Lock() - if c.configuration == nil || c.configuration.ConfigurationRevision.Generation != "900" { - t.Fatal("the connection's completed global read was dropped with the selection") - } - if c.root == nil || c.root.hydration == nil || c.rootID != rootB { - t.Fatal("the destination root was not adopted") - } + testassert.Require(t, c.configuration != nil && c.configuration.ConfigurationRevision.Generation == "900", "the connection's completed global read was dropped with the selection") + testassert.Require(t, c.root != nil && c.root.hydration != nil && c.rootID == rootB, "the destination root was not adopted") c.mu.Unlock() transport.mu.Lock() configCalls := 0 @@ -169,12 +152,8 @@ func TestGlobalReadsStayValidAcrossSelectionChanges(t *testing.T) { } } transport.mu.Unlock() - if configCalls != 1 { - t.Fatalf("navigation triggered a global re-read: configuration reads %d", configCalls) - } - if !strings.Contains(outputs(c), "unexpected EOF") { - t.Fatal("a current connection's global read error was suppressed") - } + testassert.Equal(t, configCalls, 1) + testassert.Require(t, strings.Contains(outputs(c), "unexpected EOF"), "a current connection's global read error was suppressed") } func TestConnectionRepairClearsOldFloorsBeforeNewOwnerHydration(t *testing.T) { @@ -200,8 +179,7 @@ func TestConnectionRepairClearsOldFloorsBeforeNewOwnerHydration(t *testing.T) { requests := make(chan request, 16) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/v1/health" { - w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(protocol.Health{InstanceId: owner, ProtocolVersion: protocol.N2}) + health(w, owner) return } if r.URL.Path == "/v1/events" { @@ -223,10 +201,7 @@ func TestConnectionRepairClearsOldFloorsBeforeNewOwnerHydration(t *testing.T) { })) defer server.Close() dir := t.TempDir() - raw, _ := json.Marshal(map[string]string{"endpoint": server.URL, "instance_id": owner, "protocol_version": string(protocol.N2), "credential": strings.Repeat("1", 64)}) - if err := os.WriteFile(filepath.Join(dir, "runtime.json"), raw, 0600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "runtime.json"), discoveryRecord(server.URL, owner), 0600)) c.mu.Lock() c.opts.Startup = clienthost.Options{DataDir: dir} c.mu.Unlock() @@ -241,9 +216,7 @@ func TestConnectionRepairClearsOldFloorsBeforeNewOwnerHydration(t *testing.T) { } } c.mu.Lock() - if c.configuration != nil || c.warnings != nil || c.provisional != "" || c.root.floor != nil { - t.Fatal("retirement kept old projections/floors/activity") - } + testassert.Require(t, c.configuration == nil && c.warnings == nil && c.provisional == "" && c.root.floor == nil, "retirement kept old projections/floors/activity") newReader := c.root newDone := newReader.chain.done c.mu.Unlock() @@ -279,9 +252,7 @@ func TestConnectionRepairClearsOldFloorsBeforeNewOwnerHydration(t *testing.T) { } c.mu.Unlock() settleTasks(t, c) - if !accepted || strings.Contains(outputs(c), "unexpected EOF") { - t.Fatal("old independent read failure poisoned new owner") - } + testassert.Require(t, accepted && !strings.Contains(outputs(c), "unexpected EOF"), "old independent read failure poisoned new owner") }) } } @@ -313,12 +284,8 @@ func TestGlobalsAndAgentMenuOpaqueOverflowConfiguration(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.configuration == nil || len(c.configuration.Agents) != 2 || c.configuration.Agents[0].Name != "primary" { - t.Fatal("valid opaque configuration was not adopted") - } - if c.conn != oldConn { - t.Fatal("valid opaque configuration triggered a repair loop") - } + testassert.Require(t, c.configuration != nil && len(c.configuration.Agents) == 2 && c.configuration.Agents[0].Name == "primary", "valid opaque configuration was not adopted") + testassert.Require(t, c.conn == oldConn, "valid opaque configuration triggered a repair loop") select { case <-c.exit: t.Fatal("adoption exited the client") @@ -374,12 +341,8 @@ func TestGlobalsAndAgentMenuOpaqueOverflowConfiguration(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.menu == nil || len(c.menu.items) != 2 || c.menu.items[0].Label != "primary" || c.menu.items[1].Label != "planner" { - t.Fatal("valid opaque configuration was not listed") - } - if c.conn != oldConn { - t.Fatal("the /agent menu triggered a repair loop") - } + testassert.Require(t, c.menu != nil && len(c.menu.items) == 2 && c.menu.items[0].Label == "primary" && c.menu.items[1].Label == "planner", "valid opaque configuration was not listed") + testassert.Require(t, c.conn == oldConn, "the /agent menu triggered a repair loop") select { case <-c.exit: t.Fatal("menu listing exited the client") @@ -398,17 +361,11 @@ func TestGlobalsAndAgentMenuOpaqueOverflowConfiguration(t *testing.T) { settleTasks(t, c) c.mu.Lock() defer c.mu.Unlock() - if c.menu != nil { - t.Fatal("malformed menu data was adopted as menu items") - } - if c.configuration != nil { - t.Fatal("malformed menu data was adopted as global state") - } + testassert.Require(t, c.menu == nil, "malformed menu data was adopted as menu items") + testassert.Require(t, c.configuration == nil, "malformed menu data was adopted as global state") // Command paths report the failure and keep their unknown-outcome // behavior; they never repair or resend. - if c.conn != oldConn { - t.Fatal("the /agent command path repaired instead of reporting") - } + testassert.Require(t, c.conn == oldConn, "the /agent command path repaired instead of reporting") select { case <-c.exit: t.Fatal("the /agent command path exited the client") diff --git a/internal/cliclient/lifecycle_test.go b/internal/cliclient/lifecycle_test.go index a38f195a..ce3a5412 100644 --- a/internal/cliclient/lifecycle_test.go +++ b/internal/cliclient/lifecycle_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/clienthost" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -40,16 +41,14 @@ func liveFixture(t *testing.T, active bool, special func(http.ResponseWriter, *h live.mu.Lock() live.calls = append(live.calls, r.Method+" "+r.URL.RequestURI()) live.mu.Unlock() - if r.Header.Get("Authorization") != "Bearer "+strings.Repeat("1", 64) { - t.Error("unauthenticated generated request") - } + testassert.Check(t, r.Header.Get("Authorization") == "Bearer "+strings.Repeat("1", 64), "unauthenticated generated request") if live.special != nil && live.special(w, r) { return } w.Header().Set("Content-Type", "application/json") switch { case r.URL.Path == "/v1/health": - health(w) + health(w, instance) case r.URL.Path == "/v1/events": w.Header().Set("Content-Type", "text/event-stream") w.WriteHeader(200) @@ -94,19 +93,14 @@ func liveFixture(t *testing.T, active bool, special func(http.ResponseWriter, *h })) t.Cleanup(server.Close) dir := t.TempDir() - raw, _ := json.Marshal(map[string]string{"endpoint": server.URL, "instance_id": instance, "protocol_version": string(protocol.N2), "credential": strings.Repeat("1", 64)}) - if err := os.WriteFile(filepath.Join(dir, "runtime.json"), raw, 0600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "runtime.json"), discoveryRecord(server.URL, instance), 0600)) live.opts = clienthost.Options{DataDir: dir} return live } func pipeInput(t *testing.T) (*os.File, *os.File) { t.Helper() read, write, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) t.Cleanup(func() { read.Close(); write.Close() }) return read, write } @@ -120,9 +114,7 @@ func assertNoImplicitControls(t *testing.T, live *liveRuntime) { live.mu.Lock() defer live.mu.Unlock() for _, route := range live.calls { - if strings.Contains(route, "shutdown") || strings.Contains(route, "/interrupt") || strings.Contains(route, "/stop") { - t.Fatalf("implicit control on exit: %s", route) - } + testassert.Require(t, !strings.Contains(route, "shutdown") && !strings.Contains(route, "/interrupt") && !strings.Contains(route, "/stop"), "implicit control on exit: %s", route) } } @@ -176,22 +168,14 @@ func TestRunEOFSignalReadErrorAndOutputError(t *testing.T) { err := receive(t, done) switch exit { case "EOF": - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) case "signal": var e ExitError - if !errors.As(err, &e) || e.Code != 130 { - t.Fatal(err) - } + testassert.Require(t, errors.As(err, &e) && e.Code == 130, "%v", err) case "read-error": - if err == nil || errors.Is(err, io.EOF) { - t.Fatal("read error hidden") - } + testassert.Require(t, err != nil && !errors.Is(err, io.EOF), "read error hidden") case "output-error": - if !errors.Is(err, io.ErrClosedPipe) { - t.Fatal(err) - } + testassert.Require(t, errors.Is(err, io.ErrClosedPipe), "%v", err) } assertNoImplicitControls(t, live) }) @@ -212,14 +196,13 @@ func TestRunInputPressureBackpressuresAndReleases(t *testing.T) { barrier(t, blocked.entered) // Far more keys than the queue holds: every one past capacity waits, so the // reader never reads past them while the input is closed behind them. - if _, err := write.Write([]byte(strings.Repeat("x", 5000))); err != nil { - t.Fatal(err) + { + _, err := write.Write([]byte(strings.Repeat("x", 5000))) + testassert.NoError(t, err) } write.Close() close(blocked.release) - if err := receive(t, done); err != nil { - t.Fatalf("Run after EOF behind blocked keys = %v, want nil", err) - } + testassert.NoError(t, receive(t, done)) assertNoImplicitControls(t, live) } @@ -235,16 +218,15 @@ func TestRunInputPressureReleasesOnSignal(t *testing.T) { done := runClient(c, input, blocked) barrier(t, live.streams) barrier(t, blocked.entered) - if _, err := write.Write([]byte(strings.Repeat("x", 5000))); err != nil { - t.Fatal(err) + { + _, err := write.Write([]byte(strings.Repeat("x", 5000))) + testassert.NoError(t, err) } barrierUntilFull(t, c) c.handleSignal(syscall.SIGTERM, int(input.Fd())) err := receive(t, done) var e ExitError - if !errors.As(err, &e) || e.Code != 130 { - t.Fatalf("Run = %v, want the signal exit while input pressure persists", err) - } + testassert.Require(t, errors.As(err, &e) && e.Code == 130, "Run = %v, want the signal exit while input pressure persists", err) assertNoImplicitControls(t, live) } @@ -256,9 +238,7 @@ func barrierUntilFull(t *testing.T, c *CLI) { for len(c.keys) != cap(c.keys) && time.Now().Before(deadline) { time.Sleep(5 * time.Millisecond) } - if len(c.keys) != cap(c.keys) { - t.Fatal("the bounded key queue never filled") - } + testassert.Require(t, len(c.keys) == cap(c.keys), "the bounded key queue never filled") } func TestRunParkedStartupReadStopAndOutputCancel(t *testing.T) { @@ -294,9 +274,7 @@ func TestRunParkedStartupReadStopAndOutputCancel(t *testing.T) { } barrier(t, entered) write.Close() - if err := receive(t, done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, receive(t, done)) if stage != "output" { barrier(t, cancelled) } @@ -318,15 +296,11 @@ func TestRunSSEBeforeReadsRejectsEstablishmentFailure(t *testing.T) { input, _ := pipeInput(t) c := New(Options{Startup: live.opts, Workspace: "/workspace"}) err := receive(t, runClient(c, input, io.Discard)) - if err == nil || !strings.Contains(err.Error(), "event stream establishment") { - t.Fatal(err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "event stream establishment"), "%v", err) live.mu.Lock() defer live.mu.Unlock() for _, route := range live.calls { - if strings.Contains(route, "configuration") || strings.Contains(route, "hydration") || strings.Contains(route, "sessions?") { - t.Fatalf("state read before SSE: %s", route) - } + testassert.Require(t, !strings.Contains(route, "configuration") && !strings.Contains(route, "hydration") && !strings.Contains(route, "sessions?"), "state read before SSE: %s", route) } } @@ -344,14 +318,10 @@ func TestRunBlockedOutputSSEOverflowRepairsBoundedQueue(t *testing.T) { } barrier(t, live.streams) // actual second establishment, not polling a counter c.mu.Lock() - if cap(c.events) != 256 || len(c.events) > 256 { - t.Fatal("unbounded event sink") - } + testassert.Require(t, cap(c.events) == 256 && len(c.events) <= 256, "unbounded event sink") c.mu.Unlock() write.Close() - if err := receive(t, done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, receive(t, done)) assertNoImplicitControls(t, live) } @@ -392,18 +362,15 @@ func TestRunExplicitInterruptStopAndChildCannotSubmit(t *testing.T) { frameContaining(t, screen, `"used_tokens"`) c.handleSignal(syscall.SIGTERM, int(input.Fd())) var exit ExitError - if err := receive(t, done); !errors.As(err, &exit) || exit.Code != 130 { - t.Fatal(err) + { + err := receive(t, done) + testassert.Require(t, errors.As(err, &exit) && exit.Code == 130, "%v", err) } live.mu.Lock() defer live.mu.Unlock() for _, call := range live.calls { - if strings.Contains(call, "/submit") { - t.Fatal("child input submitted work") - } - if strings.Contains(call, "/stop") && !strings.Contains(call, rootA) { - t.Fatal("Stop addressed inspector rather than root") - } + testassert.Require(t, !strings.Contains(call, "/submit"), "child input submitted work") + testassert.Require(t, !strings.Contains(call, "/stop") || strings.Contains(call, rootA), "Stop addressed inspector rather than root") } } @@ -421,8 +388,9 @@ func TestActualInputRegularQueuedResetAndEOFDoesNotControlOwner(t *testing.T) { return false } var body protocol.SubmitRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - t.Error(err) + { + err := json.NewDecoder(r.Body).Decode(&body) + testassert.Check(t, err == nil, "%v", err) } submitted <- body ownerMu.Lock() @@ -451,24 +419,18 @@ func TestActualInputRegularQueuedResetAndEOFDoesNotControlOwner(t *testing.T) { _, _ = write.Write([]byte(text + "\r")) body := receive(t, submitted) barrier(t, reads) - if body.Mode != mode || contentText(body.Content) != text || len(body.OperationId) != 32 || seen[body.OperationId] { - t.Fatalf("wrong actual input translation: %#v", body) - } + testassert.Require(t, body.Mode == mode && contentText(body.Content) == text && len(body.OperationId) == 32 && !seen[body.OperationId], "wrong actual input translation: %#v", body) seen[body.OperationId] = true } write.Close() - if err := receive(t, done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, receive(t, done)) assertNoImplicitControls(t, live) // The scripted owner retains all three delivered caller identities after // this client leaves. Runtime admission/draining semantics remain at their // existing producer owners, rather than a second terminal backend suite. ownerMu.Lock() defer ownerMu.Unlock() - if len(ownerIDs) != 3 { - t.Fatal("exit discarded accepted owner work") - } + testassert.Require(t, len(ownerIDs) == 3, "exit discarded accepted owner work") } func TestIdleKeyboardCtrlCAndCtrlDExitZero(t *testing.T) { @@ -481,9 +443,7 @@ func TestIdleKeyboardCtrlCAndCtrlDExitZero(t *testing.T) { done := runClient(c, input, screen) frameContaining(t, screen, "canonical clipboard") _, _ = write.Write([]byte{key}) - if err := receive(t, done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, receive(t, done)) assertNoImplicitControls(t, live) }) } diff --git a/internal/cliclient/pty_linux_test.go b/internal/cliclient/pty_linux_test.go index 5b5965e5..7e75e3af 100644 --- a/internal/cliclient/pty_linux_test.go +++ b/internal/cliclient/pty_linux_test.go @@ -12,44 +12,31 @@ import ( "syscall" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "golang.org/x/sys/unix" ) func pty(t *testing.T) (*os.File, *os.File, *unix.Termios) { t.Helper() fd, err := unix.Open("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY, 0) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) master := os.NewFile(uintptr(fd), "ptmx") t.Cleanup(func() { master.Close() }) - if err := unix.IoctlSetPointerInt(fd, unix.TIOCSPTLCK, 0); err != nil { - t.Fatal(err) - } + testassert.NoError(t, unix.IoctlSetPointerInt(fd, unix.TIOCSPTLCK, 0)) number, err := unix.IoctlGetInt(fd, unix.TIOCGPTN) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) slave, err := os.OpenFile(fmt.Sprintf("/dev/pts/%d", number), os.O_RDWR|unix.O_NOCTTY, 0) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) t.Cleanup(func() { slave.Close() }) old, err := unix.IoctlGetTermios(int(slave.Fd()), unix.TCGETS) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return master, slave, old } func rawRestored(t *testing.T, slave *os.File, old *unix.Termios) { t.Helper() now, err := unix.IoctlGetTermios(int(slave.Fd()), unix.TCGETS) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(now, old) { - t.Fatalf("raw mode not restored: before %#v after %#v", old, now) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(now, old), "raw mode not restored: before %#v after %#v", old, now) } func TestPTYRawRestorationNormalEOFAndActualSignal(t *testing.T) { for _, exit := range []string{"normal", "EOF", "signal"} { @@ -61,9 +48,7 @@ func TestPTYRawRestorationNormalEOFAndActualSignal(t *testing.T) { done := runClient(c, slave, screen) frameContaining(t, screen, "canonical clipboard") now, err := unix.IoctlGetTermios(int(slave.Fd()), unix.TCGETS) - if err != nil || now.Lflag&unix.ICANON != 0 { - t.Fatal("Run did not establish raw mode") - } + testassert.Require(t, err == nil && now.Lflag&unix.ICANON == 0, "Run did not establish raw mode") switch exit { case "normal": _, _ = master.Write([]byte("/exit\r")) @@ -74,23 +59,17 @@ func TestPTYRawRestorationNormalEOFAndActualSignal(t *testing.T) { // the descriptor remains usable. The remaining raw flags must // still be restored by Run, not by this input stimulus. now.Lflag |= unix.ICANON - if err := unix.IoctlSetTermios(int(slave.Fd()), unix.TCSETS, now); err != nil { - t.Fatal(err) - } + testassert.NoError(t, unix.IoctlSetTermios(int(slave.Fd()), unix.TCSETS, now)) // Complete the already parked raw read with one byte, then // supply an empty canonical record to the next native read. _, _ = master.Write([]byte{'x', old.Cc[unix.VEOF], old.Cc[unix.VEOF]}) case "signal": - if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil { - t.Fatal(err) - } + testassert.NoError(t, syscall.Kill(os.Getpid(), syscall.SIGTERM)) } err = receive(t, done) if exit == "signal" { var e ExitError - if !errors.As(err, &e) || e.Code != 130 { - t.Fatal(err) - } + testassert.Require(t, errors.As(err, &e) && e.Code == 130, "%v", err) } else if err != nil { t.Fatal(err) } @@ -114,12 +93,11 @@ func TestPTYInputPressureSignalReleasesAndRestores(t *testing.T) { barrier(t, blocked.entered) go func() { _, _ = master.Write([]byte(strings.Repeat("x", 8192))) }() barrierUntilFull(t, c) - if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil { - t.Fatal(err) - } + testassert.NoError(t, syscall.Kill(os.Getpid(), syscall.SIGTERM)) var exit ExitError - if err := receive(t, done); !errors.As(err, &exit) || exit.Code != 130 { - t.Fatal(err) + { + err := receive(t, done) + testassert.Require(t, errors.As(err, &exit) && exit.Code == 130, "%v", err) } rawRestored(t, slave, old) assertNoImplicitControls(t, live) @@ -145,18 +123,15 @@ func TestPTYBlockedCopyIdleActiveSIGINTRestoresWithoutWriterJoin(t *testing.T) { barrier(t, blocked.ready) _, _ = master.Write([]byte("/copy\r")) barrier(t, blocked.entered) - if err := syscall.Kill(os.Getpid(), syscall.SIGINT); err != nil { - t.Fatal(err) - } + testassert.NoError(t, syscall.Kill(os.Getpid(), syscall.SIGINT)) if active { barrier(t, interrupt) - if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil { - t.Fatal(err) - } + testassert.NoError(t, syscall.Kill(os.Getpid(), syscall.SIGTERM)) } var exit ExitError - if err := receive(t, done); !errors.As(err, &exit) || exit.Code != 130 { - t.Fatal(err) + { + err := receive(t, done) + testassert.Require(t, errors.As(err, &exit) && exit.Code == 130, "%v", err) } rawRestored(t, slave, old) }) diff --git a/internal/cliclient/reader_test.go b/internal/cliclient/reader_test.go index c53926bf..c56a9e4e 100644 --- a/internal/cliclient/reader_test.go +++ b/internal/cliclient/reader_test.go @@ -19,6 +19,7 @@ import ( "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/clientview" "github.com/MMinasyan/lightcode/internal/terminal" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -109,35 +110,33 @@ func discovery(t *testing.T, handler http.Handler) (clienthost.Options, *client. server := httptest.NewServer(handler) t.Cleanup(server.Close) dir := t.TempDir() - raw, _ := json.Marshal(map[string]string{"endpoint": server.URL, "instance_id": instance, "protocol_version": string(protocol.N2), "credential": strings.Repeat("1", 64)}) path := filepath.Join(dir, "runtime.json") - if err := os.WriteFile(path, raw, 0600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, discoveryRecord(server.URL, instance), 0600)) conn, err := client.Connect(context.Background(), path) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return clienthost.Options{DataDir: dir}, conn } -func health(w http.ResponseWriter) { + +// discoveryRecord builds one private discovery document for the given owner. +func discoveryRecord(endpoint, owner string) []byte { + raw, _ := json.Marshal(map[string]string{"endpoint": endpoint, "instance_id": owner, "protocol_version": string(protocol.N2), "credential": strings.Repeat("1", 64)}) + return raw +} + +func health(w http.ResponseWriter, owner string) { w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(protocol.Health{InstanceId: instance, ProtocolVersion: protocol.N2}) + _ = json.NewEncoder(w).Encode(protocol.Health{InstanceId: owner, ProtocolVersion: protocol.N2}) } func fixture(t *testing.T) (*CLI, *scriptedTransport) { t.Helper() _, conn := discovery(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/v1/health" { - t.Errorf("unexpected discovery %s", r.URL.Path) - } - health(w) + testassert.Check(t, r.URL.Path == "/v1/health", "unexpected discovery %s", r.URL.Path) + health(w, instance) })) transport := &scriptedTransport{requests: make(chan request, 32)} generated, ok := conn.ClientWithResponses.ClientInterface.(*protocol.Client) - if !ok { - t.Fatal("connection does not own the concrete generated client") - } + testassert.Require(t, ok, "connection does not own the concrete generated client") generated.Client = &http.Client{Transport: transport} c := New(Options{Workspace: "/workspace"}) c.ctx, c.cancel = context.WithCancel(context.Background()) @@ -216,9 +215,7 @@ func ids(t *testing.T, items []protocol.ConversationItem) []string { var ids []string for _, item := range items { id, err := clientview.ItemID(item) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) ids = append(ids, id) } return ids @@ -257,9 +254,7 @@ func TestReaderAnchoredHistory(t *testing.T) { t.Fatalf("cursor %q, want %q", req.req.URL.Query().Get("cursor"), want) } c.mu.Lock() - if !reflect.DeepEqual(ids(t, r.items), ids(t, cached)) { - t.Error("disconnected range published before walk completed") - } + testassert.Check(t, reflect.DeepEqual(ids(t, r.items), ids(t, cached)), "disconnected range published before walk completed") c.mu.Unlock() req.respond(page) } @@ -275,14 +270,10 @@ func TestReaderAnchoredHistory(t *testing.T) { if !reflect.DeepEqual(ids(t, r.items), want) { t.Fatalf("range %v", ids(t, r.items)) } - if r.cursor == nil || *r.cursor != "oldest-original" { - t.Fatalf("oldest edge changed: %v", r.cursor) - } + testassert.Require(t, r.cursor != nil && *r.cursor == "oldest-original", "oldest edge changed: %v", r.cursor) if test.unresolved { a, _ := r.items[9].AsAssistantItem() - if contentText(a.Content) != "settled" { - t.Fatal("unresolved value not replaced") - } + testassert.Require(t, contentText(a.Content) == "settled", "unresolved value not replaced") } }) } @@ -299,15 +290,11 @@ func TestReaderFloorOlderDisjointAndHints(t *testing.T) { done = r.chain.done c.mu.Unlock() older := receive(t, transport.requests) - if older.req.URL.Query().Get("cursor") != "real-oldest" { - t.Fatal("older cursor changed") - } + testassert.Require(t, older.req.URL.Query().Get("cursor") == "real-oldest", "older cursor changed") c.mu.Lock() r.dirty = true c.kickReaderLocked(r) - if done != r.chain.done { - t.Fatal("parallel reader") - } + testassert.Require(t, done == r.chain.done, "parallel reader") c.mu.Unlock() older.respond(protocol.HistoryPage{Items: inputs(1, 50), OlderCursor: pointer("edge"), SessionRevision: revision("6")}) receive(t, transport.requests).respond(hydration(rootA, "5", inputs(51, 100), nil)) @@ -319,9 +306,7 @@ func TestReaderFloorOlderDisjointAndHints(t *testing.T) { barrier(t, done) c.mu.Lock() defer c.mu.Unlock() - if len(r.items) != 100 || r.floor.DurableRevision != "6" || *r.cursor != "edge" || r.hydration.Usage.Context.UsedTokens != "9007199254740993" || r.hydration.Pending.Queued[0].OperationId != "queued-id" { - t.Fatalf("reader %#v", r) - } + testassert.Require(t, len(r.items) == 100 && r.floor.DurableRevision == "6" && *r.cursor == "edge" && r.hydration.Usage.Context.UsedTokens == "9007199254740993" && r.hydration.Pending.Queued[0].OperationId == "queued-id", "reader %#v", r) } func TestReaderNavigationSupersedesSuccessAndError(t *testing.T) { @@ -346,13 +331,12 @@ func TestReaderNavigationSupersedesSuccessAndError(t *testing.T) { } barrier(t, oldDone) c.mu.Lock() - if c.root != selected || c.rootID != rootB || old.hydration != nil { - t.Fatal("superseded reader published") - } + testassert.Require(t, c.root == selected && c.rootID == rootB && old.hydration == nil, "superseded reader published") c.mu.Unlock() for len(c.outputs) > 0 { - if d := <-c.outputs; strings.Contains(d.text, "late error") { - t.Fatal("superseded error printed") + { + d := <-c.outputs + testassert.Require(t, !strings.Contains(d.text, "late error"), "superseded error printed") } } }) @@ -428,7 +412,5 @@ func TestRootResetDisposesInspectorAndSameRootIntent(t *testing.T) { barrier(t, childDone) c.mu.Lock() defer c.mu.Unlock() - if c.selection <= generation || c.root == oldRoot || c.child != nil || c.childID != "" || c.root.hydration.SessionRevision.DurableRevision != "2" { - t.Fatal("root reset failed to dispose both readers") - } + testassert.Require(t, c.selection > generation && c.root != oldRoot && c.child == nil && c.childID == "" && c.root.hydration.SessionRevision.DurableRevision == "2", "root reset failed to dispose both readers") } diff --git a/internal/cliclient/render_test.go b/internal/cliclient/render_test.go index 6fe6b59b..78accc7d 100644 --- a/internal/cliclient/render_test.go +++ b/internal/cliclient/render_test.go @@ -10,6 +10,7 @@ import ( "unicode/utf8" "github.com/MMinasyan/lightcode/internal/terminal" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -139,9 +140,7 @@ func TestActualWriterPromptErasureCanonicalReplacementAndPartialOutput(t *testin c.command("/runtime NotAnOperation {}") frameContaining(t, screen, "unknown operation") result := screen.text() - if strings.Count(result, "unsubmitted") != 1 || strings.Count(result, "input 1") != 1 || !strings.Contains(result, "unknown operation") { - t.Fatalf("prompt corrupted transcript/command output:\n%s", result) - } + testassert.Require(t, strings.Count(result, "unsubmitted") == 1 && strings.Count(result, "input 1") == 1 && strings.Contains(result, "unknown operation"), "prompt corrupted transcript/command output:\n%s", result) // A pending assistant becomes terminal under the SAME item identity. The // accepted reader assignment, not permanent ID suppression, refreshes the range. pending := assistant("same-item", "pending content", true) @@ -166,9 +165,7 @@ func TestActualWriterPromptErasureCanonicalReplacementAndPartialOutput(t *testin c.report(s, terminal.RenderWarningMsg(string(raw)), true) frameContaining(t, screen, "partial restore") result = screen.text() - if strings.Count(result, "unsubmitted") != 1 || !strings.Contains(result, "terminal content") || !strings.Contains(result, "actual tool result") || !strings.Contains(strings.ReplaceAll(result, "\n", ""), "one.go") || !strings.Contains(strings.ReplaceAll(result, "\n", ""), "two.go") { - t.Fatalf("resulting screen lost content:\n%s", result) - } + testassert.Require(t, strings.Count(result, "unsubmitted") == 1 && strings.Contains(result, "terminal content") && strings.Contains(result, "actual tool result") && strings.Contains(strings.ReplaceAll(result, "\n", ""), "one.go") && strings.Contains(strings.ReplaceAll(result, "\n", ""), "two.go"), "resulting screen lost content:\n%s", result) c.requestExit(nil) } @@ -185,46 +182,30 @@ func TestFrameFollowsAcceptedReaderAssignments(t *testing.T) { return c.frameLocked(content) } const refreshed = "Authoritative refreshed range" - if !strings.Contains(frame(""), refreshed) { - t.Fatal("initial accepted range not rendered") - } + testassert.Require(t, strings.Contains(frame(""), refreshed), "initial accepted range not rendered") press(c, terminal.KeyMsg{Rune: 'x'}) - if strings.Contains(frame(""), refreshed) { - t.Fatal("key-only redraw reprinted history") - } + testassert.Require(t, !strings.Contains(frame(""), refreshed), "key-only redraw reprinted history") c.acceptEvent(eventDelivery{c.conn, scopedProgress(rootA, "active", "provisional only", false)}) - if strings.Contains(frame(""), refreshed) { - t.Fatal("provisional-only redraw reprinted history") - } + testassert.Require(t, !strings.Contains(frame(""), refreshed), "provisional-only redraw reprinted history") done = c.refreshReader(r) receive(t, transport.requests).respond(h) barrier(t, done) - if !strings.Contains(frame(""), refreshed) { - t.Fatal("equal-body accepted assignment did not refresh the range") - } + testassert.Require(t, strings.Contains(frame(""), refreshed), "equal-body accepted assignment did not refresh the range") c.mu.Lock() r.pendingOlder = true c.kickReaderLocked(r) done = r.chain.done c.mu.Unlock() older := receive(t, transport.requests) - if older.req.URL.Query().Get("cursor") != "oldest" { - t.Fatal("render invalidation changed the oldest cursor") - } + testassert.Require(t, older.req.URL.Query().Get("cursor") == "oldest", "render invalidation changed the oldest cursor") older.respond(protocol.HistoryPage{SessionRevision: revision("1"), Items: inputs(1, 1)}) barrier(t, done) rendered := frame("") - if !strings.Contains(rendered, refreshed) || !strings.Contains(rendered, "input 1") || !strings.Contains(rendered, "input 2") { - t.Fatal("accepted older range not rendered") - } + testassert.Require(t, strings.Contains(rendered, refreshed) && strings.Contains(rendered, "input 1") && strings.Contains(rendered, "input 2"), "accepted older range not rendered") c.mu.Lock() - if r.assignmentVersion != 3 || r.floor.DurableRevision != "1" || r.cursor != nil { - t.Fatal("local assignments changed protocol revision or oldest-edge state") - } + testassert.Require(t, r.assignmentVersion == 3 && r.floor.DurableRevision == "1" && r.cursor == nil, "local assignments changed protocol revision or oldest-edge state") c.mu.Unlock() - if !strings.Contains(frame("\x1b[2J\x1b[3J\x1b[H"), refreshed) { - t.Fatal("clear did not redraw the accepted range") - } + testassert.Require(t, strings.Contains(frame("\x1b[2J\x1b[3J\x1b[H"), refreshed), "clear did not redraw the accepted range") } func TestActualWriterToolMetadataRawArgsAndCanonicalClipboard(t *testing.T) { @@ -246,15 +227,11 @@ func TestActualWriterToolMetadataRawArgsAndCanonicalClipboard(t *testing.T) { c.mu.Unlock() frameContaining(t, screen, "new metadata") result := screen.text() - if !strings.Contains(result, "raw.go") || strings.Contains(result, "normalized.go") || !strings.Contains(result, "old metadata") { - t.Fatalf("tool display used wrong arguments/metadata: %s", result) - } + testassert.Require(t, strings.Contains(result, "raw.go") && !strings.Contains(result, "normalized.go") && strings.Contains(result, "old metadata"), "tool display used wrong arguments/metadata: %s", result) c.command("/copy") osc := frameContaining(t, screen, "\x1b]52;") encoded := base64.StdEncoding.EncodeToString([]byte("canonical first\ncanonical second")) - if !strings.Contains(osc, "\x1b]52;c;"+encoded+"\a") { - t.Fatalf("not canonical OSC52: %q", osc) - } + testassert.Require(t, strings.Contains(osc, "\x1b]52;c;"+encoded+"\a"), "not canonical OSC52: %q", osc) c.requestExit(nil) } @@ -287,21 +264,15 @@ func TestActualWriterProvisionalBoundedNoncanonicalAndReset(t *testing.T) { frameContaining(t, screen, "first-position") c.events <- eventDelivery{c.conn, scopedProgress(rootA, "active", "repeated-position", true)} frameContaining(t, screen, "[Refusal]") - if strings.Count(screen.text(), "input 1") != 1 { - t.Fatal("provisional reprinted canonical history") - } + testassert.Require(t, strings.Count(screen.text(), "input 1") == 1, "provisional reprinted canonical history") c.mu.Lock() - if len(r.items) != 1 { - t.Fatal("progress created canonical items") - } + testassert.Require(t, len(r.items) == 1, "progress created canonical items") c.mu.Unlock() // Direct concrete event acceptance keeps the screen fixture small while // exercising the same bounded area used by the writer above. c.acceptEvent(eventDelivery{c.conn, scopedProgress(rootA, "active", strings.Repeat("é", 40000), false)}) c.mu.Lock() - if len(c.provisional) > 65536 || !utf8.ValidString(c.provisional) || !c.provisionalTruncated { - t.Fatal("provisional bound is not UTF8 safe") - } + testassert.Require(t, len(c.provisional) <= 65536 && utf8.ValidString(c.provisional) && c.provisionalTruncated, "provisional bound is not UTF8 safe") c.mu.Unlock() done = c.refreshReader(r) h.SessionRevision = revision("2") @@ -309,9 +280,7 @@ func TestActualWriterProvisionalBoundedNoncanonicalAndReset(t *testing.T) { receive(t, transport.requests).respond(h) barrier(t, done) c.mu.Lock() - if c.provisional != "" || c.provisionalTruncated { - t.Fatal("accepted hydration did not clear provisional") - } + testassert.Require(t, c.provisional == "" && !c.provisionalTruncated, "accepted hydration did not clear provisional") c.mu.Unlock() c.requestExit(nil) } @@ -341,29 +310,21 @@ func TestUsageWarningsOwnAndGlobalAndUnknownRuntime(t *testing.T) { c.command("/context") c.command("/warnings") text := outputs(c) - if !strings.Contains(text, "9007199254740993") || !strings.Contains(text, "18446744073709551615") || !strings.Contains(text, "own") || strings.Contains(text, "sibling") { - t.Fatal("usage/warnings lost exact subset") - } + testassert.Require(t, strings.Contains(text, "9007199254740993") && strings.Contains(text, "18446744073709551615") && strings.Contains(text, "own") && !strings.Contains(text, "sibling"), "usage/warnings lost exact subset") c.mu.Lock() c.root = nil c.mu.Unlock() c.command("/warnings") text = outputs(c) - if !strings.Contains(text, "global") || strings.Contains(text, "own") || strings.Contains(text, "sibling") { - t.Fatal("global-only indicator included owned groups") - } + testassert.Require(t, strings.Contains(text, "global") && !strings.Contains(text, "own") && !strings.Contains(text, "sibling"), "global-only indicator included owned groups") c.command("/runtime UnknownOperation {}") settleTasks(t, c) text = outputs(c) - if !strings.Contains(text, "unknown operation") || strings.Contains(text, "Outcome unknown") { - t.Fatalf("unknown /runtime classification: %s", text) - } + testassert.Require(t, strings.Contains(text, "unknown operation") && !strings.Contains(text, "Outcome unknown"), "unknown /runtime classification: %s", text) transport.mu.Lock() defer transport.mu.Unlock() for _, route := range transport.calls { - if strings.Contains(route, "UnknownOperation") { - t.Fatal("unknown runtime performed HTTP") - } + testassert.Require(t, !strings.Contains(route, "UnknownOperation"), "unknown runtime performed HTTP") } } @@ -398,20 +359,14 @@ func TestActualClipboardRefusalAndNoLoadedAssistant(t *testing.T) { c.command("/copy") if test.want == "" { frame := frameContaining(t, screen, "No loaded authoritative assistant") - if strings.Contains(frame, "\x1b]52;") { - t.Fatal("an absent or empty history wrote the clipboard") - } + testassert.Require(t, !strings.Contains(frame, "\x1b]52;"), "an absent or empty history wrote the clipboard") } else { frame := frameContaining(t, screen, "\x1b]52;") - if !strings.Contains(frame, test.want) { - t.Fatalf("copy did not skip the empty assistant: %q", frame) - } + testassert.Require(t, strings.Contains(frame, test.want), "copy did not skip the empty assistant: %q", frame) } transport.mu.Lock() for _, route := range transport.calls { - if strings.Contains(route, "/history") { - t.Fatal("copy guessed skipped history") - } + testassert.Require(t, !strings.Contains(route, "/history"), "copy guessed skipped history") } transport.mu.Unlock() c.requestExit(nil) @@ -435,9 +390,7 @@ func TestProgressTupleUnmatchedOperationRepairsWithoutReplay(t *testing.T) { } c.acceptEvent(eventDelivery{c.conn, tool(rootA, "first")}) c.mu.Lock() - if !strings.Contains(c.provisional, "same-call") || len(r.items) != 1 { - t.Fatal("matched tool hint not temporary activity") - } + testassert.Require(t, strings.Contains(c.provisional, "same-call") && len(r.items) == 1, "matched tool hint not temporary activity") c.mu.Unlock() done = c.refreshReader(r) h.SessionRevision = revision("2") @@ -448,27 +401,19 @@ func TestProgressTupleUnmatchedOperationRepairsWithoutReplay(t *testing.T) { repair := receive(t, transport.requests) c.mu.Lock() done = r.chain.done - if c.provisional != "" { - t.Fatal("old Operation call ID reused in new activity") - } + testassert.Require(t, c.provisional == "", "old Operation call ID reused in new activity") c.mu.Unlock() repair.respond(h) barrier(t, done) c.mu.Lock() - if c.provisional != "" || len(r.items) != 1 { - t.Fatal("unmatched hint replayed into canonical/provisional items") - } + testassert.Require(t, c.provisional == "" && len(r.items) == 1, "unmatched hint replayed into canonical/provisional items") c.mu.Unlock() c.acceptEvent(eventDelivery{c.conn, tool(rootB, "second")}) c.mu.Lock() - if r.chain != nil || c.provisional != "" { - t.Fatal("foreign Session tool hint dirtied selected Session") - } + testassert.Require(t, r.chain == nil && c.provisional == "", "foreign Session tool hint dirtied selected Session") c.mu.Unlock() c.acceptEvent(eventDelivery{c.conn, tool(rootA, "second")}) c.mu.Lock() defer c.mu.Unlock() - if !strings.Contains(c.provisional, "same-call") { - t.Fatal("same call ID under current Operation was suppressed") - } + testassert.Require(t, strings.Contains(c.provisional, "same-call"), "same call ID under current Operation was suppressed") } diff --git a/internal/clienthost/clienthost_test.go b/internal/clienthost/clienthost_test.go index 2cf4d217..a252d517 100644 --- a/internal/clienthost/clienthost_test.go +++ b/internal/clienthost/clienthost_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/clienthost" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestDefaultOptions pins the shared default resolution: the home data @@ -19,56 +20,30 @@ func TestDefaultOptions(t *testing.T) { t.Setenv("LIGHTCODE_CONFIG", "") options, err := clienthost.DefaultOptions() - if err != nil { - t.Fatalf("DefaultOptions: %v", err) - } + testassert.NoError(t, err) wantDataDir := filepath.Join(home, ".lightcode") - if options.DataDir != wantDataDir || !filepath.IsAbs(options.DataDir) { - t.Fatalf("DataDir = %q, want absolute %q", options.DataDir, wantDataDir) - } - if options.ConfigPath != filepath.Join(wantDataDir, "config.json") { - t.Fatalf("ConfigPath = %q, want the data directory's config.json", options.ConfigPath) - } + testassert.Require(t, options.DataDir == wantDataDir && filepath.IsAbs(options.DataDir) && options.ConfigPath == filepath.Join(wantDataDir, "config.json"), "DataDir = %q, want absolute %q; ConfigPath = %q, want the data directory's config.json", options.DataDir, wantDataDir, options.ConfigPath) executable, err := os.Executable() - if err != nil { - t.Fatalf("os.Executable: %v", err) - } - if options.Executable != executable { - t.Fatalf("Executable = %q, want the running executable %q", options.Executable, executable) - } - if len(options.PrefixArgs) != 0 { - t.Fatalf("PrefixArgs = %q, want none", options.PrefixArgs) - } + testassert.NoError(t, err) + testassert.Require(t, options.Executable == executable && len(options.PrefixArgs) == 0, "got %v, want %v; PrefixArgs = %q, want none", options.Executable, executable, options.PrefixArgs) configured := filepath.Join(home, "elsewhere", "custom.json") t.Setenv("LIGHTCODE_CONFIG", configured) options, err = clienthost.DefaultOptions() - if err != nil { - t.Fatalf("DefaultOptions with LIGHTCODE_CONFIG: %v", err) - } - if options.ConfigPath != configured { - t.Fatalf("ConfigPath = %q, want the configured %q", options.ConfigPath, configured) - } - if options.DataDir != wantDataDir { - t.Fatalf("LIGHTCODE_CONFIG relocated DataDir to %q", options.DataDir) - } + testassert.NoError(t, err) + testassert.Require(t, options.ConfigPath == configured && options.DataDir == wantDataDir, "got %v, want %v; got %v, want %v", options.ConfigPath, configured, options.DataDir, wantDataDir) relative := filepath.Join("relative", "config.json") t.Setenv("LIGHTCODE_CONFIG", relative) options, err = clienthost.DefaultOptions() - if err != nil { - t.Fatalf("DefaultOptions with a relative LIGHTCODE_CONFIG: %v", err) - } + testassert.NoError(t, err) wantConfigured, err := filepath.Abs(relative) - if err != nil { - t.Fatalf("filepath.Abs: %v", err) - } - if options.ConfigPath != wantConfigured { - t.Fatalf("relative ConfigPath = %q, want normalized %q", options.ConfigPath, wantConfigured) - } + testassert.NoError(t, err) + testassert.Equal(t, options.ConfigPath, wantConfigured) t.Setenv("HOME", "") - if _, err := clienthost.DefaultOptions(); err == nil { - t.Fatal("DefaultOptions without HOME reported success") + { + _, err := clienthost.DefaultOptions() + testassert.Require(t, err != nil, "DefaultOptions without HOME reported success") } } diff --git a/internal/clienthost/process_test.go b/internal/clienthost/process_test.go index bb757d99..903e361d 100644 --- a/internal/clienthost/process_test.go +++ b/internal/clienthost/process_test.go @@ -34,6 +34,7 @@ import ( "github.com/MMinasyan/lightcode/internal/atomicfs" "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/plugins/sqlite" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" ) @@ -352,9 +353,7 @@ func newSandbox(t *testing.T) *sandbox { dataDir := t.TempDir() dir := t.TempDir() configPath := filepath.Join(dataDir, "config.json") - if err := os.WriteFile(configPath, []byte("{}"), 0o600); err != nil { - t.Fatalf("prepare isolated config: %v", err) - } + testassert.NoError(t, os.WriteFile(configPath, []byte("{}"), 0o600)) t.Setenv("HOME", home) isolateBundledCredentials(t) s := &sandbox{ @@ -383,8 +382,9 @@ type launched struct { func (s *sandbox) launched(name string) *launched { s.t.Helper() dir := filepath.Join(s.dir, name) - if err := os.MkdirAll(dir, 0o700); err != nil { - s.t.Fatalf("prepare launched attempt directory: %v", err) + { + err := os.MkdirAll(dir, 0o700) + testassert.Require(s.t, err == nil, "prepare launched attempt directory: %v", err) } return &launched{ sandbox: s, dir: dir, @@ -438,8 +438,9 @@ type directChild struct { func (s *sandbox) startDirect(t *testing.T, role string) *directChild { t.Helper() dir := filepath.Join(s.dir, role) - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatalf("prepare %s helper directory: %v", role, err) + { + err := os.MkdirAll(dir, 0o700) + testassert.Require(t, err == nil, "prepare %s helper directory: %v", role, err) } c := &directChild{ t: t, role: role, @@ -465,8 +466,9 @@ func (s *sandbox) startDirect(t *testing.T, role string) *directChild { log := &lockedBuffer{} cmd.Stdout = log cmd.Stderr = log - if err := cmd.Start(); err != nil { - t.Fatalf("start %s helper: %v", role, err) + { + err := cmd.Start() + testassert.Require(t, err == nil, "start %s helper: %v", role, err) } c.cmd = cmd c.log = log @@ -482,9 +484,7 @@ func (s *sandbox) startDirect(t *testing.T, role string) *directChild { // stopAndJoin performs the managed stop-marker shutdown and joins the exit. func (c *directChild) stopAndJoin(t *testing.T) { t.Helper() - if err := os.WriteFile(c.stop, []byte("stop"), 0o600); err != nil { - t.Fatalf("write stop marker: %v", err) - } + testassert.NoError(t, os.WriteFile(c.stop, []byte("stop"), 0o600)) select { case <-c.done: case <-time.After(15 * time.Second): @@ -533,9 +533,7 @@ func (s *sandbox) reapLaunched() { for aliveProcess(process) && time.Now().Before(deadline) { time.Sleep(10 * time.Millisecond) } - if aliveProcess(process) { - s.t.Errorf("launched child %d survived cleanup", pid) - } + testassert.Check(s.t, !aliveProcess(process), "launched child %d survived cleanup", pid) } } @@ -602,9 +600,7 @@ func TestClientHostConcurrentStartup(t *testing.T) { for _, results := range []<-chan outcome{firstResults, secondResults} { select { case result := <-results: - if result.err != nil { - t.Fatalf("concurrent Connect: %v", result.err) - } + testassert.Require(t, result.err == nil, "concurrent Connect: %v", result.err) clients = append(clients, result.client) case <-time.After(70 * time.Second): t.Fatal("concurrent Connect did not return") @@ -619,9 +615,7 @@ func TestClientHostConcurrentStartup(t *testing.T) { t.Fatalf("client instance %q, record %q", clients[0].InstanceID(), record.InstanceID) } health := authenticatedHealth(t, record.Endpoint, record.Credential) - if health.InstanceId != record.InstanceID || health.ProtocolVersion != protocol.N2 { - t.Fatalf("authenticated health = %s/%s, want the record's instance and wire version", health.InstanceId, health.ProtocolVersion) - } + testassert.Require(t, health.InstanceId == record.InstanceID && health.ProtocolVersion == protocol.N2, "authenticated health = %s/%s, want the record's instance and wire version", health.InstanceId, health.ProtocolVersion) winners := 0 for _, l := range []*launched{first, second} { @@ -636,16 +630,13 @@ func TestClientHostConcurrentStartup(t *testing.T) { winners++ } } - if winners != 1 { - t.Fatalf("factory-opening contender attempts = %d, want exactly one", winners) - } + testassert.Equal(t, winners, 1) - if err := os.WriteFile(s.stop, []byte("stop"), 0o600); err != nil { - t.Fatalf("write stop marker: %v", err) - } + testassert.NoError(t, os.WriteFile(s.stop, []byte("stop"), 0o600)) awaitMarker(t, s.closed) - if _, err := os.Stat(recordPath); !os.IsNotExist(err) { - t.Fatalf("the record survived managed shutdown: %v", err) + { + _, err := os.Stat(recordPath) + testassert.Require(t, os.IsNotExist(err), "the record survived managed shutdown: %v", err) } } @@ -658,36 +649,28 @@ func TestClientHostIncompatibleIncumbent(t *testing.T) { incumbent := s.startDirect(t, roleIncompatible) shapedEndpoint := strings.TrimSpace(awaitMarker(t, incumbent.ready, incumbent)) originalEndpoint := strings.TrimSpace(awaitMarker(t, incumbent.endpoint, incumbent)) - if shapedEndpoint == originalEndpoint { - t.Fatal("the shaped health server did not replace the record endpoint") - } + testassert.Require(t, shapedEndpoint != originalEndpoint, "the shaped health server did not replace the record endpoint") recordPath := filepath.Join(s.dataDir, "runtime.json") record := readRecord(t, recordPath) - if record.Endpoint != shapedEndpoint { - t.Fatalf("record endpoint = %q, want the shaped %q", record.Endpoint, shapedEndpoint) - } + testassert.Equal(t, record.Endpoint, shapedEndpoint) before := readFile(t, recordPath) contender := s.launched("contender") ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) defer cancel() _, err := clienthost.Connect(ctx, contender.options(filepath.Join(s.dir, "no-such-executable"), roleOwner, false)) - if !errors.Is(err, client.ErrIncompatibleProtocol) { - t.Fatalf("Connect over an incompatible incumbent = %v, want ErrIncompatibleProtocol", err) - } - if err := ctx.Err(); err != nil { - t.Fatalf("Connect did not return immediately: %v", err) - } - if attempts := readAttemptFiles(t, contender.dir); len(attempts) != 0 { - t.Fatalf("an incompatible incumbent started %d contender attempts", len(attempts)) + testassert.Require(t, errors.Is(err, client.ErrIncompatibleProtocol), "Connect over an incompatible incumbent = %v, want ErrIncompatibleProtocol", err) + testassert.NoError(t, ctx.Err()) + { + attempts := readAttemptFiles(t, contender.dir) + testassert.Equal(t, len(attempts), 0) } - if after := readFile(t, recordPath); !bytes.Equal(after, before) { - t.Fatal("the incompatible record changed across the probe") + { + after := readFile(t, recordPath) + testassert.Require(t, bytes.Equal(after, before), "the incompatible record changed across the probe") } health := authenticatedHealth(t, originalEndpoint, record.Credential) - if health.InstanceId != record.InstanceID || health.ProtocolVersion != protocol.N2 { - t.Fatalf("the incumbent's real health = %s/%s", health.InstanceId, health.ProtocolVersion) - } + testassert.Require(t, health.InstanceId == record.InstanceID && health.ProtocolVersion == protocol.N2, "the incumbent's real health = %s/%s", health.InstanceId, health.ProtocolVersion) incumbent.stopAndJoin(t) } @@ -707,9 +690,7 @@ func TestClientHostReadinessExitAndWinner(t *testing.T) { // The contender loses the held lock and exits before any owner publishes. awaitLost(t, contender) - if err := os.WriteFile(incumbent.gate, []byte("publish"), 0o600); err != nil { - t.Fatalf("open the incumbent's publish gate: %v", err) - } + testassert.NoError(t, os.WriteFile(incumbent.gate, []byte("publish"), 0o600)) awaitMarker(t, incumbent.ready, incumbent) var res outcome @@ -718,16 +699,15 @@ func TestClientHostReadinessExitAndWinner(t *testing.T) { case <-time.After(60 * time.Second): t.Fatal("Connect did not return after the contender's pre-ready exit") } - if res.err != nil { - t.Fatalf("Connect after the contender's pre-ready exit = %v, want the winner", res.err) - } + testassert.Require(t, res.err == nil, "Connect after the contender's pre-ready exit = %v, want the winner", res.err) record := readRecord(t, filepath.Join(s.dataDir, "runtime.json")) if res.client.InstanceID() != record.InstanceID { t.Fatalf("Connect returned instance %q, winner %q", res.client.InstanceID(), record.InstanceID) } for _, path := range readAttemptFiles(t, contender.dir) { - if plugins := readFactoryMarker(t, contender.dir, attemptPID(t, path)); plugins != nil { - t.Fatalf("the losing contender opened factories %q", plugins) + { + plugins := readFactoryMarker(t, contender.dir, attemptPID(t, path)) + testassert.Require(t, plugins == nil, "the losing contender opened factories %q", plugins) } } incumbent.stopAndJoin(t) @@ -752,36 +732,23 @@ func TestClientHostCancellationDoesNotKillOwner(t *testing.T) { case <-time.After(30 * time.Second): t.Fatal("canceled Connect did not return") } - if !errors.Is(res.err, context.Canceled) { - t.Fatalf("canceled Connect = %v, want context.Canceled", res.err) - } - if res.client != nil { - t.Fatal("canceled Connect returned a client") - } + testassert.Require(t, errors.Is(res.err, context.Canceled), "canceled Connect = %v, want context.Canceled", res.err) + testassert.Require(t, res.client == nil, "canceled Connect returned a client") - if err := os.WriteFile(contender.gate, []byte("go"), 0o600); err != nil { - t.Fatalf("release the surviving contender: %v", err) - } + testassert.NoError(t, os.WriteFile(contender.gate, []byte("go"), 0o600)) endpoint := strings.TrimSpace(awaitMarker(t, contender.ready)) record := readRecord(t, filepath.Join(s.dataDir, "runtime.json")) - if record.Endpoint != endpoint { - t.Fatalf("record endpoint = %q, want the surviving owner %q", record.Endpoint, endpoint) - } + testassert.Equal(t, record.Endpoint, endpoint) health := authenticatedHealth(t, record.Endpoint, record.Credential) - if health.InstanceId != record.InstanceID || health.ProtocolVersion != protocol.N2 { - t.Fatalf("the surviving owner health = %s/%s", health.InstanceId, health.ProtocolVersion) - } + testassert.Require(t, health.InstanceId == record.InstanceID && health.ProtocolVersion == protocol.N2, "the surviving owner health = %s/%s", health.InstanceId, health.ProtocolVersion) pid := attemptPID(t, attempts[0]) - if !aliveProcessByPID(pid) { - t.Fatalf("the canceled Connect killed owner process %d", pid) - } - if plugins := readFactoryMarker(t, contender.dir, pid); plugins == nil { - t.Fatal("the surviving owner opened no factories") + testassert.Require(t, aliveProcessByPID(pid), "the canceled Connect killed owner process %d", pid) + { + plugins := readFactoryMarker(t, contender.dir, pid) + testassert.Require(t, plugins != nil, "the surviving owner opened no factories") } - if err := os.WriteFile(s.stop, []byte("stop"), 0o600); err != nil { - t.Fatalf("write stop marker: %v", err) - } + testassert.NoError(t, os.WriteFile(s.stop, []byte("stop"), 0o600)) awaitMarker(t, s.closed) awaitReaped(t, pid) } @@ -803,24 +770,20 @@ func TestClientHostHealthTimeoutPreservesHolder(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() _, err := clienthost.Connect(ctx, contender.options(os.Args[0], roleOwner, false)) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("Connect against a parked health holder = %v, want the caller deadline", err) - } + testassert.Require(t, errors.Is(err, context.DeadlineExceeded), "Connect against a parked health holder = %v, want the caller deadline", err) health := authenticatedHealth(t, originalEndpoint, record.Credential) - if health.InstanceId != record.InstanceID || health.ProtocolVersion != protocol.N2 { - t.Fatalf("the preserved holder health = %s/%s", health.InstanceId, health.ProtocolVersion) - } - if after := readFile(t, recordPath); !bytes.Equal(after, before) { - t.Fatal("the parked record changed across the failed probes") + testassert.Require(t, health.InstanceId == record.InstanceID && health.ProtocolVersion == protocol.N2, "the preserved holder health = %s/%s", health.InstanceId, health.ProtocolVersion) + { + after := readFile(t, recordPath) + testassert.Require(t, bytes.Equal(after, before), "the parked record changed across the failed probes") } contenders := readAttemptFiles(t, contender.dir) - if len(contenders) == 0 { - t.Fatal("the parked holder was never probed by a started contender") - } + testassert.Require(t, len(contenders) != 0, "the parked holder was never probed by a started contender") for _, path := range contenders { - if plugins := readFactoryMarker(t, contender.dir, attemptPID(t, path)); plugins != nil { - t.Fatalf("a parked-health contender opened factories %q", plugins) + { + plugins := readFactoryMarker(t, contender.dir, attemptPID(t, path)) + testassert.Require(t, plugins == nil, "a parked-health contender opened factories %q", plugins) } } incumbent.stopAndJoin(t) @@ -844,22 +807,23 @@ func TestClientHostPreReadyFailureRestartsDiscovery(t *testing.T) { case <-time.After(30 * time.Second): t.Fatal("Connect did not return after cancellation") } - if !errors.Is(res.err, context.Canceled) { - t.Fatalf("canceled Connect = %v, want context.Canceled", res.err) - } + testassert.Require(t, errors.Is(res.err, context.Canceled), "canceled Connect = %v, want context.Canceled", res.err) for _, path := range attempts { if tail := attemptTail(t, path); !slices.Equal(tail, s.wantTail()) { t.Fatalf("failed contender argument tail = %q, want %q", tail, s.wantTail()) } - if plugins := readFactoryMarker(t, failures.dir, attemptPID(t, path)); plugins != nil { - t.Fatalf("a failing contender opened factories %q", plugins) + { + plugins := readFactoryMarker(t, failures.dir, attemptPID(t, path)) + testassert.Require(t, plugins == nil, "a failing contender opened factories %q", plugins) } } - if _, err := os.Stat(filepath.Join(s.dataDir, "runtime.json")); !os.IsNotExist(err) { - t.Fatalf("a failing contender published discovery: %v", err) + { + _, err := os.Stat(filepath.Join(s.dataDir, "runtime.json")) + testassert.Require(t, os.IsNotExist(err), "a failing contender published discovery: %v", err) } - if _, err := os.Stat(filepath.Join(s.dataDir, "lightcode.db")); !os.IsNotExist(err) { - t.Fatalf("a failing contender opened storage: %v", err) + { + _, err := os.Stat(filepath.Join(s.dataDir, "lightcode.db")) + testassert.Require(t, os.IsNotExist(err), "a failing contender opened storage: %v", err) } } @@ -874,17 +838,11 @@ func TestClientHostLaunchError(t *testing.T) { ConfigPath: s.configPath, Executable: missing, }) - if err == nil { - t.Fatal("Connect with a missing runtime executable reported success") - } - if errors.Is(err, client.ErrNoRuntime) || errors.Is(err, client.ErrIncompatibleProtocol) { - t.Fatalf("launch failure classified as a probe outcome: %v", err) - } - if !errors.Is(err, os.ErrNotExist) { - t.Fatalf("launch error = %v, want the missing executable reported", err) - } - if _, err := os.Stat(filepath.Join(s.dataDir, "runtime.json")); !os.IsNotExist(err) { - t.Fatalf("launch failure left discovery state: %v", err) + testassert.Require(t, err != nil, "Connect with a missing runtime executable reported success") + testassert.Require(t, !errors.Is(err, client.ErrNoRuntime) && !errors.Is(err, client.ErrIncompatibleProtocol) && errors.Is(err, os.ErrNotExist), "launch failure classified as a probe outcome: %v; launch error = %v, want the missing executable reported", err, err) + { + _, err := os.Stat(filepath.Join(s.dataDir, "runtime.json")) + testassert.Require(t, os.IsNotExist(err), "launch failure left discovery state: %v", err) } } @@ -903,9 +861,7 @@ type recordView struct { func readRecord(t *testing.T, path string) recordView { t.Helper() var record recordView - if err := json.Unmarshal(readFile(t, path), &record); err != nil { - t.Fatalf("decode discovery record: %v", err) - } + testassert.NoError(t, json.Unmarshal(readFile(t, path), &record)) return record } @@ -913,9 +869,7 @@ func readRecord(t *testing.T, path string) recordView { func readFile(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s: %v", path, err) - } + testassert.Require(t, err == nil, "read %s: %v", path, err) return data } @@ -923,25 +877,18 @@ func readFile(t *testing.T, path string) []byte { func authenticatedHealth(t *testing.T, endpoint, credential string) protocol.Health { t.Helper() req, err := http.NewRequest(http.MethodGet, endpoint+"/v1/health", nil) - if err != nil { - t.Fatalf("build health request: %v", err) - } + testassert.NoError(t, err) req.Header.Set("Authorization", "Bearer "+credential) resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req) - if err != nil { - t.Fatalf("authenticated health: %v", err) - } + testassert.NoError(t, err) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("read health body: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("authenticated health = %d body=%q", resp.StatusCode, body) - } + testassert.NoError(t, err) + testassert.Require(t, resp.StatusCode == http.StatusOK, "authenticated health = %d body=%q", resp.StatusCode, body) var health protocol.Health - if err := json.Unmarshal(body, &health); err != nil { - t.Fatalf("decode health body %q: %v", body, err) + { + err := json.Unmarshal(body, &health) + testassert.Require(t, err == nil, "decode health body %q: %v", body, err) } return health } @@ -955,9 +902,7 @@ func awaitMarker(t *testing.T, path string, children ...*directChild) string { if err == nil { return string(data) } - if !os.IsNotExist(err) { - t.Fatalf("read marker %s: %v", path, err) - } + testassert.Require(t, os.IsNotExist(err), "read marker %s: %v", path, err) time.Sleep(10 * time.Millisecond) } for _, child := range children { @@ -1020,8 +965,9 @@ func readAttemptFiles(t *testing.T, dir string) []string { func attemptTail(t *testing.T, path string) []string { t.Helper() var tail []string - if err := json.Unmarshal(readFile(t, path), &tail); err != nil { - t.Fatalf("decode attempt marker %s: %v", path, err) + { + err := json.Unmarshal(readFile(t, path), &tail) + testassert.Require(t, err == nil, "decode attempt marker %s: %v", path, err) } return tail } @@ -1030,9 +976,7 @@ func attemptTail(t *testing.T, path string) []string { func attemptPID(t *testing.T, path string) int { t.Helper() pid, err := strconv.Atoi(strings.TrimPrefix(filepath.Base(path), "attempt-")) - if err != nil { - t.Fatalf("attempt marker %s carries no PID: %v", path, err) - } + testassert.Require(t, err == nil, "attempt marker %s carries no PID: %v", path, err) return pid } @@ -1045,9 +989,7 @@ func readFactoryMarker(t *testing.T, dir string, pid int) []string { if os.IsNotExist(err) { return nil } - if err != nil { - t.Fatalf("read factory marker %s: %v", path, err) - } + testassert.Require(t, err == nil, "read factory marker %s: %v", path, err) var plugins []string for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { if line != "" { @@ -1079,9 +1021,7 @@ func awaitReaped(t *testing.T, pid int) { for aliveProcessByPID(pid) && time.Now().Before(deadline) { time.Sleep(10 * time.Millisecond) } - if aliveProcessByPID(pid) { - t.Fatalf("helper %d was never reaped", pid) - } + testassert.Require(t, !aliveProcessByPID(pid), "helper %d was never reaped", pid) } // lockedBuffer collects one helper child's diagnostics safely. @@ -1109,9 +1049,7 @@ func isolateBundledCredentials(t *testing.T) { t.Helper() catalogDir := filepath.Join("..", "..", "internal", "catalog", "builtin") entries, err := os.ReadDir(catalogDir) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue diff --git a/internal/clienthost/runmodels_test.go b/internal/clienthost/runmodels_test.go index 10258a2d..ea535389 100644 --- a/internal/clienthost/runmodels_test.go +++ b/internal/clienthost/runmodels_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/clientops" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -32,9 +33,7 @@ func modelsEndpoint(t *testing.T, status int, body []byte) clienthost.Options { mu.Lock() defer mu.Unlock() requests = append(requests, r.Method+" "+r.URL.RequestURI()) - if r.Header.Get("Authorization") != "Bearer "+credential { - t.Error("request lacks discovery authentication") - } + testassert.Check(t, r.Header.Get("Authorization") == "Bearer "+credential, "request lacks discovery authentication") w.Header().Set("Content-Type", "application/json") switch r.URL.Path { case "/v1/health": @@ -53,27 +52,19 @@ func modelsEndpoint(t *testing.T, status int, body []byte) clienthost.Options { mu.Lock() defer mu.Unlock() want := []string{"GET /v1/health", "GET /v1/configuration"} - if !reflect.DeepEqual(requests, want) { - t.Errorf("requests = %v, want exactly %v", requests, want) - } + testassert.Check(t, reflect.DeepEqual(requests, want), "requests = %v, want exactly %v", requests, want) }) dir := t.TempDir() record, err := json.Marshal(map[string]string{"endpoint": server.URL, "instance_id": instance, "protocol_version": string(protocol.N2), "credential": credential}) - if err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "runtime.json"), record, 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "runtime.json"), record, 0o600)) return clienthost.Options{DataDir: dir, Executable: filepath.Join(dir, "must-not-launch")} } func modelsConfiguration(t *testing.T, view protocol.ConfigurationView) clienthost.Options { t.Helper() body, err := json.Marshal(view) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return modelsEndpoint(t, http.StatusOK, body) } @@ -133,9 +124,7 @@ func TestRunModelsRetainedOutput(t *testing.T) { err := clienthost.RunModels(context.Background(), opts, tc.provider, tc.all, tc.asJSON, &out, &diagnostics) if tc.usage != "" { var usage *clienthost.UsageError - if !errors.As(err, &usage) || usage.ExitCode() != 2 || usage.Error() != tc.usage { - t.Fatalf("error = %v, want usage %q", err, tc.usage) - } + testassert.Require(t, errors.As(err, &usage) && usage.ExitCode() == 2 && usage.Error() == tc.usage, "error = %v, want usage %q", err, tc.usage) } else if err != nil { t.Fatal(err) } @@ -169,9 +158,7 @@ func TestRunModelsConfigurationFields(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { var out, diagnostics bytes.Buffer - if err := clienthost.RunModels(context.Background(), modelsConfiguration(t, view), "", tc.all, tc.asJSON, &out, &diagnostics); err != nil { - t.Fatal(err) - } + testassert.NoError(t, clienthost.RunModels(context.Background(), modelsConfiguration(t, view), "", tc.all, tc.asJSON, &out, &diagnostics)) if out.String() != tc.want || diagnostics.Len() != 0 { t.Fatalf("stdout=%q diagnostics=%q, want %q", out.String(), diagnostics.String(), tc.want) } @@ -179,18 +166,14 @@ func TestRunModelsConfigurationFields(t *testing.T) { } view.Providers[0].Models[0].Incomplete = true var allOut bytes.Buffer - if err := clienthost.RunModels(context.Background(), modelsConfiguration(t, view), "hidden", true, false, &allOut, io.Discard); err != nil { - t.Fatal(err) - } + testassert.NoError(t, clienthost.RunModels(context.Background(), modelsConfiguration(t, view), "hidden", true, false, &allOut, io.Discard)) if allOut.String() != "hidden/org/m\t9007199254740993\t0\tdefault,hidden,incomplete\n" { t.Fatalf("combined markers = %q", allOut.String()) } for _, agents := range [][]protocol.Agent{nil, {{Name: "primary"}}, {{Name: "secondary", Model: "fallback/z/slash"}}} { view.Agents = agents var out bytes.Buffer - if err := clienthost.RunModels(context.Background(), modelsConfiguration(t, view), "", false, false, &out, io.Discard); err != nil { - t.Fatal(err) - } + testassert.NoError(t, clienthost.RunModels(context.Background(), modelsConfiguration(t, view), "", false, false, &out, io.Discard)) if out.String() != "fallback/z/slash\t100\t0\t-\n" { t.Fatalf("unset primary output = %q", out.String()) } @@ -207,16 +190,12 @@ func TestRunModelsOpaqueOverflowFieldsRenders(t *testing.T) { raw := `{"configuration_revision":{"instance_id":"` + strings.Repeat("a", 32) + `","generation":"1"},"settings":{},"agents":[{"name":"primary","model":"prov/m"}],"providers":[{"id":"prov","connected":true,"extra_body":{"n":9007199254740993,"big":1e400},"models":[{"id":"m","name":"M","context_window":4096,"source":"user","extra_body":{"n":9007199254740993,"big":1e400}}]}]}` opts := modelsEndpoint(t, http.StatusOK, []byte(raw)) var out, diagnostics bytes.Buffer - if err := clienthost.RunModels(context.Background(), opts, "", false, false, &out, &diagnostics); err != nil { - t.Fatalf("RunModels: %v", err) - } + testassert.NoError(t, clienthost.RunModels(context.Background(), opts, "", false, false, &out, &diagnostics)) if out.String() != "prov/m\t4096\t0\tdefault\n" { t.Fatalf("stdout=%q diagnostics=%q, want the retained row despite opaque overflow fields", out.String(), diagnostics.String()) } var jsonOut bytes.Buffer - if err := clienthost.RunModels(context.Background(), modelsEndpoint(t, http.StatusOK, []byte(raw)), "", false, true, &jsonOut, io.Discard); err != nil { - t.Fatalf("RunModels --json: %v", err) - } + testassert.NoError(t, clienthost.RunModels(context.Background(), modelsEndpoint(t, http.StatusOK, []byte(raw)), "", false, true, &jsonOut, io.Discard)) if jsonOut.String() != `[{"ref":"prov/m","provider":"prov","providerName":"prov","model":"m","displayName":"M","contextWindow":4096,"maxOutputTokens":0,"hidden":false,"providerHidden":false,"incomplete":false,"default":true,"source":"user"}]`+"\n" { t.Fatalf("json stdout=%q", jsonOut.String()) } @@ -235,8 +214,9 @@ func TestRunModelsFailures(t *testing.T) { if err := clienthost.RunModels(context.Background(), modelsEndpoint(t, http.StatusOK, []byte(`{"providers":`)), "", false, false, &malformedOut, io.Discard); err == nil || malformedOut.Len() != 0 { t.Fatalf("malformed success: error=%v output=%q", err, malformedOut.String()) } - if err := clienthost.RunModels(context.Background(), modelsEndpoint(t, http.StatusNoContent, nil), "", false, false, io.Discard, io.Discard); err == nil { - t.Fatal("missing configuration body reported success") + { + err := clienthost.RunModels(context.Background(), modelsEndpoint(t, http.StatusNoContent, nil), "", false, false, io.Discard, io.Discard) + testassert.Require(t, err != nil, "missing configuration body reported success") } var out bytes.Buffer err := clienthost.RunModels(context.Background(), clienthost.Options{DataDir: t.TempDir(), Executable: "/does-not-exist"}, "", false, false, &out, io.Discard) diff --git a/internal/clientops/bind.go b/internal/clientops/bind.go new file mode 100644 index 00000000..fef10db4 --- /dev/null +++ b/internal/clientops/bind.go @@ -0,0 +1,231 @@ +package clientops + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sort" + + "github.com/MMinasyan/lightcode/protocol" +) + +// contentTypeJSON is the content type every declared request body uses. +const contentTypeJSON = "application/json" + +// ErrInvalidParams marks one local invocation-binding failure: the operation +// name is unknown or the supplied envelope does not match the operation's +// declared addressing. Callers distinguish these from transport failures. +var ErrInvalidParams = errors.New("invalid params") + +// invalidParams wraps one local binding failure with the caller's context. +func invalidParams(format string, args ...any) error { + return fmt.Errorf(format+": %w", append(args, ErrInvalidParams)...) +} + +// descriptor is one native operation in its generated form: the envelope +// member rules and one success status the schema declares, plus the generated +// SDK call that binds the operation's path and query arguments from the +// already-bound envelope and performs exactly that request. +type descriptor struct { + session memberRule + field memberRule + query memberRule + body memberRule + status int + call func(context.Context, protocol.ClientInterface, envelope) (*http.Response, error) +} + +// invokeGenerated looks up one native operation, binds its declared envelope +// members once, and calls its generated SDK function. The declared success +// status is meaningful only when the call succeeds; a local binding failure or +// a failed SDK call returns its ordinary error, and Invoke discards the +// response tuple on that error. +func invokeGenerated(ctx context.Context, c protocol.ClientInterface, operation string, params json.RawMessage) (*http.Response, int, error) { + declared, ok := operationDescriptors[operation] + if !ok { + return nil, 0, invalidParams("unknown operation %q", operation) + } + env, err := bindEnvelope(params, declared.session, declared.field, declared.query, declared.body) + if err != nil { + return nil, 0, err + } + response, err := declared.call(ctx, c, env) + return response, declared.status, err +} + +// OperationNames returns a fresh copy of the supported invocation names in +// sorted order. It is presentation-only (CLI help and the ACP advertisement); +// admission and dispatch own unknown-name rejection, never this list. +func OperationNames() []string { + names := make([]string, 0, len(operationDescriptors)) + for name := range operationDescriptors { + names = append(names, name) + } + sort.Strings(names) + return names +} + +// envelope is one bound invocation envelope: only the members the addressed +// operation declares are populated. +type envelope struct { + session string + field string + query json.RawMessage + body json.RawMessage +} + +// memberRule declares how one envelope member must appear for one operation: +// forbidden (the operation does not declare it), required, or declared but +// optional. An optional member is absent or present and non-null. +type memberRule int + +const ( + memberForbidden memberRule = iota + memberRequired + memberOptional +) + +// envelopeDoc is the envelope's raw declaration before membership checks. +type envelopeDoc struct { + SessionID json.RawMessage `json:"session_id"` + Field json.RawMessage `json:"field"` + Query json.RawMessage `json:"query"` + Body json.RawMessage `json:"body"` +} + +// bindEnvelope decodes one invocation envelope as a single document with +// unknown members rejected, then applies the declared-member rules: declared +// required members must be present and non-null, declared optional members +// are absent or present and non-null, and members the operation does not +// declare must be absent. Missing params is the empty envelope; a supplied +// null is invalid params like every other non-object form. +func bindEnvelope(params json.RawMessage, session, field, query, body memberRule) (envelope, error) { + if len(params) == 0 { + params = []byte("{}") + } + if isJSONNull(params) { + return envelope{}, invalidParams("invocation envelope must not be null") + } + decoder := json.NewDecoder(bytes.NewReader(params)) + decoder.DisallowUnknownFields() + decoder.UseNumber() + var doc envelopeDoc + if err := decoder.Decode(&doc); err != nil { + return envelope{}, invalidParams("invocation envelope: %v", err) + } + if _, err := decoder.Token(); err != io.EOF { + return envelope{}, invalidParams("invocation envelope carries trailing data") + } + var env envelope + rawSession, err := takeMember(doc.SessionID, "session_id", session) + if err != nil { + return envelope{}, err + } + if rawSession != nil { + if err := json.Unmarshal(rawSession, &env.session); err != nil { + return envelope{}, invalidParams("session_id: %v", err) + } + } + rawField, err := takeMember(doc.Field, "field", field) + if err != nil { + return envelope{}, err + } + if rawField != nil { + if err := json.Unmarshal(rawField, &env.field); err != nil { + return envelope{}, invalidParams("field: %v", err) + } + } + env.query, err = takeMember(doc.Query, "query", query) + if err != nil { + return envelope{}, err + } + if env.query != nil && !isJSONObject(env.query) { + return envelope{}, invalidParams("query must be one JSON object") + } + env.body, err = takeMember(doc.Body, "body", body) + if err != nil { + return envelope{}, err + } + if env.body != nil && !isJSONObject(env.body) { + return envelope{}, invalidParams("body must be one JSON object") + } + return env, nil +} + +// takeMember applies one declared-member rule to one envelope member. +func takeMember(raw json.RawMessage, name string, rule memberRule) (json.RawMessage, error) { + if rule == memberForbidden { + if len(raw) != 0 { + return nil, invalidParams("%s is not accepted for this operation", name) + } + return nil, nil + } + if len(raw) == 0 { + if rule == memberOptional { + return nil, nil + } + return nil, invalidParams("%s is required for this operation", name) + } + if isJSONNull(raw) { + return nil, invalidParams("%s must not be null", name) + } + return raw, nil +} + +// bindQuery binds one declared query object into its exact generated Params +// type: every required member must be present and non-null, every provided +// member must be non-null, and unknown members are rejected before typed +// decoding. Enum values pass through as their generated type and Runtime +// decides their semantic validity. +func bindQuery(raw json.RawMessage, required []string, target any) error { + var members map[string]json.RawMessage + if err := json.Unmarshal(raw, &members); err != nil { + return invalidParams("query: %v", err) + } + if members == nil { + return invalidParams("query must be one JSON object") + } + for _, name := range required { + if _, ok := members[name]; !ok { + return invalidParams("query is missing required member %q", name) + } + } + for name, value := range members { + if isJSONNull(value) { + return invalidParams("query member %q must not be null", name) + } + } + decoder := json.NewDecoder(bytes.NewReader(raw)) + decoder.DisallowUnknownFields() + decoder.UseNumber() + if err := decoder.Decode(target); err != nil { + return invalidParams("query: %v", err) + } + return nil +} + +// bindPath binds one envelope path string to its SDK-declared path type by +// JSON-marshaling the string and decoding it into the typed target. Decoding +// failure is the existing invalid-params class. +func bindPath(value string, target any) error { + encoded, _ := json.Marshal(value) + if err := json.Unmarshal(encoded, target); err != nil { + return invalidParams("path: %v", err) + } + return nil +} + +// isJSONNull reports whether one raw member is the null literal. +func isJSONNull(raw json.RawMessage) bool { + return bytes.Equal(bytes.TrimSpace(raw), []byte("null")) +} + +// isJSONObject reports whether one raw value is a JSON object document. +func isJSONObject(raw json.RawMessage) bool { + trimmed := bytes.TrimSpace(raw) + return len(trimmed) != 0 && trimmed[0] == '{' +} diff --git a/internal/clientops/clientops.go b/internal/clientops/clientops.go index b74b0803..a8f7e861 100644 --- a/internal/clientops/clientops.go +++ b/internal/clientops/clientops.go @@ -5,7 +5,6 @@ package clientops import ( - "bytes" "context" "encoding/json" "errors" @@ -17,19 +16,6 @@ import ( "github.com/MMinasyan/lightcode/protocol" ) -// contentTypeJSON is the content type every declared request body uses. -const contentTypeJSON = "application/json" - -// ErrInvalidParams marks one local invocation-binding failure: the operation -// name is unknown or the supplied envelope does not match the operation's -// declared addressing. Callers distinguish these from transport failures. -var ErrInvalidParams = errors.New("invalid params") - -// invalidParams wraps one local binding failure with the caller's context. -func invalidParams(format string, args ...any) error { - return fmt.Errorf(format+": %w", append(args, ErrInvalidParams)...) -} - // ResponseError is one received Runtime error or partial code-revert result. // Payload is always the generated Error; Partial carries the complete // generated result when the Runtime answered a code-revert call with one. @@ -110,13 +96,6 @@ func decodeError(raw json.RawMessage) (protocol.Error, error) { return payload, nil } -// OperationNames returns a fresh copy of the supported invocation names in -// sorted order. It is presentation-only (CLI help and the ACP advertisement); -// admission and dispatch own unknown-name rejection, never this list. -func OperationNames() []string { - return append([]string(nil), operationNames...) -} - // ErrInvalidResponse marks one received unusable response: a failed body // read, a malformed response, or an unexpected success status. This is the // invalid-delivery meaning, not a Runtime error code — receiving an unusable @@ -137,7 +116,7 @@ var ErrInvalidResponse = errors.New("invalid runtime response") // status, no body and its ordinary error; once an HTTP response exists, its // status and available body bytes are retained even on failure. func Invoke(ctx context.Context, c *client.Client, operation string, params json.RawMessage) (int, json.RawMessage, error) { - response, expected, err := invokeGenerated(ctx, c, operation, params) + response, expected, err := invokeGenerated(ctx, c.ClientWithResponses.ClientInterface, operation, params) if err != nil { return 0, nil, err } @@ -189,153 +168,3 @@ func receive(response *http.Response, expected int) (int, json.RawMessage, error } return status, body, fmt.Errorf("runtime returned status %d for an operation expecting %d: %w", status, expected, ErrInvalidResponse) } - -// envelope is one bound invocation envelope: only the members the addressed -// operation declares are populated. -type envelope struct { - session string - field string - query json.RawMessage - body json.RawMessage -} - -// memberRule declares how one envelope member must appear for one operation: -// forbidden (the operation does not declare it), required, or declared but -// optional. An optional member is absent or present and non-null. -type memberRule int - -const ( - memberForbidden memberRule = iota - memberRequired - memberOptional -) - -// envelopeDoc is the envelope's raw declaration before membership checks. -type envelopeDoc struct { - SessionID json.RawMessage `json:"session_id"` - Field json.RawMessage `json:"field"` - Query json.RawMessage `json:"query"` - Body json.RawMessage `json:"body"` -} - -// bindEnvelope decodes one invocation envelope as a single document with -// unknown members rejected, then applies the declared-member rules: declared -// required members must be present and non-null, declared optional members -// are absent or present and non-null, and members the operation does not -// declare must be absent. Missing params is the empty envelope; a supplied -// null is invalid params like every other non-object form. -func bindEnvelope(params json.RawMessage, session, field, query, body memberRule) (envelope, error) { - if len(params) == 0 { - params = []byte("{}") - } - if isJSONNull(params) { - return envelope{}, invalidParams("invocation envelope must not be null") - } - decoder := json.NewDecoder(bytes.NewReader(params)) - decoder.DisallowUnknownFields() - decoder.UseNumber() - var doc envelopeDoc - if err := decoder.Decode(&doc); err != nil { - return envelope{}, invalidParams("invocation envelope: %v", err) - } - if _, err := decoder.Token(); err != io.EOF { - return envelope{}, invalidParams("invocation envelope carries trailing data") - } - var env envelope - rawSession, err := takeMember(doc.SessionID, "session_id", session) - if err != nil { - return envelope{}, err - } - if rawSession != nil { - if err := json.Unmarshal(rawSession, &env.session); err != nil { - return envelope{}, invalidParams("session_id: %v", err) - } - } - rawField, err := takeMember(doc.Field, "field", field) - if err != nil { - return envelope{}, err - } - if rawField != nil { - if err := json.Unmarshal(rawField, &env.field); err != nil { - return envelope{}, invalidParams("field: %v", err) - } - } - env.query, err = takeMember(doc.Query, "query", query) - if err != nil { - return envelope{}, err - } - if env.query != nil && !isJSONObject(env.query) { - return envelope{}, invalidParams("query must be one JSON object") - } - env.body, err = takeMember(doc.Body, "body", body) - if err != nil { - return envelope{}, err - } - if env.body != nil && !isJSONObject(env.body) { - return envelope{}, invalidParams("body must be one JSON object") - } - return env, nil -} - -// takeMember applies one declared-member rule to one envelope member. -func takeMember(raw json.RawMessage, name string, rule memberRule) (json.RawMessage, error) { - if rule == memberForbidden { - if len(raw) != 0 { - return nil, invalidParams("%s is not accepted for this operation", name) - } - return nil, nil - } - if len(raw) == 0 { - if rule == memberOptional { - return nil, nil - } - return nil, invalidParams("%s is required for this operation", name) - } - if isJSONNull(raw) { - return nil, invalidParams("%s must not be null", name) - } - return raw, nil -} - -// bindQuery binds one declared query object into its exact generated Params -// type: every required member must be present and non-null, every provided -// member must be non-null, and unknown members are rejected before typed -// decoding. Enum values pass through as their generated type and Runtime -// decides their semantic validity. -func bindQuery(raw json.RawMessage, required []string, target any) error { - var members map[string]json.RawMessage - if err := json.Unmarshal(raw, &members); err != nil { - return invalidParams("query: %v", err) - } - if members == nil { - return invalidParams("query must be one JSON object") - } - for _, name := range required { - if _, ok := members[name]; !ok { - return invalidParams("query is missing required member %q", name) - } - } - for name, value := range members { - if isJSONNull(value) { - return invalidParams("query member %q must not be null", name) - } - } - decoder := json.NewDecoder(bytes.NewReader(raw)) - decoder.DisallowUnknownFields() - decoder.UseNumber() - if err := decoder.Decode(target); err != nil { - return invalidParams("query: %v", err) - } - return nil -} - -// isJSONNull reports whether one raw member is the null literal. -func isJSONNull(raw json.RawMessage) bool { - return bytes.Equal(bytes.TrimSpace(raw), []byte("null")) -} - -// isJSONObject reports whether one raw value is a JSON object document. -func isJSONObject(raw json.RawMessage) bool { - trimmed := bytes.TrimSpace(raw) - return len(trimmed) != 0 && trimmed[0] == '{' -} diff --git a/internal/clientops/clientops_test.go b/internal/clientops/clientops_test.go index e18fd2f2..5c9239d6 100644 --- a/internal/clientops/clientops_test.go +++ b/internal/clientops/clientops_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/client" "github.com/MMinasyan/lightcode/internal/clientops" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -63,9 +64,7 @@ func (d *scriptDoer) Do(request *http.Request) (*http.Response, error) { func (d *scriptDoer) one(t *testing.T) scriptCall { t.Helper() - if len(d.calls) != 1 { - t.Fatalf("transport received %d requests, want exactly one", len(d.calls)) - } + testassert.Equal(t, len(d.calls), 1) return d.calls[0] } @@ -83,9 +82,7 @@ func newScriptFixture(t *testing.T) (*client.Client, *scriptDoer) { t.Helper() doer := &scriptDoer{} generated, err := protocol.NewClientWithResponses("http://runtime.invalid", protocol.WithHTTPClient(doer)) - if err != nil { - t.Fatalf("NewClientWithResponses: %v", err) - } + testassert.NoError(t, err) return &client.Client{ClientWithResponses: generated}, doer } @@ -406,42 +403,24 @@ func TestCallDispatchesEveryOperation(t *testing.T) { return scriptedResponse(tc.status, tc.response), nil } result, err := clientops.Call(context.Background(), c, tc.operation, json.RawMessage(tc.params)) - if err != nil { - t.Fatalf("Call(%s) = %v", tc.operation, err) - } + testassert.Require(t, err == nil, "Call(%s) = %v", tc.operation, err) call := doer.one(t) - if call.method != tc.method { - t.Errorf("method = %q, want %q", call.method, tc.method) - } - if call.path != tc.path { - t.Errorf("path = %q, want %q", call.path, tc.path) - } - if call.rawQuery != tc.rawQuery { - t.Errorf("query = %q, want %q", call.rawQuery, tc.rawQuery) - } - if call.body != tc.body { - t.Errorf("body = %q, want %q", call.body, tc.body) - } - if tc.body != "" && call.contentType != "application/json" { - t.Errorf("content type = %q, want application/json", call.contentType) - } + testassert.Check(t, call.method == tc.method, "method = %q, want %q", call.method, tc.method) + testassert.Check(t, call.path == tc.path, "path = %q, want %q", call.path, tc.path) + testassert.Check(t, call.rawQuery == tc.rawQuery, "query = %q, want %q", call.rawQuery, tc.rawQuery) + testassert.Check(t, call.body == tc.body, "body = %q, want %q", call.body, tc.body) + testassert.Check(t, tc.body == "" || call.contentType == "application/json", "content type = %q, want application/json", call.contentType) want := tc.response if tc.status == http.StatusNoContent { want = "null" } - if string(result) != want { - t.Errorf("result = %s, want %s", result, want) - } + testassert.Check(t, string(result) == want, "result = %s, want %s", result, want) }) } names := clientops.OperationNames() - if len(names) != len(seen) { - t.Fatalf("dispatch table covers %d of %d supported operations", len(seen), len(names)) - } + testassert.Equal(t, len(names), len(seen)) for _, name := range names { - if !seen[name] { - t.Errorf("supported operation %q has no dispatch case", name) - } + testassert.Check(t, seen[name], "supported operation %q has no dispatch case", name) } } @@ -454,12 +433,8 @@ func TestCallAcceptsEmptyEnvelopePositives(t *testing.T) { return scriptedResponse(http.StatusOK, `{"instance_id":"0123456789abcdef0123456789abcdef","protocol_version":"2"}`), nil } result, err := clientops.Call(context.Background(), c, "GetHealth", params) - if err != nil { - t.Fatalf("Call(GetHealth, %q) = %v", params, err) - } - if string(result) == "" { - t.Fatal("GetHealth returned no result") - } + testassert.Require(t, err == nil, "Call(GetHealth, %q) = %v", params, err) + testassert.Require(t, string(result) != "", "GetHealth returned no result") } } @@ -515,15 +490,9 @@ func TestCallRejectsForbiddenEnvelope(t *testing.T) { t.Run(tc.name, func(t *testing.T) { c, doer := newScriptFixture(t) result, err := clientops.Call(context.Background(), c, tc.operation, json.RawMessage(tc.params)) - if len(doer.calls) != 0 { - t.Fatalf("transport received %d requests for a rejected envelope", len(doer.calls)) - } - if err == nil { - t.Fatalf("Call(%s, %s) = %s, want a local invalid-params failure", tc.operation, tc.params, result) - } - if !errors.Is(err, clientops.ErrInvalidParams) { - t.Fatalf("error = %v, want clientops.ErrInvalidParams", err) - } + testassert.Equal(t, len(doer.calls), 0) + testassert.Require(t, err != nil, "Call(%s, %s) = %s, want a local invalid-params failure", tc.operation, tc.params, result) + testassert.Require(t, errors.Is(err, clientops.ErrInvalidParams), "error = %v, want clientops.ErrInvalidParams", err) }) } } @@ -532,13 +501,12 @@ func TestCallRejectsForbiddenEnvelope(t *testing.T) { // coverage lives in the invocation-table test. func TestOperationNames(t *testing.T) { names := clientops.OperationNames() - if len(names) == 0 { - t.Fatal("OperationNames() is empty") - } + testassert.Require(t, len(names) != 0, "OperationNames() is empty") first := names[0] names[0] = "Mutated" - if again := clientops.OperationNames(); again[0] != first { - t.Fatalf("OperationNames() returned shared state: %q", again[0]) + { + again := clientops.OperationNames() + testassert.Equal(t, again[0], first) } } @@ -557,12 +525,8 @@ func TestInvocationSurfaceMatchesSchema(t *testing.T) { methods := item.Operations() for _, method := range sortedKeys(methods) { operation := methods[method] - if operation.OperationID == "" { - t.Fatalf("%s %s: operation has no operationId", method, path) - } - if declared[operation.OperationID] || streamed[operation.OperationID] { - t.Fatalf("operation %q is declared twice", operation.OperationID) - } + testassert.Require(t, operation.OperationID != "", "%s %s: operation has no operationId", method, path) + testassert.Require(t, !declared[operation.OperationID] && !streamed[operation.OperationID], "operation %q is declared twice", operation.OperationID) if operationDeclaresStream(t, operation) { streamed[operation.OperationID] = true continue @@ -570,63 +534,42 @@ func TestInvocationSurfaceMatchesSchema(t *testing.T) { declared[operation.OperationID] = true } } - if len(declared) == 0 || len(streamed) == 0 { - t.Fatalf("schema declares %d native operations and %d event streams; both sets must be non-empty", len(declared), len(streamed)) - } + testassert.Require(t, len(declared) != 0 && len(streamed) != 0, "schema declares %d native operations and %d event streams; both sets must be non-empty", len(declared), len(streamed)) names := clientops.OperationNames() - if !sort.StringsAreSorted(names) { - t.Errorf("OperationNames() is not sorted: %v", names) - } + testassert.Check(t, sort.StringsAreSorted(names), "OperationNames() is not sorted: %v", names) advertised := map[string]bool{} for _, name := range names { - if !declared[name] { - t.Errorf("advertised operation %q is not a declared native operation", name) - } - if advertised[name] { - t.Errorf("advertised operation %q appears twice", name) - } + testassert.Check(t, declared[name], "advertised operation %q is not a declared native operation", name) + testassert.Check(t, !advertised[name], "advertised operation %q appears twice", name) advertised[name] = true } dispatched := map[string]bool{} for _, tc := range dispatchCases { - if !declared[tc.operation] { - t.Errorf("dispatch case %q names %q, which is not a declared native operation", tc.name, tc.operation) - } + testassert.Check(t, declared[tc.operation], "dispatch case %q names %q, which is not a declared native operation", tc.name, tc.operation) dispatched[tc.operation] = true } for name := range declared { - if !advertised[name] { - t.Errorf("declared native operation %q is not advertised", name) - } - if !dispatched[name] { - t.Errorf("declared native operation %q has no dispatch case", name) - } + testassert.Check(t, advertised[name], "declared native operation %q is not advertised", name) + testassert.Check(t, dispatched[name], "declared native operation %q has no dispatch case", name) } for name := range streamed { - if advertised[name] { - t.Errorf("declared event stream %q is advertised as a native operation", name) - } + testassert.Check(t, !advertised[name], "declared event stream %q is advertised as a native operation", name) } // The test-only native invocation contract is the second generated // artifact: its root must name exactly the schema's native operations and // carry no stream entry. contractData, err := os.ReadFile(filepath.Join("..", "..", "frontend", "src", "generated", "native-invocations.json")) - if err != nil { - t.Fatalf("reading the native invocation contract: %v", err) - } + testassert.NoError(t, err) var contract map[string]json.RawMessage - if err := json.Unmarshal(contractData, &contract); err != nil { - t.Fatalf("decoding the native invocation contract: %v", err) - } + testassert.NoError(t, json.Unmarshal(contractData, &contract)) for name := range declared { - if _, ok := contract[name]; !ok { - t.Errorf("declared native operation %q is missing from the native invocation contract", name) + { + _, ok := contract[name] + testassert.Check(t, ok, "declared native operation %q is missing from the native invocation contract", name) } } for name := range contract { - if !declared[name] { - t.Errorf("the native invocation contract names %q, which is not a declared native operation", name) - } + testassert.Check(t, declared[name], "the native invocation contract names %q, which is not a declared native operation", name) } } @@ -638,9 +581,7 @@ func operationDeclaresStream(t *testing.T, operation *openapi3.Operation) bool { if len(status) != 3 || status[0] != '2' { continue } - if response.Value == nil { - t.Fatalf("operation %s success status %s is unresolved", operation.OperationID, status) - } + testassert.Require(t, response.Value != nil, "operation %s success status %s is unresolved", operation.OperationID, status) if response.Value.Content["text/event-stream"] != nil { return true } @@ -654,12 +595,8 @@ func schemaDocument(t *testing.T) *openapi3.T { t.Helper() loader := openapi3.NewLoader() doc, err := loader.LoadFromFile(filepath.Join("..", "..", "protocol", "openapi.yaml")) - if err != nil { - t.Fatalf("loading the protocol schema: %v", err) - } - if err := doc.Validate(context.Background()); err != nil { - t.Fatalf("validating the protocol schema: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, doc.Validate(context.Background())) return doc } @@ -690,51 +627,27 @@ func TestInvokeMapsDeclaredErrors(t *testing.T) { const payload = `{"code":"conflict","message":"in use","session_id":"s1"}` status, raw, err := invokeRejection(t, payload) var responseErr *clientops.ResponseError - if !errors.As(err, &responseErr) { - t.Fatalf("error = %v, want *clientops.ResponseError", err) - } - if status != http.StatusConflict || string(raw) != payload { - t.Fatalf("Invoke = (%d, %q), want the received status and body", status, raw) - } - if responseErr.Status != http.StatusConflict { - t.Errorf("status = %d, want %d", responseErr.Status, http.StatusConflict) - } - if responseErr.Payload.Code != protocol.Conflict || responseErr.Payload.Message != "in use" { - t.Errorf("payload = %+v", responseErr.Payload) - } - if responseErr.Partial != nil { - t.Errorf("partial = %+v, want nil", responseErr.Partial) - } + testassert.Require(t, errors.As(err, &responseErr), "error = %v, want *clientops.ResponseError", err) + testassert.Require(t, status == http.StatusConflict && string(raw) == payload, "Invoke = (%d, %q), want the received status and body", status, raw) + testassert.Check(t, responseErr.Status == http.StatusConflict, "status = %d, want %d", responseErr.Status, http.StatusConflict) + testassert.Check(t, responseErr.Payload.Code == protocol.Conflict && responseErr.Payload.Message == "in use", "payload = %+v", responseErr.Payload) + testassert.Check(t, responseErr.Partial == nil, "partial = %+v, want nil", responseErr.Partial) if responseErr.Error() != "in use" { t.Errorf("Error() = %q, want the returned message", responseErr.Error()) } // Declared and malformed remain disjoint outcomes. - if errors.Is(err, clientops.ErrInvalidResponse) { - t.Fatalf("declared rejection wrapped ErrInvalidResponse: %v", err) - } + testassert.Require(t, !errors.Is(err, clientops.ErrInvalidResponse), "declared rejection wrapped ErrInvalidResponse: %v", err) }) t.Run("partial result", func(t *testing.T) { const payload = `{"error":{"code":"conflict","message":"partial restore"},"restored":["a.go"],"skipped":[{"path":"b.go","error":"denied"}]}` status, raw, err := invokeRejection(t, payload) var responseErr *clientops.ResponseError - if !errors.As(err, &responseErr) { - t.Fatalf("error = %v, want *clientops.ResponseError", err) - } - if status != http.StatusConflict || string(raw) != payload { - t.Fatalf("Invoke = (%d, %q), want the received status and body", status, raw) - } - if responseErr.Partial == nil { - t.Fatal("partial = nil, want the complete generated result") - } - if len(responseErr.Partial.Restored) != 1 || responseErr.Partial.Restored[0] != "a.go" { - t.Errorf("restored = %v", responseErr.Partial.Restored) - } - if len(responseErr.Partial.Skipped) != 1 || responseErr.Partial.Skipped[0].Path != "b.go" { - t.Errorf("skipped = %v", responseErr.Partial.Skipped) - } - if responseErr.Payload.Message != "partial restore" { - t.Errorf("payload = %+v", responseErr.Payload) - } + testassert.Require(t, errors.As(err, &responseErr), "error = %v, want *clientops.ResponseError", err) + testassert.Require(t, status == http.StatusConflict && string(raw) == payload, "Invoke = (%d, %q), want the received status and body", status, raw) + testassert.Require(t, responseErr.Partial != nil, "partial = nil, want the complete generated result") + testassert.Check(t, len(responseErr.Partial.Restored) == 1 && responseErr.Partial.Restored[0] == "a.go", "restored = %v", responseErr.Partial.Restored) + testassert.Check(t, len(responseErr.Partial.Skipped) == 1 && responseErr.Partial.Skipped[0].Path == "b.go", "skipped = %v", responseErr.Partial.Skipped) + testassert.Check(t, responseErr.Payload.Message == "partial restore", "payload = %+v", responseErr.Payload) if responseErr.Error() != "partial restore" { t.Errorf("Error() = %q", responseErr.Error()) } @@ -742,9 +655,7 @@ func TestInvokeMapsDeclaredErrors(t *testing.T) { t.Run("partial result with empty arrays", func(t *testing.T) { _, _, err := invokeRejection(t, `{"error":{"code":"conflict","message":"none"},"restored":[],"skipped":[]}`) var responseErr *clientops.ResponseError - if !errors.As(err, &responseErr) || responseErr.Partial == nil { - t.Fatalf("error = %v, want a partial result", err) - } + testassert.Require(t, errors.As(err, &responseErr) && responseErr.Partial != nil, "error = %v, want a partial result", err) }) } @@ -783,12 +694,8 @@ func TestInvokeRejectsMalformedRejectionBodies(t *testing.T) { return scriptedResponse(http.StatusConflict, tc.body), nil } status, raw, err := clientops.Invoke(context.Background(), c, "GetHealth", nil) - if err == nil { - t.Fatal("Invoke accepted a malformed body") - } - if status != http.StatusConflict || string(raw) != tc.body { - t.Fatalf("Invoke = (%d, %q), want the received status and body", status, raw) - } + testassert.Require(t, err != nil, "Invoke accepted a malformed body") + testassert.Require(t, status == http.StatusConflict && string(raw) == tc.body, "Invoke = (%d, %q), want the received status and body", status, raw) assertInvalidDelivery(t, err) }) } @@ -830,12 +737,8 @@ func TestCallResponseMappingAndTransportFailures(t *testing.T) { } _, err := clientops.Call(context.Background(), c, "GetSessionHydration", json.RawMessage(`{"session_id":"gone"}`)) var responseErr *clientops.ResponseError - if !errors.As(err, &responseErr) { - t.Fatalf("error = %v, want *clientops.ResponseError", err) - } - if responseErr.Status != http.StatusNotFound || responseErr.Error() != "no such session" { - t.Fatalf("response error = %+v", responseErr) - } + testassert.Require(t, errors.As(err, &responseErr), "error = %v, want *clientops.ResponseError", err) + testassert.Require(t, responseErr.Status == http.StatusNotFound && responseErr.Error() == "no such session", "response error = %+v", responseErr) }) t.Run("received partial result", func(t *testing.T) { c, doer := newScriptFixture(t) @@ -844,21 +747,15 @@ func TestCallResponseMappingAndTransportFailures(t *testing.T) { } _, err := clientops.Call(context.Background(), c, "RevertSessionCode", json.RawMessage(`{"session_id":"s1","body":{}}`)) var responseErr *clientops.ResponseError - if !errors.As(err, &responseErr) || responseErr.Partial == nil { - t.Fatalf("error = %v, want a partial response error", err) - } - if len(responseErr.Partial.Restored) != 1 { - t.Fatalf("partial = %+v", responseErr.Partial) - } + testassert.Require(t, errors.As(err, &responseErr) && responseErr.Partial != nil, "error = %v, want a partial response error", err) + testassert.Require(t, len(responseErr.Partial.Restored) == 1, "partial = %+v", responseErr.Partial) }) t.Run("transport failure", func(t *testing.T) { c, doer := newScriptFixture(t) want := errors.New("dial failed") doer.respond = func(*http.Request) (*http.Response, error) { return nil, want } _, err := clientops.Call(context.Background(), c, "GetHealth", nil) - if !errors.Is(err, want) { - t.Fatalf("error = %v, want the transport error", err) - } + testassert.Require(t, errors.Is(err, want), "error = %v, want the transport error", err) assertTransportFailure(t, err) }) t.Run("raw success bytes are returned unremarshaled", func(t *testing.T) { @@ -868,12 +765,8 @@ func TestCallResponseMappingAndTransportFailures(t *testing.T) { return scriptedResponse(http.StatusOK, body), nil } result, err := clientops.Call(context.Background(), c, "GetHealth", nil) - if err != nil { - t.Fatalf("Call: %v", err) - } - if string(result) != body { - t.Fatalf("result = %s, want the exact response bytes %s", result, body) - } + testassert.NoError(t, err) + testassert.Require(t, string(result) == body, "result = %s, want the exact response bytes %s", result, body) }) } @@ -895,9 +788,7 @@ func TestInvokeRetainsStatusAndBodyOnFailure(t *testing.T) { }, nil } status, body, err := clientops.Invoke(context.Background(), c, "GetHealth", nil) - if status != http.StatusOK || string(body) != `{"partial":` { - t.Fatalf("Invoke = (%d, %q), want the received status and partial bytes", status, body) - } + testassert.Require(t, status == http.StatusOK && string(body) == `{"partial":`, "Invoke = (%d, %q), want the received status and partial bytes", status, body) assertInvalidDelivery(t, err) }) t.Run("malformed success body", func(t *testing.T) { @@ -906,9 +797,7 @@ func TestInvokeRetainsStatusAndBodyOnFailure(t *testing.T) { return scriptedResponse(http.StatusOK, "not json"), nil } status, body, err := clientops.Invoke(context.Background(), c, "GetHealth", nil) - if status != http.StatusOK || string(body) != "not json" { - t.Fatalf("Invoke = (%d, %q), want the received status and body", status, body) - } + testassert.Require(t, status == http.StatusOK && string(body) == "not json", "Invoke = (%d, %q), want the received status and body", status, body) assertInvalidDelivery(t, err) }) t.Run("unexpected success status", func(t *testing.T) { @@ -917,9 +806,7 @@ func TestInvokeRetainsStatusAndBodyOnFailure(t *testing.T) { return scriptedResponse(http.StatusCreated, `{}`), nil } status, body, err := clientops.Invoke(context.Background(), c, "GetHealth", nil) - if status != http.StatusCreated || string(body) != `{}` { - t.Fatalf("Invoke = (%d, %q), want the received status and body", status, body) - } + testassert.Require(t, status == http.StatusCreated && string(body) == `{}`, "Invoke = (%d, %q), want the received status and body", status, body) assertInvalidDelivery(t, err) }) t.Run("valid 204 returns its status and empty body", func(t *testing.T) { @@ -928,28 +815,20 @@ func TestInvokeRetainsStatusAndBodyOnFailure(t *testing.T) { return scriptedResponse(http.StatusNoContent, ""), nil } status, body, err := clientops.Invoke(context.Background(), c, "DeleteSession", json.RawMessage(`{"session_id":"s1"}`)) - if err != nil { - t.Fatalf("Invoke: %v", err) - } - if status != http.StatusNoContent || len(body) != 0 { - t.Fatalf("Invoke = (%d, %q), want the 204 status and empty body", status, body) - } + testassert.NoError(t, err) + testassert.Require(t, status == http.StatusNoContent && len(body) == 0, "Invoke = (%d, %q), want the 204 status and empty body", status, body) }) t.Run("local binding failure returns zero status and no body", func(t *testing.T) { c, _ := newScriptFixture(t) status, body, err := clientops.Invoke(context.Background(), c, "Nope", nil) - if status != 0 || body != nil || !errors.Is(err, clientops.ErrInvalidParams) { - t.Fatalf("Invoke = (%d, %q, %v), want the zero-status local binding failure", status, body, err) - } + testassert.Require(t, status == 0 && body == nil && errors.Is(err, clientops.ErrInvalidParams), "Invoke = (%d, %q, %v), want the zero-status local binding failure", status, body, err) }) t.Run("transport failure returns zero status and no body", func(t *testing.T) { c, doer := newScriptFixture(t) want := errors.New("dial failed") doer.respond = func(*http.Request) (*http.Response, error) { return nil, want } status, body, err := clientops.Invoke(context.Background(), c, "GetHealth", nil) - if status != 0 || body != nil || !errors.Is(err, want) { - t.Fatalf("Invoke = (%d, %q, %v), want the zero-status transport failure", status, body, err) - } + testassert.Require(t, status == 0 && body == nil && errors.Is(err, want), "Invoke = (%d, %q, %v), want the zero-status transport failure", status, body, err) }) } @@ -975,31 +854,14 @@ func (r *failingReader) Close() error { return nil } // ResponseError or a local binding failure. func assertInvalidDelivery(t *testing.T, err error) { t.Helper() - if !errors.Is(err, clientops.ErrInvalidResponse) { - t.Fatalf("error = %v, want ErrInvalidResponse", err) - } + testassert.Require(t, errors.Is(err, clientops.ErrInvalidResponse), "error = %v, want ErrInvalidResponse", err) var responseErr *clientops.ResponseError - if errors.As(err, &responseErr) { - t.Fatalf("invalid delivery mapped to a protocol error: %v", err) - } - if errors.Is(err, clientops.ErrInvalidParams) { - t.Fatalf("invalid delivery mapped to a local binding failure: %v", err) - } + testassert.Require(t, !errors.As(err, &responseErr) && !errors.Is(err, clientops.ErrInvalidParams), "invalid delivery mapped to a protocol error: %v; invalid delivery mapped to a local binding failure: %v", err, err) } func assertTransportFailure(t *testing.T, err error) { t.Helper() - if err == nil { - t.Fatal("want a failure") - } + testassert.Require(t, err != nil, "want a failure") var responseErr *clientops.ResponseError - if errors.As(err, &responseErr) { - t.Fatalf("transport failure mapped to a protocol error: %v", err) - } - if errors.Is(err, clientops.ErrInvalidParams) { - t.Fatalf("transport failure mapped to a local binding failure: %v", err) - } - if errors.Is(err, clientops.ErrInvalidResponse) { - t.Fatalf("transport failure mapped to an invalid delivery: %v", err) - } + testassert.Require(t, !errors.As(err, &responseErr) && !errors.Is(err, clientops.ErrInvalidParams) && !errors.Is(err, clientops.ErrInvalidResponse), "transport failure mapped to a protocol error: %v; transport failure mapped to a local binding failure: %v; transport failure mapped to an invalid delivery: %v", err, err, err) } diff --git a/internal/clientops/operations.gen.go b/internal/clientops/operations.gen.go index a4ff02c2..87f7408c 100644 --- a/internal/clientops/operations.gen.go +++ b/internal/clientops/operations.gen.go @@ -1,455 +1,434 @@ // Code generated by internal/protocolgen from protocol/openapi.yaml; DO NOT EDIT. // -// The native operation table and its dispatch are derived from the schema: -// operationNames is the sorted set of declared native operations and -// invokeGenerated binds each one's declared addressing before calling the -// matching generated client method. +// The native operation table is the SDK-resolved projection of the schema: each +// descriptor carries one operation's envelope member rules, its one numeric +// success status and the generated client call for its bound envelope. package clientops import ( "bytes" "context" - "encoding/json" "net/http" - "github.com/MMinasyan/lightcode/client" "github.com/MMinasyan/lightcode/protocol" ) -// operationNames is the schema's native operation set in sorted order: every -// declared operation whose success response is not the event stream. It is the -// sole authority for the initialize advertisement and for Call's own admission. -var operationNames = []string{ - "ArchiveSession", - "CompactSession", - "ConnectProvider", - "CreateProvider", - "CreateSession", - "DeleteProviderDetail", - "DeleteProviderModel", - "DeleteSession", - "DisconnectProvider", - "DiscoverProviderCandidates", - "DiscoverProviderModelCandidates", - "ForkSession", - "GetConfiguration", - "GetHealth", - "GetProviderDetail", - "GetRetainedCodeSnapshots", - "GetSessionCodeSnapshots", - "GetSessionHistory", - "GetSessionHydration", - "GetWarnings", - "InterruptSession", - "ListModels", - "ListProviderModels", - "ListProviders", - "ListSessions", - "ListWorkspaces", - "ReadWorkspaceFile", - "ReloadConfiguration", - "ReopenSession", - "ResetProviderField", - "ResetProviderModelField", - "RevertRetainedCode", - "RevertSessionCode", - "SetAgentTypeModel", - "SetSessionAgentType", - "StopSession", - "SubmitSession", - "UpdateConfigurationSettings", - "UpdateProviderDetail", - "UpdateProviderModel", -} - -// invokeGenerated binds one invocation envelope to the generated client -// method the named operation declares and returns that call's raw response -// beside the operation's declared success status. A local binding failure or a -// failure to obtain an HTTP response returns a nil response and the zero -// status; the caller owns response interpretation. -func invokeGenerated(ctx context.Context, c *client.Client, operation string, params json.RawMessage) (*http.Response, int, error) { - switch operation { - case "ArchiveSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.ArchiveSession(ctx, protocol.SessionID(env.session)) - return response, 200, callErr - - case "CompactSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.CompactSessionWithBody(ctx, protocol.SessionID(env.session), contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "ConnectProvider": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberRequired) - if err != nil { - return nil, 0, err - } - var query protocol.ConnectProviderParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.ConnectProviderWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "CreateProvider": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.CreateProviderWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "CreateSession": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.CreateSessionWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) - return response, 201, callErr - - case "DeleteProviderDetail": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.DeleteProviderDetailParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.DeleteProviderDetail(ctx, &query) - return response, 200, callErr - - case "DeleteProviderModel": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.DeleteProviderModelParams - if err := bindQuery(env.query, []string{"provider_id", "model_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.DeleteProviderModel(ctx, &query) - return response, 200, callErr - - case "DeleteSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.DeleteSession(ctx, protocol.SessionID(env.session)) - return response, 204, callErr - - case "DisconnectProvider": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.DisconnectProviderParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.DisconnectProvider(ctx, &query) - return response, 200, callErr - - case "DiscoverProviderCandidates": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.DiscoverProviderCandidatesWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "DiscoverProviderModelCandidates": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.DiscoverProviderModelCandidatesParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.DiscoverProviderModelCandidates(ctx, &query) - return response, 200, callErr - - case "ForkSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.ForkSessionWithBody(ctx, protocol.SessionID(env.session), contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "GetConfiguration": - if _, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberForbidden); err != nil { - return nil, 0, err - } - response, callErr := c.GetConfiguration(ctx) - return response, 200, callErr - - case "GetHealth": - if _, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberForbidden); err != nil { - return nil, 0, err - } - response, callErr := c.GetHealth(ctx) - return response, 200, callErr - - case "GetProviderDetail": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.GetProviderDetailParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.GetProviderDetail(ctx, &query) - return response, 200, callErr - - case "GetRetainedCodeSnapshots": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.GetRetainedCodeSnapshotsParams - if err := bindQuery(env.query, []string{"workspace", "session_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.GetRetainedCodeSnapshots(ctx, &query) - return response, 200, callErr - - case "GetSessionCodeSnapshots": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.GetSessionCodeSnapshots(ctx, protocol.SessionID(env.session)) - return response, 200, callErr - - case "GetSessionHistory": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberOptional, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.GetSessionHistoryParams - if env.query != nil { - if err := bindQuery(env.query, nil, &query); err != nil { - return nil, 0, err +// operationDescriptors is the schema's native operation set keyed by its source +// operationId. Each call binds the operation's generated path and query +// arguments from the already-bound envelope and performs exactly one generated +// SDK request; the shared invoker in bind.go applies the envelope rules and the +// one response owner interprets the result. +var operationDescriptors = map[string]descriptor{ + "ArchiveSession": { + session: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err } - } - response, callErr := c.GetSessionHistory(ctx, protocol.SessionID(env.session), &query) - return response, 200, callErr - - case "GetSessionHydration": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.GetSessionHydration(ctx, protocol.SessionID(env.session)) - return response, 200, callErr - - case "GetWarnings": - if _, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberForbidden); err != nil { - return nil, 0, err - } - response, callErr := c.GetWarnings(ctx) - return response, 200, callErr - - case "InterruptSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.InterruptSession(ctx, protocol.SessionID(env.session)) - return response, 204, callErr - - case "ListModels": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.ListModelsParams - if err := bindQuery(env.query, []string{"all"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.ListModels(ctx, &query) - return response, 200, callErr - - case "ListProviderModels": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.ListProviderModelsParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.ListProviderModels(ctx, &query) - return response, 200, callErr - - case "ListProviders": - if _, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberForbidden); err != nil { - return nil, 0, err - } - response, callErr := c.ListProviders(ctx) - return response, 200, callErr - - case "ListSessions": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.ListSessionsParams - if err := bindQuery(env.query, []string{"workspace", "lifecycle"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.ListSessions(ctx, &query) - return response, 200, callErr - - case "ListWorkspaces": - if _, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberForbidden); err != nil { - return nil, 0, err - } - response, callErr := c.ListWorkspaces(ctx) - return response, 200, callErr - - case "ReadWorkspaceFile": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.ReadWorkspaceFileWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "ReloadConfiguration": - if _, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberForbidden); err != nil { - return nil, 0, err - } - response, callErr := c.ReloadConfiguration(ctx) - return response, 200, callErr - - case "ReopenSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.ReopenSession(ctx, protocol.SessionID(env.session)) - return response, 200, callErr - - case "ResetProviderField": - env, err := bindEnvelope(params, memberForbidden, memberRequired, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.ResetProviderFieldParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.ResetProviderField(ctx, protocol.ProviderField(env.field), &query) - return response, 200, callErr - - case "ResetProviderModelField": - env, err := bindEnvelope(params, memberForbidden, memberRequired, memberRequired, memberForbidden) - if err != nil { - return nil, 0, err - } - var query protocol.ResetProviderModelFieldParams - if err := bindQuery(env.query, []string{"provider_id", "model_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.ResetProviderModelField(ctx, protocol.ModelField(env.field), &query) - return response, 200, callErr - - case "RevertRetainedCode": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.RevertRetainedCodeWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "RevertSessionCode": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.RevertSessionCodeWithBody(ctx, protocol.SessionID(env.session), contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "SetAgentTypeModel": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberRequired) - if err != nil { - return nil, 0, err - } - var query protocol.SetAgentTypeModelParams - if err := bindQuery(env.query, []string{"agent_type"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.SetAgentTypeModelWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "SetSessionAgentType": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.SetSessionAgentTypeWithBody(ctx, protocol.SessionID(env.session), contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "StopSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberForbidden) - if err != nil { - return nil, 0, err - } - response, callErr := c.StopSession(ctx, protocol.SessionID(env.session)) - return response, 204, callErr - - case "SubmitSession": - env, err := bindEnvelope(params, memberRequired, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.SubmitSessionWithBody(ctx, protocol.SessionID(env.session), contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "UpdateConfigurationSettings": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberForbidden, memberRequired) - if err != nil { - return nil, 0, err - } - response, callErr := c.UpdateConfigurationSettingsWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "UpdateProviderDetail": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberRequired) - if err != nil { - return nil, 0, err - } - var query protocol.UpdateProviderDetailParams - if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.UpdateProviderDetailWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - case "UpdateProviderModel": - env, err := bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberRequired) - if err != nil { - return nil, 0, err - } - var query protocol.UpdateProviderModelParams - if err := bindQuery(env.query, []string{"provider_id", "model_id"}, &query); err != nil { - return nil, 0, err - } - response, callErr := c.UpdateProviderModelWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) - return response, 200, callErr - - default: - return nil, 0, invalidParams("unknown operation %q", operation) - } + return c.ArchiveSession(ctx, pathValue) + }, + }, + "CompactSession": { + session: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.CompactSessionWithBody(ctx, pathValue, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "ConnectProvider": { + query: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.ConnectProviderParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.ConnectProviderWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "CreateProvider": { + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.CreateProviderWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "CreateSession": { + body: memberRequired, + status: 201, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.CreateSessionWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "DeleteProviderDetail": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.DeleteProviderDetailParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.DeleteProviderDetail(ctx, &query) + }, + }, + "DeleteProviderModel": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.DeleteProviderModelParams + if err := bindQuery(env.query, []string{"provider_id", "model_id"}, &query); err != nil { + return nil, err + } + return c.DeleteProviderModel(ctx, &query) + }, + }, + "DeleteSession": { + session: memberRequired, + status: 204, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.DeleteSession(ctx, pathValue) + }, + }, + "DisconnectProvider": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.DisconnectProviderParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.DisconnectProvider(ctx, &query) + }, + }, + "DiscoverProviderCandidates": { + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.DiscoverProviderCandidatesWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "DiscoverProviderModelCandidates": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.DiscoverProviderModelCandidatesParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.DiscoverProviderModelCandidates(ctx, &query) + }, + }, + "ForkSession": { + session: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.ForkSessionWithBody(ctx, pathValue, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "GetConfiguration": { + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.GetConfiguration(ctx) + }, + }, + "GetHealth": { + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.GetHealth(ctx) + }, + }, + "GetProviderDetail": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.GetProviderDetailParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.GetProviderDetail(ctx, &query) + }, + }, + "GetRetainedCodeSnapshots": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.GetRetainedCodeSnapshotsParams + if err := bindQuery(env.query, []string{"workspace", "session_id"}, &query); err != nil { + return nil, err + } + return c.GetRetainedCodeSnapshots(ctx, &query) + }, + }, + "GetSessionCodeSnapshots": { + session: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.GetSessionCodeSnapshots(ctx, pathValue) + }, + }, + "GetSessionHistory": { + session: memberRequired, + query: memberOptional, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + var query protocol.GetSessionHistoryParams + if env.query != nil { + if err := bindQuery(env.query, nil, &query); err != nil { + return nil, err + } + } + return c.GetSessionHistory(ctx, pathValue, &query) + }, + }, + "GetSessionHydration": { + session: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.GetSessionHydration(ctx, pathValue) + }, + }, + "GetWarnings": { + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.GetWarnings(ctx) + }, + }, + "InterruptSession": { + session: memberRequired, + status: 204, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.InterruptSession(ctx, pathValue) + }, + }, + "ListModels": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.ListModelsParams + if err := bindQuery(env.query, []string{"all"}, &query); err != nil { + return nil, err + } + return c.ListModels(ctx, &query) + }, + }, + "ListProviderModels": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.ListProviderModelsParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.ListProviderModels(ctx, &query) + }, + }, + "ListProviders": { + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.ListProviders(ctx) + }, + }, + "ListSessions": { + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.ListSessionsParams + if err := bindQuery(env.query, []string{"workspace", "lifecycle"}, &query); err != nil { + return nil, err + } + return c.ListSessions(ctx, &query) + }, + }, + "ListWorkspaces": { + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.ListWorkspaces(ctx) + }, + }, + "ReadWorkspaceFile": { + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.ReadWorkspaceFileWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "ReloadConfiguration": { + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.ReloadConfiguration(ctx) + }, + }, + "ReopenSession": { + session: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.ReopenSession(ctx, pathValue) + }, + }, + "ResetProviderField": { + field: memberRequired, + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.ProviderField + if err := bindPath(env.field, &pathValue); err != nil { + return nil, err + } + var query protocol.ResetProviderFieldParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.ResetProviderField(ctx, pathValue, &query) + }, + }, + "ResetProviderModelField": { + field: memberRequired, + query: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.ModelField + if err := bindPath(env.field, &pathValue); err != nil { + return nil, err + } + var query protocol.ResetProviderModelFieldParams + if err := bindQuery(env.query, []string{"provider_id", "model_id"}, &query); err != nil { + return nil, err + } + return c.ResetProviderModelField(ctx, pathValue, &query) + }, + }, + "RevertRetainedCode": { + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.RevertRetainedCodeWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "RevertSessionCode": { + session: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.RevertSessionCodeWithBody(ctx, pathValue, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "SetAgentTypeModel": { + query: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.SetAgentTypeModelParams + if err := bindQuery(env.query, []string{"agent_type"}, &query); err != nil { + return nil, err + } + return c.SetAgentTypeModelWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "SetSessionAgentType": { + session: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.SetSessionAgentTypeWithBody(ctx, pathValue, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "StopSession": { + session: memberRequired, + status: 204, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.StopSession(ctx, pathValue) + }, + }, + "SubmitSession": { + session: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var pathValue protocol.SessionID + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } + return c.SubmitSessionWithBody(ctx, pathValue, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "UpdateConfigurationSettings": { + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + return c.UpdateConfigurationSettingsWithBody(ctx, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "UpdateProviderDetail": { + query: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.UpdateProviderDetailParams + if err := bindQuery(env.query, []string{"provider_id"}, &query); err != nil { + return nil, err + } + return c.UpdateProviderDetailWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, + "UpdateProviderModel": { + query: memberRequired, + body: memberRequired, + status: 200, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { + var query protocol.UpdateProviderModelParams + if err := bindQuery(env.query, []string{"provider_id", "model_id"}, &query); err != nil { + return nil, err + } + return c.UpdateProviderModelWithBody(ctx, &query, contentTypeJSON, bytes.NewReader(env.body)) + }, + }, } diff --git a/internal/clientview/clientview_test.go b/internal/clientview/clientview_test.go index f3189792..7a87e6d9 100644 --- a/internal/clientview/clientview_test.go +++ b/internal/clientview/clientview_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/clientview" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -37,9 +38,7 @@ func TestIndependentRevisionComparators(t *testing.T) { const instance = "0123456789abcdef0123456789abcdef" orders := map[string]int{"older": -1, "equal": 0, "newer": 1} corpus := loadProjectionCorpus(t) - if len(corpus.Counters) == 0 { - t.Fatal("the shared corpus has no counter cases") - } + testassert.Require(t, len(corpus.Counters) != 0, "the shared corpus has no counter cases") for _, test := range corpus.Counters { t.Run(test.Name, func(t *testing.T) { rightInstance := instance @@ -48,9 +47,7 @@ func TestIndependentRevisionComparators(t *testing.T) { } configuration, configErr := clientview.CompareConfigurationRevision(protocol.ConfigurationRevision{InstanceId: instance, Generation: test.A}, protocol.ConfigurationRevision{InstanceId: rightInstance, Generation: test.B}) warnings, warningErr := clientview.CompareWarningsRevision(protocol.WarningsRevision{InstanceId: instance, Revision: test.A}, protocol.WarningsRevision{InstanceId: rightInstance, Revision: test.B}) - if (configErr != nil) != test.Error || (warningErr != nil) != test.Error || (!test.Error && (configuration != orders[test.Result] || warnings != orders[test.Result])) { - t.Fatalf("config %d/%v warnings %d/%v", configuration, configErr, warnings, warningErr) - } + testassert.Require(t, (configErr != nil) == test.Error && (warningErr != nil) == test.Error && (test.Error || configuration == orders[test.Result] && warnings == orders[test.Result]), "config %d/%v warnings %d/%v", configuration, configErr, warnings, warningErr) }) } } @@ -100,13 +97,9 @@ type typescriptOnlyCase struct { func loadProjectionCorpus(t *testing.T) projectionCorpus { t.Helper() data, err := os.ReadFile(filepath.FromSlash(projectionCorpusPath)) - if err != nil { - t.Fatalf("reading the shared projection corpus: %v", err) - } + testassert.NoError(t, err) var corpus projectionCorpus - if err := json.Unmarshal(data, &corpus); err != nil { - t.Fatalf("decoding the shared projection corpus: %v", err) - } + testassert.NoError(t, json.Unmarshal(data, &corpus)) return corpus } @@ -115,9 +108,7 @@ func loadProjectionCorpus(t *testing.T) projectionCorpus { // out-of-range or foreign case that must stay an error. func TestCompareSessionRevision(t *testing.T) { corpus := loadProjectionCorpus(t) - if len(corpus.Compare) == 0 { - t.Fatal("the shared projection corpus has no compare cases") - } + testassert.Require(t, len(corpus.Compare) != 0, "the shared projection corpus has no compare cases") orders := map[string]int{"older": -1, "equal": 0, "newer": 1} for _, testCase := range corpus.Compare { t.Run(testCase.Name, func(t *testing.T) { @@ -125,21 +116,13 @@ func TestCompareSessionRevision(t *testing.T) { b := decodeRevision[protocol.SessionRevision](t, testCase.B) got, err := clientview.CompareSessionRevision(a, b) if testCase.Error { - if err == nil { - t.Fatalf("CompareSessionRevision(%s, %s) = %d, nil; want an error", testCase.A, testCase.B, got) - } + testassert.Require(t, err != nil, "CompareSessionRevision(%s, %s) = %d, nil; want an error", testCase.A, testCase.B, got) return } - if err != nil { - t.Fatalf("CompareSessionRevision(%s, %s): %v", testCase.A, testCase.B, err) - } + testassert.Require(t, err == nil, "CompareSessionRevision(%s, %s): %v", testCase.A, testCase.B, err) want, ok := orders[testCase.Result] - if !ok { - t.Fatalf("corpus case %q names unknown result %q", testCase.Name, testCase.Result) - } - if got != want { - t.Fatalf("CompareSessionRevision(%s, %s) = %d, want %d", testCase.A, testCase.B, got, want) - } + testassert.Require(t, ok, "corpus case %q names unknown result %q", testCase.Name, testCase.Result) + testassert.Require(t, got == want, "CompareSessionRevision(%s, %s) = %d, want %d", testCase.A, testCase.B, got, want) }) } } @@ -151,9 +134,7 @@ func TestCompareSessionRevision(t *testing.T) { // errors. Every case also proves the call mutates neither current nor next. func TestHydrationIndependentClocks(t *testing.T) { corpus := loadProjectionCorpus(t) - if len(corpus.Accept) == 0 { - t.Fatal("the shared projection corpus has no accept cases") - } + testassert.Require(t, len(corpus.Accept) != 0, "the shared projection corpus has no accept cases") for _, testCase := range corpus.Accept { t.Run(testCase.Name, func(t *testing.T) { next := decodeRevision[protocol.Hydration](t, testCase.Next) @@ -175,19 +156,14 @@ func TestHydrationIndependentClocks(t *testing.T) { beforeCurrent, beforeNext := mustJSON(t, current), mustJSON(t, next) accepted, err := clientview.AcceptHydration(current, next, testCase.Instance, configuration, warnings) if testCase.Error { - if err == nil { - t.Fatalf("AcceptHydration(%s) = %t, nil; want an error", testCase.Name, accepted) - } + testassert.Require(t, err != nil, "AcceptHydration(%s) = %t, nil; want an error", testCase.Name, accepted) } else { - if err != nil { - t.Fatalf("AcceptHydration(%s): %v", testCase.Name, err) - } - if accepted != testCase.Accept { - t.Fatalf("AcceptHydration(%s) = %t, want %t", testCase.Name, accepted, testCase.Accept) - } + testassert.Require(t, err == nil, "AcceptHydration(%s): %v", testCase.Name, err) + testassert.Require(t, accepted == testCase.Accept, "AcceptHydration(%s) = %t, want %t", testCase.Name, accepted, testCase.Accept) } - if afterCurrent, afterNext := mustJSON(t, current), mustJSON(t, next); afterCurrent != beforeCurrent || afterNext != beforeNext { - t.Fatalf("AcceptHydration(%s) mutated its inputs", testCase.Name) + { + afterCurrent, afterNext := mustJSON(t, current), mustJSON(t, next) + testassert.Require(t, afterCurrent == beforeCurrent && afterNext == beforeNext, "AcceptHydration(%s) mutated its inputs", testCase.Name) } }) } @@ -198,28 +174,21 @@ func TestHydrationIndependentClocks(t *testing.T) { // that must stay errors. func TestItemID(t *testing.T) { corpus := loadProjectionCorpus(t) - if len(corpus.ItemIDs) == 0 { - t.Fatal("the shared projection corpus has no item id cases") - } + testassert.Require(t, len(corpus.ItemIDs) != 0, "the shared projection corpus has no item id cases") for _, testCase := range corpus.ItemIDs { t.Run(testCase.Name, func(t *testing.T) { var item protocol.ConversationItem - if err := json.Unmarshal([]byte(testCase.Item), &item); err != nil { - t.Fatalf("decoding item payload %s: %v", testCase.Item, err) + { + err := json.Unmarshal([]byte(testCase.Item), &item) + testassert.Require(t, err == nil, "decoding item payload %s: %v", testCase.Item, err) } got, err := clientview.ItemID(item) if testCase.Error { - if err == nil { - t.Fatalf("ItemID(%s) = %q, nil; want an error", testCase.Item, got) - } + testassert.Require(t, err != nil, "ItemID(%s) = %q, nil; want an error", testCase.Item, got) return } - if err != nil { - t.Fatalf("ItemID(%s): %v", testCase.Item, err) - } - if got != testCase.ID { - t.Fatalf("ItemID(%s) = %q, want %q", testCase.Item, got, testCase.ID) - } + testassert.Require(t, err == nil, "ItemID(%s): %v", testCase.Item, err) + testassert.Require(t, got == testCase.ID, "ItemID(%s) = %q, want %q", testCase.Item, got, testCase.ID) }) } } @@ -231,9 +200,7 @@ func TestItemID(t *testing.T) { // also proves the result is a fresh slice and neither input changed. func TestMergeConversation(t *testing.T) { corpus := loadProjectionCorpus(t) - if len(corpus.Merge) == 0 { - t.Fatal("the shared projection corpus has no merge cases") - } + testassert.Require(t, len(corpus.Merge) != 0, "the shared projection corpus has no merge cases") for _, testCase := range corpus.Merge { t.Run(testCase.Name, func(t *testing.T) { older := decodeItems(t, testCase.Older) @@ -241,26 +208,21 @@ func TestMergeConversation(t *testing.T) { beforeOlder, beforeNewer := mustJSON(t, older), mustJSON(t, newer) got, err := clientview.MergeConversation(older, newer) if testCase.Error { - if err == nil { - t.Fatalf("MergeConversation(%s, %s) = %d items, nil; want an error", testCase.Older, testCase.Newer, len(got)) - } + testassert.Require(t, err != nil, "MergeConversation(%s, %s) = %d items, nil; want an error", testCase.Older, testCase.Newer, len(got)) return } - if err != nil { - t.Fatalf("MergeConversation(%s): %v", testCase.Name, err) - } + testassert.Require(t, err == nil, "MergeConversation(%s): %v", testCase.Name, err) want := decodeItems(t, testCase.Result) if gotValues, wantValues := canonicalItems(t, got), canonicalItems(t, want); !reflect.DeepEqual(gotValues, wantValues) { t.Fatalf("MergeConversation(%s) = %s, want %s", testCase.Name, mustJSON(t, got), testCase.Result) } - if afterOlder, afterNewer := mustJSON(t, older), mustJSON(t, newer); afterOlder != beforeOlder || afterNewer != beforeNewer { - t.Fatalf("MergeConversation(%s) mutated its inputs", testCase.Name) + { + afterOlder, afterNewer := mustJSON(t, older), mustJSON(t, newer) + testassert.Require(t, afterOlder == beforeOlder && afterNewer == beforeNewer, "MergeConversation(%s) mutated its inputs", testCase.Name) } if len(got) > 0 { for _, input := range [][]protocol.ConversationItem{older, newer} { - if len(input) > 0 && &got[0] == &input[0] { - t.Fatalf("MergeConversation(%s) returned a slice aliasing its input", testCase.Name) - } + testassert.Require(t, len(input) <= 0 || &got[0] != &input[0], "MergeConversation(%s) returned a slice aliasing its input", testCase.Name) } } }) @@ -274,9 +236,7 @@ func TestMergeConversation(t *testing.T) { // generated boundary, not schema validation. func TestClientViewDecoderBoundary(t *testing.T) { corpus := loadProjectionCorpus(t) - if len(corpus.TypeScriptOnly) == 0 { - t.Fatal("the shared projection corpus has no typescript_only cases") - } + testassert.Require(t, len(corpus.TypeScriptOnly) != 0, "the shared projection corpus has no typescript_only cases") for _, testCase := range corpus.TypeScriptOnly { t.Run(testCase.Name, func(t *testing.T) { var err error @@ -290,9 +250,7 @@ func TestClientViewDecoderBoundary(t *testing.T) { default: t.Fatalf("typescript_only case %q names unknown function %q", testCase.Name, testCase.Function) } - if err == nil { - t.Fatalf("generated decode of %s accepted a raw JSON number", testCase.Name) - } + testassert.Require(t, err != nil, "generated decode of %s accepted a raw JSON number", testCase.Name) t.Logf("generated decode error for %s: %v", testCase.Name, err) }) } @@ -301,8 +259,9 @@ func TestClientViewDecoderBoundary(t *testing.T) { func decodeRevision[T any](t *testing.T, raw string) T { t.Helper() var value T - if err := json.Unmarshal([]byte(raw), &value); err != nil { - t.Fatalf("decoding revision payload %s: %v", raw, err) + { + err := json.Unmarshal([]byte(raw), &value) + testassert.Require(t, err == nil, "decoding revision payload %s: %v", raw, err) } return value } @@ -310,8 +269,9 @@ func decodeRevision[T any](t *testing.T, raw string) T { func decodeItems(t *testing.T, raw string) []protocol.ConversationItem { t.Helper() var items []protocol.ConversationItem - if err := json.Unmarshal([]byte(raw), &items); err != nil { - t.Fatalf("decoding item array payload %s: %v", raw, err) + { + err := json.Unmarshal([]byte(raw), &items) + testassert.Require(t, err == nil, "decoding item array payload %s: %v", raw, err) } return items } @@ -319,9 +279,7 @@ func decodeItems(t *testing.T, raw string) []protocol.ConversationItem { func mustJSON(t *testing.T, value any) string { t.Helper() data, err := json.Marshal(value) - if err != nil { - t.Fatalf("marshaling fixture: %v", err) - } + testassert.NoError(t, err) return string(data) } @@ -329,8 +287,9 @@ func canonicalItems(t *testing.T, items []protocol.ConversationItem) []any { t.Helper() out := make([]any, len(items)) for i, item := range items { - if err := json.Unmarshal([]byte(mustJSON(t, item)), &out[i]); err != nil { - t.Fatalf("decoding item %d for comparison: %v", i, err) + { + err := json.Unmarshal([]byte(mustJSON(t, item)), &out[i]) + testassert.Require(t, err == nil, "decoding item %d for comparison: %v", i, err) } } return out diff --git a/internal/cmdoutput/cmdoutput_test.go b/internal/cmdoutput/cmdoutput_test.go index 206e593e..82c11a5c 100644 --- a/internal/cmdoutput/cmdoutput_test.go +++ b/internal/cmdoutput/cmdoutput_test.go @@ -7,6 +7,8 @@ import ( "strings" "sync" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestCaptureBelowCapExactOutputNoSpill(t *testing.T) { @@ -17,9 +19,7 @@ func TestCaptureBelowCapExactOutputNoSpill(t *testing.T) { _, _ = c.Stdout().Write([]byte("hello\n")) _, _ = c.Stderr().Write([]byte("warn\n")) - if got := c.Format(); got != "hello\nwarn\n" { - t.Fatalf("Format() = %q, want combined output", got) - } + testassert.Equal(t, c.Format(), "hello\nwarn\n") assertNoFiles(t, filepath.Join(home, ".lightcode")) } @@ -31,20 +31,11 @@ func TestCaptureManyLinesAboveCapSpillsFullOutput(t *testing.T) { _, _ = c.Stdout().Write([]byte(full)) result := c.Format() - if !strings.Contains(result, "line 01") || !strings.Contains(result, "line 25") { - t.Fatalf("Format() = %q, want first and last lines", result) - } - if strings.Contains(result, "line 11") { - t.Fatalf("Format() = %q, want middle lines omitted", result) - } + testassert.Require(t, strings.Contains(result, "line 01") && strings.Contains(result, "line 25") && !strings.Contains(result, "line 11"), "Format() = %q, want first and last lines; Format() = %q, want middle lines omitted", result, result) spillPath := extractSpillPath(t, result) - if !strings.HasPrefix(spillPath, filepath.Join(home, ".lightcode", "cmd_output_")) { - t.Fatalf("spill path = %q, want cmd_output_ under home", spillPath) - } + testassert.Require(t, strings.HasPrefix(spillPath, filepath.Join(home, ".lightcode", "cmd_output_")), "spill path = %q, want cmd_output_ under home", spillPath) data, err := os.ReadFile(spillPath) - if err != nil { - t.Fatalf("ReadFile(%q): %v", spillPath, err) - } + testassert.Require(t, err == nil, "ReadFile(%q): %v", spillPath, err) if string(data) != full { t.Fatalf("spill content = %q, want full output", string(data)) } @@ -58,13 +49,9 @@ func TestCaptureFewLongLinesAboveCapTruncatesAndSpills(t *testing.T) { _, _ = c.Stdout().Write([]byte(full)) result := c.Format() - if !strings.Contains(result, "abcde... [truncated 7 chars]") { - t.Fatalf("Format() = %q, want per-line truncation", result) - } + testassert.Require(t, strings.Contains(result, "abcde... [truncated 7 chars]"), "Format() = %q, want per-line truncation", result) data, err := os.ReadFile(extractSpillPath(t, result)) - if err != nil { - t.Fatalf("read spill: %v", err) - } + testassert.NoError(t, err) if string(data) != full { t.Fatalf("spill = %q, want %q", string(data), full) } @@ -80,15 +67,11 @@ func TestCaptureSharedBudgetAcrossStdoutAndStderr(t *testing.T) { c.mu.Lock() retained := c.stdout.buf.Len() + c.stderr.buf.Len() c.mu.Unlock() - if retained != 6 { - t.Fatalf("retained bytes = %d, want shared cap 6", retained) - } + testassert.Equal(t, retained, 6) result := c.Format() spillPath := extractSpillPath(t, result) data, err := os.ReadFile(spillPath) - if err != nil { - t.Fatalf("read spill: %v", err) - } + testassert.NoError(t, err) if string(data) != "abcdefgh" { t.Fatalf("spill = %q, want stdout then stderr", string(data)) } @@ -101,13 +84,9 @@ func TestCaptureCrossingWriteUsesSingleBudget(t *testing.T) { _, _ = c.Stdout().Write([]byte("abcdef")) _, _ = c.Stderr().Write([]byte("ghij")) - if got := c.Len(); got != 10 { - t.Fatalf("Len() = %d, want total accepted bytes", got) - } + testassert.Equal(t, c.Len(), 10) data, err := os.ReadFile(extractSpillPath(t, c.Format())) - if err != nil { - t.Fatalf("read spill: %v", err) - } + testassert.NoError(t, err) if string(data) != "abcdefghij" { t.Fatalf("spill = %q, want full output", string(data)) } @@ -120,9 +99,7 @@ func TestCaptureDisabledCapReturnsFullOutputNoSpill(t *testing.T) { _, _ = c.Stdout().Write([]byte("abcdefg\n")) _, _ = c.Stderr().Write([]byte("1234567\n")) - if got := c.Format(); got != "abcdefg\n1234567\n" { - t.Fatalf("Format() = %q, want full output with disabled cap", got) - } + testassert.Equal(t, c.Format(), "abcdefg\n1234567\n") assertNoFiles(t, filepath.Join(home, ".lightcode")) } @@ -134,12 +111,8 @@ func TestCaptureRepeatedFormatReusesSpillPath(t *testing.T) { first := c.Format() second := c.Format() - if first != second { - t.Fatalf("second Format() changed result\nfirst=%q\nsecond=%q", first, second) - } - if extractSpillPath(t, first) != extractSpillPath(t, second) { - t.Fatalf("Format() did not reuse spill path") - } + testassert.Equal(t, first, second) + testassert.Require(t, extractSpillPath(t, first) == extractSpillPath(t, second), "Format() did not reuse spill path") } func TestCaptureFinalSpillFailureReturnsExplicitMarker(t *testing.T) { @@ -147,36 +120,22 @@ func TestCaptureFinalSpillFailureReturnsExplicitMarker(t *testing.T) { c := NewCapture(Options{Directory: filepath.Join(home, ".lightcode"), SpillPrefix: "cmd_output_", MaxBytes: 4, MaxLineChars: 80}) defer c.Close() _, _ = c.Stdout().Write([]byte("abcdefghi")) - if err := os.Chmod(filepath.Join(home, ".lightcode"), 0o500); err != nil { - t.Fatalf("chmod spill dir: %v", err) - } + testassert.NoError(t, os.Chmod(filepath.Join(home, ".lightcode"), 0o500)) defer func() { _ = os.Chmod(filepath.Join(home, ".lightcode"), 0o700) }() result := c.Format() - if !strings.Contains(result, "could not be saved:") { - t.Fatalf("Format() = %q, want could-not-save marker", result) - } - if strings.Contains(result, "saved to:") { - t.Fatalf("Format() = %q, should not claim saved path", result) - } + testassert.Require(t, strings.Contains(result, "could not be saved:") && !strings.Contains(result, "saved to:"), "Format() = %q, want could-not-save marker; Format() = %q, should not claim saved path", result, result) } func TestCapturePrivateSpillFailureStaysBounded(t *testing.T) { homeFile := filepath.Join(t.TempDir(), "home-file") - if err := os.WriteFile(homeFile, []byte("not a dir"), 0o600); err != nil { - t.Fatalf("WriteFile: %v", err) - } + testassert.NoError(t, os.WriteFile(homeFile, []byte("not a dir"), 0o600)) c := NewCapture(Options{Directory: filepath.Join(homeFile, ".lightcode"), SpillPrefix: "cmd_output_", MaxBytes: 4, MaxLineChars: 4}) defer c.Close() _, _ = c.Stdout().Write([]byte("abcdefghijklmnopqrstuvwxyz")) result := c.Format() - if !strings.Contains(result, "could not be saved:") { - t.Fatalf("Format() = %q, want could-not-save marker", result) - } - if strings.Contains(result, "mnopqrstuvwxyz") { - t.Fatalf("Format() = %q, retained discarded tail after private spill failure", result) - } + testassert.Require(t, strings.Contains(result, "could not be saved:") && !strings.Contains(result, "mnopqrstuvwxyz"), "Format() = %q, want could-not-save marker; Format() = %q, retained discarded tail after private spill failure", result, result) } func TestCaptureVeryLongLineTruncatedWithoutFullRetention(t *testing.T) { @@ -186,12 +145,7 @@ func TestCaptureVeryLongLineTruncatedWithoutFullRetention(t *testing.T) { _, _ = c.Stdout().Write([]byte(strings.Repeat("x", 10000))) result := c.Format() - if !strings.Contains(result, "xxxxx... [truncated 10000 chars]") { - t.Fatalf("Format() = %q, want long-line truncation", result) - } - if len(result) > 300 { - t.Fatalf("Format() length = %d, want bounded preview", len(result)) - } + testassert.Require(t, strings.Contains(result, "xxxxx... [truncated 10000 chars]") && len(result) <= 300, "Format() = %q, want long-line truncation; Format() length = %d, want bounded preview", result, len(result)) } func TestCaptureConcurrentWritersAndFormat(t *testing.T) { @@ -212,9 +166,7 @@ func TestCaptureConcurrentWritersAndFormat(t *testing.T) { }(i) } wg.Wait() - if c.Len() == 0 { - t.Fatal("Len() = 0, want captured concurrent output") - } + testassert.Require(t, c.Len() != 0, "Len() = 0, want captured concurrent output") } func TestCaptureConcurrentFormatAndClose(t *testing.T) { @@ -252,8 +204,9 @@ func TestCaptureCloseDeletesUnreturnedVisibleSpillAndKeepsReturned(t *testing.T) path := c.visiblePath c.mu.Unlock() c.Close() - if _, err := os.Stat(path); !os.IsNotExist(err) { - t.Fatalf("visible spill after Close = %v, want removed", err) + { + _, err := os.Stat(path) + testassert.Require(t, os.IsNotExist(err), "visible spill after Close = %v, want removed", err) } }) @@ -263,8 +216,9 @@ func TestCaptureCloseDeletesUnreturnedVisibleSpillAndKeepsReturned(t *testing.T) _, _ = c.Stdout().Write([]byte("abcdefghi")) path := extractSpillPath(t, c.Format()) c.Close() - if _, err := os.Stat(path); err != nil { - t.Fatalf("visible spill after Close = %v, want kept", err) + { + _, err := os.Stat(path) + testassert.NoError(t, err) } }) } @@ -281,9 +235,7 @@ func extractSpillPath(t *testing.T, result string) string { t.Helper() marker := "saved to: " idx := strings.LastIndex(result, marker) - if idx < 0 { - t.Fatalf("result = %q, missing spill marker", result) - } + testassert.Require(t, idx >= 0, "result = %q, missing spill marker", result) path := result[idx+len(marker):] if end := strings.IndexAny(path, "]\n"); end >= 0 { path = path[:end] @@ -297,10 +249,6 @@ func assertNoFiles(t *testing.T, dir string) { if os.IsNotExist(err) { return } - if err != nil { - t.Fatalf("ReadDir(%q): %v", dir, err) - } - if len(entries) != 0 { - t.Fatalf("dir %q has files %v, want empty", dir, entries) - } + testassert.Require(t, err == nil, "ReadDir(%q): %v", dir, err) + testassert.Require(t, len(entries) == 0, "dir %q has files %v, want empty", dir, entries) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 514be120..c92743bf 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // The retained first-run skeleton the Runtime configuration service creates @@ -35,25 +37,17 @@ func TestParseAcceptsCatalogProviderEnvelopeAndIgnoresRemovedMembers(t *testing. "model": { "provider": "local", "model": "chat" } } }`)) - if err != nil { - t.Fatalf("Parse returned error for the provider envelope: %v", err) - } - if _, ok := cfg.Providers["local"]; !ok { - t.Fatalf("providers map did not retain local entry: %#v", cfg.Providers) + testassert.NoError(t, err) + { + _, ok := cfg.Providers["local"] + testassert.Require(t, ok, "providers map did not retain local entry: %#v", cfg.Providers) } } func TestParseAcceptsEmptySkeleton(t *testing.T) { cfg, err := Parse([]byte(firstRunConfigSkeleton)) - if err != nil { - t.Fatalf("Parse(empty skeleton) returned error: %v", err) - } - if cfg.Providers == nil || len(cfg.Providers) != 0 { - t.Fatalf("Providers = %v, want empty map", cfg.Providers) - } - if !cfg.Sessions.AutoArchive || cfg.Sessions.ArchiveAfterDays != 7 { - t.Fatalf("Sessions defaults not applied: %+v", cfg.Sessions) - } + testassert.NoError(t, err) + testassert.Require(t, cfg.Providers != nil && len(cfg.Providers) == 0 && cfg.Sessions.AutoArchive && cfg.Sessions.ArchiveAfterDays == 7, "Providers = %v, want empty map; Sessions defaults not applied: %+v", cfg.Providers, cfg.Sessions) } func TestParsePreservesExactProviderExtraBodyNumbers(t *testing.T) { @@ -71,13 +65,12 @@ func TestParsePreservesExactProviderExtraBodyNumbers(t *testing.T) { } `)) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) providers := cfg.Providers["local"].(map[string]any) extraBody := providers["extra_body"].(map[string]any) - if got, ok := extraBody["large"].(json.Number); !ok || got.String() != "90071992547409931234567890" { - t.Fatalf("provider extra_body number = %#v (%T), want exact json.Number", extraBody["large"], extraBody["large"]) + { + got, ok := extraBody["large"].(json.Number) + testassert.Require(t, ok && got.String() == "90071992547409931234567890", "provider extra_body number = %#v (%T), want exact json.Number", extraBody["large"], extraBody["large"]) } } @@ -90,8 +83,9 @@ func TestParseRejectsTrailingWholeDocumentData(t *testing.T) { {name: "junk", data: `{"providers": {}} junk`}, } { t.Run(tc.name, func(t *testing.T) { - if _, err := Parse([]byte(tc.data)); err == nil { - t.Fatal("Parse accepted trailing whole-document data") + { + _, err := Parse([]byte(tc.data)) + testassert.Require(t, err != nil, "Parse accepted trailing whole-document data") } }) } @@ -99,9 +93,7 @@ func TestParseRejectsTrailingWholeDocumentData(t *testing.T) { func TestParseRejectsMalformedWholeDocument(t *testing.T) { _, err := Parse([]byte(`{not json`)) - if err == nil { - t.Fatal("Parse returned nil error, want the decode rejection") - } + testassert.Require(t, err != nil, "Parse returned nil error, want the decode rejection") if !strings.Contains(err.Error(), "invalid character") { t.Fatalf("error = %q, want substring %q", err.Error(), "invalid character") } @@ -127,12 +119,8 @@ func TestParseSessionsSharedPolicy(t *testing.T) { data = []byte(row.data) } got, err := ParseSessions(data) - if err != nil { - t.Fatalf("ParseSessions(%s): %v", row.data, err) - } - if got != row.want { - t.Fatalf("ParseSessions(%s) = %+v, want %+v", row.data, got, row.want) - } + testassert.Require(t, err == nil, "ParseSessions(%s): %v", row.data, err) + testassert.Require(t, got == row.want, "ParseSessions(%s) = %+v, want %+v", row.data, got, row.want) }) } @@ -140,8 +128,9 @@ func TestParseSessionsSharedPolicy(t *testing.T) { `{"auto_archive":`, `{"archive_after_days":106752}`, `{"delete_after_archive_days":9223372036854775807}`, `[]`, `"7"`, `5`, } { - if got, err := ParseSessions([]byte(bad)); err == nil { - t.Fatalf("ParseSessions(%s) = %+v, want rejection", bad, got) + { + got, err := ParseSessions([]byte(bad)) + testassert.Require(t, err != nil, "ParseSessions(%s) = %+v, want rejection", bad, got) } } } @@ -155,20 +144,12 @@ func TestParseSessionsSharesTheLegacyParseImplementation(t *testing.T) { `{"providers":{},"sessions":{"archive_after_days":-3}}`, } { cfg, err := Parse([]byte(doc)) - if err != nil { - t.Fatalf("Parse(%s): %v", doc, err) - } + testassert.Require(t, err == nil, "Parse(%s): %v", doc, err) var members map[string]json.RawMessage - if err := json.Unmarshal([]byte(doc), &members); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal([]byte(doc), &members)) want, err := ParseSessions(members["sessions"]) - if err != nil { - t.Fatalf("ParseSessions for %s: %v", doc, err) - } - if cfg.Sessions != want { - t.Fatalf("Parse(%s) sessions = %+v, ParseSessions = %+v", doc, cfg.Sessions, want) - } + testassert.Require(t, err == nil, "ParseSessions for %s: %v", doc, err) + testassert.Require(t, cfg.Sessions == want, "Parse(%s) sessions = %+v, ParseSessions = %+v", doc, cfg.Sessions, want) } // The whole-document entry shares the ParseSessions contract: its // validation errors are propagated instead of inventing defaults for a @@ -176,17 +157,9 @@ func TestParseSessionsSharesTheLegacyParseImplementation(t *testing.T) { doc := []byte(`{"providers":{},"sessions":{"archive_after_days":106752}}`) _, docErr := Parse(doc) var members map[string]json.RawMessage - if err := json.Unmarshal(doc, &members); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal(doc, &members)) _, sharedErr := ParseSessions(members["sessions"]) - if sharedErr == nil { - t.Fatal("ParseSessions accepted the overflowed policy") - } - if docErr == nil { - t.Fatalf("Parse of overflowed days = nil error, want the ParseSessions rejection %q", sharedErr) - } - if docErr.Error() != sharedErr.Error() { - t.Fatalf("Parse error = %q, want the propagated ParseSessions error %q", docErr, sharedErr) - } + testassert.Require(t, sharedErr != nil, "ParseSessions accepted the overflowed policy") + testassert.Require(t, docErr != nil, "Parse of overflowed days = nil error, want the ParseSessions rejection %q", sharedErr) + testassert.Require(t, docErr.Error() == sharedErr.Error(), "Parse error = %q, want the propagated ParseSessions error %q", docErr, sharedErr) } diff --git a/internal/config/dotenv_test.go b/internal/config/dotenv_test.go index 5d793252..2b366256 100644 --- a/internal/config/dotenv_test.go +++ b/internal/config/dotenv_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/atomicfs" + "github.com/MMinasyan/lightcode/internal/testassert" ) // envKeyForTest returns a key unlikely to collide with anything in the real @@ -35,30 +36,19 @@ func TestLoadDotEnvCreatesTemplateWhenMissing(t *testing.T) { path := filepath.Join(home, ".lightcode", ".env") m, err := LoadDotEnv() - if err != nil { - t.Fatalf("LoadDotEnv: %v", err) - } + testassert.NoError(t, err) if m.Path() != path { t.Fatalf("Path = %q, want %q", m.Path(), path) } info, err := os.Stat(path) - if err != nil { - t.Fatalf("template file not created: %v", err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o600 { t.Fatalf("template mode = %o, want 0600", info.Mode().Perm()) } data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) body := string(data) - if !strings.Contains(body, "#OPENAI_API_KEY=\n") || !strings.Contains(body, "#MINIMAX_API_KEY=\n") { - t.Fatalf("template should contain commented empty key slots, got %q", body) - } - if strings.Contains(body, "***") || strings.Contains(body, "...") { - t.Fatalf("template should not contain placeholder/credential-looking values: %q", body) - } + testassert.Require(t, strings.Contains(body, "#OPENAI_API_KEY=\n") && strings.Contains(body, "#MINIMAX_API_KEY=\n") && !strings.Contains(body, "***") && !strings.Contains(body, "..."), "template should contain commented empty key slots, got %q; template should not contain placeholder/credential-looking values: %q", body, body) if len(m.ManagedKeys()) != 0 { t.Fatalf("managed set should be empty for template, got %v", m.ManagedKeys()) } @@ -68,174 +58,188 @@ func TestLoadDotEnvPopulatesManagedSetForUnsetKeys(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) key := envKeyForTest(t, "LOAD_MGD") path := filepath.Join(dir, ".env") body := "# comment\n" + key + "=from-file\nOTHER_KEY=other\n" - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) m, err := LoadDotEnv() - if err != nil { - t.Fatalf("LoadDotEnv: %v", err) - } - if got := os.Getenv(key); got != "from-file" { - t.Fatalf("env %s = %q, want %q", key, got, "from-file") - } - if !m.IsManaged(key) { - t.Fatalf("key %s should be managed after LoadDotEnv", key) - } - if !m.IsManaged("OTHER_KEY") { - t.Fatalf("OTHER_KEY should be managed after LoadDotEnv") - } - if got := os.Getenv("OTHER_KEY"); got != "other" { - t.Fatalf("OTHER_KEY env = %q, want %q", got, "other") - } + testassert.NoError(t, err) + { + got := os.Getenv(key) + testassert.Require(t, got == "from-file", "env %s = %q, want %q", key, got, "from-file") + } + testassert.Require(t, m.IsManaged(key), "key %s should be managed after LoadDotEnv", key) + testassert.Require(t, m.IsManaged("OTHER_KEY"), "OTHER_KEY should be managed after LoadDotEnv") + testassert.Equal(t, os.Getenv("OTHER_KEY"), "other") } func TestLoadDotEnvShellKeyNotManaged(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) key := envKeyForTest(t, "SHELL_EXT") // Pre-set in shell — LoadDotEnv must not overwrite, must not mark managed. t.Setenv(key, "from-shell") path := filepath.Join(dir, ".env") body := key + "=from-file\n" - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) m, err := LoadDotEnv() - if err != nil { - t.Fatalf("LoadDotEnv: %v", err) - } - if got := os.Getenv(key); got != "from-shell" { - t.Fatalf("env %s = %q, want %q (shell must win)", key, got, "from-shell") - } - if m.IsManaged(key) { - t.Fatalf("shell-exported key %s must not be in managed set", key) + testassert.NoError(t, err) + { + got := os.Getenv(key) + testassert.Require(t, got == "from-shell", "env %s = %q, want %q (shell must win)", key, got, "from-shell") } + testassert.Require(t, !m.IsManaged(key), "shell-exported key %s must not be in managed set", key) } -func TestManagedEnvSetWritesKeyAndUpdatesProcessEnv(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, ".env") - // Seed with an unrelated line + comment to verify preservation. - seed := "# keep me\nUNRELATED=keep\n" - if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { - t.Fatal(err) - } - - key := envKeyForTest(t, "SET_WRITE") - m := NewManagedEnvForTest(path) - - if err := m.Set(key, "hello"); err != nil { - t.Fatalf("Set: %v", err) - } - - // Process env updated. - if got := os.Getenv(key); got != "hello" { - t.Fatalf("env %s = %q, want %q", key, got, "hello") - } - // Managed set updated. - if !m.IsManaged(key) { - t.Fatalf("key %s should be managed after Set", key) - } - // File contents. - data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - body := string(data) - if !strings.Contains(body, key+"=hello") { - t.Fatalf("file missing key line: %q", body) - } - if !strings.Contains(body, "# keep me") { - t.Fatalf("comment not preserved: %q", body) - } - if !strings.Contains(body, "UNRELATED=keep") { - t.Fatalf("unrelated line not preserved: %q", body) - } - // File mode. - info, err := os.Stat(path) - if err != nil { - t.Fatal(err) - } - if info.Mode().Perm() != 0o600 { - t.Fatalf("file mode = %o, want 0600", info.Mode().Perm()) - } -} - -func TestManagedEnvSetReplacesExistingKey(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, ".env") - key := envKeyForTest(t, "SET_REPLACE") - seed := key + "=old\nOTHER=x\n" - if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { - t.Fatal(err) - } - - m := NewManagedEnvForTest(path) - m.managed[key] = struct{}{} // simulate that it was loaded from .env - - if err := m.Set(key, "new"); err != nil { - t.Fatalf("Set: %v", err) - } - - data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - body := string(data) - if strings.Contains(body, key+"=old") { - t.Fatalf("old value still present: %q", body) - } - if !strings.Contains(body, key+"=new") { - t.Fatalf("new value missing: %q", body) - } - if !strings.Contains(body, "OTHER=x") { - t.Fatalf("unrelated line lost: %q", body) - } - if got := os.Getenv(key); got != "new" { - t.Fatalf("env %s = %q, want %q", key, got, "new") - } -} - -func TestManagedEnvSetRefusesExternalKey(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } - - key := envKeyForTest(t, "EXT_REFUSE") - t.Setenv(key, "external") +// TestManagedEnvOrdinaryRows drives the ordinary Set/Remove outcomes. Each +// row carries its seed, external environment and initial managed membership, +// its Set/Remove action, and independently authored post-state predicates for +// the process environment, the managed set and the real file's present and +// absent substrings. The $KEY placeholder in a seed or fragment is the row's +// env key. +func TestManagedEnvOrdinaryRows(t *testing.T) { + rows := []struct { + name string + suffix string + seed string + externalValue string + externalSet bool + managed bool + remove bool + value string + wantErr error + wantEnv string + wantEnvSet bool + wantManaged bool + wantFile []string + absentFile []string + wantMode os.FileMode + }{ + { + name: "SetWritesKeyAndUpdatesProcessEnv", + suffix: "SET_WRITE", + seed: "# keep me\nUNRELATED=keep\n", + value: "hello", + wantEnv: "hello", + wantEnvSet: true, + wantManaged: true, + wantFile: []string{"$KEY=hello", "# keep me", "UNRELATED=keep"}, + wantMode: 0o600, + }, + { + name: "SetReplacesExistingKey", + suffix: "SET_REPLACE", + seed: "$KEY=old\nOTHER=x\n", + managed: true, + value: "new", + wantEnv: "new", + wantEnvSet: true, + wantManaged: true, + wantFile: []string{"$KEY=new", "OTHER=x"}, + absentFile: []string{"$KEY=old"}, + }, + { + name: "SetRefusesExternalKey", + suffix: "EXT_REFUSE", + seed: "", + externalValue: "external", + externalSet: true, + value: "overwrite", + wantErr: ErrExternalKey, + wantEnv: "external", + wantEnvSet: true, + wantManaged: false, + absentFile: []string{"$KEY"}, + }, + { + name: "RemoveDeletesLineAndUnsets", + suffix: "RM_LINE", + seed: "# header\n$KEY=val\nKEEP=me\n", + externalValue: "val", + externalSet: true, + managed: true, + remove: true, + wantEnvSet: false, + wantManaged: false, + wantFile: []string{"# header", "KEEP=me"}, + absentFile: []string{"$KEY"}, + }, + { + name: "RemoveNoopForUnmanagedKey", + suffix: "RM_NOOP", + seed: "KEEP=me\n", + externalValue: "external", + externalSet: true, + managed: false, + remove: true, + wantEnv: "external", + wantEnvSet: true, + wantManaged: false, + wantFile: []string{"KEEP=me"}, + absentFile: []string{"$KEY"}, + }, + } + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + key := envKeyForTest(t, row.suffix) + path := filepath.Join(t.TempDir(), ".env") + seed := strings.ReplaceAll(row.seed, "$KEY", key) // $KEY is the row's env key + testassert.NoError(t, os.WriteFile(path, []byte(seed), 0o600)) + if row.externalSet { + t.Setenv(key, row.externalValue) + } + m := NewManagedEnvForTest(path) + if row.managed { + m.managed[key] = struct{}{} + } - m := NewManagedEnvForTest(path) - err := m.Set(key, "overwrite") - if !errors.Is(err, ErrExternalKey) { - t.Fatalf("Set err = %v, want ErrExternalKey", err) - } - // Env unchanged. - if got := os.Getenv(key); got != "external" { - t.Fatalf("env %s = %q, want %q", key, got, "external") - } - // File unchanged. - data, _ := os.ReadFile(path) - if strings.Contains(string(data), key) { - t.Fatalf("file should not contain key after refused Set: %q", string(data)) - } - // Not in managed set. - if m.IsManaged(key) { - t.Fatalf("refused key must not be in managed set") + var err error + if row.remove { + err = m.Remove(key) + } else { + err = m.Set(key, row.value) + } + if row.wantErr != nil { + testassert.Require(t, errors.Is(err, row.wantErr), "%s err = %v, want %v", row.name, err, row.wantErr) + } else { + testassert.NoError(t, err) + } + if row.wantEnvSet { + { + got := os.Getenv(key) + testassert.Require(t, got == row.wantEnv, "env %s = %q, want %q", key, got, row.wantEnv) + } + } else { + { + _, ok := os.LookupEnv(key) + testassert.Require(t, !ok, "env %s still set after %s", key, row.name) + } + } + testassert.Require(t, m.IsManaged(key) == row.wantManaged, "managed %s = %v after %s, want %v", key, m.IsManaged(key), row.name, row.wantManaged) + + data, err := os.ReadFile(path) + testassert.NoError(t, err) + body := string(data) + for _, frag := range row.wantFile { + frag = strings.ReplaceAll(frag, "$KEY", key) + testassert.Require(t, strings.Contains(body, frag), "file missing %q: %q", frag, body) + } + for _, frag := range row.absentFile { + frag = strings.ReplaceAll(frag, "$KEY", key) + testassert.Require(t, !strings.Contains(body, frag), "file still contains %q: %q", frag, body) + } + if row.wantMode != 0 { + info, err := os.Stat(path) + testassert.NoError(t, err) + testassert.Require(t, info.Mode().Perm() == row.wantMode, "file mode = %o, want %o", info.Mode().Perm(), row.wantMode) + } + }) } } @@ -252,108 +256,40 @@ func TestManagedEnvRejectsNULValueWithoutFileEffect(t *testing.T) { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), ".env") m := NewManagedEnvForTest(path) - if err := tc.attempt(m); err == nil { - t.Fatal("a NUL value was accepted") - } - if _, err := os.Stat(path); !os.IsNotExist(err) { - t.Fatalf("a NUL value wrote the env file (%v)", err) + { + err := tc.attempt(m) + testassert.Require(t, err != nil, "a NUL value was accepted") } - if _, defined := os.LookupEnv(key); defined { - t.Fatal("a NUL value reached the process environment") + { + _, err := os.Stat(path) + testassert.Require(t, os.IsNotExist(err), "a NUL value wrote the env file (%v)", err) } - if m.IsManaged(key) { - t.Fatal("a NUL value marked the key managed") + { + _, defined := os.LookupEnv(key) + testassert.Require(t, !defined, "a NUL value reached the process environment") } + testassert.Require(t, !m.IsManaged(key), "a NUL value marked the key managed") }) } } -func TestManagedEnvRemoveDeletesLineAndUnsets(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, ".env") - key := envKeyForTest(t, "RM_LINE") - seed := "# header\n" + key + "=val\nKEEP=me\n" - if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { - t.Fatal(err) - } - t.Setenv(key, "val") // ensure cleanup - - m := NewManagedEnvForTest(path) - m.managed[key] = struct{}{} - - if err := m.Remove(key); err != nil { - t.Fatalf("Remove: %v", err) - } - - if _, ok := os.LookupEnv(key); ok { - t.Fatalf("env %s still set after Remove", key) - } - if m.IsManaged(key) { - t.Fatalf("key %s still managed after Remove", key) - } - data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - body := string(data) - if strings.Contains(body, key+"=") { - t.Fatalf("key line still in file: %q", body) - } - if !strings.Contains(body, "# header") { - t.Fatalf("comment lost: %q", body) - } - if !strings.Contains(body, "KEEP=me") { - t.Fatalf("unrelated line lost: %q", body) - } -} - -func TestManagedEnvRemoveNoopForUnmanagedKey(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte("KEEP=me\n"), 0o600); err != nil { - t.Fatal(err) - } - - key := envKeyForTest(t, "RM_NOOP") - t.Setenv(key, "external") - - m := NewManagedEnvForTest(path) - // Key is external, not managed. - if err := m.Remove(key); err != nil { - t.Fatalf("Remove: %v", err) - } - // Env still set. - if got := os.Getenv(key); got != "external" { - t.Fatalf("env %s = %q, want %q (should be untouched)", key, got, "external") - } -} - func TestManagedEnvRoundTripSameSession(t *testing.T) { // Simulates the Commit-4 scenario: key written this session is // immediately in the managed set and removable without restart. dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) key := envKeyForTest(t, "RT_SESSION") m := NewManagedEnvForTest(path) - if err := m.Set(key, "session-val"); err != nil { - t.Fatalf("Set: %v", err) - } - if !m.IsManaged(key) { - t.Fatalf("key written this session must be managed") - } - if err := m.Remove(key); err != nil { - t.Fatalf("Remove: %v", err) - } - if m.IsManaged(key) { - t.Fatalf("key should no longer be managed after Remove") - } - if _, ok := os.LookupEnv(key); ok { - t.Fatalf("env %s still set after Remove", key) + testassert.NoError(t, m.Set(key, "session-val")) + testassert.Require(t, m.IsManaged(key), "key written this session must be managed") + testassert.NoError(t, m.Remove(key)) + testassert.Require(t, !m.IsManaged(key), "key should no longer be managed after Remove") + { + _, ok := os.LookupEnv(key) + testassert.Require(t, !ok, "env %s still set after Remove", key) } } @@ -362,17 +298,11 @@ func TestManagedEnvSetValueWithSpacesIsQuoted(t *testing.T) { t.Setenv("HOME", home) dir := filepath.Join(home, ".lightcode") path := filepath.Join(dir, ".env") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) key := envKeyForTest(t, "QUOTED") m := NewManagedEnvForTest(path) - if err := m.Set(key, "has spaces"); err != nil { - t.Fatalf("Set: %v", err) - } + testassert.NoError(t, m.Set(key, "has spaces")) data, _ := os.ReadFile(path) if !strings.Contains(string(data), key+`="has spaces"`) { t.Fatalf("quoted value not in file: %q", string(data)) @@ -380,15 +310,12 @@ func TestManagedEnvSetValueWithSpacesIsQuoted(t *testing.T) { _ = os.Unsetenv(key) m2, err := LoadDotEnv() - if err != nil { - t.Fatalf("LoadDotEnv: %v", err) - } - if got := os.Getenv(key); got != "has spaces" { - t.Fatalf("env %s after reload = %q, want %q", key, got, "has spaces") - } - if !m2.IsManaged(key) { - t.Fatalf("key %s should be managed after quoted reload", key) + testassert.NoError(t, err) + { + got := os.Getenv(key) + testassert.Require(t, got == "has spaces", "env %s after reload = %q, want %q", key, got, "has spaces") } + testassert.Require(t, m2.IsManaged(key), "key %s should be managed after quoted reload", key) } func TestManagedEnvSetQuotedSpecialValueRoundTrips(t *testing.T) { @@ -396,34 +323,25 @@ func TestManagedEnvSetQuotedSpecialValueRoundTrips(t *testing.T) { t.Setenv("HOME", home) dir := filepath.Join(home, ".lightcode") path := filepath.Join(dir, ".env") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) key := envKeyForTest(t, "SPECIAL") value := "quote=\" slash=\\ line\nbreak tab end" m := NewManagedEnvForTest(path) - if err := m.Set(key, value); err != nil { - t.Fatalf("Set: %v", err) - } + testassert.NoError(t, m.Set(key, value)) data, _ := os.ReadFile(path) body := string(data) - if strings.Contains(body, "line\nbreak") { - t.Fatalf("literal newline written inside env value: %q", body) - } - if !strings.Contains(body, `\n`) || !strings.Contains(body, `\t`) { - t.Fatalf("newline/tab escapes missing from quoted value: %q", body) - } + testassert.Require(t, !strings.Contains(body, "line\nbreak") && strings.Contains(body, `\n`) && strings.Contains(body, `\t`), "literal newline written inside env value: %q; newline/tab escapes missing from quoted value: %q", body, body) _ = os.Unsetenv(key) - if _, err := LoadDotEnv(); err != nil { - t.Fatalf("LoadDotEnv: %v", err) + { + _, err := LoadDotEnv() + testassert.NoError(t, err) } - if got := os.Getenv(key); got != value { - t.Fatalf("env %s after reload = %q, want %q", key, got, value) + { + got := os.Getenv(key) + testassert.Require(t, got == value, "env %s after reload = %q, want %q", key, got, value) } } @@ -434,15 +352,11 @@ func TestWriteDotEnvAtomicNoPartialFileOnError(t *testing.T) { // Create a regular file where the .env should go; MkdirAll will fail // because a path component is a file. blocker := filepath.Join(dir, "blocked") - if err := os.WriteFile(blocker, []byte("x"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(blocker, []byte("x"), 0o600)) badPath := filepath.Join(blocker, "subdir", ".env") err := writeDotEnvAtomic(badPath, []byte("KEY=val\n")) - if err == nil { - t.Fatal("expected error writing under a file-as-dir path") - } + testassert.Require(t, err != nil, "expected error writing under a file-as-dir path") // No .tmp files should remain under dir. entries, _ := os.ReadDir(dir) for _, e := range entries { @@ -455,9 +369,7 @@ func TestWriteDotEnvAtomicNoPartialFileOnError(t *testing.T) { func TestWriteDotEnvAtomicLeavesNoTempOnSuccess(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := writeDotEnvAtomic(path, []byte("A=1\n")); err != nil { - t.Fatalf("writeDotEnvAtomic: %v", err) - } + testassert.NoError(t, writeDotEnvAtomic(path, []byte("A=1\n"))) entries, _ := os.ReadDir(dir) for _, e := range entries { if strings.HasSuffix(e.Name(), ".tmp") { @@ -473,18 +385,14 @@ func TestWriteDotEnvAtomicLeavesNoTempOnSuccess(t *testing.T) { func TestManagedEnvKeysSorted(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) m := NewManagedEnvForTest(path) k1 := envKeyForTest(t, "SORT_B") k2 := envKeyForTest(t, "SORT_A") _ = m.Set(k1, "1") _ = m.Set(k2, "2") keys := m.ManagedKeys() - if len(keys) != 2 || keys[0] != k2 || keys[1] != k1 { - t.Fatalf("ManagedKeys = %v, want [%s, %s]", keys, k2, k1) - } + testassert.Require(t, len(keys) == 2 && keys[0] == k2 && keys[1] == k1, "ManagedKeys = %v, want [%s, %s]", keys, k2, k1) } func TestIsValidEnvKey(t *testing.T) { @@ -499,8 +407,9 @@ func TestIsValidEnvKey(t *testing.T) { "has.dot": false, } for k, want := range cases { - if got := isValidEnvKey(k); got != want { - t.Errorf("isValidEnvKey(%q) = %v, want %v", k, got, want) + { + got := isValidEnvKey(k) + testassert.Check(t, got == want, "isValidEnvKey(%q) = %v, want %v", k, got, want) } } } @@ -509,20 +418,16 @@ func TestReadDotEnvKeysAbsentFileCreatesNothing(t *testing.T) { dir := filepath.Join(t.TempDir(), "nonexistent") path := filepath.Join(dir, ".env") keys, malformed, err := ReadDotEnvKeys(path) - if err != nil { - t.Fatalf("ReadDotEnvKeys returned error: %v", err) + testassert.NoError(t, err) + testassert.Require(t, len(keys) == 0, "keys = %v, want empty", keys) + testassert.Require(t, len(malformed) == 0, "malformed = %v, want empty", malformed) + { + _, statErr := os.Stat(dir) + testassert.Require(t, os.IsNotExist(statErr), "parent dir was created: stat err = %v", statErr) } - if len(keys) != 0 { - t.Fatalf("keys = %v, want empty", keys) - } - if len(malformed) != 0 { - t.Fatalf("malformed = %v, want empty", malformed) - } - if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) { - t.Fatalf("parent dir was created: stat err = %v", statErr) - } - if _, statErr := os.Stat(path); !os.IsNotExist(statErr) { - t.Fatalf(".env was created: stat err = %v", statErr) + { + _, statErr := os.Stat(path) + testassert.Require(t, os.IsNotExist(statErr), ".env was created: stat err = %v", statErr) } } @@ -535,14 +440,10 @@ func TestReadDotEnvKeysParsesNamesOnly(t *testing.T) { "COMMENTED_OUT_IN_TEMPLATE_STYLE=\n" + "no equals sign here\n" + "BAD_QUOTE_KEY=\"bad \\x escape\"\n" - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) keys, malformed, err := ReadDotEnvKeys(path) - if err != nil { - t.Fatalf("ReadDotEnvKeys returned error: %v", err) - } + testassert.NoError(t, err) for name, wantNonEmpty := range map[string]bool{ "PLAIN_KEY": true, "EXPORTED_KEY": true, @@ -555,21 +456,16 @@ func TestReadDotEnvKeysParsesNamesOnly(t *testing.T) { t.Errorf("keys[%q] = %v, want %v (value emptiness)", name, got, wantNonEmpty) } } - if len(keys) != 3 { - t.Fatalf("keys = %v, want exactly 3 names", keys) - } - if len(malformed) != 2 { - t.Fatalf("malformed = %v, want 2 entries", malformed) - } + testassert.Require(t, len(keys) == 3, "keys = %v, want exactly 3 names", keys) + testassert.Require(t, len(malformed) == 2, "malformed = %v, want 2 entries", malformed) for _, m := range malformed { - if !strings.Contains(m, path+":") { - t.Errorf("malformed entry %q does not name the file and line", m) - } + testassert.Check(t, strings.Contains(m, path+":"), "malformed entry %q does not name the file and line", m) } // The process environment is untouched: nothing was Setenv'd. - if _, set := os.LookupEnv("PLAIN_KEY"); set { - t.Fatal("ReadDotEnvKeys set PLAIN_KEY in the process env") + { + _, set := os.LookupEnv("PLAIN_KEY") + testassert.Require(t, !set, "ReadDotEnvKeys set PLAIN_KEY in the process env") } } @@ -581,19 +477,11 @@ func TestReadDotEnvKeysFirstOccurrenceWins(t *testing.T) { "EMPTY_FIRST=value\n" + "VALUE_FIRST=value\n" + "VALUE_FIRST=\n" - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) keys, _, err := ReadDotEnvKeys(path) - if err != nil { - t.Fatal(err) - } - if keys["EMPTY_FIRST"] { - t.Fatal("EMPTY_FIRST: the empty first occurrence must win") - } - if !keys["VALUE_FIRST"] { - t.Fatal("VALUE_FIRST: the non-empty first occurrence must win") - } + testassert.NoError(t, err) + testassert.Require(t, !keys["EMPTY_FIRST"], "EMPTY_FIRST: the empty first occurrence must win") + testassert.Require(t, keys["VALUE_FIRST"], "VALUE_FIRST: the non-empty first occurrence must win") } // Quoted empty values unwrap to empty, exactly as LoadDotEnv parses them. @@ -602,25 +490,15 @@ func TestReadDotEnvKeysQuotedEmptyValues(t *testing.T) { body := "DOUBLE_QUOTED_EMPTY=\"\"\n" + "SINGLE_QUOTED_EMPTY=''\n" + "QUOTED_VALUE=\"x\"\n" - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) keys, malformed, err := ReadDotEnvKeys(path) - if err != nil { - t.Fatal(err) - } - if len(malformed) != 0 { - t.Fatalf("malformed = %v, want none", malformed) - } + testassert.NoError(t, err) + testassert.Require(t, len(malformed) == 0, "malformed = %v, want none", malformed) for _, name := range []string{"DOUBLE_QUOTED_EMPTY", "SINGLE_QUOTED_EMPTY"} { nonEmpty, ok := keys[name] - if !ok || nonEmpty { - t.Fatalf("%s: present=%v nonEmpty=%v, want defined and empty", name, ok, nonEmpty) - } - } - if !keys["QUOTED_VALUE"] { - t.Fatal("QUOTED_VALUE: a quoted non-empty value must resolve") + testassert.Require(t, ok && !nonEmpty, "%s: present=%v nonEmpty=%v, want defined and empty", name, ok, nonEmpty) } + testassert.Require(t, keys["QUOTED_VALUE"], "QUOTED_VALUE: a quoted non-empty value must resolve") } // TestManagedEnvTrySetRows covers the one-attempt TrySet contract: success @@ -630,74 +508,54 @@ func TestManagedEnvTrySetRows(t *testing.T) { t.Run("success", func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte("# keep\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("# keep\n"), 0o600)) key := envKeyForTest(t, "TRYSET_OK") m := NewManagedEnvForTest(path) - if err := m.TrySet(key, "hello"); err != nil { - t.Fatalf("TrySet: %v", err) - } - if got := os.Getenv(key); got != "hello" { - t.Fatalf("env %s = %q, want hello", key, got) - } - if !m.IsManaged(key) { - t.Fatal("key not managed after TrySet") + testassert.NoError(t, m.TrySet(key, "hello")) + { + got := os.Getenv(key) + testassert.Require(t, got == "hello", "env %s = %q, want hello", key, got) } + testassert.Require(t, m.IsManaged(key), "key not managed after TrySet") data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(data), key+"=hello") || !strings.Contains(string(data), "# keep") { - t.Fatalf("file = %q, want the key line and the preserved comment", data) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(data), key+"=hello") && strings.Contains(string(data), "# keep"), "file = %q, want the key line and the preserved comment", data) }) t.Run("contention_mutates_nothing", func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte("KEEP=me\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("KEEP=me\n"), 0o600)) key := envKeyForTest(t, "TRYSET_CONTEND") _ = os.Unsetenv(key) m := NewManagedEnvForTest(path) holder, ok, err := atomicfs.TryAcquire(envLockPath(path)) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() - if err := m.TrySet(key, "secret"); err == nil || !strings.Contains(err.Error(), "retry") { - t.Fatalf("TrySet under contention = %v, want a retryable error", err) + { + err := m.TrySet(key, "secret") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "retry"), "TrySet under contention = %v, want a retryable error", err) } - if _, set := os.LookupEnv(key); set { - t.Fatalf("process env %s was set under contention", key) - } - if m.IsManaged(key) { - t.Fatal("key was added to the managed set under contention") + { + _, set := os.LookupEnv(key) + testassert.Require(t, !set, "process env %s was set under contention", key) } + testassert.Require(t, !m.IsManaged(key), "key was added to the managed set under contention") data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if strings.Contains(string(data), key) { - t.Fatalf("file mutated under contention: %q", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), key), "file mutated under contention: %q", data) }) t.Run("external_key_refused", func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) key := envKeyForTest(t, "TRYSET_EXTERNAL") t.Setenv(key, "external") m := NewManagedEnvForTest(path) err := m.TrySet(key, "overwrite") - if !errors.Is(err, ErrExternalKey) { - t.Fatalf("TrySet err = %v, want ErrExternalKey", err) - } - if got := os.Getenv(key); got != "external" { - t.Fatalf("env %s = %q, want the external value untouched", key, got) + testassert.Require(t, errors.Is(err, ErrExternalKey), "TrySet err = %v, want ErrExternalKey", err) + { + got := os.Getenv(key) + testassert.Require(t, got == "external", "env %s = %q, want the external value untouched", key, got) } }) } @@ -711,74 +569,55 @@ func TestManagedEnvTryRemoveRows(t *testing.T) { path := filepath.Join(dir, ".env") key := envKeyForTest(t, "TRYRM_OK") seed := "# header\n" + key + "=val\nKEEP=me\n" - if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(seed), 0o600)) t.Setenv(key, "val") m := NewManagedEnvForTest(path) m.managed[key] = struct{}{} - if err := m.TryRemove(key); err != nil { - t.Fatalf("TryRemove: %v", err) - } - if _, ok := os.LookupEnv(key); ok { - t.Fatalf("env %s still set after TryRemove", key) + testassert.NoError(t, m.TryRemove(key)) + { + _, ok := os.LookupEnv(key) + testassert.Require(t, !ok, "env %s still set after TryRemove", key) } data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) body := string(data) - if strings.Contains(body, key+"=") { - t.Fatalf("key line still in file: %q", body) - } - if !strings.Contains(body, "KEEP=me") { - t.Fatalf("unrelated line lost: %q", body) - } + testassert.Require(t, !strings.Contains(body, key+"=") && strings.Contains(body, "KEEP=me"), "key line still in file: %q; unrelated line lost: %q", body, body) }) t.Run("contention_mutates_nothing", func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") key := envKeyForTest(t, "TRYRM_CONTEND") seed := "# header\n" + key + "=val\nKEEP=me\n" - if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(seed), 0o600)) t.Setenv(key, "val") m := NewManagedEnvForTest(path) m.managed[key] = struct{}{} holder, ok, err := atomicfs.TryAcquire(envLockPath(path)) - if err != nil || !ok { - t.Fatalf("seed TryAcquire: (%v, %v)", ok, err) - } + testassert.Require(t, err == nil && ok, "seed TryAcquire: (%v, %v)", ok, err) defer holder.Release() - if err := m.TryRemove(key); err == nil || !strings.Contains(err.Error(), "retry") { - t.Fatalf("TryRemove under contention = %v, want a retryable error", err) + { + err := m.TryRemove(key) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "retry"), "TryRemove under contention = %v, want a retryable error", err) } - if got := os.Getenv(key); got != "val" { - t.Fatalf("env %s = %q, want val untouched", key, got) + { + got := os.Getenv(key) + testassert.Require(t, got == "val", "env %s = %q, want val untouched", key, got) } data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(data), key+"=val") { - t.Fatalf("file mutated under contention: %q", data) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(data), key+"=val"), "file mutated under contention: %q", data) }) t.Run("unmanaged_noop", func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") key := envKeyForTest(t, "TRYRM_NOOP") - if err := os.WriteFile(path, []byte("KEEP=me\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("KEEP=me\n"), 0o600)) t.Setenv(key, "external") m := NewManagedEnvForTest(path) - if err := m.TryRemove(key); err != nil { - t.Fatalf("TryRemove unmanaged: %v", err) - } - if got := os.Getenv(key); got != "external" { - t.Fatalf("external env %s was touched: %q", key, got) + testassert.NoError(t, m.TryRemove(key)) + { + got := os.Getenv(key) + testassert.Require(t, got == "external", "external env %s was touched: %q", key, got) } }) } @@ -882,14 +721,13 @@ func TestManagedEnvSetBlocksUntilForeignLockReleases(t *testing.T) { } select { case err := <-done: - if err != nil { - t.Fatalf("Set after the holder released: %v", err) - } + testassert.NoError(t, err) case <-time.After(5 * time.Second): t.Fatal("Set did not complete after the holder released the env lock") } - if got := os.Getenv(key); got != "hello" { - t.Fatalf("env %s = %q, want hello", key, got) + { + got := os.Getenv(key) + testassert.Require(t, got == "hello", "env %s = %q, want hello", key, got) } } @@ -899,9 +737,7 @@ func captureStderr(t *testing.T) func() string { t.Helper() old := os.Stderr f, err := os.CreateTemp(t.TempDir(), "stderr-*") - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) os.Stderr = f t.Cleanup(func() { os.Stderr = old @@ -938,23 +774,19 @@ func TestDotEnvWritesReportLockReleaseFailure(t *testing.T) { m := NewManagedEnvForTest(path) stderr := captureStderr(t) - if err := m.Set(key, "hello"); err != nil { - t.Fatalf("Set: %v", err) - } + testassert.NoError(t, m.Set(key, "hello")) // The committed callback result is preserved: the write landed and // the process env was updated despite the release failure. - if got := os.Getenv(key); got != "hello" { - t.Fatalf("env %s = %q, want hello", key, got) + { + got := os.Getenv(key) + testassert.Require(t, got == "hello", "env %s = %q, want hello", key, got) } data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(data), key+"=hello") { - t.Fatalf("file missing key line: %q", data) - } - if out := stderr(); out != "lightcode: release lock "+lockPath+": "+injected.Error()+"\n" { - t.Fatalf("write stderr = %q, want the one exact release diagnostic", out) + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(data), key+"=hello"), "file missing key line: %q", data) + { + out := stderr() + testassert.Require(t, out == "lightcode: release lock "+lockPath+": "+injected.Error()+"\n", "write stderr = %q, want the one exact release diagnostic", out) } }) @@ -964,30 +796,20 @@ func TestDotEnvWritesReportLockReleaseFailure(t *testing.T) { lockPath := filepath.Join(dir, ".locks", "env.lock") key := envKeyForTest(t, "RLF_REMOVE") seed := "# header\n" + key + "=val\nKEEP=me\n" - if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(seed), 0o600)) _ = os.Setenv(key, "val") m := NewManagedEnvForTest(path) m.managed[key] = struct{}{} stderr := captureStderr(t) - if err := m.Remove(key); err != nil { - t.Fatalf("Remove: %v", err) - } + testassert.NoError(t, m.Remove(key)) data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) body := string(data) - if strings.Contains(body, key+"=") { - t.Fatalf("key line still in file: %q", body) - } - if !strings.Contains(body, "KEEP=me") { - t.Fatalf("unrelated line lost: %q", body) - } - if out := stderr(); out != "lightcode: release lock "+lockPath+": "+injected.Error()+"\n" { - t.Fatalf("remove stderr = %q, want the one exact release diagnostic", out) + testassert.Require(t, !strings.Contains(body, key+"=") && strings.Contains(body, "KEEP=me"), "key line still in file: %q; unrelated line lost: %q", body, body) + { + out := stderr() + testassert.Require(t, out == "lightcode: release lock "+lockPath+": "+injected.Error()+"\n", "remove stderr = %q, want the one exact release diagnostic", out) } }) } @@ -997,30 +819,29 @@ func TestEnvWithoutKeys(t *testing.T) { env := []string{"MANAGED_KEY=1", "managed_key=2", "PATH=/bin"} got := EnvWithoutKeys(env, []string{"MANAGED_KEY"}) want := []string{"managed_key=2", "PATH=/bin"} - if !slices.Equal(got, want) { - t.Fatalf("EnvWithoutKeys = %v, want %v", got, want) - } + testassert.Require(t, slices.Equal(got, want), "EnvWithoutKeys = %v, want %v", got, want) }) t.Run("external name retained", func(t *testing.T) { env := []string{"EXTERNAL_TOKEN=abc", "MANAGED_TOKEN=xyz"} got := EnvWithoutKeys(env, []string{"MANAGED_TOKEN"}) want := []string{"EXTERNAL_TOKEN=abc"} - if !slices.Equal(got, want) { - t.Fatalf("EnvWithoutKeys = %v, want %v", got, want) - } + testassert.Require(t, slices.Equal(got, want), "EnvWithoutKeys = %v, want %v", got, want) }) t.Run("nil inputs", func(t *testing.T) { - if got := EnvWithoutKeys(nil, nil); len(got) != 0 { - t.Fatalf("EnvWithoutKeys(nil, nil) = %v, want empty", got) + { + got := EnvWithoutKeys(nil, nil) + testassert.Require(t, len(got) == 0, "EnvWithoutKeys(nil, nil) = %v, want empty", got) } env := []string{"A=1", "B=2"} - if got := EnvWithoutKeys(env, nil); !slices.Equal(got, env) { - t.Fatalf("EnvWithoutKeys(env, nil) = %v, want %v", got, env) + { + got := EnvWithoutKeys(env, nil) + testassert.Require(t, slices.Equal(got, env), "EnvWithoutKeys(env, nil) = %v, want %v", got, env) } - if got := EnvWithoutKeys(nil, []string{"A"}); len(got) != 0 { - t.Fatalf("EnvWithoutKeys(nil, keys) = %v, want empty", got) + { + got := EnvWithoutKeys(nil, []string{"A"}) + testassert.Require(t, len(got) == 0, "EnvWithoutKeys(nil, keys) = %v, want empty", got) } }) } @@ -1031,37 +852,29 @@ func TestLoadDotEnvScannerErrorReturnsPartialManagedSet(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) key := envKeyForTest(t, "SCANNER_PARTIAL") path := filepath.Join(dir, ".env") body := key + "=injected\nHUGE=" + strings.Repeat("A", bufio.MaxScanTokenSize+1) + "\n" - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) m, err := LoadDotEnv() - if err == nil { - t.Fatal("LoadDotEnv: want scanner error from the oversized line, got nil") - } - if m == nil { - t.Fatal("LoadDotEnv: want a partial managed set alongside the scanner error, got nil") - } - if got := os.Getenv(key); got != "injected" { - t.Fatalf("env %s = %q, want the value injected before the scanner error", key, got) + testassert.Require(t, err != nil, "LoadDotEnv: want scanner error from the oversized line, got nil") + testassert.Require(t, m != nil, "LoadDotEnv: want a partial managed set alongside the scanner error, got nil") + { + got := os.Getenv(key) + testassert.Require(t, got == "injected", "env %s = %q, want the value injected before the scanner error", key, got) } - if keys := m.ManagedKeys(); !slices.Contains(keys, key) { - t.Fatalf("ManagedKeys = %v, want to contain %s", keys, key) + { + keys := m.ManagedKeys() + testassert.Require(t, slices.Contains(keys, key), "ManagedKeys = %v, want to contain %s", keys, key) } } func TestManagedEnvSubprocessEnvFiltersCurrentManagedSet(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) m := NewManagedEnvForTest(path) external := envKeyForTest(t, "SUBPROC_EXTERNAL") managed := envKeyForTest(t, "SUBPROC_MANAGED") @@ -1069,61 +882,35 @@ func TestManagedEnvSubprocessEnvFiltersCurrentManagedSet(t *testing.T) { // Before any managed write: everything passes through. env := m.SubprocessEnv() - if env == nil { - t.Fatal("SubprocessEnv = nil, want a non-nil slice") - } - if !slices.Contains(env, external+"=sibling-value") { - t.Fatalf("SubprocessEnv = %v, want the external sibling present", env) - } - if slices.Contains(env, managed+"=") { - t.Fatalf("SubprocessEnv = %v, want the unset managed name absent", env) - } + testassert.Require(t, env != nil, "SubprocessEnv = nil, want a non-nil slice") + testassert.Require(t, slices.Contains(env, external+"=sibling-value") && !slices.Contains(env, managed+"="), "SubprocessEnv = %v, want the external sibling present; SubprocessEnv = %v, want the unset managed name absent", env, env) - if err := m.TrySet(managed, "managed-secret"); err != nil { - t.Fatalf("TrySet: %v", err) - } + testassert.NoError(t, m.TrySet(managed, "managed-secret")) env = m.SubprocessEnv() - if slices.Contains(env, managed+"=managed-secret") { - t.Fatalf("SubprocessEnv = %v, want the managed key scrubbed", env) - } - if !slices.Contains(env, external+"=sibling-value") { - t.Fatalf("SubprocessEnv = %v, want the external sibling still present", env) - } + testassert.Require(t, !slices.Contains(env, managed+"=managed-secret") && slices.Contains(env, external+"=sibling-value"), "SubprocessEnv = %v, want the managed key scrubbed; SubprocessEnv = %v, want the external sibling still present", env, env) // The snapshot is one owned copy: mutating it never reaches the manager // or the process env. if len(env) > 0 { env[0] = "MUTED=1" } - if !slices.Contains(m.SubprocessEnv(), external+"=sibling-value") { - t.Fatal("a mutated snapshot changed the manager's view") - } + testassert.Require(t, slices.Contains(m.SubprocessEnv(), external+"=sibling-value"), "a mutated snapshot changed the manager's view") - if err := m.TryRemove(managed); err != nil { - t.Fatalf("TryRemove: %v", err) - } - if slices.Contains(m.SubprocessEnv(), managed+"=managed-secret") { - t.Fatal("the removed key is back in the snapshot") - } + testassert.NoError(t, m.TryRemove(managed)) + testassert.Require(t, !slices.Contains(m.SubprocessEnv(), managed+"=managed-secret"), "the removed key is back in the snapshot") } func TestManagedEnvSubprocessEnvNilReceiver(t *testing.T) { var m *ManagedEnv env := m.SubprocessEnv() - if env == nil { - t.Fatal("nil receiver returned nil, want a non-nil empty slice") - } - if len(env) != 0 { - t.Fatalf("nil receiver returned %d entries, want an empty slice", len(env)) - } + testassert.Require(t, env != nil, "nil receiver returned nil, want a non-nil empty slice") + testassert.Equal(t, len(env), 0) } func TestSubprocessEnvConcurrentWithManagedWrites(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, ".env") - if err := os.WriteFile(path, []byte(""), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(""), 0o600)) m := NewManagedEnvForTest(path) key := envKeyForTest(t, "SUBPROC_RACE") t.Setenv(key, "") @@ -1131,9 +918,7 @@ func TestSubprocessEnvConcurrentWithManagedWrites(t *testing.T) { // One synchronous managed write proves the writer path works before the // consumer loop, so a failed or unscheduled writer cannot pass vacuously. - if err := m.TrySet(key, "managed-secret"); err != nil { - t.Fatalf("initial TrySet: %v", err) - } + testassert.NoError(t, m.TrySet(key, "managed-secret")) var wg sync.WaitGroup stop := make(chan struct{}) @@ -1178,9 +963,7 @@ func TestSubprocessEnvConcurrentWithManagedWrites(t *testing.T) { // no key= prefix may ever appear in the snapshot, not even the exact // managed secret. for _, entry := range env { - if strings.HasPrefix(entry, key+"=") { - t.Fatalf("snapshot leaked a managed key entry %q", entry) - } + testassert.Require(t, !strings.HasPrefix(entry, key+"="), "snapshot leaked a managed key entry %q", entry) } } stopWriter() @@ -1191,12 +974,8 @@ func TestSubprocessEnvConcurrentWithManagedWrites(t *testing.T) { default: } // After the final state settles, the snapshot reflects it exactly. - if err := m.TrySet(key, "managed-secret"); err != nil { - t.Fatalf("TrySet: %v", err) - } + testassert.NoError(t, m.TrySet(key, "managed-secret")) for _, entry := range m.SubprocessEnv() { - if strings.HasPrefix(entry, key+"=") { - t.Fatalf("the connected key leaked into the snapshot: %q", entry) - } + testassert.Require(t, !strings.HasPrefix(entry, key+"="), "the connected key leaked into the snapshot: %q", entry) } } diff --git a/internal/config/first_run_creation_test.go b/internal/config/first_run_creation_test.go index ec243f5f..d89fc6bf 100644 --- a/internal/config/first_run_creation_test.go +++ b/internal/config/first_run_creation_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestFirstRunCreationPreservesExistingFiles verifies the retained first-run @@ -18,19 +19,14 @@ func TestFirstRunCreationPreservesExistingFiles(t *testing.T) { t.Setenv("HOME", home) t.Cleanup(func() { os.Unsetenv("LIGHTCODE_TEST_USERKEY") }) path := filepath.Join(home, ".lightcode", ".env") - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) custom := "# user managed file\nLIGHTCODE_TEST_USERKEY=1\n" - if err := os.WriteFile(path, []byte(custom), 0o600); err != nil { - t.Fatal(err) - } - if _, err := config.LoadDotEnv(); err != nil { - t.Fatalf("LoadDotEnv: %v", err) + testassert.NoError(t, os.WriteFile(path, []byte(custom), 0o600)) + { + _, err := config.LoadDotEnv() + testassert.NoError(t, err) } after, _ := os.ReadFile(path) - if string(after) != custom { - t.Fatalf("existing .env was overwritten: %q", after) - } + testassert.Require(t, string(after) == custom, "existing .env was overwritten: %q", after) }) } diff --git a/internal/config/keysource_test.go b/internal/config/keysource_test.go index a6c1f44c..eeacf1e7 100644 --- a/internal/config/keysource_test.go +++ b/internal/config/keysource_test.go @@ -1,6 +1,9 @@ package config -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) func TestClassifyKeySourceOrder(t *testing.T) { yes := func(string) bool { return true } @@ -21,9 +24,7 @@ func TestClassifyKeySourceOrder(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - if got := ClassifyKeySource(tc.apiKeyEnv, tc.external, tc.managed); got != tc.want { - t.Fatalf("ClassifyKeySource = %q, want %q", got, tc.want) - } + testassert.Equal(t, ClassifyKeySource(tc.apiKeyEnv, tc.external, tc.managed), tc.want) }) } } @@ -37,15 +38,9 @@ func TestClassifyKeySourceAgentContext(t *testing.T) { managed := func(name string) bool { return managedSet[name] } external := func(name string) bool { return envSet[name] && !managed(name) } - if got := ClassifyKeySource("MANAGED_KEY", external, managed); got != KeySourceManaged { - t.Fatalf("managed+env key = %q, want %q", got, KeySourceManaged) - } - if got := ClassifyKeySource("SHELL_KEY", external, managed); got != KeySourceExternal { - t.Fatalf("shell key = %q, want %q", got, KeySourceExternal) - } - if got := ClassifyKeySource("UNSET_KEY", external, managed); got != KeySourceNone { - t.Fatalf("unset key = %q, want %q", got, KeySourceNone) - } + testassert.Equal(t, ClassifyKeySource("MANAGED_KEY", external, managed), KeySourceManaged) + testassert.Equal(t, ClassifyKeySource("SHELL_KEY", external, managed), KeySourceExternal) + testassert.Equal(t, ClassifyKeySource("UNSET_KEY", external, managed), KeySourceNone) } // Doctor-context adapter: LoadDotEnv never ran, so doctor mirrors its @@ -65,19 +60,9 @@ func TestClassifyKeySourceDoctorContext(t *testing.T) { return inDotEnv } - if got := ClassifyKeySource("BOTH_KEY", external, managed); got != KeySourceExternal { - t.Fatalf("shell+.env key = %q, want %q", got, KeySourceExternal) - } - if got := ClassifyKeySource("DOTENV_KEY", external, managed); got != KeySourceManaged { - t.Fatalf(".env-only key = %q, want %q", got, KeySourceManaged) - } - if got := ClassifyKeySource("EMPTY_DOTENV_KEY", external, managed); got != KeySourceManaged { - t.Fatalf("empty .env value = %q, want %q (LoadDotEnv marks it managed)", got, KeySourceManaged) - } - if got := ClassifyKeySource("EMPTY_SHELL_KEY", external, managed); got != KeySourceNone { - t.Fatalf("empty shell export = %q, want %q", got, KeySourceNone) - } - if got := ClassifyKeySource("OTHER_KEY", external, managed); got != KeySourceNone { - t.Fatalf("absent key = %q, want %q", got, KeySourceNone) - } + testassert.Equal(t, ClassifyKeySource("BOTH_KEY", external, managed), KeySourceExternal) + testassert.Equal(t, ClassifyKeySource("DOTENV_KEY", external, managed), KeySourceManaged) + testassert.Equal(t, ClassifyKeySource("EMPTY_DOTENV_KEY", external, managed), KeySourceManaged) + testassert.Equal(t, ClassifyKeySource("EMPTY_SHELL_KEY", external, managed), KeySourceNone) + testassert.Equal(t, ClassifyKeySource("OTHER_KEY", external, managed), KeySourceNone) } diff --git a/internal/config/managedenv_cross_test.go b/internal/config/managedenv_cross_test.go index e8b6e0c5..43514a43 100644 --- a/internal/config/managedenv_cross_test.go +++ b/internal/config/managedenv_cross_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "sync" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestManagedEnvCrossInstanceOperations proves two independent managers (as @@ -34,44 +36,40 @@ func TestManagedEnvCrossInstanceOperations(t *testing.T) { key := k go func() { defer wg.Done() - if err := newMgr().Set(key, "v-"+key); err != nil { - t.Errorf("Set %s: %v", key, err) + { + err := newMgr().Set(key, "v-"+key) + testassert.Check(t, err == nil, "Set %s: %v", key, err) } }() } wg.Wait() present, _, err := ReadDotEnvKeys(envPath) - if err != nil { - t.Fatalf("ReadDotEnvKeys: %v", err) - } + testassert.NoError(t, err) for _, k := range keys { - if _, ok := present[k]; !ok { - t.Fatalf("key %s lost from .env; present=%v", k, present) + { + _, ok := present[k] + testassert.Require(t, ok, "key %s lost from .env; present=%v", k, present) } } // A managed key is removed. remover := newMgr() remover.managed[keys[0]] = struct{}{} - if err := remover.Remove(keys[0]); err != nil { - t.Fatalf("Remove managed: %v", err) - } + testassert.NoError(t, remover.Remove(keys[0])) present, _, _ = ReadDotEnvKeys(envPath) - if _, ok := present[keys[0]]; ok { - t.Fatalf("removed key %s still present", keys[0]) + { + _, ok := present[keys[0]] + testassert.Require(t, !ok, "removed key %s still present", keys[0]) } // An unmanaged removal (fresh manager does not own the key) is a no-op. before, _, _ := ReadDotEnvKeys(envPath) - if err := newMgr().Remove(keys[1]); err != nil { - t.Fatalf("Remove unmanaged: %v", err) - } + testassert.NoError(t, newMgr().Remove(keys[1])) after, _, _ := ReadDotEnvKeys(envPath) - if len(before) != len(after) { - t.Fatalf("unmanaged removal changed the file: %v -> %v", before, after) - } - if _, ok := after[keys[1]]; !ok { - t.Fatalf("unmanaged key %s wrongly removed", keys[1]) + testassert.Require(t, len(before) == len(after), "unmanaged removal changed the file: %v -> %v", before, after) + { + _, ok := after[keys[1]] + testassert.Require(t, ok, "unmanaged key %s wrongly removed", keys[1]) } } diff --git a/internal/config/permissions_test.go b/internal/config/permissions_test.go index acf8074b..f7707019 100644 --- a/internal/config/permissions_test.go +++ b/internal/config/permissions_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestWorkspacePermissionDirID proves the retained storage-key derivation: @@ -13,44 +15,32 @@ import ( // paths, and independent of any project record or filesystem state. func TestWorkspacePermissionDirID(t *testing.T) { got, err := WorkspacePermissionDirID("/tmp/demo") - if err != nil { - t.Fatalf("WorkspacePermissionDirID: %v", err) - } - if want := "p-84a8cd7d7a26dbdf6a21211d423355ec1989088651da3ce8fb577dc9d95ffae4"; got != want { - t.Fatalf("ID = %q, want the pinned SHA-256 derivation %q", got, want) + testassert.NoError(t, err) + { + want := "p-84a8cd7d7a26dbdf6a21211d423355ec1989088651da3ce8fb577dc9d95ffae4" + testassert.Equal(t, got, want) } for _, equivalent := range []string{"/tmp/demo/", "/tmp/./demo", "/tmp/../tmp/demo"} { same, err := WorkspacePermissionDirID(equivalent) - if err != nil { - t.Fatalf("WorkspacePermissionDirID(%q): %v", equivalent, err) - } - if same != got { - t.Fatalf("ID(%q) = %q, want the same cleaned-absolute derivation %q", equivalent, same, got) - } + testassert.Require(t, err == nil, "WorkspacePermissionDirID(%q): %v", equivalent, err) + testassert.Require(t, same == got, "ID(%q) = %q, want the same cleaned-absolute derivation %q", equivalent, same, got) } other, err := WorkspacePermissionDirID("/tmp/other") - if err != nil { - t.Fatalf("WorkspacePermissionDirID(/tmp/other): %v", err) - } - if other == got || !strings.HasPrefix(other, "p-") || len(other) != len("p-")+64 { - t.Fatalf("ID(/tmp/other) = %q, want a distinct p-<64 hex> storage key", other) - } + testassert.NoError(t, err) + testassert.Require(t, other != got && strings.HasPrefix(other, "p-") && len(other) == len("p-")+64, "ID(/tmp/other) = %q, want a distinct p-<64 hex> storage key", other) } // TestProjectsDir proves the inventory root stays home-based. func TestProjectsDir(t *testing.T) { t.Setenv("HOME", t.TempDir()) root, err := ProjectsDir() - if err != nil { - t.Fatalf("ProjectsDir: %v", err) - } + testassert.NoError(t, err) home, err := os.UserHomeDir() - if err != nil { - t.Fatalf("UserHomeDir: %v", err) - } - if want := filepath.Join(home, ".lightcode", "projects"); root != want { - t.Fatalf("ProjectsDir = %q, want %q", root, want) + testassert.NoError(t, err) + { + want := filepath.Join(home, ".lightcode", "projects") + testassert.Equal(t, root, want) } } diff --git a/internal/desktopclient/coordinated_test.go b/internal/desktopclient/coordinated_test.go index 2f447aa6..2527d7d1 100644 --- a/internal/desktopclient/coordinated_test.go +++ b/internal/desktopclient/coordinated_test.go @@ -26,6 +26,7 @@ import ( "github.com/MMinasyan/lightcode/internal/cliclient" "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" "golang.org/x/sys/unix" @@ -146,9 +147,7 @@ func (p *pipeTerminal) Write(data []byte) (int, error) { func startPipeTerminal(t *testing.T, opts clienthost.Options, workspace string) *pipeTerminal { t.Helper() inR, inW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) p := &pipeTerminal{in: inW, done: make(chan error, 1), written: make(chan struct{}, 1)} ctx, cancel := context.WithCancel(context.Background()) cli := cliclient.New(cliclient.Options{Startup: opts, Workspace: workspace}) @@ -165,8 +164,9 @@ func startPipeTerminal(t *testing.T, opts clienthost.Options, workspace string) func (p *pipeTerminal) send(t *testing.T, text string) { t.Helper() - if _, err := io.WriteString(p.in, text+"\r"); err != nil { - t.Fatal(err) + { + _, err := io.WriteString(p.in, text+"\r") + testassert.NoError(t, err) } } @@ -255,28 +255,24 @@ func (p *pipeACP) call(t *testing.T, method string, params any, target any) { t.Helper() p.next++ line, err := json.Marshal(map[string]any{"jsonrpc": "2.0", "id": p.next, "method": method, "params": params}) - if err != nil { - t.Fatal(err) - } - if _, err := p.in.Write(append(line, '\n')); err != nil { - t.Fatal(err) + testassert.NoError(t, err) + { + _, err := p.in.Write(append(line, '\n')) + testassert.NoError(t, err) } for { frame := recvBounded(t, p.frames) - if frame == nil { - t.Fatal("ACP output closed") - } + testassert.Require(t, frame != nil, "ACP output closed") p.seen = append(p.seen, frame) if string(frame["id"]) != fmt.Sprint(p.next) { continue } - if raw := frame["error"]; len(raw) != 0 { - t.Fatalf("ACP %s: %s", method, raw) + { + raw := frame["error"] + testassert.Require(t, len(raw) == 0, "ACP %s: %s", method, raw) } if target != nil { - if err := json.Unmarshal(frame["result"], target); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal(frame["result"], target)) } return } @@ -293,15 +289,9 @@ func desktopHydrate(t *testing.T, host *Host, info ConnectionInfo, id string) pr t.Helper() var h protocol.Hydration response, err := host.Call(info.Generation, "GetSessionHydration", `{"session_id":"`+id+`"}`) - if err != nil || response.Status != http.StatusOK { - t.Fatalf("desktop hydration: %+v, %v", response, err) - } - if err := json.Unmarshal([]byte(response.Body), &h); err != nil { - t.Fatal(err) - } - if h.Session.SessionId != id || h.SessionRevision.InstanceId != info.InstanceID { - t.Fatalf("foreign hydration: %+v", h.SessionRevision) - } + testassert.Require(t, err == nil && response.Status == http.StatusOK, "desktop hydration: %+v, %v", response, err) + testassert.NoError(t, json.Unmarshal([]byte(response.Body), &h)) + testassert.Require(t, h.Session.SessionId == id && h.SessionRevision.InstanceId == info.InstanceID, "foreign hydration: %+v", h.SessionRevision) return h } @@ -345,9 +335,7 @@ func openNativeFIFO(t *testing.T, path string) *os.File { t.Cleanup(func() { _ = f.Close() }) return f } - if !errors.Is(err, unix.ENXIO) || time.Now().After(deadline) { - t.Fatalf("native shell did not reach FIFO read: %v", err) - } + testassert.Require(t, errors.Is(err, unix.ENXIO) && !time.Now().After(deadline), "native shell did not reach FIFO read: %v", err) time.Sleep(10 * time.Millisecond) } } @@ -360,9 +348,7 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { waves := make([]*nativeWave, 2) for n := range waves { waves[n] = &nativeWave{fifo: filepath.Join(workspace, fmt.Sprintf("job-%d.fifo", n)), rootArrived: make(chan struct{}), childArrived: make(chan struct{}), release: make(chan struct{})} - if err := unix.Mkfifo(waves[n].fifo, 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, unix.Mkfifo(waves[n].fifo, 0o600)) defer waves[n].unblock() } model := &nativeModel{waves: waves} @@ -381,23 +367,18 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { openOwner := func() string { var err error owner, err = runtime.Open(context.Background(), runtime.Options{DataDir: dataDir, ConfigPath: configPath, Plugins: builtin.Plugins()}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) server, err := owner.OpenProtocol(context.Background()) - if err != nil { - t.Fatal(err) - } - if err := server.PublishDiscovery(filepath.Join(dataDir, "runtime.json")); err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) + testassert.NoError(t, server.PublishDiscovery(filepath.Join(dataDir, "runtime.json"))) return server.Endpoint() } oldEndpoint := openOwner() defer func() { if owner != nil { - if err := owner.Close(context.Background()); err != nil { - t.Error(err) + { + err := owner.Close(context.Background()) + testassert.Check(t, err == nil, "%v", err) } } }() @@ -405,26 +386,18 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { // runs no tests is a non-owning, immediately exiting contender. It opens no // state; common startup keeps probing the real publication across the gap. executable, err := os.Executable() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) opts := clienthost.Options{DataDir: dataDir, ConfigPath: configPath, Executable: executable, PrefixArgs: []string{"-test.run=^$", "--"}} host := New(opts, workspace) host.startup(context.Background()) defer host.shutdown(context.Background()) info, err := host.Open() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) oldInstance := info.InstanceID created, err := host.Call(info.Generation, "CreateSession", fmt.Sprintf(`{"body":{"workspace":%q,"agent_type":"primary"}}`, workspace)) - if err != nil || created.Status != http.StatusCreated { - t.Fatalf("create root: %+v, %v", created, err) - } + testassert.Require(t, err == nil && created.Status == http.StatusCreated, "create root: %+v, %v", created, err) var root protocol.Session - if err := json.Unmarshal([]byte(created.Body), &root); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal([]byte(created.Body), &root)) // Drain the actual native event pull, retaining deliveries and terminal loss. events := make(chan string, 256) lost := make(chan error, 1) @@ -443,9 +416,7 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { Runtime protocol.Health `json:"runtime"` } acp.call(t, "initialize", map[string]any{}, &initialized) - if initialized.Runtime.InstanceId != oldInstance { - t.Fatal("ACP attached another owner") - } + testassert.Require(t, initialized.Runtime.InstanceId == oldInstance, "ACP attached another owner") terminal := startPipeTerminal(t, opts, workspace) terminal.await(t, 0, func(text string) bool { return strings.Contains(text, "Authoritative refreshed range — "+root.SessionId) && strings.Contains(text, "Future: primary / prov/root") @@ -462,9 +433,7 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { return h.ActiveOperation != nil && len(h.Background) == 2 && len(h.Pending.Queued) == 1 }) queuedID, rootOperation = h.Pending.Queued[0].OperationId, h.ActiveOperation.OperationId - if len(queuedID) != 32 || queuedID == rootOperation { - t.Fatalf("caller identity: %q, %q", queuedID, rootOperation) - } + testassert.Require(t, len(queuedID) == 32 && queuedID != rootOperation, "caller identity: %q, %q", queuedID, rootOperation) childID = "" jobID := "" for _, member := range h.Background { @@ -475,50 +444,32 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { jobID = member.Id } } - if childID == "" || jobID == "" { - t.Fatalf("native members: %+v", h.Background) - } + testassert.Require(t, childID != "" && jobID != "", "native members: %+v", h.Background) child := desktopHydrate(t, host, info, childID) - if child.ActiveOperation == nil || child.Session.ParentSessionId == nil || *child.Session.ParentSessionId != root.SessionId { - t.Fatal("native child not active or not owned by root") - } - if !reflect.DeepEqual(h, acp.hydrate(t, root.SessionId)) || !reflect.DeepEqual(child, acp.hydrate(t, childID)) { - t.Fatal("desktop/ACP gated native facts differ") - } + testassert.Require(t, child.ActiveOperation != nil && child.Session.ParentSessionId != nil && *child.Session.ParentSessionId == root.SessionId, "native child not active or not owned by root") + testassert.Require(t, reflect.DeepEqual(h, acp.hydrate(t, root.SessionId)) && reflect.DeepEqual(child, acp.hydrate(t, childID)), "desktop/ACP gated native facts differ") return fifo, h, child } fifo, heldRoot, heldChild := startWave(0, terminal) _ = terminal.in.Close() - if err := recvBounded(t, terminal.done); err != nil { - t.Fatalf("terminal EOF: %v", err) - } - if !reflect.DeepEqual(heldRoot, rootRead()) || !reflect.DeepEqual(heldChild, desktopHydrate(t, host, info, childID)) { - t.Fatal("terminal departure changed native work or pending caller") - } - if !reflect.DeepEqual(heldRoot, acp.hydrate(t, root.SessionId)) || !reflect.DeepEqual(heldChild, acp.hydrate(t, childID)) { - t.Fatal("remaining ACP not healthy after terminal departure") - } + testassert.NoError(t, recvBounded(t, terminal.done)) + testassert.Require(t, reflect.DeepEqual(heldRoot, rootRead()) && reflect.DeepEqual(heldChild, desktopHydrate(t, host, info, childID)), "terminal departure changed native work or pending caller") + testassert.Require(t, reflect.DeepEqual(heldRoot, acp.hydrate(t, root.SessionId)) && reflect.DeepEqual(heldChild, acp.hydrate(t, childID)), "remaining ACP not healthy after terminal departure") model.mu.Lock() canceled := model.canceled model.mu.Unlock() - if canceled != 0 { - t.Fatal("departure canceled a native model") - } + testassert.Require(t, canceled == 0, "departure canceled a native model") fresh := startPipeTerminal(t, opts, workspace) fresh.await(t, 0, func(text string) bool { return strings.Contains(text, "Authoritative refreshed range — "+root.SessionId) }) - if !reflect.DeepEqual(heldRoot, fresh.hydrate(t, root.SessionId)) || !reflect.DeepEqual(heldChild, fresh.hydrate(t, childID)) { - t.Fatal("same-owner fresh terminal root/child reads differ") - } + testassert.Require(t, reflect.DeepEqual(heldRoot, fresh.hydrate(t, root.SessionId)) && reflect.DeepEqual(heldChild, fresh.hydrate(t, childID)), "same-owner fresh terminal root/child reads differ") // Actual streams delivered native Session hints to both observing hosts. seenDesktop := make(map[string]bool) for len(seenDesktop) != 2 { raw := recvBounded(t, events) var event protocol.Event - if err := json.Unmarshal([]byte(raw), &event); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal([]byte(raw), &event)) changed, err := event.AsSessionChangedEvent() if err != nil { continue @@ -537,9 +488,7 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { for _, frame := range acp.seen { if string(frame["method"]) == `"runtime/event"` { var event protocol.Event - if err := json.Unmarshal(frame["params"], &event); err != nil { - t.Fatal(err) - } + testassert.NoError(t, json.Unmarshal(frame["params"], &event)) changed, err := event.AsSessionChangedEvent() if err != nil { continue @@ -550,12 +499,11 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { } } } - if len(seenACP) != 2 { - t.Fatal("ACP did not forward native root and child events") - } + testassert.Require(t, len(seenACP) == 2, "ACP did not forward native root and child events") waves[0].unblock() - if _, err := io.WriteString(fifo, "complete\n"); err != nil { - t.Fatal(err) + { + _, err := io.WriteString(fifo, "complete\n") + testassert.NoError(t, err) } _ = fifo.Close() // Terminal settlement is not an execution-retirement barrier. Read current @@ -580,9 +528,7 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { admitted = true } } - if !admitted { - t.Fatal("queued caller did not become its successful Operation") - } + testassert.Require(t, admitted, "queued caller did not become its successful Operation") jobCompleted := false for _, item := range converged.Conversation.Items { input, err := item.AsInputItem() @@ -596,31 +542,25 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { } } } - if !jobCompleted { - t.Fatal("native Job did not publish its successful process completion") - } + testassert.Require(t, jobCompleted, "native Job did not publish its successful process completion") // A second native batch establishes active root/child/Job and an unadmitted // caller when the test owner explicitly closes. No client controls it. _, lossRoot, lossChild := startWave(1, fresh) lossQueued, lossChildID := queuedID, childID repairOffset := fresh.mark() - if err := owner.Close(context.Background()); err != nil { - t.Fatal(err) - } + testassert.NoError(t, owner.Close(context.Background())) owner = nil - if err := recvBounded(t, lost); err == nil { - t.Fatal("desktop did not report old-owner loss") + { + err := recvBounded(t, lost) + testassert.Require(t, err != nil, "desktop did not report old-owner loss") } newEndpoint := openOwner() newInfo, err := host.Open() - if err != nil { - t.Fatal(err) - } - if newInfo.InstanceID == oldInstance || newInfo.Generation == info.Generation { - t.Fatal("new owner reused desktop identity/generation") - } - if _, err := host.Call(info.Generation, "GetHealth", ""); err == nil { - t.Fatal("old generation survived owner replacement") + testassert.NoError(t, err) + testassert.Require(t, newInfo.InstanceID != oldInstance && newInfo.Generation != info.Generation, "new owner reused desktop identity/generation") + { + _, err := host.Call(info.Generation, "GetHealth", "") + testassert.Require(t, err != nil, "old generation survived owner replacement") } info = newInfo // Wait for the actual repair announcement; its snapshot is generated from @@ -630,9 +570,7 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { repairedNotice := false for !repairedSnapshot || !repairedConnected || !repairedNotice { frame := recvBounded(t, acp.frames) - if frame == nil { - t.Fatal("ACP ended during repair") - } + testassert.Require(t, frame != nil, "ACP ended during repair") switch string(frame["method"]) { case `"runtime/connected"`: var health protocol.Health @@ -655,43 +593,29 @@ func TestCoordinatedHostsNativeWorkDepartureAndOwnerRepair(t *testing.T) { newRoot, newChild := rootRead(), desktopHydrate(t, host, info, lossChildID) for _, pair := range []struct{ before, after protocol.Hydration }{{lossRoot, newRoot}, {lossChild, newChild}} { h := pair.after - if h.ActiveOperation != nil || len(h.Background) != 0 || len(h.Pending.Queued)+len(h.Pending.Steering) != 0 { - t.Fatal("new owner resumed active work or recovered a pending buffer") - } + testassert.Require(t, h.ActiveOperation == nil && len(h.Background) == 0 && len(h.Pending.Queued)+len(h.Pending.Steering) == 0, "new owner resumed active work or recovered a pending buffer") found := false for _, op := range h.Operations { - if op.OperationId == lossQueued { - t.Fatal("new owner admitted lost pending caller") - } + testassert.Require(t, op.OperationId != lossQueued, "new owner admitted lost pending caller") if op.OperationId == pair.before.ActiveOperation.OperationId { found = op.Status == protocol.OperationStatusInterruption } } - if !found { - t.Fatal("closed owner's active Operation did not settle interrupted") - } + testassert.Require(t, found, "closed owner's active Operation did not settle interrupted") } for _, id := range []string{root.SessionId, lossChildID} { h := desktopHydrate(t, host, info, id) - if !reflect.DeepEqual(h, acp.hydrate(t, id)) || !reflect.DeepEqual(h, fresh.hydrate(t, id)) { - t.Fatal("new-owner instance-qualified root/child reads differ") - } + testassert.Require(t, reflect.DeepEqual(h, acp.hydrate(t, id)) && reflect.DeepEqual(h, fresh.hydrate(t, id)), "new-owner instance-qualified root/child reads differ") } model.mu.Lock() calls := model.calls model.mu.Unlock() t.Logf("old owner %s instance %s; new owner %s instance %s; root %s child %s; discarded queued caller %s; native model requests %d", oldEndpoint, oldInstance, newEndpoint, info.InstanceID, root.SessionId, lossChildID, lossQueued, calls) _ = fresh.in.Close() - if err := recvBounded(t, fresh.done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, recvBounded(t, fresh.done)) _ = acp.in.Close() - if err := recvBounded(t, acp.done); err != nil { - t.Fatal(err) - } + testassert.NoError(t, recvBounded(t, acp.done)) host.shutdown(context.Background()) - if err := owner.Close(context.Background()); err != nil { - t.Fatal(err) - } + testassert.NoError(t, owner.Close(context.Background())) owner = nil } diff --git a/internal/desktopclient/host_test.go b/internal/desktopclient/host_test.go index 13afc727..14112096 100644 --- a/internal/desktopclient/host_test.go +++ b/internal/desktopclient/host_test.go @@ -22,6 +22,7 @@ import ( "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/clienthost" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/version" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" @@ -98,9 +99,7 @@ func missingExecutable(t *testing.T) string { func newHost(t *testing.T, server *httptest.Server) *Host { t.Helper() dataDir := t.TempDir() - if err := os.WriteFile(filepath.Join(dataDir, "runtime.json"), []byte(recordContent(server.URL)), 0o600); err != nil { - t.Fatalf("write discovery record: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dataDir, "runtime.json"), []byte(recordContent(server.URL)), 0o600)) host := New(clienthost.Options{DataDir: dataDir, Executable: missingExecutable(t)}, "/ws/name") host.startup(context.Background()) t.Cleanup(func() { host.shutdown(context.Background()) }) @@ -145,9 +144,7 @@ func awaitPulling(t *testing.T, host *Host) { if pulling { return } - if time.Now().After(deadline) { - t.Fatal("the event pull never took the outstanding-pull gate") - } + testassert.Require(t, !time.Now().After(deadline), "the event pull never took the outstanding-pull gate") time.Sleep(time.Millisecond) } } @@ -164,9 +161,7 @@ func awaitTerminal(t *testing.T, host *Host) { if ready { return } - if time.Now().After(deadline) { - t.Fatal("the stream never reported its terminal error") - } + testassert.Require(t, !time.Now().After(deadline), "the stream never reported its terminal error") time.Sleep(time.Millisecond) } } @@ -179,44 +174,25 @@ func TestDesktopHostOpenGenerationSupersession(t *testing.T) { host := newHost(t, server) first, err := host.Open() - if err != nil { - t.Fatalf("first Open: %v", err) - } - if first.InstanceID != testInstance { - t.Fatalf("first instance = %q, want %q", first.InstanceID, testInstance) - } - if first.Workspace != "/ws/name" { - t.Fatalf("first workspace = %q, want the host workspace", first.Workspace) - } - if first.Generation != "1" { - t.Fatalf("first generation = %q, want the first monotonic stamp", first.Generation) - } + testassert.NoError(t, err) + testassert.Require(t, first.InstanceID == testInstance && first.Workspace == "/ws/name" && first.Generation == "1", "got %v, want %v; got %v, want %v; got %v, want %v", first.InstanceID, testInstance, first.Workspace, "/ws/name", first.Generation, "1") second, err := host.Open() - if err != nil { - t.Fatalf("second Open: %v", err) - } - if second.Generation != "2" { - t.Fatalf("second generation = %q, want the next monotonic stamp", second.Generation) - } + testassert.NoError(t, err) + testassert.Equal(t, second.Generation, "2") - if _, err := host.Call(first.Generation, "GetHealth", ""); err == nil { - t.Fatal("a superseded generation accepted a call") + { + _, err := host.Call(first.Generation, "GetHealth", "") + testassert.Require(t, err != nil, "a superseded generation accepted a call") } - if _, err := host.NextEvent(first.Generation); err == nil { - t.Fatal("a superseded generation accepted an event pull") + { + _, err := host.NextEvent(first.Generation) + testassert.Require(t, err != nil, "a superseded generation accepted an event pull") } response, err := host.Call(second.Generation, "GetHealth", "") - if err != nil { - t.Fatalf("current generation Call: %v", err) - } - if response.Status != http.StatusOK { - t.Fatalf("health status = %d, want 200", response.Status) - } - if !strings.Contains(response.Body, testInstance) { - t.Fatalf("health body = %q, want the authenticated instance", response.Body) - } + testassert.NoError(t, err) + testassert.Require(t, response.Status == http.StatusOK && strings.Contains(response.Body, testInstance), "got %v, want %v; health body = %q, want the authenticated instance", response.Status, http.StatusOK, response.Body) } // TestDesktopHostOpenCancelsInFlightCall proves a newer Open cancels a @@ -233,20 +209,20 @@ func TestDesktopHostOpenCancelsInFlightCall(t *testing.T) { }) host := newHost(t, server) info, err := host.Open() - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) done := make(chan error, 1) go func() { _, err := host.Call(info.Generation, "GetSessionHydration", `{"session_id":"parked"}`) done <- err }() <-arrived - if _, err := host.Open(); err != nil { - t.Fatalf("superseding Open: %v", err) + { + _, err := host.Open() + testassert.NoError(t, err) } - if err := <-done; !errors.Is(err, context.Canceled) { - t.Fatalf("superseded call = %v, want context.Canceled", err) + { + err := <-done + testassert.Require(t, errors.Is(err, context.Canceled), "superseded call = %v, want context.Canceled", err) } select { case <-observed: @@ -281,44 +257,27 @@ func TestDesktopHostCallClassification(t *testing.T) { }) host := newHost(t, server) info, err := host.Open() - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) success, err := host.Call(info.Generation, "GetHealth", "") - if err != nil { - t.Fatalf("success Call: %v", err) - } - if success.Status != http.StatusOK || success.Invalid || !strings.Contains(success.Body, testInstance) { - t.Fatalf("success response = %+v, want the authenticated health body", success) - } + testassert.NoError(t, err) + testassert.Require(t, success.Status == http.StatusOK && !success.Invalid && strings.Contains(success.Body, testInstance), "success response = %+v, want the authenticated health body", success) rejected, err := host.Call(info.Generation, "RevertSessionCode", `{"session_id":"abc","body":{"operation_id":"op-1"}}`) - if err != nil { - t.Fatalf("rejection Call: %v", err) - } - if rejected.Status != http.StatusConflict || rejected.Invalid || rejected.Body != partial { - t.Fatalf("rejection response = %+v, want the validated partial body without rejection", rejected) - } + testassert.NoError(t, err) + testassert.Require(t, rejected.Status == http.StatusConflict && !rejected.Invalid && rejected.Body == partial, "rejection response = %+v, want the validated partial body without rejection", rejected) empty, err := host.Call(info.Generation, "InterruptSession", `{"session_id":"abc"}`) - if err != nil { - t.Fatalf("204 Call: %v", err) - } - if empty.Status != http.StatusNoContent || empty.Invalid || empty.Body != "" { - t.Fatalf("204 response = %+v, want a valid empty success", empty) - } + testassert.NoError(t, err) + testassert.Require(t, empty.Status == http.StatusNoContent && !empty.Invalid && empty.Body == "", "204 response = %+v, want a valid empty success", empty) invalid, err := host.Call(info.Generation, "GetSessionHydration", `{"session_id":"malformed"}`) - if err != nil { - t.Fatalf("invalid Call: %v", err) - } - if invalid.Status != http.StatusOK || !invalid.Invalid || invalid.Body != "not json" { - t.Fatalf("invalid response = %+v, want the received status/body with the Invalid bit", invalid) - } + testassert.NoError(t, err) + testassert.Require(t, invalid.Status == http.StatusOK && invalid.Invalid && invalid.Body == "not json", "invalid response = %+v, want the received status/body with the Invalid bit", invalid) - if _, err := host.Call(info.Generation, "Nope", ""); err == nil { - t.Fatal("an unknown operation did not reject the binding") + { + _, err := host.Call(info.Generation, "Nope", "") + testassert.Require(t, err != nil, "an unknown operation did not reject the binding") } } @@ -336,8 +295,9 @@ func TestDesktopHostNextEventPullOwnership(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() host := &Host{generation: "1", events: events, errs: errs, connCtx: ctx, connCancel: cancel} - if _, err := host.NextEvent("1"); err == nil || !strings.Contains(err.Error(), "closed") { - t.Fatalf("NextEvent = %v, want a closure loss", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "closed"), "NextEvent = %v, want a closure loss", err) } }) @@ -352,8 +312,9 @@ func TestDesktopHostNextEventPullOwnership(t *testing.T) { close(firstDone) }() awaitPulling(t, host) - if _, err := host.NextEvent("1"); err == nil || !strings.Contains(err.Error(), "already outstanding") { - t.Fatalf("second pull = %v, want the one outstanding pull", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "already outstanding"), "second pull = %v, want the one outstanding pull", err) } cancel() <-firstDone @@ -366,8 +327,9 @@ func TestDesktopHostNextEventPullOwnership(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() host := &Host{generation: "1", events: events, errs: errs, connCtx: ctx, connCancel: cancel} - if _, err := host.NextEvent("1"); err == nil || !strings.Contains(err.Error(), "closed without a terminal error") { - t.Fatalf("NextEvent = %v, want the closure loss", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "closed without a terminal error"), "NextEvent = %v, want the closure loss", err) } }) @@ -379,8 +341,9 @@ func TestDesktopHostNextEventPullOwnership(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() host := &Host{generation: "1", events: events, errs: errs, connCtx: ctx, connCancel: cancel} - if _, err := host.NextEvent("1"); !errors.Is(err, client.ErrResyncRequired) { - t.Fatalf("NextEvent = %v, want the producer's queued terminal error", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, errors.Is(err, client.ErrResyncRequired), "NextEvent = %v, want the producer's queued terminal error", err) } }) @@ -388,32 +351,28 @@ func TestDesktopHostNextEventPullOwnership(t *testing.T) { events := make(chan protocol.Event, 1) errs := make(chan error, 1) var scope protocol.Scope - if err := scope.FromRuntimeScope(protocol.RuntimeScope{Kind: protocol.RuntimeScopeKindRuntime}); err != nil { - t.Fatalf("build scope: %v", err) - } + testassert.NoError(t, scope.FromRuntimeScope(protocol.RuntimeScope{Kind: protocol.RuntimeScopeKindRuntime})) var event protocol.Event - if err := event.FromScopeEvent(protocol.ScopeEvent{Kind: protocol.ScopeOpened, Scope: scope}); err != nil { - t.Fatalf("build event: %v", err) - } + testassert.NoError(t, event.FromScopeEvent(protocol.ScopeEvent{Kind: protocol.ScopeOpened, Scope: scope})) ctx, cancel := context.WithCancel(context.Background()) defer cancel() host := &Host{generation: "1", events: events, errs: errs, connCtx: ctx, connCancel: cancel} errs <- client.ErrResyncRequired - if _, err := host.NextEvent("1"); !errors.Is(err, client.ErrResyncRequired) { - t.Fatalf("pull = %v, want the observed terminal error", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, errors.Is(err, client.ErrResyncRequired), "pull = %v, want the observed terminal error", err) } // The loss retires the generation before any later pull can observe // its remaining buffered progress. events <- event - if _, err := host.NextEvent("1"); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("later pull = %v, want the stale-generation rejection", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "later pull = %v, want the stale-generation rejection", err) } host.mu.Lock() canceled, retired := ctx.Err() != nil, host.generation == "" host.mu.Unlock() - if !canceled || !retired { - t.Fatal("an observed loss must cancel and retire only its matching generation") - } + testassert.Require(t, canceled && retired, "an observed loss must cancel and retire only its matching generation") }) } @@ -426,29 +385,22 @@ func TestDesktopHostProgressPrecedesUnobservedTerminal(t *testing.T) { events := make(chan protocol.Event, 1) errs := make(chan error, 1) var scope protocol.Scope - if err := scope.FromRuntimeScope(protocol.RuntimeScope{Kind: protocol.RuntimeScopeKindRuntime}); err != nil { - t.Fatalf("build scope: %v", err) - } + testassert.NoError(t, scope.FromRuntimeScope(protocol.RuntimeScope{Kind: protocol.RuntimeScopeKindRuntime})) var event protocol.Event - if err := event.FromScopeEvent(protocol.ScopeEvent{Kind: protocol.ScopeOpened, Scope: scope}); err != nil { - t.Fatalf("build event: %v", err) - } + testassert.NoError(t, event.FromScopeEvent(protocol.ScopeEvent{Kind: protocol.ScopeOpened, Scope: scope})) want, err := json.Marshal(event) - if err != nil { - t.Fatalf("marshal event: %v", err) - } + testassert.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) defer cancel() host := &Host{generation: "1", events: events, errs: errs, connCtx: ctx, connCancel: cancel} events <- event raw, err := host.NextEvent("1") - if err != nil || raw != string(want) { - t.Fatalf("progress pull = (%q, %v), want the exact %q ahead of the unobserved terminal", raw, err, want) - } + testassert.Require(t, err == nil && raw == string(want), "progress pull = (%q, %v), want the exact %q ahead of the unobserved terminal", raw, err, want) errs <- client.ErrResyncRequired - if _, err := host.NextEvent("1"); !errors.Is(err, client.ErrResyncRequired) { - t.Fatalf("later pull = %v, want the now-observed terminal error", err) + { + _, err := host.NextEvent("1") + testassert.Require(t, errors.Is(err, client.ErrResyncRequired), "later pull = %v, want the now-observed terminal error", err) } } @@ -490,45 +442,35 @@ func TestDesktopHostOverflowTerminalRetiresGeneration(t *testing.T) { }) host := newHost(t, server) info, err := host.Open() - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) close(start) awaitTerminal(t, host) var observed error for pulls := 0; ; pulls++ { - if pulls > total { - t.Fatal("the terminal failure was never observed") - } + testassert.Require(t, pulls <= total, "the terminal failure was never observed") raw, err := host.NextEvent(info.Generation) if err != nil { observed = err break } var delivered map[string]any - if json.Unmarshal([]byte(raw), &delivered) != nil || delivered["kind"] != "session_changed" { - t.Fatalf("pull delivered %q, want only the valid queued progress", raw) - } - } - if !errors.Is(observed, client.ErrResyncRequired) { - t.Fatalf("observed terminal = %v, want the overflow resync error", observed) + testassert.Require(t, json.Unmarshal([]byte(raw), &delivered) == nil && delivered["kind"] == "session_changed", "pull delivered %q, want only the valid queued progress", raw) } + testassert.Require(t, errors.Is(observed, client.ErrResyncRequired), "observed terminal = %v, want the overflow resync error", observed) host.mu.Lock() generationErr := host.connCtx.Err() retired := host.generation == "" host.mu.Unlock() - if generationErr == nil { - t.Fatal("the terminal failure did not cancel its connection generation") - } - if !retired { - t.Fatal("the terminal failure did not retire its connection generation") - } - if _, err := host.NextEvent(info.Generation); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("later pull = %v, want the stale-generation rejection", err) + testassert.Require(t, generationErr != nil, "the terminal failure did not cancel its connection generation") + testassert.Require(t, retired, "the terminal failure did not retire its connection generation") + { + _, err := host.NextEvent(info.Generation) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "later pull = %v, want the stale-generation rejection", err) } - if _, err := host.Call(info.Generation, "GetHealth", ""); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatal("a later request acted healthy on the lost generation") + { + _, err := host.Call(info.Generation, "GetHealth", "") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "a later request acted healthy on the lost generation") } } @@ -540,14 +482,17 @@ func TestDesktopHostOpenRejectsEstablishmentFailure(t *testing.T) { http.Error(w, "no stream", http.StatusInternalServerError) }) host := newHost(t, server) - if _, err := host.Open(); err == nil { - t.Fatal("Open succeeded over a failed event stream") + { + _, err := host.Open() + testassert.Require(t, err != nil, "Open succeeded over a failed event stream") } - if _, err := host.Call("1", "GetHealth", ""); err == nil { - t.Fatal("no generation was installed, yet a call succeeded") + { + _, err := host.Call("1", "GetHealth", "") + testassert.Require(t, err != nil, "no generation was installed, yet a call succeeded") } - if _, err := host.NextEvent("1"); err == nil { - t.Fatal("no generation was installed, yet a pull succeeded") + { + _, err := host.NextEvent("1") + testassert.Require(t, err != nil, "no generation was installed, yet a pull succeeded") } } @@ -565,9 +510,7 @@ func TestDesktopHostCloseCancelsWaits(t *testing.T) { }) host := newHost(t, server) info, err := host.Open() - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) requestDone := make(chan error, 1) go func() { _, err := host.Call(info.Generation, "GetSessionHydration", `{"session_id":"parked"}`) @@ -582,11 +525,13 @@ func TestDesktopHostCloseCancelsWaits(t *testing.T) { awaitPulling(t, host) host.shutdown(context.Background()) - if err := <-requestDone; !errors.Is(err, context.Canceled) { - t.Fatalf("closed request = %v, want context.Canceled", err) + { + err := <-requestDone + testassert.Require(t, errors.Is(err, context.Canceled), "closed request = %v, want context.Canceled", err) } - if err := <-pullDone; !errors.Is(err, context.Canceled) { - t.Fatalf("closed pull = %v, want context.Canceled", err) + { + err := <-pullDone + testassert.Require(t, errors.Is(err, context.Canceled), "closed pull = %v, want context.Canceled", err) } select { case <-observed: @@ -605,9 +550,7 @@ func TestDesktopHostNativeInvocation(t *testing.T) { defer host.shutdown(context.Background()) host.pickDirFn = func(context.Context) (string, error) { return "/chosen", nil } got, err := host.PickDirectory() - if err != nil || got != "/chosen" { - t.Fatalf("PickDirectory = (%q, %v)", got, err) - } + testassert.Require(t, err == nil && got == "/chosen", "PickDirectory = (%q, %v)", got, err) }) t.Run("directory cancel is an empty result", func(t *testing.T) { @@ -616,9 +559,7 @@ func TestDesktopHostNativeInvocation(t *testing.T) { defer host.shutdown(context.Background()) host.pickDirFn = func(context.Context) (string, error) { return "", nil } got, err := host.PickDirectory() - if err != nil || got != "" { - t.Fatalf("PickDirectory = (%q, %v), want the cancel result", got, err) - } + testassert.Require(t, err == nil && got == "", "PickDirectory = (%q, %v), want the cancel result", got, err) }) t.Run("title matches the one shared algorithm", func(t *testing.T) { @@ -627,22 +568,23 @@ func TestDesktopHostNativeInvocation(t *testing.T) { host.startup(context.Background()) var applied []string host.titleFn = func(_ context.Context, title string) { applied = append(applied, title) } - if err := host.SetTitle(workspace); err != nil { - t.Fatalf("SetTitle(%q): %v", workspace, err) + { + err := host.SetTitle(workspace) + testassert.Require(t, err == nil, "SetTitle(%q): %v", workspace, err) } - if err := host.SetTitle(workspace); err != nil { - t.Fatalf("second SetTitle(%q): %v", workspace, err) + { + err := host.SetTitle(workspace) + testassert.Require(t, err == nil, "second SetTitle(%q): %v", workspace, err) } // The initial window title and every subsequent SetTitle apply // the one shared algorithm: the plain basename join, no // special case. want := "Lightcode — " + filepath.Base(workspace) - if got := windowTitle(workspace); got != want { - t.Fatalf("initial title for %q = %q, want %q", workspace, got, want) - } - if applied[0] != want || applied[1] != want { - t.Fatalf("SetTitle(%q) applied %q, want the initial title %q", workspace, applied, want) + { + got := windowTitle(workspace) + testassert.Require(t, got == want, "initial title for %q = %q, want %q", workspace, got, want) } + testassert.Require(t, applied[0] == want && applied[1] == want, "SetTitle(%q) applied %q, want the initial title %q", workspace, applied, want) } }) @@ -713,21 +655,20 @@ func TestDesktopHostParkedCallsUnblockOnStartup(t *testing.T) { startupCtx := context.WithValue(context.Background(), startupProbeKey{}, "startup") host.startup(startupCtx) - if got := <-directory; got.err != nil || got.value != "/chosen" { - t.Fatalf("PickDirectory after startup = %+v", got) - } - if got := <-title; got.err != nil { - t.Fatalf("SetTitle after startup = %+v", got) + { + got := <-directory + testassert.Require(t, got.err == nil && got.value == "/chosen", "PickDirectory after startup = %+v", got) } - if got := <-opened; got.err == nil || !strings.Contains(got.err.Error(), "connect runtime:") { - t.Fatalf("Open after startup = %v, want the actual clienthost.Connect outcome", got.err) + { + got := <-title + testassert.Require(t, got.err == nil, "SetTitle after startup = %+v", got) } - if nativeCtx != startupCtx { - t.Fatal("the native dialog did not receive the exact startup context") - } - if titledCtx != startupCtx { - t.Fatal("the native title call did not receive the exact startup context") + { + got := <-opened + testassert.Require(t, got.err != nil && strings.Contains(got.err.Error(), "connect runtime:"), "Open after startup = %v, want the actual clienthost.Connect outcome", got.err) } + testassert.Require(t, nativeCtx == startupCtx, "the native dialog did not receive the exact startup context") + testassert.Require(t, titledCtx == startupCtx, "the native title call did not receive the exact startup context") host.shutdown(context.Background()) }) } @@ -765,18 +706,19 @@ func TestDesktopHostShutdownBeforeStartupWakesWaiters(t *testing.T) { synctest.Wait() // parked before startup, as above host.shutdown(context.Background()) - if got := <-directory; got.err == nil { - t.Fatalf("PickDirectory after shutdown-before-startup = %+v, want an error", got) - } - if got := <-title; got.err == nil { - t.Fatalf("SetTitle after shutdown-before-startup = %+v, want an error", got) + { + got := <-directory + testassert.Require(t, got.err != nil, "PickDirectory after shutdown-before-startup = %+v, want an error", got) } - if got := <-opened; got.err == nil || !strings.Contains(got.err.Error(), "closed") { - t.Fatalf("Open after shutdown-before-startup = %v, want the closed-host error", got.err) + { + got := <-title + testassert.Require(t, got.err != nil, "SetTitle after shutdown-before-startup = %+v, want an error", got) } - if invoked { - t.Fatal("shutdown-before-startup invoked a native presentation function") + { + got := <-opened + testassert.Require(t, got.err != nil && strings.Contains(got.err.Error(), "closed"), "Open after shutdown-before-startup = %v, want the closed-host error", got.err) } + testassert.Require(t, !invoked, "shutdown-before-startup invoked a native presentation function") }) } @@ -790,18 +732,19 @@ func TestDesktopHostPostCloseNativeCallsError(t *testing.T) { host.titleFn = func(context.Context, string) { invoked = true } host.shutdown(context.Background()) - if got, err := host.PickDirectory(); err == nil { - t.Fatalf("PickDirectory after close = (%q, %v), want the closed-host error", got, err) - } - if err := host.SetTitle("/ws/name"); err == nil { - t.Fatal("SetTitle after close succeeded") + { + got, err := host.PickDirectory() + testassert.Require(t, err != nil, "PickDirectory after close = (%q, %v), want the closed-host error", got, err) } - if _, err := host.Open(); err == nil { - t.Fatal("Open after close succeeded") + { + err := host.SetTitle("/ws/name") + testassert.Require(t, err != nil, "SetTitle after close succeeded") } - if invoked { - t.Fatal("post-close native calls reached the injected presentation functions") + { + _, err := host.Open() + testassert.Require(t, err != nil, "Open after close succeeded") } + testassert.Require(t, !invoked, "post-close native calls reached the injected presentation functions") } // gatedFixture scripts one authenticated health endpoint whose second and @@ -838,8 +781,7 @@ func (f *gatedFixture) health(w http.ResponseWriter, r *http.Request) { return } } - w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, `{"instance_id":%q,"protocol_version":"2"}`, testInstance) + jsonHealth()(w, r) } func (f *gatedFixture) events(w http.ResponseWriter, r *http.Request) { @@ -908,12 +850,8 @@ func TestDesktopHostPendingOpenRejectsOldStamps(t *testing.T) { server := newFixture(t, fixture.health, fixture.events) host := newHost(t, server) first, err := host.Open() - if err != nil { - t.Fatalf("first Open: %v", err) - } - if first.Generation != "1" { - t.Fatalf("first generation = %q, want 1", first.Generation) - } + testassert.NoError(t, err) + testassert.Equal(t, first.Generation, "1") close(fixture.terminate) awaitTerminal(t, host) @@ -928,22 +866,20 @@ func TestDesktopHostPendingOpenRejectsOldStamps(t *testing.T) { }() recvBounded(t, fixture.held) // the newer attempt waits at its authenticated health gate - if _, err := host.Call(first.Generation, "GetHealth", ""); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("old-stamp request during a pending open = %v, want rejection before I/O", err) + { + _, err := host.Call(first.Generation, "GetHealth", "") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "old-stamp request during a pending open = %v, want rejection before I/O", err) } - if _, err := host.NextEvent(first.Generation); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("old-stamp pull during a pending open = %v, want rejection before I/O", err) + { + _, err := host.NextEvent(first.Generation) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "old-stamp pull during a pending open = %v, want rejection before I/O", err) } close(fixture.release) result := recvBounded(t, opened) - if result.err != nil || result.info.Generation != "2" { - t.Fatalf("pending Open = (%+v, %v), want the installed second generation", result.info, result.err) - } + testassert.Require(t, result.err == nil && result.info.Generation == "2", "pending Open = (%+v, %v), want the installed second generation", result.info, result.err) response, err := host.Call(result.info.Generation, "GetHealth", "") - if err != nil || response.Status != http.StatusOK { - t.Fatalf("new-generation request = (%+v, %v), want the usable install", response, err) - } + testassert.Require(t, err == nil && response.Status == http.StatusOK, "new-generation request = (%+v, %v), want the usable install", response, err) } // TestDesktopHostFailedOpenDoesNotResurrectOldStamp proves a failed newer @@ -952,7 +888,7 @@ func TestDesktopHostPendingOpenRejectsOldStamps(t *testing.T) { func TestDesktopHostFailedOpenDoesNotResurrectOldStamp(t *testing.T) { var mu sync.Mutex var healthCalls int - failingHealth := func(w http.ResponseWriter, _ *http.Request) { + failingHealth := func(w http.ResponseWriter, r *http.Request) { mu.Lock() healthCalls++ second := healthCalls > 1 @@ -962,24 +898,24 @@ func TestDesktopHostFailedOpenDoesNotResurrectOldStamp(t *testing.T) { w.WriteHeader(http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, `{"instance_id":%q,"protocol_version":"2"}`, testInstance) + jsonHealth()(w, r) } server := newFixture(t, failingHealth, parkedStream()) host := newHost(t, server) first, err := host.Open() - if err != nil { - t.Fatalf("first Open: %v", err) - } - if _, err := host.Open(); err == nil { - t.Fatal("second Open succeeded over a failing discovery owner") + testassert.NoError(t, err) + { + _, err := host.Open() + testassert.Require(t, err != nil, "second Open succeeded over a failing discovery owner") } - if _, err := host.Call(first.Generation, "GetHealth", ""); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("old-stamp request after a failed open = %v, want the retired stamp to stay rejected", err) + { + _, err := host.Call(first.Generation, "GetHealth", "") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "old-stamp request after a failed open = %v, want the retired stamp to stay rejected", err) } - if _, err := host.NextEvent(first.Generation); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("old-stamp pull after a failed open = %v, want the retired stamp to stay rejected", err) + { + _, err := host.NextEvent(first.Generation) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "old-stamp pull after a failed open = %v, want the retired stamp to stay rejected", err) } } @@ -994,9 +930,7 @@ func TestDesktopHostLateTerminalCleanupCannotCancelPendingOpen(t *testing.T) { server := newFixture(t, fixture.health, fixture.events) host := newHost(t, server) first, err := host.Open() - if err != nil { - t.Fatalf("first Open: %v", err) - } + testassert.NoError(t, err) host.mu.Lock() barrier := &errBarrierContext{Context: host.connCtx, entered: make(chan struct{}), release: make(chan struct{})} @@ -1023,22 +957,20 @@ func TestDesktopHostLateTerminalCleanupCannotCancelPendingOpen(t *testing.T) { recvBounded(t, fixture.held) // the newer attempt is pending at its health gate close(barrier.release) // the cleanup proceeds through its read-to-lock window - if err := recvBounded(t, pulled); !errors.Is(err, client.ErrResyncRequired) { - t.Fatalf("old pull = %v, want the terminal resync error", err) + { + err := recvBounded(t, pulled) + testassert.Require(t, errors.Is(err, client.ErrResyncRequired), "old pull = %v, want the terminal resync error", err) } close(fixture.release) result := recvBounded(t, opened) - if result.err != nil || result.info.Generation != "2" { - t.Fatalf("pending Open after the late cleanup = (%+v, %v), want the cleanup to leave the pending attempt intact", result.info, result.err) - } - if _, err := host.Call(first.Generation, "GetHealth", ""); err == nil || !strings.Contains(err.Error(), "stale connection generation") { - t.Fatalf("old stamp after the new install = %v, want the stale rejection", err) + testassert.Require(t, result.err == nil && result.info.Generation == "2", "pending Open after the late cleanup = (%+v, %v), want the cleanup to leave the pending attempt intact", result.info, result.err) + { + _, err := host.Call(first.Generation, "GetHealth", "") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "stale connection generation"), "old stamp after the new install = %v, want the stale rejection", err) } response, err := host.Call(result.info.Generation, "GetHealth", "") - if err != nil || response.Status != http.StatusOK { - t.Fatalf("installed generation request = (%+v, %v), want the usable connection", response, err) - } + testassert.Require(t, err == nil && response.Status == http.StatusOK, "installed generation request = (%+v, %v), want the usable connection", response, err) } // The mounted smoke fixture: the real production Runtime over isolated HOME, @@ -1050,11 +982,10 @@ const smokeAgentsDocument = `{"integrated":{"model":"prov/m","system_prompt":"si // writeServiceFile writes one owner configuration file. func writeServiceFile(t *testing.T, path, content string) { t.Helper() - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatalf("create config directory: %v", err) - } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatalf("write %s: %v", path, err) + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + { + err := os.WriteFile(path, []byte(content), 0o600) + testassert.Require(t, err == nil, "write %s: %v", path, err) } } @@ -1064,9 +995,7 @@ func writeServiceFile(t *testing.T, path, content string) { func isolateBundledCredentials(t *testing.T) { t.Helper() entries, err := os.ReadDir(filepath.Join("..", "..", "internal", "catalog", "builtin")) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue @@ -1106,60 +1035,37 @@ func TestDesktopHostMountedRuntime(t *testing.T) { ConfigPath: configPath, Plugins: builtin.Plugins(), }) - if err != nil { - t.Fatalf("runtime.Open: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { - if err := owner.Close(context.Background()); err != nil { - t.Errorf("runtime.Close: %v", err) + { + err := owner.Close(context.Background()) + testassert.Check(t, err == nil, "runtime.Close: %v", err) } }) server, err := owner.OpenProtocol(context.Background()) - if err != nil { - t.Fatalf("OpenProtocol: %v", err) - } - if err := server.PublishDiscovery(filepath.Join(dataDir, "runtime.json")); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, server.PublishDiscovery(filepath.Join(dataDir, "runtime.json"))) host := New(clienthost.Options{DataDir: dataDir, Executable: missingExecutable(t)}, "/ws/smoke") host.startup(context.Background()) defer host.shutdown(context.Background()) info, err := host.Open() - if err != nil { - t.Fatalf("Open: %v", err) - } - if info.InstanceID == "" || info.Generation != "1" || info.Workspace != "/ws/smoke" { - t.Fatalf("connection info = %+v", info) - } + testassert.NoError(t, err) + testassert.Require(t, info.InstanceID != "" && info.Generation == "1" && info.Workspace == "/ws/smoke", "connection info = %+v", info) response, err := host.Call(info.Generation, "GetHealth", "") - if err != nil { - t.Fatalf("health Call: %v", err) - } - if response.Status != http.StatusOK { - t.Fatalf("health response status = %d", response.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, response.Status, http.StatusOK) var health protocol.Health - if err := json.Unmarshal([]byte(response.Body), &health); err != nil { - t.Fatalf("decode health body: %v", err) - } - if health.InstanceId != info.InstanceID || health.ProtocolVersion != protocol.N2 { - t.Fatalf("health = %+v, want the connected owner", health) - } + testassert.NoError(t, json.Unmarshal([]byte(response.Body), &health)) + testassert.Require(t, health.InstanceId == info.InstanceID && health.ProtocolVersion == protocol.N2, "health = %+v, want the connected owner", health) create, err := host.Call(info.Generation, "CreateSession", `{"body":{"workspace":"/ws/smoke","agent_type":"integrated"}}`) - if err != nil { - t.Fatalf("create Call: %v", err) - } - if create.Status != http.StatusCreated { - t.Fatalf("create status = %d: %s", create.Status, create.Body) - } + testassert.NoError(t, err) + testassert.Require(t, create.Status == http.StatusCreated, "create status = %d: %s", create.Status, create.Body) var created protocol.Session - if err := json.Unmarshal([]byte(create.Body), &created); err != nil { - t.Fatalf("decode created session: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(create.Body), &created)) // The established subscription is the delivery barrier: the committed // creation hint reaches the native pull. @@ -1176,34 +1082,22 @@ func TestDesktopHostMountedRuntime(t *testing.T) { }() select { case result := <-pulled: - if result.err != nil { - t.Fatalf("NextEvent: %v", result.err) - } + testassert.Require(t, result.err == nil, "NextEvent: %v", result.err) var event protocol.Event - if err := json.Unmarshal([]byte(result.raw), &event); err != nil { - t.Fatalf("decode pulled event: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(result.raw), &event)) kind, err := event.Discriminator() - if err != nil { - t.Fatalf("event discriminator: %v", err) - } + testassert.NoError(t, err) if kind != "session_changed" { continue } body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session_changed body: %v", err) - } + testassert.NoError(t, err) scope, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("session_changed scope: %v", err) - } + testassert.NoError(t, err) if scope.SessionId != created.SessionId { continue } - if body.SessionRevision.InstanceId != info.InstanceID { - t.Fatalf("hint instance = %q, want %q", body.SessionRevision.InstanceId, info.InstanceID) - } + testassert.Equal(t, body.SessionRevision.InstanceId, info.InstanceID) return case <-deadline: t.Fatal("no session_changed event for the created session arrived") @@ -1219,40 +1113,26 @@ func TestDesktopHostAppOptions(t *testing.T) { host := New(clienthost.Options{}, filepath.Join("some", "WorkspaceDir")) assets := os.DirFS(t.TempDir()) opts := appOptions(host, assets) - if opts.Width != 900 || opts.Height != 700 { - t.Fatalf("window size = %dx%d, want 900x700", opts.Width, opts.Height) - } - if want := "Lightcode — WorkspaceDir"; opts.Title != want { - t.Fatalf("title = %q, want %q", opts.Title, want) - } - if opts.AssetServer == nil || opts.AssetServer.Assets != assets { - t.Fatalf("AssetServer.Assets = %v, want the given filesystem", opts.AssetServer) - } - if opts.OnStartup == nil || opts.OnShutdown == nil { - t.Fatal("the private lifecycle hooks must be wired into the options") + testassert.Require(t, opts.Width == 900 && opts.Height == 700, "window size = %dx%d, want 900x700", opts.Width, opts.Height) + { + want := "Lightcode — WorkspaceDir" + testassert.Equal(t, opts.Title, want) } + testassert.Require(t, opts.AssetServer != nil && opts.AssetServer.Assets == assets, "AssetServer.Assets = %v, want the given filesystem", opts.AssetServer) + testassert.Require(t, opts.OnStartup != nil && opts.OnShutdown != nil, "the private lifecycle hooks must be wired into the options") // The wired hooks act on the supplied host: startup stores the caller's // context and closes readiness (observed without waiting on the host's // lifecycle), and shutdown marks that same host closed. testCtx, cancelTestCtx := context.WithCancel(context.Background()) defer cancelTestCtx() opts.OnStartup(testCtx) - if host.hostCtx != testCtx { - t.Fatalf("OnStartup stored context %v, want the supplied context", host.hostCtx) - } + testassert.Equal(t, host.hostCtx, testCtx) select { case <-host.ready: default: t.Fatal("OnStartup did not close the host readiness channel") } opts.OnShutdown(testCtx) - if !host.closed { - t.Fatal("OnShutdown did not close the host") - } - if len(opts.Bind) != 1 || opts.Bind[0] != interface{}(host) { - t.Fatalf("Bind = %v, want exactly the host", opts.Bind) - } - if opts.Linux == nil || opts.Linux.WebviewGpuPolicy != linux.WebviewGpuPolicyAlways { - t.Fatalf("Linux GPU policy = %v, want WebviewGpuPolicyAlways", opts.Linux) - } + testassert.Require(t, host.closed, "OnShutdown did not close the host") + testassert.Require(t, len(opts.Bind) == 1 && opts.Bind[0] == interface{}(host) && opts.Linux != nil && opts.Linux.WebviewGpuPolicy == linux.WebviewGpuPolicyAlways, "Bind = %v, want exactly the host; Linux GPU policy = %v, want WebviewGpuPolicyAlways", opts.Bind, opts.Linux) } diff --git a/internal/doctor/doctor_test.go b/internal/doctor/doctor_test.go index 9f39f90f..b3288cc1 100644 --- a/internal/doctor/doctor_test.go +++ b/internal/doctor/doctor_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/catalog" + "github.com/MMinasyan/lightcode/internal/testassert" ) func snapshotTree(t *testing.T, root string) map[string]string { @@ -26,9 +27,7 @@ func snapshotTree(t *testing.T, root string) map[string]string { snap[path] = fmt.Sprintf("%d:%d:%v", info.Size(), info.ModTime().UnixNano(), info.IsDir()) return nil }) - if err != nil && !os.IsNotExist(err) { - t.Fatalf("snapshot %s: %v", root, err) - } + testassert.Require(t, err == nil || os.IsNotExist(err), "snapshot %s: %v", root, err) return snap } @@ -38,9 +37,7 @@ func run(t *testing.T, p Params) (Report, bool) { before := snapshotTree(t, p.Home) report, noProviders := Run(p) after := snapshotTree(t, p.Home) - if !reflect.DeepEqual(before, after) { - t.Fatalf("doctor wrote to the filesystem:\nbefore: %v\nafter: %v", before, after) - } + testassert.Require(t, reflect.DeepEqual(before, after), "doctor wrote to the filesystem:\nbefore: %v\nafter: %v", before, after) return report, noProviders } @@ -70,23 +67,15 @@ func find(t *testing.T, r Report, group, name string) Check { func writeConfig(t *testing.T, home, body string) { t.Helper() dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), []byte(body), 0o600)) } func writeAgents(t *testing.T, home, body string) { t.Helper() dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "agents.json"), []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "agents.json"), []byte(body), 0o600)) } // keyedProviderConfig is a valid user provider with one usable model whose @@ -106,27 +95,23 @@ const keyedProviderConfig = `{ func TestFreshHome(t *testing.T) { home := t.TempDir() report, noProviders := run(t, params(home)) - if report.Failures != 0 { - t.Fatalf("fresh home has failures: %+v", report.Checks) - } - if !noProviders { - t.Fatal("fresh home must report no providers connected") - } + testassert.Require(t, report.Failures == 0, "fresh home has failures: %+v", report.Checks) + testassert.Require(t, noProviders, "fresh home must report no providers connected") c := find(t, report, "data", "dir") - if c.Status != StatusOK || c.Detail != "not created yet (created on first run)" { - t.Fatalf("data/dir = %+v", c) - } + testassert.Require(t, c.Status == StatusOK && c.Detail == "not created yet (created on first run)", "data/dir = %+v", c) for name, want := range map[string]string{ "config": "config.json: not created yet (created on first run)", "agents": "agents.json: not created yet (created on first run)", "env": ".env: not created yet (created on first run)", } { - if got := find(t, report, "data", name); got.Status != StatusOK || got.Detail != want { - t.Fatalf("data/%s = %+v, want %q", name, got, want) + { + got := find(t, report, "data", name) + testassert.Require(t, got.Status == StatusOK && got.Detail == want, "data/%s = %+v, want %q", name, got, want) } } - if _, err := os.Stat(filepath.Join(home, ".lightcode")); !os.IsNotExist(err) { - t.Fatal("doctor created the data dir") + { + _, err := os.Stat(filepath.Join(home, ".lightcode")) + testassert.Require(t, os.IsNotExist(err), "doctor created the data dir") } } @@ -138,9 +123,7 @@ func TestExternalConfigDiagnosedWithoutDataDir(t *testing.T) { writeExternal := func(t *testing.T, dir, name, body string) string { t.Helper() path := filepath.Join(dir, name) - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(body), 0o600)) return path } @@ -154,32 +137,32 @@ func TestExternalConfigDiagnosedWithoutDataDir(t *testing.T) { p.LookupEnv = func(name string) (string, bool) { return "x", name == "DOCTOR_TEST_KEY" } externalBefore := snapshotTree(t, external) report, noProviders := run(t, p) - if !reflect.DeepEqual(externalBefore, snapshotTree(t, external)) { - t.Fatal("doctor wrote to the addressed external directory") - } - if c := find(t, report, "data", "dir"); c.Status != StatusOK || c.Detail != "not created yet (created on first run)" { - t.Fatalf("data/dir = %+v", c) - } - if c := find(t, report, "data", "config"); c.Status != StatusOK || c.Detail != configPath { - t.Fatalf("data/config = %+v, want the addressed external path", c) - } - if c := find(t, report, "data", "agents"); c.Status != StatusOK || c.Detail != agentsPath { - t.Fatalf("data/agents = %+v, want the addressed adjacent path", c) + testassert.Require(t, reflect.DeepEqual(externalBefore, snapshotTree(t, external)), "doctor wrote to the addressed external directory") + { + c := find(t, report, "data", "dir") + testassert.Require(t, c.Status == StatusOK && c.Detail == "not created yet (created on first run)", "data/dir = %+v", c) } - if c := find(t, report, "providers", "keyedtest"); c.Status != StatusOK || c.Detail != "key external, connected, 1 usable models" { - t.Fatalf("providers/keyedtest = %+v, want the external config's provider diagnosed", c) + { + c := find(t, report, "data", "config") + testassert.Require(t, c.Status == StatusOK && c.Detail == configPath, "data/config = %+v, want the addressed external path", c) } - if c := find(t, report, "setup", "model"); c.Status != StatusOK || c.Detail != "keyedtest/m1" { - t.Fatalf("setup/model = %+v, want the external agents.json model diagnosed", c) + { + c := find(t, report, "data", "agents") + testassert.Require(t, c.Status == StatusOK && c.Detail == agentsPath, "data/agents = %+v, want the addressed adjacent path", c) } - if noProviders { - t.Fatal("a connected external provider must clear the fresh-install hint") + { + c := find(t, report, "providers", "keyedtest") + testassert.Require(t, c.Status == StatusOK && c.Detail == "key external, connected, 1 usable models", "providers/keyedtest = %+v, want the external config's provider diagnosed", c) } - if report.Failures != 0 { - t.Fatalf("external config has failures: %+v", report.Checks) + { + c := find(t, report, "setup", "model") + testassert.Require(t, c.Status == StatusOK && c.Detail == "keyedtest/m1", "setup/model = %+v, want the external agents.json model diagnosed", c) } - if _, err := os.Stat(filepath.Join(home, ".lightcode")); !os.IsNotExist(err) { - t.Fatal("doctor created the data dir") + testassert.Require(t, !noProviders, "a connected external provider must clear the fresh-install hint") + testassert.Require(t, report.Failures == 0, "external config has failures: %+v", report.Checks) + { + _, err := os.Stat(filepath.Join(home, ".lightcode")) + testassert.Require(t, os.IsNotExist(err), "doctor created the data dir") } }) @@ -187,27 +170,26 @@ func TestExternalConfigDiagnosedWithoutDataDir(t *testing.T) { p := params(t.TempDir()) p.ConfigPath = writeExternal(t, t.TempDir(), "config.json", `{not json`) report, _ := run(t, p) - if c := find(t, report, "data", "config"); c.Status != StatusFail { - t.Fatalf("data/config = %+v, want the addressed external file's parse failure", c) - } - if report.Failures == 0 { - t.Fatal("an invalid external config must count as a failure") + { + c := find(t, report, "data", "config") + testassert.Require(t, c.Status == StatusFail, "data/config = %+v, want the addressed external file's parse failure", c) } + testassert.Require(t, report.Failures != 0, "an invalid external config must count as a failure") }) t.Run("missing config and agents report not created", func(t *testing.T) { p := params(t.TempDir()) p.ConfigPath = filepath.Join(t.TempDir(), "config.json") report, _ := run(t, p) - if c := find(t, report, "data", "config"); c.Status != StatusOK || c.Detail != "config.json: not created yet (created on first run)" { - t.Fatalf("data/config = %+v", c) - } - if c := find(t, report, "data", "agents"); c.Status != StatusOK || c.Detail != "agents.json: not created yet (created on first run)" { - t.Fatalf("data/agents = %+v", c) + { + c := find(t, report, "data", "config") + testassert.Require(t, c.Status == StatusOK && c.Detail == "config.json: not created yet (created on first run)", "data/config = %+v", c) } - if report.Failures != 0 { - t.Fatalf("missing external files must not fail: %+v", report.Checks) + { + c := find(t, report, "data", "agents") + testassert.Require(t, c.Status == StatusOK && c.Detail == "agents.json: not created yet (created on first run)", "data/agents = %+v", c) } + testassert.Require(t, report.Failures == 0, "missing external files must not fail: %+v", report.Checks) }) t.Run("directory error keeps its own diagnostic", func(t *testing.T) { @@ -215,33 +197,30 @@ func TestExternalConfigDiagnosedWithoutDataDir(t *testing.T) { // with ENOTDIR. The dir diagnostic must report that error and the // addressed external config must still be diagnosed. homeFile := filepath.Join(t.TempDir(), "home") - if err := os.WriteFile(homeFile, []byte("not a directory"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(homeFile, []byte("not a directory"), 0o600)) p := params(homeFile) p.ConfigPath = writeExternal(t, t.TempDir(), "config.json", keyedProviderConfig) report, _ := run(t, p) - if c := find(t, report, "data", "dir"); c.Status != StatusFail { - t.Fatalf("data/dir = %+v, want the Lstat error as FAIL", c) + { + c := find(t, report, "data", "dir") + testassert.Require(t, c.Status == StatusFail, "data/dir = %+v, want the Lstat error as FAIL", c) } - if c := find(t, report, "data", "config"); c.Status != StatusOK || c.Detail != p.ConfigPath { - t.Fatalf("data/config = %+v, want the addressed config diagnosed despite the dir error", c) + { + c := find(t, report, "data", "config") + testassert.Require(t, c.Status == StatusOK && c.Detail == p.ConfigPath, "data/config = %+v, want the addressed config diagnosed despite the dir error", c) } }) } func TestMissingConfigReportedNotCreated(t *testing.T) { home := t.TempDir() - if err := os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700)) report, _ := run(t, params(home)) c := find(t, report, "data", "config") - if c.Status != StatusOK || c.Detail != "config.json: not created yet (created on first run)" { - t.Fatalf("data/config = %+v", c) - } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")); !os.IsNotExist(err) { - t.Fatal("doctor created config.json") + testassert.Require(t, c.Status == StatusOK && c.Detail == "config.json: not created yet (created on first run)", "data/config = %+v", c) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "config.json")) + testassert.Require(t, os.IsNotExist(err), "doctor created config.json") } } @@ -249,30 +228,18 @@ func TestInvalidConfigFails(t *testing.T) { home := t.TempDir() writeConfig(t, home, `{not json`) report, _ := run(t, params(home)) - if find(t, report, "data", "config").Status != StatusFail { - t.Fatalf("invalid config not FAIL: %+v", report.Checks) - } - if report.Failures == 0 { - t.Fatal("invalid config must count as a failure") - } + testassert.Require(t, find(t, report, "data", "config").Status == StatusFail, "invalid config not FAIL: %+v", report.Checks) + testassert.Require(t, report.Failures != 0, "invalid config must count as a failure") } func TestEmptySkeletonIsFreshMachine(t *testing.T) { home := t.TempDir() writeConfig(t, home, `{"providers": {}, "default_model": ""}`) report, noProviders := run(t, params(home)) - if report.Failures != 0 { - t.Fatalf("empty skeleton has failures: %+v", report.Checks) - } - if !noProviders { - t.Fatal("empty skeleton must report no providers connected") - } - if find(t, report, "setup", "provider").Status != StatusWarn { - t.Fatal("setup/provider should warn with nothing connected") - } - if find(t, report, "setup", "model").Status != StatusWarn { - t.Fatal("setup/model should warn on empty primary model") - } + testassert.Require(t, report.Failures == 0, "empty skeleton has failures: %+v", report.Checks) + testassert.Require(t, noProviders, "empty skeleton must report no providers connected") + testassert.Require(t, find(t, report, "setup", "provider").Status == StatusWarn, "setup/provider should warn with nothing connected") + testassert.Require(t, find(t, report, "setup", "model").Status == StatusWarn, "setup/model should warn on empty primary model") } func TestKeySourceClasses(t *testing.T) { @@ -281,9 +248,7 @@ func TestKeySourceClasses(t *testing.T) { writeConfig(t, home, keyedProviderConfig) report, _ := run(t, params(home)) c := find(t, report, "providers", "keyedtest") - if c.Status != StatusWarn { - t.Fatalf("unset key = %+v, want warn", c) - } + testassert.Require(t, c.Status == StatusWarn, "unset key = %+v, want warn", c) }) t.Run("shell env key is external", func(t *testing.T) { @@ -298,28 +263,18 @@ func TestKeySourceClasses(t *testing.T) { } report, noProviders := run(t, p) c := find(t, report, "providers", "keyedtest") - if c.Status != StatusOK || c.Detail != "key external, connected, 1 usable models" { - t.Fatalf("shell key = %+v", c) - } - if noProviders { - t.Fatal("a connected provider must clear the fresh-install hint") - } - if find(t, report, "setup", "provider").Status != StatusOK { - t.Fatal("setup/provider should be ok with a connected provider") - } + testassert.Require(t, c.Status == StatusOK && c.Detail == "key external, connected, 1 usable models", "shell key = %+v", c) + testassert.Require(t, !noProviders, "a connected provider must clear the fresh-install hint") + testassert.Require(t, find(t, report, "setup", "provider").Status == StatusOK, "setup/provider should be ok with a connected provider") }) t.Run("dotenv-only key is managed and connected", func(t *testing.T) { home := t.TempDir() writeConfig(t, home, keyedProviderConfig) - if err := os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=x\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=x\n"), 0o600)) report, _ := run(t, params(home)) c := find(t, report, "providers", "keyedtest") - if c.Status != StatusOK || c.Detail != "key managed, connected, 1 usable models" { - t.Fatalf("dotenv-only key = %+v (the false-none/false-disconnected regression)", c) - } + testassert.Require(t, c.Status == StatusOK && c.Detail == "key managed, connected, 1 usable models", "dotenv-only key = %+v (the false-none/false-disconnected regression)", c) }) t.Run("empty dotenv value is managed but not connected", func(t *testing.T) { @@ -334,20 +289,12 @@ func TestKeySourceClasses(t *testing.T) { t.Run(name, func(t *testing.T) { home := t.TempDir() writeConfig(t, home, keyedProviderConfig) - if err := os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte(envBody), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte(envBody), 0o600)) report, noProviders := run(t, params(home)) c := find(t, report, "providers", "keyedtest") - if c.Status != StatusOK || c.Detail != "key managed, not connected, 1 usable models" { - t.Fatalf("empty dotenv value = %+v", c) - } - if !noProviders { - t.Fatal("an empty key must not count as a connected provider") - } - if find(t, report, "setup", "provider").Status != StatusWarn { - t.Fatal("setup/provider must warn when the only key is empty") - } + testassert.Require(t, c.Status == StatusOK && c.Detail == "key managed, not connected, 1 usable models", "empty dotenv value = %+v", c) + testassert.Require(t, noProviders, "an empty key must not count as a connected provider") + testassert.Require(t, find(t, report, "setup", "provider").Status == StatusWarn, "setup/provider must warn when the only key is empty") }) } }) @@ -365,12 +312,11 @@ func TestKeySourceClasses(t *testing.T) { "default_model": "keyedtest/m1" }`) writeAgents(t, home, `{"primary": {"model": "keyedtest/m1"}}`) - if err := os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=\n"), 0o600)) report, _ := run(t, params(home)) - if c := find(t, report, "setup", "model"); c.Status != StatusWarn { - t.Fatalf("setup/model = %+v, want warn with an empty key", c) + { + c := find(t, report, "setup", "model") + testassert.Require(t, c.Status == StatusWarn, "setup/model = %+v, want warn with an empty key", c) } }) @@ -381,12 +327,8 @@ func TestKeySourceClasses(t *testing.T) { p.LookupEnv = func(name string) (string, bool) { return "", name == "DOCTOR_TEST_KEY" } report, noProviders := run(t, p) c := find(t, report, "providers", "keyedtest") - if c.Status != StatusWarn { - t.Fatalf("empty shell export = %+v, want the no-key warn (key-source none)", c) - } - if !noProviders { - t.Fatal("an empty shell export must not count as connected") - } + testassert.Require(t, c.Status == StatusWarn, "empty shell export = %+v, want the no-key warn (key-source none)", c) + testassert.Require(t, noProviders, "an empty shell export must not count as connected") }) t.Run("empty shell export shadows a non-empty dotenv value", func(t *testing.T) { @@ -394,32 +336,25 @@ func TestKeySourceClasses(t *testing.T) { // to an empty value, so the .env credential never loads. home := t.TempDir() writeConfig(t, home, keyedProviderConfig) - if err := os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=x\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=x\n"), 0o600)) p := params(home) p.LookupEnv = func(name string) (string, bool) { return "", name == "DOCTOR_TEST_KEY" } report, noProviders := run(t, p) c := find(t, report, "providers", "keyedtest") - if c.Status != StatusWarn { - t.Fatalf("shadowed dotenv key = %+v, want the no-key warn", c) - } - if !noProviders { - t.Fatal("a shell-shadowed .env credential must not count as connected") - } + testassert.Require(t, c.Status == StatusWarn, "shadowed dotenv key = %+v, want the no-key warn", c) + testassert.Require(t, noProviders, "a shell-shadowed .env credential must not count as connected") }) t.Run("shell beats dotenv", func(t *testing.T) { home := t.TempDir() writeConfig(t, home, keyedProviderConfig) - if err := os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=x\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("DOCTOR_TEST_KEY=x\n"), 0o600)) p := params(home) p.LookupEnv = func(name string) (string, bool) { return "x", name == "DOCTOR_TEST_KEY" } report, _ := run(t, p) - if c := find(t, report, "providers", "keyedtest"); c.Detail != "key external, connected, 1 usable models" { - t.Fatalf("shell+dotenv key = %+v, want external", c) + { + c := find(t, report, "providers", "keyedtest") + testassert.Require(t, c.Detail == "key external, connected, 1 usable models", "shell+dotenv key = %+v, want external", c) } }) } @@ -439,12 +374,8 @@ func TestConnectionSemantics(t *testing.T) { }`) report, noProviders := run(t, params(home)) c := find(t, report, "providers", "keylesstest") - if c.Status != StatusOK || c.Detail != "key keyless, connected, 1 usable models" { - t.Fatalf("keyless = %+v", c) - } - if noProviders { - t.Fatal("keyless connected provider must count as connected") - } + testassert.Require(t, c.Status == StatusOK && c.Detail == "key keyless, connected, 1 usable models", "keyless = %+v", c) + testassert.Require(t, !noProviders, "keyless connected provider must count as connected") }) t.Run("keyed with env set but no usable model stays disconnected", func(t *testing.T) { @@ -463,12 +394,8 @@ func TestConnectionSemantics(t *testing.T) { p.LookupEnv = func(name string) (string, bool) { return "x", name == "DOCTOR_TEST_KEY" } report, noProviders := run(t, p) c := find(t, report, "providers", "nousable") - if c.Detail != "key external, not connected, 0 usable models" { - t.Fatalf("no-usable-model provider = %+v", c) - } - if !noProviders { - t.Fatal("a provider without usable models must not count as connected") - } + testassert.Require(t, c.Detail == "key external, not connected, 0 usable models", "no-usable-model provider = %+v", c) + testassert.Require(t, noProviders, "a provider without usable models must not count as connected") }) } @@ -509,8 +436,9 @@ func TestPrimaryModelChecks(t *testing.T) { p.LookupEnv = tc.env } baseline, _ := run(t, p) - if c := find(t, baseline, "setup", "model"); c.Status != tc.wantStatus { - t.Fatalf("setup/model = %+v, want %s", c, tc.wantStatus) + { + c := find(t, baseline, "setup", "model") + testassert.Require(t, c.Status == tc.wantStatus, "setup/model = %+v, want %s", c, tc.wantStatus) } // Custom definitions that the agents parser drops with its own @@ -520,21 +448,15 @@ func TestPrimaryModelChecks(t *testing.T) { // fails only at its intended boundary (unavailable capability, // unavailable tool, wrong-typed capabilities member). var doc map[string]json.RawMessage - if err := json.Unmarshal([]byte(tc.agents), &doc); err != nil { - t.Fatalf("decode case agents document: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(tc.agents), &doc)) doc["custom_ghost_capability"] = json.RawMessage(`{"model": "test/ghost", "capabilities": ["not-declared"]}`) doc["custom_ghost_tool"] = json.RawMessage(`{"model": "test/ghost", "tools": ["not-compiled"]}`) doc["custom_broken_capabilities"] = json.RawMessage(`{"model": "test/ghost", "capabilities": "not-a-list"}`) augmented, err := json.Marshal(doc) - if err != nil { - t.Fatalf("encode augmented agents document: %v", err) - } + testassert.NoError(t, err) writeAgents(t, home, string(augmented)) report, _ := run(t, p) - if !reflect.DeepEqual(report, baseline) { - t.Fatalf("custom definitions changed the doctor report:\nbaseline: %+v\naugmented: %+v", baseline, report) - } + testassert.Require(t, reflect.DeepEqual(report, baseline), "custom definitions changed the doctor report:\nbaseline: %+v\naugmented: %+v", baseline, report) }) } } @@ -545,12 +467,8 @@ func TestCatalogWarningSeverities(t *testing.T) { writeConfig(t, home, `{"providers": {"droppedprovider": 42}, "default_model": ""}`) report, _ := run(t, params(home)) c := find(t, report, "catalog", "droppedprovider") - if c.Status != StatusFail { - t.Fatalf("user_config_skip = %+v, want fail", c) - } - if report.Failures == 0 { - t.Fatal("a dropped user provider must fail the run") - } + testassert.Require(t, c.Status == StatusFail, "user_config_skip = %+v, want fail", c) + testassert.Require(t, report.Failures != 0, "a dropped user provider must fail the run") }) t.Run("empty provider warns", func(t *testing.T) { @@ -560,12 +478,11 @@ func TestCatalogWarningSeverities(t *testing.T) { "default_model": "" }`) report, _ := run(t, params(home)) - if c := find(t, report, "catalog", "emptytest"); c.Status != StatusWarn { - t.Fatalf("empty_provider = %+v, want warn", c) - } - if report.Failures != 0 { - t.Fatal("empty_provider must not fail the run") + { + c := find(t, report, "catalog", "emptytest") + testassert.Require(t, c.Status == StatusWarn, "empty_provider = %+v, want warn", c) } + testassert.Require(t, report.Failures == 0, "empty_provider must not fail the run") }) t.Run("incomplete model warns", func(t *testing.T) { @@ -575,23 +492,21 @@ func TestCatalogWarningSeverities(t *testing.T) { "default_model": "" }`) report, _ := run(t, params(home)) - if c := find(t, report, "catalog", "incompletetest"); c.Status != StatusWarn { - t.Fatalf("incomplete_model = %+v, want warn", c) + { + c := find(t, report, "catalog", "incompletetest") + testassert.Require(t, c.Status == StatusWarn, "incomplete_model = %+v, want warn", c) } }) t.Run("corrupt discovery cache warns", func(t *testing.T) { home := t.TempDir() dir := filepath.Join(home, ".lightcode", "cache", "discovery") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "cacheprov.json"), []byte("{not json"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "cacheprov.json"), []byte("{not json"), 0o600)) report, _ := run(t, params(home)) - if c := find(t, report, "catalog", "cacheprov"); c.Status != StatusWarn { - t.Fatalf("discovery_failure = %+v, want warn", c) + { + c := find(t, report, "catalog", "cacheprov") + testassert.Require(t, c.Status == StatusWarn, "discovery_failure = %+v, want warn", c) } }) @@ -604,11 +519,10 @@ func TestCatalogWarningSeverities(t *testing.T) { {Kind: "bundled_config_skip", Provider: "bp", Message: "broken bundled"}, {Kind: "never_seen_kind", Provider: "np", Message: "novel"}, }, 0, add) - if len(checks) != 2 || checks[0].Status != StatusWarn || checks[1].Status != StatusWarn { - t.Fatalf("checks = %+v", checks) - } - if want := `unknown warning kind "never_seen_kind": novel`; checks[1].Detail != want { - t.Fatalf("unknown-kind detail = %q, want %q", checks[1].Detail, want) + testassert.Require(t, len(checks) == 2 && checks[0].Status == StatusWarn && checks[1].Status == StatusWarn, "checks = %+v", checks) + { + want := `unknown warning kind "never_seen_kind": novel` + testassert.Equal(t, checks[1].Detail, want) } }) } @@ -616,9 +530,7 @@ func TestCatalogWarningSeverities(t *testing.T) { func TestMalformedDotEnvLineWarns(t *testing.T) { home := t.TempDir() writeConfig(t, home, `{"providers": {}, "default_model": ""}`) - if err := os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("no equals sign\nGOOD_KEY=v\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", ".env"), []byte("no equals sign\nGOOD_KEY=v\n"), 0o600)) report, _ := run(t, params(home)) warned := false for _, c := range report.Checks { @@ -626,23 +538,18 @@ func TestMalformedDotEnvLineWarns(t *testing.T) { warned = true } } - if !warned { - t.Fatalf("no warn for the malformed .env line: %+v", report.Checks) - } + testassert.Require(t, warned, "no warn for the malformed .env line: %+v", report.Checks) } func TestPermissionWarnings(t *testing.T) { home := t.TempDir() dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("K=v\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, ".env"), []byte("K=v\n"), 0o644)) report, _ := run(t, params(home)) - if c := find(t, report, "data", "dir"); c.Status != StatusWarn { - t.Fatalf("0755 data dir = %+v, want warn", c) + { + c := find(t, report, "data", "dir") + testassert.Require(t, c.Status == StatusWarn, "0755 data dir = %+v, want warn", c) } envWarned := false for _, c := range report.Checks { @@ -650,25 +557,17 @@ func TestPermissionWarnings(t *testing.T) { envWarned = true } } - if !envWarned { - t.Fatal("0644 .env should warn") - } + testassert.Require(t, envWarned, "0644 .env should warn") } func TestSymlinkDataDirWarns(t *testing.T) { home := t.TempDir() real := filepath.Join(home, "realdata") - if err := os.MkdirAll(real, 0o700); err != nil { - t.Fatal(err) - } - if err := os.Symlink(real, filepath.Join(home, ".lightcode")); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(real, 0o700)) + testassert.NoError(t, os.Symlink(real, filepath.Join(home, ".lightcode"))) report, _ := run(t, params(home)) c := find(t, report, "data", "dir") - if c.Status != StatusWarn { - t.Fatalf("symlinked data dir = %+v, want warn", c) - } + testassert.Require(t, c.Status == StatusWarn, "symlinked data dir = %+v, want warn", c) } func TestResolveErrorsFail(t *testing.T) { @@ -676,27 +575,17 @@ func TestResolveErrorsFail(t *testing.T) { p := params(t.TempDir()) p.ConfigPathErr = fmt.Errorf("no home") report, _ := run(t, p) - if find(t, report, "install", "config-path").Status != StatusFail { - t.Fatal("config-path resolution error must FAIL") - } - if report.Failures == 0 { - t.Fatal("resolution failure must count") - } + testassert.Require(t, find(t, report, "install", "config-path").Status == StatusFail, "config-path resolution error must FAIL") + testassert.Require(t, report.Failures != 0, "resolution failure must count") }) t.Run("home error stops home-dependent groups", func(t *testing.T) { p := Params{HomeErr: fmt.Errorf("no home"), BinaryPath: "b", Version: "dev", Platform: "linux/amd64"} report, noProviders := Run(p) - if find(t, report, "install", "home").Status != StatusFail { - t.Fatal("home resolution error must FAIL") - } - if noProviders { - t.Fatal("fresh-install hint must not show on a resolution failure") - } + testassert.Require(t, find(t, report, "install", "home").Status == StatusFail, "home resolution error must FAIL") + testassert.Require(t, !noProviders, "fresh-install hint must not show on a resolution failure") for _, c := range report.Checks { - if c.Group != "install" { - t.Fatalf("home-dependent group ran without a home: %+v", c) - } + testassert.Require(t, c.Group == "install", "home-dependent group ran without a home: %+v", c) } }) } @@ -705,25 +594,20 @@ func TestReportJSONShape(t *testing.T) { home := t.TempDir() report, _ := run(t, params(home)) b, err := json.Marshal(report) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) var decoded struct { Checks []map[string]any `json:"checks"` OK *int `json:"ok"` Warn *int `json:"warnings"` Fail *int `json:"failures"` } - if err := json.Unmarshal(b, &decoded); err != nil { - t.Fatal(err) - } - if decoded.OK == nil || decoded.Warn == nil || decoded.Fail == nil || len(decoded.Checks) == 0 { - t.Fatalf("missing top-level fields: %s", b) - } + testassert.NoError(t, json.Unmarshal(b, &decoded)) + testassert.Require(t, decoded.OK != nil && decoded.Warn != nil && decoded.Fail != nil && len(decoded.Checks) != 0, "missing top-level fields: %s", b) for _, c := range decoded.Checks { for _, field := range []string{"group", "name", "status", "detail"} { - if _, ok := c[field]; !ok { - t.Fatalf("check missing %q: %v", field, c) + { + _, ok := c[field] + testassert.Require(t, ok, "check missing %q: %v", field, c) } } } diff --git a/internal/editpreview/preview_test.go b/internal/editpreview/preview_test.go index d7c41595..84f1ca9e 100644 --- a/internal/editpreview/preview_test.go +++ b/internal/editpreview/preview_test.go @@ -3,15 +3,15 @@ package editpreview import ( "fmt" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestBuildReplacementAndLaterInsertion(t *testing.T) { oldString := "func send() {\n\toldA()\n\toldB()\n\toldC()\n\toldD()\n\toldE()\n\tkeepOne()\n\tkeepTwo()\n}" newString := "func send() {\n\tnewA()\n\tnewB()\n\tkeepOne()\n\tkeepTwo()\n\taddedA()\n\taddedB()\n\taddedC()\n}" preview := Build(oldString, newString, "Edited x.go (1 replacement, lines 84-92).") - if preview == nil || len(preview.Hunks) != 1 { - t.Fatalf("expected one hunk, got %#v", preview) - } + testassert.Require(t, preview != nil && len(preview.Hunks) == 1, "expected one hunk, got %#v", preview) got := renderPlain(preview) want := []string{ @@ -30,36 +30,24 @@ func TestBuildReplacementAndLaterInsertion(t *testing.T) { "91 +\taddedC()", "92 }", } - if len(got) != len(want) { - t.Fatalf("expected %d rows, got %d:\n%v", len(want), len(got), got) - } + testassert.Require(t, len(got) == len(want), "expected %d rows, got %d:\n%v", len(want), len(got), got) for i := range want { - if got[i] != want[i] { - t.Fatalf("row %d mismatch\nwant: %q\n got: %q", i, want[i], got[i]) - } + testassert.Require(t, got[i] == want[i], "row %d mismatch\nwant: %q\n got: %q", i, want[i], got[i]) } } func TestBuildMultipleLineRanges(t *testing.T) { preview := Build("old", "new", "Edited x.go (2 replacements, lines 12-12, 40-40).") - if preview == nil || len(preview.Hunks) != 2 { - t.Fatalf("expected two hunks, got %#v", preview) - } - if preview.Hunks[0].Rows[0].OldLine != 12 || preview.Hunks[1].Rows[0].OldLine != 40 { - t.Fatalf("unexpected start lines: %#v", preview.Hunks) - } + testassert.Require(t, preview != nil && len(preview.Hunks) == 2, "expected two hunks, got %#v", preview) + testassert.Require(t, preview.Hunks[0].Rows[0].OldLine == 12 && preview.Hunks[1].Rows[0].OldLine == 40, "unexpected start lines: %#v", preview.Hunks) } func TestParseStartLinesMatchesEditFileSummaryFormat(t *testing.T) { starts := parseStartLines("Edited x.go (3 replacements, lines 12-15, 42-83, 201-205).") want := []int{12, 42, 201} - if len(starts) != len(want) { - t.Fatalf("expected %d starts, got %d: %#v", len(want), len(starts), starts) - } + testassert.Require(t, len(starts) == len(want), "expected %d starts, got %d: %#v", len(want), len(starts), starts) for i := range want { - if starts[i] != want[i] { - t.Fatalf("start %d = %d, want %d", i, starts[i], want[i]) - } + testassert.Require(t, starts[i] == want[i], "start %d = %d, want %d", i, starts[i], want[i]) } } diff --git a/internal/lsp/client_test.go b/internal/lsp/client_test.go index 64035446..50077867 100644 --- a/internal/lsp/client_test.go +++ b/internal/lsp/client_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/lsp/server" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestWorkspaceSymbolAllFailedInstancesIsError: when every mapped instance is @@ -25,9 +26,7 @@ func TestWorkspaceSymbolAllFailedInstancesIsError(t *testing.T) { m.mu.Unlock() content, err := NewClient(m).WorkspaceSymbol(context.Background(), "x") - if err == nil || content != "" || err.Error() != "No language servers available for the symbol query." { - t.Fatalf("WorkspaceSymbol with only failed servers = (%q, %v), want the fixed unavailability error", content, err) - } + testassert.Require(t, err != nil && content == "" && err.Error() == "No language servers available for the symbol query.", "WorkspaceSymbol with only failed servers = (%q, %v), want the fixed unavailability error", content, err) } // TestClientNoInstancesMessages: with no mapped instance at all, the symbol @@ -38,17 +37,11 @@ func TestWorkspaceSymbolAllFailedInstancesIsError(t *testing.T) { func TestClientNoInstancesMessages(t *testing.T) { client := NewClient(NewManager(t.TempDir(), t.TempDir())) got, err := client.WorkspaceSymbol(context.Background(), "x") - if err == nil || got != "" || err.Error() != "No language servers available." { - t.Fatalf("WorkspaceSymbol no instances = %q, %v", got, err) - } + testassert.Require(t, err != nil && got == "" && err.Error() == "No language servers available.", "WorkspaceSymbol no instances = %q, %v", got, err) got, err = client.GetDiagnosticsCanonical(context.Background(), []string{"main.go"}) - if err == nil || got != "" || err.Error() != "main.go: no language server for this file (could not check)" { - t.Fatalf("GetDiagnosticsCanonical unsupported path = %q, %v", got, err) - } + testassert.Require(t, err != nil && got == "" && err.Error() == "main.go: no language server for this file (could not check)", "GetDiagnosticsCanonical unsupported path = %q, %v", got, err) got, err = client.GetDiagnosticsCanonical(context.Background(), nil) - if err == nil || got != "" || err.Error() != "No language servers available for the modified files." { - t.Fatalf("GetDiagnosticsCanonical empty input = %q, %v", got, err) - } + testassert.Require(t, err != nil && got == "" && err.Error() == "No language servers available for the modified files.", "GetDiagnosticsCanonical empty input = %q, %v", got, err) } // TestGetDiagnosticsCanonicalDeadServerIsNotCleanResult pins the sample-time @@ -69,9 +62,7 @@ func TestGetDiagnosticsCanonicalDeadServerIsNotCleanResult(t *testing.T) { // every later launch crashes before readiness, so the instance can never // serve the sample. inst := newFakeServerInstance(t, log, "", 0, 0, "after-open", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) m := NewManager(t.TempDir(), t.TempDir()) def := server.ForExtension(".go") m.mu.Lock() @@ -91,31 +82,18 @@ func TestGetDiagnosticsCanonicalDeadServerIsNotCleanResult(t *testing.T) { m.mu.Unlock() healthy := filepath.Join(t.TempDir(), "healthy.go") - if err := os.WriteFile(healthy, []byte("package healthy\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(healthy, []byte("package healthy\n"), 0o600)) missingPy := filepath.Join(t.TempDir(), "missing.py") content, err := NewClient(m).GetDiagnosticsCanonical(context.Background(), []string{healthy, missingPy}) - if err == nil || content != "" { - t.Fatalf("GetDiagnosticsCanonical with a live but unsynchronized server = (%q, %v), want the could-not-check error, never a successful sample", content, err) - } - if !strings.Contains(err.Error(), "(could not check)") || !strings.Contains(err.Error(), "unavailable") { - t.Fatalf("GetDiagnosticsCanonical error = %q, want the unavailable could-not-check lines", err) - } - if !strings.Contains(err.Error(), "missing.py") || !strings.Contains(err.Error(), "healthy.go") { - t.Fatalf("GetDiagnosticsCanonical error = %q, want both the refused pyright leaf and the dead server's synced path", err) - } + testassert.Require(t, err != nil && content == "", "GetDiagnosticsCanonical with a live but unsynchronized server = (%q, %v), want the could-not-check error, never a successful sample", content, err) + testassert.Require(t, strings.Contains(err.Error(), "(could not check)") && strings.Contains(err.Error(), "unavailable") && strings.Contains(err.Error(), "missing.py") && strings.Contains(err.Error(), "healthy.go"), "GetDiagnosticsCanonical error = %q, want the unavailable could-not-check lines; GetDiagnosticsCanonical error = %q, want both the refused pyright leaf and the dead server's synced path", err, err) // The crash loop really ran to exhaustion over the served sync: the // document reached the first launch, and every later launch died before // readiness. data, err := os.ReadFile(reqLog) - if err != nil || !strings.Contains(string(data), "textDocument/didOpen") { - t.Fatalf("the document sync never reached the server (log %q, err %v)", data, err) - } + testassert.Require(t, err == nil && strings.Contains(string(data), "textDocument/didOpen"), "the document sync never reached the server (log %q, err %v)", data, err) waitForState(t, inst, stateFailed) - if n := countLaunches(log); n != maxRestarts+1 { - t.Fatalf("server processes launched = %d, want %d (the serving launch plus the exhausted restarts)", n, maxRestarts+1) - } + testassert.Equal(t, countLaunches(log), maxRestarts+1) } diff --git a/internal/lsp/instance_ready_test.go b/internal/lsp/instance_ready_test.go index bf372454..28e789c9 100644 --- a/internal/lsp/instance_ready_test.go +++ b/internal/lsp/instance_ready_test.go @@ -24,6 +24,7 @@ import ( "github.com/MMinasyan/lightcode/internal/lsp/protocol" "github.com/MMinasyan/lightcode/internal/lsp/server" + "github.com/MMinasyan/lightcode/internal/testassert" ) // fakeServerScript is a minimal LSP server over stdio that records every @@ -124,13 +125,9 @@ func newFakeServerInstanceWithLifetime(t *testing.T, log, failMarker string, ini t.Helper() home := t.TempDir() cacheDir := filepath.Join(home, ".cache", "lightcode", "lsp", "fake") - if err := os.MkdirAll(cacheDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(cacheDir, 0o755)) script := filepath.Join(cacheDir, "fake-lsp") - if err := os.WriteFile(script, []byte(fakeServerScript), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(script, []byte(fakeServerScript), 0o755)) inst := newInstance(&server.Definition{ Name: "fake", Command: "fake-lsp", @@ -155,18 +152,12 @@ func newPIDRecordingFakeServer(t *testing.T, initDelay string) (home, pidfile, l t.Helper() home = t.TempDir() cacheDir := filepath.Join(home, ".cache", "lightcode", "lsp", "fake") - if err := os.MkdirAll(cacheDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(cacheDir, 0o755)) real := filepath.Join(cacheDir, "fake-lsp.py") - if err := os.WriteFile(real, []byte(fakeServerScript), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(real, []byte(fakeServerScript), 0o755)) wrapper := filepath.Join(cacheDir, "fake-lsp") wrapperScript := "#!/bin/sh\necho $$ > \"${FAKE_PIDFILE:?}\"\nexec python3 \"$(dirname \"$0\")/fake-lsp.py\" \"$@\"\n" - if err := os.WriteFile(wrapper, []byte(wrapperScript), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(wrapper, []byte(wrapperScript), 0o755)) pidfile = filepath.Join(t.TempDir(), "lsp.pid") t.Setenv("FAKE_PIDFILE", pidfile) log = filepath.Join(t.TempDir(), "launches.log") @@ -214,15 +205,9 @@ func TestWaitReadyConcurrentCallsStartOneProcess(t *testing.T) { second := make(chan error, 1) go func() { second <- inst.waitReady(context.Background()) }() - if err := <-second; err != nil { - t.Fatalf("second caller: %v", err) - } - if err := <-first; err != nil { - t.Fatalf("first caller: %v", err) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1", n) - } + testassert.NoError(t, <-second) + testassert.NoError(t, <-first) + testassert.Equal(t, countLaunches(log), 1) } func TestWaitReadyFailedStartAllowsRetry(t *testing.T) { @@ -230,9 +215,7 @@ func TestWaitReadyFailedStartAllowsRetry(t *testing.T) { log := filepath.Join(dir, "launches.log") failMarker := filepath.Join(dir, "fail") inst := newFakeServerInstance(t, log, failMarker, 0, 0, "", "") - if err := os.WriteFile(failMarker, nil, 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(failMarker, nil, 0o644)) var wg sync.WaitGroup errs := make([]error, 2) @@ -246,9 +229,7 @@ func TestWaitReadyFailedStartAllowsRetry(t *testing.T) { wg.Wait() for i, err := range errs { - if err == nil { - t.Errorf("caller %d: error = nil after failed start, want a failure reported", i) - } + testassert.Check(t, err != nil, "caller %d: error = nil after failed start, want a failure reported", i) } // A failed start attempt ends in the idle state, not the failed one: the @@ -256,21 +237,13 @@ func TestWaitReadyFailedStartAllowsRetry(t *testing.T) { inst.mu.Lock() state := inst.state inst.mu.Unlock() - if state != stateIdle { - t.Fatalf("instance state after failed start = %d, want %d (idle)", state, stateIdle) - } + testassert.Equal(t, state, stateIdle) // A failed start attempt must leave the instance usable: once the cause // is removed a fresh wait claims a new launch and reaches ready. - if err := os.Remove(failMarker); err != nil { - t.Fatal(err) - } - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady after failed start: %v", err) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched after retry = %d, want 1", n) - } + testassert.NoError(t, os.Remove(failMarker)) + testassert.NoError(t, inst.waitReady(context.Background())) + testassert.Equal(t, countLaunches(log), 1) } func TestWaitReadyCancelDoesNotAbortStart(t *testing.T) { @@ -291,15 +264,9 @@ func TestWaitReadyCancelDoesNotAbortStart(t *testing.T) { second := make(chan error, 1) go func() { second <- inst.waitReady(context.Background()) }() - if err := <-second; err != nil { - t.Fatalf("second caller: %v, want ready despite the first caller cancelling", err) - } - if err := <-first; err != context.Canceled { - t.Fatalf("first caller: error = %v, want context.Canceled", err) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1", n) - } + testassert.NoError(t, <-second) + testassert.Equal(t, <-first, context.Canceled) + testassert.Equal(t, countLaunches(log), 1) } func TestWaitReadyTimeoutReportsStateNotSuccess(t *testing.T) { @@ -313,12 +280,8 @@ func TestWaitReadyTimeoutReportsStateNotSuccess(t *testing.T) { inst := newFakeServerInstance(t, log, "", 0, -1, "", "") err := inst.waitReady(context.Background()) - if err == nil { - t.Fatal("waitReady error = nil, want a timeout reporting the state") - } - if !strings.Contains(err.Error(), "starting") { - t.Fatalf("waitReady error = %q, want it to name the state", err) - } + testassert.Require(t, err != nil, "waitReady error = nil, want a timeout reporting the state") + testassert.Require(t, strings.Contains(err.Error(), "starting"), "waitReady error = %q, want it to name the state", err) } func TestWaitReadyRecoversAfterCrashBeforeReady(t *testing.T) { @@ -333,17 +296,14 @@ func TestWaitReadyRecoversAfterCrashBeforeReady(t *testing.T) { log := filepath.Join(t.TempDir(), "launches.log") inst := newFakeServerInstance(t, log, "", 0, 0, "first", "") - if err := inst.waitReady(context.Background()); err == nil { - t.Fatal("waitReady error = nil for the crashed launch, want a failure") + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err != nil, "waitReady error = nil for the crashed launch, want a failure") } // The restart must recover the instance: a fresh wait reaches ready. - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady after the restart: %v", err) - } - if n := countLaunches(log); n != 2 { - t.Fatalf("server processes launched = %d, want 2 (crashed attempt plus restart)", n) - } + testassert.NoError(t, inst.waitReady(context.Background())) + testassert.Equal(t, countLaunches(log), 2) } func waitForState(t *testing.T, inst *instance, want int) { @@ -356,9 +316,7 @@ func waitForState(t *testing.T, inst *instance, want int) { if state == want { return } - if time.Now().After(deadline) { - t.Fatalf("instance state = %d, want %d", state, want) - } + testassert.Require(t, !time.Now().After(deadline), "instance state = %d, want %d", state, want) time.Sleep(10 * time.Millisecond) } } @@ -371,30 +329,29 @@ func TestFailedInstanceStaysFailed(t *testing.T) { // The first wait rides the initial launch and is released with its // failure once the attempt dies. - if err := inst.waitReady(context.Background()); err == nil { - t.Fatal("waitReady error = nil for the crashed launch, want a failure") + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err != nil, "waitReady error = nil for the crashed launch, want a failure") } waitForState(t, inst, stateFailed) // A later wait on the failed instance must not launch another process. - if err := inst.waitReady(context.Background()); err == nil { - t.Fatal("waitReady on the failed instance: error = nil, want unavailable") + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err != nil, "waitReady on the failed instance: error = nil, want unavailable") } - if _, err := inst.start(context.Background()); err != nil { - t.Fatalf("start on the failed instance: %v, want a refused no-op", err) - } - if n := countLaunches(log); n != maxRestarts+1 { - t.Fatalf("server processes launched = %d, want %d (the crash loop)", n, maxRestarts+1) + { + _, err := inst.start(context.Background()) + testassert.NoError(t, err) } + testassert.Equal(t, countLaunches(log), maxRestarts+1) } func TestSupersededWatcherIsInert(t *testing.T) { log := filepath.Join(t.TempDir(), "launches.log") inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) // Reproduce the window in which a shutdown followed by a wait replaces // the attempt: kill the running process while holding the lock, so the @@ -429,33 +386,24 @@ func TestSupersededWatcherIsInert(t *testing.T) { state := inst.state restarts := inst.restarts inst.mu.Unlock() - if state != stateStarting { - t.Fatalf("state = %d, want %d (starting): the superseded watcher touched state", state, stateStarting) - } - if restarts != 0 { - t.Fatalf("restarts = %d, want 0: the superseded watcher counted a crash", restarts) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1: the superseded watcher restarted", n) - } + testassert.Equal(t, state, stateStarting) + testassert.Equal(t, restarts, 0) + testassert.Equal(t, countLaunches(log), 1) } func TestStartRefusedWhileReady(t *testing.T) { log := filepath.Join(t.TempDir(), "launches.log") inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) // A launch is already running: starting again must be refused rather than // launch a second process over the live one. - if _, err := inst.start(context.Background()); err != nil { - t.Fatalf("start on a ready instance: %v, want a refused no-op", err) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1", n) + { + _, err := inst.start(context.Background()) + testassert.NoError(t, err) } + testassert.Equal(t, countLaunches(log), 1) } // TestStartAfterPermanentCloseSelfReaps proves the close-loser path: a start @@ -508,26 +456,24 @@ func TestStartAfterPermanentCloseSelfReaps(t *testing.T) { } time.Sleep(10 * time.Millisecond) } - if pid == 0 { - t.Fatal("language server process never launched") - } + testassert.Require(t, pid != 0, "language server process never launched") inst.closeAdmission() close(release) - if err := <-started; err == nil || !strings.Contains(err.Error(), "unavailable") { - t.Fatalf("start after permanent close = %v, want the unavailable error", err) + { + err := <-started + testassert.Require(t, err != nil && strings.Contains(err.Error(), "unavailable"), "start after permanent close = %v, want the unavailable error", err) } // The close loser's process is gone and reaped by the start goroutine // itself: kill(pid, 0) returns ESRCH only once reaped. - if err := syscall.Kill(pid, 0); !errors.Is(err, syscall.ESRCH) { - t.Fatalf("close-loser process %d still alive after start returned (kill err = %v)", pid, err) + { + err := syscall.Kill(pid, 0) + testassert.Require(t, errors.Is(err, syscall.ESRCH), "close-loser process %d still alive after start returned (kill err = %v)", pid, err) } // Exactly one wait happened, and it was this start goroutine's: procDone // and the watcher were never installed, so no other waiter exists. - if n := waits.Load(); n != 1 { - t.Fatalf("process waits before watcher installation = %d, want exactly 1 (the close loser's own reap)", n) - } + testassert.Equal(t, waits.Load(), 1) inst.mu.Lock() state := inst.state procDone := inst.procDone @@ -536,17 +482,14 @@ func TestStartAfterPermanentCloseSelfReaps(t *testing.T) { if state != stateFailed { t.Fatalf("state = %d (%s), want %d (failed)", state, stateName(state), stateFailed) } - if procDone != nil || cmd != nil { - t.Fatalf("close loser installed handles: procDone = %v, cmd = %v", procDone, cmd) - } + testassert.Require(t, procDone == nil && cmd == nil, "close loser installed handles: procDone = %v, cmd = %v", procDone, cmd) // Permanent close cannot restart: a later ordinary start is a refused // no-op and launches no process. - if _, err := inst.start(context.Background()); err != nil { - t.Fatalf("start on the failed instance: %v, want a refused no-op", err) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1", n) + { + _, err := inst.start(context.Background()) + testassert.NoError(t, err) } + testassert.Equal(t, countLaunches(log), 1) } // TestOpenFileCanonicalAfterPermanentCloseRefusesWithoutProtocolWrite proves @@ -559,27 +502,20 @@ func TestOpenFileCanonicalAfterPermanentCloseRefusesWithoutProtocolWrite(t *test t.Setenv("FAKE_LSP_REQUESTS_LOG", reqLog) log := filepath.Join(t.TempDir(), "launches.log") inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) inst.closeAdmission() target := filepath.Join(t.TempDir(), "existing.txt") - if err := os.WriteFile(target, []byte("content"), 0o600); err != nil { - t.Fatal(err) - } - if err := inst.openFileCanonical(context.Background(), target); err == nil || !strings.Contains(err.Error(), "unavailable") { - t.Fatalf("openFileCanonical after permanent close = %v, want the unavailable error", err) + testassert.NoError(t, os.WriteFile(target, []byte("content"), 0o600)) + { + err := inst.openFileCanonical(context.Background(), target) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "unavailable"), "openFileCanonical after permanent close = %v, want the unavailable error", err) } // No protocol write: the server saw the startup initialize/initialized // requests but never a didOpen. data, err := os.ReadFile(reqLog) - if err != nil { - t.Fatalf("read requests log: %v", err) - } - if strings.Contains(string(data), "textDocument/didOpen") { - t.Fatal("openFileCanonical after permanent close sent didOpen to the server") - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), "textDocument/didOpen"), "openFileCanonical after permanent close sent didOpen to the server") } // TestIdleRetirementRestartsWithFreshBudget is the positive idle-retirement @@ -591,9 +527,7 @@ func TestIdleRetirementRestartsWithFreshBudget(t *testing.T) { log := filepath.Join(t.TempDir(), "launches.log") inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) // The idle-retirement path: retire the ready instance without permanent // close. inst.shutdown() @@ -605,9 +539,7 @@ func TestIdleRetirementRestartsWithFreshBudget(t *testing.T) { } // A later ordinary request restarts the retired instance. - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady after idle retirement: %v, want a restart", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) inst.mu.Lock() state = inst.state restarts := inst.restarts @@ -615,12 +547,8 @@ func TestIdleRetirementRestartsWithFreshBudget(t *testing.T) { if state != stateReady { t.Fatalf("state after restart = %d (%s), want %d (ready)", state, stateName(state), stateReady) } - if restarts != 0 { - t.Fatalf("restarts after idle retirement = %d, want a fresh budget", restarts) - } - if n := countLaunches(log); n != 2 { - t.Fatalf("server processes launched = %d, want 2 (initial plus idle-retirement restart)", n) - } + testassert.Equal(t, restarts, 0) + testassert.Equal(t, countLaunches(log), 2) } // TestPermanentCloseWatcherReapsWithoutRestart is the permanent-close watcher @@ -640,22 +568,16 @@ func TestPermanentCloseWatcherReapsWithoutRestart(t *testing.T) { waitCommand = func(cmd *exec.Cmd) error { waits.Add(1); return oldWait(cmd) } t.Cleanup(func() { waitCommand = oldWait }) - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) inst.closeAdmission() inst.mu.Lock() procDone := inst.procDone cmd := inst.cmd inst.mu.Unlock() - if procDone == nil || cmd == nil { - t.Fatal("ready instance has no procDone or cmd") - } + testassert.Require(t, procDone != nil && cmd != nil, "ready instance has no procDone or cmd") // The process exits after permanent close: the watcher reaps it. - if err := cmd.Process.Kill(); err != nil { - t.Fatalf("kill the ready server: %v", err) - } + testassert.NoError(t, cmd.Process.Kill()) select { case <-procDone: case <-time.After(10 * time.Second): @@ -664,9 +586,7 @@ func TestPermanentCloseWatcherReapsWithoutRestart(t *testing.T) { // Exactly one wait happened after the procDone/watcher handoff: the // watcher's own. No teardown path, restart, or second waiter touched the // process. - if n := waits.Load(); n != 1 { - t.Fatalf("process waits after the watcher handoff = %d, want exactly 1 (the watcher's reap)", n) - } + testassert.Equal(t, waits.Load(), 1) inst.mu.Lock() state := inst.state @@ -675,19 +595,14 @@ func TestPermanentCloseWatcherReapsWithoutRestart(t *testing.T) { if state != stateFailed { t.Fatalf("state = %d (%s), want %d (failed)", state, stateName(state), stateFailed) } - if restarts != 0 { - t.Fatalf("restarts = %d, want 0: the watcher relaunched after permanent close", restarts) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1 (no relaunch after permanent close)", n) - } + testassert.Equal(t, restarts, 0) + testassert.Equal(t, countLaunches(log), 1) // A later ordinary request refuses: permanent close cannot restart. - if err := inst.waitReady(context.Background()); err == nil { - t.Fatal("waitReady after permanent close: error = nil, want unavailable") - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched after refused wait = %d, want 1", n) + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err != nil, "waitReady after permanent close: error = nil, want unavailable") } + testassert.Equal(t, countLaunches(log), 1) } func TestSupersededShutdownLeavesNewAttemptIntact(t *testing.T) { @@ -700,9 +615,7 @@ func TestSupersededShutdownLeavesNewAttemptIntact(t *testing.T) { // stays open long enough for a newer attempt to become current. inst := newFakeServerInstance(t, log, "", 0, 0, "", "1") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) done := make(chan struct{}) go func() { inst.shutdown(); close(done) }() @@ -718,9 +631,7 @@ func TestSupersededShutdownLeavesNewAttemptIntact(t *testing.T) { if state == stateShutdown { break } - if time.Now().After(deadline) { - t.Fatal("shutdown never captured the attempt") - } + testassert.Require(t, !time.Now().After(deadline), "shutdown never captured the attempt") time.Sleep(10 * time.Millisecond) } @@ -740,15 +651,9 @@ func TestSupersededShutdownLeavesNewAttemptIntact(t *testing.T) { cmd := inst.cmd procDone := inst.procDone inst.mu.Unlock() - if state != stateStarting { - t.Fatalf("state = %d, want %d (starting): shutdown ended the newer attempt", state, stateStarting) - } - if rpc == nil || cmd == nil { - t.Fatal("shutdown cleared the newer attempt's rpc or cmd") - } - if procDone != fresh { - t.Fatal("shutdown replaced the newer attempt's procDone") - } + testassert.Equal(t, state, stateStarting) + testassert.Require(t, rpc != nil && cmd != nil, "shutdown cleared the newer attempt's rpc or cmd") + testassert.Require(t, procDone == fresh, "shutdown replaced the newer attempt's procDone") } // TestSupersededShutdownKeepsReplacementDiagnostics is the stale-attempt @@ -773,9 +678,7 @@ func TestSupersededShutdownKeepsReplacementDiagnostics(t *testing.T) { waitCommand = func(cmd *exec.Cmd) error { waits.Add(1); return oldWait(cmd) } t.Cleanup(func() { waitCommand = oldWait }) - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) inst.mu.Lock() pid := inst.cmd.Process.Pid inst.mu.Unlock() @@ -795,9 +698,7 @@ func TestSupersededShutdownKeepsReplacementDiagnostics(t *testing.T) { if state == stateShutdown { break } - if time.Now().After(deadline) { - t.Fatal("shutdown never captured the attempt") - } + testassert.Require(t, !time.Now().After(deadline), "shutdown never captured the attempt") time.Sleep(10 * time.Millisecond) } @@ -826,18 +727,15 @@ func TestSupersededShutdownKeepsReplacementDiagnostics(t *testing.T) { <-done diags := inst.fileDiagnostics(uri) - if len(diags) != 1 || diags[0].Message != "replacement error" { - t.Fatalf("diagnostics after the superseded shutdown = %+v, want the replacement attempt's cached error", diags) - } + testassert.Require(t, len(diags) == 1 && diags[0].Message == "replacement error", "diagnostics after the superseded shutdown = %+v, want the replacement attempt's cached error", diags) // The captured attempt's process was killed and reaped inside the // teardown: kill(pid, 0) returns ESRCH only once reaped, and the single // wait is the watcher's own reap. - if err := syscall.Kill(pid, 0); !errors.Is(err, syscall.ESRCH) { - t.Fatalf("captured attempt process %d still alive after the superseded shutdown (kill err = %v)", pid, err) - } - if n := waits.Load(); n != 1 { - t.Fatalf("process waits = %d, want exactly 1 (the captured attempt's watcher reap)", n) + { + err := syscall.Kill(pid, 0) + testassert.Require(t, errors.Is(err, syscall.ESRCH), "captured attempt process %d still alive after the superseded shutdown (kill err = %v)", pid, err) } + testassert.Equal(t, waits.Load(), 1) } // TestStartWithCancelledContextLaunchesNothing proves the pre-spawn guard: a @@ -858,22 +756,15 @@ func TestStartWithCancelledContextLaunchesNothing(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - if _, err := inst.start(ctx); !errors.Is(err, context.Canceled) { - t.Fatalf("start with a cancelled context: error = %v, want context.Canceled", err) - } - if n := starts.Load(); n != 0 { - t.Fatalf("successful cmd.Start invocations = %d, want 0", n) - } - if n := countLaunches(log); n != 0 { - t.Fatalf("server processes launched = %d, want 0", n) + { + _, err := inst.start(ctx) + testassert.Require(t, errors.Is(err, context.Canceled), "start with a cancelled context: error = %v, want context.Canceled", err) } + testassert.Equal(t, starts.Load(), 0) + testassert.Equal(t, countLaunches(log), 0) // The refused attempt ended the launch it claimed, so the instance is // claimable again: a live start still works. - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady after the refused attempt: %v", err) - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server processes launched = %d, want 1 after the live start", n) - } + testassert.NoError(t, inst.waitReady(context.Background())) + testassert.Equal(t, countLaunches(log), 1) } diff --git a/internal/lsp/instance_test.go b/internal/lsp/instance_test.go index 10595444..844112a4 100644 --- a/internal/lsp/instance_test.go +++ b/internal/lsp/instance_test.go @@ -10,6 +10,7 @@ import ( "github.com/MMinasyan/lightcode/internal/lsp/protocol" "github.com/MMinasyan/lightcode/internal/lsp/server" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestInstanceDiagnosticsAndReadyNotification(t *testing.T) { @@ -25,23 +26,15 @@ func TestInstanceDiagnosticsAndReadyNotification(t *testing.T) { URI: uri, Diagnostics: []protocol.Diagnostic{{Severity: &severity, Message: "bad", Range: protocol.Range{Start: protocol.Position{Line: 3}}}}, }) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) inst.handleNotification("textDocument/publishDiagnostics", params, inst.readyCh) diags := inst.fileDiagnostics(uri) - if len(diags) != 1 || diags[0].Message != "bad" || diags[0].Range.Start.Line != 3 { - t.Fatalf("diagnostics = %+v", diags) - } + testassert.Require(t, len(diags) == 1 && diags[0].Message == "bad" && diags[0].Range.Start.Line == 3, "diagnostics = %+v", diags) progress, err := json.Marshal(protocol.ProgressParams{Value: json.RawMessage(`{"kind":"end"}`)}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) inst.handleNotification("$/progress", progress, inst.readyCh) - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady after progress end: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) } // TestCaptureDiagnosticsNeverSamplesClearedCacheAsLive pins the capture @@ -109,9 +102,7 @@ func TestCaptureDiagnosticsNeverSamplesClearedCacheAsLive(t *testing.T) { if !ok { continue } - if len(sample) != 1 || len(sample[0]) != 1 || sample[0][0].Message != "seeded error" { - t.Fatalf("iteration %d: live sample = %+v, want the seeded error or the unavailable refusal", i, sample) - } + testassert.Require(t, len(sample) == 1 && len(sample[0]) == 1 && sample[0][0].Message == "seeded error", "iteration %d: live sample = %+v, want the seeded error or the unavailable refusal", i, sample) } } @@ -125,7 +116,5 @@ func TestInstanceWaitReadyCanceledWhenStarting(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - if err := inst.waitReady(ctx); err != context.Canceled { - t.Fatalf("waitReady canceled = %v, want context.Canceled", err) - } + testassert.Equal(t, inst.waitReady(ctx), context.Canceled) } diff --git a/internal/lsp/jsonrpc/client_test.go b/internal/lsp/jsonrpc/client_test.go index d48cab0b..3a3696fa 100644 --- a/internal/lsp/jsonrpc/client_test.go +++ b/internal/lsp/jsonrpc/client_test.go @@ -9,6 +9,8 @@ import ( "sync" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) type writeCloserBuffer struct { @@ -51,9 +53,7 @@ func TestClientCallSuccessAndServerError(t *testing.T) { waitForWrittenRequest(t, stdin, "workspace/symbol") writeJSONRPCMessage(t, stdoutW, message{JSONRPC: "2.0", ID: intPtr(0), Result: json.RawMessage(`{"ok":true}`)}) got := <-resultCh - if got.err != nil || string(got.data) != `{"ok":true}` { - t.Fatalf("Call success = %s, %v", got.data, got.err) - } + testassert.Require(t, got.err == nil && string(got.data) == `{"ok":true}`, "Call success = %s, %v", got.data, got.err) go func() { data, err := client.Call(context.Background(), "bad", nil) @@ -65,9 +65,7 @@ func TestClientCallSuccessAndServerError(t *testing.T) { waitForWrittenRequest(t, stdin, `"bad"`) writeJSONRPCMessage(t, stdoutW, message{JSONRPC: "2.0", ID: intPtr(1), Error: &ResponseError{Code: -32000, Message: "boom"}}) got = <-resultCh - if got.err == nil || !strings.Contains(got.err.Error(), "boom") { - t.Fatalf("Call server error = %s, %v; want boom error", got.data, got.err) - } + testassert.Require(t, got.err != nil && strings.Contains(got.err.Error(), "boom"), "Call server error = %s, %v; want boom error", got.data, got.err) } func TestClientNotifyAndOnNotify(t *testing.T) { @@ -78,18 +76,14 @@ func TestClientNotifyAndOnNotify(t *testing.T) { client.Start() defer client.Close() - if err := client.Notify("initialized", map[string]any{"ok": true}); err != nil { - t.Fatalf("Notify: %v", err) - } + testassert.NoError(t, client.Notify("initialized", map[string]any{"ok": true})) if !strings.Contains(stdin.String(), "initialized") { t.Fatalf("stdin after Notify = %q", stdin.String()) } writeJSONRPCMessage(t, stdoutW, message{JSONRPC: "2.0", Method: "window/logMessage", Params: json.RawMessage(`{"message":"hi"}`)}) select { case got := <-notifyCh: - if !strings.Contains(got, "window/logMessage") || !strings.Contains(got, "hi") { - t.Fatalf("notification = %q", got) - } + testassert.Require(t, strings.Contains(got, "window/logMessage") && strings.Contains(got, "hi"), "notification = %q", got) case <-time.After(time.Second): t.Fatal("OnNotify not called") } @@ -103,15 +97,15 @@ func TestClientCallContextCanceledAndClose(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - if _, err := client.Call(ctx, "slow", nil); err == nil { - t.Fatal("Call with canceled context error = nil, want error") + { + _, err := client.Call(ctx, "slow", nil) + testassert.Require(t, err != nil, "Call with canceled context error = nil, want error") } client.Close() - if !client.IsClosed() { - t.Fatal("IsClosed = false after Close") - } - if err := client.Notify("x", nil); err == nil { - t.Fatal("Notify after Close error = nil, want error") + testassert.Require(t, client.IsClosed(), "IsClosed = false after Close") + { + err := client.Notify("x", nil) + testassert.Require(t, err != nil, "Notify after Close error = nil, want error") } } @@ -130,12 +124,8 @@ func waitForWrittenRequest(t *testing.T, buf *writeCloserBuffer, contains string func writeJSONRPCMessage(t *testing.T, w io.Writer, msg message) { t.Helper() body, err := json.Marshal(msg) - if err != nil { - t.Fatal(err) - } - if err := WriteMessage(w, body); err != nil { - t.Fatalf("WriteMessage: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, WriteMessage(w, body)) } func intPtr(v int) *int { return &v } diff --git a/internal/lsp/jsonrpc/frame_test.go b/internal/lsp/jsonrpc/frame_test.go index f222b2ce..911a0e9b 100644 --- a/internal/lsp/jsonrpc/frame_test.go +++ b/internal/lsp/jsonrpc/frame_test.go @@ -5,35 +5,29 @@ import ( "bytes" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestWriteReadMessageRoundTrip(t *testing.T) { body := []byte(`{"jsonrpc":"2.0","id":1}`) var buf bytes.Buffer - if err := WriteMessage(&buf, body); err != nil { - t.Fatalf("WriteMessage: %v", err) - } + testassert.NoError(t, WriteMessage(&buf, body)) got, err := ReadMessage(bufio.NewReader(&buf)) - if err != nil { - t.Fatalf("ReadMessage: %v", err) - } - if string(got) != string(body) { - t.Fatalf("body = %q, want %q", got, body) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == string(body), "body = %q, want %q", got, body) } func TestReadMessageHeadersAndErrors(t *testing.T) { got, err := ReadMessage(bufio.NewReader(strings.NewReader("X-Test: yes\r\nContent-Length: 2\r\n\r\n{}"))) - if err != nil { - t.Fatalf("ReadMessage with extra header: %v", err) - } - if string(got) != "{}" { - t.Fatalf("body = %q, want {}", got) - } - if _, err := ReadMessage(bufio.NewReader(strings.NewReader("Header: nope\r\n\r\n{}"))); err == nil { - t.Fatal("missing Content-Length error = nil, want error") + testassert.NoError(t, err) + testassert.Require(t, string(got) == "{}", "body = %q, want {}", got) + { + _, err := ReadMessage(bufio.NewReader(strings.NewReader("Header: nope\r\n\r\n{}"))) + testassert.Require(t, err != nil, "missing Content-Length error = nil, want error") } - if _, err := ReadMessage(bufio.NewReader(strings.NewReader("Content-Length: nope\r\n\r\n{}"))); err == nil { - t.Fatal("bad Content-Length error = nil, want error") + { + _, err := ReadMessage(bufio.NewReader(strings.NewReader("Content-Length: nope\r\n\r\n{}"))) + testassert.Require(t, err != nil, "bad Content-Length error = nil, want error") } } diff --git a/internal/lsp/lifetime_test.go b/internal/lsp/lifetime_test.go index 0d7fa71e..e4e35e3a 100644 --- a/internal/lsp/lifetime_test.go +++ b/internal/lsp/lifetime_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/lsp/server" + "github.com/MMinasyan/lightcode/internal/testassert" "golang.org/x/sys/unix" ) @@ -44,9 +45,7 @@ func TestManagerInstallThreadsContext(t *testing.T) { select { case err := <-done: - if !errors.Is(err, context.Canceled) { - t.Fatalf("install after cancellation = %v, want context.Canceled", err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "install after cancellation = %v, want context.Canceled", err) case <-time.After(5 * time.Second): t.Fatal("install never returned after the owner context was canceled") } @@ -63,9 +62,7 @@ func TestRestartDerivesFromInstanceLifetime(t *testing.T) { lifetime, cancel := context.WithCancel(context.Background()) inst := newFakeServerInstanceWithLifetime(t, log, "", 0, 0, "", "", lifetime) - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("initial waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) cancel() inst.shutdown() inst.mu.Lock() @@ -75,18 +72,15 @@ func TestRestartDerivesFromInstanceLifetime(t *testing.T) { t.Fatalf("state after retirement = %d (%s), want %d (shutdown)", state, stateName(state), stateShutdown) } - if err := inst.waitReady(context.Background()); err == nil { - t.Fatal("waitReady after lifetime cancellation restarted the server; want the restart to fail") + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err != nil, "waitReady after lifetime cancellation restarted the server; want the restart to fail") } inst.mu.Lock() state = inst.state inst.mu.Unlock() - if state == stateReady { - t.Fatal("the restart became ready under a canceled lifetime") - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server launches logged = %d, want 1: the restart under the canceled lifetime never served", n) - } + testassert.Require(t, state != stateReady, "the restart became ready under a canceled lifetime") + testassert.Equal(t, countLaunches(log), 1) } // TestCrashRestartDerivesFromInstanceLifetime pins the crash-restart sibling: @@ -97,34 +91,25 @@ func TestCrashRestartDerivesFromInstanceLifetime(t *testing.T) { lifetime, cancel := context.WithCancel(context.Background()) inst := newFakeServerInstanceWithLifetime(t, log, "", 0, 0, "", "", lifetime) - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("initial waitReady: %v", err) - } + testassert.NoError(t, inst.waitReady(context.Background())) cancel() inst.mu.Lock() cmd := inst.cmd inst.mu.Unlock() - if cmd == nil || cmd.Process == nil { - t.Fatal("ready instance has no process") - } - if err := cmd.Process.Kill(); err != nil { - t.Fatalf("kill the ready server: %v", err) - } + testassert.Require(t, cmd != nil && cmd.Process != nil, "ready instance has no process") + testassert.NoError(t, cmd.Process.Kill()) // The watcher counts the crash and runs its restart, which must fail // under the canceled lifetime and settle the instance back to idle. waitForRestartSettled(t, inst) - if err := inst.waitReady(context.Background()); err == nil { - t.Fatal("waitReady after the crash restart became ready under a canceled lifetime; want the restart to fail") + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err != nil, "waitReady after the crash restart became ready under a canceled lifetime; want the restart to fail") } inst.mu.Lock() state := inst.state inst.mu.Unlock() - if state == stateReady { - t.Fatal("the crash restart became ready under a canceled lifetime") - } - if n := countLaunches(log); n != 1 { - t.Fatalf("server launches logged = %d, want 1: the crash restart under the canceled lifetime never served", n) - } + testassert.Require(t, state != stateReady, "the crash restart became ready under a canceled lifetime") + testassert.Equal(t, countLaunches(log), 1) } // waitForRestartSettled waits until the crash watcher has counted its restart @@ -139,9 +124,7 @@ func waitForRestartSettled(t *testing.T, inst *instance) { if restarts == 1 && state == stateIdle { return } - if time.Now().After(deadline) { - t.Fatalf("restart never settled: restarts=%d state=%d", restarts, state) - } + testassert.Require(t, !time.Now().After(deadline), "restart never settled: restarts=%d state=%d", restarts, state) time.Sleep(10 * time.Millisecond) } } @@ -167,12 +150,24 @@ func TestWorkspaceSymbolReturnsCanceledNotSwallowed(t *testing.T) { cancel() client := NewClient(m) content, err := client.WorkspaceSymbol(ctx, "x") - if !errors.Is(err, context.Canceled) { - t.Fatalf("WorkspaceSymbol canceled = (%q, %v), want the cancellation error", content, err) - } - if content != "" { - t.Fatalf("WorkspaceSymbol canceled content = %q, want the empty partial", content) - } + testassert.Require(t, errors.Is(err, context.Canceled), "WorkspaceSymbol canceled = (%q, %v), want the cancellation error", content, err) + testassert.Equal(t, content, "") +} + +// readyGoInstance registers one all-zero ready fake .go instance under a new +// Manager. Per-test files, request-log setup, instance-lock gates and queries +// stay outside. +func readyGoInstance(t *testing.T) (*instance, *Manager) { + t.Helper() + log := filepath.Join(t.TempDir(), "launches.log") + inst := newFakeServerInstance(t, log, "", 0, 0, "", "") + testassert.NoError(t, inst.waitReady(context.Background())) + m := NewManager(t.TempDir(), t.TempDir()) + def := server.ForExtension(".go") + m.mu.Lock() + m.instances[def.Name] = inst + m.mu.Unlock() + return inst, m } // TestGetDiagnosticsCanonicalCancellationReturnsPartial pins the cancellation @@ -186,22 +181,11 @@ func TestWorkspaceSymbolReturnsCanceledNotSwallowed(t *testing.T) { // returned partial proves the query reached the window instead of the // nothing-synchronized gate. func TestGetDiagnosticsCanonicalCancellationReturnsPartial(t *testing.T) { - log := filepath.Join(t.TempDir(), "launches.log") - inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } - m := NewManager(t.TempDir(), t.TempDir()) - def := server.ForExtension(".go") - m.mu.Lock() - m.instances[def.Name] = inst - m.mu.Unlock() + inst, m := readyGoInstance(t) dir := t.TempDir() healthy := filepath.Join(dir, "healthy.go") - if err := os.WriteFile(healthy, []byte("package healthy\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(healthy, []byte("package healthy\n"), 0o600)) missing := filepath.Join(dir, "missing.go") ctx, cancel := context.WithCancel(context.Background()) @@ -212,15 +196,9 @@ func TestGetDiagnosticsCanonicalCancellationReturnsPartial(t *testing.T) { defer close(done) client := NewClient(m) content, err := client.GetDiagnosticsCanonical(ctx, []string{missing, healthy}) - if !errors.Is(err, context.Canceled) { - t.Errorf("GetDiagnosticsCanonical canceled = (%q, %v), want the cancellation error", content, err) - } - if !strings.Contains(content, "(could not check)") { - t.Errorf("GetDiagnosticsCanonical canceled content = %q, want the partial could-not-check line", content) - } - if !strings.Contains(content, "missing.go") || strings.Contains(content, "healthy.go") { - t.Errorf("GetDiagnosticsCanonical canceled content = %q, want the missing leaf's refusal only", content) - } + testassert.Check(t, errors.Is(err, context.Canceled), "GetDiagnosticsCanonical canceled = (%q, %v), want the cancellation error", content, err) + testassert.Check(t, strings.Contains(content, "(could not check)"), "GetDiagnosticsCanonical canceled content = %q, want the partial could-not-check line", content) + testassert.Check(t, strings.Contains(content, "missing.go") && !strings.Contains(content, "healthy.go"), "GetDiagnosticsCanonical canceled content = %q, want the missing leaf's refusal only", content) }() cancel() inst.mu.Unlock() @@ -239,48 +217,23 @@ func TestGetDiagnosticsCanonicalCancellationReturnsPartial(t *testing.T) { func TestGetDiagnosticsCanonicalRefusesReplacedSymlink(t *testing.T) { reqLog := filepath.Join(t.TempDir(), "requests.log") t.Setenv("FAKE_LSP_REQUESTS_LOG", reqLog) - log := filepath.Join(t.TempDir(), "launches.log") - inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } - m := NewManager(t.TempDir(), t.TempDir()) - def := server.ForExtension(".go") - m.mu.Lock() - m.instances[def.Name] = inst - m.mu.Unlock() + _, m := readyGoInstance(t) dir := t.TempDir() outside := filepath.Join(t.TempDir(), "outside.go") - if err := os.WriteFile(outside, []byte("package outside\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(outside, []byte("package outside\n"), 0o600)) swapped := filepath.Join(dir, "swapped.go") - if err := os.WriteFile(swapped, []byte("package swapped\n"), 0o600); err != nil { - t.Fatal(err) - } - if err := os.Remove(swapped); err != nil { - t.Fatal(err) - } - if err := os.Symlink(outside, swapped); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(swapped, []byte("package swapped\n"), 0o600)) + testassert.NoError(t, os.Remove(swapped)) + testassert.NoError(t, os.Symlink(outside, swapped)) client := NewClient(m) content, err := client.GetDiagnosticsCanonical(context.Background(), []string{swapped}) - if err == nil || content != "" { - t.Fatalf("GetDiagnosticsCanonical = (%q, %v), want the all-failed error for the replaced symlink", content, err) - } - if !strings.Contains(err.Error(), "(could not check)") { - t.Fatalf("GetDiagnosticsCanonical error = %q, want the could-not-check refusal line", err) - } + testassert.Require(t, err != nil && content == "", "GetDiagnosticsCanonical = (%q, %v), want the all-failed error for the replaced symlink", content, err) + testassert.Require(t, strings.Contains(err.Error(), "(could not check)"), "GetDiagnosticsCanonical error = %q, want the could-not-check refusal line", err) data, err := os.ReadFile(reqLog) - if err != nil { - t.Fatalf("read requests log: %v", err) - } - if strings.Contains(string(data), "didOpen") || strings.Contains(string(data), "didChange") { - t.Fatalf("canonical read sent document sync for the symlink leaf: %s", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), "didOpen") && !strings.Contains(string(data), "didChange"), "canonical read sent document sync for the symlink leaf: %s", data) } // TestGetDiagnosticsCanonicalWriterlessFIFOIsCouldNotCheck pins the @@ -288,16 +241,7 @@ func TestGetDiagnosticsCanonicalRefusesReplacedSymlink(t *testing.T) { // cannot block the openat — the no-follow open settles immediately, none // synchronizes, and the retained could-not-check line is the all-failed error. func TestGetDiagnosticsCanonicalWriterlessFIFOIsCouldNotCheck(t *testing.T) { - log := filepath.Join(t.TempDir(), "launches.log") - inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } - m := NewManager(t.TempDir(), t.TempDir()) - def := server.ForExtension(".go") - m.mu.Lock() - m.instances[def.Name] = inst - m.mu.Unlock() + _, m := readyGoInstance(t) fifo := filepath.Join(t.TempDir(), "fifo.go") if err := unix.Mkfifo(fifo, 0o600); err != nil { @@ -316,12 +260,8 @@ func TestGetDiagnosticsCanonicalWriterlessFIFOIsCouldNotCheck(t *testing.T) { }() select { case got := <-done: - if got.err == nil || got.content != "" { - t.Fatalf("GetDiagnosticsCanonical writerless FIFO = (%q, %v), want the all-failed error", got.content, got.err) - } - if !strings.Contains(got.err.Error(), "(could not check)") { - t.Fatalf("GetDiagnosticsCanonical error = %q, want the could-not-check refusal line", got.err) - } + testassert.Require(t, got.err != nil && got.content == "", "GetDiagnosticsCanonical writerless FIFO = (%q, %v), want the all-failed error", got.content, got.err) + testassert.Require(t, strings.Contains(got.err.Error(), "(could not check)"), "GetDiagnosticsCanonical error = %q, want the could-not-check refusal line", got.err) case <-time.After(5 * time.Second): t.Fatal("GetDiagnosticsCanonical blocked on the writerless FIFO leaf; want the could-not-check line") } @@ -333,38 +273,18 @@ func TestGetDiagnosticsCanonicalWriterlessFIFOIsCouldNotCheck(t *testing.T) { // synchronizable regular sibling still produces the explicit failed-path lines // and the sampled result. func TestGetDiagnosticsCanonicalRefusedDocumentsAreCouldNotCheck(t *testing.T) { - log := filepath.Join(t.TempDir(), "launches.log") - inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } - m := NewManager(t.TempDir(), t.TempDir()) - def := server.ForExtension(".go") - m.mu.Lock() - m.instances[def.Name] = inst - m.mu.Unlock() + _, m := readyGoInstance(t) dir := t.TempDir() healthy := filepath.Join(dir, "healthy.go") - if err := os.WriteFile(healthy, []byte("package healthy\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(healthy, []byte("package healthy\n"), 0o600)) missing := filepath.Join(dir, "gone.go") unsupported := filepath.Join(dir, "notes.txt") - if err := os.WriteFile(unsupported, []byte("notes\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(unsupported, []byte("notes\n"), 0o600)) client := NewClient(m) got, err := client.GetDiagnosticsCanonical(context.Background(), []string{missing, unsupported, healthy}) - if err != nil { - t.Fatalf("GetDiagnosticsCanonical: %v", err) - } - if !strings.Contains(got, "gone.go") || !strings.Contains(got, "notes.txt") { - t.Fatalf("GetDiagnosticsCanonical = %q, want the failed-path lines for the refused documents", got) - } - if !strings.Contains(got, "(could not check)") { - t.Fatalf("GetDiagnosticsCanonical = %q, want the could-not-check rendering of the refused paths", got) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(got, "gone.go") && strings.Contains(got, "notes.txt") && strings.Contains(got, "(could not check)"), "GetDiagnosticsCanonical = %q, want the failed-path lines for the refused documents; GetDiagnosticsCanonical = %q, want the could-not-check rendering of the refused paths", got, got) } // TestGetDiagnosticsCanonicalRefusesHardlinkedLeaf pins the hardlink sibling @@ -374,40 +294,19 @@ func TestGetDiagnosticsCanonicalRefusedDocumentsAreCouldNotCheck(t *testing.T) { func TestGetDiagnosticsCanonicalRefusesHardlinkedLeaf(t *testing.T) { reqLog := filepath.Join(t.TempDir(), "requests.log") t.Setenv("FAKE_LSP_REQUESTS_LOG", reqLog) - log := filepath.Join(t.TempDir(), "launches.log") - inst := newFakeServerInstance(t, log, "", 0, 0, "", "") - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady: %v", err) - } - m := NewManager(t.TempDir(), t.TempDir()) - def := server.ForExtension(".go") - m.mu.Lock() - m.instances[def.Name] = inst - m.mu.Unlock() + _, m := readyGoInstance(t) dir := t.TempDir() original := filepath.Join(dir, "original.go") - if err := os.WriteFile(original, []byte("package original\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(original, []byte("package original\n"), 0o600)) aliased := filepath.Join(dir, "aliased.go") - if err := os.Link(original, aliased); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Link(original, aliased)) client := NewClient(m) content, err := client.GetDiagnosticsCanonical(context.Background(), []string{aliased}) - if err == nil || content != "" { - t.Fatalf("GetDiagnosticsCanonical = (%q, %v), want the all-failed error for the hardlinked leaf", content, err) - } - if !strings.Contains(err.Error(), "(could not check)") || !strings.Contains(err.Error(), "hardlink") { - t.Fatalf("GetDiagnosticsCanonical error = %q, want the could-not-check refusal line", err) - } + testassert.Require(t, err != nil && content == "", "GetDiagnosticsCanonical = (%q, %v), want the all-failed error for the hardlinked leaf", content, err) + testassert.Require(t, strings.Contains(err.Error(), "(could not check)") && strings.Contains(err.Error(), "hardlink"), "GetDiagnosticsCanonical error = %q, want the could-not-check refusal line", err) data, err := os.ReadFile(reqLog) - if err != nil { - t.Fatalf("read requests log: %v", err) - } - if strings.Contains(string(data), "didOpen") || strings.Contains(string(data), "didChange") { - t.Fatalf("canonical read sent document sync for the hardlinked leaf: %s", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), "didOpen") && !strings.Contains(string(data), "didChange"), "canonical read sent document sync for the hardlinked leaf: %s", data) } diff --git a/internal/lsp/manager_test.go b/internal/lsp/manager_test.go index 24995ccd..a0539bfa 100644 --- a/internal/lsp/manager_test.go +++ b/internal/lsp/manager_test.go @@ -15,30 +15,30 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/lsp/server" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestManagerForFileAndAllInstances(t *testing.T) { m := NewManager(t.TempDir(), t.TempDir()) - if got := m.ForFile("main.go"); got != nil { - t.Fatalf("ForFile without instance = %+v, want nil", got) + { + got := m.ForFile("main.go") + testassert.Require(t, got == nil, "ForFile without instance = %+v, want nil", got) } - if got := m.ForFile("README"); got != nil { - t.Fatalf("ForFile without extension = %+v, want nil", got) + { + got := m.ForFile("README") + testassert.Require(t, got == nil, "ForFile without extension = %+v, want nil", got) } - if got := m.ForFile("file.unknown"); got != nil { - t.Fatalf("ForFile unknown extension = %+v, want nil", got) + { + got := m.ForFile("file.unknown") + testassert.Require(t, got == nil, "ForFile unknown extension = %+v, want nil", got) } def := server.ForExtension(".go") inst := newInstance(def, m.projectRoot, m.home, context.Background(), nil) m.instances[def.Name] = inst - if got := m.ForFile("main.go"); got != inst { - t.Fatalf("ForFile(main.go) = %+v, want inserted instance", got) - } + testassert.Equal(t, m.ForFile("main.go"), inst) all := m.AllInstances() - if len(all) != 1 || all[0] != inst { - t.Fatalf("AllInstances = %+v, want inserted instance", all) - } + testassert.Require(t, len(all) == 1 && all[0] == inst, "AllInstances = %+v, want inserted instance", all) } func TestManagerConcurrentHandlerAndInstances(t *testing.T) { @@ -75,9 +75,7 @@ func TestManagerWarningHandler(t *testing.T) { m.SetWarningHandler(func(kind, message string) { warningKind, warningMessage = kind, message }) m.emitWarning("kind", "message") - if warningKind != "kind" || warningMessage != "message" { - t.Fatalf("warning = %q/%q", warningKind, warningMessage) - } + testassert.Require(t, warningKind == "kind" && warningMessage == "message", "warning = %q/%q", warningKind, warningMessage) } func TestManagerShutdownAllKillsUnresponsiveServers(t *testing.T) { @@ -97,8 +95,9 @@ func TestManagerShutdownAllKillsUnresponsiveServers(t *testing.T) { stubborn = "1" } inst := newFakeServerInstance(t, log, "", 0, 0, "", stubborn) - if err := inst.waitReady(context.Background()); err != nil { - t.Fatalf("waitReady server %d: %v", i, err) + { + err := inst.waitReady(context.Background()) + testassert.Require(t, err == nil, "waitReady server %d: %v", i, err) } servers[i] = server{inst: inst, pid: inst.cmd.Process.Pid, stubborn: stubborn == "1"} } @@ -117,15 +116,14 @@ func TestManagerShutdownAllKillsUnresponsiveServers(t *testing.T) { t.Logf("ShutdownAll with %d unresponsive servers took %s", 2, elapsed) // The negotiate path waits shutdownWait per unresponsive instance before // killing it; the teardown must kill instead, well below that. - if elapsed >= 2*time.Second { - t.Fatalf("ShutdownAll took %s, want a prompt teardown (the negotiate path waited %s per unresponsive server)", elapsed, shutdownWait) - } + testassert.Require(t, elapsed < 2*time.Second, "ShutdownAll took %s, want a prompt teardown (the negotiate path waited %s per unresponsive server)", elapsed, shutdownWait) for i, s := range servers { // The process must be gone and reaped, not merely left to the owner's // own process exit: kill(pid, 0) returns ESRCH only once reaped. - if err := syscall.Kill(s.pid, 0); !errors.Is(err, syscall.ESRCH) { - t.Errorf("server %d (stubborn=%v) process %d still alive after ShutdownAll: kill err = %v", i, s.stubborn, s.pid, err) + { + err := syscall.Kill(s.pid, 0) + testassert.Check(t, errors.Is(err, syscall.ESRCH), "server %d (stubborn=%v) process %d still alive after ShutdownAll: kill err = %v", i, s.stubborn, s.pid, err) } s.inst.mu.Lock() state := s.inst.state @@ -137,9 +135,7 @@ func TestManagerShutdownAllKillsUnresponsiveServers(t *testing.T) { if state != stateFailed { t.Errorf("server %d state = %d (%s), want %d (failed)", i, state, stateName(state), stateFailed) } - if rpc != nil || cmd != nil { - t.Errorf("server %d handles not cleared: rpc = %v, cmd = %v", i, rpc, cmd) - } + testassert.Check(t, rpc == nil && cmd == nil, "server %d handles not cleared: rpc = %v, cmd = %v", i, rpc, cmd) } } @@ -160,21 +156,18 @@ func TestStartServerAfterClosedRejectsBeforeStart(t *testing.T) { elapsed := time.Since(start) t.Logf("startServer after closed took %s", elapsed) // Rejection happens before any start or negotiation, so it is prompt. - if elapsed >= 2*time.Second { - t.Fatalf("startServer after closed took %s, want an immediate rejection", elapsed) - } + testassert.Require(t, elapsed < 2*time.Second, "startServer after closed took %s, want an immediate rejection", elapsed) // No process was ever launched: the wrapper never wrote the pidfile. - if _, err := os.Stat(pidfile); !os.IsNotExist(err) { - t.Fatalf("pidfile exists after rejected start: a process was launched after close (stat err = %v)", err) + { + _, err := os.Stat(pidfile) + testassert.Require(t, os.IsNotExist(err), "pidfile exists after rejected start: a process was launched after close (stat err = %v)", err) } // And no instance was registered. m.mu.Lock() _, mapped := m.instances[def.Name] m.mu.Unlock() - if mapped { - t.Fatal("startServer after closed registered an instance") - } + testassert.Require(t, !mapped, "startServer after closed registered an instance") } // TestStartServerAfterClosedSkipsInstall covers the LSP close-before-install @@ -206,21 +199,15 @@ func TestStartServerAfterClosedSkipsInstall(t *testing.T) { m.startServer(context.Background(), def) elapsed := time.Since(start) t.Logf("startServer after CloseAdmission with missing binary took %s", elapsed) - if elapsed >= 2*time.Second { - t.Fatalf("startServer after CloseAdmission took %s, want an immediate rejection", elapsed) - } - if installed { - t.Fatal("Install was called after CloseAdmission: the early admission check did not reject") - } + testassert.Require(t, elapsed < 2*time.Second, "startServer after CloseAdmission took %s, want an immediate rejection", elapsed) + testassert.Require(t, !installed, "Install was called after CloseAdmission: the early admission check did not reject") if warnings.Load() != 0 { t.Fatalf("warnings=%d after rejected start, want none", warnings.Load()) } m.mu.Lock() _, mapped := m.instances[def.Name] m.mu.Unlock() - if mapped { - t.Fatal("startServer after CloseAdmission mapped an instance") - } + testassert.Require(t, !mapped, "startServer after CloseAdmission mapped an instance") } // TestStartServerCloseRaceProvisionalMappingHoldsTerminalAdmission proves the @@ -261,9 +248,7 @@ func TestStartServerCloseRaceProvisionalMappingHoldsTerminalAdmission(t *testing m.ShutdownAll() elapsed := time.Since(start) t.Logf("ShutdownAll with a probe-blocked start took %s", elapsed) - if elapsed >= 2*time.Second { - t.Fatalf("ShutdownAll took %s, want a prompt permanent close", elapsed) - } + testassert.Require(t, elapsed < 2*time.Second, "ShutdownAll took %s, want a prompt permanent close", elapsed) close(release) select { @@ -274,18 +259,15 @@ func TestStartServerCloseRaceProvisionalMappingHoldsTerminalAdmission(t *testing // The released start could not launch an OS process: the mapped instance // was already terminal, so inst.start refused before cmd.Start. - if _, err := os.Stat(pidfile); !os.IsNotExist(err) { - t.Fatalf("pidfile exists: the released start launched a process after permanent close (stat err = %v)", err) - } - if n := countLaunches(log); n != 0 { - t.Fatalf("server processes launched = %d, want 0", n) + { + _, err := os.Stat(pidfile) + testassert.Require(t, os.IsNotExist(err), "pidfile exists: the released start launched a process after permanent close (stat err = %v)", err) } + testassert.Equal(t, countLaunches(log), 0) m.mu.Lock() inst := m.instances[def.Name] m.mu.Unlock() - if inst == nil { - t.Fatal("provisionally mapped instance missing after permanent close") - } + testassert.Require(t, inst != nil, "provisionally mapped instance missing after permanent close") inst.mu.Lock() state := inst.state inst.mu.Unlock() @@ -338,9 +320,7 @@ func TestStartServerCloseRaceMappedInstanceIsTornDown(t *testing.T) { m.mu.Lock() _, mapped := m.instances[def.Name] m.mu.Unlock() - if !mapped { - t.Fatal("startServer never mapped the instance") - } + testassert.Require(t, mapped, "startServer never mapped the instance") // The wrapper's pidfile can precede handle installation. Receiving // initialize proves cmd/procDone are installed and the delayed call is // underway, so teardown owns the process this row asserts was reaped. @@ -356,29 +336,24 @@ func TestStartServerCloseRaceMappedInstanceIsTornDown(t *testing.T) { } time.Sleep(10 * time.Millisecond) } - if pid == 0 { - t.Fatal("language server never reached initialization") - } + testassert.Require(t, pid != 0, "language server never reached initialization") start := time.Now() m.ShutdownAll() elapsed := time.Since(start) t.Logf("ShutdownAll with a mid-start mapped server took %s", elapsed) - if elapsed >= 2*time.Second { - t.Fatalf("ShutdownAll took %s, want a prompt teardown (no negotiation with the unresponsive server)", elapsed) - } + testassert.Require(t, elapsed < 2*time.Second, "ShutdownAll took %s, want a prompt teardown (no negotiation with the unresponsive server)", elapsed) // The mapped mid-start instance's process is killed and reaped inside the // call: kill(pid, 0) returns ESRCH only once reaped. - if err := syscall.Kill(pid, 0); !errors.Is(err, syscall.ESRCH) { - t.Fatalf("mid-start server process %d still alive after ShutdownAll returned (kill err = %v)", pid, err) + { + err := syscall.Kill(pid, 0) + testassert.Require(t, errors.Is(err, syscall.ESRCH), "mid-start server process %d still alive after ShutdownAll returned (kill err = %v)", pid, err) } // The instance ended terminal and the failed start cleans the mapping. <-started m.mu.Lock() _, stillMapped := m.instances[def.Name] m.mu.Unlock() - if stillMapped { - t.Fatal("failed start left its instance mapped") - } + testassert.Require(t, !stillMapped, "failed start left its instance mapped") } diff --git a/internal/lsp/protocol/types_test.go b/internal/lsp/protocol/types_test.go index 8a4e20f5..422087b0 100644 --- a/internal/lsp/protocol/types_test.go +++ b/internal/lsp/protocol/types_test.go @@ -3,19 +3,18 @@ package protocol import ( "encoding/json" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestURIPathRoundTrip(t *testing.T) { path := "/tmp/light code/файл.go" uri := URIFromPath(path) - if got := PathFromURI(uri); got != path { - t.Fatalf("PathFromURI(URIFromPath(%q)) = %q", path, got) - } - if got := PathFromURI("file:///tmp/a%20b.go"); got != "/tmp/a b.go" { - t.Fatalf("PathFromURI decoded = %q", got) - } - if got := PathFromURI("not a uri"); got == "" { - t.Fatal("PathFromURI invalid URI returned empty path") + testassert.Equal(t, PathFromURI(uri), path) + testassert.Equal(t, PathFromURI("file:///tmp/a%20b.go"), "/tmp/a b.go") + { + got := PathFromURI("not a uri") + testassert.Require(t, got != "", "PathFromURI invalid URI returned empty path") } } @@ -30,12 +29,8 @@ func TestHoverContentsUnmarshalVariants(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got HoverContents - if err := json.Unmarshal([]byte(tt.input), &got); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if got.Value != tt.want { - t.Fatalf("Value = %q, want %q", got.Value, tt.want) - } + testassert.NoError(t, json.Unmarshal([]byte(tt.input), &got)) + testassert.Equal(t, got.Value, tt.want) }) } } diff --git a/internal/lsp/server/definitions_ctx_test.go b/internal/lsp/server/definitions_ctx_test.go index 94965ff9..50ae516c 100644 --- a/internal/lsp/server/definitions_ctx_test.go +++ b/internal/lsp/server/definitions_ctx_test.go @@ -10,6 +10,8 @@ import ( "os/exec" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestRunWithTimeoutContextCancellation(t *testing.T) { @@ -23,9 +25,7 @@ func TestRunWithTimeoutContextCancellation(t *testing.T) { select { case err := <-done: - if !errors.Is(err, context.Canceled) { - t.Fatalf("runWithTimeout after cancellation = %v, want context.Canceled", err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "runWithTimeout after cancellation = %v, want context.Canceled", err) case <-time.After(5 * time.Second): t.Fatal("runWithTimeout never returned after the lifetime was canceled") } @@ -37,10 +37,6 @@ func TestRunWithTimeoutPreservesDeadline(t *testing.T) { start := time.Now() err := runWithTimeout(context.Background(), exec.Command("sleep", "30"), 100*time.Millisecond) elapsed := time.Since(start) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("runWithTimeout past the deadline = %v, want context.DeadlineExceeded", err) - } - if elapsed >= 5*time.Second { - t.Fatalf("runWithTimeout took %s, want a prompt deadline kill", elapsed) - } + testassert.Require(t, errors.Is(err, context.DeadlineExceeded), "runWithTimeout past the deadline = %v, want context.DeadlineExceeded", err) + testassert.Require(t, elapsed < 5*time.Second, "runWithTimeout took %s, want a prompt deadline kill", elapsed) } diff --git a/internal/lsp/server/definitions_test.go b/internal/lsp/server/definitions_test.go index 97a75660..1ca64cf0 100644 --- a/internal/lsp/server/definitions_test.go +++ b/internal/lsp/server/definitions_test.go @@ -4,47 +4,38 @@ import ( "os" "path/filepath" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestDefinitionsLookupAndDetection(t *testing.T) { - if len(All()) == 0 { - t.Fatal("All() returned no definitions") - } - if got := ForExtension(".go"); got == nil || got.Name != "gopls" { - t.Fatalf("ForExtension(.go) = %+v, want gopls", got) + testassert.Require(t, len(All()) != 0, "All() returned no definitions") + { + got := ForExtension(".go") + testassert.Require(t, got != nil && got.Name == "gopls", "ForExtension(.go) = %+v, want gopls", got) } - if got := ForExtension(".unknown"); got != nil { - t.Fatalf("ForExtension(.unknown) = %+v, want nil", got) + { + got := ForExtension(".unknown") + testassert.Require(t, got == nil, "ForExtension(.unknown) = %+v, want nil", got) } root := t.TempDir() - if got := DetectFromProject(root); len(got) != 0 { - t.Fatalf("DetectFromProject(empty) = %+v, want none", got) - } - if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module x\n"), 0o600); err != nil { - t.Fatal(err) + { + got := DetectFromProject(root) + testassert.Require(t, len(got) == 0, "DetectFromProject(empty) = %+v, want none", got) } + testassert.NoError(t, os.WriteFile(filepath.Join(root, "go.mod"), []byte("module x\n"), 0o600)) found := DetectFromProject(root) - if len(found) != 1 || found[0].Name != "gopls" { - t.Fatalf("DetectFromProject(go.mod) = %+v, want gopls", found) - } + testassert.Require(t, len(found) == 1 && found[0].Name == "gopls", "DetectFromProject(go.mod) = %+v, want gopls", found) } func TestCacheDirAndResolveBinaryPrefersCache(t *testing.T) { home := t.TempDir() def := &Definition{Name: "fake", Command: "fake-lsp"} cache := CacheDir(home, def.Name) - if cache != filepath.Join(home, ".cache", "lightcode", "lsp", "fake") { - t.Fatalf("CacheDir = %q", cache) - } + testassert.Require(t, cache == filepath.Join(home, ".cache", "lightcode", "lsp", "fake"), "CacheDir = %q", cache) bin := filepath.Join(cache, def.Command) - if err := os.MkdirAll(cache, 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o700); err != nil { - t.Fatal(err) - } - if got := ResolveBinary(home, def); got != bin { - t.Fatalf("ResolveBinary = %q, want cached binary %q", got, bin) - } + testassert.NoError(t, os.MkdirAll(cache, 0o700)) + testassert.NoError(t, os.WriteFile(bin, []byte("#!/bin/sh\n"), 0o700)) + testassert.Equal(t, ResolveBinary(home, def), bin) } diff --git a/internal/pathutil/resolver_test.go b/internal/pathutil/resolver_test.go index 8ba984d5..d6c59e68 100644 --- a/internal/pathutil/resolver_test.go +++ b/internal/pathutil/resolver_test.go @@ -4,26 +4,20 @@ import ( "os" "path/filepath" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestResolveFilePathPreservesMultipleMissingParents(t *testing.T) { realRoot := t.TempDir() linkDir := filepath.Join(t.TempDir(), "link") - if err := os.Symlink(realRoot, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realRoot, linkDir)) requested := filepath.Join(linkDir, "a", "b", "new.txt") resolved, err := ResolveFilePath(requested) - if err != nil { - t.Fatalf("ResolveFilePath error = %v", err) - } + testassert.NoError(t, err) want := filepath.Join(realRoot, "a", "b", "new.txt") - if resolved.CanonicalPath != want { - t.Fatalf("CanonicalPath = %q, want %q", resolved.CanonicalPath, want) - } - if resolved.LeafExists { - t.Fatal("LeafExists = true, want false") - } + testassert.Equal(t, resolved.CanonicalPath, want) + testassert.Require(t, !resolved.LeafExists, "LeafExists = true, want false") } diff --git a/internal/plugins/adaptation/adaptation_test.go b/internal/plugins/adaptation/adaptation_test.go index 8ca6a18b..ba8eab2f 100644 --- a/internal/plugins/adaptation/adaptation_test.go +++ b/internal/plugins/adaptation/adaptation_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/plugins/adaptation" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -15,30 +16,18 @@ import ( func resolvePlugin(t *testing.T, ctx context.Context, ref model.ModelRef) (runtime.Adaptation, error) { t.Helper() instance, err := adaptation.Plugin().Open(ctx, runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, runtime.Bindings{}) - if err != nil { - t.Fatalf("plugin Open: %v", err) - } - if instance.Close != nil { - t.Fatal("the adaptation plugin declares a Close") - } + testassert.NoError(t, err) + testassert.Require(t, instance.Close == nil, "the adaptation plugin declares a Close") resolver, ok := instance.Values["model_adaptation"].(runtime.ModelAdaptation) - if !ok { - t.Fatalf("open supplied %T, want a runtime.ModelAdaptation", instance.Values["model_adaptation"]) - } + testassert.Require(t, ok, "open supplied %T, want a runtime.ModelAdaptation", instance.Values["model_adaptation"]) return resolver.Resolve(ref) } func TestPluginDeclaresOneRuntimeScopedModelAdaptation(t *testing.T) { plugin := adaptation.Plugin() - if plugin.ID != "adaptation" || plugin.Scope != runtime.ScopeRuntime { - t.Fatalf("plugin identity = %q/%s, want adaptation/runtime", plugin.ID, plugin.Scope) - } - if plugin.ValidateConfig != nil { - t.Fatal("the adaptation plugin declares a settings validator") - } - if len(plugin.Provides) != 1 { - t.Fatalf("provides = %d capabilities, want the single ModelAdaptation export", len(plugin.Provides)) - } + testassert.Require(t, plugin.ID == "adaptation" && plugin.Scope == runtime.ScopeRuntime, "plugin identity = %q/%s, want adaptation/runtime", plugin.ID, plugin.Scope) + testassert.Require(t, plugin.ValidateConfig == nil, "the adaptation plugin declares a settings validator") + testassert.Equal(t, len(plugin.Provides), 1) } func TestResolveShippedBundledRows(t *testing.T) { @@ -46,21 +35,17 @@ func TestResolveShippedBundledRows(t *testing.T) { // The GPT-family row: excluded file tools, the included hidden patch tool, // the description replacement, and the task-execution addition. got, err := resolvePlugin(t, ctx, model.ModelRef{Provider: "openai", Model: "gpt-5.5-mini"}) - if err != nil { - t.Fatalf("Resolve(gpt): %v", err) - } - if want := []string{"edit_file", "write_file"}; !reflect.DeepEqual(got.ExcludeTools, want) { - t.Fatalf("exclude tools = %q, want %q", got.ExcludeTools, want) - } - if want := []string{"apply_patch"}; !reflect.DeepEqual(got.IncludeTools, want) { - t.Fatalf("include tools = %q, want %q", got.IncludeTools, want) - } - if got.ToolDescriptionReplacements[""] != "apply_patch" { - t.Fatalf("description replacement = %q, want the apply_patch override", got.ToolDescriptionReplacements[""]) + testassert.NoError(t, err) + { + want := []string{"edit_file", "write_file"} + testassert.Require(t, reflect.DeepEqual(got.ExcludeTools, want), "exclude tools = %q, want %q", got.ExcludeTools, want) } - if got.Additions["task_execution"] == "" { - t.Fatal("task_execution addition = empty, want the shipped coaching block") + { + want := []string{"apply_patch"} + testassert.Require(t, reflect.DeepEqual(got.IncludeTools, want), "include tools = %q, want %q", got.IncludeTools, want) } + testassert.Equal(t, got.ToolDescriptionReplacements[""], "apply_patch") + testassert.Require(t, got.Additions["task_execution"] != "", "task_execution addition = empty, want the shipped coaching block") // The grok and gemini rows carry additions with no tool levers. for _, ref := range []model.ModelRef{{Provider: "xai", Model: "grok-build-0.1"}, {Provider: "google", Model: "gemini-3-pro"}} { @@ -76,13 +61,8 @@ func TestResolveShippedBundledRows(t *testing.T) { func TestResolveUnmatchedModelIsBaseline(t *testing.T) { got, err := resolvePlugin(t, context.Background(), model.ModelRef{Provider: "anthropic", Model: "claude-x"}) - if err != nil { - t.Fatalf("Resolve: %v", err) - } - if got.ExcludeTools != nil || got.IncludeTools != nil || got.Blocks != nil || - got.Additions != nil || got.ToolDescriptionReplacements != nil { - t.Fatalf("unmatched model adaptation = %+v, want the zero baseline", got) - } + testassert.NoError(t, err) + testassert.Require(t, got.ExcludeTools == nil && got.IncludeTools == nil && got.Blocks == nil && got.Additions == nil && got.ToolDescriptionReplacements == nil, "unmatched model adaptation = %+v, want the zero baseline", got) } func TestResolveMatchesTheModelComponentOnly(t *testing.T) { @@ -90,46 +70,32 @@ func TestResolveMatchesTheModelComponentOnly(t *testing.T) { // A pattern inside the provider component never matches: the matcher // receives ref.Model alone. got, err := resolvePlugin(t, ctx, model.ModelRef{Provider: "gpt-5", Model: "unrelated"}) - if err != nil { - t.Fatalf("Resolve: %v", err) - } - if got.ExcludeTools != nil || got.IncludeTools != nil || got.Additions != nil { - t.Fatalf("provider-shaped ref adaptation = %+v, want the zero baseline", got) - } + testassert.NoError(t, err) + testassert.Require(t, got.ExcludeTools == nil && got.IncludeTools == nil && got.Additions == nil, "provider-shaped ref adaptation = %+v, want the zero baseline", got) // Matching is case-insensitive over the bare model component. got, err = resolvePlugin(t, ctx, model.ModelRef{Provider: "openai", Model: "GPT-5.4"}) - if err != nil { - t.Fatalf("Resolve: %v", err) - } - if got.IncludeTools == nil { - t.Fatal("case-insensitive row did not match") - } + testassert.NoError(t, err) + testassert.Require(t, got.IncludeTools != nil, "case-insensitive row did not match") } func TestResolveReturnsIndependentContainers(t *testing.T) { first, err := resolvePlugin(t, context.Background(), model.ModelRef{Provider: "openai", Model: "gpt-5.5"}) - if err != nil { - t.Fatalf("Resolve: %v", err) - } + testassert.NoError(t, err) first.ExcludeTools[0] = "tampered" first.IncludeTools[0] = "tampered" first.Additions["task_execution"] = "tampered" first.ToolDescriptionReplacements[""] = "tampered" second, err := resolvePlugin(t, context.Background(), model.ModelRef{Provider: "openai", Model: "gpt-5.5"}) - if err != nil { - t.Fatalf("Resolve: %v", err) - } - if second.ExcludeTools[0] != "edit_file" || second.IncludeTools[0] != "apply_patch" || - second.Additions["task_execution"] == "tampered" || second.ToolDescriptionReplacements[""] != "apply_patch" { - t.Fatalf("a mutated result leaked into the next resolve: %+v", second) - } + testassert.NoError(t, err) + testassert.Require(t, second.ExcludeTools[0] == "edit_file" && second.IncludeTools[0] == "apply_patch" && second.Additions["task_execution"] != "tampered" && second.ToolDescriptionReplacements[""] == "apply_patch", "a mutated result leaked into the next resolve: %+v", second) } func TestOpenRejectsCanceledScopeContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - if _, err := adaptation.Plugin().Open(ctx, runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, runtime.Bindings{}); err == nil { - t.Fatal("Open with a canceled scope context succeeded") + { + _, err := adaptation.Plugin().Open(ctx, runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, runtime.Bindings{}) + testassert.Require(t, err != nil, "Open with a canceled scope context succeeded") } } diff --git a/internal/plugins/builtin/builtin_test.go b/internal/plugins/builtin/builtin_test.go index d0cf82f3..5845618e 100644 --- a/internal/plugins/builtin/builtin_test.go +++ b/internal/plugins/builtin/builtin_test.go @@ -1,6 +1,9 @@ package builtin -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) // TestShippedRegistrationHoldsSixInOrder proves the shipped registration set // is exactly the six registered plugins in registration order: the durable @@ -10,16 +13,10 @@ import "testing" // integration suite. func TestShippedRegistrationHoldsSixInOrder(t *testing.T) { plugins := Plugins() - if len(plugins) != 6 { - t.Fatalf("Plugins() returned %d plugins, want six", len(plugins)) - } + testassert.Equal(t, len(plugins), 6) want := []string{"sqlite", "tools", "adaptation", "lsp", "jobs", "tasks"} for i, p := range plugins { - if p.ID != want[i] { - t.Errorf("plugin %d ID = %q, want %q", i, p.ID, want[i]) - } - if p.Open == nil { - t.Errorf("plugin %q has no Open factory", p.ID) - } + testassert.Check(t, p.ID == want[i], "plugin %d ID = %q, want %q", i, p.ID, want[i]) + testassert.Check(t, p.Open != nil, "plugin %q has no Open factory", p.ID) } } diff --git a/internal/plugins/lsp/lsp_mixed_test.go b/internal/plugins/lsp/lsp_mixed_test.go index 0e06914f..895af5fd 100644 --- a/internal/plugins/lsp/lsp_mixed_test.go +++ b/internal/plugins/lsp/lsp_mixed_test.go @@ -14,6 +14,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -27,32 +28,19 @@ import ( func TestMixedDetectionReturnsAvailableServerContent(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0) pyrightLog := plantServer(t, fakeHome, "pyright", "pyright-langserver", 0, "1", "", "") - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - _, symbol := pluginUnderTest(t, dataDir) - - workspace := markerWorkspace(t) - if err := os.WriteFile(filepath.Join(workspace, "pyproject.toml"), []byte("[project]\n"), 0o600); err != nil { - t.Fatal(err) - } + _, _, symbol, workspace := newPluginWorkspace(t, fakeHome, dataDir) + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "pyproject.toml"), []byte("[project]\n"), 0o600)) prepared := symbol.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":"Foo"}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("mixed detection query = %+v, want the available server's success", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, "Foo") { - t.Fatalf("mixed detection content = %q, want the available server's symbol", outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "Foo"), "mixed detection query = %+v, want the available server's success; mixed detection content = %q, want the available server's symbol", outcome.Result, outcome.Result.Content) waitForLog(t, requests, "workspace/symbol", 1) // The unavailable server went through its crash loop: the pyright binary // launched maxRestarts+1 times (the template writes "started" exactly // once per launch) and ended failed; no symbol query ever reached it. waitForLog(t, pyrightLog, "started", maxRestartLaunches) - if n := logCount(t, pyrightLog, "workspace/symbol"); n != 0 { - t.Fatalf("the unavailable server received %d symbol queries, want 0 (omitted)", n) - } + testassert.Equal(t, logCount(t, pyrightLog, "workspace/symbol"), 0) } // maxRestartLaunches is the launch count that exhausts the crash budget: the @@ -69,43 +57,29 @@ func TestRestartedPluginInstanceReincludesSessionFiles(t *testing.T) { dataDir := t.TempDir() workspace := markerWorkspace(t) seedGroup(t, dataDir, pluginSessionID, groupOne, filepath.Join(workspace, "a.go")) - if err := os.WriteFile(filepath.Join(workspace, "a.go"), []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "a.go"), []byte("package w\n"), 0o600)) uriA := fileURI(workspace, "a.go") // The first instance's query. plugin := Plugin() first, err := plugin.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dataDir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("first Open: %v", err) - } + testassert.NoError(t, err) firstDiag := first.Values["diagnostics"].(runtime.Tool) prepared := firstDiag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c1", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("first instance query = %+v, want success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "first instance query = %+v, want success", outcome.Result) waitForQuery(t, requests, uriA, 1) - if err := first.Close(); err != nil { - t.Fatalf("first Close: %v", err) - } + testassert.NoError(t, first.Close()) // The restarted instance: fresh detection over the same data root and // home, and the same Session's changed files are included again. second, err := plugin.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dataDir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("second Open: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = second.Close() }) secondDiag := second.Values["diagnostics"].(runtime.Tool) prepared = secondDiag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c2", "diagnostics", `{}`)) outcome = prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("restarted query = %+v, want success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "restarted query = %+v, want success", outcome.Result) waitForQuery(t, requests, uriA, 2) - if n := spy.count(); n != 2 { - t.Fatalf("workspace managers constructed = %d, want one per instance", n) - } + testassert.Equal(t, spy.count(), 2) } diff --git a/internal/plugins/lsp/lsp_test.go b/internal/plugins/lsp/lsp_test.go index eb6bc25b..6e63d244 100644 --- a/internal/plugins/lsp/lsp_test.go +++ b/internal/plugins/lsp/lsp_test.go @@ -24,6 +24,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/lsp" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -160,18 +161,14 @@ func fakeServerHome(t *testing.T, initDelay float64) (home, requests string) { func plantServer(t *testing.T, home, dirName, binaryName string, initDelay float64, crash, badSymbol, emptySymbol string) string { t.Helper() cacheDir := filepath.Join(home, ".cache", "lightcode", "lsp", dirName) - if err := os.MkdirAll(cacheDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(cacheDir, 0o755)) requests := filepath.Join(t.TempDir(), "requests.log") script := strings.ReplaceAll(fakeServerTemplate, "{{LOG}}", requests) script = strings.ReplaceAll(script, "{{DELAY}}", fmt.Sprintf("%g", initDelay)) script = strings.ReplaceAll(script, "{{CRASH}}", crash) script = strings.ReplaceAll(script, "{{BAD_SYMBOL}}", badSymbol) script = strings.ReplaceAll(script, "{{EMPTY_SYMBOL}}", emptySymbol) - if err := os.WriteFile(filepath.Join(cacheDir, binaryName), []byte(script), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(cacheDir, binaryName), []byte(script), 0o755)) return requests } @@ -194,12 +191,22 @@ func pluginUnderTest(t *testing.T, dataDir string) (runtime.Tool, runtime.Tool) return openPlugin(t, Plugin(), dataDir) } +// newPluginWorkspace installs the manager spy over fakeHome, opens the plugin +// over dataDir, and builds a marked workspace. Each case keeps its own data +// root, seeds, request log and query expectations. +func newPluginWorkspace(t *testing.T, fakeHome, dataDir string) (spy *managerSpy, diag, symbol runtime.Tool, workspace string) { + t.Helper() + spy = &managerSpy{} + spy.install(t, fakeHome) + diag, symbol = pluginUnderTest(t, dataDir) + workspace = markerWorkspace(t) + return spy, diag, symbol, workspace +} + func openPlugin(t *testing.T, plugin runtime.Plugin, dataDir string) (runtime.Tool, runtime.Tool) { t.Helper() inst, err := plugin.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dataDir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("plugin Open: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = inst.Close() }) return inst.Values["diagnostics"].(runtime.Tool), inst.Values["workspace_symbol"].(runtime.Tool) } @@ -208,9 +215,7 @@ func openPlugin(t *testing.T, plugin runtime.Plugin, dataDir string) (runtime.To func markerWorkspace(t *testing.T) string { t.Helper() root := t.TempDir() - if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module w\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(root, "go.mod"), []byte("module w\n"), 0o600)) return root } @@ -227,13 +232,9 @@ func seedGroup(t *testing.T, dataDir, sessionID, groupID, canonical string) { func seedEntry(t *testing.T, dataDir, sessionID, groupID, entryName, canonical string) { t.Helper() entry := filepath.Join(dataDir, "code", sessionID, groupID, "snapshots", "1", entryName) - if err := os.MkdirAll(entry, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entry, 0o755)) meta := "{\"original_path\":\"" + filepath.Base(canonical) + "\",\"canonical_path\":\"" + canonical + "\",\"existed\":true}" - if err := os.WriteFile(filepath.Join(entry, "meta.json"), []byte(meta), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(entry, "meta.json"), []byte(meta), 0o600)) } func logCount(t *testing.T, log, needle string) int { @@ -293,16 +294,15 @@ func TestPluginOpenStartsNothingAndResolvesHome(t *testing.T) { spy := &managerSpy{} spy.install(t, fakeHome) pluginUnderTest(t, t.TempDir()) - if spy.count() != 0 { - t.Fatal("plugin Open constructed a workspace manager; detection must start only in authorized execution") - } + testassert.Require(t, spy.count() == 0, "plugin Open constructed a workspace manager; detection must start only in authorized execution") } func TestPluginOpenHomeFailureFailsConstruction(t *testing.T) { t.Setenv("HOME", "") plugin := Plugin() - if _, err := plugin.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, runtime.Bindings{}); err == nil { - t.Fatal("plugin Open without a resolvable home = nil error, want ordinary failed construction") + { + _, err := plugin.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, runtime.Bindings{}) + testassert.Require(t, err != nil, "plugin Open without a resolvable home = nil error, want ordinary failed construction") } } @@ -312,20 +312,13 @@ func TestPluginCacheStaysHomeBased(t *testing.T) { // ever reached. fakeHome, _ := fakeServerHome(t, 0) t.Setenv("HOME", fakeHome) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + spy, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c1", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != "No files have been modified." { - t.Fatalf("diagnostics = %+v, want the no-files success", outcome.Result) - } - if n := spy.count(); n != 1 { - t.Fatalf("workspace managers constructed = %d, want 1", n) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "No files have been modified.", "diagnostics = %+v, want the no-files success", outcome.Result) + testassert.Equal(t, spy.count(), 1) if spy.root() != workspace { t.Fatalf("manager root = %q, want the canonical workspace %q", spy.root(), workspace) } @@ -333,14 +326,13 @@ func TestPluginCacheStaysHomeBased(t *testing.T) { // the retained cache root stays home-based, under it, independently of // DataDir. resolved, err := os.UserHomeDir() - if err != nil || resolved != fakeHome { - t.Fatalf("os.UserHomeDir under the faked HOME = (%q, %v), want %q", resolved, err, fakeHome) - } + testassert.Require(t, err == nil && resolved == fakeHome, "os.UserHomeDir under the faked HOME = (%q, %v), want %q", resolved, err, fakeHome) if spy.home() != resolved { t.Fatalf("manager home = %q, want the plugin-resolved %q", spy.home(), resolved) } - if cache := filepath.Join(spy.home(), ".cache", "lightcode", "lsp"); !strings.HasPrefix(cache, fakeHome+string(filepath.Separator)) { - t.Fatalf("cache root %q is not under the resolved home %q", cache, fakeHome) + { + cache := filepath.Join(spy.home(), ".cache", "lightcode", "lsp") + testassert.Require(t, strings.HasPrefix(cache, fakeHome+string(filepath.Separator)), "cache root %q is not under the resolved home %q", cache, fakeHome) } } @@ -351,11 +343,8 @@ func TestPluginCacheStaysHomeBased(t *testing.T) { func TestConcurrentFirstUsesJoinOneDetect(t *testing.T) { fakeHome, _ := fakeServerHome(t, 0) t.Setenv("HOME", fakeHome) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + spy, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) var wg sync.WaitGroup errs := make([]error, 8) @@ -369,13 +358,9 @@ func TestConcurrentFirstUsesJoinOneDetect(t *testing.T) { } wg.Wait() for i, err := range errs { - if err != nil { - t.Errorf("caller %d: %v", i, err) - } - } - if n := spy.count(); n != 1 { - t.Fatalf("workspace managers constructed = %d, want 1 shared detection", n) + testassert.Check(t, err == nil, "caller %d: %v", i, err) } + testassert.Equal(t, spy.count(), 1) } func outcomeError(outcome harness.ToolOutcome) error { @@ -400,13 +385,9 @@ func TestDetectionOnceIncludingEmptyMap(t *testing.T) { for i := 0; i < 3; i++ { prepared := symbol.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":"x"}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "No language servers available." { - t.Fatalf("call %d = %+v, want the zero-instance unavailability error", i+1, outcome.Result) - } - } - if n := spy.count(); n != 1 { - t.Fatalf("workspace managers constructed = %d, want 1: failed detection is not retried", n) + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "No language servers available.", "call %d = %+v, want the zero-instance unavailability error", i+1, outcome.Result) } + testassert.Equal(t, spy.count(), 1) } // TestCanceledWaiterDoesNotCancelDetection: a caller canceled during the @@ -414,11 +395,8 @@ func TestDetectionOnceIncludingEmptyMap(t *testing.T) { // a second caller succeeds against the started server. func TestCanceledWaiterDoesNotCancelDetection(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0.4) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - _, symbol := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + spy, _, symbol, workspace := newPluginWorkspace(t, fakeHome, dataDir) first, cancelFirst := context.WithCancel(context.Background()) prepared := symbol.Prepare(first, runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c1", "workspace_symbol", `{"query":"x"}`)) @@ -429,12 +407,8 @@ func TestCanceledWaiterDoesNotCancelDetection(t *testing.T) { select { case outcome := <-done: - if outcome.Result.Status != model.ResultInterrupted { - t.Fatalf("canceled waiter = %+v, want interrupted", outcome.Result) - } - if outcome.Result.Content == "" { - t.Fatal("interrupted result carries empty content") - } + testassert.Require(t, outcome.Result.Status == model.ResultInterrupted, "canceled waiter = %+v, want interrupted", outcome.Result) + testassert.Require(t, outcome.Result.Content != "", "interrupted result carries empty content") case <-time.After(10 * time.Second): t.Fatal("canceled waiter never settled") } @@ -443,12 +417,8 @@ func TestCanceledWaiterDoesNotCancelDetection(t *testing.T) { // started server. second := symbol.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c2", "workspace_symbol", `{"query":"x"}`)) outcome := second.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("second caller = %+v, want success against the started server", outcome.Result) - } - if n := spy.count(); n != 1 { - t.Fatalf("workspace managers constructed = %d, want 1", n) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "second caller = %+v, want success against the started server", outcome.Result) + testassert.Equal(t, spy.count(), 1) waitForLog(t, requests, "initialize", 1) } @@ -464,15 +434,11 @@ func TestDetectionUnderCanceledRuntimeYieldsNoUsableServers(t *testing.T) { dataDir := t.TempDir() workspace := markerWorkspace(t) seedGroup(t, dataDir, pluginSessionID, groupOne, filepath.Join(workspace, "a.go")) - if err := os.WriteFile(filepath.Join(workspace, "a.go"), []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "a.go"), []byte("package w\n"), 0o600)) lifetime, cancel := context.WithCancel(context.Background()) inst, err := Plugin().Open(lifetime, runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dataDir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("plugin Open: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = inst.Close() }) diag := inst.Values["diagnostics"].(runtime.Tool) // The lifetime is canceled after Open but before the first authorized @@ -484,26 +450,18 @@ func TestDetectionUnderCanceledRuntimeYieldsNoUsableServers(t *testing.T) { prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) refused := filepath.Join(workspace, "a.go") + ": no language server for this file (could not check)" - if outcome.Result.Status != model.ResultError || outcome.Result.Content != refused { - t.Fatalf("call %d under the canceled lifetime = %+v, want the per-path could-not-check error", i+1, outcome.Result) - } - } - if n := logCount(t, requests, "textDocument/didOpen"); n != 0 { - t.Fatalf("document sync observed under the canceled lifetime: %d occurrences", n) + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == refused, "call %d under the canceled lifetime = %+v, want the per-path could-not-check error", i+1, outcome.Result) } + testassert.Equal(t, logCount(t, requests, "textDocument/didOpen"), 0) // The live-lifetime sibling: the same fixtures start the server and the // query reaches it. diagLive, _ := openPlugin(t, Plugin(), dataDir) prepared := diagLive.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("live-lifetime diagnostics = %+v, want success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "live-lifetime diagnostics = %+v, want success", outcome.Result) waitForQuery(t, requests, fileURI(workspace, "a.go"), 1) - if n := spy.count(); n != 2 { - t.Fatalf("workspace managers constructed = %d, want one per instance", n) - } + testassert.Equal(t, spy.count(), 2) } // TestCloseJoinsInFlightDetection: Close waits for the shared detection to @@ -518,9 +476,7 @@ func TestCloseJoinsInFlightDetection(t *testing.T) { plugin := Plugin() inst, err := plugin.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dataDir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("plugin Open: %v", err) - } + testassert.NoError(t, err) symbol := inst.Values["workspace_symbol"].(runtime.Tool) // Start detection as a first authorized use and wait until its server @@ -534,15 +490,12 @@ func TestCloseJoinsInFlightDetection(t *testing.T) { // Close while detection is in flight: it must join before returning. start := time.Now() - if err := inst.Close(); err != nil { - t.Fatalf("Close: %v", err) - } - if elapsed := time.Since(start); elapsed < 300*time.Millisecond { - t.Fatalf("Close returned after %s; it must join the in-flight detection (init delay 0.4s)", elapsed) - } - if n := spy.count(); n != 1 { - t.Fatalf("workspace managers constructed = %d, want 1", n) + testassert.NoError(t, inst.Close()) + { + elapsed := time.Since(start) + testassert.Require(t, elapsed >= 300*time.Millisecond, "Close returned after %s; it must join the in-flight detection (init delay 0.4s)", elapsed) } + testassert.Equal(t, spy.count(), 1) waitForLog(t, requests, "initialize", 1) } @@ -566,29 +519,17 @@ func TestDeniedAndPreparedCallsStartNothing(t *testing.T) { // preparation is denied without any manager construction. loopA := filepath.Join(t.TempDir(), "loop-a") loopB := filepath.Join(t.TempDir(), "loop-b") - if err := os.Symlink(loopB, loopA); err != nil { - t.Fatal(err) - } - if err := os.Symlink(loopA, loopB); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(loopB, loopA)) + testassert.NoError(t, os.Symlink(loopA, loopB)) for _, tool := range []runtime.Tool{diag, symbol} { prepared := tool.Prepare(context.Background(), runtime.ToolContext{Workspace: loopA, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":"x"}`)) - if prepared.Immediate == nil { - t.Fatalf("symlink-loop workspace preparation = %+v, want an immediate denied outcome", prepared) - } - if prepared.Immediate.Result.Status != model.ResultDenied || prepared.Immediate.Result.Content != "Permission denied." { - t.Fatalf("symlink-loop workspace preparation = %+v, want the fixed denial", prepared.Immediate.Result) - } - if prepared.Execute != nil { - t.Fatal("the denied preparation carries an executor") - } + testassert.Require(t, prepared.Immediate != nil, "symlink-loop workspace preparation = %+v, want an immediate denied outcome", prepared) + testassert.Require(t, prepared.Immediate.Result.Status == model.ResultDenied && prepared.Immediate.Result.Content == "Permission denied.", "symlink-loop workspace preparation = %+v, want the fixed denial", prepared.Immediate.Result) + testassert.Require(t, prepared.Execute == nil, "the denied preparation carries an executor") } - if n := spy.count(); n != 0 { - t.Fatalf("workspace managers constructed = %d, want 0: denied calls start nothing", n) - } + testassert.Equal(t, spy.count(), 0) } // TestDiagnosticsNoCursorAcrossCalls: two seeded groups from two "operations" @@ -596,29 +537,19 @@ func TestDeniedAndPreparedCallsStartNothing(t *testing.T) { // checked-group set. func TestDiagnosticsNoCursorAcrossCalls(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) seedGroup(t, dataDir, pluginSessionID, groupOne, filepath.Join(workspace, "a.go")) seedGroup(t, dataDir, pluginSessionID, groupTwo, filepath.Join(workspace, "b.go")) for _, name := range []string{"a.go", "b.go"} { - if err := os.WriteFile(filepath.Join(workspace, name), []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, name), []byte("package w\n"), 0o600)) } uriA, uriB := fileURI(workspace, "a.go"), fileURI(workspace, "b.go") for call := 1; call <= 2; call++ { prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("call %d = %+v, want success", call, outcome.Result) - } - if outcome.Result.Content != "No errors found." { - t.Fatalf("call %d content = %q, want the clean no-error text", call, outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "No errors found.", "call %d = %+v, want success; call %d content = %q, want the clean no-error text", call, outcome.Result, call, outcome.Result.Content) waitForQuery(t, requests, uriA, call) waitForQuery(t, requests, uriB, call) } @@ -628,57 +559,40 @@ func TestDiagnosticsNoCursorAcrossCalls(t *testing.T) { // read. func TestDiagnosticsExcludesOtherSessions(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) seedGroup(t, dataDir, pluginSessionID, groupOne, filepath.Join(workspace, "a.go")) seedGroup(t, dataDir, otherSessionID, groupTwo, filepath.Join(workspace, "b.go")) for _, name := range []string{"a.go", "b.go"} { - if err := os.WriteFile(filepath.Join(workspace, name), []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, name), []byte("package w\n"), 0o600)) } prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("diagnostics = %+v, want success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "diagnostics = %+v, want success", outcome.Result) waitForQuery(t, requests, fileURI(workspace, "a.go"), 1) - if n := logCount(t, requests, fileURI(workspace, "b.go")); n != 0 { - t.Fatalf("the other session's file was synced: %d occurrences", n) - } + testassert.Equal(t, logCount(t, requests, fileURI(workspace, "b.go")), 0) } // TestDiagnosticsDropsOutsideRootPaths: an outside-root canonical path is // dropped un-read. func TestDiagnosticsDropsOutsideRootPaths(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) seedGroup(t, dataDir, pluginSessionID, groupOne, filepath.Join(workspace, "a.go")) outside := filepath.Join(t.TempDir(), "outside.go") seedGroup(t, dataDir, pluginSessionID, groupTwo, outside) - if err := os.WriteFile(filepath.Join(workspace, "a.go"), []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(outside, []byte("package outside\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "a.go"), []byte("package w\n"), 0o600)) + testassert.NoError(t, os.WriteFile(outside, []byte("package outside\n"), 0o600)) prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("diagnostics = %+v, want success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "diagnostics = %+v, want success", outcome.Result) waitForQuery(t, requests, fileURI(workspace, "a.go"), 1) - if n := logCount(t, requests, "outside.go"); n != 0 { - t.Fatal("the outside-root path was read despite being dropped") + { + n := logCount(t, requests, "outside.go") + testassert.Require(t, n == 0, "the outside-root path was read despite being dropped") } } @@ -686,28 +600,17 @@ func TestDiagnosticsDropsOutsideRootPaths(t *testing.T) { // to the real root and the tool succeeds against it. func TestSymlinkedWorkspaceBindsToRealRoot(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - realRoot := markerWorkspace(t) - if err := os.WriteFile(filepath.Join(realRoot, "a.go"), []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } + spy, diag, _, realRoot := newPluginWorkspace(t, fakeHome, dataDir) + testassert.NoError(t, os.WriteFile(filepath.Join(realRoot, "a.go"), []byte("package w\n"), 0o600)) seedGroup(t, dataDir, pluginSessionID, groupOne, filepath.Join(realRoot, "a.go")) link := filepath.Join(t.TempDir(), "link") - if err := os.Symlink(realRoot, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realRoot, link)) prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: link, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) - if prepared.CanonicalWorkspace != realRoot { - t.Fatalf("CanonicalWorkspace = %q, want the real root %q", prepared.CanonicalWorkspace, realRoot) - } + testassert.Equal(t, prepared.CanonicalWorkspace, realRoot) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("diagnostics = %+v, want success through the symlinked lexical workspace", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "diagnostics = %+v, want success through the symlinked lexical workspace", outcome.Result) waitForQuery(t, requests, fileURI(realRoot, "a.go"), 1) if spy.root() != realRoot { t.Fatalf("manager root = %q, want the canonical real root", spy.root()) @@ -720,40 +623,22 @@ func TestSymlinkedWorkspaceBindsToRealRoot(t *testing.T) { func TestPostAuthorizationBindingChangeFailsWithoutStart(t *testing.T) { fakeHome, _ := fakeServerHome(t, 0) t.Setenv("HOME", fakeHome) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - realRoot := markerWorkspace(t) + spy, diag, _, realRoot := newPluginWorkspace(t, fakeHome, dataDir) link := filepath.Join(t.TempDir(), "link") - if err := os.Symlink(realRoot, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realRoot, link)) prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: link, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) - if prepared.CanonicalWorkspace != realRoot { - t.Fatalf("CanonicalWorkspace = %q, want %q", prepared.CanonicalWorkspace, realRoot) - } + testassert.Equal(t, prepared.CanonicalWorkspace, realRoot) // Repoint the lexical workspace at a different root after authorization. otherRoot := t.TempDir() - if err := os.Remove(link); err != nil { - t.Fatal(err) - } - if err := os.Symlink(otherRoot, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(link)) + testassert.NoError(t, os.Symlink(otherRoot, link)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultError { - t.Fatalf("changed binding = %+v, want an error outcome", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, "binding changed") { - t.Fatalf("changed binding content = %q", outcome.Result.Content) - } - if n := spy.count(); n != 0 { - t.Fatalf("workspace managers constructed = %d, want 0: a changed binding starts nothing", n) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, "binding changed"), "changed binding = %+v, want an error outcome; changed binding content = %q", outcome.Result, outcome.Result.Content) + testassert.Equal(t, spy.count(), 0) } // TestCallerCancellationDuringQueryIsInterrupted: cancellation during the @@ -761,34 +646,21 @@ func TestPostAuthorizationBindingChangeFailsWithoutStart(t *testing.T) { // content. func TestCallerCancellationDuringQueryIsInterrupted(t *testing.T) { fakeHome, requests := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) // One healthy file (its sync proves the opens completed) and one // replaced-symlink leaf (its canonical read fails, so partial // could-not-check content accumulates before the sync wait). healthy := filepath.Join(workspace, "a.go") - if err := os.WriteFile(healthy, []byte("package w\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(healthy, []byte("package w\n"), 0o600)) seedGroup(t, dataDir, pluginSessionID, groupOne, healthy) target := filepath.Join(t.TempDir(), "target.go") - if err := os.WriteFile(target, []byte("package target\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("package target\n"), 0o600)) swapped := filepath.Join(workspace, "swapped.go") - if err := os.WriteFile(swapped, []byte("placeholder\n"), 0o600); err != nil { - t.Fatal(err) - } - if err := os.Remove(swapped); err != nil { - t.Fatal(err) - } - if err := os.Symlink(target, swapped); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(swapped, []byte("placeholder\n"), 0o600)) + testassert.NoError(t, os.Remove(swapped)) + testassert.NoError(t, os.Symlink(target, swapped)) seedGroup(t, dataDir, pluginSessionID, groupTwo, swapped) ctx, cancel := context.WithCancel(context.Background()) @@ -802,15 +674,9 @@ func TestCallerCancellationDuringQueryIsInterrupted(t *testing.T) { select { case outcome := <-done: - if outcome.Result.Status != model.ResultInterrupted { - t.Fatalf("canceled query = %+v, want interrupted", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, "(could not check)") { - t.Fatalf("interrupted content = %q, want the bounded partial could-not-check line", outcome.Result.Content) - } - if len(outcome.Result.Content) > 15360 { - t.Fatalf("interrupted content = %d bytes, want the bounded partial", len(outcome.Result.Content)) - } + testassert.Require(t, outcome.Result.Status == model.ResultInterrupted, "canceled query = %+v, want interrupted", outcome.Result) + testassert.Require(t, strings.Contains(outcome.Result.Content, "(could not check)"), "interrupted content = %q, want the bounded partial could-not-check line", outcome.Result.Content) + testassert.Require(t, len(outcome.Result.Content) <= 15360, "interrupted content = %d bytes, want the bounded partial", len(outcome.Result.Content)) case <-time.After(10 * time.Second): t.Fatal("canceled query never settled") } @@ -821,45 +687,28 @@ func TestCallerCancellationDuringQueryIsInterrupted(t *testing.T) { // a successful text line that reads as a clean query. func TestDiagnosticsListingFailureIsError(t *testing.T) { fakeHome, _ := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() // The valid Session identity's session path is a regular file: the // enumeration fails for real. - if err := os.MkdirAll(filepath.Join(dataDir, "code"), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dataDir, "code", pluginSessionID), []byte("x"), 0o600); err != nil { - t.Fatal(err) - } - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + testassert.NoError(t, os.MkdirAll(filepath.Join(dataDir, "code"), 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(dataDir, "code", pluginSessionID), []byte("x"), 0o600)) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultError { - t.Fatalf("listing failure = %+v, want an error outcome carrying the enumeration failure", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, "not a directory") { - t.Fatalf("listing failure content = %q, want the actual enumeration error", outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, "not a directory"), "listing failure = %+v, want an error outcome carrying the enumeration failure; listing failure content = %q, want the actual enumeration error", outcome.Result, outcome.Result.Content) } // TestDiagnosticsNoFiles: an empty data root settles as the retained no-files // content. func TestDiagnosticsNoFiles(t *testing.T) { fakeHome, _ := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != "No files have been modified." { - t.Fatalf("no files = %+v, want the retained no-files content", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "No files have been modified.", "no files = %+v, want the retained no-files content", outcome.Result) } // TestWorkspaceSymbolEmptyQueryRejected: a present empty query is an @@ -867,27 +716,19 @@ func TestDiagnosticsNoFiles(t *testing.T) { // input is the immediate validation error before any service start. func TestWorkspaceSymbolEmptyQueryRejected(t *testing.T) { symbol := workspaceSymbolTool{inst: &instance{}} - if _, err := symbol.Normalize(runtime.ToolContext{}, toolCall("c", "workspace_symbol", `{"query":""}`)); err == nil { - t.Fatal("workspace_symbol Normalize(empty query) = nil error, want an argument-validation error") + { + _, err := symbol.Normalize(runtime.ToolContext{}, toolCall("c", "workspace_symbol", `{"query":""}`)) + testassert.Require(t, err != nil, "workspace_symbol Normalize(empty query) = nil error, want an argument-validation error") } fakeHome, _ := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - _, symbolTool := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + spy, _, symbolTool, workspace := newPluginWorkspace(t, fakeHome, dataDir) prepared := symbolTool.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":""}`)) - if prepared.Immediate == nil || prepared.Immediate.Result.Status != model.ResultError { - t.Fatalf("empty-query preparation = %+v, want the immediate validation error", prepared) - } - if prepared.Execute != nil { - t.Fatal("the invalid-query preparation carries an executor") - } - if n := spy.count(); n != 0 { - t.Fatalf("workspace managers constructed = %d, want 0: an invalid query starts nothing", n) - } + testassert.Require(t, prepared.Immediate != nil && prepared.Immediate.Result.Status == model.ResultError, "empty-query preparation = %+v, want the immediate validation error", prepared) + testassert.Require(t, prepared.Execute == nil, "the invalid-query preparation carries an executor") + testassert.Equal(t, spy.count(), 0) } // TestWorkspaceSymbolAllFailedIsError: a server whose workspace/symbol reply @@ -897,17 +738,12 @@ func TestWorkspaceSymbolEmptyQueryRejected(t *testing.T) { func TestWorkspaceSymbolAllFailedIsError(t *testing.T) { home := t.TempDir() plantServer(t, home, "gopls", "gopls", 0, "", "1", "") - spy := &managerSpy{} - spy.install(t, home) dataDir := t.TempDir() - _, symbol := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, _, symbol, workspace := newPluginWorkspace(t, home, dataDir) prepared := symbol.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":"Foo"}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "No language servers available for the symbol query." { - t.Fatalf("undecodable symbol reply = %+v, want the fixed unavailability error", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "No language servers available for the symbol query.", "undecodable symbol reply = %+v, want the fixed unavailability error", outcome.Result) } // TestWorkspaceSymbolEmptyReplyIsSuccessful: a valid empty symbol list is a @@ -916,20 +752,12 @@ func TestWorkspaceSymbolAllFailedIsError(t *testing.T) { func TestWorkspaceSymbolEmptyReplyIsSuccessful(t *testing.T) { home := t.TempDir() plantServer(t, home, "gopls", "gopls", 0, "", "", "1") - spy := &managerSpy{} - spy.install(t, home) dataDir := t.TempDir() - _, symbol := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, _, symbol, workspace := newPluginWorkspace(t, home, dataDir) prepared := symbol.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":"Foo"}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("empty symbol reply = %+v, want the success outcome for a responding server", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, "No symbols were reported by the responding language servers.") { - t.Fatalf("empty symbol reply content = %q, want the responding-servers text", outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "No symbols were reported by the responding language servers."), "empty symbol reply = %+v, want the success outcome for a responding server; empty symbol reply content = %q, want the responding-servers text", outcome.Result, outcome.Result.Content) } // TestNormalizeContracts: the strict decode, the private-field stripping, @@ -940,28 +768,22 @@ func TestNormalizeContracts(t *testing.T) { tc := runtime.ToolContext{} got, err := diag.Normalize(tc, toolCall("c", "diagnostics", `{"_lightcode_receipt":"forged","unrelated":1}`)) - if err != nil { - t.Fatalf("diagnostics Normalize: %v", err) - } - if string(got) != `{"unrelated":1}` { - t.Fatalf("diagnostics normalized = %s, want the stripped retained members", got) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == `{"unrelated":1}`, "diagnostics normalized = %s, want the stripped retained members", got) for _, bad := range []string{`null`, `[1]`, `{`, `{} trailing`} { - if _, err := diag.Normalize(tc, toolCall("c", "diagnostics", bad)); err == nil { - t.Errorf("diagnostics Normalize(%q) = nil error, want an argument-validation error", bad) + { + _, err := diag.Normalize(tc, toolCall("c", "diagnostics", bad)) + testassert.Check(t, err != nil, "diagnostics Normalize(%q) = nil error, want an argument-validation error", bad) } } got, err = symbol.Normalize(tc, toolCall("c", "workspace_symbol", `{"query":"x","_lightcode_receipt":"forged","other":"kept"}`)) - if err != nil { - t.Fatalf("workspace_symbol Normalize: %v", err) - } - if !strings.Contains(string(got), `"query":"x"`) || !strings.Contains(string(got), `"other":"kept"`) || strings.Contains(string(got), "_lightcode_") { - t.Fatalf("workspace_symbol normalized = %s", got) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(got), `"query":"x"`) && strings.Contains(string(got), `"other":"kept"`) && !strings.Contains(string(got), "_lightcode_"), "workspace_symbol normalized = %s", got) for _, bad := range []string{`{}`, `{"query":null}`, `{"query":5}`, `{"query":["x"]}`, `{"query":""}`} { - if _, err := symbol.Normalize(tc, toolCall("c", "workspace_symbol", bad)); err == nil { - t.Errorf("workspace_symbol Normalize(%q) = nil error, want an argument-validation error", bad) + { + _, err := symbol.Normalize(tc, toolCall("c", "workspace_symbol", bad)) + testassert.Check(t, err != nil, "workspace_symbol Normalize(%q) = nil error, want an argument-validation error", bad) } } } @@ -970,32 +792,20 @@ func TestNormalizeContracts(t *testing.T) { // verbatim workspace_symbol schema, and the one workspace.inspect pair. func TestDescriptionsAndPreparationShape(t *testing.T) { diagDesc, err := diagnosticsTool{}.describe(runtime.Invocation{}, runtime.ToolConstraints{}, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("diagnostics describe: %v", err) - } - if diagDesc.Definition.Description != "Check for compilation errors in the files this Session has changed. Call after editing to verify correctness. Returns errors only (not warnings)." { - t.Fatalf("diagnostics description = %q", diagDesc.Definition.Description) - } - if diagDesc.Definition.Name != "diagnostics" || !diagDesc.Available { - t.Fatalf("diagnostics definition = %+v", diagDesc.Definition) - } + testassert.NoError(t, err) + testassert.Require(t, diagDesc.Definition.Description == "Check for compilation errors in the files this Session has changed. Call after editing to verify correctness. Returns errors only (not warnings)." && diagDesc.Definition.Name == "diagnostics" && diagDesc.Available, "got %v, want %v; diagnostics definition = %+v", diagDesc.Definition.Description, "Check for compilation errors in the files this Session has changed. Call after editing to verify correctness. Returns errors only (not warnings).", diagDesc.Definition) symbolDesc, err := workspaceSymbolTool{}.describe(runtime.Invocation{}, runtime.ToolConstraints{}, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("workspace_symbol describe: %v", err) - } - if symbolDesc.Definition.Description != "Search for symbols by name across the project. Returns matching functions, types, variables, etc. with their file locations." { - t.Fatalf("workspace_symbol description = %q", symbolDesc.Definition.Description) - } + testassert.NoError(t, err) + testassert.Equal(t, symbolDesc.Definition.Description, "Search for symbols by name across the project. Returns matching functions, types, variables, etc. with their file locations.") var schema struct { Properties map[string]any `json:"properties"` Required []string `json:"required"` } - if err := json.Unmarshal(symbolDesc.Definition.Parameters, &schema); err != nil { - t.Fatalf("workspace_symbol schema: %v", err) - } - if _, ok := schema.Properties["query"]; !ok || len(schema.Required) != 1 || schema.Required[0] != "query" { - t.Fatalf("workspace_symbol schema = %s", symbolDesc.Definition.Parameters) + testassert.NoError(t, json.Unmarshal(symbolDesc.Definition.Parameters, &schema)) + { + _, ok := schema.Properties["query"] + testassert.Require(t, ok && len(schema.Required) == 1 && schema.Required[0] == "query", "workspace_symbol schema = %s", symbolDesc.Definition.Parameters) } dataDir := t.TempDir() @@ -1008,33 +818,24 @@ func TestDescriptionsAndPreparationShape(t *testing.T) { args = `{"query":"x"}` } prepared := tool.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", args)) - if len(prepared.Permissions) != 1 { - t.Fatalf("permissions = %+v, want exactly one pair", prepared.Permissions) - } + testassert.Require(t, len(prepared.Permissions) == 1, "permissions = %+v, want exactly one pair", prepared.Permissions) pair := prepared.Permissions[0] - if pair.Permission != "workspace.inspect" || pair.Target != workspace { - t.Fatalf("permission pair = %+v, want workspace.inspect on the canonical root", pair) - } - if prepared.CanonicalWorkspace != workspace || prepared.CanonicalWriteDir != "" { - t.Fatalf("canonical binding = %q/%q, want the root and no write dir", prepared.CanonicalWorkspace, prepared.CanonicalWriteDir) - } + testassert.Require(t, pair.Permission == "workspace.inspect" && pair.Target == workspace, "permission pair = %+v, want workspace.inspect on the canonical root", pair) + testassert.Require(t, prepared.CanonicalWorkspace == workspace && prepared.CanonicalWriteDir == "", "canonical binding = %q/%q, want the root and no write dir", prepared.CanonicalWorkspace, prepared.CanonicalWriteDir) } } // TestContentCap: the at-limit and over-limit rows, the multi-byte character // straddling the boundary, and the cap on error content. func TestContentCap(t *testing.T) { - if got := capModelContent(strings.Repeat("a", 15360)); got != strings.Repeat("a", 15360) { - t.Fatal("at-limit content must be unchanged") + { + got := capModelContent(strings.Repeat("a", 15360)) + testassert.Require(t, got == strings.Repeat("a", 15360), "at-limit content must be unchanged") } over := strings.Repeat("a", 15361) got := capModelContent(over) - if len(got) != 15360 || !strings.HasSuffix(got, "\n[Output truncated]") { - t.Fatalf("over-limit cap = %d bytes, want 15360 with the marker", len(got)) - } - if !strings.HasPrefix(got, over[:15360-len("\n[Output truncated]")]) { - t.Fatal("cap did not keep the longest fitting prefix") - } + testassert.Require(t, len(got) == 15360 && strings.HasSuffix(got, "\n[Output truncated]"), "over-limit cap = %d bytes, want 15360 with the marker", len(got)) + testassert.Require(t, strings.HasPrefix(got, over[:15360-len("\n[Output truncated]")]), "cap did not keep the longest fitting prefix") // A multi-byte character straddling the boundary: the cut backs off to // the rune boundary and the result stays valid UTF-8. The first € starts @@ -1043,27 +844,17 @@ func TestContentCap(t *testing.T) { prefix := strings.Repeat("a", 15360-len(truncationMarker)-2) straddled := prefix + "€€€€€€€€€€" // each € is three bytes got = capModelContent(straddled) - if len(got) > 15360 || !strings.HasSuffix(got, "\n[Output truncated]") { - t.Fatalf("straddled cap = %d bytes, want the marker within 15360", len(got)) - } - if !utf8.ValidString(got) { - t.Fatal("cap produced invalid UTF-8") - } - if !strings.HasPrefix(got, prefix) { - t.Fatal("cap lost the complete prefix") - } + testassert.Require(t, len(got) <= 15360 && strings.HasSuffix(got, "\n[Output truncated]"), "straddled cap = %d bytes, want the marker within 15360", len(got)) + testassert.Require(t, utf8.ValidString(got), "cap produced invalid UTF-8") + testassert.Require(t, strings.HasPrefix(got, prefix), "cap lost the complete prefix") // Error content is capped by the same rule. outcome := toolOutcome("c", context.Background(), "", errors.New(strings.Repeat("e", 20000))) - if outcome.Result.Status != model.ResultError || len(outcome.Result.Content) != 15360 { - t.Fatalf("error cap = %+v (%d bytes), want the capped error content", outcome.Result, len(outcome.Result.Content)) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && len(outcome.Result.Content) == 15360, "error cap = %+v (%d bytes), want the capped error content", outcome.Result, len(outcome.Result.Content)) // Success content is capped by the same rule. outcome = toolOutcome("c", context.Background(), strings.Repeat("s", 20000), nil) - if outcome.Result.Status != model.ResultSuccess || len(outcome.Result.Content) != 15360 { - t.Fatalf("success cap = %+v (%d bytes), want the capped content", outcome.Result, len(outcome.Result.Content)) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && len(outcome.Result.Content) == 15360, "success cap = %+v (%d bytes), want the capped content", outcome.Result, len(outcome.Result.Content)) } // TestDiagnosticsBulkReadFailuresStayBounded proves the bulk-failure row: a @@ -1073,36 +864,25 @@ func TestContentCap(t *testing.T) { // by the truncation marker — bounded content through the all-failed error path. func TestDiagnosticsBulkReadFailuresStayBounded(t *testing.T) { fakeHome, _ := fakeServerHome(t, 0) - spy := &managerSpy{} - spy.install(t, fakeHome) dataDir := t.TempDir() - diag, _ := pluginUnderTest(t, dataDir) - workspace := markerWorkspace(t) + _, diag, _, workspace := newPluginWorkspace(t, fakeHome, dataDir) const count = 1000 for i := 0; i < count; i++ { leaf := filepath.Join(workspace, fmt.Sprintf("leaf%04d.go", i)) - if err := os.Symlink(filepath.Join(workspace, "missing-target.go"), leaf); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(filepath.Join(workspace, "missing-target.go"), leaf)) seedEntry(t, dataDir, pluginSessionID, groupOne, fmt.Sprintf("e%04d", i), leaf) } prepared := diag.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "diagnostics", `{}`)) outcome := prepared.Execute(context.Background()) - if outcome.Result.Status != model.ResultError { - t.Fatalf("bulk-failure diagnostics = %+v, want the all-failed error outcome", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError, "bulk-failure diagnostics = %+v, want the all-failed error outcome", outcome.Result) content := outcome.Result.Content - if len(content) > 15360 { - t.Fatalf("bulk-failure content = %d bytes, want the 15360 cap", len(content)) - } + testassert.Require(t, len(content) <= 15360, "bulk-failure content = %d bytes, want the 15360 cap", len(content)) if !strings.HasSuffix(content, "\n[Output truncated]") { t.Fatalf("bulk-failure content tail = %q, want the truncation marker", content[len(content)-60:]) } - if !strings.Contains(content, "(could not check)") { - t.Fatalf("bulk-failure content = %.120s, want could-not-check lines", content) - } + testassert.Require(t, strings.Contains(content, "(could not check)"), "bulk-failure content = %.120s, want could-not-check lines", content) } // TestPluginInstallsReportWarningBeforeDetect proves the plugin captures the @@ -1117,9 +897,7 @@ func TestPluginInstallsReportWarningBeforeDetect(t *testing.T) { t.Setenv("HOME", t.TempDir()) // cold LSP cache: nothing planted t.Setenv("PATH", t.TempDir()) // no resolvable server anywhere workspace := markerWorkspace(t) - if err := os.WriteFile(filepath.Join(workspace, "CMakeLists.txt"), []byte("cmake_minimum_required(VERSION 3.10)\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "CMakeLists.txt"), []byte("cmake_minimum_required(VERSION 3.10)\n"), 0o600)) var mu sync.Mutex var reported []string @@ -1131,9 +909,7 @@ func TestPluginInstallsReportWarningBeforeDetect(t *testing.T) { // The real plugin Open captures the Runtime's closure from its scope // identity — the only path the report below can arrive through. instance, err := Plugin().Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir(), ReportWarning: report}, runtime.Bindings{}) - if err != nil { - t.Fatalf("Open with the reporter: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = instance.Close() }) symbol := instance.Values["workspace_symbol"].(runtime.Tool) @@ -1143,8 +919,9 @@ func TestPluginInstallsReportWarningBeforeDetect(t *testing.T) { // the installed callback — the zero-instance error proves detection // completed, so the reports are not racing a later goroutine. prepared := symbol.Prepare(context.Background(), runtime.ToolContext{Workspace: workspace, AdmittedEntry: harness.EntryRef{SessionID: pluginSessionID}}, toolCall("c", "workspace_symbol", `{"query":"Foo"}`)) - if outcome := prepared.Execute(context.Background()); outcome.Result.Status != model.ResultError || outcome.Result.Content != "No language servers available." { - t.Fatalf("missing-server query = %+v, want the cold no-server error after detection", outcome.Result) + { + outcome := prepared.Execute(context.Background()) + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "No language servers available.", "missing-server query = %+v, want the cold no-server error after detection", outcome.Result) } deadline := time.Now().Add(10 * time.Second) for { @@ -1154,9 +931,7 @@ func TestPluginInstallsReportWarningBeforeDetect(t *testing.T) { if count > 0 { break } - if time.Now().After(deadline) { - t.Fatal("the manager's detection warning never reached the Runtime closure") - } + testassert.Require(t, !time.Now().After(deadline), "the manager's detection warning never reached the Runtime closure") time.Sleep(5 * time.Millisecond) } mu.Lock() diff --git a/internal/plugins/tasks/tasks_test.go b/internal/plugins/tasks/tasks_test.go index 8efa46b5..c9724694 100644 --- a/internal/plugins/tasks/tasks_test.go +++ b/internal/plugins/tasks/tasks_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -19,9 +20,7 @@ import ( // invalid-settings wrap. func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { validate := Plugin().ValidateConfig - if validate == nil { - t.Fatal("Plugin().ValidateConfig is nil, want the settings validator") - } + testassert.Require(t, validate != nil, "Plugin().ValidateConfig is nil, want the settings validator") keepDefaults := []json.RawMessage{ nil, json.RawMessage(""), @@ -32,16 +31,13 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { json.RawMessage(`{"unknown_member":1,"max_output_bytes":null}`), } for _, raw := range keepDefaults { - if err := validate(raw); err != nil { - t.Errorf("ValidateConfig(%s) = %v, want nil", raw, err) + { + err := validate(raw) + testassert.Check(t, err == nil, "ValidateConfig(%s) = %v, want nil", raw, err) } s, err := decodeSettings(raw) - if err != nil { - t.Fatalf("decodeSettings(%s): %v", raw, err) - } - if s != (settings{MaxConcurrent: 4, MaxOutputBytes: 15360}) { - t.Errorf("decodeSettings(%s) = %+v, want the defaults 4 and 15360", raw, s) - } + testassert.Require(t, err == nil, "decodeSettings(%s): %v", raw, err) + testassert.Check(t, s == (settings{MaxConcurrent: 4, MaxOutputBytes: 15360}), "decodeSettings(%s) = %+v, want the defaults 4 and 15360", raw, s) } inRange := []json.RawMessage{ json.RawMessage(`{"max_concurrent":1,"max_output_bytes":1024}`), @@ -50,8 +46,9 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { json.RawMessage(`{"max_output_bytes":2048}`), } for _, raw := range inRange { - if err := validate(raw); err != nil { - t.Errorf("ValidateConfig(%s) = %v, want nil", raw, err) + { + err := validate(raw) + testassert.Check(t, err == nil, "ValidateConfig(%s) = %v, want nil", raw, err) } } rejected := []struct { @@ -65,9 +62,7 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { } for _, tc := range rejected { err := validate(tc.raw) - if err == nil || err.Error() != tc.want { - t.Errorf("ValidateConfig(%s) = %v, want exactly %q", tc.raw, err, tc.want) - } + testassert.Check(t, err != nil && err.Error() == tc.want, "ValidateConfig(%s) = %v, want exactly %q", tc.raw, err, tc.want) } wrongType := []json.RawMessage{ json.RawMessage(`{"max_output_bytes":"many"}`), @@ -77,9 +72,7 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { } for _, raw := range wrongType { err := validate(raw) - if err == nil || !strings.HasPrefix(err.Error(), "tasks: invalid settings: ") { - t.Errorf("ValidateConfig(%s) = %v, want the tasks invalid-settings wrap", raw, err) - } + testassert.Check(t, err != nil && strings.HasPrefix(err.Error(), "tasks: invalid settings: "), "ValidateConfig(%s) = %v, want the tasks invalid-settings wrap", raw, err) } } @@ -87,9 +80,7 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { func taskCall(t *testing.T, args string) model.ToolCall { t.Helper() call, err := model.NewToolCall(model.ToolCall{ID: "call-1", Name: "task", Arguments: json.RawMessage(args)}) - if err != nil { - t.Fatalf("NewToolCall: %v", err) - } + testassert.NoError(t, err) return call } @@ -99,21 +90,17 @@ func taskCall(t *testing.T, args string) model.ToolCall { // rejected — Prepare owns the semantics. func TestNormalizeTaskArguments(t *testing.T) { got, err := taskTool{}.Normalize(runtime.ToolContext{}, taskCall(t, `{"prompt":"do it","subagent_type":"secondary","extra":42,"_lightcode_probe":"secret"}`)) - if err != nil { - t.Fatalf("Normalize: %v", err) - } + testassert.NoError(t, err) var args map[string]any - if err := json.Unmarshal(got, &args); err != nil { - t.Fatalf("normalized arguments: %v", err) + testassert.NoError(t, json.Unmarshal(got, &args)) + { + _, ok := args["_lightcode_probe"] + testassert.Check(t, !ok, "normalized arguments retained the private field: %s", got) } - if _, ok := args["_lightcode_probe"]; ok { - t.Errorf("normalized arguments retained the private field: %s", got) - } - if args["prompt"] != "do it" || args["subagent_type"] != "secondary" { - t.Errorf("normalized arguments = %s, want the members preserved", got) - } - if extra, ok := args["extra"].(float64); !ok || extra != 42 { - t.Errorf("normalized arguments = %s, want the unrelated member preserved", got) + testassert.Check(t, args["prompt"] == "do it" && args["subagent_type"] == "secondary", "normalized arguments = %s, want the members preserved", got) + { + extra, ok := args["extra"].(float64) + testassert.Check(t, ok && extra == 42, "normalized arguments = %s, want the unrelated member preserved", got) } rejected := []struct { args string @@ -127,9 +114,7 @@ func TestNormalizeTaskArguments(t *testing.T) { for _, tc := range rejected { tool := taskTool{} _, err := tool.Normalize(runtime.ToolContext{}, taskCall(t, tc.args)) - if err == nil || err.Error() != tc.want { - t.Errorf("Normalize(%s) error = %v, want exactly %q", tc.args, err, tc.want) - } + testassert.Check(t, err != nil && err.Error() == tc.want, "Normalize(%s) error = %v, want exactly %q", tc.args, err, tc.want) } } @@ -146,24 +131,21 @@ func TestPrepareRejectsMissingAndUnknown(t *testing.T) { prepare := func(args string) string { t.Helper() plan := taskTool{}.Prepare(context.Background(), tc, taskCall(t, args)) - if plan.Immediate == nil { - t.Fatalf("Prepare(%s) = %+v, want an immediate validation error", args, plan) - } - if plan.Permissions != nil || plan.Execute != nil { - t.Errorf("Prepare(%s) declared execution behind a validation error", args) - } - if plan.Immediate.Result.Status != model.ResultError { - t.Errorf("Prepare(%s) status = %q, want %q", args, plan.Immediate.Result.Status, model.ResultError) - } + testassert.Require(t, plan.Immediate != nil, "Prepare(%s) = %+v, want an immediate validation error", args, plan) + testassert.Check(t, plan.Permissions == nil && plan.Execute == nil, "Prepare(%s) declared execution behind a validation error", args) + testassert.Check(t, plan.Immediate.Result.Status == model.ResultError, "Prepare(%s) status = %q, want %q", args, plan.Immediate.Result.Status, model.ResultError) return plan.Immediate.Result.Content } - if got := prepare(`{}`); got != "missing 'prompt' parameter" { - t.Errorf("missing prompt content = %q, want %q", got, "missing 'prompt' parameter") + { + got := prepare(`{}`) + testassert.Check(t, got == "missing 'prompt' parameter", "missing prompt content = %q, want %q", got, "missing 'prompt' parameter") } - if got := prepare(`{"prompt":"do it"}`); got != "missing 'subagent_type' parameter" { - t.Errorf("missing subagent_type content = %q, want %q", got, "missing 'subagent_type' parameter") + { + got := prepare(`{"prompt":"do it"}`) + testassert.Check(t, got == "missing 'subagent_type' parameter", "missing subagent_type content = %q, want %q", got, "missing 'subagent_type' parameter") } - if got := prepare(`{"prompt":"do it","subagent_type":"ghost"}`); got != `unknown subagent type "ghost": unknown agent type "ghost"` { - t.Errorf("unknown type content = %q, want the retained wrapped shape over the roster resolver's error", got) + { + got := prepare(`{"prompt":"do it","subagent_type":"ghost"}`) + testassert.Check(t, got == `unknown subagent type "ghost": unknown agent type "ghost"`, "unknown type content = %q, want the retained wrapped shape over the roster resolver's error", got) } } diff --git a/internal/plugins/tools/commands_test.go b/internal/plugins/tools/commands_test.go index c4a87a14..9a5987ac 100644 --- a/internal/plugins/tools/commands_test.go +++ b/internal/plugins/tools/commands_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -17,71 +18,47 @@ func TestRunCommandNormalization(t *testing.T) { t.Run("applies the default timeout and strips private fields", func(t *testing.T) { raw, err := normalize(t, runCommand, tc, "run_command", `{"command":"ls","_lightcode_receipt":"x"}`) - if err != nil { - t.Fatalf("Normalize: %v", err) - } + testassert.NoError(t, err) var args map[string]any - if err := json.Unmarshal(raw, &args); err != nil { - t.Fatalf("normalized bytes: %v", err) - } - if args["command"] != "ls" || args["timeout"] != float64(120) || args["background"] != false { - t.Fatalf("normalized = %s, want the command with the default 120 timeout and background false", raw) - } - if _, ok := args["_lightcode_receipt"]; ok { - t.Fatal("normalized arguments retained a private field") + testassert.NoError(t, json.Unmarshal(raw, &args)) + testassert.Require(t, args["command"] == "ls" && args["timeout"] == float64(120) && args["background"] == false, "normalized = %s, want the command with the default 120 timeout and background false", raw) + { + _, ok := args["_lightcode_receipt"] + testassert.Require(t, !ok, "normalized arguments retained a private field") } }) t.Run("background booleans are accepted and non-booleans are rejected", func(t *testing.T) { raw, err := normalize(t, runCommand, tc, "run_command", `{"command":"ls","background":true}`) - if err != nil { - t.Fatalf("background true: %v", err) - } + testassert.NoError(t, err) var args map[string]any - if err := json.Unmarshal(raw, &args); err != nil { - t.Fatalf("normalized bytes: %v", err) - } - if args["background"] != true || args["timeout"] != float64(0) { - t.Fatalf("normalized = %s, want background true with the sub-one timeout 0", raw) - } + testassert.NoError(t, json.Unmarshal(raw, &args)) + testassert.Require(t, args["background"] == true && args["timeout"] == float64(0), "normalized = %s, want background true with the sub-one timeout 0", raw) raw, err = normalize(t, runCommand, tc, "run_command", `{"command":"ls","background":false,"timeout":5}`) - if err != nil { - t.Fatalf("background false: %v", err) - } + testassert.NoError(t, err) args = nil - if err := json.Unmarshal(raw, &args); err != nil { - t.Fatalf("normalized bytes: %v", err) - } - if args["background"] != false || args["timeout"] != float64(5) { - t.Fatalf("normalized = %s, want background false with the foreground timeout 5", raw) - } + testassert.NoError(t, json.Unmarshal(raw, &args)) + testassert.Require(t, args["background"] == false && args["timeout"] == float64(5), "normalized = %s, want background false with the foreground timeout 5", raw) raw, err = normalize(t, runCommand, tc, "run_command", `{"command":"ls","background":true,"timeout":7}`) - if err != nil { - t.Fatalf("background with override: %v", err) - } + testassert.NoError(t, err) args = nil - if err := json.Unmarshal(raw, &args); err != nil { - t.Fatalf("normalized bytes: %v", err) - } - if args["background"] != true || args["timeout"] != float64(7) { - t.Fatalf("normalized = %s, want background true with the forwarded timeout 7", raw) - } + testassert.NoError(t, json.Unmarshal(raw, &args)) + testassert.Require(t, args["background"] == true && args["timeout"] == float64(7), "normalized = %s, want background true with the forwarded timeout 7", raw) for _, args := range []string{ `{"command":"ls","background":null}`, `{"command":"ls","background":0}`, `{"command":"ls","background":"later"}`, } { - if _, err := normalize(t, runCommand, tc, "run_command", args); err == nil || !strings.Contains(err.Error(), "run_command: background must be a boolean") { - t.Errorf("run_command normalized %s to %v, want the boolean rejection", args, err) + { + _, err := normalize(t, runCommand, tc, "run_command", args) + testassert.Check(t, err != nil && strings.Contains(err.Error(), "run_command: background must be a boolean"), "run_command normalized %s to %v, want the boolean rejection", args, err) } } }) t.Run("timeout is a strict consumed integer within the duration bound", func(t *testing.T) { raw, err := normalize(t, runCommand, tc, "run_command", `{"command":"ls","timeout":5}`) - if err != nil || !strings.Contains(string(raw), `"timeout":5`) { - t.Fatalf("normalized = (%s, %v), want the override kept", raw, err) - } + testassert.Require(t, err == nil && strings.Contains(string(raw), `"timeout":5`), "normalized = (%s, %v), want the override kept", raw, err) for _, args := range []string{ `{"command":"ls","timeout":1.5}`, `{"command":"ls","timeout":"many"}`, @@ -90,21 +67,21 @@ func TestRunCommandNormalization(t *testing.T) { `{"command":"ls","timeout":9223372036854775808}`, `{"command":"ls","timeout":9223372037}`, } { - if _, err := normalize(t, runCommand, tc, "run_command", args); err == nil { - t.Errorf("run_command normalized %s, want rejection", args) + { + _, err := normalize(t, runCommand, tc, "run_command", args) + testassert.Check(t, err != nil, "run_command normalized %s, want rejection", args) } } // The default keeps sub-one and negative supplied values. raw, err = normalize(t, runCommand, tc, "run_command", `{"command":"ls","timeout":0}`) - if err != nil || !strings.Contains(string(raw), `"timeout":120`) { - t.Fatalf("normalized = (%s, %v), want the default for a sub-one override", raw, err) - } + testassert.Require(t, err == nil && strings.Contains(string(raw), `"timeout":120`), "normalized = (%s, %v), want the default for a sub-one override", raw, err) }) t.Run("empty command text keeps the retained argument error", func(t *testing.T) { for _, args := range []string{`{}`, `{"command":""}`, `{"command":null}`, `{"command":5}`} { - if _, err := normalize(t, runCommand, tc, "run_command", args); err == nil || !strings.Contains(err.Error(), "run_command: command is required") { - t.Errorf("run_command normalized %s to %v, want the retained required error", args, err) + { + _, err := normalize(t, runCommand, tc, "run_command", args) + testassert.Check(t, err != nil && strings.Contains(err.Error(), "run_command: command is required"), "run_command normalized %s to %v, want the retained required error", args, err) } } }) @@ -128,9 +105,7 @@ func TestCommandOutcomeClassifiesByDeliveredCause(t *testing.T) { return result, err } outcome := commandOutcome(ctx, "call-1", "kill -TERM $$", "", 0, defaultSettings(), t.TempDir(), nil) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "Error: Exit code -1\n" { - t.Fatalf("result = (%v, %q), want the real signal-death result as an error", outcome.Result.Status, outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "Error: Exit code -1\n", "result = (%v, %q), want the real signal-death result as an error", outcome.Result.Status, outcome.Result.Content) }) t.Run("own timeout keeps its real result after the context cancels", func(t *testing.T) { @@ -142,8 +117,6 @@ func TestCommandOutcomeClassifiesByDeliveredCause(t *testing.T) { return result, err } outcome := commandOutcome(ctx, "call-2", "sleep 5", "", 1, defaultSettings(), t.TempDir(), nil) - if outcome.Result.Status != model.ResultError || !strings.HasPrefix(outcome.Result.Content, "Error: Exit code -1 (timeout)\n") { - t.Fatalf("result = (%v, %q), want the retained timeout text as an error", outcome.Result.Status, outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.HasPrefix(outcome.Result.Content, "Error: Exit code -1 (timeout)\n"), "result = (%v, %q), want the retained timeout text as an error", outcome.Result.Status, outcome.Result.Content) }) } diff --git a/internal/plugins/tools/tools_test.go b/internal/plugins/tools/tools_test.go index 60667349..ffe43bb5 100644 --- a/internal/plugins/tools/tools_test.go +++ b/internal/plugins/tools/tools_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/tool" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" @@ -59,9 +60,7 @@ func callToolContext(workspace string, constraints runtime.ToolConstraints) runt func makeCall(t *testing.T, name, args string) model.ToolCall { t.Helper() call, err := model.NewToolCall(model.ToolCall{ID: "call-1", Name: name, Arguments: json.RawMessage(args)}) - if err != nil { - t.Fatalf("NewToolCall: %v", err) - } + testassert.NoError(t, err) return call } @@ -98,8 +97,9 @@ func TestValidateConfigSettings(t *testing.T) { `{"max_background_processes":5}`, `{"max_background_processes":"many","unknown_member":{"deep":[1,2]}}`, } { - if err := validate(raw); err != nil { - t.Errorf("ValidateConfig(%s) = %v, want accepted", raw, err) + { + err := validate(raw) + testassert.Check(t, err == nil, "ValidateConfig(%s) = %v, want accepted", raw, err) } } for _, raw := range []string{ @@ -114,8 +114,9 @@ func TestValidateConfigSettings(t *testing.T) { // Wrong section shapes. `[]`, `"tools"`, `5`, } { - if err := validate(raw); err == nil { - t.Errorf("ValidateConfig(%s) = nil, want rejected", raw) + { + err := validate(raw) + testassert.Check(t, err != nil, "ValidateConfig(%s) = nil, want rejected", raw) } } } @@ -146,22 +147,12 @@ func TestDescribeAvailabilityAndDefinitions(t *testing.T) { for _, tc := range cases { for name, want := range tc.available { got, err := describe[name](runtime.Invocation{}, tc.constraints, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("%s: describe %s: %v", tc.name, name, err) - } - if got.Available != want { - t.Errorf("%s: describe %s Available = %v, want %v", tc.name, name, got.Available, want) - } + testassert.Require(t, err == nil, "%s: describe %s: %v", tc.name, name, err) + testassert.Check(t, got.Available == want, "%s: describe %s Available = %v, want %v", tc.name, name, got.Available, want) wantHidden := name == "apply_patch" - if got.DefaultHidden != wantHidden { - t.Errorf("%s: describe %s DefaultHidden = %v, want %v", tc.name, name, got.DefaultHidden, wantHidden) - } - if got.Definition.Name != name { - t.Errorf("%s: describe %s declares name %q", tc.name, name, got.Definition.Name) - } - if got.Definition.Description == "" || !json.Valid(got.Definition.Parameters) { - t.Errorf("%s: describe %s produced an unusable definition", tc.name, name) - } + testassert.Check(t, got.DefaultHidden == wantHidden, "%s: describe %s DefaultHidden = %v, want %v", tc.name, name, got.DefaultHidden, wantHidden) + testassert.Check(t, got.Definition.Name == name, "%s: describe %s declares name %q", tc.name, name, got.Definition.Name) + testassert.Check(t, got.Definition.Description != "" && json.Valid(got.Definition.Parameters), "%s: describe %s produced an unusable definition", tc.name, name) } } } @@ -170,19 +161,17 @@ func TestDescribeAvailabilityAndDefinitions(t *testing.T) { func assertJSONEqual(t *testing.T, got json.RawMessage, want any) { t.Helper() wantRaw, err := json.Marshal(want) - if err != nil { - t.Fatalf("marshal retained schema: %v", err) - } + testassert.NoError(t, err) var gotValue, wantValue any - if err := json.Unmarshal(got, &gotValue); err != nil { - t.Fatalf("decode described schema %s: %v", got, err) - } - if err := json.Unmarshal(wantRaw, &wantValue); err != nil { - t.Fatalf("decode retained schema %s: %v", wantRaw, err) + { + err := json.Unmarshal(got, &gotValue) + testassert.Require(t, err == nil, "decode described schema %s: %v", got, err) } - if !reflect.DeepEqual(gotValue, wantValue) { - t.Errorf("described schema = %s, want the retained schema %s", got, wantRaw) + { + err := json.Unmarshal(wantRaw, &wantValue) + testassert.Require(t, err == nil, "decode retained schema %s: %v", wantRaw, err) } + testassert.Check(t, reflect.DeepEqual(gotValue, wantValue), "described schema = %s, want the retained schema %s", got, wantRaw) } // wantRunCommandDescription is the independent golden fixture for the @@ -248,41 +237,25 @@ const wantProcessParameters = `{ // constants. func TestRunCommandDescriptionsAndSchemasMatchRetained(t *testing.T) { ordinary, err := runCommandTool{}.describe(runtime.Invocation{}, runtime.ToolConstraints{}, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("describe ordinary: %v", err) - } - if ordinary.Definition.Description != wantRunCommandDescription { - t.Errorf("ordinary description = %q, want the retained description %q", ordinary.Definition.Description, wantRunCommandDescription) - } + testassert.NoError(t, err) + testassert.Check(t, ordinary.Definition.Description == wantRunCommandDescription, "ordinary description = %q, want the retained description %q", ordinary.Definition.Description, wantRunCommandDescription) assertJSONEqual(t, ordinary.Definition.Parameters, json.RawMessage(wantRunCommandParameters)) readonly, err := runCommandTool{}.describe(runtime.Invocation{}, runtime.ToolConstraints{Readonly: true}, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("describe readonly: %v", err) - } - if readonly.Definition.Description != tool.ReadOnlyRunCommandDescription { - t.Errorf("readonly description = %q, want the retained readonly description", readonly.Definition.Description) - } + testassert.NoError(t, err) + testassert.Check(t, readonly.Definition.Description == tool.ReadOnlyRunCommandDescription, "readonly description = %q, want the retained readonly description", readonly.Definition.Description) assertJSONEqual(t, readonly.Definition.Parameters, json.RawMessage(wantRunCommandParameters)) - if !ordinary.Available || !readonly.Available { - t.Errorf("availability ordinary/readonly = %v/%v, want both available", ordinary.Available, readonly.Available) - } + testassert.Check(t, ordinary.Available && readonly.Available, "availability ordinary/readonly = %v/%v, want both available", ordinary.Available, readonly.Available) } // TestProcessDescriptionAndSchemaMatchRetained pins the process tool's // model-visible surface to the independent test-local fixtures. func TestProcessDescriptionAndSchemaMatchRetained(t *testing.T) { got, err := processTool{}.describe(runtime.Invocation{}, runtime.ToolConstraints{}, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("describe process: %v", err) - } - if got.Definition.Description != wantProcessDescription { - t.Errorf("process description = %q, want the retained description %q", got.Definition.Description, wantProcessDescription) - } + testassert.NoError(t, err) + testassert.Check(t, got.Definition.Description == wantProcessDescription, "process description = %q, want the retained description %q", got.Definition.Description, wantProcessDescription) assertJSONEqual(t, got.Definition.Parameters, json.RawMessage(wantProcessParameters)) - if got.Definition.Name != "process" || !got.Available || got.DefaultHidden { - t.Errorf("process definition = %+v, want name process, available, not hidden", got) - } + testassert.Check(t, got.Definition.Name == "process" && got.Available && !got.DefaultHidden, "process definition = %+v, want name process, available, not hidden", got) } func TestNormalizeArguments(t *testing.T) { @@ -291,33 +264,26 @@ func TestNormalizeArguments(t *testing.T) { t.Run("read_file applies defaults and strips private fields", func(t *testing.T) { raw, err := normalize(t, byID["read_file"], tc, "read_file", `{"path":"a.txt","_lightcode_receipt":"x"}`) - if err != nil { - t.Fatalf("Normalize: %v", err) - } + testassert.NoError(t, err) var args map[string]any - if err := json.Unmarshal(raw, &args); err != nil { - t.Fatalf("normalized bytes: %v", err) - } - if args["path"] != "a.txt" || args["offset"] != float64(1) || args["limit"] != float64(500) { - t.Fatalf("normalized = %s, want the path with default offset/limit", raw) - } - if _, ok := args["_lightcode_receipt"]; ok { - t.Fatal("normalized arguments retained a private field") + testassert.NoError(t, json.Unmarshal(raw, &args)) + testassert.Require(t, args["path"] == "a.txt" && args["offset"] == float64(1) && args["limit"] == float64(500), "normalized = %s, want the path with default offset/limit", raw) + { + _, ok := args["_lightcode_receipt"] + testassert.Require(t, !ok, "normalized arguments retained a private field") } }) t.Run("read_file keeps exact integral spellings and rejects fractions and overflow", func(t *testing.T) { raw, err := normalize(t, byID["read_file"], tc, "read_file", `{"path":"a","offset":1e0,"limit":2.0}`) - if err != nil { - t.Fatalf("Normalize: %v", err) - } - if !strings.Contains(string(raw), `"offset":1`) || !strings.Contains(string(raw), `"limit":2`) { - t.Fatalf("normalized = %s, want exact integral values", raw) + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(raw), `"offset":1`) && strings.Contains(string(raw), `"limit":2`), "normalized = %s, want exact integral values", raw) + { + _, err := normalize(t, byID["read_file"], tc, "read_file", `{"path":"a","limit":2.5}`) + testassert.Check(t, err != nil, "fractional limit normalized, want rejection") } - if _, err := normalize(t, byID["read_file"], tc, "read_file", `{"path":"a","limit":2.5}`); err == nil { - t.Error("fractional limit normalized, want rejection") - } - if _, err := normalize(t, byID["read_file"], tc, "read_file", `{"path":"a","offset":99999999999999999999}`); err == nil { - t.Error("overflowing offset normalized, want rejection") + { + _, err := normalize(t, byID["read_file"], tc, "read_file", `{"path":"a","offset":99999999999999999999}`) + testassert.Check(t, err != nil, "overflowing offset normalized, want rejection") } }) t.Run("strict schema errors", func(t *testing.T) { @@ -334,8 +300,9 @@ func TestNormalizeArguments(t *testing.T) { if strings.HasSuffix(toolName, "2") { toolName = strings.TrimSuffix(toolName, "2") } - if _, err := normalize(t, byID[toolName], tc, toolName, args); err == nil { - t.Errorf("%s normalized %s, want rejection", toolName, args) + { + _, err := normalize(t, byID[toolName], tc, toolName, args) + testassert.Check(t, err != nil, "%s normalized %s, want rejection", toolName, args) } } }) @@ -346,30 +313,27 @@ func TestNormalizeArguments(t *testing.T) { `{"action":"read","id":5}`, `{"action":"list","id":true}`, } { - if _, err := normalize(t, byID["process"], tc, "process", args); err == nil { - t.Errorf("process normalized %s, want rejection", args) + { + _, err := normalize(t, byID["process"], tc, "process", args) + testassert.Check(t, err != nil, "process normalized %s, want rejection", args) } } raw, err := normalize(t, byID["process"], tc, "process", `{"action":"read","id":"deadbeef","_lightcode_receipt":"x","note":"kept"}`) - if err != nil { - t.Fatalf("Normalize: %v", err) - } + testassert.NoError(t, err) var normalized map[string]any - if err := json.Unmarshal(raw, &normalized); err != nil { - t.Fatalf("normalized bytes: %v", err) - } - if normalized["action"] != "read" || normalized["id"] != "deadbeef" || normalized["note"] != "kept" { - t.Fatalf("normalized = %s, want the members preserved", raw) - } - if _, ok := normalized["_lightcode_receipt"]; ok { - t.Fatal("normalized arguments retained a private field") + testassert.NoError(t, json.Unmarshal(raw, &normalized)) + testassert.Require(t, normalized["action"] == "read" && normalized["id"] == "deadbeef" && normalized["note"] == "kept", "normalized = %s, want the members preserved", raw) + { + _, ok := normalized["_lightcode_receipt"] + testassert.Require(t, !ok, "normalized arguments retained a private field") } }) t.Run("malformed, null and trailing argument bytes are validation errors", func(t *testing.T) { for _, args := range []string{``, `not json`, `null`, `[1]`, `{"path":"a"} trailing`, `"str"`} { for _, name := range []string{"read_file", "write_file", "edit_file", "apply_patch", "run_command", "process", "sleep"} { - if _, err := normalize(t, byID[name], tc, name, args); err == nil { - t.Errorf("%s normalized %q, want rejection", name, args) + { + _, err := normalize(t, byID[name], tc, name, args) + testassert.Check(t, err != nil, "%s normalized %q, want rejection", name, args) } } } @@ -381,9 +345,7 @@ func TestNormalizeArguments(t *testing.T) { func bindingChangePatchInput(t *testing.T, path string) string { t.Helper() raw, err := json.Marshal(map[string]string{"input": "*** Begin Patch\n*** Add File: " + path + "\n+new\n*** End Patch"}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return string(raw) } @@ -423,24 +385,18 @@ func TestPrepareBindingSurvivesCanonicalPathsRepoint(t *testing.T) { flipTo = realB link = filepath.Join(t.TempDir(), "w") workspace = link - if err := os.Symlink(realA, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realA, link)) } else { workspace = t.TempDir() wdA, wdB := t.TempDir(), t.TempDir() flipTo = wdB preFlipWriteDir = wdA link = filepath.Join(workspace, "wd") - if err := os.Symlink(wdA, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(wdA, link)) constraints = runtime.ToolConstraints{WriteDir: "wd"} } if cell.content != "" { - if err := os.WriteFile(filepath.Join(flipTo, "target.txt"), []byte(cell.content), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(flipTo, "target.txt"), []byte(cell.content), 0o644)) } // The seam repoints the symlink on its return: after the plugin's // canonical resolutions, before the shared preparation binds. @@ -450,27 +406,19 @@ func TestPrepareBindingSurvivesCanonicalPathsRepoint(t *testing.T) { if err != nil { return w, b, err } - if err := os.Remove(link); err != nil { - t.Fatal(err) - } - if err := os.Symlink(flipTo, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(link)) + testassert.NoError(t, os.Symlink(flipTo, link)) return w, b, err } defer func() { canonicalPathsFn = orig }() plan := prepare(t, byID[cell.toolID], callToolContext(workspace, constraints), cell.toolID, cell.args) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || !strings.Contains(outcome.Result.Content, "changed") { - t.Fatalf("result = %+v, want the changed-binding failure without substitution", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, "changed"), "result = %+v, want the changed-binding failure without substitution", outcome.Result) // The patch path threads the write-dir witness directly, so a // dropped witness would fail with the empty-witness form // ("changed from to ..."); the real changed-binding error names // the pre-flip canonical write-dir as the bound value. - if cell.boundWriteDir && !strings.Contains(outcome.Result.Content, preFlipWriteDir) { - t.Fatalf("result = %+v, want the changed-binding error naming the bound write-dir %q", outcome.Result, preFlipWriteDir) - } + testassert.Require(t, !cell.boundWriteDir || strings.Contains(outcome.Result.Content, preFlipWriteDir), "result = %+v, want the changed-binding error naming the bound write-dir %q", outcome.Result, preFlipWriteDir) }) } } diff --git a/internal/prompt/adaptation_assembly_test.go b/internal/prompt/adaptation_assembly_test.go index a8d15799..6556ba5b 100644 --- a/internal/prompt/adaptation_assembly_test.go +++ b/internal/prompt/adaptation_assembly_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/adaptation" + "github.com/MMinasyan/lightcode/internal/testassert" ) // An adaptation with no additions produces the same prompt as baseline; @@ -14,14 +15,10 @@ import ( func TestAssembleForNoAdditionsMatchesBaseline(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("some user rules"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("some user rules"), 0o600)) base := assembleFull(projectRoot, home, nil).Prompt adapt := assembleFull(projectRoot, home, &adaptation.Adaptation{}).Prompt - if base != adapt { - t.Fatalf("adaptation with no additions changed the prompt\n--- baseline ---\n%s\n--- adaptation ---\n%s", base, adapt) - } + testassert.Equal(t, base, adapt) } // Bullet 2: coaching blocks are inserted after the overridable sections and before @@ -31,30 +28,23 @@ func TestAssembleForInsertsBlocksAfterSectionsBeforeRules(t *testing.T) { projectRoot := t.TempDir() // User rules with a unique marker and NO section-override headings, so every // overridable section renders. - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_USER_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_USER_RULES_MARKER"), 0o600)) prompt := assembleFull(projectRoot, home, &adaptation.Adaptation{Blocks: []string{"COACHING_BLOCK_MARKER"}}).Prompt idxLanguage := strings.Index(prompt, strings.TrimSpace(languageSection)) // last overridable section idxBlock := strings.Index(prompt, "COACHING_BLOCK_MARKER") idxRules := strings.Index(prompt, "PROJECT_USER_RULES_MARKER") - if idxLanguage < 0 || idxBlock < 0 || idxRules < 0 { - t.Fatalf("missing anchor: language=%d block=%d rules=%d", idxLanguage, idxBlock, idxRules) - } + testassert.Require(t, idxLanguage >= 0 && idxBlock >= 0 && idxRules >= 0, "missing anchor: language=%d block=%d rules=%d", idxLanguage, idxBlock, idxRules) endLanguage := idxLanguage + len(strings.TrimSpace(languageSection)) - if !(endLanguage <= idxBlock && idxBlock < idxRules) { - t.Fatalf("block not between last section and user rules: endLanguage=%d block=%d rules=%d", endLanguage, idxBlock, idxRules) - } + testassert.Require(t, (endLanguage <= idxBlock && idxBlock < idxRules), "block not between last section and user rules: endLanguage=%d block=%d rules=%d", endLanguage, idxBlock, idxRules) // The block does not suppress any overridable section. for name, section := range overridableSections { - if !strings.Contains(prompt, strings.TrimSpace(section)) { - t.Fatalf("overridable section %q suppressed by the block", name) - } + testassert.Require(t, strings.Contains(prompt, strings.TrimSpace(section)), "overridable section %q suppressed by the block", name) } // detectOverrides is unaffected: no section heading in the rules, none overridden. - if ov := detectOverrides("PROJECT_USER_RULES_MARKER"); ov["safety"] || ov["tone"] || ov["task_execution"] || ov["language"] { - t.Fatalf("detectOverrides false positive from a block-only adaptation: %+v", ov) + { + ov := detectOverrides("PROJECT_USER_RULES_MARKER") + testassert.Require(t, !ov["safety"] && !ov["tone"] && !ov["task_execution"] && !ov["language"], "detectOverrides false positive from a block-only adaptation: %+v", ov) } } @@ -63,9 +53,7 @@ func TestAssembleForInsertsBlocksAfterSectionsBeforeRules(t *testing.T) { func TestAssembleForRendersSectionAddition(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_USER_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_USER_RULES_MARKER"), 0o600)) adapt := &adaptation.Adaptation{ Additions: map[string]string{"tone": "TONE_ADDITION_MARKER"}, } @@ -75,13 +63,9 @@ func TestAssembleForRendersSectionAddition(t *testing.T) { idxToneAdd := strings.Index(prompt, "TONE_ADDITION_MARKER") idxTask := strings.Index(prompt, strings.TrimSpace(taskExecutionSection)) idxRules := strings.Index(prompt, "PROJECT_USER_RULES_MARKER") - if idxToneMain < 0 || idxToneAdd < 0 || idxTask < 0 || idxRules < 0 { - t.Fatalf("missing anchor: toneMain=%d toneAdd=%d task=%d rules=%d", idxToneMain, idxToneAdd, idxTask, idxRules) - } + testassert.Require(t, idxToneMain >= 0 && idxToneAdd >= 0 && idxTask >= 0 && idxRules >= 0, "missing anchor: toneMain=%d toneAdd=%d task=%d rules=%d", idxToneMain, idxToneAdd, idxTask, idxRules) endToneMain := idxToneMain + len(strings.TrimSpace(toneSection)) - if !(endToneMain <= idxToneAdd && idxToneAdd < idxTask && idxTask < idxRules) { - t.Fatalf("addition not between its section main and the next section: endToneMain=%d toneAdd=%d task=%d", endToneMain, idxToneAdd, idxTask) - } + testassert.Require(t, (endToneMain <= idxToneAdd && idxToneAdd < idxTask && idxTask < idxRules), "addition not between its section main and the next section: endToneMain=%d toneAdd=%d task=%d", endToneMain, idxToneAdd, idxTask) } // Section additions render even when a user heading overrides the section main. @@ -89,34 +73,23 @@ func TestAssembleForAdditionRendersUnderUserOverride(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() // A user heading suppresses the tone main, but the tone addition must still render. - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("# tone\n\nUSER_TONE_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("# tone\n\nUSER_TONE_RULES_MARKER"), 0o600)) adapt := &adaptation.Adaptation{ Additions: map[string]string{"tone": "TONE_ADDITION_MARKER"}, } prompt := assembleFull(projectRoot, home, adapt).Prompt - if strings.Contains(prompt, strings.TrimSpace(toneSection)) { - t.Fatal("user override did not suppress the tone main") - } + testassert.Require(t, !strings.Contains(prompt, strings.TrimSpace(toneSection)), "user override did not suppress the tone main") idxToneAdd := strings.Index(prompt, "TONE_ADDITION_MARKER") idxUser := strings.Index(prompt, "USER_TONE_RULES_MARKER") - if idxToneAdd < 0 || idxUser < 0 { - t.Fatalf("missing anchor: toneAdd=%d user=%d", idxToneAdd, idxUser) - } - if idxToneAdd >= idxUser { - t.Fatalf("tone addition (%d) must render before user rules (%d)", idxToneAdd, idxUser) - } + testassert.Require(t, idxToneAdd >= 0 && idxUser >= 0 && idxToneAdd < idxUser, "missing anchor: toneAdd=%d user=%d; tone addition (%d) must render before user rules (%d)", idxToneAdd, idxUser, idxToneAdd, idxUser) } // Section additions apply independently per section id. func TestAssembleForAdditionPerSection(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_USER_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_USER_RULES_MARKER"), 0o600)) for _, name := range overridableOrder { t.Run(name, func(t *testing.T) { @@ -125,16 +98,12 @@ func TestAssembleForAdditionPerSection(t *testing.T) { Additions: map[string]string{name: marker}, } prompt := assembleFull(projectRoot, home, adapt).Prompt - if !strings.Contains(prompt, marker) { - t.Fatalf("addition for %q missing from prompt", name) - } + testassert.Require(t, strings.Contains(prompt, marker), "addition for %q missing from prompt", name) for _, other := range overridableOrder { if other == name { continue } - if strings.Contains(prompt, strings.ToUpper(other)+"_ADDITION_MARKER") { - t.Fatalf("addition for %q leaked into %q's test", other, name) - } + testassert.Require(t, !strings.Contains(prompt, strings.ToUpper(other)+"_ADDITION_MARKER"), "addition for %q leaked into %q's test", other, name) } }) } diff --git a/internal/prompt/assembler_test.go b/internal/prompt/assembler_test.go index 7e991b18..6238fb26 100644 --- a/internal/prompt/assembler_test.go +++ b/internal/prompt/assembler_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/adaptation" + "github.com/MMinasyan/lightcode/internal/testassert" ) // testStart pins the rendered session start so prompts built in different calls @@ -32,40 +33,25 @@ func buildFull(root, globalRules, projectRules string, adapt *adaptation.Adaptat func TestDetectOverrides(t *testing.T) { got := detectOverrides("# Safety\ntext\n## Tone\n### Task Execution\n# other\n") - if !got["safety"] || !got["tone"] { - t.Fatalf("detectOverrides = %+v, want safety and tone", got) - } - if !got["task_execution"] { - t.Fatalf("detectOverrides = %+v, want task_execution", got) - } - if got["language"] { - t.Fatalf("detectOverrides false positives = %+v", got) - } + testassert.Require(t, got["safety"] && got["tone"] && got["task_execution"] && !got["language"], "detectOverrides = %+v, want safety and tone; detectOverrides = %+v, want task_execution; detectOverrides false positives = %+v", got, got, got) for _, rules := range []string{"## Task Execution", "## task_execution", "## task-execution", "## Task\tExecution", "## Task Execution", "## Task Execution ##"} { - if !detectOverrides(rules)["task_execution"] { - t.Fatalf("detectOverrides(%q) did not match task_execution", rules) - } + testassert.Require(t, detectOverrides(rules)["task_execution"], "detectOverrides(%q) did not match task_execution", rules) } - if got := detectOverrides("## Environment"); got["safety"] || got["tone"] || got["task_execution"] || got["language"] { - t.Fatalf("environment heading caused override = %+v", got) + { + got := detectOverrides("## Environment") + testassert.Require(t, !got["safety"] && !got["tone"] && !got["task_execution"] && !got["language"], "environment heading caused override = %+v", got) } } func TestBuildIncludesBaseEnvironmentAndRules(t *testing.T) { prompt := buildFull("/work/project", "global rules", "project rules", nil) for _, want := range []string{"Working directory: /work/project", "global rules", "project rules"} { - if !strings.Contains(prompt, want) { - t.Fatalf("prompt missing %q\n%s", want, prompt) - } + testassert.Require(t, strings.Contains(prompt, want), "prompt missing %q\n%s", want, prompt) } withoutOverride := buildFull("/work/project", "", "", nil) withOverride := buildFull("/work/project", "# Safety\ncustom", "", nil) - if strings.Contains(withOverride, strings.TrimSpace(safetySection)) && !strings.Contains(withoutOverride, strings.TrimSpace(safetySection)) { - t.Fatal("sanity check failed for safety section") - } - if strings.Contains(withOverride, strings.TrimSpace(safetySection)) { - t.Fatal("safety section was not overridden by # Safety rules") - } + testassert.Require(t, !strings.Contains(withOverride, strings.TrimSpace(safetySection)) || strings.Contains(withoutOverride, strings.TrimSpace(safetySection)), "sanity check failed for safety section") + testassert.Require(t, !strings.Contains(withOverride, strings.TrimSpace(safetySection)), "safety section was not overridden by # Safety rules") } func TestBuildIncludesReadableSectionHeadings(t *testing.T) { @@ -81,51 +67,33 @@ func TestBuildIncludesReadableSectionHeadings(t *testing.T) { "## Task Execution", "## Language", } { - if !strings.Contains(prompt, want) { - t.Fatalf("prompt missing heading %q\n%s", want, prompt) - } + testassert.Require(t, strings.Contains(prompt, want), "prompt missing heading %q\n%s", want, prompt) } idxEnv := strings.Index(prompt, "## Environment") idxWorkingDir := strings.Index(prompt, "Working directory: /work/project") - if idxEnv < 0 || idxWorkingDir < 0 || idxEnv > idxWorkingDir { - t.Fatalf("environment heading not before working directory: heading=%d workingDir=%d", idxEnv, idxWorkingDir) - } + testassert.Require(t, idxEnv >= 0 && idxWorkingDir >= 0 && idxEnv <= idxWorkingDir, "environment heading not before working directory: heading=%d workingDir=%d", idxEnv, idxWorkingDir) } func TestBuildTaskExecutionOverrideUsesReadableHeading(t *testing.T) { withoutOverride := buildFull("/work/project", "", "", nil) withOverride := buildFull("/work/project", "## Task Execution\ncustom task rules", "", nil) - if !strings.Contains(withoutOverride, strings.TrimSpace(taskExecutionSection)) { - t.Fatal("sanity check failed for task execution section") - } - if strings.Contains(withOverride, strings.TrimSpace(taskExecutionSection)) { - t.Fatal("task execution section was not overridden by ## Task Execution rules") - } - if !strings.Contains(withOverride, "custom task rules") { - t.Fatal("custom task execution rules missing") - } + testassert.Require(t, strings.Contains(withoutOverride, strings.TrimSpace(taskExecutionSection)), "sanity check failed for task execution section") + testassert.Require(t, !strings.Contains(withOverride, strings.TrimSpace(taskExecutionSection)), "task execution section was not overridden by ## Task Execution rules") + testassert.Require(t, strings.Contains(withOverride, "custom task rules"), "custom task execution rules missing") } func TestAssemblerWarnings(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() first := assembleFull(projectRoot, home, nil) - if !hasWarning(first.Warnings, WarnRulesNotFound) { - t.Fatalf("warnings = %+v, want rules_not_found", first.Warnings) - } + testassert.Require(t, hasWarning(first.Warnings, WarnRulesNotFound), "warnings = %+v, want rules_not_found", first.Warnings) - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte(strings.Repeat("x", 20001)), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte(strings.Repeat("x", 20001)), 0o600)) second := assembleFull(projectRoot, home, nil) - if !hasWarning(second.Warnings, WarnRulesTooLarge) { - t.Fatalf("warnings = %+v, want rules_too_large", second.Warnings) - } + testassert.Require(t, hasWarning(second.Warnings, WarnRulesTooLarge), "warnings = %+v, want rules_too_large", second.Warnings) // The service reads rules fresh every call: the warning reflects the current // on-disk state, not a cached earlier result. - if hasWarning(second.Warnings, WarnRulesNotFound) { - t.Fatalf("warnings = %+v, want no rules_not_found after AGENTS.md written", second.Warnings) - } + testassert.Require(t, !hasWarning(second.Warnings, WarnRulesNotFound), "warnings = %+v, want no rules_not_found after AGENTS.md written", second.Warnings) } // TestServiceIsStatelessAcrossProjectsAndSessions verifies the shared service @@ -135,12 +103,8 @@ func TestServiceIsStatelessAcrossProjectsAndSessions(t *testing.T) { home := t.TempDir() projectA := t.TempDir() projectB := t.TempDir() - if err := os.WriteFile(filepath.Join(projectA, "AGENTS.md"), []byte("PROJECT_A_RULES"), 0o600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(projectB, "AGENTS.md"), []byte("PROJECT_B_RULES"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectA, "AGENTS.md"), []byte("PROJECT_A_RULES"), 0o600)) + testassert.NoError(t, os.WriteFile(filepath.Join(projectB, "AGENTS.md"), []byte("PROJECT_B_RULES"), 0o600)) svc := NewService(home) spec := Spec{Size: SizeFull} @@ -148,33 +112,19 @@ func TestServiceIsStatelessAcrossProjectsAndSessions(t *testing.T) { // rules; neither leaks the other's. a := svc.Assemble(projectA, testStart, spec).Prompt b := svc.Assemble(projectB, testStart, spec).Prompt - if !strings.Contains(a, "Working directory: "+projectA) || !strings.Contains(a, "PROJECT_A_RULES") { - t.Fatalf("project A prompt missing its own root/rules\n%s", a) - } - if strings.Contains(a, projectB) || strings.Contains(a, "PROJECT_B_RULES") { - t.Fatalf("project A prompt leaked project B state\n%s", a) - } - if !strings.Contains(b, "Working directory: "+projectB) || !strings.Contains(b, "PROJECT_B_RULES") { - t.Fatalf("project B prompt missing its own root/rules\n%s", b) - } + testassert.Require(t, strings.Contains(a, "Working directory: "+projectA) && strings.Contains(a, "PROJECT_A_RULES") && !strings.Contains(a, projectB) && !strings.Contains(a, "PROJECT_B_RULES"), "project A prompt missing its own root/rules\n%s; project A prompt leaked project B state\n%s", a, a) + testassert.Require(t, strings.Contains(b, "Working directory: "+projectB) && strings.Contains(b, "PROJECT_B_RULES"), "project B prompt missing its own root/rules\n%s", b) // Re-rendering project A after project B yields byte-identical output: no state // carried over from the intervening call. - if again := svc.Assemble(projectA, testStart, spec).Prompt; again != a { - t.Fatalf("re-render of project A differs after an intervening project B call\n--- first ---\n%s\n--- again ---\n%s", a, again) - } + testassert.Equal(t, svc.Assemble(projectA, testStart, spec).Prompt, a) // Distinct session starts render distinct environment lines for the same root. otherStart := testStart.Add(90 * time.Minute) early := svc.Assemble(projectA, testStart, spec).Prompt late := svc.Assemble(projectA, otherStart, spec).Prompt - if early == late { - t.Fatal("distinct session starts produced identical prompts") - } - if !strings.Contains(early, testStart.Format("2006-01-02 15:04:05 MST")) || - !strings.Contains(late, otherStart.Format("2006-01-02 15:04:05 MST")) { - t.Fatal("session start not rendered per call") - } + testassert.Require(t, early != late, "distinct session starts produced identical prompts") + testassert.Require(t, strings.Contains(early, testStart.Format("2006-01-02 15:04:05 MST")) && strings.Contains(late, otherStart.Format("2006-01-02 15:04:05 MST")), "session start not rendered per call") } // TestAssembledBaselinePromptDoesNotAdvertiseMemoryTools is the Step-2 prompt @@ -195,29 +145,26 @@ func TestAssembledBaselinePromptDoesNotAdvertiseMemoryTools(t *testing.T) { "memory tools", "Memory Instructions", } { - if strings.Contains(prompt, forbidden) { - t.Fatalf("assembled %s prompt still advertises removed memory behavior (%q):\n%s", size, forbidden, prompt) - } + testassert.Require(t, !strings.Contains(prompt, forbidden), "assembled %s prompt still advertises removed memory behavior (%q):\n%s", size, forbidden, prompt) } } } func TestReadRulesFilePrefersAgentsThenClaude(t *testing.T) { dir := t.TempDir() - if got, err := readRulesFile(dir); err != nil || got != "" { - t.Fatalf("empty readRulesFile = %q, %v", got, err) - } - if err := os.WriteFile(filepath.Join(dir, "CLAUDE.md"), []byte("claude"), 0o600); err != nil { - t.Fatal(err) - } - if got, err := readRulesFile(dir); err != nil || got != "claude" { - t.Fatalf("CLAUDE readRulesFile = %q, %v", got, err) - } - if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("agents"), 0o600); err != nil { - t.Fatal(err) - } - if got, err := readRulesFile(dir); err != nil || got != "agents" { - t.Fatalf("AGENTS readRulesFile = %q, %v", got, err) + { + got, err := readRulesFile(dir) + testassert.Require(t, err == nil && got == "", "empty readRulesFile = %q, %v", got, err) + } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "CLAUDE.md"), []byte("claude"), 0o600)) + { + got, err := readRulesFile(dir) + testassert.Require(t, err == nil && got == "claude", "CLAUDE readRulesFile = %q, %v", got, err) + } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("agents"), 0o600)) + { + got, err := readRulesFile(dir) + testassert.Require(t, err == nil && got == "agents", "AGENTS readRulesFile = %q, %v", got, err) } } diff --git a/internal/prompt/composition_test.go b/internal/prompt/composition_test.go index d5bd0929..23d47414 100644 --- a/internal/prompt/composition_test.go +++ b/internal/prompt/composition_test.go @@ -8,34 +8,27 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/adaptation" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestAssembleFullSpecIsStatelessAndDeterministic(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600)) spec := Spec{Size: SizeFull} // A baseline (nil adapt) render and an explicit full spec are the same prompt, // and repeated renders are byte-identical: the service holds no state that // would drift between calls. base := assembleFull(projectRoot, home, nil).Prompt explicit := assembleSpec(projectRoot, home, spec).Prompt - if base != explicit { - t.Fatalf("full empty-body spec differs from baseline\n--- baseline ---\n%s\n--- spec ---\n%s", base, explicit) - } - if again := assembleSpec(projectRoot, home, spec).Prompt; again != explicit { - t.Fatalf("repeated render differs\n--- first ---\n%s\n--- again ---\n%s", explicit, again) - } + testassert.Equal(t, base, explicit) + testassert.Equal(t, assembleSpec(projectRoot, home, spec).Prompt, explicit) } func TestAssembleConcurrent(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600)) svc := NewService(home) adapt := &adaptation.Adaptation{Blocks: []string{"CONCURRENT_BLOCK"}} var wg sync.WaitGroup @@ -62,27 +55,21 @@ func TestAssembleConcurrent(t *testing.T) { func TestAssembleFullWithAdaptationRendersAdaptationContent(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600)) adapt := &adaptation.Adaptation{ Blocks: []string{"FULL_BLOCK_MARKER"}, Additions: map[string]string{"tone": "FULL_TONE_ADDITION"}, } prompt := assembleFull(projectRoot, home, adapt).Prompt for _, want := range []string{"FULL_BLOCK_MARKER", "FULL_TONE_ADDITION", "PROJECT_RULES_MARKER", "## Environment"} { - if !strings.Contains(prompt, want) { - t.Fatalf("adapted full prompt missing %q\n%s", want, prompt) - } + testassert.Require(t, strings.Contains(prompt, want), "adapted full prompt missing %q\n%s", want, prompt) } } func TestAssembleForSpecSimpleIncludesOnlySimpleSectionsAndBodySlot(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("## Tone\n\nPROJECT_TONE_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("## Tone\n\nPROJECT_TONE_RULES_MARKER"), 0o600)) adapt := &adaptation.Adaptation{ Blocks: []string{"SIMPLE_BLOCK_MARKER"}, Additions: map[string]string{"safety": "SIMPLE_SAFETY_ADDITION", "tone": "SIMPLE_TONE_ADDITION"}, @@ -105,9 +92,7 @@ func TestAssembleForSpecSimpleIncludesOnlySimpleSectionsAndBodySlot(t *testing.T "Do the focused job.", "PROJECT_TONE_RULES_MARKER", } { - if !strings.Contains(prompt, want) { - t.Fatalf("simple prompt missing %q\n%s", want, prompt) - } + testassert.Require(t, strings.Contains(prompt, want), "simple prompt missing %q\n%s", want, prompt) } for _, forbidden := range []string{ strings.TrimSpace(rulesFileGuideSection), @@ -116,9 +101,7 @@ func TestAssembleForSpecSimpleIncludesOnlySimpleSectionsAndBodySlot(t *testing.T strings.TrimSpace(languageSection), "SIMPLE_TONE_ADDITION", } { - if strings.Contains(prompt, forbidden) { - t.Fatalf("simple prompt contains omitted content %q\n%s", forbidden, prompt) - } + testassert.Require(t, !strings.Contains(prompt, forbidden), "simple prompt contains omitted content %q\n%s", forbidden, prompt) } assertOrder(t, prompt, @@ -134,9 +117,7 @@ func TestAssembleForSpecSimpleIncludesOnlySimpleSectionsAndBodySlot(t *testing.T func TestAssembleForSpecNoneProducesBodyOnly(t *testing.T) { home := t.TempDir() projectRoot := t.TempDir() - if err := os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(projectRoot, "AGENTS.md"), []byte("PROJECT_RULES_MARKER"), 0o600)) body := "## Your Role and Instructions\n\nOnly compact this transcript." prompt := assembleSpec(projectRoot, home, Spec{ Size: SizeNone, @@ -144,9 +125,7 @@ func TestAssembleForSpecNoneProducesBodyOnly(t *testing.T) { Adapt: &adaptation.Adaptation{Blocks: []string{"IGNORED_BLOCK"}}, }).Prompt - if !strings.Contains(prompt, body) { - t.Fatalf("none prompt missing body %q\n%s", body, prompt) - } + testassert.Require(t, strings.Contains(prompt, body), "none prompt missing body %q\n%s", body, prompt) for _, forbidden := range []string{ strings.TrimSpace(identitySection), strings.TrimSpace(coreRulesSection), @@ -160,12 +139,11 @@ func TestAssembleForSpecNoneProducesBodyOnly(t *testing.T) { "PROJECT_RULES_MARKER", "IGNORED_BLOCK", } { - if strings.Contains(prompt, forbidden) { - t.Fatalf("none prompt contains omitted content %q\n%s", forbidden, prompt) - } + testassert.Require(t, !strings.Contains(prompt, forbidden), "none prompt contains omitted content %q\n%s", forbidden, prompt) } - if count := strings.Count(prompt, agentPromptHeading); count != 1 { - t.Fatalf("agent prompt heading count = %d, want 1\n%s", count, prompt) + { + count := strings.Count(prompt, agentPromptHeading) + testassert.Require(t, count == 1, "agent prompt heading count = %d, want 1\n%s", count, prompt) } } @@ -174,12 +152,8 @@ func assertOrder(t *testing.T, text string, markers ...string) { prev := -1 for _, marker := range markers { idx := strings.Index(text, marker) - if idx < 0 { - t.Fatalf("missing order marker %q\n%s", marker, text) - } - if idx < prev { - t.Fatalf("marker %q at %d appeared before previous marker at %d\n%s", marker, idx, prev, text) - } + testassert.Require(t, idx >= 0, "missing order marker %q\n%s", marker, text) + testassert.Require(t, idx >= prev, "marker %q at %d appeared before previous marker at %d\n%s", marker, idx, prev, text) prev = idx } } diff --git a/internal/protocolgen/main.go b/internal/protocolgen/main.go index db101426..c92f857b 100644 --- a/internal/protocolgen/main.go +++ b/internal/protocolgen/main.go @@ -1,25 +1,31 @@ -// Command protocolgen generates the native invocation bindings and their -// test-only invocation contract from the protocol schema. It is a private -// build command: it runs during generation, imports the pinned schema loader -// and the standard library, and never enters a shipped Runtime, Harness, -// client or plugin path. +// Command protocolgen generates the SDK, the native invocation bindings and +// their test-only invocation contract from one protocol schema. It is a +// private build command: it imports the pinned SDK generator and schema loader +// and never enters a shipped Runtime, Harness, client or plugin path. // // go run ./internal/protocolgen \ // -schema protocol/openapi.yaml \ +// -config protocol/oapi-codegen.yaml \ +// -sdk-out protocol/protocol.gen.go \ // -out internal/clientops/operations.gen.go \ // -native-contract-out frontend/src/generated/native-invocations.json // -// Both artifacts are resolved, validated and formatted in memory before -// either destination is written, so an unsupported schema shape fails the -// command with both outputs untouched. +// The schema is loaded and validated once. The SDK generator owns every +// schema/reference/type/name derivation; this command only projects its +// resolved operation records into the native envelope limits before writing. +// All three artifacts are resolved and formatted in memory before any +// destination is written, so an unsupported schema shape or a failed SDK +// generation leaves all three untouched. package main import ( + "bytes" "context" "encoding/json" "flag" "fmt" "go/format" + "go/token" "os" "sort" "strconv" @@ -27,6 +33,8 @@ import ( "text/template" "github.com/getkin/kin-openapi/openapi3" + "github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen" + "gopkg.in/yaml.v3" ) // The three envelope member modes the schema declares for one operation. @@ -54,38 +62,46 @@ type queryMember struct { kind string } -// operation is one schema-declared native operation in its generated form. +// operation is one SDK-resolved native operation in its projected form. type operation struct { - id string - session string - field string - fieldType string - query string - body string - queryMembers []queryMember - status int + id string + method string + session string + sessionType string + field string + fieldType string + query string + queryRequired []string + queryMembers []queryMember + body string + status int } -// main parses the three explicit paths and runs the generator. +// main parses the explicit paths and runs the generator. func main() { schema := flag.String("schema", "", "path to the protocol OpenAPI schema") + config := flag.String("config", "", "path to the SDK generator configuration") + sdkOut := flag.String("sdk-out", "", "path of the generated SDK") out := flag.String("out", "", "path of the generated Go operation bindings") nativeContractOut := flag.String("native-contract-out", "", "path of the generated native invocation contract") flag.Parse() - if err := run(*schema, *out, *nativeContractOut); err != nil { + if err := run(*schema, *config, *sdkOut, *out, *nativeContractOut); err != nil { fmt.Fprintln(os.Stderr, "protocolgen:", err) os.Exit(1) } } -// run writes the two artifacts generated from one schema. A schema or shape -// failure writes neither destination; an ordinary destination I/O failure -// fails the command after the other destination may already exist. -func run(schemaPath, outPath, contractPath string) error { - source, contract, err := generateFiles(schemaPath) +// run writes the three artifacts generated from one schema. A schema, SDK or +// shape failure writes none of the destinations; an ordinary destination I/O +// failure fails the command after the earlier destinations may already exist. +func run(schemaPath, configPath, sdkPath, outPath, contractPath string) error { + sdk, source, contract, err := generateFiles(schemaPath, configPath) if err != nil { return err } + if err := os.WriteFile(sdkPath, sdk, 0o644); err != nil { + return fmt.Errorf("writing %s: %w", sdkPath, err) + } if err := os.WriteFile(outPath, source, 0o644); err != nil { return fmt.Errorf("writing %s: %w", outPath, err) } @@ -95,69 +111,92 @@ func run(schemaPath, outPath, contractPath string) error { return nil } -// generateFiles loads the schema and returns both artifacts, fully formatted, -// without touching either destination. -func generateFiles(schemaPath string) ([]byte, []byte, error) { +// loadConfiguration reads one SDK generator configuration with unknown members +// rejected, applies its defaults and validates it. +func loadConfiguration(path string) (codegen.Configuration, error) { + data, err := os.ReadFile(path) + if err != nil { + return codegen.Configuration{}, fmt.Errorf("reading %s: %w", path, err) + } + decoder := yaml.NewDecoder(bytes.NewReader(data)) + decoder.KnownFields(true) + var configuration codegen.Configuration + if err := decoder.Decode(&configuration); err != nil { + return codegen.Configuration{}, fmt.Errorf("decoding %s: %w", path, err) + } + configuration = configuration.UpdateDefaults() + if err := configuration.Validate(); err != nil { + return codegen.Configuration{}, fmt.Errorf("validating %s: %w", path, err) + } + return configuration, nil +} + +// generateFiles loads the schema, generates its SDK and projects the SDK's +// resolved operation records into both native artifacts. Nothing is written. +// Generate runs first because OperationDefinitions' schema/type mapping +// depends on the global state Generate initializes; the two calls are +// sequential over the same document. +func generateFiles(schemaPath, configPath string) ([]byte, []byte, []byte, error) { + configuration, err := loadConfiguration(configPath) + if err != nil { + return nil, nil, nil, err + } loader := openapi3.NewLoader() + // Source origins feed OperationDefinition.SpecOrder; without them the SDK + // producer would lose the schema's declaration order. + loader.IncludeOrigin = true doc, err := loader.LoadFromFile(schemaPath) if err != nil { - return nil, nil, fmt.Errorf("loading %s: %w", schemaPath, err) + return nil, nil, nil, fmt.Errorf("loading %s: %w", schemaPath, err) } if err := doc.Validate(context.Background()); err != nil { - return nil, nil, fmt.Errorf("validating %s: %w", schemaPath, err) + return nil, nil, nil, fmt.Errorf("validating %s: %w", schemaPath, err) + } + sdk, err := codegen.Generate(doc, configuration) + if err != nil { + return nil, nil, nil, fmt.Errorf("generating the SDK: %w", err) } - operations, err := collectOperations(doc) + definitions, err := codegen.OperationDefinitions(doc) if err != nil { - return nil, nil, err + return nil, nil, nil, fmt.Errorf("resolving the SDK operation records: %w", err) + } + operations, err := projectOperations(definitions) + if err != nil { + return nil, nil, nil, err } source, err := renderSource(operations) if err != nil { - return nil, nil, err + return nil, nil, nil, err } contract, err := renderContract(operations) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return source, contract, nil + return []byte(sdk), source, contract, nil } -// collectOperations derives every native operation from the validated schema: -// its addressing, its declared query members, its request body and its one -// success status. The one declared event stream is excluded — exactly one must -// exist — and any shape the envelope cannot address fails the command. Duplicate -// operation IDs are already rejected by the mandatory schema validation before -// this pass runs, so no second identity check is kept here. -func collectOperations(doc *openapi3.T) ([]operation, error) { - paths := doc.Paths.Map() - var ( - operations []operation - streamed []string - ) - for _, pathKey := range sortedKeys(paths) { - item := paths[pathKey] - methods := item.Operations() - for _, method := range sortedKeys(methods) { - declared := methods[method] - if !generatedName(declared.OperationID) { - return nil, fmt.Errorf("%s %s: operationId %q is not a generated Go name", strings.ToUpper(method), pathKey, declared.OperationID) - } - status, stream, err := successResponse(declared) - if err != nil { - return nil, fmt.Errorf("operation %s: %w", declared.OperationID, err) - } - if stream { - streamed = append(streamed, declared.OperationID) - continue - } - op, err := nativeOperation(doc, item, declared, status) - if err != nil { - return nil, fmt.Errorf("operation %s: %w", declared.OperationID, err) - } - operations = append(operations, op) +// projectOperations projects every SDK-resolved operation into the native +// envelope: stream operations are classified first and excluded whole, then +// the envelope's addressing limits are checked against the SDK records. +func projectOperations(definitions []codegen.OperationDefinition) ([]operation, error) { + var operations []operation + for _, definition := range definitions { + name := definition.SpecOperationId + if name == "" { + return nil, fmt.Errorf("operation %s %s has no source operationId", definition.Method, definition.Path) } - } - if len(streamed) != 1 { - return nil, fmt.Errorf("declares %d event streams, want exactly one", len(streamed)) + status, stream, err := successResponse(definition.Responses) + if err != nil { + return nil, fmt.Errorf("operation %s: %w", name, err) + } + if stream { + continue + } + op, err := projectOperation(definition, name, status) + if err != nil { + return nil, fmt.Errorf("operation %s: %w", name, err) + } + operations = append(operations, op) } sort.Slice(operations, func(i, j int) bool { return operations[i].id < operations[j].id }) return operations, nil @@ -169,10 +208,10 @@ func collectOperations(doc *openapi3.T) ([]operation, error) { // content set that is not exactly one of the two declared media types, and a // non-204 success without content are all refused; every non-success // alternative, including the default error response, is ignored. -func successResponse(declared *openapi3.Operation) (int, bool, error) { - responses := declared.Responses.Map() +func successResponse(responses []codegen.ResponseDefinition) (int, bool, error) { var statuses []string - for key := range responses { + for _, response := range responses { + key := response.StatusCode if !strings.HasPrefix(key, "2") { continue } @@ -181,38 +220,46 @@ func successResponse(declared *openapi3.Operation) (int, bool, error) { } statuses = append(statuses, key) } - sort.Strings(statuses) if len(statuses) != 1 { return 0, false, fmt.Errorf("declares %d success statuses, want exactly one", len(statuses)) } - status, err := strconv.Atoi(statuses[0]) - if err != nil { - return 0, false, fmt.Errorf("success status %q is not a number", statuses[0]) - } - response := responses[statuses[0]] - if response.Value == nil { - return 0, false, fmt.Errorf("success status %d is unresolved", status) + status, _ := strconv.Atoi(statuses[0]) + var contents []codegen.ResponseContentDefinition + for _, response := range responses { + if response.StatusCode == statuses[0] { + contents = response.Contents + break + } } - content := response.Value.Content // A 204 success is the empty success the retained response owner reads as // no body at all: it must declare no content, before any media // classification. if status == noContentStatus { - if len(content) != 0 { - return 0, false, fmt.Errorf("success status %d declares media types %v, want no content", status, sortedKeys(content)) + if len(contents) != 0 { + return 0, false, fmt.Errorf("success status %d declares media types %v, want no content", status, contentTypes(contents)) } return status, false, nil } switch { - case len(content) == 1 && content[jsonMediaType] != nil: + case len(contents) == 1 && contents[0].ContentType == jsonMediaType: return status, false, nil - case len(content) == 1 && content[sseMediaType] != nil: + case len(contents) == 1 && contents[0].ContentType == sseMediaType: return status, true, nil - case len(content) == 0: + case len(contents) == 0: return 0, false, fmt.Errorf("success status %d declares no content", status) default: - return 0, false, fmt.Errorf("success status %d declares media types %v, want exactly one of %s or %s", status, sortedKeys(content), jsonMediaType, sseMediaType) + return 0, false, fmt.Errorf("success status %d declares media types %v, want exactly one of %s or %s", status, contentTypes(contents), jsonMediaType, sseMediaType) + } +} + +// contentTypes returns one response's declared media types in sorted order. +func contentTypes(contents []codegen.ResponseContentDefinition) []string { + types := make([]string, 0, len(contents)) + for _, content := range contents { + types = append(types, content.ContentType) } + sort.Strings(types) + return types } // isDigits reports whether every byte of one string is an ASCII decimal digit. @@ -225,286 +272,242 @@ func isDigits(value string) bool { return true } -// nativeOperation derives one native operation's addressing from its declared -// path parameters, query parameters and request body. -func nativeOperation(doc *openapi3.T, item *openapi3.PathItem, declared *openapi3.Operation, status int) (operation, error) { +// projectOperation derives one native operation's addressing from its SDK +// combined path/query parameter records and its resolved request body. A +// referenced path-parameter schema binds the envelope's field member; an +// inline string parameter named id binds session_id. Both must resolve to the +// string type. All query parameters must be string or boolean primitives, and +// a body must be exactly one required application/json object. +func projectOperation(definition codegen.OperationDefinition, name string, status int) (operation, error) { op := operation{ - id: declared.OperationID, + id: name, + method: definition.OperationId, status: status, session: modeForbidden, field: modeForbidden, query: modeForbidden, body: modeForbidden, } - var pathParameters int - for _, parameter := range append(append(openapi3.Parameters{}, item.Parameters...), declared.Parameters...) { - value := parameter.Value - if value == nil { - return operation{}, fmt.Errorf("parameter is unresolved") + if len(definition.HeaderParams) != 0 { + return operation{}, fmt.Errorf("declares %d header parameters, want none", len(definition.HeaderParams)) + } + if len(definition.CookieParams) != 0 { + return operation{}, fmt.Errorf("declares %d cookie parameters, want none", len(definition.CookieParams)) + } + switch len(definition.PathParams) { + case 0: + case 1: + parameter := definition.PathParams[0] + schema := parameter.Schema.OAPISchema + if schema == nil || schema.Type == nil || !schema.Type.Is("string") { + return operation{}, fmt.Errorf("path parameter %q is not the string type", parameter.ParamName) } - switch value.In { - case openapi3.ParameterInPath: - pathParameters++ - if pathParameters > 1 { - return operation{}, fmt.Errorf("declares %d path parameters, want at most one", pathParameters) - } - if value.Schema == nil || value.Schema.Value == nil { - return operation{}, fmt.Errorf("path parameter %q has no schema", value.Name) - } - if ref := value.Schema.Ref; ref != "" { - name, err := enumTypeName(doc, ref) - if err != nil { - return operation{}, fmt.Errorf("path parameter %q: %w", value.Name, err) - } - op.field = modeRequired - op.fieldType = name - continue - } - if value.Name != "id" || !isStringSchema(value.Schema.Value) { - return operation{}, fmt.Errorf("path parameter %q has no supported addressing", value.Name) - } + emitted, err := pathType(parameter.Schema.TypeDecl()) + if err != nil { + return operation{}, fmt.Errorf("path parameter %q: %w", parameter.ParamName, err) + } + if parameter.Spec != nil && parameter.Spec.Schema != nil && parameter.Spec.Schema.Ref != "" { + op.field = modeRequired + op.fieldType = emitted + } else if parameter.ParamName == "id" { op.session = modeRequired - case openapi3.ParameterInQuery: - member, err := queryParameter(value) - if err != nil { - return operation{}, err - } - for _, existing := range op.queryMembers { - if existing.name == member.name { - return operation{}, fmt.Errorf("query parameter %q is declared twice", member.name) - } - } - op.queryMembers = append(op.queryMembers, member) + op.sessionType = emitted + } else { + return operation{}, fmt.Errorf("path parameter %q has no supported addressing", parameter.ParamName) + } + default: + return operation{}, fmt.Errorf("declares %d path parameters, want at most one", len(definition.PathParams)) + } + for _, parameter := range definition.QueryParams { + schema := parameter.Schema.OAPISchema + if schema == nil || schema.Type == nil { + return operation{}, fmt.Errorf("query parameter %q has an unsupported primitive type", parameter.ParamName) + } + member := queryMember{name: parameter.ParamName, required: parameter.Required} + switch { + case schema.Type.Is("string"): + member.kind = "string" + case schema.Type.Is("boolean"): + member.kind = "boolean" default: - return operation{}, fmt.Errorf("parameter %q is declared in unsupported location %q", value.Name, value.In) + return operation{}, fmt.Errorf("query parameter %q has an unsupported primitive type", parameter.ParamName) } + op.queryMembers = append(op.queryMembers, member) } - op.query = modeForbidden - if len(op.queryMembers) > 0 { + if len(op.queryMembers) != 0 { op.query = modeOptional for _, member := range op.queryMembers { if member.required { op.query = modeRequired + op.queryRequired = append(op.queryRequired, member.name) } } } - if declared.RequestBody != nil && declared.RequestBody.Value != nil { - content := declared.RequestBody.Value.Content - if len(content) != 1 || content[jsonMediaType] == nil { - return operation{}, fmt.Errorf("request body declares media types %v, want exactly %s", sortedKeys(content), jsonMediaType) + if len(definition.Bodies) != 0 { + if len(definition.Bodies) != 1 || definition.Bodies[0].ContentType != jsonMediaType { + types := make([]string, 0, len(definition.Bodies)) + for _, body := range definition.Bodies { + types = append(types, body.ContentType) + } + sort.Strings(types) + return operation{}, fmt.Errorf("request body declares media types %v, want exactly %s", types, jsonMediaType) } - schema := content[jsonMediaType].Schema - if schema == nil || schema.Value == nil { - return operation{}, fmt.Errorf("request body schema is unresolved") + body := definition.Bodies[0] + if !body.Required { + return operation{}, fmt.Errorf("request body is optional, want required") } - if schema.Value.Type == nil || !schema.Value.Type.Is("object") { + schema := body.Schema.OAPISchema + if schema == nil || schema.Type == nil || !schema.Type.Is("object") { return operation{}, fmt.Errorf("request body schema is not one explicit object") } - if !declared.RequestBody.Value.Required { - return operation{}, fmt.Errorf("request body is optional, want required") - } op.body = modeRequired } return op, nil } -// enumTypeName resolves one referenced path-parameter schema to the generated -// protocol type the binder casts the envelope's field member to: an internal -// component reference to a resolved string schema with a nonempty all-string -// enum and a component name the generated client uses unchanged. -func enumTypeName(doc *openapi3.T, ref string) (string, error) { - const prefix = "#/components/schemas/" - if !strings.HasPrefix(ref, prefix) { - return "", fmt.Errorf("reference %q is not an internal #/components/schemas/ reference", ref) - } - name := strings.TrimPrefix(ref, prefix) - if !generatedName(name) { - return "", fmt.Errorf("referenced component name %q is not a generated Go name", name) - } - component := doc.Components.Schemas[name] - if component == nil || component.Value == nil { - return "", fmt.Errorf("referenced component %q is unresolved", name) - } - schema := component.Value - if !isStringSchema(schema) { - return "", fmt.Errorf("referenced component %q is not the string type", name) - } - if len(schema.Enum) == 0 { - return "", fmt.Errorf("referenced component %q declares no enum values", name) - } - for _, value := range schema.Enum { - if _, ok := value.(string); !ok { - return "", fmt.Errorf("referenced component %q declares the non-string enum value %v", name, value) - } +// pathType maps one SDK path-parameter type declaration to the Go type the +// generated call binds: the builtin string or an exported package-local +// identifier qualified with the protocol package. Any other type expression is +// an unsupported native shape. +func pathType(declaration string) (string, error) { + if declaration == "string" { + return "string", nil } - return name, nil -} - -// generatedName reports whether one schema identifier is already the exact -// generated Go name the pinned generator uses: nonempty, an ASCII uppercase -// initial and only ASCII letters and digits after it. An identifier that would -// need normalization — a lowercase initial, an underscore or other separator, -// a leading digit — is refused explicitly instead of being silently mapped or -// left to fail as generated Go. -func generatedName(name string) bool { - if name == "" || name[0] < 'A' || name[0] > 'Z' { - return false - } - for index := 1; index < len(name); index++ { - character := name[index] - switch { - case character >= 'A' && character <= 'Z', - character >= 'a' && character <= 'z', - character >= '0' && character <= '9': - default: - return false - } + if token.IsIdentifier(declaration) && token.IsExported(declaration) { + return "protocol." + declaration, nil } - return true -} - -// queryParameter resolves one declared query parameter's primitive type: only -// the string and boolean primitives the generated Params types bind are -// supported. -func queryParameter(value *openapi3.Parameter) (queryMember, error) { - if value.Schema == nil || value.Schema.Value == nil { - return queryMember{}, fmt.Errorf("query parameter %q has no schema", value.Name) - } - schema := value.Schema.Value - switch { - case isStringSchema(schema): - return queryMember{name: value.Name, required: value.Required, kind: "string"}, nil - case schema.Type != nil && schema.Type.Is("boolean"): - return queryMember{name: value.Name, required: value.Required, kind: "boolean"}, nil - default: - return queryMember{}, fmt.Errorf("query parameter %q has an unsupported primitive type", value.Name) - } -} - -// isStringSchema reports whether one schema is the string primitive. -func isStringSchema(schema *openapi3.Schema) bool { - return schema.Type != nil && schema.Type.Is("string") + return "", fmt.Errorf("type %q is not a supported native path type", declaration) } -// sortedKeys returns one string-keyed map's keys in sorted order, so every -// derived artifact is deterministic. -func sortedKeys[V any](values map[string]V) []string { - keys := make([]string, 0, len(values)) - for key := range values { - keys = append(keys, key) - } - sort.Strings(keys) - return keys -} - -// sourceTemplate renders the generated Go bindings. go/format normalizes the -// result, so the template only has to emit valid, complete Go. +// sourceTemplate renders the generated native operation table. go/format +// normalizes the result, so the template only has to emit valid, complete Go. const sourceTemplate = `// Code generated by internal/protocolgen from protocol/openapi.yaml; DO NOT EDIT. // -// The native operation table and its dispatch are derived from the schema: -// operationNames is the sorted set of declared native operations and -// invokeGenerated binds each one's declared addressing before calling the -// matching generated client method. +// The native operation table is the SDK-resolved projection of the schema: each +// descriptor carries one operation's envelope member rules, its one numeric +// success status and the generated client call for its bound envelope. package clientops +{{- if .Imports}} import ( {{- if .NeedsBytes}} "bytes" {{- end}} "context" - "encoding/json" "net/http" - "github.com/MMinasyan/lightcode/client" -{{- if .NeedsProtocol}} "github.com/MMinasyan/lightcode/protocol" -{{- end}} ) - -// operationNames is the schema's native operation set in sorted order: every -// declared operation whose success response is not the event stream. It is the -// sole authority for the initialize advertisement and for Call's own admission. -var operationNames = []string{ -{{- range .Names}} - {{printf "%q" .}}, {{- end}} -} -// invokeGenerated binds one invocation envelope to the generated client -// method the named operation declares and returns that call's raw response -// beside the operation's declared success status. A local binding failure or a -// failure to obtain an HTTP response returns a nil response and the zero -// status; the caller owns response interpretation. -func invokeGenerated(ctx context.Context, c *client.Client, operation string, params json.RawMessage) (*http.Response, int, error) { - switch operation { -{{- range .Arms}} -{{template "arm" .}} +// operationDescriptors is the schema's native operation set keyed by its source +// operationId. Each call binds the operation's generated path and query +// arguments from the already-bound envelope and performs exactly one generated +// SDK request; the shared invoker in bind.go applies the envelope rules and the +// one response owner interprets the result. +var operationDescriptors = map[string]descriptor{ +{{- range .Operations}} + {{printf "%q" .ID}}: { +{{- if .Session}} + session: memberRequired, {{- end}} - default: - return nil, 0, invalidParams("unknown operation %q", operation) - } -} -{{define "arm"}} case {{printf "%q" .ID}}: -{{- if .NeedEnv}} - env, err := bindEnvelope({{.Envelope}}) - if err != nil { - return nil, 0, err - } -{{- else}} - if _, err := bindEnvelope({{.Envelope}}); err != nil { - return nil, 0, err - } +{{- if .Field}} + field: memberRequired, +{{- end}} +{{- if .Query}} + query: member{{if .QueryOptional}}Optional{{else}}Required{{end}}, +{{- end}} +{{- if .Body}} + body: memberRequired, {{- end}} -{{- if .QueryType}} - var query protocol.{{.QueryType}} + status: {{.Status}}, + call: func(ctx context.Context, c protocol.ClientInterface, env envelope) (*http.Response, error) { +{{- if .Session}} + var pathValue {{.SessionType}} + if err := bindPath(env.session, &pathValue); err != nil { + return nil, err + } +{{- end}} +{{- if .Field}} + var pathValue {{.FieldType}} + if err := bindPath(env.field, &pathValue); err != nil { + return nil, err + } +{{- end}} +{{- if .Query}} + var query protocol.{{.QueryType}} {{- if .QueryOptional}} - if env.query != nil { - if err := bindQuery(env.query, nil, &query); err != nil { - return nil, 0, err + if env.query != nil { + if err := bindQuery(env.query, nil, &query); err != nil { + return nil, err + } } - } {{- else}} - if err := bindQuery(env.query, {{.QueryRequired}}, &query); err != nil { - return nil, 0, err - } + if err := bindQuery(env.query, {{.QueryRequired}}, &query); err != nil { + return nil, err + } +{{- end}} {{- end}} + return {{.Call}} + }, + }, {{- end}} - response, callErr := {{.Call}} - return response, {{.Expected}}, callErr -{{end}}` +} +` -// armView is one generated dispatch arm. +// armView is one generated descriptor literal. type armView struct { ID string - Envelope string - NeedEnv bool - QueryType string - QueryRequired string + Session bool + SessionType string + Field bool + FieldType string + Query bool QueryOptional bool + QueryRequired string + QueryType string + Body bool Call string - Expected string + Status int } // sourceData is the whole generated file's template data. type sourceData struct { - Names []string - Arms []armView - NeedsBytes bool - NeedsProtocol bool + Operations []armView + Imports bool + NeedsBytes bool } -// renderSource renders and formats the generated Go bindings in memory. +// renderSource renders and formats the generated native bindings in memory. func renderSource(operations []operation) ([]byte, error) { - data := sourceData{} + data := sourceData{Imports: len(operations) != 0} for _, op := range operations { - data.Names = append(data.Names, op.id) - data.Arms = append(data.Arms, arm(op)) - if op.body != modeForbidden { - data.NeedsBytes = true + view := armView{ + ID: op.id, + Session: op.session != modeForbidden, + SessionType: op.sessionType, + Field: op.field != modeForbidden, + FieldType: op.fieldType, + Query: op.query != modeForbidden, + QueryType: op.method + "Params", + Body: op.body != modeForbidden, + Status: op.status, + Call: callExpression(op), } - if op.session != modeForbidden || op.field != modeForbidden || op.query != modeForbidden { - data.NeedsProtocol = true + view.QueryOptional = op.query == modeOptional + if len(op.queryRequired) != 0 { + quoted := make([]string, 0, len(op.queryRequired)) + for _, name := range op.queryRequired { + quoted = append(quoted, strconv.Quote(name)) + } + view.QueryRequired = "[]string{" + strings.Join(quoted, ", ") + "}" + } else { + view.QueryRequired = "nil" } + if view.Body { + data.NeedsBytes = true + } + data.Operations = append(data.Operations, view) } parsed, err := template.New("source").Parse(sourceTemplate) if err != nil { @@ -521,63 +524,23 @@ func renderSource(operations []operation) ([]byte, error) { return formatted, nil } -// arm derives one dispatch arm: the envelope's declared member rules, the -// query binding and the exact generated client call for the operation. The -// declared success status is emitted as the schema's own number, so any single -// numeric 2xx is supported without a named standard-library constant. -func arm(op operation) armView { - view := armView{ - ID: op.id, - Envelope: fmt.Sprintf("params, %s, %s, %s, %s", - goMode(op.session), goMode(op.field), goMode(op.query), goMode(op.body)), - Expected: strconv.Itoa(op.status), - } - view.NeedEnv = op.session != modeForbidden || op.field != modeForbidden || op.query != modeForbidden || op.body != modeForbidden - if op.query != modeForbidden { - view.QueryType = op.id + "Params" - view.QueryOptional = op.query == modeOptional - var required []string - for _, member := range op.queryMembers { - if member.required { - required = append(required, strconv.Quote(member.name)) - } - } - if len(required) == 0 { - view.QueryRequired = "nil" - } else { - view.QueryRequired = "[]string{" + strings.Join(required, ", ") + "}" - } - } +// callExpression renders one descriptor's generated SDK call: the bound path +// value, the bound query Params and the raw body reader in the SDK's declared +// argument order. +func callExpression(op operation) string { arguments := []string{"ctx"} - if op.session != modeForbidden { - arguments = append(arguments, "protocol.SessionID(env.session)") - } - if op.fieldType != "" { - arguments = append(arguments, "protocol."+op.fieldType+"(env.field)") + if op.session != modeForbidden || op.field != modeForbidden { + arguments = append(arguments, "pathValue") } if op.query != modeForbidden { arguments = append(arguments, "&query") } - name := "c." + op.id + name := "c." + op.method if op.body != modeForbidden { name += "WithBody" arguments = append(arguments, "contentTypeJSON", "bytes.NewReader(env.body)") } - view.Call = name + "(" + strings.Join(arguments, ", ") + ")" - return view -} - -// goMode maps one declared envelope mode to the handwritten member rule -// constant the generated bindings use. -func goMode(mode string) string { - switch mode { - case modeRequired: - return "memberRequired" - case modeOptional: - return "memberOptional" - default: - return "memberForbidden" - } + return name + "(" + strings.Join(arguments, ", ") + ")" } // contractMember is one query member's published contract. @@ -596,8 +559,8 @@ type contractOperation struct { } // renderContract renders the test-only native invocation contract in memory: -// one entry per native operation, keyed by operation ID, with sorted keys and -// no stream entry. +// one entry per native operation, keyed by native operation ID, with sorted +// keys and no stream entry. func renderContract(operations []operation) ([]byte, error) { doc := make(map[string]contractOperation, len(operations)) for _, op := range operations { diff --git a/internal/protocolgen/main_test.go b/internal/protocolgen/main_test.go index 1e349462..e96e8b84 100644 --- a/internal/protocolgen/main_test.go +++ b/internal/protocolgen/main_test.go @@ -2,16 +2,21 @@ package main import ( "bytes" + "encoding/json" "os" + "os/exec" "path/filepath" + "reflect" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) -// The fixture schema is a minimal valid protocol: one JSON operation, one -// optional-query operation and the one declared event stream. Each case below -// composes the paths it needs, so a supported addition and every unsupported -// shape are exercised against a schema that would otherwise generate. +// The fixture schemas are one valid Health/Events baseline with local +// variations. Each case below composes only the paths it needs, so every +// unsupported shape is exercised against a schema that would otherwise +// generate. const ( fixtureHead = `openapi: "3.0.3" info: @@ -35,11 +40,22 @@ paths: operationId: GetEvents responses: "200": - description: the one event stream + description: the event stream content: text/event-stream: schema: type: object +` + baselineSchema = fixtureHead + healthPath + eventsPath + + // fixtureConfig is the minimal SDK configuration the fixture module + // compiles against: the generated protocol package and its client. + fixtureConfig = `package: protocol +generate: + models: true + client: true +compatibility: + preserve-original-operation-id-casing-in-embedded-spec: true ` ) @@ -47,306 +63,67 @@ paths: func writeFixture(t *testing.T, text string) string { t.Helper() path := filepath.Join(t.TempDir(), "openapi.yaml") - if err := os.WriteFile(path, []byte(text), 0o644); err != nil { - t.Fatalf("writing fixture schema: %v", err) - } + testassert.NoError(t, os.WriteFile(path, []byte(text), 0o644)) return path } -// generateFixture runs the generator over one schema text and returns both -// artifacts. -func generateFixture(t *testing.T, text string) (string, string) { +// writeConfig writes the fixture SDK configuration and returns its path. +func writeConfig(t *testing.T) string { t.Helper() - source, contract, err := generateFiles(writeFixture(t, text)) - if err != nil { - t.Fatalf("generating from the fixture schema: %v", err) - } - return string(source), string(contract) + path := filepath.Join(t.TempDir(), "oapi-codegen.yaml") + testassert.NoError(t, os.WriteFile(path, []byte(fixtureConfig), 0o644)) + return path } -// TestSupportedAdditionGenerates proves a supported schema addition generates -// into both artifacts deterministically: the added operation appears in the -// sorted names, in its dispatch arm and in the invocation contract, and two -// runs over the same schema are byte-identical. -func TestSupportedAdditionGenerates(t *testing.T) { - const addition = ` /v1/sessions/{id}/extra: - post: - operationId: Extra - parameters: - - name: id - in: path - required: true - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - type: object - responses: - "201": - description: the added operation - content: - application/json: - schema: - type: object -` - schema := fixtureHead + healthPath + eventsPath + addition - source, contract := generateFixture(t, schema) - if !strings.Contains(source, `"Extra",`) { - t.Errorf("generated bindings do not advertise the added operation:\n%s", source) - } - if !strings.Contains(source, `case "Extra":`) { - t.Errorf("generated bindings have no dispatch arm for the added operation") - } - if !strings.Contains(source, "c.ExtraWithBody(ctx, protocol.SessionID(env.session), contentTypeJSON, bytes.NewReader(env.body))") { - t.Errorf("generated arm does not call the added operation's generated method") - } - if !strings.Contains(source, "return response, 201, callErr") { - t.Errorf("generated arm does not carry the added operation's declared success status") - } - if !strings.Contains(contract, `"Extra": {`) { - t.Errorf("invocation contract has no entry for the added operation:\n%s", contract) - } - if !strings.Contains(contract, `"body": "required"`) || !strings.Contains(contract, `"session_id": "required"`) { - t.Errorf("invocation contract does not carry the added operation's declared modes") - } - if strings.Contains(contract, "GetEvents") { - t.Errorf("invocation contract carries the event stream:\n%s", contract) - } - againSource, againContract := generateFixture(t, schema) - if source != againSource || contract != againContract { - t.Error("two runs over the same schema produced different artifacts") - } +// generateFixture runs the generator over one schema text and returns the SDK, +// native bindings and invocation contract. +func generateFixture(t *testing.T, text string) (string, string, string) { + t.Helper() + sdk, source, contract, err := generateFiles(writeFixture(t, text), writeConfig(t)) + testassert.NoError(t, err) + return string(sdk), string(source), string(contract) } -// TestOptionalQueryMembersGenerate proves the optional query envelope modes -// derive from the schema: a query mixing required and optional members binds -// every member and requires the required ones, and an optional-only query -// binds only when present through the nil-required guard. The contract -// publishes both forms and neither operation declares a request body. -func TestOptionalQueryMembersGenerate(t *testing.T) { - const mixedQuery = ` /v1/optional: - post: - operationId: Optional - parameters: - - name: cursor - in: query - required: false - schema: - type: string - - name: all - in: query - required: true - schema: - type: boolean - responses: - "200": - description: the mixed query operation - content: - application/json: - schema: - type: object -` - const optionalQuery = ` /v1/sessions/{id}/history: +// TestStreamCardinality proves stream operations are excluded whole without +// imposing any cardinality: zero streams and multiple streams both generate the +// remaining native operations and publish no stream entry. +func TestStreamCardinality(t *testing.T) { + const secondEvents = ` /v1/other-events: get: - operationId: GetSessionHistory - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: cursor - in: query - required: false - schema: - type: string + operationId: OtherEvents responses: "200": - description: the optional query operation + description: a second event stream content: - application/json: + text/event-stream: schema: type: object ` for _, tc := range []struct { - name string - path string - source []string - contract []string + name string + schema string }{ - { - name: "required and optional query members without a body", - path: mixedQuery, - source: []string{ - `bindEnvelope(params, memberForbidden, memberForbidden, memberRequired, memberForbidden)`, - `bindQuery(env.query, []string{"all"}, &query)`, - `c.Optional(ctx, &query)`, - }, - contract: []string{ - `"Optional": { - "session_id": "forbidden", - "field": "forbidden", - "query": "required", - "body": "forbidden"`, - `"cursor": { - "required": false, - "type": "string" - }`, - `"all": { - "required": true, - "type": "boolean" - }`, - }, - }, - { - name: "an optional-only query binds only when present", - path: optionalQuery, - source: []string{ - `bindEnvelope(params, memberRequired, memberForbidden, memberOptional, memberForbidden)`, - `if env.query != nil {`, - `bindQuery(env.query, nil, &query)`, - `c.GetSessionHistory(ctx, protocol.SessionID(env.session), &query)`, - }, - contract: []string{ - `"GetSessionHistory": { - "session_id": "required", - "field": "forbidden", - "query": "optional", - "body": "forbidden"`, - `"cursor": { - "required": false, - "type": "string" - }`, - }, - }, + {"zero streams", fixtureHead + healthPath}, + {"multiple streams", baselineSchema + secondEvents}, } { t.Run(tc.name, func(t *testing.T) { - source, contract := generateFixture(t, fixtureHead+healthPath+eventsPath+tc.path) - for _, want := range tc.source { - if !strings.Contains(source, want) { - t.Errorf("generated bindings are missing %q:\n%s", want, source) - } - } - for _, want := range tc.contract { - if !strings.Contains(contract, want) { - t.Errorf("invocation contract is missing %q:\n%s", want, contract) - } + _, source, contract := generateFixture(t, tc.schema) + testassert.Check(t, strings.Contains(source, `"GetHealth": {`), "generated bindings lost the non-stream operation:\n%s", source) + testassert.Check(t, strings.Contains(contract, `"GetHealth": {`), "invocation contract lost the non-stream operation:\n%s", contract) + for _, stream := range []string{"GetEvents", "OtherEvents"} { + testassert.Check(t, !strings.Contains(source, `"`+stream+`": {`), "generated bindings carry stream operation %q:\n%s", stream, source) + testassert.Check(t, !strings.Contains(contract, `"`+stream+`"`), "invocation contract carries stream operation %q:\n%s", stream, contract) } }) } } -// TestSupportedShapesGenerate pins the accepted shapes the extraction pass -// must keep: a referenced string-enum field path emits its generated protocol -// type and call with the declared metadata, and a single numeric 2xx — -// including one with no named standard-library constant — is emitted as the -// schema's own number, beside the unchanged contentless 204 arm. -func TestSupportedShapesGenerate(t *testing.T) { - const fieldsPath = ` /v1/fields/{field}: - delete: - operationId: ResetField - parameters: - - name: field - in: path - required: true - schema: - $ref: "#/components/schemas/ProviderField" - - name: provider_id - in: query - required: true - schema: - type: string - responses: - "200": - description: the reset post-state - content: - application/json: - schema: - type: object -` - const acceptedPath = ` /v1/accepted: - post: - operationId: Accepted203 - responses: - "203": - description: an unlisted numeric success - content: - application/json: - schema: - type: object -` - const extendedPath = ` /v1/extended: - get: - operationId: Extended210 - responses: - "210": - description: another unlisted numeric success - content: - application/json: - schema: - type: object -` - const removedPath = ` /v1/removed: - delete: - operationId: Removed204 - responses: - "204": - description: a contentless success -` - const components = `components: - schemas: - ProviderField: - type: string - enum: - - name - - hidden -` - source, contract := generateFixture(t, fixtureHead+healthPath+eventsPath+fieldsPath+acceptedPath+extendedPath+removedPath+components) - for _, want := range []string{ - `case "ResetField":`, - `bindEnvelope(params, memberForbidden, memberRequired, memberRequired, memberForbidden)`, - `bindQuery(env.query, []string{"provider_id"}, &query)`, - `c.ResetField(ctx, protocol.ProviderField(env.field), &query)`, - `return response, 203, callErr`, - `return response, 210, callErr`, - `return response, 204, callErr`, - `"Accepted203",`, - `"Extended210",`, - `"Removed204",`, - `"ResetField",`, - } { - if !strings.Contains(source, want) { - t.Errorf("generated bindings are missing %q:\n%s", want, source) - } - } - if strings.Contains(source, "http.Status") { - t.Errorf("generated bindings carry a named status constant instead of the schema's number:\n%s", source) - } - for _, want := range []string{ - `"ResetField": {`, - `"field": "required"`, - `"Accepted203": {`, - `"Extended210": {`, - `"Removed204": {`, - `"provider_id": { - "required": true, - "type": "string" - }`, - } { - if !strings.Contains(contract, want) { - t.Errorf("invocation contract is missing %q:\n%s", want, contract) - } - } -} - -// TestUnsupportedShapesWriteNothing proves every unsupported schema shape -// fails the command before either destination is touched: both pre-existing -// artifacts keep their exact bytes. +// TestUnsupportedShapesWriteNothing proves every unsupported schema shape and +// every failed SDK generation fails the command before any of the three +// destinations is touched: all three pre-existing artifacts keep their exact +// bytes. func TestUnsupportedShapesWriteNothing(t *testing.T) { - base := fixtureHead + healthPath + eventsPath + base := baselineSchema cases := []struct { name string schema string @@ -437,24 +214,6 @@ func TestUnsupportedShapesWriteNothing(t *testing.T) { text/event-stream: schema: type: object -`, - }, - { - name: "no event stream", - schema: fixtureHead + healthPath, - }, - { - name: "a second event stream", - schema: base + ` /v1/other: - get: - operationId: Other - responses: - "200": - description: a second stream - content: - text/event-stream: - schema: - type: object `, }, { @@ -520,6 +279,46 @@ func TestUnsupportedShapesWriteNothing(t *testing.T) { application/json: schema: type: object +`, + }, + { + name: "a header parameter", + schema: base + ` /v1/other: + get: + operationId: Other + parameters: + - name: X-Trace + in: header + required: false + schema: + type: string + responses: + "200": + description: a header argument + content: + application/json: + schema: + type: object +`, + }, + { + name: "a cookie parameter", + schema: base + ` /v1/other: + get: + operationId: Other + parameters: + - name: session + in: cookie + required: false + schema: + type: string + responses: + "200": + description: a cookie argument + content: + application/json: + schema: + type: object `, }, { @@ -683,7 +482,7 @@ components: `, }, { - name: "a referenced non-enum string field path", + name: "a referenced external field path", schema: base + ` /v1/fields/{field}: delete: operationId: ResetField @@ -692,7 +491,7 @@ components: in: path required: true schema: - $ref: "#/components/schemas/FieldName" + $ref: "https://example.invalid/field.yaml#/FieldName" responses: "200": description: the reset post-state @@ -700,53 +499,45 @@ components: application/json: schema: type: object -components: - schemas: - FieldName: - type: string `, }, { - name: "a referenced mixed-enum field path", - schema: base + ` /v1/fields/{field}: - delete: - operationId: ResetField + name: "a formatted path type outside the admitted set", + schema: base + ` /v1/sessions/{id}/extra: + get: + operationId: Extra parameters: - - name: field + - name: id in: path required: true schema: - $ref: "#/components/schemas/FieldName" + type: string + x-go-type: "[]string" responses: "200": - description: the reset post-state + description: an unsupported formatted path type content: application/json: schema: type: object -components: - schemas: - FieldName: - type: string - enum: - - name - - 1 `, }, { - name: "a referenced external field path", - schema: base + ` /v1/fields/{field}: - delete: - operationId: ResetField - parameters: - - name: field - in: path - required: true - schema: - $ref: "https://example.invalid/field.yaml#/FieldName" + name: "an array request body", + schema: base + ` /v1/other: + post: + operationId: Other + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: string responses: "200": - description: the reset post-state + description: an array body content: application/json: schema: @@ -754,13 +545,19 @@ components: `, }, { - name: "a noncanonical lowercase operation name", + name: "a string request body", schema: base + ` /v1/other: - get: - operationId: get_extra + post: + operationId: Other + requestBody: + required: true + content: + application/json: + schema: + type: string responses: "200": - description: a normalized name + description: a string body content: application/json: schema: @@ -768,13 +565,21 @@ components: `, }, { - name: "a noncanonical separated operation name", + name: "an untyped request body", schema: base + ` /v1/other: - get: - operationId: Get_extra + post: + operationId: Other + requestBody: + required: true + content: + application/json: + schema: + properties: + name: + type: string responses: "200": - description: a normalized name + description: an untyped body content: application/json: schema: @@ -782,13 +587,22 @@ components: `, }, { - name: "a leading-digit operation name", + name: "a two-media request body", schema: base + ` /v1/other: - get: - operationId: 9Lives + post: + operationId: Other + requestBody: + required: true + content: + application/json: + schema: + type: object + text/plain: + schema: + type: string responses: "200": - description: a normalized name + description: a two-media body content: application/json: schema: @@ -796,148 +610,581 @@ components: `, }, { - name: "a noncanonical referenced type name", - schema: base + ` /v1/fields/{field}: + name: "a failed SDK generation", + schema: base + ` /v1/other: + post: + operationId: Other + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + 'bad"name': + type: string + responses: + "200": + description: a body the SDK cannot emit + content: + application/json: + schema: + type: object +`, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + schemaPath := filepath.Join(dir, "openapi.yaml") + testassert.NoError(t, os.WriteFile(schemaPath, []byte(tc.schema), 0o644)) + configPath := filepath.Join(dir, "oapi-codegen.yaml") + testassert.NoError(t, os.WriteFile(configPath, []byte(fixtureConfig), 0o644)) + sdkPath := filepath.Join(dir, "protocol.gen.go") + outPath := filepath.Join(dir, "operations.gen.go") + contractPath := filepath.Join(dir, "native-invocations.json") + sentinels := map[string]string{ + sdkPath: "untouched sdk\n", + outPath: "untouched native\n", + contractPath: "untouched contract\n", + } + for path, sentinel := range sentinels { + testassert.NoError(t, os.WriteFile(path, []byte(sentinel), 0o644)) + } + if err := run(schemaPath, configPath, sdkPath, outPath, contractPath); err == nil { + t.Fatal("the generator accepted an unsupported schema shape") + } else { + t.Logf("rejected: %v", err) + } + for path, sentinel := range sentinels { + content, err := os.ReadFile(path) + testassert.Require(t, err == nil, "reading %s: %v", path, err) + if !bytes.Equal(content, []byte(sentinel)) { + t.Fatalf("unsupported input rewrote %s: %q", filepath.Base(path), content) + } + } + }) + } +} + +// fixtureDispatchSchema is the one compiled owner of every accepted native +// shape: the committed ProviderField/ModelField enum references, an enum-less +// referenced string whose component name the SDK normalizes, a named date +// schema the SDK aliases to a non-string Go representation, inline and +// component-parameter session paths, an operation ID the SDK normalizes, +// path-level query parameters overridden at the operation level in both +// directions, mixed required/optional queries, one required JSON object body, +// and the unlisted 201/203/210/204 success statuses. +const fixtureDispatchSchema = `openapi: "3.0.3" +info: + title: protocolgen-dispatch-fixture + version: "1" +paths: + /v1/health: + get: + operationId: GetHealth + responses: + "200": + description: readiness + content: + application/json: + schema: + type: object + /v1/events: + get: + operationId: GetEvents + responses: + "200": + description: the event stream + content: + text/event-stream: + schema: + type: object + /v1/providers/fields/{field}: delete: - operationId: ResetField + operationId: ResetProviderField parameters: - name: field in: path required: true schema: - $ref: "#/components/schemas/provider_field" + $ref: "#/components/schemas/ProviderField" + - name: provider_id + in: query + required: true + schema: + type: string responses: "200": - description: the reset post-state + description: provider field reset content: application/json: schema: type: object -components: - schemas: - provider_field: - type: string - enum: - - name -`, - }, - { - name: "an array request body", - schema: base + ` /v1/other: + /v1/providers/models/fields/{field}: + delete: + operationId: ResetProviderModelField + parameters: + - name: field + in: path + required: true + schema: + $ref: "#/components/schemas/ModelField" + - name: provider_id + in: query + required: true + schema: + type: string + - name: model_id + in: query + required: true + schema: + type: string + responses: + "200": + description: model field reset + content: + application/json: + schema: + type: object + /v1/plain/fields/{field}: + delete: + operationId: ResetPlainField + parameters: + - name: field + in: path + required: true + schema: + $ref: "#/components/schemas/plain_field" + responses: + "200": + description: plain field reset + content: + application/json: + schema: + type: object + /v1/sessions/{id}/plain: post: - operationId: Other - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - type: string + operationId: PingSession + parameters: + - $ref: "#/components/parameters/SessionID" responses: "200": - description: an array body + description: plain session path content: application/json: schema: type: object -`, - }, - { - name: "a string request body", - schema: base + ` /v1/other: + /v1/sessions/{id}/extra: post: - operationId: Other + operationId: Extra + parameters: + - name: id + in: path + required: true + schema: + type: string requestBody: required: true content: application/json: schema: - type: string + type: object + responses: + "201": + description: the added operation + content: + application/json: + schema: + type: object + /v1/normalized: + get: + operationId: get_extra + parameters: + - name: cursor + in: query + required: false + schema: + type: string responses: "200": - description: a string body + description: normalized operation ID content: application/json: schema: type: object -`, - }, - { - name: "an untyped request body", - schema: base + ` /v1/other: - post: - operationId: Other - requestBody: + /v1/mixed: + get: + operationId: MixedQuery + parameters: + - name: all + in: query + required: true + schema: + type: boolean + - name: cursor + in: query + required: false + schema: + type: string + responses: + "200": + description: a mixed query + content: + application/json: + schema: + type: object + /v1/overrides: + parameters: + - name: flag + in: query + required: false + schema: + type: boolean + get: + operationId: QueryOverride + parameters: + - name: flag + in: query + required: true + schema: + type: boolean + responses: + "200": + description: required override + content: + application/json: + schema: + type: object + /v1/optional-override: + parameters: + - name: limit + in: query required: true - content: - application/json: - schema: - properties: - name: - type: string + schema: + type: string + get: + operationId: OptionalOverride + parameters: + - name: limit + in: query + required: false + schema: + type: string responses: "200": - description: an untyped body + description: optional override content: application/json: schema: type: object -`, - }, - { - name: "a two-media request body", - schema: base + ` /v1/other: + /v1/accepted: post: - operationId: Other + operationId: Accepted203 + responses: + "203": + description: an unlisted numeric success + content: + application/json: + schema: + type: object + /v1/extended: + get: + operationId: Extended210 + responses: + "210": + description: another unlisted numeric success + content: + application/json: + schema: + type: object + /v1/removed: + delete: + operationId: Removed204 + responses: + "204": + description: a contentless success + /v1/dated/{day}: + get: + operationId: GetDated + parameters: + - name: day + in: path + required: true + schema: + $ref: "#/components/schemas/Day" + responses: + "200": + description: a date path + content: + application/json: + schema: + type: object + /v1/sessions: + post: + operationId: CreateSession requestBody: required: true content: application/json: schema: type: object - text/plain: - schema: - type: string responses: "200": - description: a two-media body + description: raw body content: application/json: schema: type: object -`, - }, +components: + parameters: + SessionID: + name: id + in: path + required: true + schema: + type: string + schemas: + ProviderField: + type: string + enum: + - name + - hidden + ModelField: + type: string + enum: + - name + - context_window + plain_field: + type: string + Day: + type: string + format: date +` + +// fixtureDispatchTest is the generated module's own test. It drives the real +// generated SDK through the real stateless binder against an httptest route and +// compares every request's path, query, raw body, declared status and received +// response bytes with independently authored expectations. The date row proves +// the SDK's non-string path type stays compiled and parsed, and a direct string +// cast producer would fail this module's compilation. +const fixtureDispatchTest = `package clientops + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + + "github.com/MMinasyan/lightcode/protocol" +) + +type recordedCall struct { + method string + path string + query string + body string +} + +func TestGeneratedFixtureDispatch(t *testing.T) { + var mu sync.Mutex + var recorded []recordedCall + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("reading request body: %v", err) + } + mu.Lock() + recorded = append(recorded, recordedCall{method: r.Method, path: r.URL.EscapedPath(), query: r.URL.RawQuery, body: string(body)}) + mu.Unlock() + status := http.StatusOK + switch r.URL.Path { + case "/v1/sessions/s1/extra": + status = http.StatusCreated + case "/v1/accepted": + status = 203 + case "/v1/extended": + status = 210 + case "/v1/removed": + status = http.StatusNoContent + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if status == http.StatusNoContent { + return + } + _, _ = w.Write([]byte("{\"ok\":true}")) + })) + defer server.Close() + client, err := protocol.NewClient(server.URL) + if err != nil { + t.Fatalf("NewClient: %v", err) } - for _, tc := range cases { + for _, tc := range []struct { + name string + operation string + params string + method string + path string + query string + body string + status int + response string + }{ + {"health without an envelope", "GetHealth", "", "GET", "/v1/health", "", "", 200, "{\"ok\":true}"}, + {"enum-backed referenced field", "ResetProviderField", "{\"field\":\"hidden\",\"query\":{\"provider_id\":\"prov\"}}", "DELETE", "/v1/providers/fields/hidden", "provider_id=prov", "", 200, "{\"ok\":true}"}, + {"model enum-backed referenced field", "ResetProviderModelField", "{\"field\":\"context_window\",\"query\":{\"provider_id\":\"prov\",\"model_id\":\"m\"}}", "DELETE", "/v1/providers/models/fields/context_window", "provider_id=prov&model_id=m", "", 200, "{\"ok\":true}"}, + {"enum-less normalized referenced field", "ResetPlainField", "{\"field\":\"anything\"}", "DELETE", "/v1/plain/fields/anything", "", "", 200, "{\"ok\":true}"}, + {"component-parameter session path", "PingSession", "{\"session_id\":\"s1\"}", "POST", "/v1/sessions/s1/plain", "", "", 200, "{\"ok\":true}"}, + {"inline string session path with a body", "Extra", "{\"session_id\":\"s1\",\"body\":{\"workspace\":\"/ws\",\"opaque\":9007199254740993}}", "POST", "/v1/sessions/s1/extra", "", "{\"workspace\":\"/ws\",\"opaque\":9007199254740993}", 201, "{\"ok\":true}"}, + {"normalized operation ID", "get_extra", "{\"query\":{\"cursor\":\"abc\"}}", "GET", "/v1/normalized", "cursor=abc", "", 200, "{\"ok\":true}"}, + {"mixed required and optional query", "MixedQuery", "{\"query\":{\"all\":true,\"cursor\":\"abc\"}}", "GET", "/v1/mixed", "all=true&cursor=abc", "", 200, "{\"ok\":true}"}, + {"operation-level required override", "QueryOverride", "{\"query\":{\"flag\":true}}", "GET", "/v1/overrides", "flag=true", "", 200, "{\"ok\":true}"}, + {"operation-level optional override", "OptionalOverride", "{\"query\":{\"limit\":\"x\"}}", "GET", "/v1/optional-override", "limit=x", "", 200, "{\"ok\":true}"}, + {"optional override without a query member", "OptionalOverride", "", "GET", "/v1/optional-override", "", "", 200, "{\"ok\":true}"}, + {"unlisted 203 success", "Accepted203", "", "POST", "/v1/accepted", "", "", 203, "{\"ok\":true}"}, + {"unlisted 210 success", "Extended210", "", "GET", "/v1/extended", "", "", 210, "{\"ok\":true}"}, + {"contentless 204 success", "Removed204", "", "DELETE", "/v1/removed", "", "", 204, ""}, + {"named date path", "GetDated", "{\"field\":\"2024-01-02\"}", "GET", "/v1/dated/2024-01-02", "", "", 200, "{\"ok\":true}"}, + {"raw JSON body", "CreateSession", "{\"body\":{\"workspace\":\"/ws\",\"opaque\":9007199254740993}}", "POST", "/v1/sessions", "", "{\"workspace\":\"/ws\",\"opaque\":9007199254740993}", 200, "{\"ok\":true}"}, + } { t.Run(tc.name, func(t *testing.T) { - dir := t.TempDir() - schemaPath := filepath.Join(dir, "openapi.yaml") - if err := os.WriteFile(schemaPath, []byte(tc.schema), 0o644); err != nil { - t.Fatalf("writing the fixture schema: %v", err) + mu.Lock() + before := len(recorded) + mu.Unlock() + response, status, err := invokeGenerated(context.Background(), client, tc.operation, json.RawMessage(tc.params)) + if err != nil { + t.Fatalf("invokeGenerated(%s): %v", tc.operation, err) } - outPath := filepath.Join(dir, "operations.gen.go") - contractPath := filepath.Join(dir, "native-invocations.json") - const sentinel = "untouched\n" - for _, path := range []string{outPath, contractPath} { - if err := os.WriteFile(path, []byte(sentinel), 0o644); err != nil { - t.Fatalf("writing the sentinel artifact: %v", err) - } + if status != tc.status || response.StatusCode != tc.status { + t.Fatalf("%s status = declared %d received %d, want %d", tc.operation, status, response.StatusCode, tc.status) } - if err := run(schemaPath, outPath, contractPath); err == nil { - t.Fatal("the generator accepted an unsupported schema shape") - } else { - t.Logf("rejected: %v", err) + body, err := io.ReadAll(response.Body) + _ = response.Body.Close() + if err != nil || string(body) != tc.response { + t.Fatalf("%s result = %q, %v, want %q", tc.operation, body, err, tc.response) } - for _, path := range []string{outPath, contractPath} { - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("reading %s: %v", path, err) - } - if !bytes.Equal(content, []byte(sentinel)) { - t.Fatalf("unsupported input rewrote %s: %q", filepath.Base(path), content) - } + mu.Lock() + call := recorded[before] + mu.Unlock() + if call.method != tc.method || call.path != tc.path || call.query != tc.query || call.body != tc.body { + t.Fatalf("%s request = (%s %s ? %s body %q), want (%s %s ? %s body %q)", tc.operation, call.method, call.path, call.query, call.body, tc.method, tc.path, tc.query, tc.body) } }) } + for _, tc := range []struct { + name string + operation string + params string + }{ + {"omitted required query override", "QueryOverride", ""}, + {"mixed query missing its required member", "MixedQuery", "{\"query\":{\"cursor\":\"abc\"}}"}, + {"invalid date path text", "GetDated", "{\"field\":\"not-a-date\"}"}, + } { + t.Run(tc.name, func(t *testing.T) { + mu.Lock() + before := len(recorded) + mu.Unlock() + _, _, err := invokeGenerated(context.Background(), client, tc.operation, json.RawMessage(tc.params)) + if !errors.Is(err, ErrInvalidParams) { + t.Fatalf("%s error = %v, want ErrInvalidParams", tc.operation, err) + } + mu.Lock() + reached := len(recorded) != before + mu.Unlock() + if reached { + t.Fatalf("%s reached the transport after local rejection", tc.operation) + } + }) + } + names := OperationNames() + want := []string{"Accepted203", "CreateSession", "Extended210", "Extra", "GetDated", "GetHealth", "MixedQuery", "OptionalOverride", "PingSession", "QueryOverride", "Removed204", "ResetPlainField", "ResetProviderField", "ResetProviderModelField", "get_extra"} + if strings.Join(names, ",") != strings.Join(want, ",") { + t.Fatalf("OperationNames() = %v, want %v", names, want) + } +} +` + +// TestGeneratedFixtureCompilesAndDispatches generates the fixture SDK and its +// native bindings into a disposable module with the product module path, copies +// the real stateless binder, compiles them, and drives the actual generated +// client through the binder against httptest routes. The fixture is the one +// compiled owner of every accepted native shape; the rows carry independently +// expected paths, queries, raw bodies, declared statuses, response bytes and +// contract metadata. +func TestGeneratedFixtureCompilesAndDispatches(t *testing.T) { + dir := t.TempDir() + schemaPath := filepath.Join(dir, "openapi.yaml") + testassert.NoError(t, os.WriteFile(schemaPath, []byte(fixtureDispatchSchema), 0o644)) + configPath := filepath.Join(dir, "oapi-codegen.yaml") + testassert.NoError(t, os.WriteFile(configPath, []byte(fixtureConfig), 0o644)) + sdkPath := filepath.Join(dir, "protocol", "protocol.gen.go") + nativePath := filepath.Join(dir, "internal", "clientops", "operations.gen.go") + contractPath := filepath.Join(dir, "native-invocations.json") + testassert.NoError(t, os.MkdirAll(filepath.Dir(sdkPath), 0o755)) + testassert.NoError(t, os.MkdirAll(filepath.Dir(nativePath), 0o755)) + if err := run(schemaPath, configPath, sdkPath, nativePath, contractPath); err != nil { + t.Fatalf("generating the fixture module: %v", err) + } + binder, err := os.ReadFile(filepath.Join("..", "clientops", "bind.go")) + testassert.NoError(t, err) + testassert.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(nativePath), "bind.go"), binder, 0o644)) + testassert.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(nativePath), "bind_test.go"), []byte(fixtureDispatchTest), 0o644)) + contractData, err := os.ReadFile(contractPath) + testassert.NoError(t, err) + var contract map[string]contractOperation + testassert.NoError(t, json.Unmarshal(contractData, &contract)) + wantContract := map[string]contractOperation{ + "Accepted203": {SessionID: modeForbidden, Field: modeForbidden, Query: modeForbidden, Body: modeForbidden}, + "CreateSession": {SessionID: modeForbidden, Field: modeForbidden, Query: modeForbidden, Body: modeRequired}, + "Extended210": {SessionID: modeForbidden, Field: modeForbidden, Query: modeForbidden, Body: modeForbidden}, + "Extra": {SessionID: modeRequired, Field: modeForbidden, Query: modeForbidden, Body: modeRequired}, + "GetDated": {SessionID: modeForbidden, Field: modeRequired, Query: modeForbidden, Body: modeForbidden}, + "GetHealth": {SessionID: modeForbidden, Field: modeForbidden, Query: modeForbidden, Body: modeForbidden}, + "MixedQuery": {SessionID: modeForbidden, Field: modeForbidden, Query: modeRequired, Body: modeForbidden, QueryMembers: map[string]contractMember{"all": {Required: true, Type: "boolean"}, "cursor": {Required: false, Type: "string"}}}, + "OptionalOverride": {SessionID: modeForbidden, Field: modeForbidden, Query: modeOptional, Body: modeForbidden, QueryMembers: map[string]contractMember{"limit": {Required: false, Type: "string"}}}, + "PingSession": {SessionID: modeRequired, Field: modeForbidden, Query: modeForbidden, Body: modeForbidden}, + "QueryOverride": {SessionID: modeForbidden, Field: modeForbidden, Query: modeRequired, Body: modeForbidden, QueryMembers: map[string]contractMember{"flag": {Required: true, Type: "boolean"}}}, + "Removed204": {SessionID: modeForbidden, Field: modeForbidden, Query: modeForbidden, Body: modeForbidden}, + "ResetPlainField": {SessionID: modeForbidden, Field: modeRequired, Query: modeForbidden, Body: modeForbidden}, + "ResetProviderField": {SessionID: modeForbidden, Field: modeRequired, Query: modeRequired, Body: modeForbidden, QueryMembers: map[string]contractMember{"provider_id": {Required: true, Type: "string"}}}, + "ResetProviderModelField": {SessionID: modeForbidden, Field: modeRequired, Query: modeRequired, Body: modeForbidden, QueryMembers: map[string]contractMember{"provider_id": {Required: true, Type: "string"}, "model_id": {Required: true, Type: "string"}}}, + "get_extra": {SessionID: modeForbidden, Field: modeForbidden, Query: modeOptional, Body: modeForbidden, QueryMembers: map[string]contractMember{"cursor": {Required: false, Type: "string"}}}, + } + testassert.Require(t, len(contract) == len(wantContract), "fixture contract names %d operations, want %d:\n%s", len(contract), len(wantContract), contractData) + for name, want := range wantContract { + got, ok := contract[name] + testassert.Require(t, ok, "fixture contract is missing %q", name) + testassert.Require(t, got.SessionID == want.SessionID && got.Field == want.Field && got.Query == want.Query && got.Body == want.Body, "fixture contract %q = %+v, want the declared modes %+v", name, got, want) + if want.QueryMembers == nil { + testassert.Require(t, len(got.QueryMembers) == 0, "fixture contract %q carries query members %+v, want none", name, got.QueryMembers) + } else { + testassert.Require(t, reflect.DeepEqual(got.QueryMembers, want.QueryMembers), "fixture contract %q query members = %+v, want %+v", name, got.QueryMembers, want.QueryMembers) + } + } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(`module github.com/MMinasyan/lightcode + +go 1.26.0 + +require github.com/oapi-codegen/runtime v1.7.0 + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/google/uuid v1.6.0 // indirect +) +`), 0o644)) + sum, err := os.ReadFile(filepath.Join("..", "..", "go.sum")) + testassert.NoError(t, err) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "go.sum"), sum, 0o644)) + cmd := exec.Command("go", "test", "-mod=mod", "./internal/clientops") + cmd.Dir = dir + { + out, err := cmd.CombinedOutput() + testassert.Require(t, err == nil, "compiling/dispatching the generated fixture: %v\n%s", err, out) + } } diff --git a/internal/safefs/safefs_linux_test.go b/internal/safefs/safefs_linux_test.go index 779ac566..25ec6d90 100644 --- a/internal/safefs/safefs_linux_test.go +++ b/internal/safefs/safefs_linux_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "golang.org/x/sys/unix" ) @@ -18,12 +19,8 @@ func TestOpenExistingRefusesSymlinkLeaf(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "target.txt") link := filepath.Join(root, "link.txt") - if err := os.WriteFile(target, []byte("content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(target, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("content"), 0o644)) + testassert.NoError(t, os.Symlink(target, link)) f, err := OpenExisting(link, os.O_RDONLY) if err == nil { f.Close() @@ -34,13 +31,9 @@ func TestOpenExistingRefusesSymlinkLeaf(t *testing.T) { func TestOpenForWriteCreatesParentsAndRefusesSymlinkParent(t *testing.T) { root := t.TempDir() realDir := filepath.Join(root, "real") - if err := os.Mkdir(realDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(realDir, 0o755)) linkDir := filepath.Join(root, "link") - if err := os.Symlink(realDir, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realDir, linkDir)) if f, _, err := OpenForWrite(filepath.Join(root, "a", "b", "file.txt"), 0o644); err != nil { t.Fatalf("OpenForWrite create parents: %v", err) @@ -72,12 +65,8 @@ func TestOpenExistingWriterlessFIFORejectedWithoutBlocking(t *testing.T) { }() select { case err := <-done: - if err == nil { - t.Fatal("OpenExisting succeeded on writerless FIFO leaf") - } - if !errors.Is(err, ErrNonRegular) { - t.Fatalf("OpenExisting writerless FIFO err = %v, want ErrNonRegular", err) - } + testassert.Require(t, err != nil, "OpenExisting succeeded on writerless FIFO leaf") + testassert.Require(t, errors.Is(err, ErrNonRegular), "OpenExisting writerless FIFO err = %v, want ErrNonRegular", err) case <-time.After(5 * time.Second): t.Fatal("OpenExisting blocked on writerless FIFO; want immediate ErrNonRegular") } @@ -101,13 +90,9 @@ func TestOpenForWriteRefusesFIFOLeaf(t *testing.T) { func TestPR11Closure_OpenExistingAndOpenForWriteRefuseHardlinkLeaf(t *testing.T) { root := t.TempDir() outside := filepath.Join(t.TempDir(), "outside.txt") - if err := os.WriteFile(outside, []byte("outside-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(outside, []byte("outside-content"), 0o644)) hardlinked := filepath.Join(root, "hardlinked.txt") - if err := os.Link(outside, hardlinked); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Link(outside, hardlinked)) t.Run("OpenExisting", func(t *testing.T) { f, err := OpenExisting(hardlinked, os.O_RDONLY) @@ -130,127 +115,97 @@ func TestRemoveLeafUnlinksSymlinkItself(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "target.txt") link := filepath.Join(root, "link.txt") - if err := os.WriteFile(target, []byte("content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(target, link); err != nil { - t.Fatal(err) - } - if err := RemoveLeaf(link); err != nil { - t.Fatal(err) - } - if _, err := os.Lstat(link); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("link lstat err = %v, want not exist", err) + testassert.NoError(t, os.WriteFile(target, []byte("content"), 0o644)) + testassert.NoError(t, os.Symlink(target, link)) + testassert.NoError(t, RemoveLeaf(link)) + { + _, err := os.Lstat(link) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "link lstat err = %v, want not exist", err) } data, err := os.ReadFile(target) - if err != nil || string(data) != "content" { - t.Fatalf("target content = %q, %v; want unchanged", data, err) - } + testassert.Require(t, err == nil && string(data) == "content", "target content = %q, %v; want unchanged", data, err) } func TestRemoveLeafRefusesHardlinkedRegularLeafAndPreservesLinks(t *testing.T) { root := t.TempDir() outside := filepath.Join(t.TempDir(), "outside.txt") - if err := os.WriteFile(outside, []byte("outside-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(outside, []byte("outside-content"), 0o644)) hardlinked := filepath.Join(root, "hardlinked.txt") - if err := os.Link(outside, hardlinked); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Link(outside, hardlinked)) err := RemoveLeaf(hardlinked) - if err == nil { - t.Fatal("RemoveLeaf succeeded on hardlinked regular leaf; want refusal") - } + testassert.Require(t, err != nil, "RemoveLeaf succeeded on hardlinked regular leaf; want refusal") msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "hardlink") && !strings.Contains(msg, "link count") && !strings.Contains(msg, "nlink") { - t.Fatalf("error %v does not mention hardlink/link count/nlink", err) - } - if _, err := os.Lstat(hardlinked); err != nil { - t.Fatalf("hardlinked leaf lstat = %v; want preserved", err) + testassert.Require(t, strings.Contains(msg, "hardlink") || strings.Contains(msg, "link count") || strings.Contains(msg, "nlink"), "error %v does not mention hardlink/link count/nlink", err) + { + _, err := os.Lstat(hardlinked) + testassert.NoError(t, err) } - if got, err := os.ReadFile(outside); err != nil || string(got) != "outside-content" { - t.Fatalf("outside file = %q, %v; want unchanged", got, err) + { + got, err := os.ReadFile(outside) + testassert.Require(t, err == nil && string(got) == "outside-content", "outside file = %q, %v; want unchanged", got, err) } } func TestRemoveLeafRefusesHardlinkedSymlinkLeafAndPreservesLinks(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "target.txt") - if err := os.WriteFile(target, []byte("target-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("target-content"), 0o644)) link := filepath.Join(root, "link.txt") - if err := os.Symlink(target, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(target, link)) hardlinkedSymlink := filepath.Join(root, "hardlinked-link.txt") - if err := os.Link(link, hardlinkedSymlink); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Link(link, hardlinkedSymlink)) assertHardlinkedSymlink(t, link) assertHardlinkedSymlink(t, hardlinkedSymlink) err := RemoveLeaf(hardlinkedSymlink) - if err == nil { - t.Fatal("RemoveLeaf succeeded on hardlinked symlink leaf; want refusal") - } + testassert.Require(t, err != nil, "RemoveLeaf succeeded on hardlinked symlink leaf; want refusal") msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "hardlink") && !strings.Contains(msg, "link count") && !strings.Contains(msg, "nlink") { - t.Fatalf("error %v does not mention hardlink/link count/nlink", err) - } - if _, err := os.Lstat(link); err != nil { - t.Fatalf("original symlink lstat = %v; want preserved", err) + testassert.Require(t, strings.Contains(msg, "hardlink") || strings.Contains(msg, "link count") || strings.Contains(msg, "nlink"), "error %v does not mention hardlink/link count/nlink", err) + { + _, err := os.Lstat(link) + testassert.NoError(t, err) } - if _, err := os.Lstat(hardlinkedSymlink); err != nil { - t.Fatalf("hardlinked symlink lstat = %v; want preserved", err) + { + _, err := os.Lstat(hardlinkedSymlink) + testassert.NoError(t, err) } - if got, err := os.ReadFile(target); err != nil || string(got) != "target-content" { - t.Fatalf("target file = %q, %v; want unchanged", got, err) + { + got, err := os.ReadFile(target) + testassert.Require(t, err == nil && string(got) == "target-content", "target file = %q, %v; want unchanged", got, err) } } func TestRemoveLeafRemovesEmptyDirectory(t *testing.T) { dir := filepath.Join(t.TempDir(), "empty") - if err := os.Mkdir(dir, 0o755); err != nil { - t.Fatal(err) - } - if err := RemoveLeaf(dir); err != nil { - t.Fatalf("RemoveLeaf(empty dir) = %v, want nil", err) - } - if _, err := os.Lstat(dir); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("empty dir lstat err = %v, want not exist", err) + testassert.NoError(t, os.Mkdir(dir, 0o755)) + testassert.NoError(t, RemoveLeaf(dir)) + { + _, err := os.Lstat(dir) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "empty dir lstat err = %v, want not exist", err) } } func TestRemoveLeafRefusesNonEmptyDirectoryAndPreservesChild(t *testing.T) { dir := filepath.Join(t.TempDir(), "nonempty") - if err := os.Mkdir(dir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(dir, 0o755)) child := filepath.Join(dir, "file.txt") - if err := os.WriteFile(child, []byte("content"), 0o644); err != nil { - t.Fatal(err) - } - if err := RemoveLeaf(dir); err == nil { - t.Fatal("RemoveLeaf(non-empty dir) = nil, want refusal") + testassert.NoError(t, os.WriteFile(child, []byte("content"), 0o644)) + { + err := RemoveLeaf(dir) + testassert.Require(t, err != nil, "RemoveLeaf(non-empty dir) = nil, want refusal") } if info, err := os.Stat(dir); err != nil || !info.IsDir() { t.Fatalf("non-empty dir stat = %v, isDir = %v; want intact directory", err, err == nil && info.IsDir()) } data, err := os.ReadFile(child) - if err != nil || string(data) != "content" { - t.Fatalf("non-empty dir child content = %q, %v; want unchanged", data, err) - } + testassert.Require(t, err == nil && string(data) == "content", "non-empty dir child content = %q, %v; want unchanged", data, err) } func assertHardlinkedSymlink(t *testing.T, path string) { t.Helper() info, err := os.Lstat(path) - if err != nil { - t.Fatalf("lstat %s: %v", path, err) - } + testassert.Require(t, err == nil, "lstat %s: %v", path, err) if info.Mode()&os.ModeSymlink == 0 { t.Fatalf("%s mode = %s, want symlink", path, info.Mode()) } @@ -258,7 +213,5 @@ func assertHardlinkedSymlink(t *testing.T, path string) { if !ok { t.Fatalf("%s stat type = %T, want *syscall.Stat_t", path, info.Sys()) } - if st.Nlink <= 1 { - t.Fatalf("%s symlink nlink = %d, want > 1", path, st.Nlink) - } + testassert.Require(t, st.Nlink > 1, "%s symlink nlink = %d, want > 1", path, st.Nlink) } diff --git a/internal/safefs/safefs_opendirectory_linux_test.go b/internal/safefs/safefs_opendirectory_linux_test.go index 2a716846..db8bebe0 100644 --- a/internal/safefs/safefs_opendirectory_linux_test.go +++ b/internal/safefs/safefs_opendirectory_linux_test.go @@ -6,51 +6,38 @@ import ( "os" "path/filepath" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestOpenDirectoryOpensRealDirectory(t *testing.T) { root := t.TempDir() - if err := os.WriteFile(filepath.Join(root, "a.txt"), []byte("x"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(filepath.Join(root, "sub"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(root, "a.txt"), []byte("x"), 0o644)) + testassert.NoError(t, os.Mkdir(filepath.Join(root, "sub"), 0o755)) f, err := OpenDirectory(root) - if err != nil { - t.Fatalf("OpenDirectory error = %v", err) - } + testassert.NoError(t, err) defer f.Close() entries, err := f.ReadDir(-1) - if err != nil { - t.Fatalf("ReadDir error = %v", err) - } - if len(entries) != 2 { - t.Fatalf("entries = %d, want 2", len(entries)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(entries), 2) } func TestOpenDirectoryOpensFilesystemRoot(t *testing.T) { f, err := OpenDirectory("/") - if err != nil { - t.Fatalf("OpenDirectory(/) error = %v", err) - } + testassert.NoError(t, err) defer f.Close() - if _, err := f.ReadDir(-1); err != nil { - t.Fatalf("ReadDir error = %v", err) + { + _, err := f.ReadDir(-1) + testassert.NoError(t, err) } } func TestOpenDirectoryRefusesSymlinkLeafWithoutFollowing(t *testing.T) { root := t.TempDir() real := filepath.Join(t.TempDir(), "real") - if err := os.Mkdir(real, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(real, 0o755)) link := filepath.Join(root, "link") - if err := os.Symlink(real, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(real, link)) f, err := OpenDirectory(link) if err == nil { f.Close() @@ -61,9 +48,7 @@ func TestOpenDirectoryRefusesSymlinkLeafWithoutFollowing(t *testing.T) { func TestOpenDirectoryRefusesRegularFileLeaf(t *testing.T) { root := t.TempDir() file := filepath.Join(root, "file.txt") - if err := os.WriteFile(file, []byte("x"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(file, []byte("x"), 0o644)) f, err := OpenDirectory(file) if err == nil { f.Close() @@ -72,7 +57,8 @@ func TestOpenDirectoryRefusesRegularFileLeaf(t *testing.T) { } func TestOpenDirectoryRefusesMissingLeaf(t *testing.T) { - if _, err := OpenDirectory(filepath.Join(t.TempDir(), "missing")); err == nil { - t.Fatal("OpenDirectory accepted a missing leaf") + { + _, err := OpenDirectory(filepath.Join(t.TempDir(), "missing")) + testassert.Require(t, err != nil, "OpenDirectory accepted a missing leaf") } } diff --git a/internal/selfupdate/selfupdate_test.go b/internal/selfupdate/selfupdate_test.go index 680424f5..241f50d8 100644 --- a/internal/selfupdate/selfupdate_test.go +++ b/internal/selfupdate/selfupdate_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/version" ) @@ -47,42 +48,26 @@ func TestLatestTag(t *testing.T) { tag, err := LatestTag() if tc.wantErr != "" { - if err == nil || !strings.Contains(err.Error(), tc.wantErr) { - t.Fatalf("err = %v, want substring %q", err, tc.wantErr) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), tc.wantErr), "err = %v, want substring %q", err, tc.wantErr) return } - if err != nil { - t.Fatal(err) - } - if tag != tc.wantTag { - t.Fatalf("tag = %q, want %q", tag, tc.wantTag) - } + testassert.NoError(t, err) + testassert.Equal(t, tag, tc.wantTag) }) } } func TestAssetURLs(t *testing.T) { asset, sums := AssetURLs("", "v0.0.4") - if asset != "https://github.com/MMinasyan/lightcode/releases/download/v0.0.4/"+AssetName { - t.Fatalf("channel asset URL = %q", asset) - } - if sums != "https://github.com/MMinasyan/lightcode/releases/download/v0.0.4/SHA256SUMS" { - t.Fatalf("channel sums URL = %q", sums) - } + testassert.Equal(t, asset, "https://github.com/MMinasyan/lightcode/releases/download/v0.0.4/"+AssetName) + testassert.Equal(t, sums, "https://github.com/MMinasyan/lightcode/releases/download/v0.0.4/SHA256SUMS") // A base override composes / with no tag in the path, // matching the installer's semantics. asset, sums = AssetURLs("http://example.test/dir/", "v0.0.4") - if asset != "http://example.test/dir/"+AssetName { - t.Fatalf("base asset URL = %q", asset) - } - if sums != "http://example.test/dir/SHA256SUMS" { - t.Fatalf("base sums URL = %q", sums) - } - if strings.Contains(asset, "v0.0.4") { - t.Fatal("base URL must not contain the tag") - } + testassert.Equal(t, asset, "http://example.test/dir/"+AssetName) + testassert.Equal(t, sums, "http://example.test/dir/SHA256SUMS") + testassert.Require(t, !strings.Contains(asset, "v0.0.4"), "base URL must not contain the tag") } func TestVerifySHA256(t *testing.T) { @@ -96,14 +81,14 @@ func TestVerifySHA256(t *testing.T) { return []byte("0123456789abcdef install.sh\n" + assetSum + " " + AssetName + "\n") } - if err := VerifySHA256(data, manifest(good), AssetName); err != nil { - t.Fatalf("happy path: %v", err) - } - if err := VerifySHA256(data, manifest(strings.Repeat("0", 64)), AssetName); err == nil || !strings.Contains(err.Error(), "checksum mismatch") { - t.Fatalf("mismatch err = %v", err) + testassert.NoError(t, VerifySHA256(data, manifest(good), AssetName)) + { + err := VerifySHA256(data, manifest(strings.Repeat("0", 64)), AssetName) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "checksum mismatch"), "mismatch err = %v", err) } - if err := VerifySHA256(data, []byte("deadbeef other-file\n"), AssetName); err == nil || !strings.Contains(err.Error(), "no entry") { - t.Fatalf("missing entry err = %v", err) + { + err := VerifySHA256(data, []byte("deadbeef other-file\n"), AssetName) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "no entry"), "missing entry err = %v", err) } } @@ -122,21 +107,16 @@ func buildTarball(t *testing.T, members map[string]struct { if m.typeflag == tar.TypeReg { hdr.Size = int64(len(m.body)) } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } + testassert.NoError(t, tw.WriteHeader(hdr)) if m.typeflag == tar.TypeReg { - if _, err := tw.Write([]byte(m.body)); err != nil { - t.Fatal(err) + { + _, err := tw.Write([]byte(m.body)) + testassert.NoError(t, err) } } } - if err := tw.Close(); err != nil { - t.Fatal(err) - } - if err := gz.Close(); err != nil { - t.Fatal(err) - } + testassert.NoError(t, tw.Close()) + testassert.NoError(t, gz.Close()) return buf.Bytes() } @@ -150,45 +130,46 @@ func TestExtractBinary(t *testing.T) { t.Run("happy path", func(t *testing.T) { tb := buildTarball(t, map[string]member{"lightcode": {body: "BINARY", typeflag: tar.TypeReg}}) data, err := ExtractBinary(tb) - if err != nil { - t.Fatal(err) - } - if string(data) != "BINARY" { - t.Fatalf("extracted %q", data) - } + testassert.NoError(t, err) + testassert.Require(t, string(data) == "BINARY", "extracted %q", data) }) t.Run("missing member", func(t *testing.T) { tb := buildTarball(t, map[string]member{"otherfile": {body: "X", typeflag: tar.TypeReg}}) - if _, err := ExtractBinary(tb); err == nil || !strings.Contains(err.Error(), "no lightcode member") { - t.Fatalf("err = %v", err) + { + _, err := ExtractBinary(tb) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "no lightcode member"), "err = %v", err) } }) t.Run("symlink member rejected", func(t *testing.T) { tb := buildTarball(t, map[string]member{"lightcode": {typeflag: tar.TypeSymlink, linkname: "/bin/sh"}}) - if _, err := ExtractBinary(tb); err == nil || !strings.Contains(err.Error(), "not a regular file") { - t.Fatalf("err = %v", err) + { + _, err := ExtractBinary(tb) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "not a regular file"), "err = %v", err) } }) t.Run("dir member rejected", func(t *testing.T) { tb := buildTarball(t, map[string]member{"lightcode": {typeflag: tar.TypeDir}}) - if _, err := ExtractBinary(tb); err == nil || !strings.Contains(err.Error(), "not a regular file") { - t.Fatalf("err = %v", err) + { + _, err := ExtractBinary(tb) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "not a regular file"), "err = %v", err) } }) t.Run("short body", func(t *testing.T) { tb := buildTarball(t, map[string]member{"lightcode": {body: "BINARY", typeflag: tar.TypeReg}}) - if _, err := ExtractBinary(tb[:len(tb)/2]); err == nil { - t.Fatal("truncated archive must error") + { + _, err := ExtractBinary(tb[:len(tb)/2]) + testassert.Require(t, err != nil, "truncated archive must error") } }) t.Run("not gzip", func(t *testing.T) { - if _, err := ExtractBinary([]byte("plain bytes")); err == nil { - t.Fatal("non-gzip input must error") + { + _, err := ExtractBinary([]byte("plain bytes")) + testassert.Require(t, err != nil, "non-gzip input must error") } }) } @@ -202,97 +183,76 @@ func TestStageAndSmokeAndReplace(t *testing.T) { t.Run("single inode from stage through replace", func(t *testing.T) { dir := t.TempDir() target := filepath.Join(dir, "lightcode") - if err := os.WriteFile(target, []byte("old"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("old"), 0o755)) staged, err := Stage(dir, smokeScript(`echo "lightcode v9.9.9 (linux/amd64)"`)) - if err != nil { - t.Fatal(err) - } - if !strings.HasPrefix(filepath.Base(staged), ".lightcode.tmp.") { - t.Fatalf("staged name = %q", staged) - } + testassert.NoError(t, err) + testassert.Require(t, strings.HasPrefix(filepath.Base(staged), ".lightcode.tmp."), "staged name = %q", staged) info, err := os.Stat(staged) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o755 { t.Fatalf("staged mode = %v", info.Mode()) } stagedIno := info.Sys().(*syscall.Stat_t).Ino - if err := SmokeCheck(staged, "v9.9.9"); err != nil { - t.Fatalf("smoke: %v", err) - } - if err := Replace(staged, target); err != nil { - t.Fatal(err) - } + testassert.NoError(t, SmokeCheck(staged, "v9.9.9")) + testassert.NoError(t, Replace(staged, target)) finalInfo, err := os.Stat(target) - if err != nil { - t.Fatal(err) - } - if finalInfo.Sys().(*syscall.Stat_t).Ino != stagedIno { - t.Fatal("the promoted inode is not the smoke-checked inode") - } + testassert.NoError(t, err) + testassert.Require(t, finalInfo.Sys().(*syscall.Stat_t).Ino == stagedIno, "the promoted inode is not the smoke-checked inode") }) t.Run("stale staged files do not block", func(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, ".lightcode.tmp.stale"), []byte("x"), 0o755); err != nil { - t.Fatal(err) - } - if _, err := Stage(dir, []byte("new")); err != nil { - t.Fatalf("pre-existing stale temp file blocked staging: %v", err) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, ".lightcode.tmp.stale"), []byte("x"), 0o755)) + { + _, err := Stage(dir, []byte("new")) + testassert.NoError(t, err) } }) t.Run("smoke fails on wrong tag", func(t *testing.T) { dir := t.TempDir() staged, err := Stage(dir, smokeScript(`echo "lightcode v0.0.1 (linux/amd64)"`)) - if err != nil { - t.Fatal(err) - } - if err := SmokeCheck(staged, "v9.9.9"); err == nil || !strings.Contains(err.Error(), "did not report") { - t.Fatalf("err = %v", err) + testassert.NoError(t, err) + { + err := SmokeCheck(staged, "v9.9.9") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "did not report"), "err = %v", err) } }) t.Run("smoke fails on nonzero exit even with matching tag", func(t *testing.T) { dir := t.TempDir() staged, err := Stage(dir, smokeScript(`echo "lightcode v9.9.9 (linux/amd64)"; exit 3`)) - if err != nil { - t.Fatal(err) - } - if err := SmokeCheck(staged, "v9.9.9"); err == nil || !strings.Contains(err.Error(), "failed --version") { - t.Fatalf("err = %v", err) + testassert.NoError(t, err) + { + err := SmokeCheck(staged, "v9.9.9") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "failed --version"), "err = %v", err) } }) t.Run("smoke fails on hang", func(t *testing.T) { dir := t.TempDir() staged, err := Stage(dir, smokeScript("sleep 60")) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) old := SmokeTimeout SmokeTimeout = 200 * time.Millisecond defer func() { SmokeTimeout = old }() - if err := SmokeCheck(staged, "v9.9.9"); err == nil || !strings.Contains(err.Error(), "did not exit") { - t.Fatalf("err = %v", err) + { + err := SmokeCheck(staged, "v9.9.9") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "did not exit"), "err = %v", err) } }) t.Run("smoke rejects foreign-owned staged file", func(t *testing.T) { dir := t.TempDir() staged, err := Stage(dir, smokeScript("echo v9.9.9")) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) old := Geteuid Geteuid = func() int { return os.Geteuid() + 1 } defer func() { Geteuid = old }() - if err := SmokeCheck(staged, "v9.9.9"); err == nil || !strings.Contains(err.Error(), "not owned") { - t.Fatalf("err = %v", err) + { + err := SmokeCheck(staged, "v9.9.9") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "not owned"), "err = %v", err) } }) @@ -301,27 +261,17 @@ func TestStageAndSmokeAndReplace(t *testing.T) { dir := t.TempDir() // The script fails the tag check when the canary leaks through. staged, err := Stage(dir, smokeScript(`if [ -n "$LIGHTCODE_SMOKE_CANARY" ]; then echo leaked; else echo lightcode v9.9.9; fi`)) - if err != nil { - t.Fatal(err) - } - if err := SmokeCheck(staged, "v9.9.9"); err != nil { - t.Fatalf("LIGHTCODE_ env leaked into the smoke child: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, SmokeCheck(staged, "v9.9.9")) }) } func TestDirWritable(t *testing.T) { dir := t.TempDir() - if !DirWritable(dir) { - t.Fatal("temp dir must be writable") - } + testassert.Require(t, DirWritable(dir), "temp dir must be writable") locked := filepath.Join(dir, "locked") - if err := os.Mkdir(locked, 0o500); err != nil { - t.Fatal(err) - } - if os.Geteuid() != 0 && DirWritable(locked) { - t.Fatal("0500 dir must not be writable") - } + testassert.NoError(t, os.Mkdir(locked, 0o500)) + testassert.Require(t, os.Geteuid() == 0 || !DirWritable(locked), "0500 dir must not be writable") } func TestCheckStatusPrecedence(t *testing.T) { @@ -346,17 +296,13 @@ func TestCheckStatusPrecedence(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := Check(tc.current, tc.current, tc.latest) - if got.Status != tc.wantStatus || got.UpdateAvailable != tc.wantUpdate || got.Installable != tc.wantInstallable { - t.Fatalf("Check = %+v, want status=%s update=%v installable=%v", got, tc.wantStatus, tc.wantUpdate, tc.wantInstallable) - } + testassert.Require(t, got.Status == tc.wantStatus && got.UpdateAvailable == tc.wantUpdate && got.Installable == tc.wantInstallable, "Check = %+v, want status=%s update=%v installable=%v", got, tc.wantStatus, tc.wantUpdate, tc.wantInstallable) }) } } func TestMinInstallableShape(t *testing.T) { - if !version.IsRelease(MinInstallable) { - t.Fatalf("MinInstallable %q is not an exact vMAJOR.MINOR.PATCH tag - a placeholder cannot ship", MinInstallable) - } + testassert.Require(t, version.IsRelease(MinInstallable), "MinInstallable %q is not an exact vMAJOR.MINOR.PATCH tag - a placeholder cannot ship", MinInstallable) } // TestFloorWithinReleaseTag is the release-workflow gate: it runs against @@ -366,15 +312,9 @@ func TestFloorWithinReleaseTag(t *testing.T) { if tag == "" { t.Skip("LIGHTCODE_RELEASE_TAG not set") } - if !version.IsRelease(MinInstallable) { - t.Fatalf("floor %q is not an exact vMAJOR.MINOR.PATCH tag", MinInstallable) - } - if !version.IsRelease(tag) { - t.Fatalf("release tag %q is not an exact vMAJOR.MINOR.PATCH tag", tag) - } - if version.Compare(MinInstallable, tag) > 0 { - t.Fatalf("floor %s exceeds the tag being released (%s)", MinInstallable, tag) - } + testassert.Require(t, version.IsRelease(MinInstallable), "floor %q is not an exact vMAJOR.MINOR.PATCH tag", MinInstallable) + testassert.Require(t, version.IsRelease(tag), "release tag %q is not an exact vMAJOR.MINOR.PATCH tag", tag) + testassert.Require(t, version.Compare(MinInstallable, tag) <= 0, "floor %s exceeds the tag being released (%s)", MinInstallable, tag) } func TestEnvWithoutLightcode(t *testing.T) { @@ -382,25 +322,17 @@ func TestEnvWithoutLightcode(t *testing.T) { t.Setenv("KEEP_THIS_VAR", "1") env := envWithoutLightcode() joined := strings.Join(env, "\n") - if strings.Contains(joined, "LIGHTCODE_") { - t.Fatalf("LIGHTCODE_ vars not stripped: %s", joined) - } - if !strings.Contains(joined, "KEEP_THIS_VAR=1") { - t.Fatal("non-LIGHTCODE vars must be kept") - } + testassert.Require(t, !strings.Contains(joined, "LIGHTCODE_"), "LIGHTCODE_ vars not stripped: %s", joined) + testassert.Require(t, strings.Contains(joined, "KEEP_THIS_VAR=1"), "non-LIGHTCODE vars must be kept") } func TestIgnoreReleaseBaseAsRoot(t *testing.T) { old := Geteuid defer func() { Geteuid = old }() Geteuid = func() int { return 0 } - if !IgnoreReleaseBaseAsRoot() { - t.Fatal("euid 0 must ignore the base override") - } + testassert.Require(t, IgnoreReleaseBaseAsRoot(), "euid 0 must ignore the base override") Geteuid = func() int { return 1000 } - if IgnoreReleaseBaseAsRoot() { - t.Fatal("non-root must keep the base override") - } + testassert.Require(t, !IgnoreReleaseBaseAsRoot(), "non-root must keep the base override") } // Sanity: Download surfaces non-200 and short reads as errors. @@ -416,10 +348,9 @@ func TestDownload(t *testing.T) { defer srv.Close() data, err := Download(srv.URL + "/ok") - if err != nil || string(data) != "payload" { - t.Fatalf("data=%q err=%v", data, err) - } - if _, err := Download(srv.URL + "/missing"); err == nil || !strings.Contains(err.Error(), "unexpected status") { - t.Fatalf("err = %v", err) + testassert.Require(t, err == nil && string(data) == "payload", "data=%q err=%v", data, err) + { + _, err := Download(srv.URL + "/missing") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "unexpected status"), "err = %v", err) } } diff --git a/internal/shellparse/shellparse_test.go b/internal/shellparse/shellparse_test.go index e227f7b3..b27409ae 100644 --- a/internal/shellparse/shellparse_test.go +++ b/internal/shellparse/shellparse_test.go @@ -1,47 +1,30 @@ package shellparse -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) func TestParseNormalizesShellArgv(t *testing.T) { segments, err := Parse(`git diff --out\put=/tmp/x && rg --pre\=cmd pattern .`) - if err != nil { - t.Fatal(err) - } - if len(segments) != 2 { - t.Fatalf("segments = %d, want 2", len(segments)) - } - if got := segments[0].Argv[2]; got != "--output=/tmp/x" { - t.Fatalf("git arg = %q, want normalized --output=/tmp/x", got) - } - if got := segments[1].Argv[1]; got != "--pre=cmd" { - t.Fatalf("rg arg = %q, want normalized --pre=cmd", got) - } + testassert.NoError(t, err) + testassert.Equal(t, len(segments), 2) + testassert.Equal(t, segments[0].Argv[2], "--output=/tmp/x") + testassert.Equal(t, segments[1].Argv[1], "--pre=cmd") } func TestParseNormalizesShellLineContinuation(t *testing.T) { segments, err := Parse("r\\\nm -rf x") - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || segments[0].Text != "rm -rf x" { - t.Fatalf("segments = %#v, want shell-effective rm segment", segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && segments[0].Text == "rm -rf x", "segments = %#v, want shell-effective rm segment", segments) segments, err = Parse("r\\\r\nm -rf x") - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || segments[0].Text != "rm -rf x" { - t.Fatalf("CRLF segments = %#v, want shell-effective rm segment", segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && segments[0].Text == "rm -rf x", "CRLF segments = %#v, want shell-effective rm segment", segments) segments, err = Parse("echo \"a\\\nb\"") - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || len(segments[0].Argv) != 2 || segments[0].Argv[1] != "ab" { - t.Fatalf("double-quoted line continuation segments = %#v, want ab arg", segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && len(segments[0].Argv) == 2 && segments[0].Argv[1] == "ab", "double-quoted line continuation segments = %#v, want ab arg", segments) } func TestParseDoubleQuotedBackslashSemantics(t *testing.T) { @@ -57,24 +40,16 @@ func TestParseDoubleQuotedBackslashSemantics(t *testing.T) { for _, tt := range tests { t.Run(tt.command, func(t *testing.T) { segments, err := Parse(tt.command) - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || len(segments[0].Argv) != 2 || segments[0].Argv[1] != tt.want { - t.Fatalf("segments = %#v, want second argv %q", segments, tt.want) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && len(segments[0].Argv) == 2 && segments[0].Argv[1] == tt.want, "segments = %#v, want second argv %q", segments, tt.want) }) } } func TestParseAllowsLiteralSingleQuotedSubstitution(t *testing.T) { segments, err := Parse("echo '$(whoami)'") - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || len(segments[0].Argv) != 2 || segments[0].Argv[1] != "$(whoami)" { - t.Fatalf("segments = %#v, want literal substitution arg", segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && len(segments[0].Argv) == 2 && segments[0].Argv[1] == "$(whoami)", "segments = %#v, want literal substitution arg", segments) } func TestParseRejectsExecutableSubstitution(t *testing.T) { @@ -85,8 +60,9 @@ func TestParseRejectsExecutableSubstitution(t *testing.T) { "echo \"`whoami`\"", } { t.Run(command, func(t *testing.T) { - if _, err := Parse(command); err == nil { - t.Fatal("Parse returned nil error, want substitution rejection") + { + _, err := Parse(command) + testassert.Require(t, err != nil, "Parse returned nil error, want substitution rejection") } }) } @@ -108,30 +84,17 @@ func TestParseRedirections(t *testing.T) { } { t.Run(command, func(t *testing.T) { segments, err := Parse(command) - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || !segments[0].HasRedirection { - t.Fatalf("segments = %#v, want one segment with HasRedirection=true", segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && segments[0].HasRedirection, "segments = %#v, want one segment with HasRedirection=true", segments) }) } } func TestParseQuotedMetacharactersAreLiteral(t *testing.T) { segments, err := Parse(`grep -E 'foo|bar' file && echo '>'`) - if err != nil { - t.Fatal(err) - } - if len(segments) != 2 { - t.Fatalf("segments = %d, want 2", len(segments)) - } - if segments[0].HasRedirection || segments[1].HasRedirection { - t.Fatalf("segments = %#v, want no redirections", segments) - } - if segments[0].Argv[2] != "foo|bar" || segments[1].Argv[1] != ">" { - t.Fatalf("argv = %#v %#v, want quoted literals", segments[0].Argv, segments[1].Argv) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 2 && !segments[0].HasRedirection && !segments[1].HasRedirection, "got %v, want %v; segments = %#v, want no redirections", len(segments), 2, segments) + testassert.Require(t, segments[0].Argv[2] == "foo|bar" && segments[1].Argv[1] == ">", "argv = %#v %#v, want quoted literals", segments[0].Argv, segments[1].Argv) } func TestParseMarksUnsafeExpansions(t *testing.T) { @@ -145,12 +108,8 @@ func TestParseMarksUnsafeExpansions(t *testing.T) { } { t.Run(command, func(t *testing.T) { segments, err := Parse(command) - if err != nil { - t.Fatal(err) - } - if len(segments) != 1 || !segments[0].UnsafeExpansion { - t.Fatalf("segments = %#v, want unsafe expansion", segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && segments[0].UnsafeExpansion, "segments = %#v, want unsafe expansion", segments) }) } } @@ -161,8 +120,9 @@ func TestPR11Closure_RedirectionTargetMarksOrRejectsSubstitution(t *testing.T) { "echo ok >$(id)", "echo ok > `id`", } { - if _, err := Parse(command); err == nil { - t.Fatalf("Parse(%q) returned nil error; want non-nil (redirection-target substitution must reject)", command) + { + _, err := Parse(command) + testassert.Require(t, err != nil, "Parse(%q) returned nil error; want non-nil (redirection-target substitution must reject)", command) } } }) @@ -176,12 +136,8 @@ func TestPR11Closure_RedirectionTargetMarksOrRejectsSubstitution(t *testing.T) { "echo ok >~/file", } { segments, err := Parse(command) - if err != nil { - t.Fatalf("Parse(%q) = error %v; want nil error + UnsafeExpansion=true", command, err) - } - if len(segments) != 1 || !segments[0].UnsafeExpansion { - t.Fatalf("Parse(%q) segments = %#v; want one segment with UnsafeExpansion=true", command, segments) - } + testassert.Require(t, err == nil, "Parse(%q) = error %v; want nil error + UnsafeExpansion=true", command, err) + testassert.Require(t, len(segments) == 1 && segments[0].UnsafeExpansion, "Parse(%q) segments = %#v; want one segment with UnsafeExpansion=true", command, segments) } }) @@ -194,15 +150,8 @@ func TestPR11Closure_RedirectionTargetMarksOrRejectsSubstitution(t *testing.T) { "echo ok >'~'", } { segments, err := Parse(command) - if err != nil { - t.Fatalf("Parse(%q) = error %v; want nil error (single-quoted target is literal)", command, err) - } - if len(segments) != 1 || segments[0].UnsafeExpansion { - t.Fatalf("Parse(%q) segments = %#v; want one segment with UnsafeExpansion=false", command, segments) - } - if !segments[0].HasRedirection { - t.Fatalf("Parse(%q) segments = %#v; want HasRedirection=true", command, segments) - } + testassert.Require(t, err == nil, "Parse(%q) = error %v; want nil error (single-quoted target is literal)", command, err) + testassert.Require(t, len(segments) == 1 && !segments[0].UnsafeExpansion && segments[0].HasRedirection, "Parse(%q) segments = %#v; want one segment with UnsafeExpansion=false; Parse(%q) segments = %#v; want HasRedirection=true", command, segments, command, segments) } }) @@ -211,8 +160,9 @@ func TestPR11Closure_RedirectionTargetMarksOrRejectsSubstitution(t *testing.T) { `echo ok >"$(id)"`, "echo ok >\"`id`\"", } { - if _, err := Parse(command); err == nil { - t.Fatalf("Parse(%q) returned nil error; want non-nil (double-quoted substitution must reject)", command) + { + _, err := Parse(command) + testassert.Require(t, err != nil, "Parse(%q) returned nil error; want non-nil (double-quoted substitution must reject)", command) } } }) @@ -223,33 +173,19 @@ func TestPR11Closure_RedirectionTargetMarksOrRejectsSubstitution(t *testing.T) { `echo ok >"text $VAR"`, } { segments, err := Parse(command) - if err != nil { - t.Fatalf("Parse(%q) = error %v; want nil error + UnsafeExpansion=true", command, err) - } - if len(segments) != 1 || !segments[0].UnsafeExpansion { - t.Fatalf("Parse(%q) segments = %#v; want one segment with UnsafeExpansion=true", command, segments) - } + testassert.Require(t, err == nil, "Parse(%q) = error %v; want nil error + UnsafeExpansion=true", command, err) + testassert.Require(t, len(segments) == 1 && segments[0].UnsafeExpansion, "Parse(%q) segments = %#v; want one segment with UnsafeExpansion=true", command, segments) } // Glob chars inside double-quotes are literal — not unsafe. segments, err := Parse(`echo ok >"foo*"`) - if err != nil { - t.Fatalf("Parse double-quoted glob = error %v; want nil", err) - } - if len(segments) != 1 || segments[0].UnsafeExpansion { - t.Fatalf(`Parse "echo ok >\"foo*\"" segments = %#v; want UnsafeExpansion=false (literal inside double-quote)`, segments) - } + testassert.NoError(t, err) + testassert.Require(t, len(segments) == 1 && !segments[0].UnsafeExpansion, `Parse "echo ok >\"foo*\"" segments = %#v; want UnsafeExpansion=false (literal inside double-quote)`, segments) }) } func TestPR11Closure_PipeAmpersandIsSingleOperator(t *testing.T) { segments, err := Parse("echo a |& cat") - if err != nil { - t.Fatal(err) - } - if len(segments) != 2 { - t.Fatalf("segments count = %d, want 2 (|& is one separator)", len(segments)) - } - if segments[0].Separator != "|&" { - t.Fatalf("segments[0].Separator = %q, want %q", segments[0].Separator, "|&") - } + testassert.NoError(t, err) + testassert.Equal(t, len(segments), 2) + testassert.Equal(t, segments[0].Separator, "|&") } diff --git a/internal/shellparse/shellsimple_test.go b/internal/shellparse/shellsimple_test.go index 614dd4c7..acd7a3fa 100644 --- a/internal/shellparse/shellsimple_test.go +++ b/internal/shellparse/shellsimple_test.go @@ -1,6 +1,9 @@ package shellparse -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) // TestParseSimpleGrammar pins the restricted simple-command grammar: every // conclusive row decomposes into per-segment source-faithful Text targets, @@ -139,12 +142,8 @@ func TestParseSimpleGrammar(t *testing.T) { continue } for i := range segments { - if segments[i].Text != tc.text[i] { - t.Errorf("%s: ParseSimple(%q)[%d].Text = %q, want %q", tc.name, tc.command, i, segments[i].Text, tc.text[i]) - } - if segments[i].UnsafeExpansion || segments[i].HasRedirection { - t.Errorf("%s: ParseSimple(%q)[%d] carries grammar markers outside the simple-command shape", tc.name, tc.command, i) - } + testassert.Check(t, segments[i].Text == tc.text[i], "%s: ParseSimple(%q)[%d].Text = %q, want %q", tc.name, tc.command, i, segments[i].Text, tc.text[i]) + testassert.Check(t, !segments[i].UnsafeExpansion && !segments[i].HasRedirection, "%s: ParseSimple(%q)[%d] carries grammar markers outside the simple-command shape", tc.name, tc.command, i) } } } diff --git a/internal/snapshot/code_groups_test.go b/internal/snapshot/code_groups_test.go index 6ce7b038..52e0b96c 100644 --- a/internal/snapshot/code_groups_test.go +++ b/internal/snapshot/code_groups_test.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // stageGroupCapture drives one real CodeStore capture in the given group @@ -16,14 +18,14 @@ func stageGroupCapture(t *testing.T, groupDir, target string, turn int, content t.Helper() code := mustOpenCodeStore(t, groupDir) entryID, _, err := code.SnapshotResolvedEntry(turn, target, target) - if err != nil { - t.Fatalf("SnapshotResolvedEntry(%s): %v", target, err) - } - if err := os.WriteFile(target, content, 0o600); err != nil { - t.Fatalf("write %s: %v", target, err) + testassert.Require(t, err == nil, "SnapshotResolvedEntry(%s): %v", target, err) + { + err := os.WriteFile(target, content, 0o600) + testassert.Require(t, err == nil, "write %s: %v", target, err) } - if err := code.RecordSnapshotContent(turn, entryID, content); err != nil { - t.Fatalf("RecordSnapshotContent(%s): %v", target, err) + { + err := code.RecordSnapshotContent(turn, entryID, content) + testassert.Require(t, err == nil, "RecordSnapshotContent(%s): %v", target, err) } code.RetainSnapshotEntry(turn, entryID) } @@ -38,12 +40,8 @@ func stageTwoGroups(t *testing.T) (newest, oldest, file string) { newest = filepath.Join(root, "entry-new") oldest = filepath.Join(root, "entry-old") file = filepath.Join(root, "tree", "app.txt") - if err := os.MkdirAll(filepath.Dir(file), 0o700); err != nil { - t.Fatalf("mkdir: %v", err) - } - if err := os.WriteFile(file, []byte("v0"), 0o600); err != nil { - t.Fatalf("write v0: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(file), 0o700)) + testassert.NoError(t, os.WriteFile(file, []byte("v0"), 0o600)) stageGroupCapture(t, oldest, file, 1, []byte("v1")) stageGroupCapture(t, newest, file, 1, []byte("v2")) return newest, oldest, file @@ -58,24 +56,13 @@ func TestRevertCodeGroupsSharesSkipLedgerAcrossGroups(t *testing.T) { // The external edit matches the older group's last-write identity, so only // the shared ledger can keep the older group from restoring. - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write external v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) result, err := RevertCodeGroups([]string{newest, oldest}, 0) - if err != nil { - t.Fatalf("RevertCodeGroups: %v", err) - } - if len(result.Restored) != 0 { - t.Fatalf("restored = %v, want no restore after the newer skip", result.Restored) - } - if len(result.Skipped) != 1 || result.Skipped[0].Path != file { - t.Fatalf("skipped = %+v, want exactly one reported skip for %s", result.Skipped, file) - } + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 0 && len(result.Skipped) == 1 && result.Skipped[0].Path == file, "restored = %v, want no restore after the newer skip; skipped = %+v, want exactly one reported skip for %s", result.Restored, result.Skipped, file) data, err := os.ReadFile(file) - if err != nil || string(data) != "v1" { - t.Fatalf("file = (%q, %v), want the external v1 untouched", data, err) - } + testassert.Require(t, err == nil && string(data) == "v1", "file = (%q, %v), want the external v1 untouched", data, err) } // TestRevertCodeGroupsRestoresAcrossGroupsInOrder proves the shared ledger @@ -85,21 +72,17 @@ func TestRevertCodeGroupsRestoresAcrossGroupsInOrder(t *testing.T) { newest, oldest, file := stageTwoGroups(t) result, err := RevertCodeGroups([]string{newest, oldest}, 0) - if err != nil { - t.Fatalf("RevertCodeGroups: %v", err) - } - if len(result.Restored) != 2 || result.Restored[0] != file || result.Restored[1] != file { - t.Fatalf("restored = %v, want the newest then the older preimage", result.Restored) - } + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 2 && result.Restored[0] == file && result.Restored[1] == file, "restored = %v, want the newest then the older preimage", result.Restored) data, err := os.ReadFile(file) - if err != nil || string(data) != "v0" { - t.Fatalf("file = (%q, %v), want the oldest preimage v0", data, err) + testassert.Require(t, err == nil && string(data) == "v0", "file = (%q, %v), want the oldest preimage v0", data, err) + { + _, err := os.Stat(filepath.Join(newest, "snapshots", "1")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "newest turn dir = %v, want it removed after the restore", err) } - if _, err := os.Stat(filepath.Join(newest, "snapshots", "1")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("newest turn dir = %v, want it removed after the restore", err) - } - if _, err := os.Stat(filepath.Join(oldest, "snapshots", "1")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("older turn dir = %v, want it removed after the restore", err) + { + _, err := os.Stat(filepath.Join(oldest, "snapshots", "1")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "older turn dir = %v, want it removed after the restore", err) } } @@ -111,29 +94,21 @@ func TestRevertCodeGroupsPartialFailurePreservesPriorEffects(t *testing.T) { newest, oldest, file := stageTwoGroups(t) badEntry := filepath.Join(oldest, "snapshots", "1", hashString(file)) - if err := os.MkdirAll(badEntry, 0o700); err != nil { - t.Fatalf("mkdir bad entry: %v", err) - } - if err := os.WriteFile(filepath.Join(badEntry, "meta.json"), []byte("{"), 0o600); err != nil { - t.Fatalf("write bad meta: %v", err) - } + testassert.NoError(t, os.MkdirAll(badEntry, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(badEntry, "meta.json"), []byte("{"), 0o600)) result, err := RevertCodeGroups([]string{newest, oldest}, 0) - if err == nil { - t.Fatalf("RevertCodeGroups = %+v, want the older group's traversal error", result) - } - if len(result.Restored) != 1 || result.Restored[0] != file { - t.Fatalf("restored = %v, want the newest group's honest restore", result.Restored) - } + testassert.Require(t, err != nil, "RevertCodeGroups = %+v, want the older group's traversal error", result) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == file, "restored = %v, want the newest group's honest restore", result.Restored) data, err := os.ReadFile(file) - if err != nil || string(data) != "v1" { - t.Fatalf("file = (%q, %v), want the newest group's preimage v1", data, err) + testassert.Require(t, err == nil && string(data) == "v1", "file = (%q, %v), want the newest group's preimage v1", data, err) + { + _, err := os.Stat(filepath.Join(newest, "snapshots", "1")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "newest turn dir = %v, want it removed after its restore", err) } - if _, err := os.Stat(filepath.Join(newest, "snapshots", "1")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("newest turn dir = %v, want it removed after its restore", err) - } - if _, err := os.Stat(filepath.Join(oldest, "snapshots", "1")); err != nil { - t.Fatalf("older turn dir = %v, want it kept after the failure", err) + { + _, err := os.Stat(filepath.Join(oldest, "snapshots", "1")) + testassert.NoError(t, err) } } @@ -146,20 +121,16 @@ func TestRevertCodeGroupsAfterTurnAppliesPerDirectory(t *testing.T) { stageGroupCapture(t, newest, file, 2, []byte("v3")) result, err := RevertCodeGroups([]string{newest, oldest}, 1) - if err != nil { - t.Fatalf("RevertCodeGroups: %v", err) - } - if len(result.Restored) != 1 || result.Restored[0] != file { - t.Fatalf("restored = %v, want only the newest group's turn-2 preimage", result.Restored) - } + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == file, "restored = %v, want only the newest group's turn-2 preimage", result.Restored) data, err := os.ReadFile(file) - if err != nil || string(data) != "v2" { - t.Fatalf("file = (%q, %v), want the turn-2 preimage v2", data, err) - } - if _, err := os.Stat(filepath.Join(newest, "snapshots", "1")); err != nil { - t.Fatalf("newest turn-1 dir = %v, want it kept at the after-turn floor", err) - } - if _, err := os.Stat(filepath.Join(oldest, "snapshots", "1")); err != nil { - t.Fatalf("older turn-1 dir = %v, want it kept at the after-turn floor", err) + testassert.Require(t, err == nil && string(data) == "v2", "file = (%q, %v), want the turn-2 preimage v2", data, err) + { + _, err := os.Stat(filepath.Join(newest, "snapshots", "1")) + testassert.NoError(t, err) + } + { + _, err := os.Stat(filepath.Join(oldest, "snapshots", "1")) + testassert.NoError(t, err) } } diff --git a/internal/snapshot/code_store_test.go b/internal/snapshot/code_store_test.go index 3ee376f2..5cc3900f 100644 --- a/internal/snapshot/code_store_test.go +++ b/internal/snapshot/code_store_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/pathutil" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestCodeStoreArtifactOnlyGroupReopenAndRestore proves that an isolated @@ -21,99 +22,55 @@ func TestCodeStoreArtifactOnlyGroupReopenAndRestore(t *testing.T) { projectDir := t.TempDir() target := filepath.Join(projectDir, "notes.txt") original := []byte("original\n") - if err := os.WriteFile(target, original, 0o644); err != nil { - t.Fatalf("write target: %v", err) - } + testassert.NoError(t, os.WriteFile(target, original, 0o644)) code, err := OpenCodeStore(groupDir) - if err != nil { - t.Fatalf("OpenCodeStore: %v", err) - } + testassert.NoError(t, err) // Construction creates no directories. - if _, err := os.Stat(filepath.Join(groupDir, "snapshots")); !os.IsNotExist(err) { - t.Fatalf("construction created snapshots dir: stat err = %v, want not exist", err) + { + _, err := os.Stat(filepath.Join(groupDir, "snapshots")) + testassert.Require(t, os.IsNotExist(err), "construction created snapshots dir: stat err = %v, want not exist", err) } abs, err := filepath.Abs(target) - if err != nil { - t.Fatalf("abs: %v", err) - } + testassert.NoError(t, err) entryID, created, err := code.SnapshotResolvedEntry(1, abs, abs) - if err != nil { - t.Fatalf("SnapshotResolvedEntry: %v", err) - } - if !created { - t.Fatalf("SnapshotResolvedEntry created = false, want true") - } + testassert.NoError(t, err) + testassert.Require(t, created, "SnapshotResolvedEntry created = false, want true") code.RetainSnapshotEntry(1, entryID) mutated := []byte("mutated\n") - if err := os.WriteFile(target, mutated, 0o644); err != nil { - t.Fatalf("mutate target: %v", err) - } - if err := code.RecordSnapshotContent(1, entryID, mutated); err != nil { - t.Fatalf("RecordSnapshotContent: %v", err) - } + testassert.NoError(t, os.WriteFile(target, mutated, 0o644)) + testassert.NoError(t, code.RecordSnapshotContent(1, entryID, mutated)) // The group directory holds only the snapshot artifact tree: no // conversation, Project, claim, metadata or transcript files. entries, err := os.ReadDir(groupDir) - if err != nil { - t.Fatalf("read group dir: %v", err) - } - if len(entries) != 1 || entries[0].Name() != "snapshots" { - t.Fatalf("group dir entries = %v, want only snapshots", entries) - } + testassert.NoError(t, err) + testassert.Require(t, len(entries) == 1 && entries[0].Name() == "snapshots", "group dir entries = %v, want only snapshots", entries) turns, err := code.ListTurns() - if err != nil { - t.Fatalf("ListTurns: %v", err) - } - if len(turns) != 1 || turns[0].Turn != 1 || len(turns[0].Files) != 1 { - t.Fatalf("ListTurns = %+v, want one turn with one file", turns) - } - if turns[0].Files[0].LastWrite == nil || turns[0].Files[0].LastWrite.Hash != hashBytes(mutated) { - t.Fatalf("last-write identity = %+v, want hash of mutated content", turns[0].Files[0].LastWrite) - } + testassert.NoError(t, err) + testassert.Require(t, len(turns) == 1 && turns[0].Turn == 1 && len(turns[0].Files) == 1, "ListTurns = %+v, want one turn with one file", turns) + testassert.Require(t, turns[0].Files[0].LastWrite != nil && turns[0].Files[0].LastWrite.Hash == hashBytes(mutated), "last-write identity = %+v, want hash of mutated content", turns[0].Files[0].LastWrite) // Reopen the same group with a fresh handle: listing and restore must // work with no conversation/Project state at all. reopened, err := OpenCodeStore(groupDir) - if err != nil { - t.Fatalf("reopen OpenCodeStore: %v", err) - } + testassert.NoError(t, err) reopenedTurns, err := reopened.ListTurns() - if err != nil { - t.Fatalf("reopened ListTurns: %v", err) - } - if len(reopenedTurns) != 1 || len(reopenedTurns[0].Files) != 1 { - t.Fatalf("reopened ListTurns = %+v, want one turn with one file", reopenedTurns) - } + testassert.NoError(t, err) + testassert.Require(t, len(reopenedTurns) == 1 && len(reopenedTurns[0].Files) == 1, "reopened ListTurns = %+v, want one turn with one file", reopenedTurns) result, err := reopened.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode: %v", err) - } - if len(result.Restored) != 1 || result.Restored[0] != abs { - t.Fatalf("RevertCode restored = %v, want [%s]", result.Restored, abs) - } - if len(result.Skipped) != 0 { - t.Fatalf("RevertCode skipped = %+v, want none", result.Skipped) - } + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == abs && len(result.Skipped) == 0, "RevertCode restored = %v, want [%s]; RevertCode skipped = %+v, want none", result.Restored, abs, result.Skipped) got, err := os.ReadFile(target) - if err != nil { - t.Fatalf("read restored target: %v", err) - } - if string(got) != string(original) { - t.Fatalf("restored content = %q, want %q", got, original) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == string(original), "restored content = %q, want %q", got, original) after, err := os.ReadDir(filepath.Join(groupDir, "snapshots")) - if err != nil { - t.Fatalf("read snapshots dir after restore: %v", err) - } - if len(after) != 0 { - t.Fatalf("snapshots dir after restore = %v, want empty", after) - } + testassert.NoError(t, err) + testassert.Require(t, len(after) == 0, "snapshots dir after restore = %v, want empty", after) } // TestCodeStoreTwoGroupsRestoreIndependently proves the one-disk-group-per- @@ -130,31 +87,25 @@ func TestCodeStoreTwoGroupsRestoreIndependently(t *testing.T) { fileA := filepath.Join(projectDir, "a.txt") fileB := filepath.Join(projectDir, "b.txt") originalA, originalB := []byte("a original\n"), []byte("b original\n") - if err := os.WriteFile(fileA, originalA, 0o644); err != nil { - t.Fatalf("write a.txt: %v", err) - } - if err := os.WriteFile(fileB, originalB, 0o644); err != nil { - t.Fatalf("write b.txt: %v", err) - } + testassert.NoError(t, os.WriteFile(fileA, originalA, 0o644)) + testassert.NoError(t, os.WriteFile(fileB, originalB, 0o644)) // Each operation's group captures its own target's preimage; each // mutation records its post-write identity. capture := func(groupDir, target string, mutated []byte) { t.Helper() code, err := OpenCodeStore(groupDir) - if err != nil { - t.Fatalf("OpenCodeStore(%s): %v", groupDir, err) - } + testassert.Require(t, err == nil, "OpenCodeStore(%s): %v", groupDir, err) entryID, created, err := code.SnapshotResolvedEntry(1, target, target) - if err != nil || !created { - t.Fatalf("SnapshotResolvedEntry(%s) = (%v, %v), want one created entry", groupDir, entryID, err) - } + testassert.Require(t, err == nil && created, "SnapshotResolvedEntry(%s) = (%v, %v), want one created entry", groupDir, entryID, err) code.RetainSnapshotEntry(1, entryID) - if err := os.WriteFile(target, mutated, 0o644); err != nil { - t.Fatalf("mutate %s: %v", target, err) + { + err := os.WriteFile(target, mutated, 0o644) + testassert.Require(t, err == nil, "mutate %s: %v", target, err) } - if err := code.RecordSnapshotContent(1, entryID, mutated); err != nil { - t.Fatalf("RecordSnapshotContent(%s): %v", groupDir, err) + { + err := code.RecordSnapshotContent(1, entryID, mutated) + testassert.Require(t, err == nil, "RecordSnapshotContent(%s): %v", groupDir, err) } } mutatedA, mutatedB := []byte("a mutated\n"), []byte("b mutated\n") @@ -164,58 +115,34 @@ func TestCodeStoreTwoGroupsRestoreIndependently(t *testing.T) { // Restoring group A through a fresh handle: only A's preimage returns, // B's file stays mutated and B's snapshot tree stays intact. restoreA, err := OpenCodeStore(groupADir) - if err != nil { - t.Fatalf("reopen group A: %v", err) - } + testassert.NoError(t, err) resultA, err := restoreA.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode(A): %v", err) - } - if len(resultA.Restored) != 1 || resultA.Restored[0] != fileA || len(resultA.Skipped) != 0 { - t.Fatalf("RevertCode(A) = %+v, want exactly %s restored", resultA, fileA) - } + testassert.NoError(t, err) + testassert.Require(t, len(resultA.Restored) == 1 && resultA.Restored[0] == fileA && len(resultA.Skipped) == 0, "RevertCode(A) = %+v, want exactly %s restored", resultA, fileA) gotA, err := os.ReadFile(fileA) - if err != nil || string(gotA) != string(originalA) { - t.Fatalf("a.txt after restore A = (%q, %v), want the original bytes", gotA, err) - } + testassert.Require(t, err == nil && string(gotA) == string(originalA), "a.txt after restore A = (%q, %v), want the original bytes", gotA, err) gotB, err := os.ReadFile(fileB) - if err != nil || string(gotB) != string(mutatedB) { - t.Fatalf("b.txt after restore A = (%q, %v), want it untouched at the mutated bytes", gotB, err) - } + testassert.Require(t, err == nil && string(gotB) == string(mutatedB), "b.txt after restore A = (%q, %v), want it untouched at the mutated bytes", gotB, err) turnsB, err := mustOpenCodeStore(t, groupBDir).ListTurns() - if err != nil || len(turnsB) != 1 || len(turnsB[0].Files) != 1 { - t.Fatalf("group B turns after restore A = %+v err %v, want the snapshot tree intact", turnsB, err) - } + testassert.Require(t, err == nil && len(turnsB) == 1 && len(turnsB[0].Files) == 1, "group B turns after restore A = %+v err %v, want the snapshot tree intact", turnsB, err) turnsA, err := mustOpenCodeStore(t, groupADir).ListTurns() - if err != nil || len(turnsA) != 0 { - t.Fatalf("group A turns after restore A = %+v err %v, want the restored turn dirs removed", turnsA, err) - } + testassert.Require(t, err == nil && len(turnsA) == 0, "group A turns after restore A = %+v err %v, want the restored turn dirs removed", turnsA, err) // Restoring group B independently restores its own preimage. restoreB, err := OpenCodeStore(groupBDir) - if err != nil { - t.Fatalf("reopen group B: %v", err) - } + testassert.NoError(t, err) resultB, err := restoreB.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode(B): %v", err) - } - if len(resultB.Restored) != 1 || resultB.Restored[0] != fileB || len(resultB.Skipped) != 0 { - t.Fatalf("RevertCode(B) = %+v, want exactly %s restored", resultB, fileB) - } + testassert.NoError(t, err) + testassert.Require(t, len(resultB.Restored) == 1 && resultB.Restored[0] == fileB && len(resultB.Skipped) == 0, "RevertCode(B) = %+v, want exactly %s restored", resultB, fileB) gotB, err = os.ReadFile(fileB) - if err != nil || string(gotB) != string(originalB) { - t.Fatalf("b.txt after restore B = (%q, %v), want the original bytes", gotB, err) - } + testassert.Require(t, err == nil && string(gotB) == string(originalB), "b.txt after restore B = (%q, %v), want the original bytes", gotB, err) } // mustOpenCodeStore opens one code group, failing the test on error. func mustOpenCodeStore(t *testing.T, groupDir string) *CodeStore { t.Helper() code, err := OpenCodeStore(groupDir) - if err != nil { - t.Fatalf("OpenCodeStore(%s): %v", groupDir, err) - } + testassert.Require(t, err == nil, "OpenCodeStore(%s): %v", groupDir, err) return code } @@ -235,50 +162,37 @@ func newTestCodeGroup(t *testing.T) *CodeStore { func snapshotEntry(t *testing.T, store *CodeStore, turn int, originalPath, canonicalPath string) string { t.Helper() entryID, _, err := store.SnapshotResolvedEntry(turn, originalPath, canonicalPath) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return entryID } func recordSnapshotContent(t *testing.T, store *CodeStore, turn int, entryID string, content string) { t.Helper() - if err := store.RecordSnapshotContent(turn, entryID, []byte(content)); err != nil { - t.Fatal(err) - } + testassert.NoError(t, store.RecordSnapshotContent(turn, entryID, []byte(content))) } func TestCodeStoreRejectsInvalidTurn(t *testing.T) { store := newTestCodeGroup(t) path := filepath.Join(t.TempDir(), "file.txt") - if _, _, err := store.SnapshotResolvedEntry(0, path, path); err == nil { - t.Fatal("SnapshotResolvedEntry(0) error = nil, want error") + { + _, _, err := store.SnapshotResolvedEntry(0, path, path) + testassert.Require(t, err != nil, "SnapshotResolvedEntry(0) error = nil, want error") } } func TestSnapshotMetaJSONRoundTrip(t *testing.T) { want := SnapshotMeta{OriginalPath: "/project/link.go", CanonicalPath: "/real/file.go", Existed: true} data, err := json.Marshal(want) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) var got SnapshotMeta - if err := json.Unmarshal(data, &got); err != nil { - t.Fatal(err) - } - if got != want { - t.Fatalf("round-trip snapshot meta = %+v, want %+v", got, want) - } + testassert.NoError(t, json.Unmarshal(data, &got)) + testassert.Equal(t, got, want) } func TestSnapshotMetaBackwardsCompatibleWithoutCanonicalPath(t *testing.T) { var got SnapshotMeta - if err := json.Unmarshal([]byte(`{"original_path":"/project/file.go","existed":true}`), &got); err != nil { - t.Fatal(err) - } - if got.OriginalPath != "/project/file.go" || got.CanonicalPath != "" || !got.Existed { - t.Fatalf("old snapshot meta decode = %+v, want original path with empty canonical path", got) - } + testassert.NoError(t, json.Unmarshal([]byte(`{"original_path":"/project/file.go","existed":true}`), &got)) + testassert.Require(t, got.OriginalPath == "/project/file.go" && got.CanonicalPath == "" && got.Existed, "old snapshot meta decode = %+v, want original path with empty canonical path", got) } func TestSnapshotDeduplicatesSymlinkAndRealPath(t *testing.T) { @@ -292,12 +206,8 @@ func TestSnapshotDeduplicatesSymlinkAndRealPath(t *testing.T) { projectDir := t.TempDir() realPath := filepath.Join(projectDir, "file.txt") linkPath := filepath.Join(projectDir, "link.txt") - if err := os.WriteFile(realPath, []byte("v1"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realPath, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(realPath, []byte("v1"), 0o644)) + testassert.NoError(t, os.Symlink(realPath, linkPath)) const turn = 1 first, second := realPath, linkPath @@ -308,37 +218,21 @@ func TestSnapshotDeduplicatesSymlinkAndRealPath(t *testing.T) { // The second capture resolves its requested path to a canonical // target the way the tool layer binds one before capture. resolved, err := pathutil.ResolveFilePath(second) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) snapshotEntry(t, store, turn, second, resolved.CanonicalPath) entries, err := os.ReadDir(filepath.Join(store.snapshotsDir, "1")) - if err != nil { - t.Fatal(err) - } - if len(entries) != 1 { - t.Fatalf("snapshot entries = %d, want 1", len(entries)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(entries), 1) - if err := os.WriteFile(realPath, []byte("v2"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(realPath, []byte("v2"), 0o644)) recordSnapshotContent(t, store, turn, entryID, "v2") affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 1 { - t.Fatalf("affected = %v, want one path", affected) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 1, "affected = %v, want one path", affected) got, err := os.ReadFile(realPath) - if err != nil { - t.Fatal(err) - } - if string(got) != "v1" { - t.Fatalf("content = %q, want v1", got) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == "v1", "content = %q, want v1", got) }) } } @@ -348,39 +242,24 @@ func TestSnapshotResolvedPreservesOriginalPathForListing(t *testing.T) { projectDir := t.TempDir() realPath := filepath.Join(projectDir, "file.txt") linkPath := filepath.Join(projectDir, "link.txt") - if err := os.WriteFile(realPath, []byte("v1"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realPath, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(realPath, []byte("v1"), 0o644)) + testassert.NoError(t, os.Symlink(realPath, linkPath)) const turn = 1 entryID := snapshotEntry(t, store, turn, linkPath, realPath) entries, err := store.ListTurns() - if err != nil { - t.Fatal(err) - } - if len(entries) != 1 || len(entries[0].Files) != 1 { - t.Fatalf("ListTurns = %+v, want one snapshotted file", entries) - } - if got := entries[0].Files[0].OriginalPath; got != linkPath { - t.Fatalf("OriginalPath = %q, want requested path %q", got, linkPath) - } - if err := os.WriteFile(realPath, []byte("v2"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) + testassert.Require(t, len(entries) == 1 && len(entries[0].Files) == 1, "ListTurns = %+v, want one snapshotted file", entries) + testassert.Equal(t, entries[0].Files[0].OriginalPath, linkPath) + testassert.NoError(t, os.WriteFile(realPath, []byte("v2"), 0o644)) recordSnapshotContent(t, store, turn, entryID, "v2") affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(affected.Restored, []string{linkPath}) { - t.Fatalf("affected = %v, want original requested path %q", affected, linkPath) - } - if got, err := os.ReadFile(realPath); err != nil || string(got) != "v1" { - t.Fatalf("real file after revert = %q, %v; want v1", got, err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{linkPath}), "affected = %v, want original requested path %q", affected, linkPath) + { + got, err := os.ReadFile(realPath) + testassert.Require(t, err == nil && string(got) == "v1", "real file after revert = %q, %v; want v1", got, err) } } @@ -388,35 +267,21 @@ func TestSnapshotResolvedEntryReportsCreatedOnlyForNewEntry(t *testing.T) { store := newTestCodeGroup(t) const turn = 1 path := filepath.Join(t.TempDir(), "file.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) entryID, created, err := store.SnapshotResolvedEntry(turn, path, path) - if err != nil { - t.Fatal(err) - } - if entryID == "" || !created { - t.Fatalf("first SnapshotResolvedEntry = (%q, %v), want non-empty created entry", entryID, created) - } + testassert.NoError(t, err) + testassert.Require(t, entryID != "" && created, "first SnapshotResolvedEntry = (%q, %v), want non-empty created entry", entryID, created) entryID2, created, err := store.SnapshotResolvedEntry(turn, path, path) - if err != nil { - t.Fatal(err) - } - if entryID2 != entryID || created { - t.Fatalf("second SnapshotResolvedEntry = (%q, %v), want same existing entry %q", entryID2, created, entryID) - } - if err := store.DiscardSnapshotEntry(turn, entryID); err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) + testassert.Require(t, entryID2 == entryID && !created, "second SnapshotResolvedEntry = (%q, %v), want same existing entry %q", entryID2, created, entryID) + testassert.NoError(t, store.DiscardSnapshotEntry(turn, entryID)) if entries, err := os.ReadDir(filepath.Join(store.snapshotsDir, "1")); err != nil { t.Fatal(err) } else if len(entries) != 1 { t.Fatalf("snapshot entries after one discard = %d, want one retained by second user", len(entries)) } - if err := store.DiscardSnapshotEntry(turn, entryID2); err != nil { - t.Fatal(err) - } + testassert.NoError(t, store.DiscardSnapshotEntry(turn, entryID2)) if entries, err := os.ReadDir(filepath.Join(store.snapshotsDir, "1")); err != nil && !errors.Is(err, os.ErrNotExist) { t.Fatal(err) } else if len(entries) != 0 { @@ -428,28 +293,16 @@ func TestSnapshotResolvedEntryRetainPreventsConcurrentDiscard(t *testing.T) { store := newTestCodeGroup(t) const turn = 1 path := filepath.Join(t.TempDir(), "file.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) entryID, created, err := store.SnapshotResolvedEntry(turn, path, path) - if err != nil { - t.Fatal(err) - } - if !created { - t.Fatal("first SnapshotResolvedEntry was not created") - } + testassert.NoError(t, err) + testassert.Require(t, created, "first SnapshotResolvedEntry was not created") entryID2, created, err := store.SnapshotResolvedEntry(turn, path, path) - if err != nil { - t.Fatal(err) - } - if entryID2 != entryID || created { - t.Fatalf("second SnapshotResolvedEntry = (%q, %v), want same existing entry %q", entryID2, created, entryID) - } + testassert.NoError(t, err) + testassert.Require(t, entryID2 == entryID && !created, "second SnapshotResolvedEntry = (%q, %v), want same existing entry %q", entryID2, created, entryID) store.RetainSnapshotEntry(turn, entryID2) - if err := store.DiscardSnapshotEntry(turn, entryID); err != nil { - t.Fatal(err) - } + testassert.NoError(t, store.DiscardSnapshotEntry(turn, entryID)) if entries, err := os.ReadDir(filepath.Join(store.snapshotsDir, "1")); err != nil { t.Fatal(err) } else if len(entries) != 1 { @@ -461,14 +314,10 @@ func TestSnapshotResolvedRetainsEntryAgainstLaterDiscard(t *testing.T) { store := newTestCodeGroup(t) const turn = 1 path := filepath.Join(t.TempDir(), "file.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) entryID := snapshotEntry(t, store, turn, path, path) store.RetainSnapshotEntry(turn, entryID) - if err := store.DiscardSnapshotEntry(turn, hashString(path)); err != nil { - t.Fatal(err) - } + testassert.NoError(t, store.DiscardSnapshotEntry(turn, hashString(path))) if entries, err := os.ReadDir(filepath.Join(store.snapshotsDir, "1")); err != nil { t.Fatal(err) } else if len(entries) != 1 { @@ -481,31 +330,28 @@ func TestSnapshotFirstWriteWinsAndNewFileDeletesOnRevert(t *testing.T) { projectDir := t.TempDir() existing := filepath.Join(projectDir, "existing.txt") created := filepath.Join(projectDir, "created.txt") - if err := os.WriteFile(existing, []byte("v1"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(existing, []byte("v1"), 0o644)) const turn = 1 existingEntry := snapshotEntry(t, store, turn, existing, existing) - if err := os.WriteFile(existing, []byte("v2"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(existing, []byte("v2"), 0o644)) recordSnapshotContent(t, store, turn, existingEntry, "v2") // The second capture of the same target keeps the first preimage. snapshotEntry(t, store, turn, existing, existing) createdEntry := snapshotEntry(t, store, turn, created, created) - if err := os.WriteFile(created, []byte("new"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(created, []byte("new"), 0o644)) recordSnapshotContent(t, store, turn, createdEntry, "new") - if _, err := store.RevertCode(0); err != nil { - t.Fatal(err) + { + _, err := store.RevertCode(0) + testassert.NoError(t, err) } - if got, err := os.ReadFile(existing); err != nil || string(got) != "v1" { - t.Fatalf("existing content = %q, err = %v; want v1", got, err) + { + got, err := os.ReadFile(existing) + testassert.Require(t, err == nil && string(got) == "v1", "existing content = %q, err = %v; want v1", got, err) } - if _, err := os.Stat(created); !os.IsNotExist(err) { - t.Fatalf("created file should be removed, stat err = %v", err) + { + _, err := os.Stat(created) + testassert.Require(t, os.IsNotExist(err), "created file should be removed, stat err = %v", err) } } @@ -516,56 +362,36 @@ func TestRevertSkipsCanonicalPathReplacedBySymlinkAndContinues(t *testing.T) { repointedTarget := filepath.Join(projectDir, "repointed.txt") linkPath := filepath.Join(projectDir, "link.txt") ownedPath := filepath.Join(projectDir, "owned.txt") - if err := os.WriteFile(originalTarget, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(repointedTarget, []byte("other"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(ownedPath, []byte("owned-before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(originalTarget, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalTarget, []byte("before"), 0o644)) + testassert.NoError(t, os.WriteFile(repointedTarget, []byte("other"), 0o644)) + testassert.NoError(t, os.WriteFile(ownedPath, []byte("owned-before"), 0o644)) + testassert.NoError(t, os.Symlink(originalTarget, linkPath)) const turn = 1 linkEntry := snapshotEntry(t, store, turn, linkPath, originalTarget) ownedEntry := snapshotEntry(t, store, turn, ownedPath, ownedPath) - if err := os.WriteFile(originalTarget, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalTarget, []byte("after"), 0o644)) recordSnapshotContent(t, store, turn, linkEntry, "after") - if err := os.WriteFile(ownedPath, []byte("owned-after"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(ownedPath, []byte("owned-after"), 0o644)) recordSnapshotContent(t, store, turn, ownedEntry, "owned-after") - if err := os.Remove(linkPath); err != nil { - t.Fatal(err) - } - if err := os.Symlink(repointedTarget, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(linkPath)) + testassert.NoError(t, os.Symlink(repointedTarget, linkPath)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v, want skipped symlink target", err) - } - if !reflect.DeepEqual(affected.Restored, []string{ownedPath}) { - t.Fatalf("restored = %v, want [%s]", affected.Restored, ownedPath) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != linkPath { - t.Fatalf("skipped = %+v, want %s", affected.Skipped, linkPath) - } - if got, err := os.ReadFile(originalTarget); err != nil || string(got) != "after" { - t.Fatalf("original target = %q, %v; want after", got, err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{ownedPath}) && len(affected.Skipped) == 1 && affected.Skipped[0].Path == linkPath, "restored = %v, want [%s]; skipped = %+v, want %s", affected.Restored, ownedPath, affected.Skipped, linkPath) + { + got, err := os.ReadFile(originalTarget) + testassert.Require(t, err == nil && string(got) == "after", "original target = %q, %v; want after", got, err) } - if got, err := os.ReadFile(repointedTarget); err != nil || string(got) != "other" { - t.Fatalf("repointed target = %q, %v; want other", got, err) + { + got, err := os.ReadFile(repointedTarget) + testassert.Require(t, err == nil && string(got) == "other", "repointed target = %q, %v; want other", got, err) } - if got, err := os.ReadFile(ownedPath); err != nil || string(got) != "owned-before" { - t.Fatalf("owned target = %q, %v; want owned-before", got, err) + { + got, err := os.ReadFile(ownedPath) + testassert.Require(t, err == nil && string(got) == "owned-before", "owned target = %q, %v; want owned-before", got, err) } } @@ -574,47 +400,30 @@ func TestRevertSkipsExistingFileReplacedByDirectoryAndContinues(t *testing.T) { projectDir := t.TempDir() dirPath := filepath.Join(projectDir, "target.txt") ownedPath := filepath.Join(projectDir, "owned.txt") - if err := os.WriteFile(dirPath, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(ownedPath, []byte("owned-before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(dirPath, []byte("before"), 0o644)) + testassert.NoError(t, os.WriteFile(ownedPath, []byte("owned-before"), 0o644)) const turn = 1 dirEntry := snapshotEntry(t, store, turn, dirPath, dirPath) ownedEntry := snapshotEntry(t, store, turn, ownedPath, ownedPath) - if err := os.WriteFile(dirPath, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(dirPath, []byte("after"), 0o644)) recordSnapshotContent(t, store, turn, dirEntry, "after") - if err := os.WriteFile(ownedPath, []byte("owned-after"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(ownedPath, []byte("owned-after"), 0o644)) recordSnapshotContent(t, store, turn, ownedEntry, "owned-after") - if err := os.Remove(dirPath); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(dirPath, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(dirPath)) + testassert.NoError(t, os.Mkdir(dirPath, 0o755)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v, want skipped directory replacement", err) - } - if !reflect.DeepEqual(affected.Restored, []string{ownedPath}) { - t.Fatalf("restored = %v, want [%s]", affected.Restored, ownedPath) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != dirPath { - t.Fatalf("skipped = %+v, want %s", affected.Skipped, dirPath) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{ownedPath}) && len(affected.Skipped) == 1 && affected.Skipped[0].Path == dirPath, "restored = %v, want [%s]; skipped = %+v, want %s", affected.Restored, ownedPath, affected.Skipped, dirPath) + { + info, err := os.Lstat(dirPath) + testassert.Require(t, err == nil && info.IsDir(), "directory replacement = %v, %v; want preserved directory", info, err) } - if info, err := os.Lstat(dirPath); err != nil || !info.IsDir() { - t.Fatalf("directory replacement = %v, %v; want preserved directory", info, err) - } - if got, err := os.ReadFile(ownedPath); err != nil || string(got) != "owned-before" { - t.Fatalf("owned target = %q, %v; want owned-before", got, err) + { + got, err := os.ReadFile(ownedPath) + testassert.Require(t, err == nil && string(got) == "owned-before", "owned target = %q, %v; want owned-before", got, err) } } @@ -622,38 +431,25 @@ func TestRevertOldSnapshotMetaFallsBackToOriginalPath(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() path := filepath.Join(projectDir, "file.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(path)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := copyFile(path, filepath.Join(entryDir, "original")); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, copyFile(path, filepath.Join(entryDir, "original"))) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ OriginalPath: path, Existed: true, LastWrite: &SnapshotContentIdentity{Hash: hashBytes([]byte("after"))}, - }); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } + })) + testassert.NoError(t, os.WriteFile(path, []byte("after"), 0o644)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(affected.Restored, []string{path}) { - t.Fatalf("affected = %v, want [%s]", affected, path) - } - if got, err := os.ReadFile(path); err != nil || string(got) != "before" { - t.Fatalf("restored content = %q, %v; want before", got, err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{path}), "affected = %v, want [%s]", affected, path) + { + got, err := os.ReadFile(path) + testassert.Require(t, err == nil && string(got) == "before", "restored content = %q, %v; want before", got, err) } } @@ -661,38 +457,25 @@ func TestRevertOldSnapshotMetaRestoresMissingRegularPathWithOldHash(t *testing.T store := newTestCodeGroup(t) projectDir := t.TempDir() path := filepath.Join(projectDir, "file.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(path)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := copyFile(path, filepath.Join(entryDir, "original")); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, copyFile(path, filepath.Join(entryDir, "original"))) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ OriginalPath: path, Existed: true, LastWrite: &SnapshotContentIdentity{Absent: true}, - }); err != nil { - t.Fatal(err) - } - if err := os.Remove(path); err != nil { - t.Fatal(err) - } + })) + testassert.NoError(t, os.Remove(path)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(affected.Restored, []string{path}) { - t.Fatalf("affected = %v, want [%s]", affected, path) - } - if got, err := os.ReadFile(path); err != nil || string(got) != "before" { - t.Fatalf("restored content = %q, %v; want before", got, err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{path}), "affected = %v, want [%s]", affected, path) + { + got, err := os.ReadFile(path) + testassert.Require(t, err == nil && string(got) == "before", "restored content = %q, %v; want before", got, err) } } @@ -700,31 +483,22 @@ func TestRevertSkipsExistingFileSnapshotWithoutRecordedLastWriteIdentity(t *test store := newTestCodeGroup(t) projectDir := t.TempDir() path := filepath.Join(projectDir, "target.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) const turn = 1 snapshotEntry(t, store, turn, path, path) - if err := os.WriteFile(path, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("after"), 0o644)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 0 { - t.Fatalf("restored = %v, want none", affected.Restored) + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == path, "restored = %v, want none; skipped = %+v, want %s", affected.Restored, affected, path) + { + got, err := os.ReadFile(path) + testassert.Require(t, err == nil && string(got) == "after", "path content = %q, %v; want after", got, err) } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != path { - t.Fatalf("skipped = %+v, want %s", affected, path) - } - if got, err := os.ReadFile(path); err != nil || string(got) != "after" { - t.Fatalf("path content = %q, %v; want after", got, err) - } - if got := readIntDirs(store.snapshotsDir); !reflect.DeepEqual(got, []int{turn}) { - t.Fatalf("snapshot dirs = %v, want [%d]", got, turn) + { + got := readIntDirs(store.snapshotsDir) + testassert.Require(t, reflect.DeepEqual(got, []int{turn}), "snapshot dirs = %v, want [%d]", got, turn) } } @@ -733,41 +507,26 @@ func TestRevertOldSnapshotMetaRestoresCurrentSymlinkTargetWhenHashMatches(t *tes projectDir := t.TempDir() originalTarget := filepath.Join(projectDir, "original.txt") linkPath := filepath.Join(projectDir, "link.txt") - if err := os.WriteFile(originalTarget, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(originalTarget, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalTarget, []byte("before"), 0o644)) + testassert.NoError(t, os.Symlink(originalTarget, linkPath)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(originalTarget)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := copyFile(originalTarget, filepath.Join(entryDir, "original")); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, copyFile(originalTarget, filepath.Join(entryDir, "original"))) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ OriginalPath: linkPath, Existed: true, LastWrite: &SnapshotContentIdentity{Hash: hashBytes([]byte("after"))}, - }); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(originalTarget, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } + })) + testassert.NoError(t, os.WriteFile(originalTarget, []byte("after"), 0o644)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(affected.Restored, []string{linkPath}) { - t.Fatalf("affected = %v, want [%s]", affected, linkPath) - } - if got, err := os.ReadFile(originalTarget); err != nil || string(got) != "before" { - t.Fatalf("restored content = %q, %v; want before", got, err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{linkPath}), "affected = %v, want [%s]", affected, linkPath) + { + got, err := os.ReadFile(originalTarget) + testassert.Require(t, err == nil && string(got) == "before", "restored content = %q, %v; want before", got, err) } } @@ -776,35 +535,19 @@ func TestRevertOldSnapshotMetaRefusesGoneSymlinkTarget(t *testing.T) { projectDir := t.TempDir() originalTarget := filepath.Join(projectDir, "original.txt") linkPath := filepath.Join(projectDir, "link.txt") - if err := os.WriteFile(originalTarget, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(originalTarget, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalTarget, []byte("before"), 0o644)) + testassert.NoError(t, os.Symlink(originalTarget, linkPath)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(originalTarget)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := copyFile(originalTarget, filepath.Join(entryDir, "original")); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: linkPath, Existed: true}); err != nil { - t.Fatal(err) - } - if err := os.Remove(linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, copyFile(originalTarget, filepath.Join(entryDir, "original"))) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: linkPath, Existed: true})) + testassert.NoError(t, os.Remove(linkPath)) affected, err := store.RevertCode(0) - if err == nil || !strings.Contains(err.Error(), "target missing and cannot be proven") { - t.Fatalf("RevertCode error = %v, want missing legacy target refusal", err) - } - if len(affected.Restored) != 0 { - t.Fatalf("affected = %v, want none on refused restore", affected) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "target missing and cannot be proven"), "RevertCode error = %v, want missing legacy target refusal", err) + testassert.Require(t, len(affected.Restored) == 0, "affected = %v, want none on refused restore", affected) } func TestRevertOldSnapshotMetaRefusesCurrentSymlinkPath(t *testing.T) { @@ -813,49 +556,29 @@ func TestRevertOldSnapshotMetaRefusesCurrentSymlinkPath(t *testing.T) { originalTarget := filepath.Join(projectDir, "original.txt") repointedTarget := filepath.Join(projectDir, "repointed.txt") linkPath := filepath.Join(projectDir, "link.txt") - if err := os.WriteFile(originalTarget, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(repointedTarget, []byte("other"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(originalTarget, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalTarget, []byte("before"), 0o644)) + testassert.NoError(t, os.WriteFile(repointedTarget, []byte("other"), 0o644)) + testassert.NoError(t, os.Symlink(originalTarget, linkPath)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(originalTarget)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := copyFile(originalTarget, filepath.Join(entryDir, "original")); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: linkPath, Existed: true}); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(originalTarget, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Remove(linkPath); err != nil { - t.Fatal(err) - } - if err := os.Symlink(repointedTarget, linkPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, copyFile(originalTarget, filepath.Join(entryDir, "original"))) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: linkPath, Existed: true})) + testassert.NoError(t, os.WriteFile(originalTarget, []byte("after"), 0o644)) + testassert.NoError(t, os.Remove(linkPath)) + testassert.NoError(t, os.Symlink(repointedTarget, linkPath)) affected, err := store.RevertCode(0) - if err == nil || !strings.Contains(err.Error(), "legacy snapshot target hash changed") { - t.Fatalf("RevertCode error = %v, want legacy hash-change refusal", err) - } - if len(affected.Restored) != 0 { - t.Fatalf("affected = %v, want none on refused restore", affected) - } - if got, err := os.ReadFile(originalTarget); err != nil || string(got) != "after" { - t.Fatalf("original target = %q, %v; want after", got, err) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "legacy snapshot target hash changed"), "RevertCode error = %v, want legacy hash-change refusal", err) + testassert.Require(t, len(affected.Restored) == 0, "affected = %v, want none on refused restore", affected) + { + got, err := os.ReadFile(originalTarget) + testassert.Require(t, err == nil && string(got) == "after", "original target = %q, %v; want after", got, err) } - if got, err := os.ReadFile(repointedTarget); err != nil || string(got) != "other" { - t.Fatalf("repointed target = %q, %v; want other", got, err) + { + got, err := os.ReadFile(repointedTarget) + testassert.Require(t, err == nil && string(got) == "other", "repointed target = %q, %v; want other", got, err) } } @@ -864,39 +587,23 @@ func TestRevertModernSnapshotSkipsFinalTargetSwappedToSymlink(t *testing.T) { projectDir := t.TempDir() target := filepath.Join(projectDir, "target.txt") secret := filepath.Join(projectDir, "secret.txt") - if err := os.WriteFile(target, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(secret, []byte("secret"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("before"), 0o644)) + testassert.NoError(t, os.WriteFile(secret, []byte("secret"), 0o644)) const turn = 1 entryID := snapshotEntry(t, store, turn, target, target) - if err := os.WriteFile(target, []byte("after"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("after"), 0o644)) recordSnapshotContent(t, store, turn, entryID, "after") - if err := os.Remove(target); err != nil { - t.Fatal(err) - } - if err := os.Symlink(secret, target); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(target)) + testassert.NoError(t, os.Symlink(secret, target)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v, want skipped symlink replacement", err) - } - if len(affected.Restored) != 0 { - t.Fatalf("restored = %v, want none", affected.Restored) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != target { - t.Fatalf("skipped = %+v, want %s", affected, target) - } - if got, err := os.ReadFile(secret); err != nil || string(got) != "secret" { - t.Fatalf("secret target = %q, %v; want unchanged", got, err) + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == target, "restored = %v, want none; skipped = %+v, want %s", affected.Restored, affected, target) + { + got, err := os.ReadFile(secret) + testassert.Require(t, err == nil && string(got) == "secret", "secret target = %q, %v; want unchanged", got, err) } } @@ -905,32 +612,23 @@ func TestRevertExistedFalseSkipsSymlinkWithoutRecordedIdentity(t *testing.T) { projectDir := t.TempDir() createdPath := filepath.Join(projectDir, "created.txt") secret := filepath.Join(projectDir, "secret.txt") - if err := os.WriteFile(secret, []byte("secret"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(secret, []byte("secret"), 0o644)) const turn = 1 snapshotEntry(t, store, turn, createdPath, createdPath) - if err := os.Symlink(secret, createdPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(secret, createdPath)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == createdPath, "restored = %v, want none; skipped = %+v, want %s", affected.Restored, affected, createdPath) + { + _, err := os.Lstat(createdPath) + testassert.NoError(t, err) } - if len(affected.Restored) != 0 { - t.Fatalf("restored = %v, want none", affected.Restored) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != createdPath { - t.Fatalf("skipped = %+v, want %s", affected, createdPath) - } - if _, err := os.Lstat(createdPath); err != nil { - t.Fatalf("created symlink should be preserved, lstat err = %v", err) - } - if got, err := os.ReadFile(secret); err != nil || string(got) != "secret" { - t.Fatalf("secret target = %q, %v; want unchanged", got, err) + { + got, err := os.ReadFile(secret) + testassert.Require(t, err == nil && string(got) == "secret", "secret target = %q, %v; want unchanged", got, err) } } @@ -941,46 +639,32 @@ func TestRevertExistedFalseRefusesRepointedParent(t *testing.T) { repointedRoot := filepath.Join(projectDir, "repointed") linkRoot := filepath.Join(projectDir, "link") for _, dir := range []string{originalRoot, repointedRoot} { - if err := os.Mkdir(dir, 0o755); err != nil { - t.Fatal(err) - } - } - if err := os.Symlink(originalRoot, linkRoot); err != nil { - t.Fatal(err) + testassert.NoError(t, os.Mkdir(dir, 0o755)) } + testassert.NoError(t, os.Symlink(originalRoot, linkRoot)) requestedPath := filepath.Join(linkRoot, "created.txt") originalTarget := filepath.Join(originalRoot, "created.txt") repointedTarget := filepath.Join(repointedRoot, "created.txt") const turn = 1 entryID := snapshotEntry(t, store, turn, requestedPath, originalTarget) - if err := os.WriteFile(originalTarget, []byte("original"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalTarget, []byte("original"), 0o644)) recordSnapshotContent(t, store, turn, entryID, "original") - if err := os.WriteFile(repointedTarget, []byte("repointed"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Remove(linkRoot); err != nil { - t.Fatal(err) - } - if err := os.Symlink(repointedRoot, linkRoot); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(repointedTarget, []byte("repointed"), 0o644)) + testassert.NoError(t, os.Remove(linkRoot)) + testassert.NoError(t, os.Symlink(repointedRoot, linkRoot)) affected, err := store.RevertCode(0) - if err == nil || !strings.Contains(err.Error(), "canonical path changed") { - t.Fatalf("RevertCode error = %v, want canonical path changed error", err) - } - if len(affected.Restored) != 0 { - t.Fatalf("affected = %v, want none on refused delete", affected) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "canonical path changed"), "RevertCode error = %v, want canonical path changed error", err) + testassert.Require(t, len(affected.Restored) == 0, "affected = %v, want none on refused delete", affected) + { + got, err := os.ReadFile(originalTarget) + testassert.Require(t, err == nil && string(got) == "original", "original target = %q, %v; want preserved", got, err) } - if got, err := os.ReadFile(originalTarget); err != nil || string(got) != "original" { - t.Fatalf("original target = %q, %v; want preserved", got, err) - } - if got, err := os.ReadFile(repointedTarget); err != nil || string(got) != "repointed" { - t.Fatalf("repointed target = %q, %v; want preserved", got, err) + { + got, err := os.ReadFile(repointedTarget) + testassert.Require(t, err == nil && string(got) == "repointed", "repointed target = %q, %v; want preserved", got, err) } } @@ -991,27 +675,21 @@ func TestRevertCodeRestoresLaterSnapshotsAndDeletesLaterSnapshotDirs(t *testing. // The empty turn-1 artifact directory is seeded explicitly: turn identity // is named by the test, not allocated. - if err := os.MkdirAll(filepath.Join(store.snapshotsDir, "1"), 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(store.snapshotsDir, "1"), 0o700)) entryID := snapshotEntry(t, store, 2, path, path) - if err := os.WriteFile(path, []byte("new"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("new"), 0o644)) recordSnapshotContent(t, store, 2, entryID, "new") affected, err := store.RevertCode(1) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(affected.Restored, []string{path}) { - t.Fatalf("affected = %v, want [%s]", affected, path) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{path}), "affected = %v, want [%s]", affected, path) + { + _, err := os.Stat(path) + testassert.Require(t, os.IsNotExist(err), "created file should be removed after RevertCode, stat err = %v", err) } - if _, err := os.Stat(path); !os.IsNotExist(err) { - t.Fatalf("created file should be removed after RevertCode, stat err = %v", err) - } - if got := readIntDirs(store.snapshotsDir); !reflect.DeepEqual(got, []int{1}) { - t.Fatalf("snapshot dirs = %v, want [1]", got) + { + got := readIntDirs(store.snapshotsDir) + testassert.Require(t, reflect.DeepEqual(got, []int{1}), "snapshot dirs = %v, want [1]", got) } } @@ -1024,13 +702,9 @@ func TestRevertCodeReportsRestoredAndSkippedStateInSameTurn(t *testing.T) { pathB := filepath.Join(projectDir, "fileB.txt") secret := filepath.Join(projectDir, "secret.txt") for _, p := range []string{pathA, pathB} { - if err := os.WriteFile(p, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - } - if err := os.WriteFile(secret, []byte("secret"), 0o644); err != nil { - t.Fatal(err) + testassert.NoError(t, os.WriteFile(p, []byte("before"), 0o644)) } + testassert.NoError(t, os.WriteFile(secret, []byte("secret"), 0o644)) const turn = 1 entryA := snapshotEntry(t, store, turn, pathA, pathA) @@ -1042,28 +716,18 @@ func TestRevertCodeReportsRestoredAndSkippedStateInSameTurn(t *testing.T) { // dir name = hashString(EvalSymlinks(canonicalPath))). Resolve symlinks // before hashing so the prediction holds when TMPDIR itself is symlinked. realA, err := filepath.EvalSymlinks(pathA) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) realB, err := filepath.EvalSymlinks(pathB) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) firstPath, secondPath := pathA, pathB if hashString(realA) > hashString(realB) { firstPath, secondPath = pathB, pathA } - if err := os.Remove(secondPath); err != nil { - t.Fatal(err) - } - if err := os.Symlink(secret, secondPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(secondPath)) + testassert.NoError(t, os.Symlink(secret, secondPath)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v, want skipped symlink target", err) - } + testassert.NoError(t, err) containsFirst := false for _, p := range affected.Restored { if p == firstPath { @@ -1071,12 +735,8 @@ func TestRevertCodeReportsRestoredAndSkippedStateInSameTurn(t *testing.T) { break } } - if !containsFirst { - t.Fatalf("affected = %v, want to include %q (already-restored entry before mid-turn failure)", affected, firstPath) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != secondPath { - t.Fatalf("skipped = %+v, want %s", affected, secondPath) - } + testassert.Require(t, containsFirst, "affected = %v, want to include %q (already-restored entry before mid-turn failure)", affected, firstPath) + testassert.Require(t, len(affected.Skipped) == 1 && affected.Skipped[0].Path == secondPath, "skipped = %+v, want %s", affected, secondPath) } // One skipped identity blocks every older snapshot entry of the same file @@ -1085,41 +745,28 @@ func TestRevertSkipBlocksOlderSnapshotForSameFile(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() path := filepath.Join(projectDir, "file.txt") - if err := os.WriteFile(path, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("before"), 0o644)) entry1 := snapshotEntry(t, store, 1, path, path) - if err := os.WriteFile(path, []byte("first"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("first"), 0o644)) recordSnapshotContent(t, store, 1, entry1, "first") entry2 := snapshotEntry(t, store, 2, path, path) - if err := os.WriteFile(path, []byte("second"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("second"), 0o644)) recordSnapshotContent(t, store, 2, entry2, "second") - if err := os.WriteFile(path, []byte("first"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("first"), 0o644)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 0 { - t.Fatalf("restored = %v, want none", affected.Restored) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != path { - t.Fatalf("skipped = %+v, want one skipped %s", affected, path) - } - if got, err := os.ReadFile(path); err != nil || string(got) != "first" { - t.Fatalf("path content = %q, %v; want first", got, err) - } - if got := readIntDirs(store.snapshotsDir); !reflect.DeepEqual(got, []int{1, 2}) { - t.Fatalf("snapshot dirs = %v, want [1 2]", got) + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == path, "restored = %v, want none; skipped = %+v, want one skipped %s", affected.Restored, affected, path) + { + got, err := os.ReadFile(path) + testassert.Require(t, err == nil && string(got) == "first", "path content = %q, %v; want first", got, err) + } + { + got := readIntDirs(store.snapshotsDir) + testassert.Require(t, reflect.DeepEqual(got, []int{1, 2}), "snapshot dirs = %v, want [1 2]", got) } } diff --git a/internal/snapshot/list_changed_files_test.go b/internal/snapshot/list_changed_files_test.go index b7a07ac2..ac8fd1b5 100644 --- a/internal/snapshot/list_changed_files_test.go +++ b/internal/snapshot/list_changed_files_test.go @@ -6,6 +6,8 @@ import ( "slices" "strconv" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // The ListChangedFiles contract tests: the durable identity shape on the @@ -22,9 +24,7 @@ func seedListGroup(t *testing.T, dataDir, sessionID, groupID string, turns map[i for turn, files := range turns { for i, meta := range files { entry := filepath.Join(dataDir, "code", sessionID, groupID, "snapshots", strconv.Itoa(turn), "entry"+strconv.Itoa(i)) - if err := os.MkdirAll(entry, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entry, 0o755)) writeMeta(t, filepath.Join(entry, "meta.json"), meta) } } @@ -33,25 +33,20 @@ func seedListGroup(t *testing.T, dataDir, sessionID, groupID string, turns map[i func writeMeta(t *testing.T, path string, meta SnapshotMeta) { t.Helper() data := "{\"original_path\":\"" + meta.OriginalPath + "\",\"canonical_path\":\"" + meta.CanonicalPath + "\",\"existed\":true}" - if err := os.WriteFile(path, []byte(data), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(data), 0o600)) } func TestListChangedFilesAbsentSessionIsEmptySuccess(t *testing.T) { paths, err := ListChangedFiles(t.TempDir(), listTestSession) - if err != nil { - t.Fatalf("ListChangedFiles absent session: %v", err) - } - if len(paths) != 0 { - t.Fatalf("paths = %q, want empty", paths) - } + testassert.NoError(t, err) + testassert.Require(t, len(paths) == 0, "paths = %q, want empty", paths) } func TestListChangedFilesSessionIDShape(t *testing.T) { for _, bad := range []string{"", "short", "0123456789abcdef0123456789abcde", "0123456789ABCDEF0123456789ABCDEF", "0123456789abcdef0123456789abcdeg"} { - if _, err := ListChangedFiles(t.TempDir(), bad); err == nil { - t.Errorf("ListChangedFiles(%q) = nil error, want the identity-shape rejection", bad) + { + _, err := ListChangedFiles(t.TempDir(), bad) + testassert.Check(t, err != nil, "ListChangedFiles(%q) = nil error, want the identity-shape rejection", bad) } } } @@ -60,99 +55,61 @@ func TestListChangedFilesEnumerationError(t *testing.T) { dataDir := t.TempDir() // The session path is a file: reading it as a directory is an error other // than not-exist. - if err := os.MkdirAll(filepath.Join(dataDir, "code"), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dataDir, "code", listTestSession), []byte("x"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(dataDir, "code"), 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(dataDir, "code", listTestSession), []byte("x"), 0o600)) paths, err := ListChangedFiles(dataDir, listTestSession) - if err == nil { - t.Fatal("ListChangedFiles on a file session path = nil error, want the enumeration failure") - } - if paths != nil { - t.Fatalf("paths = %q, want nil", paths) - } + testassert.Require(t, err != nil, "ListChangedFiles on a file session path = nil error, want the enumeration failure") + testassert.Require(t, paths == nil, "paths = %q, want nil", paths) } func TestListChangedFilesSkipsInvalidNamesSymlinksAndNonDirectories(t *testing.T) { dataDir := t.TempDir() sessionDir := filepath.Join(dataDir, "code", listTestSession) - if err := os.MkdirAll(sessionDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(sessionDir, 0o755)) // The command-output spill directory: a non-hex sibling of the groups. - if err := os.MkdirAll(filepath.Join(sessionDir, "output"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(sessionDir, "output"), 0o755)) // A valid group whose content would leak if the name check were missing. - if err := os.MkdirAll(filepath.Join(sessionDir, "output", "snapshots", "1", "e"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(sessionDir, "output", "snapshots", "1", "e"), 0o755)) writeMeta(t, filepath.Join(sessionDir, "output", "snapshots", "1", "e", "meta.json"), SnapshotMeta{OriginalPath: "/o", CanonicalPath: "/leaked"}) // A symlinked group directory with real content behind it. real := filepath.Join(t.TempDir(), "realgroup") - if err := os.MkdirAll(filepath.Join(real, "snapshots", "1", "e"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(real, "snapshots", "1", "e"), 0o755)) writeMeta(t, filepath.Join(real, "snapshots", "1", "e", "meta.json"), SnapshotMeta{OriginalPath: "/s", CanonicalPath: "/symlinked"}) - if err := os.Symlink(real, filepath.Join(sessionDir, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(real, filepath.Join(sessionDir, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))) // A non-directory child with a valid group-ID name. - if err := os.WriteFile(filepath.Join(sessionDir, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), []byte("x"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(sessionDir, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), []byte("x"), 0o600)) // A short-named group: content present, name invalid. - if err := os.MkdirAll(filepath.Join(sessionDir, "short", "snapshots", "1", "e"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(sessionDir, "short", "snapshots", "1", "e"), 0o755)) writeMeta(t, filepath.Join(sessionDir, "short", "snapshots", "1", "e", "meta.json"), SnapshotMeta{OriginalPath: "/h", CanonicalPath: "/shortgroup"}) paths, err := ListChangedFiles(dataDir, listTestSession) - if err != nil { - t.Fatalf("ListChangedFiles: %v", err) - } - if len(paths) != 0 { - t.Fatalf("paths = %q, want every invalid-name, symlink and non-directory child skipped", paths) - } + testassert.NoError(t, err) + testassert.Require(t, len(paths) == 0, "paths = %q, want every invalid-name, symlink and non-directory child skipped", paths) } func TestListChangedFilesSkipsMalformedMetadataAndEmptyCanonical(t *testing.T) { dataDir := t.TempDir() sessionDir := filepath.Join(dataDir, "code", listTestSession) group := filepath.Join(sessionDir, "cccccccccccccccccccccccccccccccc", "snapshots", "1", "a") - if err := os.MkdirAll(group, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(group, 0o755)) // Malformed metadata is skipped silently by ListTurns. - if err := os.WriteFile(filepath.Join(group, "meta.json"), []byte("{not json"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(group, "meta.json"), []byte("{not json"), 0o600)) // An empty-CanonicalPath entry is skipped: no legacy-path fallback. empty := filepath.Join(sessionDir, "cccccccccccccccccccccccccccccccc", "snapshots", "1", "b") - if err := os.MkdirAll(empty, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(empty, 0o755)) writeMeta(t, filepath.Join(empty, "meta.json"), SnapshotMeta{OriginalPath: "/legacy-only"}) // The one valid entry. valid := filepath.Join(sessionDir, "cccccccccccccccccccccccccccccccc", "snapshots", "2", "c") - if err := os.MkdirAll(valid, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(valid, 0o755)) writeMeta(t, filepath.Join(valid, "meta.json"), SnapshotMeta{OriginalPath: "/orig", CanonicalPath: "/kept.go"}) paths, err := ListChangedFiles(dataDir, listTestSession) - if err != nil { - t.Fatalf("ListChangedFiles: %v", err) - } - if len(paths) != 1 || paths[0] != "/kept.go" { - t.Fatalf("paths = %q, want [/kept.go] only", paths) - } + testassert.NoError(t, err) + testassert.Require(t, len(paths) == 1 && paths[0] == "/kept.go", "paths = %q, want [/kept.go] only", paths) } func TestListChangedFilesDeduplicatesAndSortsAcrossGroups(t *testing.T) { @@ -169,13 +126,9 @@ func TestListChangedFilesDeduplicatesAndSortsAcrossGroups(t *testing.T) { seedListGroup(t, dataDir, listTestSession, "dddddddddddddddddddddddddddddddd", groups()) seedListGroup(t, dataDir, listTestSession, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", groups()) paths, err := ListChangedFiles(dataDir, listTestSession) - if err != nil { - t.Fatalf("ListChangedFiles: %v", err) - } + testassert.NoError(t, err) want := []string{"/a.go", "/m.go", "/z.go"} - if !slices.Equal(paths, want) { - t.Fatalf("paths = %q, want %q", paths, want) - } + testassert.Require(t, slices.Equal(paths, want), "paths = %q, want %q", paths, want) } func TestListChangedFilesTurnErrorDiscardsResult(t *testing.T) { @@ -191,25 +144,17 @@ func TestListChangedFilesTurnErrorDiscardsResult(t *testing.T) { // entries, and the complete result — including the earlier group's // path — must be discarded. turnDir := filepath.Join(sessionDir, "ffffffffffffffffffffffffffffffff", "snapshots", "1", "e") - if err := os.MkdirAll(turnDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(turnDir, 0o755)) writeMeta(t, filepath.Join(turnDir, "meta.json"), SnapshotMeta{OriginalPath: "/x", CanonicalPath: "/x.go"}) if os.Geteuid() == 0 { t.Skip("directory permissions do not block reads as root") } - if err := os.Chmod(filepath.Join(sessionDir, "ffffffffffffffffffffffffffffffff", "snapshots", "1"), 0o000); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Chmod(filepath.Join(sessionDir, "ffffffffffffffffffffffffffffffff", "snapshots", "1"), 0o000)) t.Cleanup(func() { _ = os.Chmod(filepath.Join(sessionDir, "ffffffffffffffffffffffffffffffff", "snapshots", "1"), 0o755) }) paths, err := ListChangedFiles(dataDir, listTestSession) - if err == nil { - t.Fatal("ListChangedFiles = nil error, want the turn-read failure") - } - if paths != nil { - t.Fatalf("paths = %q, want the complete result discarded", paths) - } + testassert.Require(t, err != nil, "ListChangedFiles = nil error, want the turn-read failure") + testassert.Require(t, paths == nil, "paths = %q, want the complete result discarded", paths) } diff --git a/internal/snapshot/snapshot_restore_security_test.go b/internal/snapshot/snapshot_restore_security_test.go index 954fa654..8c287878 100644 --- a/internal/snapshot/snapshot_restore_security_test.go +++ b/internal/snapshot/snapshot_restore_security_test.go @@ -7,6 +7,8 @@ import ( "strings" "syscall" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestRevertSkipsCreatedDirectoryLeavesWithoutRecordedIdentity(t *testing.T) { @@ -16,20 +18,11 @@ func TestRevertSkipsCreatedDirectoryLeavesWithoutRecordedIdentity(t *testing.T) createdPath := filepath.Join(projectDir, "created-dir") const turn = 1 snapshotEntry(t, store, turn, createdPath, createdPath) - if err := os.Mkdir(createdPath, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(createdPath, 0o755)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 0 { - t.Fatalf("restored = %v, want none", affected.Restored) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != createdPath { - t.Fatalf("skipped = %+v, want %s", affected.Skipped, createdPath) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == createdPath, "restored = %v, want none; skipped = %+v, want %s", affected.Restored, affected.Skipped, createdPath) if info, err := os.Lstat(createdPath); err != nil || !info.IsDir() { t.Fatalf("created directory should be preserved, stat = %v isDir=%v", err, err == nil && info.IsDir()) } @@ -42,25 +35,15 @@ func TestRevertSkipsCreatedDirectoryLeavesWithoutRecordedIdentity(t *testing.T) childPath := filepath.Join(createdPath, "child.txt") const turn = 1 snapshotEntry(t, store, turn, createdPath, createdPath) - if err := os.Mkdir(createdPath, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(childPath, []byte("child"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(createdPath, 0o755)) + testassert.NoError(t, os.WriteFile(childPath, []byte("child"), 0o644)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 0 { - t.Fatalf("restored = %v, want none", affected.Restored) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != createdPath { - t.Fatalf("skipped = %+v, want %s", affected.Skipped, createdPath) - } - if got, err := os.ReadFile(childPath); err != nil || string(got) != "child" { - t.Fatalf("child content = %q, %v; want preserved", got, err) + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == createdPath, "restored = %v, want none; skipped = %+v, want %s", affected.Restored, affected.Skipped, createdPath) + { + got, err := os.ReadFile(childPath) + testassert.Require(t, err == nil && string(got) == "child", "child content = %q, %v; want preserved", got, err) } }) } @@ -72,63 +55,41 @@ func TestLegacyCreatedDeleteRequiresCleanPathHash(t *testing.T) { t.Run("hash match still refuses", func(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() - if err := os.Mkdir(filepath.Join(projectDir, "sub"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(filepath.Join(projectDir, "sub"), 0o755)) cleanPath := filepath.Join(projectDir, "created.txt") dirtyPath := filepath.Join(projectDir, "sub") + string(filepath.Separator) + ".." + string(filepath.Separator) + "created.txt" - if err := os.WriteFile(cleanPath, []byte("created"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(cleanPath, []byte("created"), 0o644)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(filepath.Clean(dirtyPath))) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: dirtyPath, Existed: false}); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: dirtyPath, Existed: false})) _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded for legacy Existed:false with hash match; want unconditional refusal") - } - if !strings.Contains(err.Error(), "legacy delete refused") { - t.Fatalf("error %v does not mention legacy delete refused", err) - } - if got, readErr := os.ReadFile(cleanPath); readErr != nil || string(got) != "created" { - t.Fatalf("file content = %q, %v; want unchanged after refused legacy delete", got, readErr) + testassert.Require(t, err != nil, "RevertCode succeeded for legacy Existed:false with hash match; want unconditional refusal") + testassert.Require(t, strings.Contains(err.Error(), "legacy delete refused"), "error %v does not mention legacy delete refused", err) + { + got, readErr := os.ReadFile(cleanPath) + testassert.Require(t, readErr == nil && string(got) == "created", "file content = %q, %v; want unchanged after refused legacy delete", got, readErr) } }) t.Run("hash mismatch refuses", func(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() - if err := os.Mkdir(filepath.Join(projectDir, "sub"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(filepath.Join(projectDir, "sub"), 0o755)) victimClean := filepath.Join(projectDir, "victim.txt") victimDirty := filepath.Join(projectDir, "sub") + string(filepath.Separator) + ".." + string(filepath.Separator) + "victim.txt" otherClean := filepath.Join(projectDir, "other.txt") - if err := os.WriteFile(victimClean, []byte("victim"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(victimClean, []byte("victim"), 0o644)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(filepath.Clean(otherClean))) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: victimDirty, Existed: false}); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{OriginalPath: victimDirty, Existed: false})) affected, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded for mismatched legacy delete hash, want refusal") - } - if len(affected.Restored) != 0 { - t.Fatalf("affected = %v, want none", affected) - } - if got, err := os.ReadFile(victimClean); err != nil || string(got) != "victim" { - t.Fatalf("victim content = %q, %v; want unchanged", got, err) + testassert.Require(t, err != nil, "RevertCode succeeded for mismatched legacy delete hash, want refusal") + testassert.Require(t, len(affected.Restored) == 0, "affected = %v, want none", affected) + { + got, err := os.ReadFile(victimClean) + testassert.Require(t, err == nil && string(got) == "victim", "victim content = %q, %v; want unchanged", got, err) } }) } @@ -138,32 +99,21 @@ func TestLegacyMissingLeafRestoreRequiresTargetProof(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() realDir := filepath.Join(projectDir, "real") - if err := os.Mkdir(realDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(realDir, 0o755)) linkDir := filepath.Join(projectDir, "linked") - if err := os.Symlink(realDir, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realDir, linkDir)) target := filepath.Join(realDir, "file.txt") requested := filepath.Join(linkDir, "file.txt") - if err := os.WriteFile(target, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("before"), 0o644)) writeLegacySnapshotForMissingLeafProof(t, store, requested, target) - if err := os.Remove(target); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(target)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(affected.Restored, []string{requested}) { - t.Fatalf("affected = %v, want [%s]", affected, requested) - } - if got, err := os.ReadFile(target); err != nil || string(got) != "before" { - t.Fatalf("restored target = %q, %v; want before", got, err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(affected.Restored, []string{requested}), "affected = %v, want [%s]", affected, requested) + { + got, err := os.ReadFile(target) + testassert.Require(t, err == nil && string(got) == "before", "restored target = %q, %v; want before", got, err) } }) @@ -172,45 +122,26 @@ func TestLegacyMissingLeafRestoreRequiresTargetProof(t *testing.T) { projectDir := t.TempDir() realDir := filepath.Join(projectDir, "real") secretDir := filepath.Join(projectDir, "secret") - if err := os.Mkdir(realDir, 0o755); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(secretDir, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(realDir, 0o755)) + testassert.NoError(t, os.Mkdir(secretDir, 0o755)) linkDir := filepath.Join(projectDir, "linked") - if err := os.Symlink(realDir, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realDir, linkDir)) target := filepath.Join(realDir, "file.txt") requested := filepath.Join(linkDir, "file.txt") secretTarget := filepath.Join(secretDir, "file.txt") - if err := os.WriteFile(target, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(secretTarget, []byte("secret"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("before"), 0o644)) + testassert.NoError(t, os.WriteFile(secretTarget, []byte("secret"), 0o644)) writeLegacySnapshotForMissingLeafProof(t, store, requested, target) - if err := os.Remove(target); err != nil { - t.Fatal(err) - } - if err := os.Remove(linkDir); err != nil { - t.Fatal(err) - } - if err := os.Symlink(secretDir, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(target)) + testassert.NoError(t, os.Remove(linkDir)) + testassert.NoError(t, os.Symlink(secretDir, linkDir)) affected, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded for repointed symlink parent, want refusal") - } - if len(affected.Restored) != 0 { - t.Fatalf("affected = %v, want none", affected) - } - if got, err := os.ReadFile(secretTarget); err != nil || string(got) != "secret" { - t.Fatalf("secret target = %q, %v; want unchanged", got, err) + testassert.Require(t, err != nil, "RevertCode succeeded for repointed symlink parent, want refusal") + testassert.Require(t, len(affected.Restored) == 0, "affected = %v, want none", affected) + { + got, err := os.ReadFile(secretTarget) + testassert.Require(t, err == nil && string(got) == "secret", "secret target = %q, %v; want unchanged", got, err) } }) } @@ -218,19 +149,13 @@ func TestLegacyMissingLeafRestoreRequiresTargetProof(t *testing.T) { func writeLegacySnapshotForMissingLeafProof(t *testing.T, store *CodeStore, requested, canonical string) { t.Helper() entryDir := filepath.Join(store.snapshotsDir, "1", hashString(canonical)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := copyFile(canonical, filepath.Join(entryDir, "original")); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, copyFile(canonical, filepath.Join(entryDir, "original"))) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), SnapshotMeta{ OriginalPath: requested, Existed: true, LastWrite: &SnapshotContentIdentity{Absent: true}, - }); err != nil { - t.Fatal(err) - } + })) } // Legacy Existed:false snapshots (no meta.CanonicalPath) must be refused @@ -241,27 +166,18 @@ func TestPR11Closure_LegacyExistedFalseDeletesAreRefused(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() createdPath := filepath.Join(projectDir, "victim.txt") - if err := os.WriteFile(createdPath, []byte("victim-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(createdPath, []byte("victim-content"), 0o644)) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(filepath.Clean(createdPath))) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), - SnapshotMeta{OriginalPath: createdPath, Existed: false}); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), + SnapshotMeta{OriginalPath: createdPath, Existed: false})) _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded for legacy Existed:false, want refusal") - } - if !strings.Contains(err.Error(), "legacy delete refused") { - t.Fatalf("error %v does not mention legacy delete refused", err) - } - if got, err := os.ReadFile(createdPath); err != nil || string(got) != "victim-content" { - t.Fatalf("file content = %q, %v; want unchanged", got, err) + testassert.Require(t, err != nil, "RevertCode succeeded for legacy Existed:false, want refusal") + testassert.Require(t, strings.Contains(err.Error(), "legacy delete refused"), "error %v does not mention legacy delete refused", err) + { + got, err := os.ReadFile(createdPath) + testassert.Require(t, err == nil && string(got) == "victim-content", "file content = %q, %v; want unchanged", got, err) } }) @@ -270,21 +186,13 @@ func TestPR11Closure_LegacyExistedFalseDeletesAreRefused(t *testing.T) { projectDir := t.TempDir() absentPath := filepath.Join(projectDir, "absent.txt") entryDir := filepath.Join(store.snapshotsDir, "1", hashString(filepath.Clean(absentPath))) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatal(err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), - SnapshotMeta{OriginalPath: absentPath, Existed: false}); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), + SnapshotMeta{OriginalPath: absentPath, Existed: false})) _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded for legacy Existed:false with absent target, want refusal") - } - if !strings.Contains(err.Error(), "legacy delete refused") { - t.Fatalf("error %v does not mention legacy delete refused", err) - } + testassert.Require(t, err != nil, "RevertCode succeeded for legacy Existed:false with absent target, want refusal") + testassert.Require(t, strings.Contains(err.Error(), "legacy delete refused"), "error %v does not mention legacy delete refused", err) }) } @@ -295,36 +203,25 @@ func TestPR11Closure_HardlinkRefusedOnSnapshotRestore(t *testing.T) { store := newTestCodeGroup(t) projectDir := t.TempDir() target := filepath.Join(projectDir, "target.txt") - if err := os.WriteFile(target, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("before"), 0o644)) const turn = 1 entryID := snapshotEntry(t, store, turn, target, target) // Replace target with a hardlink to an outside-boundary file. outside := filepath.Join(t.TempDir(), "outside.txt") - if err := os.WriteFile(outside, []byte("outside-content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Remove(target); err != nil { - t.Fatal(err) - } - if err := os.Link(outside, target); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(outside, []byte("outside-content"), 0o644)) + testassert.NoError(t, os.Remove(target)) + testassert.NoError(t, os.Link(outside, target)) recordSnapshotContent(t, store, turn, entryID, "outside-content") _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded with hardlinked restore target; want refusal") - } + testassert.Require(t, err != nil, "RevertCode succeeded with hardlinked restore target; want refusal") msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "hardlink") && !strings.Contains(msg, "link count") && !strings.Contains(msg, "nlink") { - t.Fatalf("error %v does not mention hardlink/link count/nlink", err) - } - if got, err := os.ReadFile(outside); err != nil || string(got) != "outside-content" { - t.Fatalf("outside file = %q, %v; want unchanged", got, err) + testassert.Require(t, strings.Contains(msg, "hardlink") || strings.Contains(msg, "link count") || strings.Contains(msg, "nlink"), "error %v does not mention hardlink/link count/nlink", err) + { + got, err := os.ReadFile(outside) + testassert.Require(t, err == nil && string(got) == "outside-content", "outside file = %q, %v; want unchanged", got, err) } } @@ -340,27 +237,21 @@ func TestPR11Closure_HardlinkRefusedOnSnapshotCreatedDelete(t *testing.T) { entryID := snapshotEntry(t, store, turn, createdPath, createdPath) outside := filepath.Join(t.TempDir(), "outside.txt") - if err := os.WriteFile(outside, []byte("outside-content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Link(outside, createdPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(outside, []byte("outside-content"), 0o644)) + testassert.NoError(t, os.Link(outside, createdPath)) recordSnapshotContent(t, store, turn, entryID, "outside-content") _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded deleting hardlinked created-file leaf; want refusal") - } + testassert.Require(t, err != nil, "RevertCode succeeded deleting hardlinked created-file leaf; want refusal") msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "hardlink") && !strings.Contains(msg, "link count") && !strings.Contains(msg, "nlink") { - t.Fatalf("error %v does not mention hardlink/link count/nlink", err) - } - if _, err := os.Lstat(createdPath); err != nil { - t.Fatalf("created hardlink lstat = %v; want preserved", err) + testassert.Require(t, strings.Contains(msg, "hardlink") || strings.Contains(msg, "link count") || strings.Contains(msg, "nlink"), "error %v does not mention hardlink/link count/nlink", err) + { + _, err := os.Lstat(createdPath) + testassert.NoError(t, err) } - if got, err := os.ReadFile(outside); err != nil || string(got) != "outside-content" { - t.Fatalf("outside file = %q, %v; want unchanged", got, err) + { + got, err := os.ReadFile(outside) + testassert.Require(t, err == nil && string(got) == "outside-content", "outside file = %q, %v; want unchanged", got, err) } } @@ -373,36 +264,29 @@ func TestPR11Closure_HardlinkedSymlinkRefusedOnSnapshotCreatedDelete(t *testing. entryID := snapshotEntry(t, store, turn, createdPath, createdPath) target := filepath.Join(t.TempDir(), "outside-target.txt") - if err := os.WriteFile(target, []byte("outside-target"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("outside-target"), 0o644)) originalLink := filepath.Join(t.TempDir(), "original-link") - if err := os.Symlink(target, originalLink); err != nil { - t.Fatal(err) - } - if err := os.Link(originalLink, createdPath); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(target, originalLink)) + testassert.NoError(t, os.Link(originalLink, createdPath)) assertHardlinkedSymlink(t, originalLink) assertHardlinkedSymlink(t, createdPath) recordSnapshotContent(t, store, turn, entryID, "outside-target") _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded deleting hardlinked symlink created leaf; want refusal") - } + testassert.Require(t, err != nil, "RevertCode succeeded deleting hardlinked symlink created leaf; want refusal") msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "hardlink") && !strings.Contains(msg, "link count") && !strings.Contains(msg, "nlink") { - t.Fatalf("error %v does not mention hardlink/link count/nlink", err) - } - if _, err := os.Lstat(originalLink); err != nil { - t.Fatalf("original symlink lstat = %v; want preserved", err) + testassert.Require(t, strings.Contains(msg, "hardlink") || strings.Contains(msg, "link count") || strings.Contains(msg, "nlink"), "error %v does not mention hardlink/link count/nlink", err) + { + _, err := os.Lstat(originalLink) + testassert.NoError(t, err) } - if _, err := os.Lstat(createdPath); err != nil { - t.Fatalf("created hardlinked symlink lstat = %v; want preserved", err) + { + _, err := os.Lstat(createdPath) + testassert.NoError(t, err) } - if got, err := os.ReadFile(target); err != nil || string(got) != "outside-target" { - t.Fatalf("target file = %q, %v; want unchanged", got, err) + { + got, err := os.ReadFile(target) + testassert.Require(t, err == nil && string(got) == "outside-target", "target file = %q, %v; want unchanged", got, err) } } @@ -415,29 +299,25 @@ func TestPR11Closure_ModernDeleteBoundToEntryID(t *testing.T) { projectDir := t.TempDir() originalPath := filepath.Join(projectDir, "created.txt") victimPath := filepath.Join(projectDir, "victim.txt") - if err := os.WriteFile(victimPath, []byte("victim-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(victimPath, []byte("victim-content"), 0o644)) const turn = 1 snapshotEntry(t, store, turn, originalPath, originalPath) // The capture writes meta with CanonicalPath=originalPath, entryDir=hashString(originalPath). // Tamper meta.json to redirect CanonicalPath (and OriginalPath) to victimPath. entryDir := filepath.Join(store.snapshotsDir, "1", hashString(filepath.Clean(originalPath))) - if _, err := os.Stat(entryDir); err != nil { - t.Fatalf("expected snapshot entry dir at %s: %v", entryDir, err) - } - if err := writeJSON(filepath.Join(entryDir, "meta.json"), - SnapshotMeta{OriginalPath: victimPath, CanonicalPath: victimPath, Existed: false}); err != nil { - t.Fatal(err) + { + _, err := os.Stat(entryDir) + testassert.Require(t, err == nil, "expected snapshot entry dir at %s: %v", entryDir, err) } + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), + SnapshotMeta{OriginalPath: victimPath, CanonicalPath: victimPath, Existed: false})) _, err := store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded with tampered meta.json; want refusal") - } - if got, readErr := os.ReadFile(victimPath); readErr != nil || string(got) != "victim-content" { - t.Fatalf("victim file = %q, %v; want unchanged", got, readErr) + testassert.Require(t, err != nil, "RevertCode succeeded with tampered meta.json; want refusal") + { + got, readErr := os.ReadFile(victimPath) + testassert.Require(t, readErr == nil && string(got) == "victim-content", "victim file = %q, %v; want unchanged", got, readErr) } _ = turn } @@ -452,47 +332,35 @@ func TestPR11Closure_ModernRestoreBoundToEntryID(t *testing.T) { projectDir := t.TempDir() originalPath := filepath.Join(projectDir, "original.txt") victimPath := filepath.Join(projectDir, "victim.txt") - if err := os.WriteFile(originalPath, []byte("original-content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(victimPath, []byte("victim-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(originalPath, []byte("original-content"), 0o644)) + testassert.NoError(t, os.WriteFile(victimPath, []byte("victim-content"), 0o644)) const turn = 1 snapshotEntry(t, store, turn, originalPath, originalPath) // Snapshot names the entry dir after hashString(EvalSymlinks(canonicalPath)), // so resolve symlinks before hashing in case TMPDIR itself is symlinked. realOriginal, err := filepath.EvalSymlinks(originalPath) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) realVictim, err := filepath.EvalSymlinks(victimPath) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) entryDir := filepath.Join(store.snapshotsDir, "1", hashString(realOriginal)) - if _, err := os.Stat(entryDir); err != nil { - t.Fatalf("expected snapshot entry dir at %s: %v", entryDir, err) + { + _, err := os.Stat(entryDir) + testassert.Require(t, err == nil, "expected snapshot entry dir at %s: %v", entryDir, err) } // Redirect meta to victimPath, but write its canonical-resolved form so // the tampered entry is self-consistent under ResolveFilePath. Without // the new entry-id binding, the redirect would slip through; with it, // hashString(realVictim) != entryID is the only line of defense. - if err := writeJSON(filepath.Join(entryDir, "meta.json"), - SnapshotMeta{OriginalPath: victimPath, CanonicalPath: realVictim, Existed: true}); err != nil { - t.Fatal(err) - } + testassert.NoError(t, writeJSON(filepath.Join(entryDir, "meta.json"), + SnapshotMeta{OriginalPath: victimPath, CanonicalPath: realVictim, Existed: true})) _, err = store.RevertCode(0) - if err == nil { - t.Fatal("RevertCode succeeded with tampered modern restore meta.json; want refusal") - } - if !strings.Contains(err.Error(), "modern restore refused") { - t.Fatalf("error %v does not mention modern restore refusal", err) - } - if got, readErr := os.ReadFile(victimPath); readErr != nil || string(got) != "victim-content" { - t.Fatalf("victim file = %q, %v; want unchanged", got, readErr) + testassert.Require(t, err != nil, "RevertCode succeeded with tampered modern restore meta.json; want refusal") + testassert.Require(t, strings.Contains(err.Error(), "modern restore refused"), "error %v does not mention modern restore refusal", err) + { + got, readErr := os.ReadFile(victimPath) + testassert.Require(t, readErr == nil && string(got) == "victim-content", "victim file = %q, %v; want unchanged", got, readErr) } _ = turn } @@ -500,9 +368,7 @@ func TestPR11Closure_ModernRestoreBoundToEntryID(t *testing.T) { func assertHardlinkedSymlink(t *testing.T, path string) { t.Helper() info, err := os.Lstat(path) - if err != nil { - t.Fatalf("lstat %s: %v", path, err) - } + testassert.Require(t, err == nil, "lstat %s: %v", path, err) if info.Mode()&os.ModeSymlink == 0 { t.Fatalf("%s mode = %s, want symlink", path, info.Mode()) } @@ -510,7 +376,5 @@ func assertHardlinkedSymlink(t *testing.T, path string) { if !ok { t.Fatalf("%s stat type = %T, want *syscall.Stat_t", path, info.Sys()) } - if st.Nlink <= 1 { - t.Fatalf("%s symlink nlink = %d, want > 1", path, st.Nlink) - } + testassert.Require(t, st.Nlink > 1, "%s symlink nlink = %d, want > 1", path, st.Nlink) } diff --git a/internal/snapshot/snapshot_shape_test.go b/internal/snapshot/snapshot_shape_test.go index 8feb8f17..3c58e868 100644 --- a/internal/snapshot/snapshot_shape_test.go +++ b/internal/snapshot/snapshot_shape_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "golang.org/x/sys/unix" ) @@ -22,9 +23,7 @@ func TestSnapshotResolvedRejectsNonRegularExistingTargets(t *testing.T) { name: "directory", target: func(t *testing.T) string { path := filepath.Join(t.TempDir(), "target-dir") - if err := os.Mkdir(path, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(path, 0o755)) return path }, }, @@ -62,24 +61,14 @@ func TestSnapshotResolvedRejectsNonRegularExistingTargets(t *testing.T) { _, _, err := store.SnapshotResolvedEntry(turn, target, target) return err }) - if err == nil { - t.Fatalf("SnapshotResolved(%s) succeeded, want non-regular target refusal", tc.name) - } + testassert.Require(t, err != nil, "SnapshotResolved(%s) succeeded, want non-regular target refusal", tc.name) msg := strings.ToLower(err.Error()) - if strings.Contains(msg, "did not fail closed before blocking") { - t.Fatalf("SnapshotResolved(%s) error = %v, want refusal before blocking on target I/O", tc.name, err) - } - if !strings.Contains(msg, "non-regular") { - t.Fatalf("SnapshotResolved(%s) error = %v, want explicit non-regular target refusal", tc.name, err) - } + testassert.Require(t, !strings.Contains(msg, "did not fail closed before blocking"), "SnapshotResolved(%s) error = %v, want refusal before blocking on target I/O", tc.name, err) + testassert.Require(t, strings.Contains(msg, "non-regular"), "SnapshotResolved(%s) error = %v, want explicit non-regular target refusal", tc.name, err) entries, readErr := os.ReadDir(filepath.Join(store.snapshotsDir, "1")) - if readErr != nil && !errors.Is(readErr, os.ErrNotExist) { - t.Fatal(readErr) - } - if len(entries) != 0 { - t.Fatalf("snapshot entries = %d, want none for refused %s", len(entries), tc.name) - } + testassert.Require(t, readErr == nil || errors.Is(readErr, os.ErrNotExist), "%v", readErr) + testassert.Require(t, len(entries) == 0, "snapshot entries = %d, want none for refused %s", len(entries), tc.name) if info, statErr := os.Lstat(target); statErr != nil { t.Fatalf("lstat %s = %v, want target preserved", target, statErr) } else if info.Mode().IsRegular() { @@ -94,16 +83,16 @@ func TestSnapshotResolvedStillAllowsMissingAndRegularTargets(t *testing.T) { const turn = 1 missing := filepath.Join(t.TempDir(), "new-file.txt") - if _, _, err := store.SnapshotResolvedEntry(turn, missing, missing); err != nil { - t.Fatalf("SnapshotResolvedEntry missing target error = %v", err) + { + _, _, err := store.SnapshotResolvedEntry(turn, missing, missing) + testassert.NoError(t, err) } regular := filepath.Join(t.TempDir(), "regular.txt") - if err := os.WriteFile(regular, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } - if _, _, err := store.SnapshotResolvedEntry(turn, regular, regular); err != nil { - t.Fatalf("SnapshotResolvedEntry regular target error = %v", err) + testassert.NoError(t, os.WriteFile(regular, []byte("before"), 0o644)) + { + _, _, err := store.SnapshotResolvedEntry(turn, regular, regular) + testassert.NoError(t, err) } } diff --git a/internal/storage/composition_test.go b/internal/storage/composition_test.go index 8aac675b..4cf5b648 100644 --- a/internal/storage/composition_test.go +++ b/internal/storage/composition_test.go @@ -16,6 +16,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestStorageComposition constructs memory and a temporary-file SQLite store @@ -37,9 +38,7 @@ func TestStorageComposition(t *testing.T) { name: "sqlite", open: func(t *testing.T) harness.Storage { store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "storage.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) return store }, close: func(t *testing.T, store harness.Storage) { @@ -48,8 +47,9 @@ func TestStorageComposition(t *testing.T) { t.Fatalf("sqlite backend built %T, want *storage.SQLite", store) return } - if err := sqlite.Close(); err != nil { - t.Errorf("Close: %v", err) + { + err := sqlite.Close() + testassert.Check(t, err == nil, "Close: %v", err) } }, }, @@ -77,33 +77,19 @@ func TestStorageComposition(t *testing.T) { }) return err }) - if err != nil { - t.Fatalf("Transact: %v", err) - } + testassert.NoError(t, err) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - if len(entries) != 1 || entries[0].ID != "e1" || entries[0].Sequence != 1 || entries[0].Kind != harness.EntryInput || string(entries[0].Payload) != `{"text":"hello"}` { - t.Fatalf("round-trip entries = %+v, want one input entry e1 at sequence 1 with its original payload", entries) - } + testassert.NoError(t, err) + testassert.Require(t, len(entries) == 1 && entries[0].ID == "e1" && entries[0].Sequence == 1 && entries[0].Kind == harness.EntryInput && string(entries[0].Payload) == `{"text":"hello"}`, "round-trip entries = %+v, want one input entry e1 at sequence 1 with its original payload", entries) register, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: "s1", Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } - if register.Revision != 1 || string(register.Payload) != `{"state":"active"}` { - t.Fatalf("round-trip register = %+v, want revision 1 with its original payload", register) - } + testassert.NoError(t, err) + testassert.Require(t, register.Revision == 1 && string(register.Payload) == `{"state":"active"}`, "round-trip register = %+v, want revision 1 with its original payload", register) ids, err := store.ListSessionIDs(ctx) - if err != nil { - t.Fatalf("ListSessionIDs: %v", err) - } - if len(ids) != 1 || ids[0] != "s1" { - t.Fatalf("ListSessionIDs = %v, want [s1]", ids) - } + testassert.NoError(t, err) + testassert.Require(t, len(ids) == 1 && ids[0] == "s1", "ListSessionIDs = %v, want [s1]", ids) }) } } diff --git a/internal/storage/conformance_test.go b/internal/storage/conformance_test.go index e80626df..5dc63f87 100644 --- a/internal/storage/conformance_test.go +++ b/internal/storage/conformance_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) // conformanceBackend supplies one storage implementation to the shared suite. @@ -108,9 +109,7 @@ func confOpRegister(sessionID, opID string) harness.RegisterDraft { func confTxn(t *testing.T, ctx context.Context, store harness.Storage, fn func(txn harness.Transaction) error) { t.Helper() - if err := store.Transact(ctx, fn); err != nil { - t.Fatalf("Transact: unexpected error: %v", err) - } + testassert.NoError(t, store.Transact(ctx, fn)) } func confCreateSession(t *testing.T, ctx context.Context, store harness.Storage, sessionID string) { @@ -123,16 +122,12 @@ func confCreateSession(t *testing.T, ctx context.Context, store harness.Storage, func confPayloadEqual(t *testing.T, got json.RawMessage, want string) { t.Helper() - if string(got) != want { - t.Fatalf("payload = %s, want %s", got, want) - } + testassert.Require(t, string(got) == want, "payload = %s, want %s", got, want) } func confWantInvalid(t *testing.T, name string, err error) { t.Helper() - if !errors.Is(err, harness.ErrInvalid) { - t.Errorf("%s: error = %v, want ErrInvalid", name, err) - } + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "%s: error = %v, want ErrInvalid", name, err) } // confEnvelopeValidity covers the caller-side envelope rules: closed kinds, @@ -158,8 +153,9 @@ func confEnvelopeValidity(t *testing.T, b conformanceBackend) { }) confWantInvalid(t, "InsertEntry "+name, err) } - if entries, err := store.ReadEntries(ctx, "s1", 0); err != nil || len(entries) != 0 { - t.Fatalf("rejected drafts persisted: %d entries, error %v", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "s1", 0) + testassert.Require(t, err == nil && len(entries) == 0, "rejected drafts persisted: %d entries, error %v", len(entries), err) } validPayloads := []string{`{}`, `null`, `42`, `"text"`, `[1,2,{"k":"v"}]`} @@ -171,16 +167,10 @@ func confEnvelopeValidity(t *testing.T, b conformanceBackend) { }) } entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - if len(entries) != len(validPayloads) { - t.Fatalf("ReadEntries returned %d entries, want %d", len(entries), len(validPayloads)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(entries), len(validPayloads)) for i, entry := range entries { - if entry.Sequence != int64(i+1) { - t.Errorf("entry %s sequence = %d, want %d", entry.ID, entry.Sequence, i+1) - } + testassert.Check(t, entry.Sequence == int64(i+1), "entry %s sequence = %d, want %d", entry.ID, entry.Sequence, i+1) confPayloadEqual(t, entry.Payload, validPayloads[i]) } @@ -191,11 +181,10 @@ func confEnvelopeValidity(t *testing.T, b conformanceBackend) { return err }) entries, err = store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - if got := entries[len(entries)-1]; got.ID != "op-entry" || got.OperationID != "op-1" { - t.Errorf("optional operation identity not round-tripped: %+v", got) + testassert.NoError(t, err) + { + got := entries[len(entries)-1] + testassert.Check(t, got.ID == "op-entry" && got.OperationID == "op-1", "optional operation identity not round-tripped: %+v", got) } invalidRegisters := map[string]harness.RegisterDraft{ @@ -231,8 +220,9 @@ func confEnvelopeValidity(t *testing.T, b conformanceBackend) { _, err := store.ReadRegister(ctx, key) confWantInvalid(t, "ReadRegister "+name, err) } - if _, err := store.ReadRegister(ctx, confSessionKey("missing")); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("ReadRegister missing session: error = %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, confSessionKey("missing")) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "ReadRegister missing session: error = %v, want ErrNotFound", err) } for _, revision := range []int64{0, -1} { @@ -248,14 +238,17 @@ func confEnvelopeValidity(t *testing.T, b conformanceBackend) { }) confWantInvalid(t, "ReplaceRegister bad payload", err) - if _, err := store.ReadEntries(ctx, "s1", -1); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("ReadEntries negative boundary: error = %v, want ErrInvalid", err) + { + _, err := store.ReadEntries(ctx, "s1", -1) + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "ReadEntries negative boundary: error = %v, want ErrInvalid", err) } - if _, err := store.ReadEntries(ctx, "", 0); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("ReadEntries empty session: error = %v, want ErrInvalid", err) + { + _, err := store.ReadEntries(ctx, "", 0) + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "ReadEntries empty session: error = %v, want ErrInvalid", err) } - if _, err := store.ReadRegisters(ctx, ""); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("ReadRegisters empty session: error = %v, want ErrInvalid", err) + { + _, err := store.ReadRegisters(ctx, "") + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "ReadRegisters empty session: error = %v, want ErrInvalid", err) } } @@ -274,9 +267,7 @@ func confPayloadOwnership(t *testing.T, b conformanceBackend) { }) draftPayload[5] = '2' // mutating the caller's bytes must not reach the store entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) confPayloadEqual(t, entries[0].Payload, `{"n":1}`) registerPayload := rawJSON(`{"v":1}`) @@ -288,9 +279,7 @@ func confPayloadOwnership(t *testing.T, b conformanceBackend) { }) registerPayload[5] = '9' register, err := store.ReadRegister(ctx, confOpKey("s1", "op1")) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) confPayloadEqual(t, register.Payload, `{"v":1}`) replacePayload := rawJSON(`{"rev":2}`) @@ -300,31 +289,21 @@ func confPayloadOwnership(t *testing.T, b conformanceBackend) { }) replacePayload[7] = '9' register, err = store.ReadRegister(ctx, confSessionKey("s1")) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) confPayloadEqual(t, register.Payload, `{"rev":2}`) first, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) first[0].Payload[5] = '7' second, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) confPayloadEqual(t, second[0].Payload, `{"n":1}`) g1, err := store.ReadRegister(ctx, confSessionKey("s1")) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) g1.Payload[7] = '8' g2, err := store.ReadRegister(ctx, confSessionKey("s1")) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } + testassert.NoError(t, err) confPayloadEqual(t, g2.Payload, `{"rev":2}`) } @@ -350,9 +329,7 @@ func confEntryIdentityOrder(t *testing.T, b conformanceBackend) { firstSequence, secondSequence = e1.Sequence, e2.Sequence return nil }) - if firstSequence != 1 || secondSequence != 2 { - t.Fatalf("first transaction sequences = %d, %d; want 1, 2", firstSequence, secondSequence) - } + testassert.Require(t, firstSequence == 1 && secondSequence == 2, "first transaction sequences = %d, %d; want 1, 2", firstSequence, secondSequence) var s2Sequence, s1Next int64 confTxn(t, ctx, store, func(txn harness.Transaction) error { @@ -367,50 +344,46 @@ func confEntryIdentityOrder(t *testing.T, b conformanceBackend) { s2Sequence, s1Next = e3.Sequence, e4.Sequence return nil }) - if s2Sequence != 1 { - t.Errorf("second session first sequence = %d, want 1 (per-session counters are independent)", s2Sequence) - } - if s1Next != 3 { - t.Errorf("first session next sequence = %d, want 3", s1Next) - } + testassert.Check(t, s2Sequence == 1, "second session first sequence = %d, want 1 (per-session counters are independent)", s2Sequence) + testassert.Check(t, s1Next == 3, "first session next sequence = %d, want 3", s1Next) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) wantIDs := []string{"e1", "e2", "e4"} for i, entry := range entries { - if entry.ID != wantIDs[i] || entry.Sequence != int64(i+1) { - t.Errorf("entry %d = {%s, seq %d}, want {%s, seq %d}", i, entry.ID, entry.Sequence, wantIDs[i], i+1) - } - if entry.CommittedAt.Location() != time.UTC { - t.Errorf("entry %s commit time %v is not UTC", entry.ID, entry.CommittedAt) - } + testassert.Check(t, entry.ID == wantIDs[i] && entry.Sequence == int64(i+1), "entry %d = {%s, seq %d}, want {%s, seq %d}", i, entry.ID, entry.Sequence, wantIDs[i], i+1) + testassert.Check(t, entry.CommittedAt.Location() == time.UTC, "entry %s commit time %v is not UTC", entry.ID, entry.CommittedAt) } - if tail, err := store.ReadEntries(ctx, "s1", 2); err != nil || len(tail) != 1 || tail[0].ID != "e4" { - t.Errorf("ReadEntries after 2 = %v (error %v), want [e4]", tail, err) + { + tail, err := store.ReadEntries(ctx, "s1", 2) + testassert.Check(t, err == nil && len(tail) == 1 && tail[0].ID == "e4", "ReadEntries after 2 = %v (error %v), want [e4]", tail, err) } - if rest, err := store.ReadEntries(ctx, "s1", 3); err != nil || len(rest) != 0 { - t.Errorf("ReadEntries after last sequence = %v (error %v), want empty", rest, err) + { + rest, err := store.ReadEntries(ctx, "s1", 3) + testassert.Check(t, err == nil && len(rest) == 0, "ReadEntries after last sequence = %v (error %v), want empty", rest, err) } - if s2Entries, err := store.ReadEntries(ctx, "s2", 0); err != nil || len(s2Entries) != 1 || s2Entries[0].ID != "e3" { - t.Errorf("second session entries = %v (error %v), want [e3]", s2Entries, err) + { + s2Entries, err := store.ReadEntries(ctx, "s2", 0) + testassert.Check(t, err == nil && len(s2Entries) == 1 && s2Entries[0].ID == "e3", "second session entries = %v (error %v), want [e3]", s2Entries, err) } - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.InsertEntry(confEntry("s1", "e1")) - return err - }); !errors.Is(err, harness.ErrConflict) { - t.Errorf("duplicate entry id in one session: error = %v, want ErrConflict", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.InsertEntry(confEntry("s1", "e1")) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrConflict), "duplicate entry id in one session: error = %v, want ErrConflict", err) } - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.InsertEntry(confEntry("s2", "e1")) - return err - }); !errors.Is(err, harness.ErrConflict) { - t.Errorf("duplicate entry id across sessions: error = %v, want ErrConflict", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.InsertEntry(confEntry("s2", "e1")) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrConflict), "duplicate entry id across sessions: error = %v, want ErrConflict", err) } - if entries, err := store.ReadEntries(ctx, "s1", 0); err != nil || len(entries) != 3 { - t.Errorf("conflicted transactions changed entries: %d entries (error %v)", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "s1", 0) + testassert.Check(t, err == nil && len(entries) == 3, "conflicted transactions changed entries: %d entries (error %v)", len(entries), err) } } @@ -424,12 +397,8 @@ func confRegisterMechanics(t *testing.T, b conformanceBackend) { key := confSessionKey("s1") register, err := store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } - if register.Revision != 1 || register.Key != key { - t.Fatalf("fresh session register = revision %d key %+v, want revision 1 key %+v", register.Revision, register.Key, key) - } + testassert.NoError(t, err) + testassert.Require(t, register.Revision == 1 && register.Key == key, "fresh session register = revision %d key %+v, want revision 1 key %+v", register.Revision, register.Key, key) confPayloadEqual(t, register.Payload, `{"session":"s1"}`) confTxn(t, ctx, store, func(txn harness.Transaction) error { @@ -437,45 +406,35 @@ func confRegisterMechanics(t *testing.T, b conformanceBackend) { return err }) register, err = store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } - if register.Revision != 2 { - t.Errorf("replacement revision = %d, want 2", register.Revision) - } + testassert.NoError(t, err) + testassert.Check(t, register.Revision == 2, "replacement revision = %d, want 2", register.Revision) confPayloadEqual(t, register.Payload, `{"state":"replaced","extra":[1,2]}`) - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.ReplaceRegister(key, 1, rawJSON(`{"stale":true}`)) - return err - }); !errors.Is(err, harness.ErrConflict) { - t.Errorf("stale replacement: error = %v, want ErrConflict", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.ReplaceRegister(key, 1, rawJSON(`{"stale":true}`)) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrConflict), "stale replacement: error = %v, want ErrConflict", err) } register, err = store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } - if register.Revision != 2 || string(register.Payload) != `{"state":"replaced","extra":[1,2]}` { - t.Errorf("conflicted replacement changed state: revision %d payload %s", register.Revision, register.Payload) - } + testassert.NoError(t, err) + testassert.Check(t, register.Revision == 2 && string(register.Payload) == `{"state":"replaced","extra":[1,2]}`, "conflicted replacement changed state: revision %d payload %s", register.Revision, register.Payload) confTxn(t, ctx, store, func(txn harness.Transaction) error { _, err := txn.ReplaceRegister(key, 2, rawJSON(`{"state":"replaced","extra":[1,2]}`)) return err }) register, err = store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister: %v", err) - } - if register.Revision != 3 { - t.Errorf("unchanged-payload replacement revision = %d, want 3", register.Revision) - } + testassert.NoError(t, err) + testassert.Check(t, register.Revision == 3, "unchanged-payload replacement revision = %d, want 3", register.Revision) - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.InsertRegister(harness.RegisterDraft{Key: key, Payload: confSessionPayload("s1")}) - return err - }); !errors.Is(err, harness.ErrConflict) { - t.Errorf("duplicate register insertion: error = %v, want ErrConflict", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.InsertRegister(harness.RegisterDraft{Key: key, Payload: confSessionPayload("s1")}) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrConflict), "duplicate register insertion: error = %v, want ErrConflict", err) } confCreateSession(t, ctx, store, "s2") @@ -483,14 +442,16 @@ func confRegisterMechanics(t *testing.T, b conformanceBackend) { _, err := txn.InsertRegister(confOpRegister("s1", "op1")) return err }) - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.InsertRegister(confOpRegister("s2", "op1")) - return err - }); !errors.Is(err, harness.ErrConflict) { - t.Errorf("duplicate operation identity across sessions: error = %v, want ErrConflict", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.InsertRegister(confOpRegister("s2", "op1")) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrConflict), "duplicate operation identity across sessions: error = %v, want ErrConflict", err) } - if _, err := store.ReadRegister(ctx, confOpKey("s2", "op1")); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("conflicted operation register exists: error = %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, confOpKey("s2", "op1")) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "conflicted operation register exists: error = %v, want ErrNotFound", err) } } @@ -510,27 +471,32 @@ func confStructuralOwnership(t *testing.T, b conformanceBackend) { _, err := txn.InsertRegister(confOpRegister("fork-origin", "op1")) return err }) - if entries, err := store.ReadEntries(ctx, "fork-origin", 0); err != nil || len(entries) != 1 { - t.Fatalf("same-transaction dependent insertion lost: %d entries (error %v)", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "fork-origin", 0) + testassert.Require(t, err == nil && len(entries) == 1, "same-transaction dependent insertion lost: %d entries (error %v)", len(entries), err) } - if _, err := store.ReadRegister(ctx, confOpKey("fork-origin", "op1")); err != nil { - t.Fatalf("ReadRegister staged in creation transaction: %v", err) + { + _, err := store.ReadRegister(ctx, confOpKey("fork-origin", "op1")) + testassert.NoError(t, err) } - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.InsertEntry(confEntry("ghost", "e1")) - return err - }); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("entry without session register: error = %v, want ErrNotFound", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.InsertEntry(confEntry("ghost", "e1")) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "entry without session register: error = %v, want ErrNotFound", err) } - if err := store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.InsertRegister(confOpRegister("ghost", "op9")) - return err - }); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("operation register without session register: error = %v, want ErrNotFound", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.InsertRegister(confOpRegister("ghost", "op9")) + return err + }) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "operation register without session register: error = %v, want ErrNotFound", err) } - if ids, err := store.ListSessionIDs(ctx); err != nil || len(ids) != 1 || ids[0] != "fork-origin" { - t.Errorf("ListSessionIDs = %v (error %v), want [fork-origin]", ids, err) + { + ids, err := store.ListSessionIDs(ctx) + testassert.Check(t, err == nil && len(ids) == 1 && ids[0] == "fork-origin", "ListSessionIDs = %v (error %v), want [fork-origin]", ids, err) } } @@ -555,30 +521,20 @@ func confStructuralOrphan(t *testing.T, b conformanceBackend) { "ReadRegisterOp": func() error { _, err := store.ReadRegister(ctx, confOpKey("ghost", "op1")); return err }, } { err := read() - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("%s on orphan session: error = %v, want ErrCorrupt", name, err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "%s on orphan session: error = %v, want ErrCorrupt", name, err) var corrupt *harness.CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != "ghost" { - t.Errorf("%s on orphan session: recovered %+v, want owning session ghost", name, corrupt) - } + testassert.Check(t, errors.As(err, &corrupt) && corrupt.SessionID == "ghost", "%s on orphan session: recovered %+v, want owning session ghost", name, corrupt) } ids, err := store.ListSessionIDs(ctx) - if err != nil || len(ids) != 2 || ids[0] != "ghost" || ids[1] != "keeper" { - t.Fatalf("ListSessionIDs = %v (error %v), want [ghost keeper]", ids, err) - } + testassert.Require(t, err == nil && len(ids) == 2 && ids[0] == "ghost" && ids[1] == "keeper", "ListSessionIDs = %v (error %v), want [ghost keeper]", ids, err) err = store.Transact(ctx, func(txn harness.Transaction) error { return txn.DeleteSession("ghost") }) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("DeleteSession on orphan session: error = %v, want ErrCorrupt", err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "DeleteSession on orphan session: error = %v, want ErrCorrupt", err) var corrupt *harness.CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != "ghost" { - t.Errorf("DeleteSession orphan corruption recovered %+v, want owning session ghost", corrupt) - } + testassert.Check(t, errors.As(err, &corrupt) && corrupt.SessionID == "ghost", "DeleteSession orphan corruption recovered %+v, want owning session ghost", corrupt) // Every mutation that meets the orphan state reports the same typed // corruption instead of treating the session as absent, and inserting the @@ -603,29 +559,29 @@ func confStructuralOrphan(t *testing.T, b conformanceBackend) { } for name, mutate := range mutations { err := store.Transact(ctx, func(txn harness.Transaction) error { return mutate(txn) }) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("%s on orphan session: error = %v, want ErrCorrupt", name, err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "%s on orphan session: error = %v, want ErrCorrupt", name, err) var orphanErr *harness.CorruptionError - if !errors.As(err, &orphanErr) || orphanErr.SessionID != "ghost" { - t.Errorf("%s on orphan session recovered %+v, want owning session ghost", name, orphanErr) - } + testassert.Check(t, errors.As(err, &orphanErr) && orphanErr.SessionID == "ghost", "%s on orphan session recovered %+v, want owning session ghost", name, orphanErr) } - if _, err := store.ReadRegister(ctx, confSessionKey("ghost")); !errors.Is(err, harness.ErrCorrupt) { - t.Errorf("orphan session after rejected mutations: error = %v, want ErrCorrupt (the orphan must stay unrepaired)", err) + { + _, err := store.ReadRegister(ctx, confSessionKey("ghost")) + testassert.Check(t, errors.Is(err, harness.ErrCorrupt), "orphan session after rejected mutations: error = %v, want ErrCorrupt (the orphan must stay unrepaired)", err) } - if ids, err := store.ListSessionIDs(ctx); err != nil || len(ids) != 2 || ids[0] != "ghost" || ids[1] != "keeper" { - t.Errorf("rejected mutations changed discovery: %v (error %v)", ids, err) + { + ids, err := store.ListSessionIDs(ctx) + testassert.Check(t, err == nil && len(ids) == 2 && ids[0] == "ghost" && ids[1] == "keeper", "rejected mutations changed discovery: %v (error %v)", ids, err) } - if entries, err := store.ReadEntries(ctx, "keeper", 0); err != nil || len(entries) != 1 { - t.Errorf("keeper entries = %d (error %v), want 1", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "keeper", 0) + testassert.Check(t, err == nil && len(entries) == 1, "keeper entries = %d (error %v), want 1", len(entries), err) } if b.reopen != nil { store = b.reopen(t, store) - if _, err := store.ReadEntries(ctx, "ghost", 0); !errors.Is(err, harness.ErrCorrupt) { - t.Errorf("orphan corruption after reopen: error = %v, want ErrCorrupt", err) + { + _, err := store.ReadEntries(ctx, "ghost", 0) + testassert.Check(t, errors.Is(err, harness.ErrCorrupt), "orphan corruption after reopen: error = %v, want ErrCorrupt", err) } } @@ -633,13 +589,9 @@ func confStructuralOrphan(t *testing.T, b conformanceBackend) { // owning-session corruption on the entries read, not as an absent session. b.orphanOperationRegister(t, store, "ghost-op") _, err = store.ReadEntries(ctx, "ghost-op", 0) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("orphan operation register: error = %v, want ErrCorrupt", err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "orphan operation register: error = %v, want ErrCorrupt", err) var opOrphan *harness.CorruptionError - if !errors.As(err, &opOrphan) || opOrphan.SessionID != "ghost-op" { - t.Errorf("orphan operation register recovered %+v, want owning session ghost-op", opOrphan) - } + testassert.Check(t, errors.As(err, &opOrphan) && opOrphan.SessionID == "ghost-op", "orphan operation register recovered %+v, want owning session ghost-op", opOrphan) // Replacing that exact extant orphan Operation register is the same typed // owning-session corruption, and the orphan stays unchanged: a second @@ -649,17 +601,14 @@ func confStructuralOrphan(t *testing.T, b conformanceBackend) { _, err := txn.ReplaceRegister(confOpKey("ghost-op", "orphan-op"), 1, rawJSON(`{"operation":"replaced"}`)) return err }) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("ReplaceRegister on an extant orphan Operation register: error = %v, want ErrCorrupt", err) - } - if !errors.As(err, &opOrphan) || opOrphan.SessionID != "ghost-op" { - t.Errorf("ReplaceRegister on an extant orphan Operation register recovered %+v, want owning session ghost-op", opOrphan) - } - if err = store.Transact(ctx, func(txn harness.Transaction) error { - _, err := txn.ReplaceRegister(confOpKey("ghost-op", "orphan-op"), 1, rawJSON(`{"operation":"replaced"}`)) - return err - }); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("second ReplaceRegister on the unchanged orphan: error = %v, want ErrCorrupt (a consumed revision would be ErrConflict)", err) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "ReplaceRegister on an extant orphan Operation register: error = %v, want ErrCorrupt", err) + testassert.Check(t, errors.As(err, &opOrphan) && opOrphan.SessionID == "ghost-op", "ReplaceRegister on an extant orphan Operation register recovered %+v, want owning session ghost-op", opOrphan) + { + err = store.Transact(ctx, func(txn harness.Transaction) error { + _, err := txn.ReplaceRegister(confOpKey("ghost-op", "orphan-op"), 1, rawJSON(`{"operation":"replaced"}`)) + return err + }) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "second ReplaceRegister on the unchanged orphan: error = %v, want ErrCorrupt (a consumed revision would be ErrConflict)", err) } } @@ -681,32 +630,25 @@ func confMalformedSessionRegister(t *testing.T, b conformanceBackend) { } for name, read := range reads { err := read() - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("%s on a malformed session register: error = %v, want ErrCorrupt", name, err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "%s on a malformed session register: error = %v, want ErrCorrupt", name, err) var corrupt *harness.CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != "forged" { - t.Errorf("%s on a malformed session register recovered %+v, want owning session forged", name, corrupt) - } + testassert.Check(t, errors.As(err, &corrupt) && corrupt.SessionID == "forged", "%s on a malformed session register recovered %+v, want owning session forged", name, corrupt) } err := store.Transact(ctx, func(txn harness.Transaction) error { _, err := txn.InsertEntry(confEntry("forged", "e1")) return err }) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("InsertEntry on a malformed session register: error = %v, want ErrCorrupt", err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "InsertEntry on a malformed session register: error = %v, want ErrCorrupt", err) ids, err := store.ListSessionIDs(ctx) - if err != nil || len(ids) != 1 || ids[0] != "forged" { - t.Errorf("ListSessionIDs = %v (error %v), want [forged]", ids, err) - } + testassert.Check(t, err == nil && len(ids) == 1 && ids[0] == "forged", "ListSessionIDs = %v (error %v), want [forged]", ids, err) if b.reopen != nil { store = b.reopen(t, store) - if _, err := store.ReadEntries(ctx, "forged", 0); !errors.Is(err, harness.ErrCorrupt) { - t.Errorf("malformed session register after reopen: error = %v, want ErrCorrupt", err) + { + _, err := store.ReadEntries(ctx, "forged", 0) + testassert.Check(t, errors.Is(err, harness.ErrCorrupt), "malformed session register after reopen: error = %v, want ErrCorrupt", err) } } @@ -722,18 +664,12 @@ func confMalformedSessionRegister(t *testing.T, b conformanceBackend) { return nil }) validRegisters, err := store.ReadRegisters(ctx, "valid-parent") - if err != nil || len(validRegisters) != 2 || validRegisters[0].Key.Kind != harness.RegisterSession || validRegisters[1].Key.OperationID != "op1" { - t.Fatalf("valid parent ReadRegisters = %v (error %v), want the canonical Session register followed by the Operation register", validRegisters, err) - } + testassert.Require(t, err == nil && len(validRegisters) == 2 && validRegisters[0].Key.Kind == harness.RegisterSession && validRegisters[1].Key.OperationID == "op1", "valid parent ReadRegisters = %v (error %v), want the canonical Session register followed by the Operation register", validRegisters, err) b.malformedSessionRegister(t, store, "valid-parent") _, err = store.ReadRegisters(ctx, "valid-parent") - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("ReadRegisters beside a valid parent: error = %v, want ErrCorrupt", err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "ReadRegisters beside a valid parent: error = %v, want ErrCorrupt", err) var siblingCorrupt *harness.CorruptionError - if !errors.As(err, &siblingCorrupt) || siblingCorrupt.SessionID != "valid-parent" { - t.Errorf("ReadRegisters beside a valid parent recovered %+v, want owning session valid-parent", siblingCorrupt) - } + testassert.Check(t, errors.As(err, &siblingCorrupt) && siblingCorrupt.SessionID == "valid-parent", "ReadRegisters beside a valid parent recovered %+v, want owning session valid-parent", siblingCorrupt) } // confTransactionReadYourWrites proves transaction reads observe the committed @@ -755,9 +691,7 @@ func confTransactionReadYourWrites(t *testing.T, b conformanceBackend) { if err != nil { return err } - if len(entries) != 2 || entries[0].ID != "e1" || entries[1].ID != "e2" { - t.Errorf("transaction read saw %v, want [e1 e2]", entries) - } + testassert.Check(t, len(entries) == 2 && entries[0].ID == "e1" && entries[1].ID == "e2", "transaction read saw %v, want [e1 e2]", entries) if _, err := txn.InsertRegister(confOpRegister("s1", "op1")); err != nil { return err } @@ -765,18 +699,15 @@ func confTransactionReadYourWrites(t *testing.T, b conformanceBackend) { if err != nil { return err } - if staged.Revision != 1 { - t.Errorf("staged register revision = %d, want 1", staged.Revision) - } + testassert.Check(t, staged.Revision == 1, "staged register revision = %d, want 1", staged.Revision) return nil }) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 2 { - t.Fatalf("committed entries = %d (error %v), want 2", len(entries), err) - } - if _, err := store.ReadRegister(ctx, confOpKey("s1", "op1")); err != nil { - t.Fatalf("committed register read: %v", err) + testassert.Require(t, err == nil && len(entries) == 2, "committed entries = %d (error %v), want 2", len(entries), err) + { + _, err := store.ReadRegister(ctx, confOpKey("s1", "op1")) + testassert.NoError(t, err) } } @@ -804,21 +735,16 @@ func confTransactionRollback(t *testing.T, b conformanceBackend) { } return boom }) - if !errors.Is(err, boom) { - t.Fatalf("Transact error = %v, want the callback error", err) - } + testassert.Require(t, errors.Is(err, boom), "Transact error = %v, want the callback error", err) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 1 || entries[0].ID != "e1" { - t.Fatalf("rollback left %d entries (error %v), want [e1]", len(entries), err) - } + testassert.Require(t, err == nil && len(entries) == 1 && entries[0].ID == "e1", "rollback left %d entries (error %v), want [e1]", len(entries), err) register, err := store.ReadRegister(ctx, confSessionKey("s1")) - if err != nil || register.Revision != 1 { - t.Fatalf("rollback left register revision %d (error %v), want 1", register.Revision, err) - } + testassert.Require(t, err == nil && register.Revision == 1, "rollback left register revision %d (error %v), want 1", register.Revision, err) confPayloadEqual(t, register.Payload, `{"session":"s1"}`) - if _, err := store.ReadRegister(ctx, confOpKey("s1", "op1")); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("rollback left staged register: error = %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, confOpKey("s1", "op1")) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "rollback left staged register: error = %v, want ErrNotFound", err) } var retrySequence int64 @@ -827,9 +753,7 @@ func confTransactionRollback(t *testing.T, b conformanceBackend) { retrySequence = entry.Sequence return err }) - if retrySequence != 2 { - t.Errorf("retry sequence = %d, want 2 (rollback must not consume a sequence)", retrySequence) - } + testassert.Check(t, retrySequence == 2, "retry sequence = %d, want 2 (rollback must not consume a sequence)", retrySequence) } // confTransactionIgnoredConflict proves a mutation conflict latches failure @@ -851,15 +775,15 @@ func confTransactionIgnoredConflict(t *testing.T, b conformanceBackend) { _ = err return nil }) - if !errors.Is(err, harness.ErrConflict) { - t.Fatalf("ignored mutation conflict: Transact error = %v, want ErrConflict", err) - } + testassert.Require(t, errors.Is(err, harness.ErrConflict), "ignored mutation conflict: Transact error = %v, want ErrConflict", err) - if entries, err := store.ReadEntries(ctx, "s1", 0); err != nil || len(entries) != 0 { - t.Errorf("latched conflict committed staged entries: %d (error %v)", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "s1", 0) + testassert.Check(t, err == nil && len(entries) == 0, "latched conflict committed staged entries: %d (error %v)", len(entries), err) } - if _, err := store.ReadRegister(ctx, confOpKey("s1", "dup")); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("latched conflict committed staged register: error = %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, confOpKey("s1", "dup")) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "latched conflict committed staged register: error = %v, want ErrNotFound", err) } } @@ -884,23 +808,17 @@ func confTransactionPanic(t *testing.T, b conformanceBackend) { panic("boom") }) }() - if panicked != "boom" { - t.Fatalf("panic did not propagate, recovered %v", panicked) - } + testassert.Equal(t, panicked, "boom") entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 1 || entries[0].ID != "e1" { - t.Fatalf("panic rollback left %d entries (error %v), want [e1]", len(entries), err) - } + testassert.Require(t, err == nil && len(entries) == 1 && entries[0].ID == "e1", "panic rollback left %d entries (error %v), want [e1]", len(entries), err) var retrySequence int64 confTxn(t, ctx, store, func(txn harness.Transaction) error { entry, err := txn.InsertEntry(confEntry("s1", "e3")) retrySequence = entry.Sequence return err }) - if retrySequence != 2 { - t.Errorf("post-panic insert sequence = %d, want 2 (rollback must not consume a sequence)", retrySequence) - } + testassert.Check(t, retrySequence == 2, "post-panic insert sequence = %d, want 2 (rollback must not consume a sequence)", retrySequence) } // confTransactionContext proves context cancellation rolls back staged writes @@ -921,12 +839,8 @@ func confTransactionContext(t *testing.T, b conformanceBackend) { called = true return nil }) - if !errors.Is(err, context.Canceled) { - t.Errorf("Transact on canceled context: error = %v, want context.Canceled", err) - } - if called { - t.Error("callback invoked on canceled context") - } + testassert.Check(t, errors.Is(err, context.Canceled), "Transact on canceled context: error = %v, want context.Canceled", err) + testassert.Check(t, !called, "callback invoked on canceled context") transactionCtx, cancelTransaction := context.WithCancel(context.Background()) err = store.Transact(transactionCtx, func(txn harness.Transaction) error { @@ -936,13 +850,9 @@ func confTransactionContext(t *testing.T, b conformanceBackend) { cancelTransaction() return nil }) - if !errors.Is(err, context.Canceled) { - t.Errorf("pre-commit cancellation: error = %v, want context.Canceled", err) - } + testassert.Check(t, errors.Is(err, context.Canceled), "pre-commit cancellation: error = %v, want context.Canceled", err) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 1 || entries[0].ID != "e1" { - t.Errorf("cancellation committed staged writes: %d entries (error %v)", len(entries), err) - } + testassert.Check(t, err == nil && len(entries) == 1 && entries[0].ID == "e1", "cancellation committed staged writes: %d entries (error %v)", len(entries), err) reads := map[string]func() error{ "ReadEntries": func() error { _, err := store.ReadEntries(canceled, "s1", 0); return err }, @@ -952,15 +862,17 @@ func confTransactionContext(t *testing.T, b conformanceBackend) { "Transact": func() error { return store.Transact(canceled, func(harness.Transaction) error { return nil }) }, } for name, read := range reads { - if err := read(); !errors.Is(err, context.Canceled) { - t.Errorf("%s on canceled context: error = %v, want context.Canceled", name, err) + { + err := read() + testassert.Check(t, errors.Is(err, context.Canceled), "%s on canceled context: error = %v, want context.Canceled", name, err) } } deadlineCtx, cancelDeadline := context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) defer cancelDeadline() - if _, err := store.ReadEntries(deadlineCtx, "s1", 0); !errors.Is(err, context.DeadlineExceeded) { - t.Errorf("ReadEntries on expired deadline: error = %v, want context.DeadlineExceeded", err) + { + _, err := store.ReadEntries(deadlineCtx, "s1", 0) + testassert.Check(t, errors.Is(err, context.DeadlineExceeded), "ReadEntries on expired deadline: error = %v, want context.DeadlineExceeded", err) } } @@ -1000,15 +912,12 @@ func confTransactionQueuedCancellation(t *testing.T, b conformanceBackend) { <-queuedCtx.Done() // the deadline expires while the queued transaction still waits close(release) - if err := <-first; err != nil { - t.Fatalf("first transaction: %v", err) - } - if err := <-queued; !errors.Is(err, context.DeadlineExceeded) { - t.Errorf("queued transaction error = %v, want context.DeadlineExceeded", err) - } - if queuedCalled { - t.Error("queued transaction callback ran after cancellation") + testassert.NoError(t, <-first) + { + err := <-queued + testassert.Check(t, errors.Is(err, context.DeadlineExceeded), "queued transaction error = %v, want context.DeadlineExceeded", err) } + testassert.Check(t, !queuedCalled, "queued transaction callback ran after cancellation") } // confTransactionBarrier proves readers outside a transaction observe either @@ -1044,21 +953,13 @@ func confTransactionBarrier(t *testing.T, b conformanceBackend) { <-staged entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("outside reader: %v", err) - } + testassert.NoError(t, err) preCommit := len(entries) == 1 && entries[0].ID == "e1" postCommit := len(entries) == 3 && entries[0].ID == "e1" && entries[1].ID == "e2" && entries[2].ID == "e3" - if !preCommit && !postCommit { - t.Errorf("outside reader observed a partial transition %v, want exactly [e1] or [e1 e2 e3]", entries) - } - if err := <-committed; err != nil { - t.Fatalf("Transact: %v", err) - } + testassert.Check(t, preCommit || postCommit, "outside reader observed a partial transition %v, want exactly [e1] or [e1 e2 e3]", entries) + testassert.NoError(t, <-committed) entries, err = store.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 3 || entries[2].ID != "e3" { - t.Errorf("post-commit read = %v (error %v), want the complete committed transition [e1 e2 e3]", entries, err) - } + testassert.Check(t, err == nil && len(entries) == 3 && entries[2].ID == "e3", "post-commit read = %v (error %v), want the complete committed transition [e1 e2 e3]", entries, err) } // confRetainedTransaction proves using a transaction after its callback @@ -1073,14 +974,17 @@ func confRetainedTransaction(t *testing.T, b conformanceBackend) { retained = txn return nil }) - if _, err := retained.ReadEntries("s1", 0); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("retained ReadEntries: error = %v, want ErrInvalid", err) + { + _, err := retained.ReadEntries("s1", 0) + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "retained ReadEntries: error = %v, want ErrInvalid", err) } - if _, err := retained.ReadRegister(confSessionKey("s1")); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("retained ReadRegister: error = %v, want ErrInvalid", err) + { + _, err := retained.ReadRegister(confSessionKey("s1")) + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "retained ReadRegister: error = %v, want ErrInvalid", err) } - if _, err := retained.InsertEntry(confEntry("s1", "e1")); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("retained InsertEntry: error = %v, want ErrInvalid", err) + { + _, err := retained.InsertEntry(confEntry("s1", "e1")) + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "retained InsertEntry: error = %v, want ErrInvalid", err) } } @@ -1104,9 +1008,7 @@ func confMultiSessionTransaction(t *testing.T, b conformanceBackend) { if err != nil { return err } - if len(source) != 2 { - t.Errorf("transaction read of source session saw %d entries, want 2", len(source)) - } + testassert.Check(t, len(source) == 2, "transaction read of source session saw %d entries, want 2", len(source)) if _, err := txn.InsertRegister(harness.RegisterDraft{Key: confSessionKey("dst"), Payload: confSessionPayload("dst")}); err != nil { return err } @@ -1115,13 +1017,9 @@ func confMultiSessionTransaction(t *testing.T, b conformanceBackend) { }) dstEntries, err := store.ReadEntries(ctx, "dst", 0) - if err != nil || len(dstEntries) != 1 || dstEntries[0].ID != "copy" { - t.Fatalf("destination entries = %v (error %v), want [copy]", dstEntries, err) - } + testassert.Require(t, err == nil && len(dstEntries) == 1 && dstEntries[0].ID == "copy", "destination entries = %v (error %v), want [copy]", dstEntries, err) srcEntries, err := store.ReadEntries(ctx, "src", 0) - if err != nil || len(srcEntries) != 2 { - t.Fatalf("source entries = %d (error %v), want 2", len(srcEntries), err) - } + testassert.Require(t, err == nil && len(srcEntries) == 2, "source entries = %d (error %v), want 2", len(srcEntries), err) boom := errors.New("multi-session rollback") err = store.Transact(ctx, func(txn harness.Transaction) error { @@ -1133,14 +1031,14 @@ func confMultiSessionTransaction(t *testing.T, b conformanceBackend) { } return boom }) - if !errors.Is(err, boom) { - t.Fatalf("Transact error = %v, want the callback error", err) - } - if ids, err := store.ListSessionIDs(ctx); err != nil || len(ids) != 2 { - t.Errorf("rollback changed the session set: %v (error %v)", ids, err) + testassert.Require(t, errors.Is(err, boom), "Transact error = %v, want the callback error", err) + { + ids, err := store.ListSessionIDs(ctx) + testassert.Check(t, err == nil && len(ids) == 2, "rollback changed the session set: %v (error %v)", ids, err) } - if entries, err := store.ReadEntries(ctx, "dst", 0); err != nil || len(entries) != 1 { - t.Errorf("rollback changed destination entries: %d (error %v)", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "dst", 0) + testassert.Check(t, err == nil && len(entries) == 1, "rollback changed destination entries: %d (error %v)", len(entries), err) } } @@ -1163,18 +1061,12 @@ func confRevisionConflict(t *testing.T, b conformanceBackend) { _, err := txn.ReplaceRegister(key, 99, rawJSON(`{"stale":true}`)) return err }) - if !errors.Is(err, harness.ErrConflict) { - t.Fatalf("stale preparation: error = %v, want ErrConflict", err) - } + testassert.Require(t, errors.Is(err, harness.ErrConflict), "stale preparation: error = %v, want ErrConflict", err) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 1 || entries[0].ID != "e1" { - t.Errorf("stale replacement consumed a staged sequence: %v (error %v)", entries, err) - } + testassert.Check(t, err == nil && len(entries) == 1 && entries[0].ID == "e1", "stale replacement consumed a staged sequence: %v (error %v)", entries, err) register, err := store.ReadRegister(ctx, key) - if err != nil || register.Revision != 1 { - t.Errorf("stale replacement changed the register: revision %d (error %v)", register.Revision, err) - } + testassert.Check(t, err == nil && register.Revision == 1, "stale replacement changed the register: revision %d (error %v)", register.Revision, err) confPayloadEqual(t, register.Payload, `{"session":"s1"}`) confTxn(t, ctx, store, func(txn harness.Transaction) error { @@ -1182,9 +1074,7 @@ func confRevisionConflict(t *testing.T, b conformanceBackend) { return err }) register, err = store.ReadRegister(ctx, key) - if err != nil || register.Revision != 2 { - t.Errorf("retry at exact revision = revision %d (error %v), want 2", register.Revision, err) - } + testassert.Check(t, err == nil && register.Revision == 2, "retry at exact revision = revision %d (error %v), want 2", register.Revision, err) } // confRecoveryReads covers sorted session discovery, deterministic register @@ -1194,9 +1084,7 @@ func confRecoveryReads(t *testing.T, b conformanceBackend) { store := b.newStore(t) ids, err := store.ListSessionIDs(ctx) - if err != nil || len(ids) != 0 { - t.Fatalf("empty store ListSessionIDs = %v (error %v), want empty", ids, err) - } + testassert.Require(t, err == nil && len(ids) == 0, "empty store ListSessionIDs = %v (error %v), want empty", ids, err) confCreateSession(t, ctx, store, "b") confCreateSession(t, ctx, store, "a") @@ -1212,45 +1100,28 @@ func confRecoveryReads(t *testing.T, b conformanceBackend) { }) ids, err = store.ListSessionIDs(ctx) - if err != nil || len(ids) != 2 || ids[0] != "a" || ids[1] != "b" { - t.Fatalf("ListSessionIDs = %v (error %v), want [a b]", ids, err) - } + testassert.Require(t, err == nil && len(ids) == 2 && ids[0] == "a" && ids[1] == "b", "ListSessionIDs = %v (error %v), want [a b]", ids, err) registers, err := store.ReadRegisters(ctx, "a") - if err != nil { - t.Fatalf("ReadRegisters: %v", err) - } - if len(registers) != 3 { - t.Fatalf("ReadRegisters returned %d registers, want 3", len(registers)) - } - if registers[0].Key.Kind != harness.RegisterSession { - t.Errorf("first register = %+v, want the session register", registers[0].Key) - } - if registers[1].Key.OperationID != "op-a" || registers[2].Key.OperationID != "op-b" { - t.Errorf("operation register order = %s, %s; want op-a, op-b", registers[1].Key.OperationID, registers[2].Key.OperationID) - } + testassert.NoError(t, err) + testassert.Equal(t, len(registers), 3) + testassert.Check(t, registers[0].Key.Kind == harness.RegisterSession, "first register = %+v, want the session register", registers[0].Key) + testassert.Check(t, registers[1].Key.OperationID == "op-a" && registers[2].Key.OperationID == "op-b", "operation register order = %s, %s; want op-a, op-b", registers[1].Key.OperationID, registers[2].Key.OperationID) for _, register := range registers { - if register.Revision != 1 { - t.Errorf("register %+v revision = %d, want 1", register.Key, register.Revision) - } + testassert.Check(t, register.Revision == 1, "register %+v revision = %d, want 1", register.Key, register.Revision) } onlySession, err := store.ReadRegisters(ctx, "b") - if err != nil || len(onlySession) != 1 || onlySession[0].Key.Kind != harness.RegisterSession { - t.Fatalf("register-only session ReadRegisters = %v (error %v), want the session register", onlySession, err) - } - if entries, err := store.ReadEntries(ctx, "b", 0); err != nil || len(entries) != 0 { - t.Errorf("register-only session ReadEntries = %v (error %v), want empty", entries, err) + testassert.Require(t, err == nil && len(onlySession) == 1 && onlySession[0].Key.Kind == harness.RegisterSession, "register-only session ReadRegisters = %v (error %v), want the session register", onlySession, err) + { + entries, err := store.ReadEntries(ctx, "b", 0) + testassert.Check(t, err == nil && len(entries) == 0, "register-only session ReadEntries = %v (error %v), want empty", entries, err) } idsAgain, err := store.ListSessionIDs(ctx) - if err != nil || len(idsAgain) != 2 || idsAgain[0] != "a" || idsAgain[1] != "b" { - t.Errorf("repeated ListSessionIDs = %v (error %v), want [a b]", idsAgain, err) - } + testassert.Check(t, err == nil && len(idsAgain) == 2 && idsAgain[0] == "a" && idsAgain[1] == "b", "repeated ListSessionIDs = %v (error %v), want [a b]", idsAgain, err) reread, err := store.ReadRegisters(ctx, "a") - if err != nil || len(reread) != 3 || reread[2].Key.OperationID != "op-b" || reread[2].Revision != 1 { - t.Errorf("recovery reads mutated state: %v (error %v)", reread, err) - } + testassert.Check(t, err == nil && len(reread) == 3 && reread[2].Key.OperationID == "op-b" && reread[2].Revision == 1, "recovery reads mutated state: %v (error %v)", reread, err) } // confSequenceExhaustion proves sequence advancement beyond math.MaxInt64 @@ -1270,17 +1141,11 @@ func confSequenceExhaustion(t *testing.T, b conformanceBackend) { _, err := txn.InsertEntry(confEntry("s1", "overflow")) return err }) - if !errors.Is(err, harness.ErrStorage) { - t.Fatalf("sequence exhaustion: error = %v, want ErrStorage", err) - } + testassert.Require(t, errors.Is(err, harness.ErrStorage), "sequence exhaustion: error = %v, want ErrStorage", err) entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - if len(entries) != 2 || entries[1].Sequence != math.MaxInt64 { - t.Errorf("aborted exhaustion transaction changed entries: %v", entries) - } + testassert.NoError(t, err) + testassert.Check(t, len(entries) == 2 && entries[1].Sequence == math.MaxInt64, "aborted exhaustion transaction changed entries: %v", entries) } // confRevisionExhaustion proves revision advancement beyond math.MaxInt64 @@ -1294,20 +1159,14 @@ func confRevisionExhaustion(t *testing.T, b conformanceBackend) { b.exhaustRevision(t, store, "s1") register, err := store.ReadRegister(ctx, key) - if err != nil || register.Revision != math.MaxInt64 { - t.Fatalf("fixture revision = %d (error %v), want %d", register.Revision, err, int64(math.MaxInt64)) - } + testassert.Require(t, err == nil && register.Revision == math.MaxInt64, "fixture revision = %d (error %v), want %d", register.Revision, err, int64(math.MaxInt64)) err = store.Transact(ctx, func(txn harness.Transaction) error { _, err := txn.ReplaceRegister(key, math.MaxInt64, rawJSON(`{"next":true}`)) return err }) - if !errors.Is(err, harness.ErrStorage) { - t.Fatalf("revision exhaustion: error = %v, want ErrStorage", err) - } + testassert.Require(t, errors.Is(err, harness.ErrStorage), "revision exhaustion: error = %v, want ErrStorage", err) register, err = store.ReadRegister(ctx, key) - if err != nil || register.Revision != math.MaxInt64 { - t.Fatalf("aborted exhaustion transaction changed revision: %d (error %v)", register.Revision, err) - } + testassert.Require(t, err == nil && register.Revision == math.MaxInt64, "aborted exhaustion transaction changed revision: %d (error %v)", register.Revision, err) confPayloadEqual(t, register.Payload, `{"session":"s1"}`) } @@ -1331,21 +1190,13 @@ func confCorruption(t *testing.T, b conformanceBackend) { b.corruptEntry(t, store, "owner") ids, err := store.ListSessionIDs(ctx) - if err != nil || len(ids) != 2 || ids[0] != "keeper" || ids[1] != "owner" { - t.Fatalf("corrupt state disappeared from discovery: %v (error %v)", ids, err) - } + testassert.Require(t, err == nil && len(ids) == 2 && ids[0] == "keeper" && ids[1] == "owner", "corrupt state disappeared from discovery: %v (error %v)", ids, err) _, err = store.ReadEntries(ctx, "owner", 0) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("corrupt entry: error = %v, want ErrCorrupt", err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "corrupt entry: error = %v, want ErrCorrupt", err) var corrupt *harness.CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != "owner" { - t.Errorf("corrupt entry recovered %+v, want owning session owner", corrupt) - } + testassert.Check(t, errors.As(err, &corrupt) && corrupt.SessionID == "owner", "corrupt entry recovered %+v, want owning session owner", corrupt) keeperEntries, err := store.ReadEntries(ctx, "keeper", 0) - if err != nil || len(keeperEntries) != 1 || keeperEntries[0].ID != "k1" { - t.Errorf("keeper readable entries = %v (error %v), want [k1]", keeperEntries, err) - } + testassert.Check(t, err == nil && len(keeperEntries) == 1 && keeperEntries[0].ID == "k1", "keeper readable entries = %v (error %v), want [k1]", keeperEntries, err) b.corruptRegister(t, store, "owner") @@ -1354,32 +1205,30 @@ func confCorruption(t *testing.T, b conformanceBackend) { "ReadRegisters": func() error { _, err := store.ReadRegisters(ctx, "owner"); return err }, } { err := read() - if !errors.Is(err, harness.ErrCorrupt) { - t.Errorf("%s on corrupt register: error = %v, want ErrCorrupt", name, err) - } + testassert.Check(t, errors.Is(err, harness.ErrCorrupt), "%s on corrupt register: error = %v, want ErrCorrupt", name, err) var corrupt *harness.CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != "owner" { - t.Errorf("%s on corrupt register recovered %+v, want owning session owner", name, corrupt) - } + testassert.Check(t, errors.As(err, &corrupt) && corrupt.SessionID == "owner", "%s on corrupt register recovered %+v, want owning session owner", name, corrupt) } - if _, err := store.ReadRegister(ctx, confSessionKey("keeper")); err != nil { - t.Errorf("keeper register unreadable beside corruption: %v", err) + { + _, err := store.ReadRegister(ctx, confSessionKey("keeper")) + testassert.Check(t, err == nil, "keeper register unreadable beside corruption: %v", err) } ids, err = store.ListSessionIDs(ctx) - if err != nil || len(ids) != 2 { - t.Errorf("register corruption removed discovery: %v (error %v)", ids, err) - } + testassert.Check(t, err == nil && len(ids) == 2, "register corruption removed discovery: %v (error %v)", ids, err) if b.reopen != nil { store = b.reopen(t, store) - if _, err := store.ReadEntries(ctx, "owner", 0); !errors.Is(err, harness.ErrCorrupt) { - t.Errorf("entry corruption after reopen: error = %v, want ErrCorrupt", err) + { + _, err := store.ReadEntries(ctx, "owner", 0) + testassert.Check(t, errors.Is(err, harness.ErrCorrupt), "entry corruption after reopen: error = %v, want ErrCorrupt", err) } - if _, err := store.ReadRegister(ctx, confSessionKey("owner")); !errors.Is(err, harness.ErrCorrupt) { - t.Errorf("register corruption after reopen: error = %v, want ErrCorrupt", err) + { + _, err := store.ReadRegister(ctx, confSessionKey("owner")) + testassert.Check(t, errors.Is(err, harness.ErrCorrupt), "register corruption after reopen: error = %v, want ErrCorrupt", err) } - if entries, err := store.ReadEntries(ctx, "keeper", 0); err != nil || len(entries) != 1 { - t.Errorf("keeper after reopen: %d entries (error %v)", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "keeper", 0) + testassert.Check(t, err == nil && len(entries) == 1, "keeper after reopen: %d entries (error %v)", len(entries), err) } } } @@ -1411,19 +1260,11 @@ func confCompactionPayloadRoundTrip(t *testing.T, b conformanceBackend) { store = b.reopen(t, store) } entries, err := store.ReadEntries(ctx, "s1", 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - if len(entries) != 2 { - t.Fatalf("ReadEntries returned %d entries, want 2", len(entries)) - } - if entries[0].ID != "c1" || entries[0].Kind != harness.EntryCompaction || entries[0].OperationID != "op-1" { - t.Errorf("owned compaction envelope = %+v", entries[0]) - } + testassert.NoError(t, err) + testassert.Equal(t, len(entries), 2) + testassert.Check(t, entries[0].ID == "c1" && entries[0].Kind == harness.EntryCompaction && entries[0].OperationID == "op-1", "owned compaction envelope = %+v", entries[0]) confPayloadEqual(t, entries[0].Payload, owned) - if entries[1].ID != "c2" || entries[1].Kind != harness.EntryCompaction || entries[1].OperationID != "" { - t.Errorf("copied compaction envelope = %+v", entries[1]) - } + testassert.Check(t, entries[1].ID == "c2" && entries[1].Kind == harness.EntryCompaction && entries[1].OperationID == "", "copied compaction envelope = %+v", entries[1]) confPayloadEqual(t, entries[1].Payload, copied) } @@ -1459,34 +1300,39 @@ func confSessionDeletion(t *testing.T, b conformanceBackend) { }) ids, err := store.ListSessionIDs(ctx) - if err != nil || len(ids) != 1 || ids[0] != "kept" { - t.Fatalf("ListSessionIDs after deletion = %v (error %v), want [kept]", ids, err) + testassert.Require(t, err == nil && len(ids) == 1 && ids[0] == "kept", "ListSessionIDs after deletion = %v (error %v), want [kept]", ids, err) + { + _, err := store.ReadRegister(ctx, confSessionKey("gone")) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "deleted session register: error = %v, want ErrNotFound", err) } - if _, err := store.ReadRegister(ctx, confSessionKey("gone")); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("deleted session register: error = %v, want ErrNotFound", err) + { + _, err := store.ReadEntries(ctx, "gone", 0) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "deleted session entries: error = %v, want ErrNotFound", err) } - if _, err := store.ReadEntries(ctx, "gone", 0); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("deleted session entries: error = %v, want ErrNotFound", err) + { + _, err := store.ReadRegisters(ctx, "gone") + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "deleted session registers: error = %v, want ErrNotFound", err) } - if _, err := store.ReadRegisters(ctx, "gone"); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("deleted session registers: error = %v, want ErrNotFound", err) + { + _, err := store.ReadRegister(ctx, confOpKey("gone", "op1")) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "deleted session operation register: error = %v, want ErrNotFound", err) } - if _, err := store.ReadRegister(ctx, confOpKey("gone", "op1")); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("deleted session operation register: error = %v, want ErrNotFound", err) - } - if entries, err := store.ReadEntries(ctx, "kept", 0); err != nil || len(entries) != 1 { - t.Errorf("other session changed by deletion: %d entries (error %v)", len(entries), err) + { + entries, err := store.ReadEntries(ctx, "kept", 0) + testassert.Check(t, err == nil && len(entries) == 1, "other session changed by deletion: %d entries (error %v)", len(entries), err) } - if err := store.Transact(ctx, func(txn harness.Transaction) error { - return txn.DeleteSession("absent") - }); !errors.Is(err, harness.ErrNotFound) { - t.Errorf("DeleteSession absent: error = %v, want ErrNotFound", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + return txn.DeleteSession("absent") + }) + testassert.Check(t, errors.Is(err, harness.ErrNotFound), "DeleteSession absent: error = %v, want ErrNotFound", err) } - if err := store.Transact(ctx, func(txn harness.Transaction) error { - return txn.DeleteSession("") - }); !errors.Is(err, harness.ErrInvalid) { - t.Errorf("DeleteSession empty identity: error = %v, want ErrInvalid", err) + { + err := store.Transact(ctx, func(txn harness.Transaction) error { + return txn.DeleteSession("") + }) + testassert.Check(t, errors.Is(err, harness.ErrInvalid), "DeleteSession empty identity: error = %v, want ErrInvalid", err) } boom := errors.New("deletion rollback") @@ -1496,11 +1342,10 @@ func confSessionDeletion(t *testing.T, b conformanceBackend) { } return boom }) - if !errors.Is(err, boom) { - t.Fatalf("staged deletion Transact error = %v, want the callback error", err) - } - if ids, err := store.ListSessionIDs(ctx); err != nil || len(ids) != 1 || ids[0] != "kept" { - t.Errorf("rolled-back deletion removed the session: %v (error %v)", ids, err) + testassert.Require(t, errors.Is(err, boom), "staged deletion Transact error = %v, want the callback error", err) + { + ids, err := store.ListSessionIDs(ctx) + testassert.Check(t, err == nil && len(ids) == 1 && ids[0] == "kept", "rolled-back deletion removed the session: %v (error %v)", ids, err) } staged := make(chan struct{}) @@ -1516,16 +1361,11 @@ func confSessionDeletion(t *testing.T, b conformanceBackend) { }() <-staged ids, err = store.ListSessionIDs(ctx) // the complete pre-commit or post-commit set, never partial - if err != nil { - t.Fatalf("outside reader during deletion: %v", err) - } - if len(ids) > 1 || (len(ids) == 1 && ids[0] != "kept") { - t.Errorf("outside reader observed a partially deleted session set: %v, want exactly [kept] or empty", ids) - } - if err := <-committed; err != nil { - t.Fatalf("deletion Transact: %v", err) - } - if ids, err = store.ListSessionIDs(ctx); err != nil || len(ids) != 0 { - t.Errorf("session set after committed deletion = %v (error %v), want empty", ids, err) + testassert.NoError(t, err) + testassert.Check(t, len(ids) <= 1 && (len(ids) != 1 || ids[0] == "kept"), "outside reader observed a partially deleted session set: %v, want exactly [kept] or empty", ids) + testassert.NoError(t, <-committed) + { + ids, err = store.ListSessionIDs(ctx) + testassert.Check(t, err == nil && len(ids) == 0, "session set after committed deletion = %v (error %v), want empty", ids, err) } } diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go index 50afcb50..518c108f 100644 --- a/internal/storage/memory_test.go +++ b/internal/storage/memory_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestMemoryConformance runs the shared storage conformance suite against the @@ -59,12 +60,11 @@ func TestMemoryReadCancellationUnderWriterLock(t *testing.T) { <-readCtx.Done() // the deadline expires while the reads still wait for the writer close(release) - if err := <-first; err != nil { - t.Fatalf("writer transaction: %v", err) - } + testassert.NoError(t, <-first) for range reads { - if err := <-readDone; !errors.Is(err, context.DeadlineExceeded) { - t.Errorf("blocked read error = %v, want context.DeadlineExceeded", err) + { + err := <-readDone + testassert.Check(t, errors.Is(err, context.DeadlineExceeded), "blocked read error = %v, want context.DeadlineExceeded", err) } } } diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index f454d8f7..121a6942 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -30,9 +30,9 @@ const ( // sqliteSchemaVersion is the only accepted schema version; it is stamped // into PRAGMA user_version at initialization and checked at open. Version - // 6 is the compaction entry payload and projection state; the SQL tables - // are unchanged. - sqliteSchemaVersion = 6 + // 7 is the changed capture and settlement payloads; the SQL tables are + // unchanged. + sqliteSchemaVersion = 7 // sqlitePoolDSN configures every physical connection of the long-lived // pool: write-ahead logging, full synchronous durability, immediate diff --git a/internal/storage/sqlite_test.go b/internal/storage/sqlite_test.go index 608d88fb..fe729aea 100644 --- a/internal/storage/sqlite_test.go +++ b/internal/storage/sqlite_test.go @@ -24,6 +24,7 @@ import ( "github.com/mattn/go-sqlite3" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestSQLiteConformance runs the shared storage conformance suite against the @@ -37,9 +38,7 @@ func TestSQLiteConformance(t *testing.T) { func newSQLiteStore(t *testing.T) *SQLite { t.Helper() store, err := OpenSQLite(filepath.Join(t.TempDir(), "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { store.Close() }) return store } @@ -55,13 +54,9 @@ func sqliteBackend() conformanceBackend { newStore: func(t *testing.T) harness.Storage { return newSQLiteStore(t) }, reopen: func(t *testing.T, store harness.Storage) harness.Storage { s := store.(*SQLite) - if err := s.Close(); err != nil { - t.Fatalf("close: %v", err) - } + testassert.NoError(t, s.Close()) reopened, err := OpenSQLite(s.path) - if err != nil { - t.Fatalf("reopen %q: %v", s.path, err) - } + testassert.Require(t, err == nil, "reopen %q: %v", s.path, err) t.Cleanup(func() { reopened.Close() }) return reopened }, @@ -70,9 +65,7 @@ func sqliteBackend() conformanceBackend { conn := sqliteFixtureConn(t, s) defer conn.Close() var highest sql.NullInt64 - if err := conn.QueryRowContext(context.Background(), `SELECT MAX(sequence) FROM entries WHERE session_id = ?`, sessionID).Scan(&highest); err != nil { - t.Fatalf("read highest sequence: %v", err) - } + testassert.NoError(t, conn.QueryRowContext(context.Background(), `SELECT MAX(sequence) FROM entries WHERE session_id = ?`, sessionID).Scan(&highest)) sqliteRawExec(t, conn, `PRAGMA ignore_check_constraints = ON`) defer sqliteRawExec(t, conn, `PRAGMA ignore_check_constraints = OFF`) // Valid in every envelope field except the zero commit time. @@ -140,16 +133,15 @@ func sqliteBackend() conformanceBackend { func sqliteFixtureConn(t *testing.T, store *SQLite) *sql.Conn { t.Helper() conn, err := store.db.Conn(context.Background()) - if err != nil { - t.Fatalf("fixture connection: %v", err) - } + testassert.NoError(t, err) return conn } func sqliteRawExec(t *testing.T, conn *sql.Conn, query string, args ...any) { t.Helper() - if _, err := conn.ExecContext(context.Background(), query, args...); err != nil { - t.Fatalf("exec %q: %v", query, err) + { + _, err := conn.ExecContext(context.Background(), query, args...) + testassert.Require(t, err == nil, "exec %q: %v", query, err) } } @@ -157,9 +149,7 @@ func sqliteRawExec(t *testing.T, conn *sql.Conn, query string, args ...any) { func sqliteFileDigest(t *testing.T, path string) string { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %q: %v", path, err) - } + testassert.Require(t, err == nil, "read %q: %v", path, err) return fmt.Sprintf("%x", sha256.Sum256(data)) } @@ -168,8 +158,9 @@ func sqliteFileDigest(t *testing.T, path string) string { func sqliteAssertNoSidecars(t *testing.T, path string) { t.Helper() for _, suffix := range []string{"-wal", "-shm", "-journal"} { - if _, err := os.Stat(path + suffix); !errors.Is(err, fs.ErrNotExist) { - t.Errorf("sidecar %q exists: %v", path+suffix, err) + { + _, err := os.Stat(path + suffix) + testassert.Check(t, errors.Is(err, fs.ErrNotExist), "sidecar %q exists: %v", path+suffix, err) } } } @@ -180,22 +171,22 @@ func sqliteAssertNoSidecars(t *testing.T, path string) { func sqliteFixtureDatabase(t *testing.T, path string, ddl []string, version int) { t.Helper() db, err := sql.Open(sqliteDriverName, path) - if err != nil { - t.Fatalf("open fixture database: %v", err) - } + testassert.NoError(t, err) defer db.Close() for _, stmt := range ddl { - if _, err := db.Exec(stmt); err != nil { - t.Fatalf("exec %q: %v", stmt, err) + { + _, err := db.Exec(stmt) + testassert.Require(t, err == nil, "exec %q: %v", stmt, err) } } - if _, err := db.Exec(fmt.Sprintf("PRAGMA user_version = %d", version)); err != nil { - t.Fatalf("set user_version: %v", err) + { + _, err := db.Exec(fmt.Sprintf("PRAGMA user_version = %d", version)) + testassert.NoError(t, err) } } // TestSQLiteSchemaInitialization proves a missing path is initialized with the -// complete canonical schema in one shot: version 6, exactly the two canonical +// complete canonical schema in one shot: version 7, exactly the two canonical // tables and the one explicit index, only the autoindexes implied by those // tables, a well-formed image, no sidecars after close, and no partial file // when initialization fails. @@ -203,125 +194,94 @@ func TestSQLiteSchemaInitialization(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "lightcode.db") store, err := OpenSQLite(path) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) ctx := context.Background() conn, err := store.db.Conn(ctx) - if err != nil { - t.Fatalf("connection: %v", err) - } + testassert.NoError(t, err) var version int - if err := conn.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&version); err != nil || version != 6 { - t.Fatalf("PRAGMA user_version = %d (error %v), want 6", version, err) + { + err := conn.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&version) + testassert.Require(t, err == nil && version == 7, "PRAGMA user_version = %d (error %v), want 7", version, err) } objects := map[string]string{} rows, err := conn.QueryContext(ctx, `SELECT type, name, sql FROM sqlite_schema WHERE sql IS NOT NULL`) - if err != nil { - t.Fatalf("read schema objects: %v", err) - } + testassert.NoError(t, err) for rows.Next() { var typ, name, ddl string - if err := rows.Scan(&typ, &name, &ddl); err != nil { - t.Fatalf("read schema objects: %v", err) - } + testassert.NoError(t, rows.Scan(&typ, &name, &ddl)) objects[typ+" "+name] = ddl } - if err := rows.Err(); err != nil { - t.Fatalf("read schema objects: %v", err) - } + testassert.NoError(t, rows.Err()) rows.Close() want := map[string]string{ "table entries": sqliteEntriesDDL, "table registers": sqliteRegistersDDL, "index operation_register_id": sqliteOperationIndexDDL, } - if len(objects) != len(want) { - t.Errorf("user-defined schema objects = %v, want exactly %v", objects, want) - } + testassert.Check(t, len(objects) == len(want), "user-defined schema objects = %v, want exactly %v", objects, want) for key, ddl := range want { - if objects[key] != ddl { - t.Errorf("schema object %q = %q, want the canonical definition", key, ddl) - } + testassert.Check(t, objects[key] == ddl, "schema object %q = %q, want the canonical definition", key, ddl) } autoindexes := map[string]bool{} rows, err = conn.QueryContext(ctx, `SELECT name FROM sqlite_schema WHERE type = 'index' AND sql IS NULL`) - if err != nil { - t.Fatalf("read autoindexes: %v", err) - } + testassert.NoError(t, err) defer rows.Close() for rows.Next() { var name string - if err := rows.Scan(&name); err != nil { - t.Fatalf("read autoindexes: %v", err) - } + testassert.NoError(t, rows.Scan(&name)) autoindexes[name] = true } - if err := rows.Err(); err != nil { - t.Fatalf("read autoindexes: %v", err) - } + testassert.NoError(t, rows.Err()) wantAutoindexes := map[string]bool{ "sqlite_autoindex_entries_1": true, "sqlite_autoindex_entries_2": true, "sqlite_autoindex_registers_1": true, } - if len(autoindexes) != len(wantAutoindexes) { - t.Errorf("autoindexes = %v, want exactly %v", autoindexes, wantAutoindexes) - } + testassert.Check(t, len(autoindexes) == len(wantAutoindexes), "autoindexes = %v, want exactly %v", autoindexes, wantAutoindexes) for name := range wantAutoindexes { - if !autoindexes[name] { - t.Errorf("implied autoindex %q missing", name) - } + testassert.Check(t, autoindexes[name], "implied autoindex %q missing", name) } var integrity string - if err := conn.QueryRowContext(ctx, `PRAGMA integrity_check`).Scan(&integrity); err != nil || integrity != "ok" { - t.Errorf("PRAGMA integrity_check = %q (error %v), want ok", integrity, err) + { + err := conn.QueryRowContext(ctx, `PRAGMA integrity_check`).Scan(&integrity) + testassert.Check(t, err == nil && integrity == "ok", "PRAGMA integrity_check = %q (error %v), want ok", integrity, err) } conn.Close() - if err := store.Close(); err != nil { - t.Fatalf("close: %v", err) - } + testassert.NoError(t, store.Close()) sqliteAssertNoSidecars(t, path) // An existing zero-length file is initialized exactly like a missing one. empty := filepath.Join(dir, "empty.db") - if err := os.WriteFile(empty, nil, 0o644); err != nil { - t.Fatalf("create zero-length file: %v", err) - } + testassert.NoError(t, os.WriteFile(empty, nil, 0o644)) emptyStore, err := OpenSQLite(empty) - if err != nil { - t.Fatalf("OpenSQLite zero-length file: %v", err) - } + testassert.NoError(t, err) var emptyVersion int - if err := emptyStore.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&emptyVersion); err != nil || emptyVersion != 6 { - t.Errorf("zero-length file user_version = %d (error %v), want 6", emptyVersion, err) - } - if err := emptyStore.Close(); err != nil { - t.Fatalf("close zero-length store: %v", err) + { + err := emptyStore.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&emptyVersion) + testassert.Check(t, err == nil && emptyVersion == 7, "zero-length file user_version = %d (error %v), want 7", emptyVersion, err) } + testassert.NoError(t, emptyStore.Close()) sqliteAssertNoSidecars(t, empty) // A caller path holding reserved URI characters initializes exactly that // file, never a truncated sibling database. reserved := filepath.Join(dir, "weird?name#part.db") reservedStore, err := OpenSQLite(reserved) - if err != nil { - t.Fatalf("OpenSQLite reserved URI path: %v", err) - } + testassert.NoError(t, err) var reservedVersion int - if err := reservedStore.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&reservedVersion); err != nil || reservedVersion != 6 { - t.Errorf("reserved-path user_version = %d (error %v), want 6", reservedVersion, err) - } - if err := reservedStore.Close(); err != nil { - t.Fatalf("close reserved-path store: %v", err) + { + err := reservedStore.db.QueryRowContext(ctx, `PRAGMA user_version`).Scan(&reservedVersion) + testassert.Check(t, err == nil && reservedVersion == 7, "reserved-path user_version = %d (error %v), want 7", reservedVersion, err) } - if _, err := os.Stat(reserved); err != nil { - t.Fatalf("reserved-path database %q missing: %v", reserved, err) + testassert.NoError(t, reservedStore.Close()) + { + _, err := os.Stat(reserved) + testassert.Require(t, err == nil, "reserved-path database %q missing: %v", reserved, err) } // The relative literal filename `:memory:` is a caller-supplied @@ -330,37 +290,29 @@ func TestSQLiteSchemaInitialization(t *testing.T) { // database. t.Chdir(dir) memoryStore, err := OpenSQLite(":memory:") - if err != nil { - t.Fatalf("OpenSQLite :memory: path: %v", err) - } + testassert.NoError(t, err) confCreateSession(t, ctx, memoryStore, "s1") confTxn(t, ctx, memoryStore, func(txn harness.Transaction) error { _, err := txn.InsertEntry(confEntry("s1", "e1")) return err }) - if _, err := os.Stat(filepath.Join(dir, ":memory:")); err != nil { - t.Fatalf("durable :memory: file missing: %v", err) - } - if err := memoryStore.Close(); err != nil { - t.Fatalf("close :memory: store: %v", err) + { + _, err := os.Stat(filepath.Join(dir, ":memory:")) + testassert.NoError(t, err) } + testassert.NoError(t, memoryStore.Close()) reopenedMemory, err := OpenSQLite(":memory:") - if err != nil { - t.Fatalf("reopen :memory: path: %v", err) - } - if entries, err := reopenedMemory.ReadEntries(ctx, "s1", 0); err != nil || len(entries) != 1 || entries[0].ID != "e1" { - t.Errorf("durable :memory: entries = %v (error %v), want [e1]", entries, err) - } - if err := reopenedMemory.Close(); err != nil { - t.Fatalf("close reopened :memory: store: %v", err) + testassert.NoError(t, err) + { + entries, err := reopenedMemory.ReadEntries(ctx, "s1", 0) + testassert.Check(t, err == nil && len(entries) == 1 && entries[0].ID == "e1", "durable :memory: entries = %v (error %v), want [e1]", entries, err) } + testassert.NoError(t, reopenedMemory.Close()) // Every initialized file is the requested one; no sibling or truncated // database was created beside them. dirEntries, err := os.ReadDir(dir) - if err != nil { - t.Fatalf("read directory: %v", err) - } + testassert.NoError(t, err) initialized := map[string]bool{} for _, dirEntry := range dirEntries { initialized[dirEntry.Name()] = true @@ -372,23 +324,21 @@ func TestSQLiteSchemaInitialization(t *testing.T) { ":memory:": true, } for name := range wantFiles { - if !initialized[name] { - t.Errorf("initialized file %q missing", name) - } + testassert.Check(t, initialized[name], "initialized file %q missing", name) } for name := range initialized { - if !wantFiles[name] { - t.Errorf("unexpected sibling or truncated database %q was initialized", name) - } + testassert.Check(t, wantFiles[name], "unexpected sibling or truncated database %q was initialized", name) } // A failed initialization must not leave a partial database behind. blocked := filepath.Join(dir, "missing", "lightcode.db") - if _, err := OpenSQLite(blocked); err == nil { - t.Fatal("OpenSQLite into a missing directory succeeded") + { + _, err := OpenSQLite(blocked) + testassert.Require(t, err != nil, "OpenSQLite into a missing directory succeeded") } - if _, err := os.Stat(blocked); !errors.Is(err, fs.ErrNotExist) { - t.Errorf("failed initialization left %q behind: %v", blocked, err) + { + _, err := os.Stat(blocked) + testassert.Check(t, errors.Is(err, fs.ErrNotExist), "failed initialization left %q behind: %v", blocked, err) } } @@ -400,9 +350,7 @@ func TestSQLitePhysicalShapes(t *testing.T) { store := newSQLiteStore(t) ctx := context.Background() conn, err := store.db.Conn(ctx) - if err != nil { - t.Fatalf("connection: %v", err) - } + testassert.NoError(t, err) defer conn.Close() rejected := []struct { @@ -424,18 +372,15 @@ func TestSQLitePhysicalShapes(t *testing.T) { {"malformed register payload", `INSERT INTO registers (session_id, kind, operation_id, revision, payload) VALUES ('s', 'session', '', 1, '{')`, nil}, } for _, tc := range rejected { - if _, err := conn.ExecContext(ctx, tc.query, tc.args...); err == nil { - t.Errorf("%s: physical constraint not enforced", tc.name) + { + _, err := conn.ExecContext(ctx, tc.query, tc.args...) + testassert.Check(t, err != nil, "%s: physical constraint not enforced", tc.name) } } var userObjects int - if err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM sqlite_schema WHERE sql IS NOT NULL`).Scan(&userObjects); err != nil { - t.Fatalf("count user-defined objects: %v", err) - } - if userObjects != 3 { - t.Errorf("user-defined object count = %d, want 3 (two tables, one index)", userObjects) - } + testassert.NoError(t, conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM sqlite_schema WHERE sql IS NOT NULL`).Scan(&userObjects)) + testassert.Check(t, userObjects == 3, "user-defined object count = %d, want 3 (two tables, one index)", userObjects) } // TestSQLiteRejectsNonEmptyVersionZero proves a non-empty version-zero @@ -447,132 +392,105 @@ func TestSQLiteRejectsNonEmptyVersionZero(t *testing.T) { before := sqliteFileDigest(t, path) store, err := OpenSQLite(path) - if !errors.Is(err, harness.ErrIncompatible) { - t.Fatalf("error = %v, want ErrIncompatible", err) - } + testassert.Require(t, errors.Is(err, harness.ErrIncompatible), "error = %v, want ErrIncompatible", err) var incompatible *harness.IncompatibleSchemaError - if !errors.As(err, &incompatible) { - t.Fatalf("error = %T, want *harness.IncompatibleSchemaError", err) - } - if incompatible.Found != 0 || incompatible.Supported != 6 { - t.Errorf("recovered %+v, want found 0 supported 6", incompatible) - } - if store != nil { - t.Error("rejected database returned a store") - } - if after := sqliteFileDigest(t, path); after != before { - t.Error("rejected version-zero database file changed") + testassert.Require(t, errors.As(err, &incompatible), "error = %T, want *harness.IncompatibleSchemaError", err) + testassert.Check(t, incompatible.Found == 0 && incompatible.Supported == 7, "recovered %+v, want found 0 supported 7", incompatible) + testassert.Check(t, store == nil, "rejected database returned a store") + { + after := sqliteFileDigest(t, path) + testassert.Check(t, after == before, "rejected version-zero database file changed") } sqliteAssertNoSidecars(t, path) } // TestSQLiteRejectsPriorVersion proves a database left at the prior schema -// version 5 — the child-lineage and background-completion payload shape with +// version 6 — the child-lineage and background-completion payload shape with // the unchanged canonical tables — is rejected through the read-only open // phase with the incompatible class before recovery ever sees the store, // leaving the file byte-identical with no sidecars. func TestSQLiteRejectsPriorVersion(t *testing.T) { path := filepath.Join(t.TempDir(), "prior.db") - sqliteFixtureDatabase(t, path, []string{sqliteEntriesDDL, sqliteRegistersDDL, sqliteOperationIndexDDL}, 5) + sqliteFixtureDatabase(t, path, []string{sqliteEntriesDDL, sqliteRegistersDDL, sqliteOperationIndexDDL}, 6) before := sqliteFileDigest(t, path) store, err := OpenSQLite(path) - if !errors.Is(err, harness.ErrIncompatible) { - t.Fatalf("error = %v, want ErrIncompatible", err) - } + testassert.Require(t, errors.Is(err, harness.ErrIncompatible), "error = %v, want ErrIncompatible", err) var incompatible *harness.IncompatibleSchemaError - if !errors.As(err, &incompatible) || incompatible.Found != 5 || incompatible.Supported != 6 { - t.Errorf("recovered %+v, want found 5 supported 6", incompatible) - } - if store != nil { - t.Error("rejected prior-version database returned a store") - } - if after := sqliteFileDigest(t, path); after != before { - t.Error("rejected prior-version database file changed") + testassert.Check(t, errors.As(err, &incompatible) && incompatible.Found == 6 && incompatible.Supported == 7, "recovered %+v, want found 6 supported 7", incompatible) + testassert.Check(t, store == nil, "rejected prior-version database returned a store") + { + after := sqliteFileDigest(t, path) + testassert.Check(t, after == before, "rejected prior-version database file changed") } sqliteAssertNoSidecars(t, path) } // TestSQLiteRejectsUnsupportedVersion proves a database with a future schema -// version beyond the accepted 6 is rejected without any file or sidecar +// version beyond the accepted 7 is rejected without any file or sidecar // change. func TestSQLiteRejectsUnsupportedVersion(t *testing.T) { path := filepath.Join(t.TempDir(), "future.db") - sqliteFixtureDatabase(t, path, []string{sqliteEntriesDDL, sqliteRegistersDDL, sqliteOperationIndexDDL}, 7) + sqliteFixtureDatabase(t, path, []string{sqliteEntriesDDL, sqliteRegistersDDL, sqliteOperationIndexDDL}, 8) before := sqliteFileDigest(t, path) _, err := OpenSQLite(path) - if !errors.Is(err, harness.ErrIncompatible) { - t.Fatalf("error = %v, want ErrIncompatible", err) - } + testassert.Require(t, errors.Is(err, harness.ErrIncompatible), "error = %v, want ErrIncompatible", err) var incompatible *harness.IncompatibleSchemaError - if !errors.As(err, &incompatible) || incompatible.Found != 7 || incompatible.Supported != 6 { - t.Errorf("recovered %+v, want found 7 supported 6", incompatible) - } - if after := sqliteFileDigest(t, path); after != before { - t.Error("rejected version-7 database file changed") + testassert.Check(t, errors.As(err, &incompatible) && incompatible.Found == 8 && incompatible.Supported == 7, "recovered %+v, want found 8 supported 7", incompatible) + { + after := sqliteFileDigest(t, path) + testassert.Check(t, after == before, "rejected version-8 database file changed") } sqliteAssertNoSidecars(t, path) - // Unsupported hot-WAL sibling: version 7 lives only in an uncheckpointed - // WAL beside a checkpointed version 6 main file, with no SHM file — the + // Unsupported hot-WAL sibling: version 8 lives only in an uncheckpointed + // WAL beside a checkpointed version 7 main file, with no SHM file — the // crashed-writer state. Read-only validation must see the WAL-carried // version and reject the copy while leaving the database and WAL bytes // unchanged; a SQLite-owned -shm WAL index may appear and remain. source := filepath.Join(t.TempDir(), "wal-source.db") live, err := sql.Open(sqliteDriverName, sqliteDSN(source, "_journal_mode=WAL&_synchronous=FULL")) - if err != nil { - t.Fatalf("open WAL source: %v", err) + testassert.NoError(t, err) + { + _, err := live.Exec(`PRAGMA user_version = 7`) + testassert.NoError(t, err) } - if _, err := live.Exec(`PRAGMA user_version = 6`); err != nil { - t.Fatalf("checkpointed version: %v", err) + { + _, err := live.Exec(`PRAGMA wal_checkpoint(FULL)`) + testassert.NoError(t, err) } - if _, err := live.Exec(`PRAGMA wal_checkpoint(FULL)`); err != nil { - t.Fatalf("checkpoint version 6: %v", err) - } - if _, err := live.Exec(`PRAGMA user_version = 7`); err != nil { - t.Fatalf("hot version: %v", err) + { + _, err := live.Exec(`PRAGMA user_version = 8`) + testassert.NoError(t, err) } mainBytes, err := os.ReadFile(source) - if err != nil { - t.Fatalf("copy source main file: %v", err) - } + testassert.NoError(t, err) walBytes, err := os.ReadFile(source + "-wal") - if err != nil { - t.Fatalf("copy source WAL: %v", err) - } + testassert.NoError(t, err) target := filepath.Join(t.TempDir(), "wal-crashed.db") - if err := os.WriteFile(target, mainBytes, 0o644); err != nil { - t.Fatalf("materialize target main file: %v", err) - } - if err := os.WriteFile(target+"-wal", walBytes, 0o644); err != nil { - t.Fatalf("materialize target WAL: %v", err) - } - if err := live.Close(); err != nil { - t.Fatalf("close WAL source: %v", err) - } + testassert.NoError(t, os.WriteFile(target, mainBytes, 0o644)) + testassert.NoError(t, os.WriteFile(target+"-wal", walBytes, 0o644)) + testassert.NoError(t, live.Close()) beforeMain := sqliteFileDigest(t, target) beforeWAL := sqliteFileDigest(t, target+"-wal") crashedStore, err := OpenSQLite(target) - if !errors.Is(err, harness.ErrIncompatible) { - t.Fatalf("error = %v, want ErrIncompatible", err) - } + testassert.Require(t, errors.Is(err, harness.ErrIncompatible), "error = %v, want ErrIncompatible", err) var walIncompatible *harness.IncompatibleSchemaError - if !errors.As(err, &walIncompatible) || walIncompatible.Found != 7 || walIncompatible.Supported != 6 { - t.Errorf("recovered %+v, want the WAL-carried version found 7 supported 6", walIncompatible) - } - if crashedStore != nil { - t.Error("rejected WAL-mode database returned a store") + testassert.Check(t, errors.As(err, &walIncompatible) && walIncompatible.Found == 8 && walIncompatible.Supported == 7, "recovered %+v, want the WAL-carried version found 8 supported 7", walIncompatible) + testassert.Check(t, crashedStore == nil, "rejected WAL-mode database returned a store") + { + after := sqliteFileDigest(t, target) + testassert.Check(t, after == beforeMain, "rejected WAL-mode database file changed") } - if after := sqliteFileDigest(t, target); after != beforeMain { - t.Error("rejected WAL-mode database file changed") + { + after := sqliteFileDigest(t, target+"-wal") + testassert.Check(t, after == beforeWAL, "rejected WAL-mode database WAL changed") } - if after := sqliteFileDigest(t, target+"-wal"); after != beforeWAL { - t.Error("rejected WAL-mode database WAL changed") - } - if _, err := os.Stat(target + "-journal"); !errors.Is(err, fs.ErrNotExist) { - t.Error("rejected WAL-mode database created a journal sidecar") + { + _, err := os.Stat(target + "-journal") + testassert.Check(t, errors.Is(err, fs.ErrNotExist), "rejected WAL-mode database created a journal sidecar") } } @@ -602,21 +520,13 @@ ON registers(operation_id) WHERE kind = 'operation'` } { t.Run(name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "schema.db") - sqliteFixtureDatabase(t, path, ddl, 6) + sqliteFixtureDatabase(t, path, ddl, 7) store, err := OpenSQLite(path) - if !errors.Is(err, harness.ErrIncompatible) { - t.Fatalf("error = %v, want ErrIncompatible", err) - } + testassert.Require(t, errors.Is(err, harness.ErrIncompatible), "error = %v, want ErrIncompatible", err) var incompatible *harness.IncompatibleSchemaError - if !errors.As(err, &incompatible) { - t.Fatalf("error = %T, want *harness.IncompatibleSchemaError", err) - } - if incompatible.Found != 6 || incompatible.Supported != 6 { - t.Errorf("recovered %+v, want found 6 supported 6", incompatible) - } - if store != nil { - t.Error("rejected database returned a store") - } + testassert.Require(t, errors.As(err, &incompatible), "error = %T, want *harness.IncompatibleSchemaError", err) + testassert.Check(t, incompatible.Found == 7 && incompatible.Supported == 7, "recovered %+v, want found 7 supported 7", incompatible) + testassert.Check(t, store == nil, "rejected database returned a store") }) } } @@ -629,9 +539,7 @@ func TestSQLitePersistenceAcrossCloseReopen(t *testing.T) { ctx := context.Background() store, err := OpenSQLite(path) - if err != nil { - t.Fatalf("initial open: %v", err) - } + testassert.NoError(t, err) confCreateSession(t, ctx, store, "s1") var first harness.Entry confTxn(t, ctx, store, func(txn harness.Transaction) error { @@ -645,39 +553,23 @@ func TestSQLitePersistenceAcrossCloseReopen(t *testing.T) { first, err = txn.InsertEntry(harness.EntryDraft{SessionID: "s1", ID: "e1", Kind: harness.EntryToolResult, OperationID: "op1", Payload: rawJSON(`{"entry":"e1"}`)}) return err }) - if err := store.Close(); err != nil { - t.Fatalf("close: %v", err) - } + testassert.NoError(t, store.Close()) reopened, err := OpenSQLite(path) - if err != nil { - t.Fatalf("exact-version reopen: %v", err) - } + testassert.NoError(t, err) defer reopened.Close() ids, err := reopened.ListSessionIDs(ctx) - if err != nil || len(ids) != 1 || ids[0] != "s1" { - t.Fatalf("ListSessionIDs after reopen = %v (error %v), want [s1]", ids, err) - } + testassert.Require(t, err == nil && len(ids) == 1 && ids[0] == "s1", "ListSessionIDs after reopen = %v (error %v), want [s1]", ids, err) entries, err := reopened.ReadEntries(ctx, "s1", 0) - if err != nil || len(entries) != 1 { - t.Fatalf("ReadEntries after reopen = %v (error %v), want [e1]", entries, err) - } + testassert.Require(t, err == nil && len(entries) == 1, "ReadEntries after reopen = %v (error %v), want [e1]", entries, err) entry := entries[0] - if entry.ID != "e1" || entry.Sequence != 1 || entry.Kind != harness.EntryToolResult || entry.OperationID != "op1" || string(entry.Payload) != `{"entry":"e1"}` { - t.Errorf("reopened entry = %+v, want the committed entry %#v", entry, first) - } - if !entry.CommittedAt.Equal(first.CommittedAt) || entry.CommittedAt.Location() != time.UTC { - t.Errorf("reopened commit time = %v, want the committed %v in UTC", entry.CommittedAt, first.CommittedAt) - } + testassert.Check(t, entry.ID == "e1" && entry.Sequence == 1 && entry.Kind == harness.EntryToolResult && entry.OperationID == "op1" && string(entry.Payload) == `{"entry":"e1"}`, "reopened entry = %+v, want the committed entry %#v", entry, first) + testassert.Check(t, entry.CommittedAt.Equal(first.CommittedAt) && entry.CommittedAt.Location() == time.UTC, "reopened commit time = %v, want the committed %v in UTC", entry.CommittedAt, first.CommittedAt) register, err := reopened.ReadRegister(ctx, confSessionKey("s1")) - if err != nil || register.Revision != 2 || string(register.Payload) != `{"state":"replaced"}` { - t.Errorf("reopened session register = %+v (error %v), want revision 2", register, err) - } + testassert.Check(t, err == nil && register.Revision == 2 && string(register.Payload) == `{"state":"replaced"}`, "reopened session register = %+v (error %v), want revision 2", register, err) operation, err := reopened.ReadRegister(ctx, confOpKey("s1", "op1")) - if err != nil || operation.Revision != 1 { - t.Errorf("reopened operation register = %+v (error %v), want revision 1", operation, err) - } + testassert.Check(t, err == nil && operation.Revision == 1, "reopened operation register = %+v (error %v), want revision 1", operation, err) // Current hot-WAL sibling: the canonical schema, schema version, and // committed data live only in an uncheckpointed WAL. The source database @@ -688,66 +580,53 @@ func TestSQLitePersistenceAcrossCloseReopen(t *testing.T) { // may appear and remain. source := filepath.Join(t.TempDir(), "wal-current-source.db") live, err := sql.Open(sqliteDriverName, sqliteDSN(source, "_journal_mode=WAL&_synchronous=FULL")) - if err != nil { - t.Fatalf("open hot-WAL source: %v", err) - } + testassert.NoError(t, err) for _, stmt := range []string{ sqliteEntriesDDL, sqliteRegistersDDL, sqliteOperationIndexDDL, - `PRAGMA user_version = 6`, + `PRAGMA user_version = 7`, `INSERT INTO registers (session_id, kind, operation_id, revision, payload) VALUES ('s1', 'session', '', 1, '{"session":"s1"}')`, `INSERT INTO entries (session_id, sequence, entry_id, operation_id, kind, committed_at_ns, payload) VALUES ('s1', 1, 'e1', '', 'input', 1700000000000000000, '{"entry":"e1"}')`, } { - if _, err := live.Exec(stmt); err != nil { - t.Fatalf("build hot-WAL source: %v", err) + { + _, err := live.Exec(stmt) + testassert.NoError(t, err) } } mainBytes, err := os.ReadFile(source) - if err != nil { - t.Fatalf("copy source main file: %v", err) - } + testassert.NoError(t, err) walBytes, err := os.ReadFile(source + "-wal") - if err != nil { - t.Fatalf("copy source WAL: %v", err) - } - if err := live.Close(); err != nil { - t.Fatalf("close hot-WAL source: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, live.Close()) crashed := filepath.Join(t.TempDir(), "wal-current.db") - if err := os.WriteFile(crashed, mainBytes, 0o644); err != nil { - t.Fatalf("materialize crash copy: %v", err) - } - if err := os.WriteFile(crashed+"-wal", walBytes, 0o644); err != nil { - t.Fatalf("materialize crash WAL: %v", err) - } + testassert.NoError(t, os.WriteFile(crashed, mainBytes, 0o644)) + testassert.NoError(t, os.WriteFile(crashed+"-wal", walBytes, 0o644)) beforeMain := sqliteFileDigest(t, crashed) beforeWAL := sqliteFileDigest(t, crashed+"-wal") crashStore, err := OpenSQLite(crashed) - if err != nil { - t.Fatalf("read-only validation of the WAL-carried state: %v", err) - } + testassert.NoError(t, err) ids, err = crashStore.ListSessionIDs(ctx) - if err != nil || len(ids) != 1 || ids[0] != "s1" { - t.Errorf("WAL-carried session discovery = %v (error %v), want [s1]", ids, err) - } - if walEntries, err := crashStore.ReadEntries(ctx, "s1", 0); err != nil || len(walEntries) != 1 || walEntries[0].ID != "e1" { - t.Errorf("WAL-carried entries = %v (error %v), want [e1]", walEntries, err) - } - if after := sqliteFileDigest(t, crashed); after != beforeMain { - t.Error("validated crash-copy database changed") + testassert.Check(t, err == nil && len(ids) == 1 && ids[0] == "s1", "WAL-carried session discovery = %v (error %v), want [s1]", ids, err) + { + walEntries, err := crashStore.ReadEntries(ctx, "s1", 0) + testassert.Check(t, err == nil && len(walEntries) == 1 && walEntries[0].ID == "e1", "WAL-carried entries = %v (error %v), want [e1]", walEntries, err) } - if after := sqliteFileDigest(t, crashed+"-wal"); after != beforeWAL { - t.Error("validated crash-copy WAL changed") + { + after := sqliteFileDigest(t, crashed) + testassert.Check(t, after == beforeMain, "validated crash-copy database changed") } - if _, err := os.Stat(crashed + "-journal"); !errors.Is(err, fs.ErrNotExist) { - t.Error("validated crash copy created a journal sidecar") + { + after := sqliteFileDigest(t, crashed+"-wal") + testassert.Check(t, after == beforeWAL, "validated crash-copy WAL changed") } - if err := crashStore.Close(); err != nil { - t.Fatalf("close crash copy: %v", err) + { + _, err := os.Stat(crashed + "-journal") + testassert.Check(t, errors.Is(err, fs.ErrNotExist), "validated crash copy created a journal sidecar") } + testassert.NoError(t, crashStore.Close()) } // TestSQLitePhysicalCorruption proves a database whose schema image is @@ -756,41 +635,29 @@ func TestSQLitePersistenceAcrossCloseReopen(t *testing.T) { func TestSQLitePhysicalCorruption(t *testing.T) { path := filepath.Join(t.TempDir(), "lightcode.db") store, err := OpenSQLite(path) - if err != nil { - t.Fatalf("initial open: %v", err) - } + testassert.NoError(t, err) confCreateSession(t, context.Background(), store, "s1") - if err := store.Close(); err != nil { - t.Fatalf("close: %v", err) - } + testassert.NoError(t, store.Close()) // Destroy the b-tree page type of the schema root while keeping the // database header intact, so the file is still recognizable as SQLite // but its schema image is unreadable. file, err := os.OpenFile(path, os.O_RDWR, 0) - if err != nil { - t.Fatalf("open for corruption: %v", err) - } - if _, err := file.WriteAt([]byte{0x00}, 100); err != nil { - t.Fatalf("corrupt schema page: %v", err) - } - if err := file.Close(); err != nil { - t.Fatalf("close after corruption: %v", err) + testassert.NoError(t, err) + { + _, err := file.WriteAt([]byte{0x00}, 100) + testassert.NoError(t, err) } + testassert.NoError(t, file.Close()) before := sqliteFileDigest(t, path) opened, err := OpenSQLite(path) - if !errors.Is(err, harness.ErrStorage) { - t.Fatalf("error = %v, want ErrStorage", err) - } - if errors.Is(err, harness.ErrIncompatible) { - t.Errorf("physical corruption misclassified as incompatible schema: %v", err) - } - if opened != nil { - t.Error("corrupt database returned a store") - } - if after := sqliteFileDigest(t, path); after != before { - t.Error("rejected corrupt database file changed") + testassert.Require(t, errors.Is(err, harness.ErrStorage), "error = %v, want ErrStorage", err) + testassert.Check(t, !errors.Is(err, harness.ErrIncompatible), "physical corruption misclassified as incompatible schema: %v", err) + testassert.Check(t, opened == nil, "corrupt database returned a store") + { + after := sqliteFileDigest(t, path) + testassert.Check(t, after == before, "rejected corrupt database file changed") } } @@ -807,48 +674,38 @@ func TestSQLiteConnectionConfiguration(t *testing.T) { for i := 0; i < 3; i++ { conn, err := store.db.Conn(ctx) - if err != nil { - t.Fatalf("connection %d: %v", i, err) - } + testassert.Require(t, err == nil, "connection %d: %v", i, err) for pragma, want := range map[string]string{ "PRAGMA journal_mode": "wal", "PRAGMA synchronous": "2", "PRAGMA busy_timeout": "5000", } { var got string - if err := conn.QueryRowContext(ctx, pragma).Scan(&got); err != nil { - t.Fatalf("connection %d, %s: %v", i, pragma, err) - } - if got != want { - t.Errorf("connection %d, %s = %q, want %q", i, pragma, got, want) + { + err := conn.QueryRowContext(ctx, pragma).Scan(&got) + testassert.Require(t, err == nil, "connection %d, %s: %v", i, pragma, err) } + testassert.Check(t, got == want, "connection %d, %s = %q, want %q", i, pragma, got, want) } - if err := conn.Close(); err != nil { - t.Fatalf("connection %d close: %v", i, err) + { + err := conn.Close() + testassert.Require(t, err == nil, "connection %d close: %v", i, err) } } tx, err := store.db.BeginTx(ctx, nil) - if err != nil { - t.Fatalf("BeginTx: %v", err) - } + testassert.NoError(t, err) other, err := sql.Open(sqliteDriverName, sqliteDSN(store.path, "_busy_timeout=10")) - if err != nil { - t.Fatalf("open competing connection: %v", err) - } + testassert.NoError(t, err) defer other.Close() if _, err := other.Exec(`BEGIN IMMEDIATE`); err == nil { t.Error("BEGIN IMMEDIATE succeeded while the pool transaction holds the write lock; the pool transaction did not use BEGIN IMMEDIATE") } else { var busy sqlite3.Error - if !errors.As(err, &busy) || busy.Code != sqlite3.ErrBusy { - t.Errorf("BEGIN IMMEDIATE error = %v, want SQLITE_BUSY", err) - } - } - if err := tx.Rollback(); err != nil { - t.Fatalf("release pool transaction: %v", err) + testassert.Check(t, errors.As(err, &busy) && busy.Code == sqlite3.ErrBusy, "BEGIN IMMEDIATE error = %v, want SQLITE_BUSY", err) } + testassert.NoError(t, tx.Rollback()) // One public read's statements share one deferred read snapshot on a // single pooled connection: a concurrent writer commits mid-read, and the @@ -871,15 +728,12 @@ func TestSQLiteConnectionConfiguration(t *testing.T) { if err != nil { return err } - if len(before) != 1 || before[0].ID != "e1" || len(after) != 1 || after[0].ID != "e1" { - t.Errorf("read snapshot observed a concurrent commit: before %v, after %v, want the same pre-commit state [e1]", before, after) - } + testassert.Check(t, len(before) == 1 && before[0].ID == "e1" && len(after) == 1 && after[0].ID == "e1", "read snapshot observed a concurrent commit: before %v, after %v, want the same pre-commit state [e1]", before, after) return nil }) - if err != nil { - t.Fatalf("read snapshot: %v", err) - } - if entries, err := store.ReadEntries(ctx, "s1", 0); err != nil || len(entries) != 2 || entries[1].ID != "e2" { - t.Errorf("post-snapshot read = %v (error %v), want the committed transition [e1 e2]", entries, err) + testassert.NoError(t, err) + { + entries, err := store.ReadEntries(ctx, "s1", 0) + testassert.Check(t, err == nil && len(entries) == 2 && entries[1].ID == "e2", "post-snapshot read = %v (error %v), want the committed transition [e1 e2]", entries, err) } } diff --git a/internal/terminal/clipboard_test.go b/internal/terminal/clipboard_test.go index fbd1ebe9..30a0f5c8 100644 --- a/internal/terminal/clipboard_test.go +++ b/internal/terminal/clipboard_test.go @@ -3,6 +3,8 @@ package terminal import ( "bytes" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestWriteClipboardOSC52Bytes pins the exact OSC-52 sequence: the escape @@ -10,11 +12,7 @@ import ( // BEL terminator. func TestWriteClipboardOSC52Bytes(t *testing.T) { var b bytes.Buffer - if err := WriteClipboardOSC52(&b, "hello"); err != nil { - t.Fatalf("WriteClipboardOSC52: %v", err) - } + testassert.NoError(t, WriteClipboardOSC52(&b, "hello")) want := "\x1b]52;c;aGVsbG8=\x07" - if got := b.String(); got != want { - t.Fatalf("OSC-52 bytes = %q, want %q", got, want) - } + testassert.Equal(t, b.String(), want) } diff --git a/internal/terminal/input_test.go b/internal/terminal/input_test.go index 4305167c..a3a2403a 100644 --- a/internal/terminal/input_test.go +++ b/internal/terminal/input_test.go @@ -1,28 +1,37 @@ package terminal -import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" + "testing" +) func TestInputHistoryNavigationRestoresDraft(t *testing.T) { h := NewInputHistory() h.Reset([]string{"first", "second"}) - if got, ok := h.Prev("draft"); !ok || got != "second" { - t.Fatalf("Prev() = %q, %v; want second, true", got, ok) + { + got, ok := h.Prev("draft") + testassert.Require(t, ok && got == "second", "Prev() = %q, %v; want second, true", got, ok) } - if got, ok := h.Prev("ignored"); !ok || got != "first" { - t.Fatalf("Prev() = %q, %v; want first, true", got, ok) + { + got, ok := h.Prev("ignored") + testassert.Require(t, ok && got == "first", "Prev() = %q, %v; want first, true", got, ok) } - if _, ok := h.Prev("ignored"); ok { - t.Fatalf("Prev() at oldest entry returned ok") + { + _, ok := h.Prev("ignored") + testassert.Require(t, !ok, "Prev() at oldest entry returned ok") } - if got, ok := h.Next(); !ok || got != "second" { - t.Fatalf("Next() = %q, %v; want second, true", got, ok) + { + got, ok := h.Next() + testassert.Require(t, ok && got == "second", "Next() = %q, %v; want second, true", got, ok) } - if got, ok := h.Next(); !ok || got != "draft" { - t.Fatalf("Next() = %q, %v; want draft, true", got, ok) + { + got, ok := h.Next() + testassert.Require(t, ok && got == "draft", "Next() = %q, %v; want draft, true", got, ok) } - if _, ok := h.Next(); ok { - t.Fatalf("Next() at draft returned ok") + { + _, ok := h.Next() + testassert.Require(t, !ok, "Next() at draft returned ok") } } @@ -32,14 +41,17 @@ func TestInputHistoryAddSkipsConsecutiveDuplicates(t *testing.T) { h.Add("same") h.Add("next") - if got, ok := h.Prev(""); !ok || got != "next" { - t.Fatalf("Prev() = %q, %v; want next, true", got, ok) + { + got, ok := h.Prev("") + testassert.Require(t, ok && got == "next", "Prev() = %q, %v; want next, true", got, ok) } - if got, ok := h.Prev(""); !ok || got != "same" { - t.Fatalf("Prev() = %q, %v; want same, true", got, ok) + { + got, ok := h.Prev("") + testassert.Require(t, ok && got == "same", "Prev() = %q, %v; want same, true", got, ok) } - if _, ok := h.Prev(""); ok { - t.Fatalf("duplicate entry was stored") + { + _, ok := h.Prev("") + testassert.Require(t, !ok, "duplicate entry was stored") } } @@ -53,12 +65,14 @@ func TestCompleteSlashCommandCompletesAgainstSuppliedList(t *testing.T) { {"/nope", "/nope"}, // no match is returned unchanged } for _, tc := range cases { - if got := CompleteSlashCommand(tc.text, commands); got != tc.want { - t.Errorf("CompleteSlashCommand(%q) = %q, want %q", tc.text, got, tc.want) + { + got := CompleteSlashCommand(tc.text, commands) + testassert.Check(t, got == tc.want, "CompleteSlashCommand(%q) = %q, want %q", tc.text, got, tc.want) } } // Multiple matches extend to their longest common prefix. - if got, want := CompleteSlashCommand("/al", []string{"/alpha", "/alpine"}), "/alp"; got != want { - t.Errorf("CompleteSlashCommand(/al) = %q, want %q", got, want) + { + got, want := CompleteSlashCommand("/al", []string{"/alpha", "/alpine"}), "/alp" + testassert.Check(t, got == want, "CompleteSlashCommand(/al) = %q, want %q", got, want) } } diff --git a/internal/terminal/menu_test.go b/internal/terminal/menu_test.go index a3f7430c..ba993d03 100644 --- a/internal/terminal/menu_test.go +++ b/internal/terminal/menu_test.go @@ -6,6 +6,8 @@ import ( "strings" "sync" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestShowMenuDoesNotRedrawAtNavigationBounds(t *testing.T) { @@ -27,9 +29,7 @@ func TestShowMenuDoesNotRedrawAtNavigationBounds(t *testing.T) { } result := ShowMenu(&sync.Mutex{}, func(s string) { out.WriteString(s) }, readKey, "Revert - pick turn", items, 80) - if result.Selected != -1 { - t.Fatalf("selected = %d, want cancel", result.Selected) - } + testassert.Equal(t, result.Selected, -1) if got := bytes.Count(out.Bytes(), []byte("Revert - pick turn")); got != 1 { t.Fatalf("menu rendered %d times, want 1\n%s", got, out.String()) } @@ -55,11 +55,10 @@ func TestShowMenuRedrawUsesRenderedLineCount(t *testing.T) { _ = ShowMenu(&sync.Mutex{}, func(s string) { out.WriteString(s) }, readKey, "Revert - pick turn", items, 80) rendered := out.String() - if strings.Contains(rendered, "\x1b7") || strings.Contains(rendered, "\x1b8") || strings.Contains(rendered, "\x1b[J") { - t.Fatalf("menu redraw should not use cursor save/restore anchors:\n%q", rendered) - } - if got := strings.Count(rendered, "\x1b[1A"); got != 8 { - t.Fatalf("cursor-up clears = %d, want 8 for redraw+exit of 5-row menu frame\n%q", got, rendered) + testassert.Require(t, !strings.Contains(rendered, "\x1b7") && !strings.Contains(rendered, "\x1b8") && !strings.Contains(rendered, "\x1b[J"), "menu redraw should not use cursor save/restore anchors:\n%q", rendered) + { + got := strings.Count(rendered, "\x1b[1A") + testassert.Require(t, got == 8, "cursor-up clears = %d, want 8 for redraw+exit of 5-row menu frame\n%q", got, rendered) } } @@ -68,12 +67,8 @@ func TestConfirmYNDistinguishesReadErrorFromNo(t *testing.T) { var out bytes.Buffer readKey := func() (KeyMsg, error) { return KeyMsg{Special: KeyEnter}, nil } got, err := ConfirmYN(&sync.Mutex{}, func(s string) { out.WriteString(s) }, readKey, "also revert code?", 80) - if err != nil { - t.Fatalf("ConfirmYN err = %v, want nil", err) - } - if !got { - t.Fatal("ConfirmYN = false, want true on Enter at Yes") - } + testassert.NoError(t, err) + testassert.Require(t, got, "ConfirmYN = false, want true on Enter at Yes") }) t.Run("enter_no", func(t *testing.T) { @@ -86,36 +81,24 @@ func TestConfirmYNDistinguishesReadErrorFromNo(t *testing.T) { } var out bytes.Buffer got, err := ConfirmYN(&sync.Mutex{}, func(s string) { out.WriteString(s) }, readKey, "also revert code?", 80) - if err != nil { - t.Fatalf("ConfirmYN err = %v, want nil", err) - } - if got { - t.Fatal("ConfirmYN = true, want false on Enter at No") - } + testassert.NoError(t, err) + testassert.Require(t, !got, "ConfirmYN = true, want false on Enter at No") }) t.Run("escape_is_no", func(t *testing.T) { var out bytes.Buffer readKey := func() (KeyMsg, error) { return KeyMsg{Special: KeyEscape}, nil } got, err := ConfirmYN(&sync.Mutex{}, func(s string) { out.WriteString(s) }, readKey, "also revert code?", 80) - if err != nil { - t.Fatalf("ConfirmYN err = %v, want nil", err) - } - if got { - t.Fatal("ConfirmYN = true, want false on Escape") - } + testassert.NoError(t, err) + testassert.Require(t, !got, "ConfirmYN = true, want false on Escape") }) t.Run("read_error_reported", func(t *testing.T) { var out bytes.Buffer readKey := func() (KeyMsg, error) { return KeyMsg{}, errors.New("read failed") } got, err := ConfirmYN(&sync.Mutex{}, func(s string) { out.WriteString(s) }, readKey, "also revert code?", 80) - if err == nil { - t.Fatal("ConfirmYN err = nil, want the read error") - } - if got { - t.Fatal("ConfirmYN = true, want false with the error") - } + testassert.Require(t, err != nil, "ConfirmYN err = nil, want the read error") + testassert.Require(t, !got, "ConfirmYN = true, want false with the error") }) } @@ -135,8 +118,9 @@ func TestRenderMenuConstrainsLineWidths(t *testing.T) { lines := strings.Split(rendered, nl) // 3 items plus the title and footer rows. - if got, want := len(lines), 5; got != want { - t.Fatalf("rendered rows = %d, want %d\n%s", got, want, rendered) + { + got, want := len(lines), 5 + testassert.Require(t, got == want, "rendered rows = %d, want %d\n%s", got, want, rendered) } for _, line := range lines { if got := VisibleWidth(line); got > menuLineWidth(width) { @@ -156,15 +140,11 @@ func TestRenderMenuNormalizesAndTruncatesMultilineText(t *testing.T) { "footer\nhint", ) - if strings.Contains(rendered, "\nsecond") || strings.Contains(rendered, "\rthird") || strings.Contains(rendered, "\t") { - t.Fatalf("menu text was not normalized:\n%q", rendered) - } - if !strings.Contains(rendered, "first line second") { - t.Fatalf("normalized detail missing from menu:\n%q", rendered) - } + testassert.Require(t, !strings.Contains(rendered, "\nsecond") && !strings.Contains(rendered, "\rthird") && !strings.Contains(rendered, "\t") && strings.Contains(rendered, "first line second"), "menu text was not normalized:\n%q; normalized detail missing from menu:\n%q", rendered, rendered) // 1 item plus the title and footer rows. - if got, want := len(strings.Split(rendered, nl)), 3; got != want { - t.Fatalf("rendered rows = %d, want %d\n%s", got, want, rendered) + { + got, want := len(strings.Split(rendered, nl)), 3 + testassert.Require(t, got == want, "rendered rows = %d, want %d\n%s", got, want, rendered) } for _, line := range strings.Split(rendered, nl) { if got := VisibleWidth(line); got > menuLineWidth(50) { @@ -174,8 +154,9 @@ func TestRenderMenuNormalizesAndTruncatesMultilineText(t *testing.T) { } func TestMenuLineWidthKeepsWideTerminalsConservative(t *testing.T) { - if got, want := menuLineWidth(90), 56; got != want { - t.Fatalf("menuLineWidth(90) = %d, want %d", got, want) + { + got, want := menuLineWidth(90), 56 + testassert.Equal(t, got, want) } rendered := RenderMenu( "Revert - pick turn", @@ -187,8 +168,9 @@ func TestMenuLineWidthKeepsWideTerminalsConservative(t *testing.T) { defaultMenuFooter, ) for _, line := range strings.Split(rendered, nl) { - if got := VisibleWidth(line); got > 56 { - t.Fatalf("line width = %d, want <= 56: %q\n%s", got, line, rendered) + { + got := VisibleWidth(line) + testassert.Require(t, got <= 56, "line width = %d, want <= 56: %q\n%s", got, line, rendered) } } } @@ -202,7 +184,5 @@ func TestRenderMenuTitleDoesNotUseFullWidthRule(t *testing.T) { defaultMenuFooter, ) firstLine := strings.Split(rendered, nl)[0] - if strings.Count(firstLine, "─") > 4 { - t.Fatalf("title should not render a full-width rule: %q", firstLine) - } + testassert.Require(t, strings.Count(firstLine, "─") <= 4, "title should not render a full-width rule: %q", firstLine) } diff --git a/internal/terminal/render_test.go b/internal/terminal/render_test.go index 67052f05..b4b47445 100644 --- a/internal/terminal/render_test.go +++ b/internal/terminal/render_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/editpreview" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestRenderEditPreviewColorsOnlyGutter(t *testing.T) { @@ -15,18 +16,7 @@ func TestRenderEditPreviewColorsOnlyGutter(t *testing.T) { }}}} rendered := RenderEditPreview(preview, " ", 80, true) - if !strings.Contains(rendered, " "+colorRed+"12 -"+colorReset+colorDim+"removed()"+colorReset) { - t.Fatalf("removed gutter was not red or spacing is wrong: %q", rendered) - } - if !strings.Contains(rendered, " "+colorGreen+"12 +"+colorReset+colorDim+"added()"+colorReset) { - t.Fatalf("added gutter was not green or spacing is wrong: %q", rendered) - } - if !strings.Contains(rendered, " "+colorDim+"13 "+colorReset+colorDim+"kept()"+colorReset) { - t.Fatalf("context gutter was not dim or spacing is wrong: %q", rendered) - } - if strings.Contains(rendered, colorRed+"removed()") || strings.Contains(rendered, colorGreen+"added()") { - t.Fatalf("code content should not be colored: %q", rendered) - } + testassert.Require(t, strings.Contains(rendered, " "+colorRed+"12 -"+colorReset+colorDim+"removed()"+colorReset) && strings.Contains(rendered, " "+colorGreen+"12 +"+colorReset+colorDim+"added()"+colorReset) && strings.Contains(rendered, " "+colorDim+"13 "+colorReset+colorDim+"kept()"+colorReset) && !strings.Contains(rendered, colorRed+"removed()") && !strings.Contains(rendered, colorGreen+"added()"), "removed gutter was not red or spacing is wrong: %q; added gutter was not green or spacing is wrong: %q; context gutter was not dim or spacing is wrong: %q; code content should not be colored: %q", rendered, rendered, rendered, rendered) } func TestRenderEditPreviewCollapsesUnlessExpanded(t *testing.T) { @@ -45,23 +35,14 @@ func TestRenderEditPreviewCollapsesUnlessExpanded(t *testing.T) { }}}} collapsed := RenderEditPreview(preview, "", 80, false) - if strings.Contains(collapsed, "eleven") { - t.Fatalf("collapsed preview should hide eleventh row: %q", collapsed) - } - if !strings.Contains(collapsed, "(1 more line)") { - t.Fatalf("collapsed preview missing more-lines marker: %q", collapsed) - } - if got, want := strings.Count(collapsed, nl), collapsedEditPreviewLines+1; got != want { - t.Fatalf("collapsed preview lines = %d, want %d: %q", got, want, collapsed) + testassert.Require(t, !strings.Contains(collapsed, "eleven") && strings.Contains(collapsed, "(1 more line)"), "collapsed preview should hide eleventh row: %q; collapsed preview missing more-lines marker: %q", collapsed, collapsed) + { + got, want := strings.Count(collapsed, nl), collapsedEditPreviewLines+1 + testassert.Require(t, got == want, "collapsed preview lines = %d, want %d: %q", got, want, collapsed) } expanded := RenderEditPreview(preview, "", 80, true) - if !strings.Contains(expanded, "eleven") { - t.Fatalf("expanded preview missing eleventh row: %q", expanded) - } - if strings.Contains(expanded, "more lines") { - t.Fatalf("expanded preview should not show more-lines marker: %q", expanded) - } + testassert.Require(t, strings.Contains(expanded, "eleven") && !strings.Contains(expanded, "more lines"), "expanded preview missing eleventh row: %q; expanded preview should not show more-lines marker: %q", expanded, expanded) } func TestRenderEditPreviewCollapsedSkipsHunkGaps(t *testing.T) { @@ -84,37 +65,24 @@ func TestRenderEditPreviewCollapsedSkipsHunkGaps(t *testing.T) { }} collapsed := RenderEditPreview(preview, "", 80, false) - if got, want := strings.Count(collapsed, nl), collapsedEditPreviewLines+1; got != want { - t.Fatalf("collapsed preview terminal lines = %d, want %d: %q", got, want, collapsed) - } - if strings.Contains(collapsed, nl+nl) { - t.Fatalf("collapsed preview should not include hunk gap: %q", collapsed) - } - if strings.Contains(collapsed, "eleven") { - t.Fatalf("collapsed preview should hide eleventh row: %q", collapsed) + { + got, want := strings.Count(collapsed, nl), collapsedEditPreviewLines+1 + testassert.Require(t, got == want, "collapsed preview terminal lines = %d, want %d: %q", got, want, collapsed) } + testassert.Require(t, !strings.Contains(collapsed, nl+nl) && !strings.Contains(collapsed, "eleven"), "collapsed preview should not include hunk gap: %q; collapsed preview should hide eleventh row: %q", collapsed, collapsed) } func TestRenderOutputPreviewShowsMoreLinesMarker(t *testing.T) { rendered := renderOutputPreview("1\n2\n3\n4\n5\n6\n7", " ", 80, colorDim, false) - if strings.Contains(rendered, "6") || strings.Contains(rendered, "7") { - t.Fatalf("collapsed output should hide extra lines: %q", rendered) - } - if !strings.Contains(rendered, "(2 more lines)") { - t.Fatalf("collapsed output missing more-lines marker: %q", rendered) - } + testassert.Require(t, !strings.Contains(rendered, "6") && !strings.Contains(rendered, "7") && strings.Contains(rendered, "(2 more lines)"), "collapsed output should hide extra lines: %q; collapsed output missing more-lines marker: %q", rendered, rendered) expanded := renderOutputPreview("1\n2\n3\n4\n5\n6\n7", " ", 80, colorDim, true) - if !strings.Contains(expanded, "6") || strings.Contains(expanded, "more lines") { - t.Fatalf("expanded output should show all lines without marker: %q", expanded) - } + testassert.Require(t, strings.Contains(expanded, "6") && !strings.Contains(expanded, "more lines"), "expanded output should show all lines without marker: %q", expanded) } func TestRenderToolCallShowsWriteLineCount(t *testing.T) { rendered := RenderToolCall("write_file", `{"path":"x.go","content":"one\ntwo\n"}`, nil) - if !strings.Contains(rendered, colorGreen+"+2"+colorCyan) { - t.Fatalf("write_file header missing green inserted-line count: %q", rendered) - } + testassert.Require(t, strings.Contains(rendered, colorGreen+"+2"+colorCyan), "write_file header missing green inserted-line count: %q", rendered) } func TestRenderToolCallShowsEditChangeCounts(t *testing.T) { @@ -125,28 +93,12 @@ func TestRenderToolCallShowsEditChangeCounts(t *testing.T) { }}}}} rendered := RenderToolCall("edit_file", `{"path":"x.go"}`, metadata) - if !strings.Contains(rendered, colorGreen+"+2"+colorCyan) { - t.Fatalf("edit_file header missing green inserted-line count: %q", rendered) - } - if !strings.Contains(rendered, colorRed+"-1"+colorCyan) { - t.Fatalf("edit_file header missing red deleted-line count: %q", rendered) - } + testassert.Require(t, strings.Contains(rendered, colorGreen+"+2"+colorCyan) && strings.Contains(rendered, colorRed+"-1"+colorCyan), "edit_file header missing green inserted-line count: %q; edit_file header missing red deleted-line count: %q", rendered, rendered) } func TestRenderToolCallPreservesMultilineCommandHeader(t *testing.T) { rendered := RenderToolCall("run_command", `{"command":"cd /tmp && git commit -m \"Title\n\n - one\n - two with a deliberately long line that should not be shortened by the header renderer\""}`, nil) - if !strings.Contains(rendered, "Title"+nl+" (3 more lines)") { - t.Fatalf("tool header should collapse multiline command with continuation marker: %q", rendered) - } - if strings.Contains(rendered, `Title - one - two`) { - t.Fatalf("tool header should not collapse multiline command into one line: %q", rendered) - } - if strings.Contains(rendered, "- one") || strings.Contains(rendered, "- two") { - t.Fatalf("tool header should hide continuation command lines: %q", rendered) - } - if strings.Contains(rendered, "...") { - t.Fatalf("tool header should not truncate command lines: %q", rendered) - } + testassert.Require(t, strings.Contains(rendered, "Title"+nl+" (3 more lines)") && !strings.Contains(rendered, `Title - one - two`) && !strings.Contains(rendered, "- one") && !strings.Contains(rendered, "- two") && !strings.Contains(rendered, "..."), "tool header should collapse multiline command with continuation marker: %q; tool header should not collapse multiline command into one line: %q; tool header should hide continuation command lines: %q; tool header should not truncate command lines: %q", rendered, rendered, rendered, rendered) } func TestExtractJSONString(t *testing.T) { @@ -166,9 +118,7 @@ func TestExtractJSONString(t *testing.T) { } for _, tc := range cases { got := extractJSONString(tc.json, tc.key) - if got != tc.want { - t.Errorf("extractJSONString(%q, %q) = %q, want %q", tc.json, tc.key, got, tc.want) - } + testassert.Check(t, got == tc.want, "extractJSONString(%q, %q) = %q, want %q", tc.json, tc.key, got, tc.want) } } @@ -187,20 +137,14 @@ func TestExtractJSONNumber(t *testing.T) { } for _, tc := range cases { got := extractJSONNumber(tc.json, tc.key) - if got != tc.want { - t.Errorf("extractJSONNumber(%q, %q) = %q, want %q", tc.json, tc.key, got, tc.want) - } + testassert.Check(t, got == tc.want, "extractJSONNumber(%q, %q) = %q, want %q", tc.json, tc.key, got, tc.want) } } func TestFormatToolArgsSleepQuotedNumberFallback(t *testing.T) { got := FormatToolArgs("sleep", `{"seconds":"5"}`) - if got != `{"seconds":"5"}` { - t.Errorf("FormatToolArgs sleep with quoted number = %q, want raw args fallback", got) - } + testassert.Check(t, got == `{"seconds":"5"}`, "FormatToolArgs sleep with quoted number = %q, want raw args fallback", got) got = FormatToolArgs("sleep", `{"seconds":5}`) - if got != "5s" { - t.Errorf("FormatToolArgs sleep with real number = %q, want %q", got, "5s") - } + testassert.Check(t, got == "5s", "FormatToolArgs sleep with real number = %q, want %q", got, "5s") } diff --git a/internal/testassert/assert.go b/internal/testassert/assert.go new file mode 100644 index 00000000..ed82f450 --- /dev/null +++ b/internal/testassert/assert.go @@ -0,0 +1,39 @@ +// Package testassert provides the assertion primitives shared by the Go test +// suites: fatal and nonfatal boolean checks, fatal error presence, and fatal +// comparable equality. It exists so test files share one leaf assertion +// construction instead of repeating the same guard. +package testassert + +import "testing" + +// Require marks the test failed and stops it when ok is false. +func Require(t testing.TB, ok bool, format string, args ...any) { + t.Helper() + if !ok { + t.Fatalf(format, args...) + } +} + +// Check marks the test failed when ok is false and continues execution. +func Check(t testing.TB, ok bool, format string, args ...any) { + t.Helper() + if !ok { + t.Errorf(format, args...) + } +} + +// NoError marks the test failed and stops it when err is non-nil. +func NoError(t testing.TB, err error) { + t.Helper() + if err != nil { + t.Fatalf("%v", err) + } +} + +// Equal marks the test failed and stops it when got and want differ. +func Equal[T comparable](t testing.TB, got, want T) { + t.Helper() + if got != want { + t.Fatalf("got %v, want %v", got, want) + } +} diff --git a/internal/testassert/assert_test.go b/internal/testassert/assert_test.go new file mode 100644 index 00000000..c4f645a2 --- /dev/null +++ b/internal/testassert/assert_test.go @@ -0,0 +1,85 @@ +package testassert + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + "testing" +) + +// selfExecEnv switches the running test binary into the child mode of the +// assertion falsification regression. +const selfExecEnv = "LIGHTCODE_TESTASSERT_SELF_EXEC" + +// selfExecMarker is printed after the assertion calls of one child mode. The +// parent requires it exactly where the call must not stop the test. +const selfExecMarker = "testassert self-exec: after assertions" + +// TestAssertionsSelfExec proves the shared assertion owner still fails when it +// must: positive calls pass, false Require, NoError and Equal stop the test +// before the marker, and false Check fails while reaching it. +func TestAssertionsSelfExec(t *testing.T) { + if mode := os.Getenv(selfExecEnv); mode != "" { + runSelfExec(t, mode) + return + } + cases := []struct { + mode string + wantFailed bool + wantMarker bool + }{ + {"pass", false, true}, + {"require", true, false}, + {"check", true, true}, + {"noerror", true, false}, + {"equal", true, false}, + } + for _, tc := range cases { + t.Run(tc.mode, func(t *testing.T) { + cmd := exec.Command(os.Args[0], "-test.run=^"+t.Name()+"$", "-test.timeout=120s") + cmd.Env = append(os.Environ(), selfExecEnv+"="+tc.mode) + out, err := cmd.CombinedOutput() + testassertRun(t, tc.mode, tc.wantFailed, tc.wantMarker, err, string(out)) + }) + } +} + +// testassertRun checks one child mode's exit status and marker in the parent: +// t.Name() in the child includes the subtest, so the run filter selects it +// while the environment selects the subject. +func testassertRun(t *testing.T, mode string, wantFailed, wantMarker bool, err error, out string) { + t.Helper() + if wantFailed { + if err == nil { + t.Fatalf("%s child passed, want a failing exit status:\n%s", mode, out) + } + } else if err != nil { + t.Fatalf("%s child failed: %v\n%s", mode, err, out) + } + if got := strings.Contains(out, selfExecMarker); got != wantMarker { + t.Fatalf("%s child marker present = %v, want %v:\n%s", mode, got, wantMarker, out) + } +} + +// runSelfExec is the child body: the named assertion call and the marker that +// follows it when the call does not stop the test. +func runSelfExec(t *testing.T, mode string) { + switch mode { + case "pass": + Require(t, true, "require true") + Check(t, true, "check true") + NoError(t, nil) + Equal(t, "same", "same") + case "require": + Require(t, false, "require false") + case "check": + Check(t, false, "check false") + case "noerror": + NoError(t, errors.New("noerror failure")) + case "equal": + Equal(t, 1, 2) + } + fmt.Println(selfExecMarker) +} diff --git a/internal/tool/apply_patch_display_test.go b/internal/tool/apply_patch_display_test.go index 10da045c..0f6e1eaf 100644 --- a/internal/tool/apply_patch_display_test.go +++ b/internal/tool/apply_patch_display_test.go @@ -6,90 +6,55 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/editpreview" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestApplyPatchPreviewMetadataEmitsEditPreviewFiles(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "x.txt"), []byte("alpha\nBEFORE\nbeta"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("bye"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "x.txt"), []byte("alpha\nBEFORE\nbeta"), 0o644)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "a.txt"), []byte("bye"), 0o644)) store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Add File: new.txt\n+hi\n*** Update File: x.txt\n@@\n alpha\n-BEFORE\n+AFTER\n beta\n*** Delete File: a.txt") res, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err != nil { - t.Fatalf("Execute err = %v", err) - } + testassert.NoError(t, err) md := ApplyPatchPreviewMetadata(res.Previews) files, ok := editpreview.FilesFromMetadata(md) - if !ok { - t.Fatalf("edit_preview_files not present in metadata: %+v", md) - } - if len(files) != 3 { - t.Fatalf("files len = %d, want 3 (Add, Update, Delete)", len(files)) - } - if files[0].Op != "A" || files[0].Path != "new.txt" { - t.Fatalf("files[0] = %+v, want A new.txt", files[0]) - } - if len(files[0].Preview.Hunks) != 1 || len(files[0].Preview.Hunks[0].Rows) != 1 { - t.Fatalf("files[0].Preview = %+v, want one hunk with one add row", files[0].Preview) - } - if files[1].Op != "M" || files[1].Path != "x.txt" { - t.Fatalf("files[1] = %+v, want M x.txt", files[1]) - } - if len(files[1].Preview.Hunks) != 1 { - t.Fatalf("files[1].Preview.Hunks len = %d, want 1", len(files[1].Preview.Hunks)) - } + testassert.Require(t, ok, "edit_preview_files not present in metadata: %+v", md) + testassert.Equal(t, len(files), 3) + testassert.Require(t, files[0].Op == "A" && files[0].Path == "new.txt", "files[0] = %+v, want A new.txt", files[0]) + testassert.Require(t, len(files[0].Preview.Hunks) == 1 && len(files[0].Preview.Hunks[0].Rows) == 1, "files[0].Preview = %+v, want one hunk with one add row", files[0].Preview) + testassert.Require(t, files[1].Op == "M" && files[1].Path == "x.txt", "files[1] = %+v, want M x.txt", files[1]) + testassert.Equal(t, len(files[1].Preview.Hunks), 1) wantRows := []editpreview.Row{ {Kind: editpreview.KindContext, OldLine: 1, NewLine: 1, Text: "alpha"}, {Kind: editpreview.KindRemove, OldLine: 2, Text: "BEFORE"}, {Kind: editpreview.KindAdd, NewLine: 2, Text: "AFTER"}, {Kind: editpreview.KindContext, OldLine: 3, NewLine: 3, Text: "beta"}, } - if len(files[1].Preview.Hunks[0].Rows) != len(wantRows) { - t.Fatalf("files[1] hunk rows len = %d, want %d", len(files[1].Preview.Hunks[0].Rows), len(wantRows)) - } + testassert.Equal(t, len(files[1].Preview.Hunks[0].Rows), len(wantRows)) for i, r := range files[1].Preview.Hunks[0].Rows { - if r != wantRows[i] { - t.Fatalf("files[1] hunk row[%d] = %+v, want %+v", i, r, wantRows[i]) - } - } - if files[2].Op != "D" || files[2].Path != "a.txt" { - t.Fatalf("files[2] = %+v, want D a.txt", files[2]) + testassert.Require(t, r == wantRows[i], "files[1] hunk row[%d] = %+v, want %+v", i, r, wantRows[i]) } + testassert.Require(t, files[2].Op == "D" && files[2].Path == "a.txt", "files[2] = %+v, want D a.txt", files[2]) } func TestApplyPatchPreviewMetadataMoveTwoEntries(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "old.go"), []byte("a\nB\nc"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "old.go"), []byte("a\nB\nc"), 0o644)) store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Update File: old.go\n*** Move to: new.go\n@@\n a\n-B\n+B2\n c") res, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err != nil { - t.Fatalf("Execute err = %v", err) - } + testassert.NoError(t, err) md := ApplyPatchPreviewMetadata(res.Previews) files, ok := editpreview.FilesFromMetadata(md) - if !ok { - t.Fatalf("edit_preview_files not present: %+v", md) - } - if len(files) != 2 { - t.Fatalf("files len = %d, want 2 (M new.go, D old.go)", len(files)) - } - if files[0].Op != "M" || files[0].Path != "new.go" { - t.Fatalf("files[0] = %+v, want M new.go", files[0]) - } - if files[1].Op != "D" || files[1].Path != "old.go" { - t.Fatalf("files[1] = %+v, want D old.go", files[1]) - } + testassert.Require(t, ok, "edit_preview_files not present: %+v", md) + testassert.Equal(t, len(files), 2) + testassert.Require(t, files[0].Op == "M" && files[0].Path == "new.go", "files[0] = %+v, want M new.go", files[0]) + testassert.Require(t, files[1].Op == "D" && files[1].Path == "old.go", "files[1] = %+v, want D old.go", files[1]) } func TestApplyPatchFailureEmitsNoPreviewMetadata(t *testing.T) { @@ -98,22 +63,23 @@ func TestApplyPatchFailureEmitsNoPreviewMetadata(t *testing.T) { input := applyPatchInput(t, "*** Delete File: ghost.txt") res, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want non-nil for delete-of-missing") - } - if md := ApplyPatchPreviewMetadata(res.Previews); md != nil { - t.Fatalf("metadata after failure = %+v, want nil", md) + testassert.Require(t, err != nil, "Execute err = nil, want non-nil for delete-of-missing") + { + md := ApplyPatchPreviewMetadata(res.Previews) + testassert.Require(t, md == nil, "metadata after failure = %+v, want nil", md) } } func TestEditPreviewFilesFromMetadataAbsent(t *testing.T) { - if files, ok := editpreview.FilesFromMetadata(map[string]any{"other": 1}); ok || files != nil { - t.Fatalf("got %v / %v, want nil / false", files, ok) + { + files, ok := editpreview.FilesFromMetadata(map[string]any{"other": 1}) + testassert.Require(t, !ok && files == nil, "got %v / %v, want nil / false", files, ok) } } func TestEditPreviewFilesFromMetadataWrongShape(t *testing.T) { - if files, ok := editpreview.FilesFromMetadata(map[string]any{"edit_preview_files": "not an array"}); ok || files != nil { - t.Fatalf("got %v / %v, want nil / false", files, ok) + { + files, ok := editpreview.FilesFromMetadata(map[string]any{"edit_preview_files": "not an array"}) + testassert.Require(t, !ok && files == nil, "got %v / %v, want nil / false", files, ok) } } diff --git a/internal/tool/apply_patch_match_test.go b/internal/tool/apply_patch_match_test.go index a7e5ab62..61852c11 100644 --- a/internal/tool/apply_patch_match_test.go +++ b/internal/tool/apply_patch_match_test.go @@ -3,85 +3,82 @@ package tool import ( "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestLocateScenarios(t *testing.T) { - type probe struct { + cases := []struct { + name string + lines []string + h hunk cursor int start int wantErr bool errPrefix string - } - cases := []struct { - name string - lines []string - h hunk - probes []probe }{ { - name: "TestLocateExactMatch", - lines: []string{"foo", "bar", "baz"}, - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "bar"}}}, - probes: []probe{ - {cursor: 0, start: 1}, - }, + name: "TestLocateExactMatch", + lines: []string{"foo", "bar", "baz"}, + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "bar"}}}, + cursor: 0, + start: 1, }, { - name: "TestLocateForwardScan", - lines: []string{"foo", "bar", "foo", "bar"}, - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}, {Kind: lineContext, Text: "bar"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - {cursor: 2, start: 2}, - }, + name: "TestLocateForwardScan first match", + lines: []string{"foo", "bar", "foo", "bar"}, + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}, {Kind: lineContext, Text: "bar"}}}, + cursor: 0, + start: 0, }, { - name: "TestLocateLevel2TrailingWhitespace", - lines: []string{"foo ", "bar", "baz"}, - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateForwardScan second match", + lines: []string{"foo", "bar", "foo", "bar"}, + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}, {Kind: lineContext, Text: "bar"}}}, + cursor: 2, + start: 2, }, { - name: "TestLocateLevel3LeadingAndTrailingWhitespace", - lines: []string{" foo ", "bar", "baz"}, - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateLevel2TrailingWhitespace", + lines: []string{"foo ", "bar", "baz"}, + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, + cursor: 0, + start: 0, }, { - name: "TestLocateLevel4UnicodeDashes", - lines: []string{"foo\u2014bar", "baz"}, // em-dash - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo-bar"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateLevel3LeadingAndTrailingWhitespace", + lines: []string{" foo ", "bar", "baz"}, + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, + cursor: 0, + start: 0, }, { - name: "TestLocateLevel4UnicodeQuotes", - lines: []string{"say \u201Chello\u201D", "baz"}, // curly double quotes - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "say \"hello\""}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateLevel4UnicodeDashes", + lines: []string{"foo\u2014bar", "baz"}, // em-dash + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo-bar"}}}, + cursor: 0, + start: 0, }, { - name: "TestLocateLevel4ExoticSpaces", - lines: []string{"\u00A0foo\u00A0", "bar"}, // non-breaking space (U+00A0) on each side - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateLevel4UnicodeQuotes", + lines: []string{"say \u201Chello\u201D", "baz"}, // curly double quotes + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "say \"hello\""}}}, + cursor: 0, + start: 0, }, { - name: "TestLocateLowerLevelBeatsHigher", - lines: []string{"foo", "bar", "foo "}, // Level 1 matches at 0; level-2 fuzzy match at 2. - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateLevel4ExoticSpaces", + lines: []string{"\u00A0foo\u00A0", "bar"}, // non-breaking space (U+00A0) on each side + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, + cursor: 0, + start: 0, + }, + { + name: "TestLocateLowerLevelBeatsHigher", + lines: []string{"foo", "bar", "foo "}, // Level 1 matches at 0; level-2 fuzzy match at 2. + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, + cursor: 0, + start: 0, }, { name: "TestLocateAnchorAdvancesCursor", @@ -90,9 +87,8 @@ func TestLocateScenarios(t *testing.T) { anchor: "anchor", lines: []hunkLine{{Kind: lineContext, Text: "bar"}}, }, - probes: []probe{ - {cursor: 0, start: 2}, - }, + cursor: 0, + start: 2, }, { name: "TestLocateAnchorDoesNotNarrowRegion", @@ -101,9 +97,8 @@ func TestLocateScenarios(t *testing.T) { anchor: "anchor", lines: []hunkLine{{Kind: lineContext, Text: "match"}}, }, - probes: []probe{ - {cursor: 0, start: 4}, - }, + cursor: 0, + start: 4, }, { name: "TestLocateAnchorFoundViaFuzzyLadder", @@ -112,9 +107,8 @@ func TestLocateScenarios(t *testing.T) { anchor: "anchor", lines: []hunkLine{{Kind: lineContext, Text: "match"}}, }, - probes: []probe{ - {cursor: 0, start: 1}, - }, + cursor: 0, + start: 1, }, { name: "TestLocateEOFTrailingEmptyRetryDoesNotMatchBeforeEOF", @@ -123,17 +117,17 @@ func TestLocateScenarios(t *testing.T) { {Kind: lineContext, Text: "alpha"}, {Kind: lineRemove, Text: ""}, }}, - probes: []probe{ - {cursor: 0, wantErr: true, errPrefix: "Failed to find expected lines in p:"}, - }, + cursor: 0, + wantErr: true, + errPrefix: "Failed to find expected lines in p:", }, { - name: "TestLocatePatternNotFound", - lines: []string{"foo", "bar"}, - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "missing"}}}, - probes: []probe{ - {cursor: 0, wantErr: true, errPrefix: "Failed to find expected lines in p:"}, - }, + name: "TestLocatePatternNotFound", + lines: []string{"foo", "bar"}, + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "missing"}}}, + cursor: 0, + wantErr: true, + errPrefix: "Failed to find expected lines in p:", }, { name: "TestLocateAnchorNotFound", @@ -142,9 +136,9 @@ func TestLocateScenarios(t *testing.T) { anchor: "missing", lines: []hunkLine{{Kind: lineContext, Text: "foo"}}, }, - probes: []probe{ - {cursor: 0, wantErr: true, errPrefix: "Failed to find context 'missing' in p"}, - }, + cursor: 0, + wantErr: true, + errPrefix: "Failed to find context 'missing' in p", }, { name: "TestLocateMixedContextAndRemove", @@ -155,48 +149,43 @@ func TestLocateScenarios(t *testing.T) { {Kind: lineAdd, Text: "REPLACEMENT"}, // not in pattern {Kind: lineContext, Text: "beta"}, }}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + cursor: 0, + start: 0, }, { - name: "TestLocateNoContextOrRemove", - lines: []string{"foo", "bar"}, // Hunk with only add lines: no pattern to match; return the cursor. - h: hunk{lines: []hunkLine{{Kind: lineAdd, Text: "anything"}}}, - probes: []probe{ - {cursor: 0, start: 0}, - }, + name: "TestLocateNoContextOrRemove", + lines: []string{"foo", "bar"}, // Hunk with only add lines: no pattern to match; return the cursor. + h: hunk{lines: []hunkLine{{Kind: lineAdd, Text: "anything"}}}, + cursor: 0, + start: 0, }, { - name: "TestLocateCursorClamped", - lines: []string{"foo", "bar"}, // Negative cursor clamps to 0; cursor past end clamps to len. - h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, - probes: []probe{ - {cursor: -5, start: 0}, - {cursor: 100, wantErr: true}, - }, + name: "TestLocateCursorClamped negative cursor", + lines: []string{"foo", "bar"}, // Negative cursor clamps to 0. + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, + cursor: -5, + start: 0, + }, + { + name: "TestLocateCursorClamped past end", + lines: []string{"foo", "bar"}, // Cursor past end clamps to len. + h: hunk{lines: []hunkLine{{Kind: lineContext, Text: "foo"}}}, + cursor: 100, + wantErr: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - for _, p := range tc.probes { - start, err := locate(tc.lines, tc.h, "p", p.cursor) - if p.wantErr { - if err == nil { - t.Fatalf("locate(cursor=%d) err = nil, want non-nil", p.cursor) - } - if p.errPrefix != "" && !strings.HasPrefix(err.Error(), p.errPrefix) { - t.Fatalf("locate(cursor=%d) err = %q, want prefix %q", p.cursor, err.Error(), p.errPrefix) - } - continue - } - if err != nil { - t.Fatalf("locate(cursor=%d) err = %v", p.cursor, err) - } - if start != p.start { - t.Fatalf("locate(cursor=%d) start = %d, want %d", p.cursor, start, p.start) + start, err := locate(tc.lines, tc.h, "p", tc.cursor) + if tc.wantErr { + testassert.Require(t, err != nil, "locate(cursor=%d) err = nil, want non-nil", tc.cursor) + if tc.errPrefix != "" && !strings.HasPrefix(err.Error(), tc.errPrefix) { + t.Fatalf("locate(cursor=%d) err = %q, want prefix %q", tc.cursor, err.Error(), tc.errPrefix) } + return } + testassert.Require(t, err == nil, "locate(cursor=%d) err = %v", tc.cursor, err) + testassert.Require(t, start == tc.start, "locate(cursor=%d) start = %d, want %d", tc.cursor, start, tc.start) }) } } @@ -212,19 +201,11 @@ func TestLocateEOFTrailingEmptyRetry(t *testing.T) { {Kind: lineContext, Text: ""}, }} start, err := locate(lines, h, "p", 0) - if err != nil { - t.Fatalf("locate err = %v (EOF empty retry should have dropped the trailing empty)", err) - } - if start != 0 { - t.Fatalf("start = %d, want 0", start) - } + testassert.NoError(t, err) + testassert.Equal(t, start, 0) match, err := locateHunk(lines, h, "p", 0) - if err != nil { - t.Fatalf("locateHunk err = %v", err) - } - if match.start != 0 || match.realMatchedLen != 2 || !match.syntheticTrailingEmpty { - t.Fatalf("locateHunk = %+v, want start 0 len 2 synthetic trailing empty", match) - } + testassert.NoError(t, err) + testassert.Require(t, match.start == 0 && match.realMatchedLen == 2 && match.syntheticTrailingEmpty, "locateHunk = %+v, want start 0 len 2 synthetic trailing empty", match) } func TestLocateNoEOFTrailingEmptyWhenFileHasIt(t *testing.T) { @@ -237,17 +218,9 @@ func TestLocateNoEOFTrailingEmptyWhenFileHasIt(t *testing.T) { {Kind: lineContext, Text: ""}, }} start, err := locate(lines, h, "p", 0) - if err != nil { - t.Fatalf("locate err = %v", err) - } - if start != 0 { - t.Fatalf("start = %d, want 0", start) - } + testassert.NoError(t, err) + testassert.Equal(t, start, 0) match, err := locateHunk(lines, h, "p", 0) - if err != nil { - t.Fatalf("locateHunk err = %v", err) - } - if match.start != 0 || match.realMatchedLen != 3 || match.syntheticTrailingEmpty { - t.Fatalf("locateHunk = %+v, want start 0 len 3 no synthetic line", match) - } + testassert.NoError(t, err) + testassert.Require(t, match.start == 0 && match.realMatchedLen == 3 && !match.syntheticTrailingEmpty, "locateHunk = %+v, want start 0 len 3 no synthetic line", match) } diff --git a/internal/tool/apply_patch_parse_test.go b/internal/tool/apply_patch_parse_test.go index ceeb8d00..9964e952 100644 --- a/internal/tool/apply_patch_parse_test.go +++ b/internal/tool/apply_patch_parse_test.go @@ -4,6 +4,8 @@ import ( "errors" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestParsePatchEnvelopeErrors(t *testing.T) { @@ -25,9 +27,7 @@ func TestParsePatchEnvelopeErrors(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := parsePatch(tc.input) - if !errors.Is(err, tc.want) { - t.Fatalf("parsePatch(%q) err = %v, want %v", tc.input, err, tc.want) - } + testassert.Require(t, errors.Is(err, tc.want), "parsePatch(%q) err = %v, want %v", tc.input, err, tc.want) }) } } @@ -35,175 +35,85 @@ func TestParsePatchEnvelopeErrors(t *testing.T) { func TestParsePatchAddFile(t *testing.T) { input := "*** Begin Patch\n*** Add File: new.txt\n+hello\n+world\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops) != 1 { - t.Fatalf("ops = %d, want 1", len(p.ops)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops), 1) op := p.ops[0] - if op.kind != opAdd { - t.Fatalf("op.kind = %d, want opAdd", op.kind) - } - if op.path != "new.txt" { - t.Fatalf("op.path = %q, want new.txt", op.path) - } - if len(op.hunks) != 2 { - t.Fatalf("hunks = %d, want 2 (one per + line)", len(op.hunks)) - } - if op.hunks[0].lines[0].Kind != lineAdd || op.hunks[0].lines[0].Text != "hello" { - t.Fatalf("hunks[0] = %+v, want add/hello", op.hunks[0]) - } - if op.hunks[1].lines[0].Kind != lineAdd || op.hunks[1].lines[0].Text != "world" { - t.Fatalf("hunks[1] = %+v, want add/world", op.hunks[1]) - } + testassert.Require(t, op.kind == opAdd && op.path == "new.txt" && len(op.hunks) == 2, "got %v, want %v; got %v, want %v; got %v, want %v", op.kind, opAdd, op.path, "new.txt", len(op.hunks), 2) + testassert.Require(t, op.hunks[0].lines[0].Kind == lineAdd && op.hunks[0].lines[0].Text == "hello", "hunks[0] = %+v, want add/hello", op.hunks[0]) + testassert.Require(t, op.hunks[1].lines[0].Kind == lineAdd && op.hunks[1].lines[0].Text == "world", "hunks[1] = %+v, want add/world", op.hunks[1]) } func TestParsePatchAddFileEmptyContentLine(t *testing.T) { input := "*** Begin Patch\n*** Add File: new.txt\n+a\n+\n+b\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops) != 1 { - t.Fatalf("ops = %d, want 1", len(p.ops)) - } - if len(p.ops[0].hunks) != 3 { - t.Fatalf("hunks = %d, want 3", len(p.ops[0].hunks)) - } - if got := p.ops[0].hunks[1].lines[0].Text; got != "" { - t.Fatalf("hunks[1] text = %q, want empty (a bare + line is an empty content line)", got) - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops), 1) + testassert.Equal(t, len(p.ops[0].hunks), 3) + testassert.Equal(t, p.ops[0].hunks[1].lines[0].Text, "") } func TestParsePatchUpdateFile(t *testing.T) { input := "*** Begin Patch\n*** Update File: a.go\n@@ func Foo\n ctx\n-old\n+new\n ctx2\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops) != 1 { - t.Fatalf("ops = %d, want 1", len(p.ops)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops), 1) op := p.ops[0] - if op.kind != opUpdate { - t.Fatalf("op.kind = %d, want opUpdate", op.kind) - } - if op.path != "a.go" { - t.Fatalf("op.path = %q, want a.go", op.path) - } - if op.movePath != "" { - t.Fatalf("op.movePath = %q, want empty", op.movePath) - } - if len(op.hunks) != 1 { - t.Fatalf("hunks = %d, want 1", len(op.hunks)) - } + testassert.Require(t, op.kind == opUpdate && op.path == "a.go" && op.movePath == "" && len(op.hunks) == 1, "got %v, want %v; got %v, want %v; got %v, want %v; got %v, want %v", op.kind, opUpdate, op.path, "a.go", op.movePath, "", len(op.hunks), 1) h := op.hunks[0] - if h.anchor != "func Foo" { - t.Fatalf("hunk anchor = %q, want func Foo", h.anchor) - } + testassert.Equal(t, h.anchor, "func Foo") want := []hunkLine{ {Kind: lineContext, Text: "ctx"}, {Kind: lineRemove, Text: "old"}, {Kind: lineAdd, Text: "new"}, {Kind: lineContext, Text: "ctx2"}, } - if len(h.lines) != len(want) { - t.Fatalf("hunk lines = %d, want %d", len(h.lines), len(want)) - } + testassert.Equal(t, len(h.lines), len(want)) for i, l := range h.lines { - if l != want[i] { - t.Fatalf("hunk lines[%d] = %+v, want %+v", i, l, want[i]) - } + testassert.Require(t, l == want[i], "hunk lines[%d] = %+v, want %+v", i, l, want[i]) } } func TestParsePatchUpdateFileNoAnchor(t *testing.T) { input := "*** Begin Patch\n*** Update File: a.go\n@@\n-removed\n+added\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if got := p.ops[0].hunks[0].anchor; got != "" { - t.Fatalf("hunk anchor = %q, want empty", got) - } + testassert.NoError(t, err) + testassert.Equal(t, p.ops[0].hunks[0].anchor, "") } func TestParsePatchDeleteFile(t *testing.T) { input := "*** Begin Patch\n*** Delete File: old.txt\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops) != 1 { - t.Fatalf("ops = %d, want 1", len(p.ops)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops), 1) op := p.ops[0] - if op.kind != opDelete { - t.Fatalf("op.kind = %d, want opDelete", op.kind) - } - if op.path != "old.txt" { - t.Fatalf("op.path = %q, want old.txt", op.path) - } - if len(op.hunks) != 0 { - t.Fatalf("hunks = %d, want 0", len(op.hunks)) - } + testassert.Require(t, op.kind == opDelete && op.path == "old.txt" && len(op.hunks) == 0, "got %v, want %v; got %v, want %v; got %v, want %v", op.kind, opDelete, op.path, "old.txt", len(op.hunks), 0) } func TestParsePatchUpdateWithMove(t *testing.T) { input := "*** Begin Patch\n*** Update File: old.go\n*** Move to: new.go\n@@ package main\n-func main() {}\n+func main() { run() }\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } + testassert.NoError(t, err) op := p.ops[0] - if op.kind != opUpdate { - t.Fatalf("op.kind = %d, want opUpdate", op.kind) - } - if op.path != "old.go" { - t.Fatalf("op.path = %q, want old.go", op.path) - } - if op.movePath != "new.go" { - t.Fatalf("op.movePath = %q, want new.go", op.movePath) - } - if len(op.hunks) != 1 { - t.Fatalf("hunks = %d, want 1", len(op.hunks)) - } + testassert.Require(t, op.kind == opUpdate && op.path == "old.go" && op.movePath == "new.go" && len(op.hunks) == 1, "got %v, want %v; got %v, want %v; got %v, want %v; got %v, want %v", op.kind, opUpdate, op.path, "old.go", op.movePath, "new.go", len(op.hunks), 1) } func TestParsePatchMultipleOps(t *testing.T) { input := "*** Begin Patch\n*** Add File: a\n+x\n*** Update File: b\n@@\n-c\n+d\n*** Delete File: c\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops) != 3 { - t.Fatalf("ops = %d, want 3", len(p.ops)) - } - if p.ops[0].kind != opAdd || p.ops[0].path != "a" { - t.Fatalf("ops[0] = %+v, want Add a", p.ops[0]) - } - if p.ops[1].kind != opUpdate || p.ops[1].path != "b" { - t.Fatalf("ops[1] = %+v, want Update b", p.ops[1]) - } - if p.ops[2].kind != opDelete || p.ops[2].path != "c" { - t.Fatalf("ops[2] = %+v, want Delete c", p.ops[2]) - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops), 3) + testassert.Require(t, p.ops[0].kind == opAdd && p.ops[0].path == "a", "ops[0] = %+v, want Add a", p.ops[0]) + testassert.Require(t, p.ops[1].kind == opUpdate && p.ops[1].path == "b", "ops[1] = %+v, want Update b", p.ops[1]) + testassert.Require(t, p.ops[2].kind == opDelete && p.ops[2].path == "c", "ops[2] = %+v, want Delete c", p.ops[2]) } func TestParsePatchMultipleHunksOneFile(t *testing.T) { input := "*** Begin Patch\n*** Update File: a\n@@ first\n-old1\n+new1\n@@ second\n-old2\n+new2\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } + testassert.NoError(t, err) op := p.ops[0] - if len(op.hunks) != 2 { - t.Fatalf("hunks = %d, want 2", len(op.hunks)) - } - if op.hunks[0].anchor != "first" || op.hunks[1].anchor != "second" { - t.Fatalf("anchors = %q, %q, want first, second", op.hunks[0].anchor, op.hunks[1].anchor) - } + testassert.Equal(t, len(op.hunks), 2) + testassert.Require(t, op.hunks[0].anchor == "first" && op.hunks[1].anchor == "second", "anchors = %q, %q, want first, second", op.hunks[0].anchor, op.hunks[1].anchor) } func TestParsePatchPreservesPrefixCharactersInContent(t *testing.T) { @@ -211,55 +121,33 @@ func TestParsePatchPreservesPrefixCharactersInContent(t *testing.T) { // strip exactly one prefix byte and preserve the rest verbatim. input := "*** Begin Patch\n*** Add File: a\n+++counter\n+--option\n+ indented\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops[0].hunks) != 3 { - t.Fatalf("hunks = %d, want 3", len(p.ops[0].hunks)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops[0].hunks), 3) want := []string{"++counter", "--option", " indented"} for i, h := range p.ops[0].hunks { - if h.lines[0].Kind != lineAdd { - t.Fatalf("hunks[%d] kind = %d, want lineAdd", i, h.lines[0].Kind) - } - if h.lines[0].Text != want[i] { - t.Fatalf("hunks[%d] text = %q, want %q", i, h.lines[0].Text, want[i]) - } + testassert.Require(t, h.lines[0].Kind == lineAdd, "hunks[%d] kind = %d, want lineAdd", i, h.lines[0].Kind) + testassert.Require(t, h.lines[0].Text == want[i], "hunks[%d] text = %q, want %q", i, h.lines[0].Text, want[i]) } } func TestParsePatchPreservesRawCR(t *testing.T) { input := "*** Begin Patch\n*** Add File: a\n+line1\r\n+line2\r\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if len(p.ops[0].hunks) != 2 { - t.Fatalf("hunks = %d, want 2", len(p.ops[0].hunks)) - } - if got := p.ops[0].hunks[0].lines[0].Text; got != "line1\r" { - t.Fatalf("hunks[0] text = %q, want %q (\\r must ride inside the line)", got, "line1\r") - } - if got := p.ops[0].hunks[1].lines[0].Text; got != "line2\r" { - t.Fatalf("hunks[1] text = %q, want %q", got, "line2\r") - } + testassert.NoError(t, err) + testassert.Equal(t, len(p.ops[0].hunks), 2) + testassert.Equal(t, p.ops[0].hunks[0].lines[0].Text, "line1\r") + testassert.Equal(t, p.ops[0].hunks[1].lines[0].Text, "line2\r") } func TestParsePatchPreservesCRInUpdateHunk(t *testing.T) { input := "*** Begin Patch\n*** Update File: a\n@@\n ctx\r\n-old\r\n+new\r\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } + testassert.NoError(t, err) h := p.ops[0].hunks[0] want := []string{"ctx\r", "old\r", "new\r"} - if len(h.lines) != len(want) { - t.Fatalf("hunk lines = %d, want %d", len(h.lines), len(want)) - } + testassert.Equal(t, len(h.lines), len(want)) for i, l := range h.lines { - if l.Text != want[i] { - t.Fatalf("hunk lines[%d] text = %q, want %q", i, l.Text, want[i]) - } + testassert.Require(t, l.Text == want[i], "hunk lines[%d] text = %q, want %q", i, l.Text, want[i]) } } @@ -320,12 +208,8 @@ func TestParsePatchRefusals(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { _, err := parsePatch(tc.input) - if err == nil { - t.Fatal("parsePatch err = nil, want non-nil") - } - if tc.sentinel != nil && !errors.Is(err, tc.sentinel) { - t.Fatalf("parsePatch err = %v, want %v", err, tc.sentinel) - } + testassert.Require(t, err != nil, "parsePatch err = nil, want non-nil") + testassert.Require(t, tc.sentinel == nil || errors.Is(err, tc.sentinel), "parsePatch err = %v, want %v", err, tc.sentinel) if tc.contains != "" && !strings.Contains(err.Error(), tc.contains) { t.Fatalf("parsePatch err = %q, want it to contain %q", err.Error(), tc.contains) } @@ -336,21 +220,13 @@ func TestParsePatchRefusals(t *testing.T) { func TestParsePatchTrailingNewlineTolerated(t *testing.T) { input := "*** Begin Patch\n*** Add File: a\n+x\n*** End Patch\n" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v, want nil (trailing newline tolerated)", err) - } - if len(p.ops) != 1 || p.ops[0].path != "a" { - t.Fatalf("ops = %+v, want single Add of a", p.ops) - } + testassert.NoError(t, err) + testassert.Require(t, len(p.ops) == 1 && p.ops[0].path == "a", "ops = %+v, want single Add of a", p.ops) } func TestParsePatchAnchorsWithLeadingTrailingSpaces(t *testing.T) { input := "*** Begin Patch\n*** Update File: a\n@@ spaced anchor \n-x\n+y\n*** End Patch" p, err := parsePatch(input) - if err != nil { - t.Fatalf("parsePatch err = %v", err) - } - if got := p.ops[0].hunks[0].anchor; got != "spaced anchor" { - t.Fatalf("anchor = %q, want %q (anchor should be trimmed)", got, "spaced anchor") - } + testassert.NoError(t, err) + testassert.Equal(t, p.ops[0].hunks[0].anchor, "spaced anchor") } diff --git a/internal/tool/apply_patch_test.go b/internal/tool/apply_patch_test.go index 30ec1348..43890e6a 100644 --- a/internal/tool/apply_patch_test.go +++ b/internal/tool/apply_patch_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/editpreview" + "github.com/MMinasyan/lightcode/internal/testassert" ) func applyPatchInput(t *testing.T, s string) string { @@ -107,14 +108,11 @@ func TestApplyPatchAllOrNothingValidationFailure(t *testing.T) { input := applyPatchInput(t, "*** Add File: new.txt\n+hello\n*** Update File: ghost.go\n@@\n-missing\n+replacement") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want non-nil") - } - if !strings.Contains(err.Error(), "does not exist") { - t.Fatalf("err = %v, want does-not-exist error", err) - } - if _, statErr := os.Stat(newPath); !os.IsNotExist(statErr) { - t.Fatalf("Add landed despite later op's validation failure: stat err = %v", statErr) + testassert.Require(t, err != nil, "Execute err = nil, want non-nil") + testassert.Require(t, strings.Contains(err.Error(), "does not exist"), "err = %v, want does-not-exist error", err) + { + _, statErr := os.Stat(newPath) + testassert.Require(t, os.IsNotExist(statErr), "Add landed despite later op's validation failure: stat err = %v", statErr) } } @@ -128,14 +126,11 @@ func TestApplyPatchAllOrNothingPartialApplyError(t *testing.T) { input := applyPatchInput(t, "*** Add File: new.txt\n+hi\n*** Delete File: ghost.txt") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want non-nil") - } - if !strings.Contains(err.Error(), "does not exist") { - t.Fatalf("err = %v, want does-not-exist error", err) - } - if _, statErr := os.Stat(newPath); !os.IsNotExist(statErr) { - t.Fatalf("Add landed despite later op's validation failure: stat err = %v", statErr) + testassert.Require(t, err != nil, "Execute err = nil, want non-nil") + testassert.Require(t, strings.Contains(err.Error(), "does not exist"), "err = %v, want does-not-exist error", err) + { + _, statErr := os.Stat(newPath) + testassert.Require(t, os.IsNotExist(statErr), "Add landed despite later op's validation failure: stat err = %v", statErr) } } @@ -146,9 +141,7 @@ func TestApplyPatchMidWriteFailureReturnsExitError(t *testing.T) { // result must be a *ExitError carrying the A summary and the I/O // error message. dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "x.txt"), []byte("alpha\nBEFORE\nbeta"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "x.txt"), []byte("alpha\nBEFORE\nbeta"), 0o644)) store := &applyPatchStore{ turn: 1, failOnCall: 2, // the second snapshot call fails @@ -157,99 +150,62 @@ func TestApplyPatchMidWriteFailureReturnsExitError(t *testing.T) { input := applyPatchInput(t, "*** Add File: new.txt\n+hi\n*** Update File: x.txt\n@@\n alpha\n-BEFORE\n+AFTER\n beta") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want non-nil (mid-write failure)") - } + testassert.Require(t, err != nil, "Execute err = nil, want non-nil (mid-write failure)") var exitErr *ExitError - if !errors.As(err, &exitErr) { - t.Fatalf("Execute err = %T %v, want *ExitError", err, err) - } - if !strings.Contains(exitErr.Output, "A new.txt") { - t.Fatalf("ExitError.Output = %q, want it to include the A new.txt committed summary", exitErr.Output) - } - if !strings.Contains(exitErr.Output, "simulated disk full") { - t.Fatalf("ExitError.Output = %q, want it to include the snapshot error", exitErr.Output) - } + testassert.Require(t, errors.As(err, &exitErr), "Execute err = %T %v, want *ExitError", err, err) + testassert.Require(t, strings.Contains(exitErr.Output, "A new.txt") && strings.Contains(exitErr.Output, "simulated disk full"), "ExitError.Output = %q, want it to include the A new.txt committed summary; ExitError.Output = %q, want it to include the snapshot error", exitErr.Output, exitErr.Output) } func TestApplyPatchRefusesUpdateWhenContentChangesAfterValidation(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "target.txt") - if err := os.WriteFile(path, []byte("alpha\nBEFORE\nbeta"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("alpha\nBEFORE\nbeta"), 0o644)) store := &applyPatchStore{turn: 1, onSnapshot: func(call int) { if call == 1 { - if err := os.WriteFile(path, []byte("alpha\nCHANGED\nbeta"), 0o644); err != nil { - t.Fatalf("mutate target: %v", err) - } + testassert.NoError(t, os.WriteFile(path, []byte("alpha\nCHANGED\nbeta"), 0o644)) } }} input := applyPatchInput(t, "*** Add File: first.txt\n+hi\n*** Update File: target.txt\n@@\n alpha\n-BEFORE\n+AFTER\n beta") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil || !strings.Contains(err.Error(), "changed after validation") { - t.Fatalf("Execute err = %v, want changed-after-validation", err) - } - if got := readFile(t, path); got != "alpha\nCHANGED\nbeta" { - t.Fatalf("target = %q, want concurrent content retained", got) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "changed after validation"), "Execute err = %v, want changed-after-validation", err) + testassert.Equal(t, readFile(t, path), "alpha\nCHANGED\nbeta") } func TestApplyPatchRefusesDeleteWhenContentChangesAfterValidation(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "target.txt") - if err := os.WriteFile(path, []byte("delete me"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("delete me"), 0o644)) store := &applyPatchStore{turn: 1, onSnapshot: func(call int) { if call == 1 { - if err := os.WriteFile(path, []byte("changed"), 0o644); err != nil { - t.Fatalf("mutate target: %v", err) - } + testassert.NoError(t, os.WriteFile(path, []byte("changed"), 0o644)) } }} input := applyPatchInput(t, "*** Add File: first.txt\n+hi\n*** Delete File: target.txt") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil || !strings.Contains(err.Error(), "changed after validation") { - t.Fatalf("Execute err = %v, want changed-after-validation", err) - } - if got := readFile(t, path); got != "changed" { - t.Fatalf("target = %q, want concurrent content retained", got) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "changed after validation"), "Execute err = %v, want changed-after-validation", err) + testassert.Equal(t, readFile(t, path), "changed") } func TestApplyPatchRefusesMoveSourceWhenContentChangesAfterDestinationWrite(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "old.txt") dst := filepath.Join(dir, "new.txt") - if err := os.WriteFile(src, []byte("old content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(src, []byte("old content"), 0o644)) store := &applyPatchStore{turn: 1, onSnapshot: func(call int) { if call == 2 { - if err := os.WriteFile(src, []byte("changed"), 0o644); err != nil { - t.Fatalf("mutate source: %v", err) - } + testassert.NoError(t, os.WriteFile(src, []byte("changed"), 0o644)) } }} input := applyPatchInput(t, "*** Add File: first.txt\n+hi\n*** Update File: old.txt\n*** Move to: new.txt") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil || !strings.Contains(err.Error(), "changed after validation") { - t.Fatalf("Execute err = %v, want changed-after-validation", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "changed after validation"), "Execute err = %v, want changed-after-validation", err) var exitErr *ExitError - if !errors.As(err, &exitErr) || !strings.Contains(exitErr.Output, "M new.txt") { - t.Fatalf("Execute err = %v, want partial summary with M new.txt", err) - } - if got := readFile(t, src); got != "changed" { - t.Fatalf("source = %q, want concurrent content retained", got) - } - if got := readFile(t, dst); got != "old content" { - t.Fatalf("dest = %q, want committed pre-change content", got) - } + testassert.Require(t, errors.As(err, &exitErr) && strings.Contains(exitErr.Output, "M new.txt"), "Execute err = %v, want partial summary with M new.txt", err) + testassert.Equal(t, readFile(t, src), "changed") + testassert.Equal(t, readFile(t, dst), "old content") } func TestApplyPatchRecordsSnapshotIdentityForAddUpdateAndDelete(t *testing.T) { @@ -257,17 +213,14 @@ func TestApplyPatchRecordsSnapshotIdentityForAddUpdateAndDelete(t *testing.T) { updatePath := filepath.Join(dir, "update.txt") deletePath := filepath.Join(dir, "delete.txt") addPath := filepath.Join(dir, "new.txt") - if err := os.WriteFile(updatePath, []byte("a\nB\nc"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(deletePath, []byte("remove me"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(updatePath, []byte("a\nB\nc"), 0o644)) + testassert.NoError(t, os.WriteFile(deletePath, []byte("remove me"), 0o644)) store := &applyPatchStore{turn: 4} input := applyPatchInput(t, "*** Add File: new.txt\n+created\n*** Update File: update.txt\n@@\n a\n-B\n+B2\n c\n*** Delete File: delete.txt") - if _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}); err != nil { - t.Fatalf("Execute err = %v", err) + { + _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) + testassert.NoError(t, err) } assertSnapshotIdentityContent(t, store.identityRecords, 4, addPath, "created") @@ -279,83 +232,52 @@ func TestApplyPatchSnapshotsEveryTouchedFile(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "old.go") updated := filepath.Join(dir, "x.txt") - if err := os.WriteFile(src, []byte("a\nB\nc"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(updated, []byte("a\nB\nc"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(src, []byte("a\nB\nc"), 0o644)) + testassert.NoError(t, os.WriteFile(updated, []byte("a\nB\nc"), 0o644)) store := &applyPatchStore{turn: 7} input := applyPatchInput(t, "*** Update File: x.txt\n@@\n a\n-B\n+B2\n c\n*** Update File: old.go\n*** Move to: new.go\n@@\n a\n-B\n+B2\n c") - if _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}); err != nil { - t.Fatalf("Execute err = %v", err) + { + _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) + testassert.NoError(t, err) } // Expected snapshots: updated (Update), new.go (Move dest, Existed:false), // old.go (Move source, Existed:true). That's 3 snapshot calls under turn=7. - if len(store.calls) != 3 { - t.Fatalf("snapshot calls = %d, want 3", len(store.calls)) - } + testassert.Equal(t, len(store.calls), 3) for _, c := range store.calls { - if c.turn != 7 { - t.Fatalf("snapshot call turn = %d, want 7", c.turn) - } + testassert.Equal(t, c.turn, 7) } } func TestApplyPatchAddFailsIfFileExists(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "exists.txt") - if err := os.WriteFile(path, []byte("old"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("old"), 0o644)) store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Add File: exists.txt\n+new") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want non-nil") - } - if !strings.Contains(err.Error(), "already exists") { - t.Fatalf("err = %v, want it to mention already-exists", err) - } - if got := readFile(t, path); got != "old" { - t.Fatalf("file = %q, want unchanged %q", got, "old") - } - if len(store.calls) != 0 { - t.Fatalf("snapshot calls = %d, want 0 (refused before any snapshot capture)", len(store.calls)) - } + testassert.Require(t, err != nil, "Execute err = nil, want non-nil") + testassert.Require(t, strings.Contains(err.Error(), "already exists"), "err = %v, want it to mention already-exists", err) + testassert.Equal(t, readFile(t, path), "old") + testassert.Equal(t, len(store.calls), 0) } func TestApplyPatchMoveFailsIfDestinationExists(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "old.go") dst := filepath.Join(dir, "new.go") - if err := os.WriteFile(src, []byte("alpha\nBEFORE\nbeta"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(dst, []byte("preexisting"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(src, []byte("alpha\nBEFORE\nbeta"), 0o644)) + testassert.NoError(t, os.WriteFile(dst, []byte("preexisting"), 0o644)) store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Update File: old.go\n*** Move to: new.go\n@@\n alpha\n-BEFORE\n+AFTER\n beta") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want non-nil (move destination already exists)") - } - if !strings.Contains(err.Error(), "already exists") { - t.Fatalf("err = %v, want already-exists error", err) - } - if got := readFile(t, src); got != "alpha\nBEFORE\nbeta" { - t.Fatalf("source modified: got %q, want unchanged", got) - } - if got := readFile(t, dst); got != "preexisting" { - t.Fatalf("destination modified: got %q, want unchanged", got) - } - if len(store.calls) != 0 { - t.Fatalf("snapshot calls = %d, want 0 (refused before any snapshot capture)", len(store.calls)) - } + testassert.Require(t, err != nil, "Execute err = nil, want non-nil (move destination already exists)") + testassert.Require(t, strings.Contains(err.Error(), "already exists"), "err = %v, want already-exists error", err) + testassert.Equal(t, readFile(t, src), "alpha\nBEFORE\nbeta") + testassert.Equal(t, readFile(t, dst), "preexisting") + testassert.Equal(t, len(store.calls), 0) } func TestApplyPatchCanonicalCollisionRejectedBeforeApply(t *testing.T) { @@ -364,162 +286,106 @@ func TestApplyPatchCanonicalCollisionRejectedBeforeApply(t *testing.T) { // (raw-path dedup catches only exact duplicates); target planning // catches the canonical collision. dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hi"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hi"), 0o644)) store := &applyPatchStore{turn: 1} // "a.txt" and "./a.txt" are different raw paths but resolve to the same canonical. input := applyPatchInput(t, "*** Update File: a.txt\n@@\n-hi\n+bye\n*** Update File: ./a.txt\n@@\n-hi\n+there") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil || !strings.Contains(err.Error(), "resolve to the same file") { - t.Fatalf("Execute err = %v, want canonical collision error", err) - } - if got := readFile(t, filepath.Join(dir, "a.txt")); got != "hi" { - t.Fatalf("a.txt = %q, want unchanged (collision rejected before apply)", got) - } - if len(store.calls) != 0 { - t.Fatalf("snapshot calls = %d, want 0 (collision rejected before apply)", len(store.calls)) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "resolve to the same file"), "Execute err = %v, want canonical collision error", err) + testassert.Equal(t, readFile(t, filepath.Join(dir, "a.txt")), "hi") + testassert.Equal(t, len(store.calls), 0) } func TestApplyPatchCanonicalCollisionSymlinkRejected(t *testing.T) { dir := t.TempDir() real := filepath.Join(dir, "real.txt") link := filepath.Join(dir, "link.txt") - if err := os.WriteFile(real, []byte("content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(real, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(real, []byte("content"), 0o644)) + testassert.NoError(t, os.Symlink(real, link)) store := &applyPatchStore{turn: 1} // Two raw paths resolving to the same canonical via symlink. input := applyPatchInput(t, "*** Update File: real.txt\n@@\n content\n+changed\n*** Delete File: link.txt") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err == nil || !strings.Contains(err.Error(), "resolve to the same file") { - t.Fatalf("Execute err = %v, want canonical collision error", err) - } - if got := readFile(t, real); got != "content" { - t.Fatalf("real.txt = %q, want unchanged (collision rejected before apply)", got) - } - if len(store.calls) != 0 { - t.Fatalf("snapshot calls = %d, want 0 (collision rejected before apply)", len(store.calls)) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "resolve to the same file"), "Execute err = %v, want canonical collision error", err) + testassert.Equal(t, readFile(t, real), "content") + testassert.Equal(t, len(store.calls), 0) } func TestApplyPatchFuzzyMatchPreservesActualTrailingWhitespaceContext(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "a.go") - if err := os.WriteFile(path, []byte("alpha \nBEFORE\nbeta"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("alpha \nBEFORE\nbeta"), 0o644)) input := applyPatchInput(t, "*** Update File: a.go\n@@\n alpha\n-BEFORE\n+AFTER\n beta") - if _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}); err != nil { - t.Fatalf("Execute err = %v", err) - } - if got := readFile(t, path); got != "alpha \nAFTER\nbeta" { - t.Fatalf("file = %q, want context trailing spaces preserved", got) + { + _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}) + testassert.NoError(t, err) } + testassert.Equal(t, readFile(t, path), "alpha \nAFTER\nbeta") } func TestApplyPatchFuzzyMatchPreservesActualUnicodeContext(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "a.go") - if err := os.WriteFile(path, []byte("say \u201Chello\u201D\nBEFORE\nbeta"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("say \u201Chello\u201D\nBEFORE\nbeta"), 0o644)) input := applyPatchInput(t, "*** Update File: a.go\n@@\n say \"hello\"\n-BEFORE\n+AFTER\n beta") - if _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}); err != nil { - t.Fatalf("Execute err = %v", err) - } - if got := readFile(t, path); got != "say \u201Chello\u201D\nAFTER\nbeta" { - t.Fatalf("file = %q, want unicode context preserved", got) + { + _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}) + testassert.NoError(t, err) } + testassert.Equal(t, readFile(t, path), "say \u201Chello\u201D\nAFTER\nbeta") } func TestApplyPatchSyntheticEOFContextDoesNotCreateTrailingNewline(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "a.txt") - if err := os.WriteFile(path, []byte("foo\nbar"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("foo\nbar"), 0o644)) input := applyPatchInput(t, "*** Update File: a.txt\n@@\n foo\n-bar\n+baz\n ") res, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}) - if err != nil { - t.Fatalf("Execute err = %v", err) - } - if got := readFile(t, path); got != "foo\nbaz" { - t.Fatalf("file = %q, want no synthetic trailing newline", got) - } + testassert.NoError(t, err) + testassert.Equal(t, readFile(t, path), "foo\nbaz") rows := previewRowsFor(t, ApplyPatchPreviewMetadata(res.Previews), "a.txt") - if len(rows) != 3 { - t.Fatalf("preview rows = %#v, want context/remove/add only", rows) - } - if rows[0].Kind != editpreview.KindContext || rows[0].Text != "foo" || - rows[1].Kind != editpreview.KindRemove || rows[1].Text != "bar" || - rows[2].Kind != editpreview.KindAdd || rows[2].Text != "baz" { - t.Fatalf("preview rows = %#v, want synthetic EOF context omitted", rows) - } + testassert.Require(t, len(rows) == 3 && rows[0].Kind == editpreview.KindContext && rows[0].Text == "foo" && rows[1].Kind == editpreview.KindRemove && rows[1].Text == "bar" && rows[2].Kind == editpreview.KindAdd && rows[2].Text == "baz", "preview rows = %#v, want context/remove/add only; preview rows = %#v, want synthetic EOF context omitted", rows, rows) } func TestApplyPatchSyntheticEOFRemoveFails(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "a.txt") - if err := os.WriteFile(path, []byte("foo"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("foo"), 0o644)) input := applyPatchInput(t, "*** Update File: a.txt\n@@\n-\n+bar") _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}) - if err == nil { - t.Fatal("Execute err = nil, want synthetic EOF remove to fail") - } - if !strings.HasPrefix(err.Error(), "Failed to find expected lines in a.txt:") { - t.Fatalf("err = %v, want expected-lines failure", err) - } - if got := readFile(t, path); got != "foo" { - t.Fatalf("file = %q, want unchanged", got) - } + testassert.Require(t, err != nil, "Execute err = nil, want synthetic EOF remove to fail") + testassert.Require(t, strings.HasPrefix(err.Error(), "Failed to find expected lines in a.txt:"), "err = %v, want expected-lines failure", err) + testassert.Equal(t, readFile(t, path), "foo") } func TestApplyPatchRealEOFContextPreservesTrailingNewline(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "a.txt") - if err := os.WriteFile(path, []byte("foo\nbar\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("foo\nbar\n"), 0o644)) input := applyPatchInput(t, "*** Update File: a.txt\n@@\n foo\n-bar\n+baz\n ") res, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}) - if err != nil { - t.Fatalf("Execute err = %v", err) - } - if got := readFile(t, path); got != "foo\nbaz\n" { - t.Fatalf("file = %q, want real trailing newline preserved", got) - } + testassert.NoError(t, err) + testassert.Equal(t, readFile(t, path), "foo\nbaz\n") rows := previewRowsFor(t, ApplyPatchPreviewMetadata(res.Previews), "a.txt") - if len(rows) != 4 || rows[3].Kind != editpreview.KindContext || rows[3].Text != "" { - t.Fatalf("preview rows = %#v, want real EOF context retained", rows) - } + testassert.Require(t, len(rows) == 4 && rows[3].Kind == editpreview.KindContext && rows[3].Text == "", "preview rows = %#v, want real EOF context retained", rows) } func TestApplyPatchRealEmptyRemoveStillWorks(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "a.txt") - if err := os.WriteFile(path, []byte("foo\n\nbaz"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("foo\n\nbaz"), 0o644)) input := applyPatchInput(t, "*** Update File: a.txt\n@@\n foo\n-\n+bar\n baz") - if _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}); err != nil { - t.Fatalf("Execute err = %v", err) - } - if got := readFile(t, path); got != "foo\nbar\nbaz" { - t.Fatalf("file = %q, want real empty line replaced", got) + { + _, err := execPatch(t, dir, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"input": input}) + testassert.NoError(t, err) } + testassert.Equal(t, readFile(t, path), "foo\nbar\nbaz") } // previewRowsFor returns the captured preview rows for one path from the @@ -527,17 +393,11 @@ func TestApplyPatchRealEmptyRemoveStillWorks(t *testing.T) { func previewRowsFor(t *testing.T, md map[string]any, path string) []editpreview.Row { t.Helper() files, ok := editpreview.FilesFromMetadata(md) - if !ok { - t.Fatalf("edit_preview_files not present: %+v", md) - } - if len(files) != 1 { - t.Fatalf("preview files = %+v, want exactly one entry", files) - } + testassert.Require(t, ok, "edit_preview_files not present: %+v", md) + testassert.Require(t, len(files) == 1, "preview files = %+v, want exactly one entry", files) for _, f := range files { if f.Path == path { - if len(f.Preview.Hunks) != 1 { - t.Fatalf("preview for %s = %+v, want one hunk", path, f.Preview) - } + testassert.Require(t, len(f.Preview.Hunks) == 1, "preview for %s = %+v, want one hunk", path, f.Preview) return f.Preview.Hunks[0].Rows } } @@ -548,8 +408,6 @@ func previewRowsFor(t *testing.T, md map[string]any, path string) []editpreview. func readFile(t *testing.T, path string) string { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s: %v", path, err) - } + testassert.Require(t, err == nil, "read %s: %v", path, err) return string(data) } diff --git a/internal/tool/edit_file_test.go b/internal/tool/edit_file_test.go index 3f6a9349..929b37e0 100644 --- a/internal/tool/edit_file_test.go +++ b/internal/tool/edit_file_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // execEdit runs one edit through the exported Normalize* + Prepare* entries. @@ -39,12 +41,8 @@ func TestPreparedEditReplacesUniqueStringAndReportsLineRange(t *testing.T) { "old_string": "beta", "new_string": "delta", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Edited "+path+" (1 replacement, lines 2-2)." { - t.Fatalf("Execute result = %q, want one-replacement summary", result) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Edited "+path+" (1 replacement, lines 2-2).") assertFileContent(t, path, "alpha\ndelta\ngamma") } @@ -58,12 +56,8 @@ func TestPreparedEditReplaceAllReportsAllLineRanges(t *testing.T) { "new_string": "done", "replace_all": true, }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Edited "+path+" (2 replacements, lines 1-1, 3-3)." { - t.Fatalf("Execute result = %q, want plural summary with both line ranges", result) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Edited "+path+" (2 replacements, lines 1-1, 3-3).") assertFileContent(t, path, "done\nother\ndone") } @@ -76,9 +70,7 @@ func TestPreparedEditRejectsAmbiguousMatchWithoutReplaceAll(t *testing.T) { "old_string": "same", "new_string": "done", }) - if err == nil || !strings.Contains(err.Error(), "old_string matches 2 locations") { - t.Fatalf("Execute error = %v, want ambiguous-match error", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "old_string matches 2 locations"), "Execute error = %v, want ambiguous-match error", err) assertFileContent(t, path, "same\nsame") } @@ -104,9 +96,7 @@ func TestPreparedEditValidatesOldString(t *testing.T) { "old_string": tt.oldString, "new_string": tt.newString, }) - if err == nil || !strings.Contains(err.Error(), tt.want) { - t.Fatalf("Execute error = %v, want containing %q", err, tt.want) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), tt.want), "Execute error = %v, want containing %q", err, tt.want) assertFileContent(t, path, "content") }) } @@ -121,12 +111,8 @@ func TestPreparedEditMultilineReplacementLineRange(t *testing.T) { "old_string": "old", "new_string": "new\nline", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Edited "+path+" (1 replacement, lines 2-3)." { - t.Fatalf("Execute result = %q, want multiline replacement range", result) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Edited "+path+" (1 replacement, lines 2-3).") assertFileContent(t, path, "start\nnew\nline\nend") } @@ -140,21 +126,11 @@ func TestPreparedEditWithSnapshotSnapshotsBeforeEdit(t *testing.T) { "old_string": "before", "new_string": "after", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Edited "+path+" (1 replacement, lines 1-1)." { - t.Fatalf("Execute result = %q, want edit summary", result) - } - if len(store.calls) != 1 { - t.Fatalf("snapshot calls = %d, want 1", len(store.calls)) - } - if store.calls[0].turn != 11 || store.calls[0].path != path { - t.Fatalf("snapshot call = %+v, want turn=11 path=%q", store.calls[0], path) - } - if store.before[path] != "before" { - t.Fatalf("snapshot saw %q, want pre-edit content", store.before[path]) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Edited "+path+" (1 replacement, lines 1-1).") + testassert.Equal(t, len(store.calls), 1) + testassert.Require(t, store.calls[0].turn == 11 && store.calls[0].path == path, "snapshot call = %+v, want turn=11 path=%q", store.calls[0], path) + testassert.Require(t, store.before[path] == "before", "snapshot saw %q, want pre-edit content", store.before[path]) assertFileContent(t, path, "after") } @@ -162,13 +138,9 @@ func TestPreparedEditWithSnapshotUsesCanonicalTargetAndRequestedResult(t *testin root := t.TempDir() realDir := t.TempDir() target := filepath.Join(realDir, "file.txt") - if err := os.WriteFile(target, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("before"), 0o644)) linkDir := filepath.Join(root, "linked") - if err := os.Symlink(realDir, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realDir, linkDir)) requested := filepath.Join(linkDir, "file.txt") store := &recordingSnapshotStore{turn: 12, before: map[string]string{}} @@ -178,30 +150,16 @@ func TestPreparedEditWithSnapshotUsesCanonicalTargetAndRequestedResult(t *testin "new_string": "after", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Edited "+requested+" (1 replacement, lines 1-1)." { - t.Fatalf("Execute result = %q, want requested path in edit summary", result) - } - if len(store.calls) != 1 { - t.Fatalf("snapshot calls = %d, want 1", len(store.calls)) - } - if store.calls[0].path != requested || store.calls[0].canonical != target { - t.Fatalf("snapshot call = %+v, want original=%q canonical=%q", store.calls[0], requested, target) - } - if store.before[target] != "before" { - t.Fatalf("snapshot saw %q, want pre-edit content", store.before[target]) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Edited "+requested+" (1 replacement, lines 1-1).") + testassert.Equal(t, len(store.calls), 1) + testassert.Require(t, store.calls[0].path == requested && store.calls[0].canonical == target, "snapshot call = %+v, want original=%q canonical=%q", store.calls[0], requested, target) + testassert.Require(t, store.before[target] == "before", "snapshot saw %q, want pre-edit content", store.before[target]) assertFileContent(t, target, "after") } func TestApplyEditPureHelper(t *testing.T) { edit, err := ApplyEdit("a\nb\na", "a", "z", true, "file.txt") - if err != nil { - t.Fatalf("ApplyEdit error = %v", err) - } - if edit.UpdatedContent != "z\nb\nz" || edit.Summary != "Edited file.txt (2 replacements, lines 1-1, 3-3)." || edit.LineRanges != "1-1, 3-3" || edit.Count != 2 { - t.Fatalf("ApplyEdit result = %+v, want all replacements and line ranges", edit) - } + testassert.NoError(t, err) + testassert.Require(t, edit.UpdatedContent == "z\nb\nz" && edit.Summary == "Edited file.txt (2 replacements, lines 1-1, 3-3)." && edit.LineRanges == "1-1, 3-3" && edit.Count == 2, "ApplyEdit result = %+v, want all replacements and line ranges", edit) } diff --git a/internal/tool/file_revalidation_test.go b/internal/tool/file_revalidation_test.go index 1c785e38..e920b61d 100644 --- a/internal/tool/file_revalidation_test.go +++ b/internal/tool/file_revalidation_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" ) // The prepared call binds its canonical target at preparation; when the @@ -20,61 +21,42 @@ func TestFileToolsRefuseRepointedApprovedSymlink(t *testing.T) { safe := filepath.Join(root, "safe.txt") secret := filepath.Join(root, "secret.txt") link := filepath.Join(root, "link.txt") - if err := os.WriteFile(safe, []byte("safe"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(secret, []byte("secret"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(safe, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(safe, []byte("safe"), 0o644)) + testassert.NoError(t, os.WriteFile(secret, []byte("secret"), 0o644)) + testassert.NoError(t, os.Symlink(safe, link)) // Prepare every call while the link resolves to the safe file. readArgs, err := NormalizeReadArgs(map[string]any{"path": link}, 500) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) readCall, err := PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, readArgs) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) writeArgs, err := NormalizeWriteArgs(map[string]any{"path": link, "content": "after"}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) writeCall, err := PrepareWriteCall(root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, writeArgs) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) editArgs, err := NormalizeEditArgs(map[string]any{ "path": link, "old_string": "safe", "new_string": "after", }) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) editCall, err := PrepareEditCall(root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, editArgs) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) - if err := os.Remove(link); err != nil { - t.Fatal(err) - } - if err := os.Symlink(secret, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(link)) + testassert.NoError(t, os.Symlink(secret, link)) - if _, err := readCall.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("read_file error = %v, want canonical change refusal", err) + { + _, err := readCall.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "read_file error = %v, want canonical change refusal", err) } - if _, err := writeCall.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("write_file error = %v, want canonical change refusal", err) + { + _, err := writeCall.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "write_file error = %v, want canonical change refusal", err) } - if _, err := editCall.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("edit_file error = %v, want canonical change refusal", err) + { + _, err := editCall.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "edit_file error = %v, want canonical change refusal", err) } assertFileContent(t, safe, "safe") @@ -85,58 +67,41 @@ func TestFileToolsRefuseFinalTargetReplacedBySymlink(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "target.txt") secret := filepath.Join(root, "secret.txt") - if err := os.WriteFile(target, []byte("safe"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(secret, []byte("secret"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("safe"), 0o644)) + testassert.NoError(t, os.WriteFile(secret, []byte("secret"), 0o644)) // Prepare every call while the target is the real file. readArgs, err := NormalizeReadArgs(map[string]any{"path": target}, 500) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) readCall, err := PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, readArgs) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) writeArgs, err := NormalizeWriteArgs(map[string]any{"path": target, "content": "after"}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) writeCall, err := PrepareWriteCall(root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, writeArgs) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) editArgs, err := NormalizeEditArgs(map[string]any{ "path": target, "old_string": "safe", "new_string": "after", }) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) editCall, err := PrepareEditCall(root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, editArgs) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) - if err := os.Remove(target); err != nil { - t.Fatal(err) - } - if err := os.Symlink(secret, target); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(target)) + testassert.NoError(t, os.Symlink(secret, target)) - if _, err := readCall.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("read_file error = %v, want canonical change refusal", err) + { + _, err := readCall.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "read_file error = %v, want canonical change refusal", err) } - if _, err := writeCall.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("write_file error = %v, want canonical change refusal", err) + { + _, err := writeCall.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "write_file error = %v, want canonical change refusal", err) } - if _, err := editCall.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("edit_file error = %v, want canonical change refusal", err) + { + _, err := editCall.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "edit_file error = %v, want canonical change refusal", err) } assertFileContent(t, secret, "secret") } diff --git a/internal/tool/file_shape_test.go b/internal/tool/file_shape_test.go index a591b184..28a498a6 100644 --- a/internal/tool/file_shape_test.go +++ b/internal/tool/file_shape_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" "golang.org/x/sys/unix" ) @@ -216,9 +217,7 @@ func TestNonRegularFileToolsDoNotSnapshot(t *testing.T) { err := tc.run(store, path) release() assertNonRegularRefusal(t, path, err) - if len(store.calls) != 0 { - t.Fatalf("snapshot calls = %+v, want none", store.calls) - } + testassert.Require(t, len(store.calls) == 0, "snapshot calls = %+v, want none", store.calls) }) } } @@ -229,19 +228,11 @@ func TestMissingWriteFileStillCreatesNewFile(t *testing.T) { "path": path, "content": "created", }) - if err != nil { - t.Fatalf("WriteFile missing target error = %v", err) - } - if !strings.Contains(result, "Wrote") { - t.Fatalf("result = %q, want write success", result) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result, "Wrote"), "result = %q, want write success", result) data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if string(data) != "created" { - t.Fatalf("content = %q, want created", data) - } + testassert.NoError(t, err) + testassert.Require(t, string(data) == "created", "content = %q, want created", data) } type nonRegularSnapshotCall struct { @@ -270,26 +261,14 @@ func (s *recordingNonRegularSnapshotStore) CurrentTurn() int { func assertNonRegularRefusal(t *testing.T, path string, err error) { t.Helper() - if err == nil { - t.Fatal("operation succeeded, want non-regular target refusal") - } + testassert.Require(t, err != nil, "operation succeeded, want non-regular target refusal") msg := strings.ToLower(err.Error()) - if strings.Contains(msg, "has not been read yet") { - t.Fatalf("error = %v, want target-shape refusal before read-required checks", err) - } - if strings.Contains(msg, "did not fail closed before blocking") { - t.Fatalf("error = %v, want target-shape refusal before blocking on target I/O", err) - } - if !strings.Contains(msg, "non-regular") { - t.Fatalf("error = %v, want explicit non-regular target-shape refusal", err) - } + testassert.Require(t, !strings.Contains(msg, "has not been read yet"), "error = %v, want target-shape refusal before read-required checks", err) + testassert.Require(t, !strings.Contains(msg, "did not fail closed before blocking"), "error = %v, want target-shape refusal before blocking on target I/O", err) + testassert.Require(t, strings.Contains(msg, "non-regular"), "error = %v, want explicit non-regular target-shape refusal", err) info, statErr := os.Lstat(path) - if statErr != nil { - t.Fatalf("lstat %s = %v, want non-regular target still present", path, statErr) - } - if info.Mode().IsRegular() { - t.Fatalf("%s became a regular file after refused operation", path) - } + testassert.Require(t, statErr == nil, "lstat %s = %v, want non-regular target still present", path, statErr) + testassert.Require(t, !info.Mode().IsRegular(), "%s became a regular file after refused operation", path) } func runNonRegularTargetWithTimeout(fn func() error) error { @@ -308,9 +287,7 @@ func runNonRegularTargetWithTimeout(fn func() error) error { func nonRegularDirectoryTarget(t *testing.T) string { t.Helper() path := filepath.Join(t.TempDir(), "target-dir") - if err := os.Mkdir(path, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(path, 0o755)) return path } @@ -375,33 +352,21 @@ func TestPR11Closure_HardlinkRefusedAcrossAllOperations(t *testing.T) { project := t.TempDir() outsideDir := t.TempDir() outside = filepath.Join(outsideDir, "outside.txt") - if err := os.WriteFile(outside, []byte("outside-content"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(outside, []byte("outside-content"), 0o644)) hardlinked = filepath.Join(project, "hardlinked.txt") - if err := os.Link(outside, hardlinked); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Link(outside, hardlinked)) info, err := os.Stat(hardlinked) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) st, ok := info.Sys().(*syscall.Stat_t) - if !ok || st.Nlink < 2 { - t.Fatalf("setup: hardlinked target nlink = %d, want >= 2", st.Nlink) - } + testassert.Require(t, ok && st.Nlink >= 2, "setup: hardlinked target nlink = %d, want >= 2", st.Nlink) return hardlinked, outside } assertHardlinkRefusal := func(t *testing.T, err error) { t.Helper() - if err == nil { - t.Fatal("operation succeeded on hardlinked target; want refusal") - } + testassert.Require(t, err != nil, "operation succeeded on hardlinked target; want refusal") msg := strings.ToLower(err.Error()) - if !strings.Contains(msg, "hardlink") && !strings.Contains(msg, "link count") && !strings.Contains(msg, "nlink") { - t.Fatalf("error %v does not mention hardlink/link count/nlink", err) - } + testassert.Require(t, strings.Contains(msg, "hardlink") || strings.Contains(msg, "link count") || strings.Contains(msg, "nlink"), "error %v does not mention hardlink/link count/nlink", err) } t.Run("read_file", func(t *testing.T) { diff --git a/internal/tool/prepare_test.go b/internal/tool/prepare_test.go index 6562dd62..53b92815 100644 --- a/internal/tool/prepare_test.go +++ b/internal/tool/prepare_test.go @@ -10,121 +10,72 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestPrepareReadBindsParentDirectoryOnlyWhenLeafMissing(t *testing.T) { root := t.TempDir() existing := filepath.Join(root, "real.txt") - if err := os.WriteFile(existing, []byte("one\ntwo\nthree"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(existing, []byte("one\ntwo\nthree"), 0o644)) // Existing leaf: one file target, no parent-directory binding, so an // ordinary existing-file read requires no parent permission. args, err := NormalizeReadArgs(map[string]any{"path": "real.txt"}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } - if len(prepared.Targets) != 1 { - t.Fatalf("targets = %+v, want exactly the file target", prepared.Targets) - } - if prepared.Targets[0].CanonicalPath != existing { - t.Fatalf("canonical target = %q, want %q", prepared.Targets[0].CanonicalPath, existing) - } + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 1, "targets = %+v, want exactly the file target", prepared.Targets) + testassert.Equal(t, prepared.Targets[0].CanonicalPath, existing) // Missing leaf: the parent directory is bound as a second declared // file.read target for the authorized suggestion listing. args, err = NormalizeReadArgs(map[string]any{"path": "missing.txt"}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err = PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } - if len(prepared.Targets) != 2 { - t.Fatalf("targets = %+v, want file plus parent directory", prepared.Targets) - } - if prepared.Targets[1].CanonicalPath != root { - t.Fatalf("parent target = %+v, want directory binding on %q", prepared.Targets[1], root) - } + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 2, "targets = %+v, want file plus parent directory", prepared.Targets) + testassert.Require(t, prepared.Targets[1].CanonicalPath == root, "parent target = %+v, want directory binding on %q", prepared.Targets[1], root) } func TestPrepareReadDisappearedLeafReturnsPlainNotFound(t *testing.T) { root := t.TempDir() path := filepath.Join(root, "gone.txt") - if err := os.WriteFile(path, []byte("content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(root, "config.json"), []byte("{}"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("content"), 0o644)) + testassert.NoError(t, os.WriteFile(filepath.Join(root, "config.json"), []byte("{}"), 0o644)) args, err := NormalizeReadArgs(map[string]any{"path": "gone.txt"}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } - if len(prepared.Targets) != 1 { - t.Fatalf("targets = %+v, want the existing leaf only", prepared.Targets) - } + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 1, "targets = %+v, want the existing leaf only", prepared.Targets) // The leaf existed during preparation but disappears before opening: // plain not-found, no undeclared fallback I/O, no suggestions. - if err := os.Remove(path); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(path)) result, err := prepared.Execute(context.Background()) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "read_file: file not found: "+filepath.Join(root, "gone.txt") { - t.Fatalf("Execute result = %q, want plain not-found without suggestions", result) - } + testassert.NoError(t, err) + testassert.Require(t, result == "read_file: file not found: "+filepath.Join(root, "gone.txt"), "Execute result = %q, want plain not-found without suggestions", result) } func TestSuggestFromBoundDirectoryReadsThroughNoFollowDescriptor(t *testing.T) { root := t.TempDir() realNames := filepath.Join(root, "real-dir") - if err := os.Mkdir(realNames, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(realNames, "config.json"), []byte("{}"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(realNames, 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(realNames, "config.json"), []byte("{}"), 0o644)) otherNames := filepath.Join(root, "other-dir") - if err := os.Mkdir(otherNames, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(otherNames, "config.json"), []byte("{}"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(otherNames, 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(otherNames, "config.json"), []byte("{}"), 0o644)) parent := readTarget{displayPath: realNames, canonicalPath: realNames} got := suggestFromBoundDirectory(parent, filepath.Join(realNames, "confg.json")) - if !strings.Contains(got, "Did you mean:") || !strings.Contains(got, "config.json") { - t.Fatalf("suggestions = %q, want scored names from the bound directory", got) - } + testassert.Require(t, strings.Contains(got, "Did you mean:") && strings.Contains(got, "config.json"), "suggestions = %q, want scored names from the bound directory", got) // Replace the bound directory with a symlink pointing elsewhere: the // listing must go through the bound no-follow descriptor and fail // closed, never follow the replacement and list the redirected names. - if err := os.RemoveAll(realNames); err != nil { - t.Fatal(err) - } - if err := os.Symlink(otherNames, realNames); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.RemoveAll(realNames)) + testassert.NoError(t, os.Symlink(otherNames, realNames)) got = suggestFromBoundDirectory(parent, filepath.Join(realNames, "confg.json")) - if got != "read_file: file not found: "+filepath.Join(realNames, "confg.json") { - t.Fatalf("suggestions = %q, want plain not-found without redirected suggestions", got) - } + testassert.Require(t, got == "read_file: file not found: "+filepath.Join(realNames, "confg.json"), "suggestions = %q, want plain not-found without redirected suggestions", got) } // TestConsumedIntAtUse pins the point-of-use consumption of the canonical @@ -149,9 +100,7 @@ func TestConsumedIntAtUse(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - if got := consumedIntAtUse(tc.args, tc.key); got != tc.want { - t.Fatalf("consumedIntAtUse = %d, want %d", got, tc.want) - } + testassert.Equal(t, consumedIntAtUse(tc.args, tc.key), tc.want) }) } } @@ -252,14 +201,10 @@ func TestNormalizeReadArgsStrictIntegers(t *testing.T) { t.Run(tc.name, func(t *testing.T) { args, err := NormalizeReadArgs(tc.args, 500) if tc.wantErr != "" { - if err == nil || !strings.Contains(err.Error(), tc.wantErr) { - t.Fatalf("NormalizeReadArgs error = %v, want containing %q", err, tc.wantErr) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), tc.wantErr), "NormalizeReadArgs error = %v, want containing %q", err, tc.wantErr) return } - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) // Consumed integers normalize to their canonical-integer // json.Number lexemes — compared against the literal expected // lexeme, not through the producer: 2.0 becomes "2", 1e0 @@ -277,31 +222,33 @@ func TestNormalizeReadArgsStrictIntegers(t *testing.T) { } func TestNormalizeWriteEditArgsStrictConsumedFields(t *testing.T) { - if _, err := NormalizeWriteArgs(map[string]any{"path": "a.txt", "content": 123}); err == nil || !strings.Contains(err.Error(), "content is required") { - t.Fatalf("NormalizeWriteArgs error = %v, want content required error", err) + { + _, err := NormalizeWriteArgs(map[string]any{"path": "a.txt", "content": 123}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "content is required"), "NormalizeWriteArgs error = %v, want content required error", err) } - if _, err := NormalizeWriteArgs(map[string]any{"path": "a.txt"}); err == nil || !strings.Contains(err.Error(), "content is required") { - t.Fatalf("NormalizeWriteArgs error = %v, want content required error", err) + { + _, err := NormalizeWriteArgs(map[string]any{"path": "a.txt"}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "content is required"), "NormalizeWriteArgs error = %v, want content required error", err) } - if _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": 1, "new_string": "x"}); err == nil || !strings.Contains(err.Error(), "old_string is required") { - t.Fatalf("NormalizeEditArgs error = %v, want old_string required error", err) + { + _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": 1, "new_string": "x"}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "old_string is required"), "NormalizeEditArgs error = %v, want old_string required error", err) } - if _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": true}); err == nil || !strings.Contains(err.Error(), "new_string is required") { - t.Fatalf("NormalizeEditArgs error = %v, want new_string required error", err) + { + _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": true}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "new_string is required"), "NormalizeEditArgs error = %v, want new_string required error", err) } - if _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": "y", "replace_all": "yes"}); err == nil || !strings.Contains(err.Error(), "replace_all must be a boolean") { - t.Fatalf("NormalizeEditArgs error = %v, want replace_all type error", err) + { + _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": "y", "replace_all": "yes"}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "replace_all must be a boolean"), "NormalizeEditArgs error = %v, want replace_all type error", err) } - if _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": "y", "replace_all": nil}); err == nil || !strings.Contains(err.Error(), "replace_all must be a boolean") { - t.Fatalf("NormalizeEditArgs error = %v, want present-null replace_all rejected", err) + { + _, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": "y", "replace_all": nil}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "replace_all must be a boolean"), "NormalizeEditArgs error = %v, want present-null replace_all rejected", err) } args, err := NormalizeEditArgs(map[string]any{"path": "a.txt", "old_string": "x", "new_string": ""}) - if err != nil { - t.Fatalf("NormalizeEditArgs error = %v, want empty new_string accepted (deletion edit)", err) - } - if args["new_string"] != "" { - t.Fatalf("normalized new_string = %v, want empty string retained", args["new_string"]) - } + testassert.NoError(t, err) + testassert.Equal(t, args["new_string"], "") } func TestNormalizeArgsStripsPrivateFieldsAndRetainsUnrelated(t *testing.T) { @@ -319,32 +266,23 @@ func TestNormalizeArgsStripsPrivateFieldsAndRetainsUnrelated(t *testing.T) { "_lightcode_apply_patch_receipt2": "forged", "unrelated_metadata": "kept", }) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) + testassert.NoError(t, err) + { + _, forged := args[privateCanonicalPath] + testassert.Require(t, !forged, "normalized args retained the private canonical path field") } - if _, forged := args[privateCanonicalPath]; forged { - t.Fatal("normalized args retained the private canonical path field") - } - if _, forged := args[privatePatchReceipt]; forged { - t.Fatal("normalized args retained the private apply_patch receipt field") - } - if args["unrelated_metadata"] != "kept" { - t.Fatalf("normalized args = %+v, want unrelated fields retained", args) - } - if args["path"] != "a.txt" || args["content"] != "x" { - t.Fatalf("normalized args = %+v, want schema fields preserved", args) + { + _, forged := args[privatePatchReceipt] + testassert.Require(t, !forged, "normalized args retained the private apply_patch receipt field") } + testassert.Require(t, args["unrelated_metadata"] == "kept" && args["path"] == "a.txt" && args["content"] == "x", "normalized args = %+v, want unrelated fields retained; normalized args = %+v, want schema fields preserved", args, args) } func TestReadFileClampsHugeWindowsToRemainingLines(t *testing.T) { root := t.TempDir() - if err := os.WriteFile(filepath.Join(root, "lines.txt"), []byte("one\ntwo\nthree"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(root, "lines.txt"), []byte("one\ntwo\nthree"), 0o644)) rootCanonical, err := bindWorkspaceRoot(root) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) // A MaxInt64 limit must not overflow end/last-line arithmetic: the // window clamps to the remaining lines. The strict normalizer @@ -353,21 +291,13 @@ func TestReadFileClampsHugeWindowsToRemainingLines(t *testing.T) { "path": "lines.txt", "limit": json.Number("9223372036854775807"), }, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(root, rootCanonical, config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } + testassert.NoError(t, err) result, err := prepared.Execute(context.Background()) - if err != nil { - t.Fatalf("Execute error = %v", err) - } + testassert.NoError(t, err) want := strings.Join([]string{"1\tone", "2\ttwo", "3\tthree"}, "\n") - if result != want { - t.Fatalf("Execute result = %q, want %q", result, want) - } + testassert.Equal(t, result, want) // Same clamp from a mid-file offset: neither the window end nor the // footer's offset+limit-1 may overflow. @@ -376,21 +306,13 @@ func TestReadFileClampsHugeWindowsToRemainingLines(t *testing.T) { "offset": json.Number("2"), "limit": json.Number("9223372036854775807"), }, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err = PrepareReadCall(root, rootCanonical, config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } + testassert.NoError(t, err) result, err = prepared.Execute(context.Background()) - if err != nil { - t.Fatalf("Execute error = %v", err) - } + testassert.NoError(t, err) want = strings.Join([]string{"2\ttwo", "3\tthree", "(Showing lines 2-3 of 3.)"}, "\n") - if result != want { - t.Fatalf("Execute result = %q, want %q", result, want) - } + testassert.Equal(t, result, want) } func TestScoreFileSuggestionsConsumesSuppliedEntries(t *testing.T) { @@ -408,11 +330,10 @@ func TestScoreFileSuggestionsConsumesSuppliedEntries(t *testing.T) { } // Prefix-ratio and small-distance matches only; directories skipped; // nearest distance first. - if len(got) != 2 || names[0] != "config.json" || names[1] != "config.local.json" { - t.Fatalf("suggestions = %v, want the two config matches nearest-first", names) - } - if got := scoreFileSuggestions(entries, "main.go"); len(got) != 1 || got[0].name != "main.go" { - t.Fatalf("suggestions = %+v, want only the exact stem match", got) + testassert.Require(t, len(got) == 2 && names[0] == "config.json" && names[1] == "config.local.json", "suggestions = %v, want the two config matches nearest-first", names) + { + got := scoreFileSuggestions(entries, "main.go") + testassert.Require(t, len(got) == 1 && got[0].name == "main.go", "suggestions = %+v, want only the exact stem match", got) } } @@ -434,146 +355,98 @@ func (e fsFakeEntry) IsDir() bool { return e.mode.IsDir() } func (e fsFakeEntry) Type() os.FileMode { return e.mode.Type() } func (e fsFakeEntry) Info() (os.FileInfo, error) { return nil, os.ErrNotExist } -func TestPrepareReadRevalidatesRepointedWorkspaceRoot(t *testing.T) { +// workspaceRepointFixture builds the two real trees and the Workspace root +// symlink the repointed-root cases share. The caller keeps each operation's +// own normalization, preparation and execution. +func workspaceRepointFixture(t *testing.T, realAContent string) (realA, realB, root string) { + t.Helper() base := t.TempDir() - realA := filepath.Join(base, "realA") - realB := filepath.Join(base, "realB") + realA = filepath.Join(base, "realA") + realB = filepath.Join(base, "realB") for _, dir := range []string{realA, realB} { - if err := os.Mkdir(dir, 0o755); err != nil { - t.Fatal(err) - } - } - if err := os.WriteFile(filepath.Join(realA, "f.txt"), []byte("A-content"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(realB, "f.txt"), []byte("B-content"), 0o644); err != nil { - t.Fatal(err) - } - root := filepath.Join(base, "w") - if err := os.Symlink(realA, root); err != nil { - t.Fatal(err) + testassert.NoError(t, os.Mkdir(dir, 0o755)) } + testassert.NoError(t, os.WriteFile(filepath.Join(realA, "f.txt"), []byte(realAContent), 0o644)) + root = filepath.Join(base, "w") + testassert.NoError(t, os.Symlink(realA, root)) + return realA, realB, root +} + +// repointWorkspaceRoot points the Workspace root symlink at another tree. +func repointWorkspaceRoot(t *testing.T, root, target string) { + t.Helper() + testassert.NoError(t, os.Remove(root)) + testassert.NoError(t, os.Symlink(target, root)) +} + +func TestPrepareReadRevalidatesRepointedWorkspaceRoot(t *testing.T) { + realA, realB, root := workspaceRepointFixture(t, "A-content") + testassert.NoError(t, os.WriteFile(filepath.Join(realB, "f.txt"), []byte("B-content"), 0o644)) // The target is addressed directly under the real directory, so its // canonical path is independent of the root symlink; only the bound // Workspace root canonical can detect the repoint. args, err := NormalizeReadArgs(map[string]any{"path": filepath.Join(realA, "f.txt")}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } + testassert.NoError(t, err) // Baseline: with the binding intact the read succeeds. - if result, err := prepared.Execute(context.Background()); err != nil || !strings.Contains(result, "A-content") { - t.Fatalf("Execute = %q, %v; want A-content", result, err) + { + result, err := prepared.Execute(context.Background()) + testassert.Require(t, err == nil && strings.Contains(result, "A-content"), "Execute = %q, %v; want A-content", result, err) } // Repoint the Workspace root after preparation: the closure must refuse // the changed root binding instead of reading a substituted tree. args, err = NormalizeReadArgs(map[string]any{"path": filepath.Join(realA, "f.txt")}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err = PrepareReadCall(root, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } - if err := os.Remove(root); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realB, root); err != nil { - t.Fatal(err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "workspace root changed") { - t.Fatalf("Execute error = %v, want workspace root change refusal", err) + testassert.NoError(t, err) + repointWorkspaceRoot(t, root, realB) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "workspace root changed"), "Execute error = %v, want workspace root change refusal", err) } assertFileContent(t, filepath.Join(realA, "f.txt"), "A-content") assertFileContent(t, filepath.Join(realB, "f.txt"), "B-content") } func TestPrepareWriteRevalidatesRepointedWorkspaceRoot(t *testing.T) { - base := t.TempDir() - realA := filepath.Join(base, "realA") - realB := filepath.Join(base, "realB") - for _, dir := range []string{realA, realB} { - if err := os.Mkdir(dir, 0o755); err != nil { - t.Fatal(err) - } - } - if err := os.WriteFile(filepath.Join(realA, "f.txt"), []byte("x"), 0o644); err != nil { - t.Fatal(err) - } - root := filepath.Join(base, "w") - if err := os.Symlink(realA, root); err != nil { - t.Fatal(err) - } + realA, realB, root := workspaceRepointFixture(t, "x") store := &applyPatchStore{turn: 1} args, err := NormalizeWriteArgs(map[string]any{"path": filepath.Join(realA, "f.txt"), "content": "y"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareWriteCall(root, "", "", CapabilityOptions{}, store, args) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } - if err := os.Remove(root); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realB, root); err != nil { - t.Fatal(err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "workspace root changed") { - t.Fatalf("Execute error = %v, want workspace root change refusal", err) + testassert.NoError(t, err) + repointWorkspaceRoot(t, root, realB) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "workspace root changed"), "Execute error = %v, want workspace root change refusal", err) } // No mutation happened under either tree. assertFileContent(t, filepath.Join(realA, "f.txt"), "x") - if _, err := os.Stat(filepath.Join(realB, "f.txt")); !os.IsNotExist(err) { - t.Fatalf("realB f.txt stat err = %v, want absent (no substituted mutation)", err) + { + _, err := os.Stat(filepath.Join(realB, "f.txt")) + testassert.Require(t, os.IsNotExist(err), "realB f.txt stat err = %v, want absent (no substituted mutation)", err) } } func TestPreparePatchRevalidatesRepointedWorkspaceRoot(t *testing.T) { - base := t.TempDir() - realA := filepath.Join(base, "realA") - realB := filepath.Join(base, "realB") - for _, dir := range []string{realA, realB} { - if err := os.Mkdir(dir, 0o755); err != nil { - t.Fatal(err) - } - } - if err := os.WriteFile(filepath.Join(realA, "f.txt"), []byte("hi"), 0o644); err != nil { - t.Fatal(err) - } - root := filepath.Join(base, "w") - if err := os.Symlink(realA, root); err != nil { - t.Fatal(err) - } + realA, realB, root := workspaceRepointFixture(t, "hi") store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Update File: "+filepath.Join(realA, "f.txt")+"\n@@\n-hi\n+bye") args, err := NormalizePatchArgs(map[string]any{"input": input}) - if err != nil { - t.Fatalf("NormalizePatchArgs error = %v", err) - } + testassert.NoError(t, err) rootCanonical, err := bindWorkspaceRoot(root) - if err != nil { - t.Fatalf("bindWorkspaceRoot error = %v", err) - } + testassert.NoError(t, err) prepared, err := PreparePatchCall(root, rootCanonical, "", CapabilityOptions{}, store, args) - if err != nil { - t.Fatalf("PreparePatchCall error = %v", err) - } - if err := os.Remove(root); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realB, root); err != nil { - t.Fatal(err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "workspace root changed") { - t.Fatalf("Execute error = %v, want workspace root change refusal", err) + testassert.NoError(t, err) + repointWorkspaceRoot(t, root, realB) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "workspace root changed"), "Execute error = %v, want workspace root change refusal", err) } assertFileContent(t, filepath.Join(realA, "f.txt"), "hi") } @@ -581,49 +454,31 @@ func TestPreparePatchRevalidatesRepointedWorkspaceRoot(t *testing.T) { func TestPrepareTargetNilTrackerRepeatedReadsReturnContent(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "file.txt") - if err := os.WriteFile(path, []byte("one\ntwo"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte("one\ntwo"), 0o644)) args, err := NormalizeReadArgs(map[string]any{"path": path}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(dir, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } + testassert.NoError(t, err) for i := 0; i < 2; i++ { result, err := prepared.Execute(context.Background()) - if err != nil { - t.Fatalf("read %d error = %v", i+1, err) - } + testassert.Require(t, err == nil, "read %d error = %v", i+1, err) // Target reads never return a cached-read notice. - if !strings.Contains(result, "1\tone") || strings.Contains(result, "File unchanged") { - t.Fatalf("read %d result = %q, want full content", i+1, result) - } + testassert.Require(t, strings.Contains(result, "1\tone") && !strings.Contains(result, "File unchanged"), "read %d result = %q, want full content", i+1, result) } } func TestPrepareReadTargetsExpressParentAllowDeny(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte("{}"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), []byte("{}"), 0o644)) t.Run("parent denied denies missing-leaf suggestions", func(t *testing.T) { // Missing leaf: the parent directory is a declared target, so a // parent denial is expressible before any enumeration. args, err := NormalizeReadArgs(map[string]any{"path": "confg.json"}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(dir, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } - if len(prepared.Targets) != 2 { - t.Fatalf("targets = %+v, want file plus parent directory", prepared.Targets) - } + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 2, "targets = %+v, want file plus parent directory", prepared.Targets) // Every prepared target is checked; a parent denial must refuse // the call before execution, so no enumeration runs. allowed := true @@ -633,11 +488,10 @@ func TestPrepareReadTargetsExpressParentAllowDeny(t *testing.T) { break } } - if allowed { - t.Fatal("parent denial was not expressible through the prepared targets") - } - if _, err := prepared.Execute(context.Background()); err != nil { - t.Fatalf("closure execution itself failed = %v, want refusal to come from the permission layer", err) + testassert.Require(t, !allowed, "parent denial was not expressible through the prepared targets") + { + _, err := prepared.Execute(context.Background()) + testassert.NoError(t, err) } }) @@ -645,81 +499,56 @@ func TestPrepareReadTargetsExpressParentAllowDeny(t *testing.T) { // Existing leaf: only the file target is declared, so a parent // denial does not block an allowed existing-file read. args, err := NormalizeReadArgs(map[string]any{"path": "config.json"}, 500) - if err != nil { - t.Fatalf("NormalizeReadArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareReadCall(dir, "", config.ToolsConfig{ReadMaxLines: 500}, args) - if err != nil { - t.Fatalf("PrepareReadCall error = %v", err) - } - if len(prepared.Targets) != 1 || prepared.Targets[0].CanonicalPath != filepath.Join(dir, "config.json") { - t.Fatalf("targets = %+v, want only the file target (parent not required)", prepared.Targets) - } + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 1 && prepared.Targets[0].CanonicalPath == filepath.Join(dir, "config.json"), "targets = %+v, want only the file target (parent not required)", prepared.Targets) result, err := prepared.Execute(context.Background()) - if err != nil || !strings.Contains(result, "{}") { - t.Fatalf("Execute = %q, %v; want file content (parent permission not required)", result, err) - } + testassert.Require(t, err == nil && strings.Contains(result, "{}"), "Execute = %q, %v; want file content (parent permission not required)", result, err) }) } func TestPreparedEditAndWritePaths(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "sub", "file.txt") - if err := os.Mkdir(filepath.Join(dir, "sub"), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte("hello"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(filepath.Join(dir, "sub"), 0o755)) + testassert.NoError(t, os.WriteFile(path, []byte("hello"), 0o644)) store := &applyPatchStore{turn: 1} // Prepared write through the exported entry (no tracker, nil-tracker // target path) overwrites content. args, err := NormalizeWriteArgs(map[string]any{"path": path, "content": "changed"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareWriteCall(dir, "", "", CapabilityOptions{}, store, args) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } - if len(prepared.Targets) != 1 || prepared.Targets[0].CanonicalPath != path { - t.Fatalf("targets = %+v, want the single canonical write target", prepared.Targets) - } - if result, err := prepared.Execute(context.Background()); err != nil || result != "Wrote "+path+"." { - t.Fatalf("Execute = %q, %v; want write result", result, err) + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 1 && prepared.Targets[0].CanonicalPath == path, "targets = %+v, want the single canonical write target", prepared.Targets) + { + result, err := prepared.Execute(context.Background()) + testassert.Require(t, err == nil && result == "Wrote "+path+".", "Execute = %q, %v; want write result", result, err) } assertFileContent(t, path, "changed") // Prepared edit through the exported entry applies the replacement. editArgs, err := NormalizeEditArgs(map[string]any{"path": path, "old_string": "changed", "new_string": "final"}) - if err != nil { - t.Fatalf("NormalizeEditArgs error = %v", err) - } + testassert.NoError(t, err) editPrepared, err := PrepareEditCall(dir, "", "", CapabilityOptions{}, store, editArgs) - if err != nil { - t.Fatalf("PrepareEditCall error = %v", err) - } - if len(editPrepared.Targets) != 1 || editPrepared.Targets[0].CanonicalPath != path { - t.Fatalf("edit targets = %+v, want the single canonical edit target", editPrepared.Targets) - } - if result, err := editPrepared.Execute(context.Background()); err != nil || !strings.Contains(result, "lines 1-") { - t.Fatalf("Execute = %q, %v; want edit summary", result, err) + testassert.NoError(t, err) + testassert.Require(t, len(editPrepared.Targets) == 1 && editPrepared.Targets[0].CanonicalPath == path, "edit targets = %+v, want the single canonical edit target", editPrepared.Targets) + { + result, err := editPrepared.Execute(context.Background()) + testassert.Require(t, err == nil && strings.Contains(result, "lines 1-"), "Execute = %q, %v; want edit summary", result, err) } assertFileContent(t, path, "final") // A prepared edit whose old_string is missing fails inside the closure // without mutating. missing, err := NormalizeEditArgs(map[string]any{"path": path, "old_string": "absent", "new_string": "x"}) - if err != nil { - t.Fatalf("NormalizeEditArgs error = %v", err) - } + testassert.NoError(t, err) missingPrepared, err := PrepareEditCall(dir, "", "", CapabilityOptions{}, store, missing) - if err != nil { - t.Fatalf("PrepareEditCall error = %v", err) - } - if _, err := missingPrepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "not found") { - t.Fatalf("Execute error = %v, want missing match error", err) + testassert.NoError(t, err) + { + _, err := missingPrepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "not found"), "Execute error = %v, want missing match error", err) } assertFileContent(t, path, "final") @@ -727,15 +556,12 @@ func TestPreparedEditAndWritePaths(t *testing.T) { // parent directories as part of the single file.write target. nested := filepath.Join(dir, "created", "deep", "new.txt") nestedArgs, err := NormalizeWriteArgs(map[string]any{"path": nested, "content": "made"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) nestedPrepared, err := PrepareWriteCall(dir, "", "", CapabilityOptions{}, store, nestedArgs) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } - if _, err := nestedPrepared.Execute(context.Background()); err != nil { - t.Fatalf("Execute error = %v, want required parent creation", err) + testassert.NoError(t, err) + { + _, err := nestedPrepared.Execute(context.Background()) + testassert.NoError(t, err) } assertFileContent(t, nested, "made") } @@ -750,23 +576,15 @@ func TestPrepareWriteAndEditDiscardSnapshotClaimOnPostSnapshotRevalidationFailur real1 := filepath.Join(dir, "real1.txt") real2 := filepath.Join(dir, "real2.txt") for _, p := range []string{real1, real2} { - if err := os.WriteFile(p, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(p, []byte("before"), 0o644)) } link := filepath.Join(dir, "link.txt") repointTo := func(target string) { t.Helper() - if err := os.Remove(link); err != nil { - t.Fatal(err) - } - if err := os.Symlink(target, link); err != nil { - t.Fatal(err) - } - } - if err := os.Symlink(real1, link); err != nil { - t.Fatal(err) + testassert.NoError(t, os.Remove(link)) + testassert.NoError(t, os.Symlink(target, link)) } + testassert.NoError(t, os.Symlink(real1, link)) // Write path: the snapshot hook flips the approved symlink after // capture, exactly in the snapshot-to-mutation window. The execution @@ -779,19 +597,14 @@ func TestPrepareWriteAndEditDiscardSnapshotClaimOnPostSnapshotRevalidationFailur } }} args, err := NormalizeWriteArgs(map[string]any{"path": link, "content": "after"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareWriteCall(dir, "", "", CapabilityOptions{}, store, args) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("write Execute error = %v, want post-snapshot canonical change refusal", err) - } - if len(store.discards) != 1 || store.discards[0] != real1 { - t.Fatalf("write discards = %v, want the unmutated snapshot claim for %s discarded once", store.discards, real1) + testassert.NoError(t, err) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "write Execute error = %v, want post-snapshot canonical change refusal", err) } + testassert.Require(t, len(store.discards) == 1 && store.discards[0] == real1, "write discards = %v, want the unmutated snapshot claim for %s discarded once", store.discards, real1) assertFileContent(t, real1, "before") assertFileContent(t, real2, "before") @@ -803,55 +616,38 @@ func TestPrepareWriteAndEditDiscardSnapshotClaimOnPostSnapshotRevalidationFailur } }} editArgs, err := NormalizeEditArgs(map[string]any{"path": link, "old_string": "before", "new_string": "after"}) - if err != nil { - t.Fatalf("NormalizeEditArgs error = %v", err) - } + testassert.NoError(t, err) editPrepared, err := PrepareEditCall(dir, "", "", CapabilityOptions{}, store2, editArgs) - if err != nil { - t.Fatalf("PrepareEditCall error = %v", err) - } - if _, err := editPrepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("edit Execute error = %v, want post-snapshot canonical change refusal", err) - } - if len(store2.discards) != 1 || store2.discards[0] != real1 { - t.Fatalf("edit discards = %v, want the unmutated snapshot claim for %s discarded once", store2.discards, real1) + testassert.NoError(t, err) + { + _, err := editPrepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "edit Execute error = %v, want post-snapshot canonical change refusal", err) } + testassert.Require(t, len(store2.discards) == 1 && store2.discards[0] == real1, "edit discards = %v, want the unmutated snapshot claim for %s discarded once", store2.discards, real1) assertFileContent(t, real1, "before") assertFileContent(t, real2, "before") } func TestPreparePatchExecutesParsedPatchWithoutReparse(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hi"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "a.txt"), []byte("hi"), 0o644)) store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Update File: a.txt\n@@\n-hi\n+bye") args := map[string]any{"input": input} prepared, err := PreparePatchCall(dir, "", "", CapabilityOptions{}, store, args) - if err != nil { - t.Fatalf("PreparePatchCall error = %v", err) - } - if len(prepared.Targets) != 1 || prepared.Targets[0].CanonicalPath != filepath.Join(dir, "a.txt") { - t.Fatalf("targets = %+v, want the single canonical patch target", prepared.Targets) - } + testassert.NoError(t, err) + testassert.Require(t, len(prepared.Targets) == 1 && prepared.Targets[0].CanonicalPath == filepath.Join(dir, "a.txt"), "targets = %+v, want the single canonical patch target", prepared.Targets) // Swapping the arguments after preparation must not leak into the // execution closure: the patch was parsed exactly once and the closure // consumes that parsed plan without redecoding or reparsing. args["input"] = applyPatchInput(t, "*** Update File: a.txt\n@@\n-hi\n+TAMPERED") result, err := prepared.Execute(context.Background()) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if !strings.Contains(result.Result, "Success. Updated the following files:") { - t.Fatalf("Execute result = %q, want success summary", result.Result) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result.Result, "Success. Updated the following files:"), "Execute result = %q, want success summary", result.Result) // The structured result carries the captured preview data. - if len(result.Previews) != 1 || result.Previews[0].Op != "M" || result.Previews[0].Path != "a.txt" { - t.Fatalf("previews = %+v, want the applied M preview for a.txt", result.Previews) - } + testassert.Require(t, len(result.Previews) == 1 && result.Previews[0].Op == "M" && result.Previews[0].Path == "a.txt", "previews = %+v, want the applied M preview for a.txt", result.Previews) assertFileContent(t, filepath.Join(dir, "a.txt"), "bye") } @@ -859,33 +655,22 @@ func TestPreparePatchClosureRevalidatesRepointedTarget(t *testing.T) { dir := t.TempDir() real1 := filepath.Join(dir, "real1.txt") real2 := filepath.Join(dir, "real2.txt") - if err := os.WriteFile(real1, []byte("hi"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(real2, []byte("hi"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(real1, []byte("hi"), 0o644)) + testassert.NoError(t, os.WriteFile(real2, []byte("hi"), 0o644)) link := filepath.Join(dir, "link.txt") - if err := os.Symlink(real1, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(real1, link)) store := &applyPatchStore{turn: 1} input := applyPatchInput(t, "*** Update File: link.txt\n@@\n-hi\n+bye") prepared, err := PreparePatchCall(dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err != nil { - t.Fatalf("PreparePatchCall error = %v", err) - } + testassert.NoError(t, err) // Repoint the approved leaf after preparation: the closure must refuse // the changed canonical target instead of silently substituting it. - if err := os.Remove(link); err != nil { - t.Fatal(err) - } - if err := os.Symlink(real2, link); err != nil { - t.Fatal(err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical path changed") { - t.Fatalf("Execute error = %v, want canonical change refusal", err) + testassert.NoError(t, os.Remove(link)) + testassert.NoError(t, os.Symlink(real2, link)) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical path changed"), "Execute error = %v, want canonical change refusal", err) } assertFileContent(t, real1, "hi") assertFileContent(t, real2, "hi") @@ -895,36 +680,24 @@ func TestPreparePatchPartialFailureReportsCommittedFiles(t *testing.T) { dir := t.TempDir() first := filepath.Join(dir, "first.txt") second := filepath.Join(dir, "second.txt") - if err := os.WriteFile(first, []byte("one"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(second, []byte("two"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(first, []byte("one"), 0o644)) + testassert.NoError(t, os.WriteFile(second, []byte("two"), 0o644)) store := &applyPatchStore{turn: 1} // After the first op commits its snapshot, tamper with the second op's // file: the per-op current-content check must refuse it and the partial // summary must report the committed first file. store.onSnapshot = func(call int) { if call == 1 { - if err := os.WriteFile(second, []byte("tampered"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(second, []byte("tampered"), 0o644)) } } input := applyPatchInput(t, "*** Update File: first.txt\n@@\n-one\n+ONE\n*** Update File: second.txt\n@@\n-two\n+TWO") prepared, err := PreparePatchCall(dir, "", "", CapabilityOptions{}, store, map[string]any{"input": input}) - if err != nil { - t.Fatalf("PreparePatchCall error = %v", err) - } + testassert.NoError(t, err) _, err = prepared.Execute(context.Background()) - if err == nil { - t.Fatal("Execute succeeded, want partial-failure error") - } - if !strings.Contains(err.Error(), "M first.txt") || !strings.Contains(err.Error(), "changed after validation") { - t.Fatalf("Execute error = %v, want committed-files summary plus validation failure", err) - } + testassert.Require(t, err != nil, "Execute succeeded, want partial-failure error") + testassert.Require(t, strings.Contains(err.Error(), "M first.txt") && strings.Contains(err.Error(), "changed after validation"), "Execute error = %v, want committed-files summary plus validation failure", err) assertFileContent(t, first, "ONE") assertFileContent(t, second, "tampered") } @@ -933,31 +706,22 @@ func TestPrepareWriteRevalidatesWriteDirWitness(t *testing.T) { root := t.TempDir() realA := filepath.Join(root, "realA") realB := filepath.Join(root, "realB") - if err := os.Mkdir(realA, 0o755); err != nil { - t.Fatal(err) - } - if err := os.Mkdir(realB, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(realA, 0o755)) + testassert.NoError(t, os.Mkdir(realB, 0o755)) wd := filepath.Join(root, "wd") - if err := os.Symlink(realA, wd); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realA, wd)) store := &applyPatchStore{turn: 1} // The target is addressed directly through the real directory while the // write-dir witness goes through the symlink; the target canonical path // is inside the witness boundary at preparation. args, err := NormalizeWriteArgs(map[string]any{"path": filepath.Join(realA, "file.txt"), "content": "x"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PrepareWriteCall(root, "", "", CapabilityOptions{WriteDir: wd}, store, args) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } - if _, err := prepared.Execute(context.Background()); err != nil { - t.Fatalf("Execute error = %v", err) + testassert.NoError(t, err) + { + _, err := prepared.Execute(context.Background()) + testassert.NoError(t, err) } assertFileContent(t, filepath.Join(realA, "file.txt"), "x") @@ -966,21 +730,14 @@ func TestPrepareWriteRevalidatesWriteDirWitness(t *testing.T) { // target canonical path is unchanged, but the closure must refuse it: // the bound canonical write-dir boundary changed. args, err = NormalizeWriteArgs(map[string]any{"path": filepath.Join(realA, "file.txt"), "content": "y"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err = PrepareWriteCall(root, "", "", CapabilityOptions{WriteDir: wd}, store, args) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } - if err := os.Remove(wd); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realB, wd); err != nil { - t.Fatal(err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical write_dir changed") { - t.Fatalf("Execute error = %v, want write-dir revalidation refusal", err) + testassert.NoError(t, err) + testassert.NoError(t, os.Remove(wd)) + testassert.NoError(t, os.Symlink(realB, wd)) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical write_dir changed"), "Execute error = %v, want write-dir revalidation refusal", err) } assertFileContent(t, filepath.Join(realA, "file.txt"), "x") @@ -988,62 +745,47 @@ func TestPrepareWriteRevalidatesWriteDirWitness(t *testing.T) { // inside both the old and the new directory: the canonical boundary // comparison catches it where a fresh containment check would pass. // Restore the witness first so preparation binds the old boundary. - if err := os.Remove(wd); err != nil { - t.Fatal(err) - } - if err := os.Symlink(realA, wd); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(wd)) + testassert.NoError(t, os.Symlink(realA, wd)) nested := filepath.Join(realA, "B") - if err := os.Mkdir(nested, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(nested, 0o755)) args, err = NormalizeWriteArgs(map[string]any{"path": filepath.Join(nested, "f.txt"), "content": "n"}) - if err != nil { - t.Fatalf("NormalizeWriteArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err = PrepareWriteCall(root, "", "", CapabilityOptions{WriteDir: wd}, store, args) - if err != nil { - t.Fatalf("PrepareWriteCall error = %v", err) - } + testassert.NoError(t, err) // wd still points at realA during preparation: boundary realA, target // realA/B/f.txt inside it. Flip wd to the nested realA/B — the target // stays inside both boundaries. - if err := os.Remove(wd); err != nil { - t.Fatal(err) - } - if err := os.Symlink(nested, wd); err != nil { - t.Fatal(err) - } - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical write_dir changed") { - t.Fatalf("Execute error = %v, want write-dir canonical change refusal (target inside both boundaries)", err) + testassert.NoError(t, os.Remove(wd)) + testassert.NoError(t, os.Symlink(nested, wd)) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical write_dir changed"), "Execute error = %v, want write-dir canonical change refusal (target inside both boundaries)", err) } - if _, err := os.Stat(filepath.Join(nested, "f.txt")); !os.IsNotExist(err) { - t.Fatalf("nested f.txt stat err = %v, want absent (no mutation)", err) + { + _, err := os.Stat(filepath.Join(nested, "f.txt")) + testassert.Require(t, os.IsNotExist(err), "nested f.txt stat err = %v, want absent (no mutation)", err) } } func TestNormalizePatchArgsStrictInputType(t *testing.T) { - if _, err := NormalizePatchArgs(map[string]any{"input": 42}); err == nil || !strings.Contains(err.Error(), "apply_patch: input must be a string") { - t.Fatalf("NormalizePatchArgs error = %v, want input type error", err) + { + _, err := NormalizePatchArgs(map[string]any{"input": 42}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "apply_patch: input must be a string"), "NormalizePatchArgs error = %v, want input type error", err) } - if _, err := NormalizePatchArgs(map[string]any{"input": nil}); err == nil || !strings.Contains(err.Error(), "apply_patch: input must be a string") { - t.Fatalf("NormalizePatchArgs error = %v, want present-null input rejected", err) + { + _, err := NormalizePatchArgs(map[string]any{"input": nil}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "apply_patch: input must be a string"), "NormalizePatchArgs error = %v, want present-null input rejected", err) } args, err := NormalizePatchArgs(map[string]any{"input": "*** Begin Patch\n*** End Patch", "unrelated": "kept"}) - if err != nil { - t.Fatalf("NormalizePatchArgs error = %v", err) - } - if args["unrelated"] != "kept" { - t.Fatalf("normalized args = %+v, want unrelated fields retained", args) - } + testassert.NoError(t, err) + testassert.Require(t, args["unrelated"] == "kept", "normalized args = %+v, want unrelated fields retained", args) // Missing input stays a later patch-syntax error, not a normalization one. args, err = NormalizePatchArgs(map[string]any{}) - if err != nil { - t.Fatalf("NormalizePatchArgs error = %v, want missing input tolerated at normalization", err) - } - if _, err := PreparePatchCall(t.TempDir(), "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, args); err == nil || !strings.Contains(err.Error(), "input is empty") { - t.Fatalf("PreparePatchCall error = %v, want patch-syntax error", err) + testassert.NoError(t, err) + { + _, err := PreparePatchCall(t.TempDir(), "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, args) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "input is empty"), "PreparePatchCall error = %v, want patch-syntax error", err) } } @@ -1064,28 +806,18 @@ func newPatchWriteDirWitnessFixture(t *testing.T) patchWriteDirWitnessFixture { root := t.TempDir() realA := filepath.Join(root, "realA") nested := filepath.Join(realA, "B") - if err := os.MkdirAll(nested, 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(nested, 0o755)) target := filepath.Join(nested, "a.txt") - if err := os.WriteFile(target, []byte("x"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("x"), 0o644)) wd := filepath.Join(root, "wd") - if err := os.Symlink(realA, wd); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realA, wd)) return patchWriteDirWitnessFixture{ root: root, target: target, wd: wd, flip: func() { - if err := os.Remove(wd); err != nil { - t.Fatal(err) - } - if err := os.Symlink(nested, wd); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(wd)) + testassert.NoError(t, os.Symlink(nested, wd)) }, } } @@ -1105,20 +837,15 @@ func TestPreparePatchRevalidatesWriteDirWitness(t *testing.T) { f := newPatchWriteDirWitnessFixture(t) store := &applyPatchStore{turn: 1} rootCanonical, err := bindWorkspaceRoot(f.root) - if err != nil { - t.Fatalf("bindWorkspaceRoot error = %v", err) - } + testassert.NoError(t, err) writeDirCanonical, err := bindWriteDir(f.root, "apply_patch", f.wd) - if err != nil { - t.Fatalf("bindWriteDir error = %v", err) - } + testassert.NoError(t, err) prepared, err := PreparePatchCall(f.root, rootCanonical, writeDirCanonical, CapabilityOptions{WriteDir: f.wd}, store, map[string]any{"input": patchWriteDirWitnessInput(t, f.target)}) - if err != nil { - t.Fatalf("PreparePatchCall error = %v", err) - } + testassert.NoError(t, err) f.flip() - if _, err := prepared.Execute(context.Background()); err == nil || !strings.Contains(err.Error(), "approved canonical write_dir changed") { - t.Fatalf("Execute error = %v, want write-dir canonical change refusal", err) + { + _, err := prepared.Execute(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "approved canonical write_dir changed"), "Execute error = %v, want write-dir canonical change refusal", err) } assertFileContent(t, f.target, "x") }) @@ -1128,68 +855,47 @@ func TestPreparePatchRevalidatesWriteDirWitness(t *testing.T) { func TestPreparePatchRejectsOutsideWriteDirAtPreparation(t *testing.T) { root := t.TempDir() inside := filepath.Join(root, "in") - if err := os.Mkdir(inside, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(inside, "a.txt"), []byte("x"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(inside, 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(inside, "a.txt"), []byte("x"), 0o644)) rootCanonical, err := bindWorkspaceRoot(root) - if err != nil { - t.Fatalf("bindWorkspaceRoot error = %v", err) - } + testassert.NoError(t, err) writeDirCanonical, err := bindWriteDir(root, "apply_patch", inside) - if err != nil { - t.Fatalf("bindWriteDir error = %v", err) - } + testassert.NoError(t, err) outside := filepath.Join(root, "out") - if err := os.Mkdir(outside, 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(outside, "b.txt"), []byte("y"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Mkdir(outside, 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(outside, "b.txt"), []byte("y"), 0o644)) // A patch destination outside write_dir is rejected at preparation, // before authorization: no targets are declared and nothing runs. input := applyPatchInput(t, "*** Add File: "+filepath.Join(outside, "new.txt")+"\n+data") args, err := NormalizePatchArgs(map[string]any{"input": input}) - if err != nil { - t.Fatalf("NormalizePatchArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err := PreparePatchCall(root, rootCanonical, writeDirCanonical, CapabilityOptions{WriteDir: inside}, &applyPatchStore{turn: 1}, args) - if err == nil || !strings.Contains(err.Error(), "outside write_dir") { - t.Fatalf("PreparePatchCall error = %v, want outside-write_dir rejection at preparation", err) - } - if prepared != nil { - t.Fatalf("prepared call = %+v, want nil after preparation rejection", prepared) - } - if _, err := os.Stat(filepath.Join(outside, "new.txt")); !os.IsNotExist(err) { - t.Fatalf("outside new.txt stat err = %v, want absent", err) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "outside write_dir"), "PreparePatchCall error = %v, want outside-write_dir rejection at preparation", err) + testassert.Require(t, prepared == nil, "prepared call = %+v, want nil after preparation rejection", prepared) + { + _, err := os.Stat(filepath.Join(outside, "new.txt")) + testassert.Require(t, os.IsNotExist(err), "outside new.txt stat err = %v, want absent", err) } // A source outside write_dir is likewise rejected at preparation. input = applyPatchInput(t, "*** Update File: "+filepath.Join(outside, "b.txt")+"\n@@\n-y\n+z") args, err = NormalizePatchArgs(map[string]any{"input": input}) - if err != nil { - t.Fatalf("NormalizePatchArgs error = %v", err) - } - if _, err := PreparePatchCall(root, rootCanonical, writeDirCanonical, CapabilityOptions{WriteDir: inside}, &applyPatchStore{turn: 1}, args); err == nil || !strings.Contains(err.Error(), "outside write_dir") { - t.Fatalf("PreparePatchCall error = %v, want outside-write_dir source rejection at preparation", err) + testassert.NoError(t, err) + { + _, err := PreparePatchCall(root, rootCanonical, writeDirCanonical, CapabilityOptions{WriteDir: inside}, &applyPatchStore{turn: 1}, args) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "outside write_dir"), "PreparePatchCall error = %v, want outside-write_dir source rejection at preparation", err) } // An inside patch prepares and applies normally. input = applyPatchInput(t, "*** Update File: "+filepath.Join(inside, "a.txt")+"\n@@\n-x\n+fixed") args, err = NormalizePatchArgs(map[string]any{"input": input}) - if err != nil { - t.Fatalf("NormalizePatchArgs error = %v", err) - } + testassert.NoError(t, err) prepared, err = PreparePatchCall(root, rootCanonical, writeDirCanonical, CapabilityOptions{WriteDir: inside}, &applyPatchStore{turn: 1}, args) - if err != nil { - t.Fatalf("PreparePatchCall error = %v, want inside-write_dir patch prepared", err) - } - if _, err := prepared.Execute(context.Background()); err != nil { - t.Fatalf("Execute error = %v", err) + testassert.NoError(t, err) + { + _, err := prepared.Execute(context.Background()) + testassert.NoError(t, err) } assertFileContent(t, filepath.Join(inside, "a.txt"), "fixed") } diff --git a/internal/tool/read_file_test.go b/internal/tool/read_file_test.go index cd58d006..b81843c1 100644 --- a/internal/tool/read_file_test.go +++ b/internal/tool/read_file_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" ) func readFileTestFile(t *testing.T, name, content string) string { @@ -19,9 +20,7 @@ func readFileTestFile(t *testing.T, name, content string) string { func readFileTestFileBytes(t *testing.T, name string, data []byte) string { t.Helper() path := filepath.Join(t.TempDir(), name) - if err := os.WriteFile(path, data, 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, data, 0o644)) return path } @@ -56,18 +55,14 @@ func TestPreparedReadLineRangesAndFooter(t *testing.T) { "offset": json.Number("2"), "limit": json.Number("2"), }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } + testassert.NoError(t, err) want := strings.Join([]string{ "2\ttwo", "3\tthree", "(Showing lines 2-3 of 4.)", }, "\n") - if result != want { - t.Fatalf("read_file output = %q, want %q", result, want) - } + testassert.Equal(t, result, want) } func TestPreparedReadDefaultLimitAndFooter(t *testing.T) { @@ -79,18 +74,14 @@ func TestPreparedReadDefaultLimitAndFooter(t *testing.T) { "offset": json.Number("-10"), "limit": json.Number("0"), }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } + testassert.NoError(t, err) want := strings.Join([]string{ "1\tone", "2\ttwo", "(Showing lines 1-2 of 3. Use offset=3 to continue.)", }, "\n") - if result != want { - t.Fatalf("read_file output = %q, want %q", result, want) - } + testassert.Equal(t, result, want) } func TestPreparedReadRejectsBinaryFiles(t *testing.T) { @@ -108,9 +99,7 @@ func TestPreparedReadRejectsBinaryFiles(t *testing.T) { root := filepath.Dir(path) _, err := execRead(t, root, "", config.ToolsConfig{ReadMaxLines: 500}, map[string]any{"path": path}) - if err == nil || !strings.Contains(err.Error(), "appears to be a binary file") { - t.Fatalf("Execute error = %v, want binary-file error", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "appears to be a binary file"), "Execute error = %v, want binary-file error", err) }) } } @@ -120,12 +109,8 @@ func TestPreparedReadToleratesInvalidUTF8Text(t *testing.T) { root := filepath.Dir(path) result, err := execRead(t, root, "", config.ToolsConfig{ReadMaxLines: 500}, map[string]any{"path": path}) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if !strings.Contains(result, "1\tok") || !strings.Contains(result, "2\tnext") { - t.Fatalf("Execute result = %q, want line-numbered fallback text", result) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result, "1\tok") && strings.Contains(result, "2\tnext"), "Execute result = %q, want line-numbered fallback text", result) } func TestPreparedReadTruncatesLongLinesAndOutputBytes(t *testing.T) { @@ -137,13 +122,6 @@ func TestPreparedReadTruncatesLongLinesAndOutputBytes(t *testing.T) { ReadLineMaxChars: 3, MaxOutputBytes: 24, }, map[string]any{"path": path}) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if !strings.Contains(result, "1\tabc... [truncated]") { - t.Fatalf("Execute result = %q, want per-line truncation", result) - } - if !strings.Contains(result, "Output truncated at 24 bytes") { - t.Fatalf("Execute result = %q, want byte-cap truncation footer", result) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result, "1\tabc... [truncated]") && strings.Contains(result, "Output truncated at 24 bytes"), "Execute result = %q, want per-line truncation; Execute result = %q, want byte-cap truncation footer", result, result) } diff --git a/internal/tool/run_command_ro_test.go b/internal/tool/run_command_ro_test.go index 3c330993..8ba62d3f 100644 --- a/internal/tool/run_command_ro_test.go +++ b/internal/tool/run_command_ro_test.go @@ -3,6 +3,8 @@ package tool import ( "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestReadOnlyRunCommandAllowsWhitelistedCommands(t *testing.T) { @@ -34,8 +36,9 @@ func TestReadOnlyRunCommandAllowsWhitelistedCommands(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if _, err := ReadOnlyCommand(tt.command); err != nil { - t.Fatalf("ReadOnlyCommand(%q) error = %v, want accepted", tt.command, err) + { + _, err := ReadOnlyCommand(tt.command) + testassert.Require(t, err == nil, "ReadOnlyCommand(%q) error = %v, want accepted", tt.command, err) } }) } @@ -110,8 +113,9 @@ func TestReadOnlyRunCommandRejectsUnsafeCommands(t *testing.T) { for _, command := range tests { t.Run(command, func(t *testing.T) { - if _, err := ReadOnlyCommand(command); err == nil { - t.Fatalf("ReadOnlyCommand(%q) accepted, want read-only rejection", command) + { + _, err := ReadOnlyCommand(command) + testassert.Require(t, err != nil, "ReadOnlyCommand(%q) accepted, want read-only rejection", command) } }) } @@ -167,8 +171,9 @@ func TestIsReadOnlyCommandClassifiesChainsBeforePrefixAllow(t *testing.T) { for _, tt := range tests { t.Run(tt.command, func(t *testing.T) { - if got := isReadOnlyCommand(tt.command); got != tt.want { - t.Fatalf("isReadOnlyCommand(%q) = %v, want %v", tt.command, got, tt.want) + { + got := isReadOnlyCommand(tt.command) + testassert.Require(t, got == tt.want, "isReadOnlyCommand(%q) = %v, want %v", tt.command, got, tt.want) } }) } @@ -182,14 +187,10 @@ func TestReadOnlyRunCommandRejectsAmbiguousGitConfigFlags(t *testing.T) { "git show --config-env core.pager=HOME HEAD", } { t.Run(command, func(t *testing.T) { - if isReadOnlyCommand(command) { - t.Fatalf("isReadOnlyCommand(%q) = true, want false", command) - } + testassert.Require(t, !isReadOnlyCommand(command), "isReadOnlyCommand(%q) = true, want false", command) }) } - if !isReadOnlyCommand("git diff -- -c") { - t.Fatal("isReadOnlyCommand(\"git diff -- -c\") = false, want true for literal path after --") - } + testassert.Require(t, isReadOnlyCommand("git diff -- -c"), "isReadOnlyCommand(\"git diff -- -c\") = false, want true for literal path after --") } func TestPR11Closure_GitSubcommandLocalDashCDeliberatelyBlocked(t *testing.T) { @@ -199,14 +200,10 @@ func TestPR11Closure_GitSubcommandLocalDashCDeliberatelyBlocked(t *testing.T) { "git show -c HEAD", } { t.Run(command, func(t *testing.T) { - if isReadOnlyCommand(command) { - t.Fatalf("isReadOnlyCommand(%q) = true, want false (subcommand-local -c is deliberately blocked)", command) - } + testassert.Require(t, !isReadOnlyCommand(command), "isReadOnlyCommand(%q) = true, want false (subcommand-local -c is deliberately blocked)", command) }) } - if !isReadOnlyCommand("git diff -- -c") { - t.Fatal("isReadOnlyCommand(\"git diff -- -c\") = false, want true (literal -- escape must still work)") - } + testassert.Require(t, isReadOnlyCommand("git diff -- -c"), "isReadOnlyCommand(\"git diff -- -c\") = false, want true (literal -- escape must still work)") } func TestPR11Closure_ReadOnlyRejectsHeredocAndHereString(t *testing.T) { @@ -216,9 +213,7 @@ func TestPR11Closure_ReadOnlyRejectsHeredocAndHereString(t *testing.T) { `grep pattern <<<"$var"`, } { t.Run(command, func(t *testing.T) { - if isReadOnlyCommand(command) { - t.Fatalf("isReadOnlyCommand(%q) = true, want false (heredoc/here-string redirection must be rejected)", command) - } + testassert.Require(t, !isReadOnlyCommand(command), "isReadOnlyCommand(%q) = true, want false (heredoc/here-string redirection must be rejected)", command) }) } } @@ -229,17 +224,12 @@ func TestPR11Closure_ReadOnlyRejectsHeredocAndHereString(t *testing.T) { // is one the allowlist rejects. func TestReadOnlyRunCommandDescriptionMatchesEnforcement(t *testing.T) { for _, command := range []string{"git branch --list", "git tag -l 'v*'"} { - if _, err := ReadOnlyCommand(command); err != nil { - t.Errorf("ReadOnlyCommand(%q) error = %v, want the advertised listing mode accepted", command, err) + { + _, err := ReadOnlyCommand(command) + testassert.Check(t, err == nil, "ReadOnlyCommand(%q) error = %v, want the advertised listing mode accepted", command, err) } } - if !strings.Contains(ReadOnlyRunCommandDescription, "branch/tag listing") { - t.Error("read-only description does not advertise the branch/tag listing modes the allowlist accepts") - } - if strings.Contains(ReadOnlyRunCommandDescription, "cd ") { - t.Error("read-only description suggests a cd command the allowlist rejects") - } - if isReadOnlyCommand("cd /tmp && pwd") { - t.Error("isReadOnlyCommand accepted a cd chain, so the description's removed suggestion was not unsupported") - } + testassert.Check(t, strings.Contains(ReadOnlyRunCommandDescription, "branch/tag listing"), "read-only description does not advertise the branch/tag listing modes the allowlist accepts") + testassert.Check(t, !strings.Contains(ReadOnlyRunCommandDescription, "cd "), "read-only description suggests a cd command the allowlist rejects") + testassert.Check(t, !isReadOnlyCommand("cd /tmp && pwd"), "isReadOnlyCommand accepted a cd chain, so the description's removed suggestion was not unsupported") } diff --git a/internal/tool/run_command_test.go b/internal/tool/run_command_test.go index df41820d..0c430a6e 100644 --- a/internal/tool/run_command_test.go +++ b/internal/tool/run_command_test.go @@ -12,6 +12,8 @@ import ( "strings" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestRunForegroundCommandTimeoutSendsTERMBeforeKILL pins the timeout @@ -22,20 +24,14 @@ func TestRunForegroundCommandTimeoutSendsTERMBeforeKILL(t *testing.T) { output, err := RunForegroundCommand(context.Background(), "trap 'echo got-term; exit 0' TERM; sleep 10", dir, 1, 0, 0, filepath.Join(dir, ".lightcode"), os.Environ()) var exitErr *ExitError - if !errors.As(err, &exitErr) { - t.Fatalf("error = %T %v, want *ExitError", err, err) - } - if !strings.Contains(output, "got-term") || !strings.Contains(exitErr.Output, "got-term") { - t.Fatalf("output=%q exitErr.Output=%q, want SIGTERM trap output before timeout error", output, exitErr.Output) - } + testassert.Require(t, errors.As(err, &exitErr), "error = %T %v, want *ExitError", err, err) + testassert.Require(t, strings.Contains(output, "got-term") && strings.Contains(exitErr.Output, "got-term"), "output=%q exitErr.Output=%q, want SIGTERM trap output before timeout error", output, exitErr.Output) } func TestRunCommandTerminateProcessUsesExistingWaitChannel(t *testing.T) { cmd := exec.Command("sh", "-c", "trap '' TERM; sleep 10") cmd.SysProcAttr = childProcAttr() - if err := cmd.Start(); err != nil { - t.Fatalf("Start error = %v", err) - } + testassert.NoError(t, cmd.Start()) done := make(chan error, 1) waitDone := make(chan struct{}) go func() { @@ -89,9 +85,7 @@ func TestRunForegroundCommandCancellationWhileRunningIsDeterministic(t *testing. select { case err := <-errCh: var exitErr *ExitError - if !errors.As(err, &exitErr) || exitErr.ExitCode != -1 || exitErr.Output != "command cancelled" { - t.Fatalf("err = %v, want the exact cancellation ExitError", err) - } + testassert.Require(t, errors.As(err, &exitErr) && exitErr.ExitCode == -1 && exitErr.Output == "command cancelled", "err = %v, want the exact cancellation ExitError", err) case <-time.After(5 * time.Second): t.Fatal("cancelled command did not return") } @@ -114,9 +108,7 @@ func TestRunForegroundCommandCompletedBeatsLateEvents(t *testing.T) { defer func() { waitCommand = origWait }() result, err := RunForegroundCommand(context.Background(), "printf ok", dir, 1, 0, 0, filepath.Join(dir, ".lightcode"), os.Environ()) - if err != nil || result != "ok" { - t.Fatalf("result = (%q, %v), want the already-finished real result", result, err) - } + testassert.Require(t, err == nil && result == "ok", "result = (%q, %v), want the already-finished real result", result, err) }) t.Run("late cancellation", func(t *testing.T) { @@ -142,8 +134,9 @@ func TestRunForegroundCommandCompletedBeatsLateEvents(t *testing.T) { cancel() select { case result := <-resCh: - if err := <-errCh; err != nil || result != "ok" { - t.Fatalf("result = (%q, %v), want the already-finished real result", result, err) + { + err := <-errCh + testassert.Require(t, err == nil && result == "ok", "result = (%q, %v), want the already-finished real result", result, err) } case <-time.After(5 * time.Second): t.Fatal("completed command did not return") @@ -158,11 +151,10 @@ func TestRunForegroundCommandTimeoutOverflowRejectedBeforeLaunch(t *testing.T) { probe := filepath.Join(dir, "overflow-launched") _, err := RunForegroundCommand(context.Background(), "touch "+probe, dir, int(math.MaxInt64/int64(time.Second))+1, 0, 0, filepath.Join(dir, ".lightcode"), os.Environ()) - if err == nil || !strings.Contains(err.Error(), "overflows the seconds-to-duration conversion") { - t.Fatalf("err = %v, want the overflow rejection", err) - } - if _, statErr := os.Stat(probe); !os.IsNotExist(statErr) { - t.Fatalf("command launched despite the overflow rejection") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "overflows the seconds-to-duration conversion"), "err = %v, want the overflow rejection", err) + { + _, statErr := os.Stat(probe) + testassert.Require(t, os.IsNotExist(statErr), "command launched despite the overflow rejection") } } @@ -176,9 +168,7 @@ func TestRunForegroundCommandParentDeadlineIsCancellation(t *testing.T) { _, err := RunForegroundCommand(ctx, "sleep 5", dir, 0, 0, 0, filepath.Join(dir, ".lightcode"), os.Environ()) var exitErr *ExitError - if !errors.As(err, &exitErr) || exitErr.ExitCode != -1 || !strings.HasPrefix(exitErr.Output, "command cancelled") { - t.Fatalf("err = %v, want the cancellation classification for a parent deadline", err) - } + testassert.Require(t, errors.As(err, &exitErr) && exitErr.ExitCode == -1 && strings.HasPrefix(exitErr.Output, "command cancelled"), "err = %v, want the cancellation classification for a parent deadline", err) } // TestRunForegroundCommandNonZeroExitLargeOutputIsBounded pins the nonzero @@ -191,18 +181,9 @@ func TestRunForegroundCommandNonZeroExitLargeOutputIsBounded(t *testing.T) { output, err := RunForegroundCommand(context.Background(), "printf 'abcdefg\\n1234567\\n' && exit 7", "", 2, 12, 5, spillDir, os.Environ()) var exitErr *ExitError - if !errors.As(err, &exitErr) { - t.Fatalf("error = %T %v, want *ExitError", err, err) - } - if output == "abcdefg\n1234567\n" { - t.Fatalf("output = %q, want bounded output", output) - } - if !strings.Contains(exitErr.Output, "Error: Exit code 7\n") { - t.Fatalf("ExitError.Output = %q, missing exit header", exitErr.Output) - } - if !strings.Contains(exitErr.Output, "Full output (16 bytes) saved to: "+home+"/.lightcode/cmd_output_") { - t.Fatalf("ExitError.Output = %q, want spill marker", exitErr.Output) - } + testassert.Require(t, errors.As(err, &exitErr), "error = %T %v, want *ExitError", err, err) + testassert.Require(t, output != "abcdefg\n1234567\n", "output = %q, want bounded output", output) + testassert.Require(t, strings.Contains(exitErr.Output, "Error: Exit code 7\n") && strings.Contains(exitErr.Output, "Full output (16 bytes) saved to: "+home+"/.lightcode/cmd_output_"), "ExitError.Output = %q, missing exit header; ExitError.Output = %q, want spill marker", exitErr.Output, exitErr.Output) } // TestRunForegroundCommandTimeoutLargeOutputIsBounded pins the timeout @@ -214,18 +195,9 @@ func TestRunForegroundCommandTimeoutLargeOutputIsBounded(t *testing.T) { output, err := RunForegroundCommand(context.Background(), "printf 'abcdefg\\n1234567\\n'; sleep 5", "", 1, 12, 5, spillDir, os.Environ()) var exitErr *ExitError - if !errors.As(err, &exitErr) { - t.Fatalf("error = %T %v, want *ExitError", err, err) - } - if output == "abcdefg\n1234567\n" { - t.Fatalf("output = %q, want bounded output", output) - } - if !strings.Contains(exitErr.Output, "Error: Exit code -1 (timeout)\n") { - t.Fatalf("ExitError.Output = %q, missing timeout header", exitErr.Output) - } - if !strings.Contains(exitErr.Output, "Full output (16 bytes) saved to: "+home+"/.lightcode/cmd_output_") { - t.Fatalf("ExitError.Output = %q, want spill marker", exitErr.Output) - } + testassert.Require(t, errors.As(err, &exitErr), "error = %T %v, want *ExitError", err, err) + testassert.Require(t, output != "abcdefg\n1234567\n", "output = %q, want bounded output", output) + testassert.Require(t, strings.Contains(exitErr.Output, "Error: Exit code -1 (timeout)\n") && strings.Contains(exitErr.Output, "Full output (16 bytes) saved to: "+home+"/.lightcode/cmd_output_"), "ExitError.Output = %q, missing timeout header; ExitError.Output = %q, want spill marker", exitErr.Output, exitErr.Output) } // TestRunForegroundCommandTimeoutKillsNearConfiguredDeadline pins that a @@ -237,14 +209,11 @@ func TestRunForegroundCommandTimeoutKillsNearConfiguredDeadline(t *testing.T) { _, err := RunForegroundCommand(context.Background(), "sleep 2", dir, 1, 0, 0, filepath.Join(dir, ".lightcode"), os.Environ()) var exitErr *ExitError - if !errors.As(err, &exitErr) { - t.Fatalf("error = %T %v, want *ExitError", err, err) - } - if exitErr.ExitCode != -1 || !strings.Contains(exitErr.Output, "timeout") { - t.Fatalf("ExitError = %+v, want timeout with exit code -1", exitErr) - } - if elapsed := time.Since(start); elapsed > 1500*time.Millisecond { - t.Fatalf("timeout elapsed %s, want command killed close to the configured deadline", elapsed) + testassert.Require(t, errors.As(err, &exitErr), "error = %T %v, want *ExitError", err, err) + testassert.Require(t, exitErr.ExitCode == -1 && strings.Contains(exitErr.Output, "timeout"), "ExitError = %+v, want timeout with exit code -1", exitErr) + { + elapsed := time.Since(start) + testassert.Require(t, elapsed <= 1500*time.Millisecond, "timeout elapsed %s, want command killed close to the configured deadline", elapsed) } } @@ -272,29 +241,21 @@ func TestRunForegroundCommandCancellationOutput(t *testing.T) { } time.Sleep(10 * time.Millisecond) } - if _, err := os.Stat(ready); err != nil { - t.Fatalf("command did not reach ready marker: %v", err) + { + _, err := os.Stat(ready) + testassert.NoError(t, err) } cancel() var exitErr *ExitError select { case err := <-errCh: - if !errors.As(err, &exitErr) { - t.Fatalf("error = %T %v, want *ExitError", err, err) - } + testassert.Require(t, errors.As(err, &exitErr), "error = %T %v, want *ExitError", err, err) case <-time.After(2 * time.Second): t.Fatal("cancelled command did not return") } output := <-outCh - if !strings.HasPrefix(exitErr.Output, "command cancelled\n") { - t.Fatalf("ExitError.Output = %q, want cancellation header plus output", exitErr.Output) - } - if !strings.Contains(exitErr.Output, "Full output (16 bytes) saved to: "+home+"/.lightcode/cmd_output_") { - t.Fatalf("ExitError.Output = %q, want spill marker", exitErr.Output) - } - if output == "abcdefg\n1234567\n" { - t.Fatalf("output = %q, want bounded output", output) - } + testassert.Require(t, strings.HasPrefix(exitErr.Output, "command cancelled\n") && strings.Contains(exitErr.Output, "Full output (16 bytes) saved to: "+home+"/.lightcode/cmd_output_"), "ExitError.Output = %q, want cancellation header plus output; ExitError.Output = %q, want spill marker", exitErr.Output, exitErr.Output) + testassert.Require(t, output != "abcdefg\n1234567\n", "output = %q, want bounded output", output) }) } @@ -304,22 +265,20 @@ func TestNormalizeRunCommandArgs(t *testing.T) { decoder := json.NewDecoder(strings.NewReader(raw)) decoder.UseNumber() var args map[string]any - if err := decoder.Decode(&args); err != nil { - t.Fatalf("decode %s: %v", raw, err) + { + err := decoder.Decode(&args) + testassert.Require(t, err == nil, "decode %s: %v", raw, err) } return args } assertNormalized := func(t *testing.T, raw string, wantTimeout int) { t.Helper() normalized, err := NormalizeRunCommandArgs(decode(t, raw), 120) - if err != nil { - t.Fatalf("NormalizeRunCommandArgs(%s) = %v, want accepted", raw, err) - } - if normalized["command"] != "ls" { - t.Fatalf("command = %v, want ls", normalized["command"]) - } - if got, ok := normalized["timeout"].(json.Number); !ok || got.String() != strconv.Itoa(wantTimeout) { - t.Fatalf("timeout = %v, want the canonical lexeme %d", normalized["timeout"], wantTimeout) + testassert.Require(t, err == nil, "NormalizeRunCommandArgs(%s) = %v, want accepted", raw, err) + testassert.Equal(t, normalized["command"], "ls") + { + got, ok := normalized["timeout"].(json.Number) + testassert.Require(t, ok && got.String() == strconv.Itoa(wantTimeout), "timeout = %v, want the canonical lexeme %d", normalized["timeout"], wantTimeout) } } @@ -343,8 +302,9 @@ func TestNormalizeRunCommandArgs(t *testing.T) { `{"command":"ls","timeout":9223372036854775808}`, `{"command":"ls","timeout":9223372037}`, } { - if _, err := NormalizeRunCommandArgs(decode(t, raw), 120); err == nil { - t.Errorf("NormalizeRunCommandArgs(%s) accepted, want rejection", raw) + { + _, err := NormalizeRunCommandArgs(decode(t, raw), 120) + testassert.Check(t, err != nil, "NormalizeRunCommandArgs(%s) accepted, want rejection", raw) } } for _, raw := range []string{ @@ -352,8 +312,9 @@ func TestNormalizeRunCommandArgs(t *testing.T) { `{"command":"ls","background":0}`, `{"command":"ls","background":"later"}`, } { - if _, err := NormalizeRunCommandArgs(decode(t, raw), 120); err == nil || err.Error() != "run_command: background must be a boolean" { - t.Errorf("NormalizeRunCommandArgs(%s) = %v, want the boolean-consumption error", raw, err) + { + _, err := NormalizeRunCommandArgs(decode(t, raw), 120) + testassert.Check(t, err != nil && err.Error() == "run_command: background must be a boolean", "NormalizeRunCommandArgs(%s) = %v, want the boolean-consumption error", raw, err) } } @@ -379,19 +340,20 @@ func TestNormalizeRunCommandArgs(t *testing.T) { t.Errorf("NormalizeRunCommandArgs(%s) = %v, want accepted", tc.raw, err) continue } - if got, ok := normalized["background"].(bool); !ok || got != tc.wantBackground { - t.Errorf("NormalizeRunCommandArgs(%s) background = %v, want %v", tc.raw, normalized["background"], tc.wantBackground) + { + got, ok := normalized["background"].(bool) + testassert.Check(t, ok && got == tc.wantBackground, "NormalizeRunCommandArgs(%s) background = %v, want %v", tc.raw, normalized["background"], tc.wantBackground) } - if got, ok := normalized["timeout"].(json.Number); !ok || got.String() != strconv.Itoa(tc.wantTimeout) { - t.Errorf("NormalizeRunCommandArgs(%s) timeout = %v, want the canonical lexeme %d", tc.raw, normalized["timeout"], tc.wantTimeout) + { + got, ok := normalized["timeout"].(json.Number) + testassert.Check(t, ok && got.String() == strconv.Itoa(tc.wantTimeout), "NormalizeRunCommandArgs(%s) timeout = %v, want the canonical lexeme %d", tc.raw, normalized["timeout"], tc.wantTimeout) } } normalized, err := NormalizeRunCommandArgs(decode(t, `{"command":"ls","_lightcode_receipt":"x"}`), 120) - if err != nil { - t.Fatalf("private-field strip: %v", err) - } - if _, ok := normalized["_lightcode_receipt"]; ok { - t.Fatal("normalized arguments retained a private field") + testassert.NoError(t, err) + { + _, ok := normalized["_lightcode_receipt"] + testassert.Require(t, !ok, "normalized arguments retained a private field") } } diff --git a/internal/tool/shared_test_helpers_test.go b/internal/tool/shared_test_helpers_test.go index 593db42a..d0e49938 100644 --- a/internal/tool/shared_test_helpers_test.go +++ b/internal/tool/shared_test_helpers_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/snapshot" + "github.com/MMinasyan/lightcode/internal/testassert" ) // testCodeGroupStore adapts one real snapshot.CodeStore to the shared @@ -33,9 +34,7 @@ func (s testCodeGroupStore) Snapshot(turn int, absPath string) error { func newCodeGroupStore(t *testing.T, groupDir string, turn int) testCodeGroupStore { t.Helper() store, err := snapshot.OpenCodeStore(groupDir) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) return testCodeGroupStore{CodeStore: store, turn: turn} } @@ -140,11 +139,10 @@ func (s *recordingSnapshotStore) CurrentTurn() int { func assertFileContent(t *testing.T, path, want string) { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if got := string(data); got != want { - t.Fatalf("content of %s = %q, want %q", path, got, want) + testassert.NoError(t, err) + { + got := string(data) + testassert.Require(t, got == want, "content of %s = %q, want %q", path, got, want) } } diff --git a/internal/tool/sleep_test.go b/internal/tool/sleep_test.go index 263fc920..ab58e5dc 100644 --- a/internal/tool/sleep_test.go +++ b/internal/tool/sleep_test.go @@ -5,6 +5,8 @@ import ( "errors" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestSleepCustomDurationAndClampsMinimum(t *testing.T) { @@ -23,14 +25,11 @@ func TestSleepCustomDurationAndClampsMinimum(t *testing.T) { t.Run(tt.name, func(t *testing.T) { start := time.Now() result, err := (Sleep{}).Execute(context.Background(), tt.params) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != tt.want { - t.Fatalf("Execute result = %q, want %q", result, tt.want) - } - if elapsed := time.Since(start); elapsed < tt.minWait { - t.Fatalf("elapsed = %s, want at least %s", elapsed, tt.minWait) + testassert.NoError(t, err) + testassert.Equal(t, result, tt.want) + { + elapsed := time.Since(start) + testassert.Require(t, elapsed >= tt.minWait, "elapsed = %s, want at least %s", elapsed, tt.minWait) } }) } @@ -42,11 +41,10 @@ func TestSleepContextCancelReturnsPromptly(t *testing.T) { start := time.Now() result, err := (Sleep{}).Execute(ctx, map[string]any{"seconds": float64(300)}) - if !errors.Is(err, context.Canceled) { - t.Fatalf("Execute result=%q error=%v, want context.Canceled", result, err) - } - if elapsed := time.Since(start); elapsed > 100*time.Millisecond { - t.Fatalf("cancelled sleep elapsed %s, want prompt return", elapsed) + testassert.Require(t, errors.Is(err, context.Canceled), "Execute result=%q error=%v, want context.Canceled", result, err) + { + elapsed := time.Since(start) + testassert.Require(t, elapsed <= 100*time.Millisecond, "cancelled sleep elapsed %s, want prompt return", elapsed) } } @@ -59,10 +57,9 @@ func TestSleepContextCancelDuringSleep(t *testing.T) { start := time.Now() _, err := (Sleep{}).Execute(ctx, map[string]any{"seconds": float64(10)}) - if !errors.Is(err, context.Canceled) { - t.Fatalf("Execute error = %v, want context.Canceled", err) - } - if elapsed := time.Since(start); elapsed > 500*time.Millisecond { - t.Fatalf("cancelled sleep elapsed %s, want prompt return", elapsed) + testassert.Require(t, errors.Is(err, context.Canceled), "Execute error = %v, want context.Canceled", err) + { + elapsed := time.Since(start) + testassert.Require(t, elapsed <= 500*time.Millisecond, "cancelled sleep elapsed %s, want prompt return", elapsed) } } diff --git a/internal/tool/snapshot_lifecycle_test.go b/internal/tool/snapshot_lifecycle_test.go index bc7aeb3f..705a1e8e 100644 --- a/internal/tool/snapshot_lifecycle_test.go +++ b/internal/tool/snapshot_lifecycle_test.go @@ -7,6 +7,8 @@ import ( "sync" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestWriteFileRetainsSnapshotWhenTargetChangesBeforeMutation(t *testing.T) { @@ -18,21 +20,15 @@ func TestWriteFileRetainsSnapshotWhenTargetChangesBeforeMutation(t *testing.T) { name: "deleted", mutate: func(t *testing.T, path string) { t.Helper() - if err := os.Remove(path); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(path)) }, }, { name: "recreated", mutate: func(t *testing.T, path string) { t.Helper() - if err := os.Remove(path); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte("external"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(path)) + testassert.NoError(t, os.WriteFile(path, []byte("external"), 0o644)) }, }, } { @@ -52,17 +48,11 @@ func TestWriteFileRetainsSnapshotWhenTargetChangesBeforeMutation(t *testing.T) { "content": "after", }) - if err != nil { - t.Fatalf("write_file error = %v", err) - } + testassert.NoError(t, err) assertFileContent(t, path, "after") affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v", err) - } - if len(affected.Restored) != 1 || affected.Restored[0] != path { - t.Fatalf("RevertCode affected = %v, want restored %s", affected, path) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 1 && affected.Restored[0] == path, "RevertCode affected = %v, want restored %s", affected, path) assertFileContent(t, path, "before") }) } @@ -79,16 +69,10 @@ func TestEditFileDiscardsSnapshotWhenEditFailsBeforeMutation(t *testing.T) { "new_string": "after", }) - if err == nil { - t.Fatal("edit_file succeeded, want missing match error") - } + testassert.Require(t, err != nil, "edit_file succeeded, want missing match error") affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v, want no active snapshot entry", err) - } - if len(affected.Restored) != 0 { - t.Fatalf("RevertCode affected = %v, want no files", affected) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0, "RevertCode affected = %v, want no files", affected) assertFileContent(t, path, "before") } @@ -99,12 +83,8 @@ func TestEditFileRetainsSnapshotWhenTargetChangesBeforeMutation(t *testing.T) { wrapped := &mutatingTransactionalSnapshotStore{ testCodeGroupStore: store, mutate: func() { - if err := os.Remove(path); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte("external"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(path)) + testassert.NoError(t, os.WriteFile(path, []byte("external"), 0o644)) }, } @@ -114,17 +94,11 @@ func TestEditFileRetainsSnapshotWhenTargetChangesBeforeMutation(t *testing.T) { "new_string": "after", }) - if err != nil { - t.Fatalf("edit_file error = %v", err) - } + testassert.NoError(t, err) assertFileContent(t, path, "after") affected, err := store.RevertCode(0) - if err != nil { - t.Fatalf("RevertCode error = %v", err) - } - if len(affected.Restored) != 1 || affected.Restored[0] != path { - t.Fatalf("RevertCode affected = %v, want restored %s", affected, path) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 1 && affected.Restored[0] == path, "RevertCode affected = %v, want restored %s", affected, path) assertFileContent(t, path, "before") } @@ -133,27 +107,22 @@ func TestFailedValidationDoesNotDiscardPreviouslyRetainedSnapshot(t *testing.T) path := snapshotLifecycleFile(t, root, "file.txt", "before") store := newSnapshotLifecycleStore(t) - if _, err := execWrite(t, root, "", "", CapabilityOptions{}, store, map[string]any{ - "path": path, - "content": "after", - }); err != nil { - t.Fatal(err) + { + _, err := execWrite(t, root, "", "", CapabilityOptions{}, store, map[string]any{ + "path": path, + "content": "after", + }) + testassert.NoError(t, err) } _, err := execEdit(t, root, "", "", CapabilityOptions{}, store, map[string]any{ "path": path, "old_string": "missing", "new_string": "other", }) - if err == nil { - t.Fatal("edit_file succeeded, want missing match error") - } + testassert.Require(t, err != nil, "edit_file succeeded, want missing match error") affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 1 || affected.Restored[0] != path { - t.Fatalf("RevertCode affected = %v, want retained snapshot for %s", affected, path) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 1 && affected.Restored[0] == path, "RevertCode affected = %v, want retained snapshot for %s", affected, path) assertFileContent(t, path, "before") } @@ -162,32 +131,25 @@ func TestWriteFileSameTurnSecondWriteControlsSnapshotIdentity(t *testing.T) { path := snapshotLifecycleFile(t, root, "file.txt", "before") store := newSnapshotLifecycleStore(t) - if _, err := execWrite(t, root, "", "", CapabilityOptions{}, store, map[string]any{ - "path": path, - "content": "first", - }); err != nil { - t.Fatal(err) - } - if _, err := execWrite(t, root, "", "", CapabilityOptions{}, store, map[string]any{ - "path": path, - "content": "second", - }); err != nil { - t.Fatal(err) + { + _, err := execWrite(t, root, "", "", CapabilityOptions{}, store, map[string]any{ + "path": path, + "content": "first", + }) + testassert.NoError(t, err) } - if err := os.WriteFile(path, []byte("first"), 0o644); err != nil { - t.Fatal(err) + { + _, err := execWrite(t, root, "", "", CapabilityOptions{}, store, map[string]any{ + "path": path, + "content": "second", + }) + testassert.NoError(t, err) } + testassert.NoError(t, os.WriteFile(path, []byte("first"), 0o644)) affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 0 { - t.Fatalf("RevertCode restored = %v, want none", affected.Restored) - } - if len(affected.Skipped) != 1 || affected.Skipped[0].Path != path { - t.Fatalf("RevertCode skipped = %+v, want skipped %s", affected, path) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 0 && len(affected.Skipped) == 1 && affected.Skipped[0].Path == path, "RevertCode restored = %v, want none; RevertCode skipped = %+v, want skipped %s", affected.Restored, affected, path) assertFileContent(t, path, "first") } @@ -223,9 +185,7 @@ func TestConcurrentSameTurnWritesKeepLatestSnapshotIdentity(t *testing.T) { secondFinishedBeforeRelease := false select { case err := <-secondDone: - if err != nil { - t.Fatalf("second write before first release: %v", err) - } + testassert.NoError(t, err) secondFinishedBeforeRelease = true case <-store.secondLockAttempt: case <-time.After(time.Second): @@ -233,26 +193,15 @@ func TestConcurrentSameTurnWritesKeepLatestSnapshotIdentity(t *testing.T) { } close(store.releaseFirstRecord) - if err := <-firstDone; err != nil { - t.Fatalf("first write: %v", err) - } + testassert.NoError(t, <-firstDone) if !secondFinishedBeforeRelease { - if err := <-secondDone; err != nil { - t.Fatalf("second write: %v", err) - } + testassert.NoError(t, <-secondDone) } assertFileContent(t, path, "second") affected, err := store.RevertCode(0) - if err != nil { - t.Fatal(err) - } - if len(affected.Restored) != 1 || affected.Restored[0] != path { - t.Fatalf("RevertCode restored = %v, want %s", affected.Restored, path) - } - if len(affected.Skipped) != 0 { - t.Fatalf("RevertCode skipped = %+v, want none", affected.Skipped) - } + testassert.NoError(t, err) + testassert.Require(t, len(affected.Restored) == 1 && affected.Restored[0] == path && len(affected.Skipped) == 0, "RevertCode restored = %v, want %s; RevertCode skipped = %+v, want none", affected.Restored, path, affected.Skipped) assertFileContent(t, path, "before") } @@ -306,19 +255,10 @@ func TestRetainedPartialMutationRecordsCurrentIdentityAndReverts(t *testing.T) { err := tc.run(t, root, path, store) - if err == nil { - t.Fatalf("%s with %s succeeded, want injected I/O failure", tc.name, step) - } + testassert.Require(t, err != nil, "%s with %s succeeded, want injected I/O failure", tc.name, step) affected, revErr := store.RevertCode(0) - if revErr != nil { - t.Fatalf("RevertCode error = %v", revErr) - } - if len(affected.Restored) != 1 || affected.Restored[0] != path { - t.Fatalf("RevertCode affected = %+v, want restored %s", affected, path) - } - if len(affected.Skipped) != 0 { - t.Fatalf("RevertCode skipped = %+v, want none", affected.Skipped) - } + testassert.Require(t, revErr == nil, "RevertCode error = %v", revErr) + testassert.Require(t, len(affected.Restored) == 1 && affected.Restored[0] == path && len(affected.Skipped) == 0, "RevertCode affected = %+v, want restored %s; RevertCode skipped = %+v, want none", affected, path, affected.Skipped) assertFileContent(t, path, "before") }) } @@ -363,21 +303,13 @@ func TestRetainedPartialCreatedFileMutationRecordsCurrentIdentityAndDeletesOnRev err := tc.run(t, root, path, store) - if err == nil { - t.Fatalf("%s with %s succeeded, want injected I/O failure", tc.name, step) - } + testassert.Require(t, err != nil, "%s with %s succeeded, want injected I/O failure", tc.name, step) affected, revErr := store.RevertCode(0) - if revErr != nil { - t.Fatalf("RevertCode error = %v", revErr) - } - if len(affected.Restored) != 1 || affected.Restored[0] != path { - t.Fatalf("RevertCode affected = %+v, want restored %s", affected, path) - } - if len(affected.Skipped) != 0 { - t.Fatalf("RevertCode skipped = %+v, want none", affected.Skipped) - } - if _, statErr := os.Lstat(path); !os.IsNotExist(statErr) { - t.Fatalf("created file should be removed after RevertCode, stat err = %v", statErr) + testassert.Require(t, revErr == nil, "RevertCode error = %v", revErr) + testassert.Require(t, len(affected.Restored) == 1 && affected.Restored[0] == path && len(affected.Skipped) == 0, "RevertCode affected = %+v, want restored %s; RevertCode skipped = %+v, want none", affected, path, affected.Skipped) + { + _, statErr := os.Lstat(path) + testassert.Require(t, os.IsNotExist(statErr), "created file should be removed after RevertCode, stat err = %v", statErr) } }) } @@ -521,11 +453,7 @@ func newSnapshotLifecycleStore(t *testing.T) testCodeGroupStore { func snapshotLifecycleFile(t *testing.T, root, name, content string) string { t.Helper() path := filepath.Join(root, name) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(content), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) + testassert.NoError(t, os.WriteFile(path, []byte(content), 0o644)) return path } diff --git a/internal/tool/workspace_root_test.go b/internal/tool/workspace_root_test.go index 3181ad3f..318e57a4 100644 --- a/internal/tool/workspace_root_test.go +++ b/internal/tool/workspace_root_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestFileToolsResolveRelativePathsFromWorkspaceRoot(t *testing.T) { @@ -16,37 +17,36 @@ func TestFileToolsResolveRelativePathsFromWorkspaceRoot(t *testing.T) { t.Chdir(other) existing := filepath.Join(root, "existing.txt") - if err := os.WriteFile(existing, []byte("old\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(existing, []byte("old\n"), 0o600)) out, err := execRead(t, root, "", config.ToolsConfig{ReadMaxLines: 500}, map[string]any{"path": "existing.txt"}) - if err != nil { - t.Fatalf("read_file: %v", err) - } - if !strings.Contains(out, "old") { - t.Fatalf("read output = %q, want root file content", out) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(out, "old"), "read output = %q, want root file content", out) - if _, err := execEdit(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{ - "path": "existing.txt", - "old_string": "old", - "new_string": "new", - }); err != nil { - t.Fatalf("edit_file: %v", err) + { + _, err := execEdit(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{ + "path": "existing.txt", + "old_string": "old", + "new_string": "new", + }) + testassert.NoError(t, err) } - if got, err := os.ReadFile(existing); err != nil || string(got) != "new\n" { - t.Fatalf("root existing.txt = %q, %v; want edited root file", got, err) + { + got, err := os.ReadFile(existing) + testassert.Require(t, err == nil && string(got) == "new\n", "root existing.txt = %q, %v; want edited root file", got, err) } - if _, err := execWrite(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"path": "created.txt", "content": "created"}); err != nil { - t.Fatalf("write_file: %v", err) + { + _, err := execWrite(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{"path": "created.txt", "content": "created"}) + testassert.NoError(t, err) } - if got, err := os.ReadFile(filepath.Join(root, "created.txt")); err != nil || string(got) != "created" { - t.Fatalf("root created.txt = %q, %v; want created root file", got, err) + { + got, err := os.ReadFile(filepath.Join(root, "created.txt")) + testassert.Require(t, err == nil && string(got) == "created", "root created.txt = %q, %v; want created root file", got, err) } - if _, err := os.Stat(filepath.Join(other, "created.txt")); !os.IsNotExist(err) { - t.Fatalf("created.txt was written relative to cwd; stat err = %v", err) + { + _, err := os.Stat(filepath.Join(other, "created.txt")) + testassert.Require(t, os.IsNotExist(err), "created.txt was written relative to cwd; stat err = %v", err) } } @@ -56,10 +56,6 @@ func TestRunCommandUsesWorkspaceRoot(t *testing.T) { t.Chdir(other) out, err := RunForegroundCommand(context.Background(), "pwd", root, 2, 15360, 5000, t.TempDir(), os.Environ()) - if err != nil { - t.Fatalf("run_command: %v", err) - } - if strings.TrimSpace(out) != root { - t.Fatalf("pwd = %q, want workspace root %q", out, root) - } + testassert.NoError(t, err) + testassert.Require(t, strings.TrimSpace(out) == root, "pwd = %q, want workspace root %q", out, root) } diff --git a/internal/tool/write_file_test.go b/internal/tool/write_file_test.go index f4781de4..04dfdf4b 100644 --- a/internal/tool/write_file_test.go +++ b/internal/tool/write_file_test.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // execWrite runs one write through the exported Normalize* + Prepare* entries. @@ -37,12 +39,8 @@ func TestPreparedWriteCreatesNewFileAndParentDirs(t *testing.T) { "path": path, "content": "created", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Wrote "+path+"." { - t.Fatalf("Execute result = %q, want original path in success message", result) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Wrote "+path+".") assertFileContent(t, path, "created") } @@ -50,19 +48,21 @@ func TestPreparedWriteWritesContentExactlyIncludingTrailingNewline(t *testing.T) root := t.TempDir() path := filepath.Join(root, "exact.txt") - if _, err := execWrite(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{ - "path": path, - "content": "no trailing newline", - }); err != nil { - t.Fatalf("first Execute error = %v", err) + { + _, err := execWrite(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{ + "path": path, + "content": "no trailing newline", + }) + testassert.NoError(t, err) } assertFileContent(t, path, "no trailing newline") - if _, err := execWrite(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{ - "path": path, - "content": "with trailing newline\n", - }); err != nil { - t.Fatalf("second Execute error = %v", err) + { + _, err := execWrite(t, root, "", "", CapabilityOptions{}, &applyPatchStore{turn: 1}, map[string]any{ + "path": path, + "content": "with trailing newline\n", + }) + testassert.NoError(t, err) } assertFileContent(t, path, "with trailing newline\n") } @@ -76,21 +76,11 @@ func TestPreparedWriteWithSnapshotSnapshotsBeforeWrite(t *testing.T) { "path": path, "content": "after", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Wrote "+path+"." { - t.Fatalf("Execute result = %q, want original path in success message", result) - } - if len(store.calls) != 1 { - t.Fatalf("snapshot calls = %d, want 1", len(store.calls)) - } - if store.calls[0].turn != 9 || store.calls[0].path != path { - t.Fatalf("snapshot call = %+v, want turn=9 path=%q", store.calls[0], path) - } - if store.before[path] != "before" { - t.Fatalf("snapshot saw %q, want pre-write content", store.before[path]) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Wrote "+path+".") + testassert.Equal(t, len(store.calls), 1) + testassert.Require(t, store.calls[0].turn == 9 && store.calls[0].path == path, "snapshot call = %+v, want turn=9 path=%q", store.calls[0], path) + testassert.Require(t, store.before[path] == "before", "snapshot saw %q, want pre-write content", store.before[path]) assertFileContent(t, path, "after") } @@ -98,13 +88,9 @@ func TestPreparedWriteWithSnapshotUsesCanonicalTargetAndRequestedResult(t *testi root := t.TempDir() realDir := t.TempDir() target := filepath.Join(realDir, "file.txt") - if err := os.WriteFile(target, []byte("before"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(target, []byte("before"), 0o644)) linkDir := filepath.Join(root, "linked") - if err := os.Symlink(realDir, linkDir); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(realDir, linkDir)) requested := filepath.Join(linkDir, "file.txt") store := &recordingSnapshotStore{turn: 10, before: map[string]string{}} @@ -113,20 +99,10 @@ func TestPreparedWriteWithSnapshotUsesCanonicalTargetAndRequestedResult(t *testi "content": "after", }) - if err != nil { - t.Fatalf("Execute error = %v", err) - } - if result != "Wrote "+requested+"." { - t.Fatalf("Execute result = %q, want requested path in success message", result) - } - if len(store.calls) != 1 { - t.Fatalf("snapshot calls = %d, want 1", len(store.calls)) - } - if store.calls[0].path != requested || store.calls[0].canonical != target { - t.Fatalf("snapshot call = %+v, want original=%q canonical=%q", store.calls[0], requested, target) - } - if store.before[target] != "before" { - t.Fatalf("snapshot saw %q, want pre-write content", store.before[target]) - } + testassert.NoError(t, err) + testassert.Equal(t, result, "Wrote "+requested+".") + testassert.Equal(t, len(store.calls), 1) + testassert.Require(t, store.calls[0].path == requested && store.calls[0].canonical == target, "snapshot call = %+v, want original=%q canonical=%q", store.calls[0], requested, target) + testassert.Require(t, store.before[target] == "before", "snapshot saw %q, want pre-write content", store.before[target]) assertFileContent(t, target, "after") } diff --git a/internal/toolargs/decode_test.go b/internal/toolargs/decode_test.go index e95aec8b..b8c6f670 100644 --- a/internal/toolargs/decode_test.go +++ b/internal/toolargs/decode_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestDecodeValidDomain proves the accepted side: one complete JSON object @@ -11,29 +13,22 @@ import ( // lexeme preserved exactly as a json.Number beyond float64. func TestDecodeValidDomain(t *testing.T) { args, err := Decode(json.RawMessage(` {"a":"x","b":true,"c":null,"d":{"e":[1,2]}} `)) - if err != nil { - t.Fatalf("Decode: %v", err) - } - if args["a"] != "x" || args["b"] != true || args["c"] != nil { - t.Fatalf("decoded = %#v, want the members preserved", args) - } + testassert.NoError(t, err) + testassert.Require(t, args["a"] == "x" && args["b"] == true && args["c"] == nil, "decoded = %#v, want the members preserved", args) if nested, ok := args["d"].(map[string]any); !ok { t.Fatalf("decoded = %#v, want the nested object preserved", args) } else if list, ok := nested["e"].([]any); !ok || len(list) != 2 { t.Fatalf("decoded = %#v, want the nested array preserved", args) } - if empty, err := Decode(json.RawMessage(`{}`)); err != nil || len(empty) != 0 || empty == nil { - t.Fatalf("Decode({}) = %#v, %v, want an empty owned map", empty, err) + { + empty, err := Decode(json.RawMessage(`{}`)) + testassert.Require(t, err == nil && len(empty) == 0 && empty != nil, "Decode({}) = %#v, %v, want an empty owned map", empty, err) } nums, err := Decode(json.RawMessage(`{"big":123456789012345678901234567890,"exp":1e400,"neg":-0.0}`)) - if err != nil { - t.Fatalf("Decode numbers: %v", err) - } + testassert.NoError(t, err) for key, want := range map[string]string{"big": "123456789012345678901234567890", "exp": "1e400", "neg": "-0.0"} { got, ok := nums[key].(json.Number) - if !ok || got.String() != want { - t.Errorf("decoded %s = %#v, want the exact lexeme %q", key, nums[key], want) - } + testassert.Check(t, ok && got.String() == want, "decoded %s = %#v, want the exact lexeme %q", key, nums[key], want) } } @@ -62,15 +57,11 @@ func TestDecodeInvalidDomain(t *testing.T) { } for raw, want := range exact { _, err := Decode(json.RawMessage(raw)) - if err == nil || err.Error() != want { - t.Errorf("Decode(%q) error = %v, want exactly %q", raw, err, want) - } + testassert.Check(t, err != nil && err.Error() == want, "Decode(%q) error = %v, want exactly %q", raw, err, want) } for raw, want := range prefix { _, err := Decode(json.RawMessage(raw)) - if err == nil || !strings.HasPrefix(err.Error(), want) { - t.Errorf("Decode(%q) error = %v, want prefix %q", raw, err, want) - } + testassert.Check(t, err != nil && strings.HasPrefix(err.Error(), want), "Decode(%q) error = %v, want prefix %q", raw, err, want) } } @@ -79,13 +70,9 @@ func TestDecodeInvalidDomain(t *testing.T) { func TestDecodeRetainsNoCallerBytes(t *testing.T) { raw := []byte(`{"s":"keep","n":123}`) args, err := Decode(raw) - if err != nil { - t.Fatalf("Decode: %v", err) - } + testassert.NoError(t, err) for i := range raw { raw[i] = 'x' } - if args["s"] != "keep" || args["n"].(json.Number).String() != "123" { - t.Fatalf("decoded = %#v, want values independent of the mutated caller bytes", args) - } + testassert.Require(t, args["s"] == "keep" && args["n"].(json.Number).String() == "123", "decoded = %#v, want values independent of the mutated caller bytes", args) } diff --git a/internal/version/version_test.go b/internal/version/version_test.go index 147092ea..85a6bec3 100644 --- a/internal/version/version_test.go +++ b/internal/version/version_test.go @@ -4,6 +4,8 @@ import ( "runtime" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestIsRelease(t *testing.T) { @@ -32,8 +34,9 @@ func TestIsRelease(t *testing.T) { {"v0.0.3 ", false}, } for _, c := range cases { - if got := IsRelease(c.v); got != c.want { - t.Errorf("IsRelease(%q) = %v, want %v", c.v, got, c.want) + { + got := IsRelease(c.v) + testassert.Check(t, got == c.want, "IsRelease(%q) = %v, want %v", c.v, got, c.want) } } } @@ -73,8 +76,9 @@ func TestCompare(t *testing.T) { {"v2.0.0", "v1.18446744073709551615.18446744073709551615", 1}, } for _, c := range cases { - if got := Compare(c.a, c.b); got != c.want { - t.Errorf("Compare(%q, %q) = %d, want %d", c.a, c.b, got, c.want) + { + got := Compare(c.a, c.b) + testassert.Check(t, got == c.want, "Compare(%q, %q) = %d, want %d", c.a, c.b, got, c.want) } } } @@ -101,8 +105,9 @@ func TestStringClassification(t *testing.T) { } for _, c := range cases { setIdentity(t, c.v, c.commit) - if got := String(); got != c.want { - t.Errorf("String() with Version=%q Commit=%q = %q, want %q", c.v, c.commit, got, c.want) + { + got := String() + testassert.Check(t, got == c.want, "String() with Version=%q Commit=%q = %q, want %q", c.v, c.commit, got, c.want) } } } @@ -119,8 +124,9 @@ func TestLineShapes(t *testing.T) { } for _, c := range cases { setIdentity(t, c.v, c.commit) - if got := Line(); got != c.want { - t.Errorf("Line() with Version=%q Commit=%q = %q, want %q", c.v, c.commit, got, c.want) + { + got := Line() + testassert.Check(t, got == c.want, "Line() with Version=%q Commit=%q = %q, want %q", c.v, c.commit, got, c.want) } } } @@ -128,16 +134,9 @@ func TestLineShapes(t *testing.T) { func TestCurrentReportsRawValues(t *testing.T) { setIdentity(t, "v9.9.9", "abcdef1") info := Current() - if info.Version != "v9.9.9" || info.Commit != "abcdef1" { - t.Fatalf("Current() = %+v, want raw stamped values", info) - } - if info.OS == "" || info.Arch == "" { - t.Fatalf("Current() must fill os/arch, got %+v", info) - } + testassert.Require(t, info.Version == "v9.9.9" && info.Commit == "abcdef1" && info.OS != "" && info.Arch != "", "Current() = %+v, want raw stamped values; Current() must fill os/arch, got %+v", info, info) setIdentity(t, "dev", "") info = Current() - if info.Version != "dev" || info.Commit != "" { - t.Fatalf("Current() = %+v, want dev with empty commit", info) - } + testassert.Require(t, info.Version == "dev" && info.Commit == "", "Current() = %+v, want dev with empty commit", info) } diff --git a/model/encode_headers_test.go b/model/encode_headers_test.go index 9bed1b38..d57377e1 100644 --- a/model/encode_headers_test.go +++ b/model/encode_headers_test.go @@ -2,6 +2,10 @@ package model import "testing" +import ( + "github.com/MMinasyan/lightcode/internal/testassert" +) + // TestBuildHeadersPinsDefaultsKeyAndOverrides pins the single header helper: JSON content type and SSE accept defaults, Bearer authorization exactly when a key is present (empty omits it entirely), then resolved headers overwriting matching defaults case-insensitively while adding new ones — every entry stored in MIME canonical form with http.Header.Set semantics. func TestBuildHeadersPinsDefaultsKeyAndOverrides(t *testing.T) { t.Run("defaults-without-key", func(t *testing.T) { // no API key: exactly the two protocol defaults, nothing else may appear. @@ -58,17 +62,17 @@ func TestBuildHeadersPinsDefaultsKeyAndOverrides(t *testing.T) { // assertHeaderMapEqual fails with both maps shown when they differ in size or any value. func assertHeaderMapEqual(t *testing.T, got, want map[string]string) { t.Helper() // one comparison helper for every header subtest above and below. - if len(got) != len(want) { - t.Fatalf("headers = %#v (len %d), want %#v (len %d)", got, len(got), want, len(want)) - } + testassert.Require(t, len(got) == len(want), "headers = %#v (len %d), want %#v (len %d)", got, len(got), want, len(want)) for k, v := range want { // every expected key present with the exact value. - if gv, ok := got[k]; !ok || gv != v { - t.Fatalf("header %q = %q (present=%v), want %q", k, gv, ok, v) + { + gv, ok := got[k] + testassert.Require(t, ok && gv == v, "header %q = %q (present=%v), want %q", k, gv, ok, v) } } for k := range got { // no unexpected key may appear either. - if _, ok := want[k]; !ok { - t.Fatalf("unexpected header %q: %#v", k, got) + { + _, ok := want[k] + testassert.Require(t, ok, "unexpected header %q: %#v", k, got) } } } @@ -90,8 +94,9 @@ func TestChatEndpointPinsDefaultAndCustom(t *testing.T) { t.Run(tc.name, func(t *testing.T) { rt := testResolved() // identity fields are irrelevant to endpoint resolution; only BaseURL varies per row. rt.BaseURL = tc.base - if got := ChatEndpoint(rt); got != tc.want { - t.Fatalf("ChatEndpoint(base=%q) = %q, want %q", tc.base, got, tc.want) + { + got := ChatEndpoint(rt) + testassert.Require(t, got == tc.want, "ChatEndpoint(base=%q) = %q, want %q", tc.base, got, tc.want) } }) } diff --git a/model/encode_replay_test.go b/model/encode_replay_test.go index a4acf35e..b6d11192 100644 --- a/model/encode_replay_test.go +++ b/model/encode_replay_test.go @@ -3,6 +3,8 @@ package model import ( "encoding/json" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // keptAssistant returns a same-source assistant message with an optional tool call. @@ -24,9 +26,7 @@ func keptAssistant(t *testing.T, source ModelRef, withCall bool) Message { func withExtra(t *testing.T, in Message, key string) Message { in.Extra = Extra{key: json.RawMessage(`"kept"`)} // re-run construction so ownership/clone rules match production paths for every fixture in this file (NewMessage deep-copies extras at its accepting boundary). out, err := NewMessage(in) - if err != nil { - t.Fatalf("NewMessage with extra %q failed: %v", key, err) - } + testassert.Require(t, err == nil, "NewMessage with extra %q failed: %v", key, err) return out } @@ -55,8 +55,10 @@ func TestReplayPolicyMatrix(t *testing.T) { body := mustEncode(t, rt, Request{Messages: []Message{msg}}) objs := decodeMessages(t, body) // exactly one message for this fixture on every row. - if _, ok := objs[0]["marker"]; (ok && !tc.wantKeep) || (!ok && tc.wantKeep) { - t.Fatalf("marker present=%v want keep=%v: %#v", ok, tc.wantKeep, objs[0]) // wire presence is the whole observable policy outcome here. + // wire presence is the whole observable policy outcome here. + { + _, ok := objs[0]["marker"] + testassert.Require(t, (!ok || tc.wantKeep) && (ok || !tc.wantKeep), "marker present=%v want keep=%v: %#v", ok, tc.wantKeep, objs[0]) } }) } @@ -86,50 +88,53 @@ func TestTargetDropSetAppliesAtEveryRetainedScope(t *testing.T) { } call, err := NewToolCall(ToolCall{ID: "c-drop", Name: "lookup", Arguments: json.RawMessage(`{}`), Extra: Extra{"drop_me": json.RawMessage(`3`), "keep_me": json.RawMessage(`4`)}}) - if err != nil { - t.Fatalf("NewToolCall(drop-scope fixture): %v", err) - } + testassert.NoError(t, err) msg.ToolCalls = append(msg.ToolCalls, call) msg.Extra["keep_me"] = json.RawMessage(`"m-kept"`) validated, err := NewMessage(msg) - if err != nil { - t.Fatalf("NewMessage(drop-scope message): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rtRow, Request{Messages: []Message{validated}}) msgs := decodeMessages(t, body) obj := msgs[0] - if _, ok := obj["drop_me"]; ok { - t.Fatalf("dropped message-scope key survived onto the wire: %#v", obj) + { + _, ok := obj["drop_me"] + testassert.Require(t, !ok, "dropped message-scope key survived onto the wire: %#v", obj) } - if _, ok := wireString(t, obj, "keep_me"); !ok { - t.Fatalf("non-dropped message-scope key lost during drop-set application: %#v", obj) + { + _, ok := wireString(t, obj, "keep_me") + testassert.Require(t, ok, "non-dropped message-scope key lost during drop-set application: %#v", obj) } parts := unmarshalContentParts(t, obj) - if _, ok := parts[0]["drop_me"]; ok { - t.Fatalf("dropped content-part-scope key survived onto the wire: %#v", parts[0]) + { + _, ok := parts[0]["drop_me"] + testassert.Require(t, !ok, "dropped content-part-scope key survived onto the wire: %#v", parts[0]) } - if _, ok := parts[0]["keep_me"]; !ok { - t.Fatalf("non-dropped content-part-scope key lost during drop-set application: %#v", parts[0]) + { + _, ok := parts[0]["keep_me"] + testassert.Require(t, ok, "non-dropped content-part-scope key lost during drop-set application: %#v", parts[0]) } var calls []map[string]json.RawMessage - if err := json.Unmarshal(obj["tool_calls"], &calls); err != nil || len(calls) != 1 { - t.Fatalf("tool_calls not a one-object array: %s", obj["tool_calls"]) + { + err := json.Unmarshal(obj["tool_calls"], &calls) + testassert.Require(t, err == nil && len(calls) == 1, "tool_calls not a one-object array: %s", obj["tool_calls"]) } - if _, ok := calls[0]["drop_me"]; ok { - t.Fatalf("dropped tool-call-scope key survived onto the wire: %#v", calls[0]) + { + _, ok := calls[0]["drop_me"] + testassert.Require(t, !ok, "dropped tool-call-scope key survived onto the wire: %#v", calls[0]) } - if _, ok := calls[0]["keep_me"]; !ok { - t.Fatalf("non-dropped tool-call-scope key lost during drop-set application: %#v", calls[0]) + { + _, ok := calls[0]["keep_me"] + testassert.Require(t, ok, "non-dropped tool-call-scope key lost during drop-set application: %#v", calls[0]) } }) } diff --git a/model/encode_reserved_test.go b/model/encode_reserved_test.go index 79f97674..4865be22 100644 --- a/model/encode_reserved_test.go +++ b/model/encode_reserved_test.go @@ -5,6 +5,8 @@ import ( "errors" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // reservedKeys is the closed set from rule 5; it must match production order-independently. @@ -43,24 +45,22 @@ func TestReservedKeysRejectedInEverySidecarLayer(t *testing.T) { // rule 5: pres } body, _, err := Encode(rt, req, runtimeExtras) // must reject before producing a body. - if err == nil { // no error at all is the failure mode under test. - t.Fatalf("reserved key %q in layer %s was not rejected (body=%s)", key, tc.name, string(body)) - } + // no error at all is the failure mode under test. + testassert.Require(t, err != nil, "reserved key %q in layer %s was not rejected (body=%s)", key, tc.name, string(body)) var reserved *ReservedKeyError // typed identity with exactly this one offending key. - if !errors.As(err, &reserved) || !errors.Is(err, ErrReservedKeys) { - t.Fatalf("error is not the typed/sentinel reserved-key error: %v", err) - } - if len(reserved.Keys) != 1 || reserved.Keys[0] != key { // one and only this key. - t.Fatalf("keys = %#v want [%s]", reserved.Keys, key) - } + testassert.Require(t, errors.As(err, &reserved) && errors.Is(err, ErrReservedKeys), "error is not the typed/sentinel reserved-key error: %v", err) + // one and only this key. + testassert.Require(t, len(reserved.Keys) == 1 && reserved.Keys[0] == key, "keys = %#v want [%s]", reserved.Keys, key) }) } } - forgedValue[1] = 'X' // corrupt the shared fixture after all subtests; none may have aliased it into retained state. - if _, _, err := Encode(testResolved(), req, nil); err != nil { // a clean encode still succeeds afterwards (no cross-call state). - t.Fatalf("clean encode failed after rejection storm: %v", err) + forgedValue[1] = 'X' // corrupt the shared fixture after all subtests; none may have aliased it into retained state. + // a clean encode still succeeds afterwards (no cross-call state). + { + _, _, err := Encode(testResolved(), req, nil) + testassert.NoError(t, err) } } @@ -76,40 +76,34 @@ func TestReservedErrorIdentifiesEveryPresentKey(t *testing.T) { // distinct keys body, _, err := Encode(rt, req, runtimeExtras) // one error identifies every present reserved key exactly once each (set semantics). var reserved *ReservedKeyError // typed shape carries the full distinct set. - if !errors.As(err, &reserved) || !errors.Is(err, ErrReservedKeys) { - t.Fatalf("expected one typed reserved-key error, got %v (body=%s)", err, string(body)) - } + testassert.Require(t, errors.As(err, &reserved) && errors.Is(err, ErrReservedKeys), "expected one typed reserved-key error, got %v (body=%s)", err, string(body)) got := map[string]int{} // duplicates must collapse; every distinct present key appears. for _, k := range reserved.Keys { - if got[k]++; got[k] > 1 { - t.Fatalf("key %q reported more than once: %#v", k, reserved.Keys) - } + got[k]++ + testassert.Require(t, got[k] <= 1, "key %q reported more than once: %#v", k, reserved.Keys) } wantSet := map[string]bool{"n": true, "stream_options": true, "model": true, "max_tokens": true} // four distinct keys across three layers. - if len(reserved.Keys) != len(wantSet) { // absent keys (messages/tools/tool_choice/stream/max_completion_tokens) are not reported. - t.Fatalf("keys = %#v want exactly %d distinct present reserved keys", reserved.Keys, len(wantSet)) - } + // absent keys (messages/tools/tool_choice/stream/max_completion_tokens) are not reported. + testassert.Require(t, len(reserved.Keys) == len(wantSet), "keys = %#v want exactly %d distinct present reserved keys", reserved.Keys, len(wantSet)) for k := range got { // every reported key is one of the four expected ones. - if !wantSet[k] { - t.Fatalf("unexpected key %q in error: %#v", k, reserved.Keys) - } + testassert.Require(t, wantSet[k], "unexpected key %q in error: %#v", k, reserved.Keys) } - msg := err.Error() // text identifies each present key (order itself is diagnostic and not pinned). - if prefix := "reserved keys in extra body: "; !strings.HasPrefix(msg, prefix) { // THE retained legacy rendering pinned exactly: unreserved prefix plus bare key names, never the sentinel's own text as a prefix and never quoted keys. - t.Fatalf("error text lost the retained legacy prefix %q: %s", prefix, msg) + msg := err.Error() // text identifies each present key (order itself is diagnostic and not pinned). + // THE retained legacy rendering pinned exactly: unreserved prefix plus bare key names, never the sentinel's own text as a prefix and never quoted keys. + { + prefix := "reserved keys in extra body: " + testassert.Require(t, strings.HasPrefix(msg, prefix), "error text lost the retained legacy prefix %q: %s", prefix, msg) } for _, k := range []string{"n", "stream_options", "model", "max_tokens"} { - if !strings.Contains(msg, k) { // each present key appears bare in the joined list (order between keys is diagnostic and not pinned). - t.Fatalf("error text does not identify %q: %s", k, msg) - } + // each present key appears bare in the joined list (order between keys is diagnostic and not pinned). + testassert.Require(t, strings.Contains(msg, k), "error text does not identify %q: %s", k, msg) } - if len(body) != 0 { // a rejected encode returns no body bytes (diagnostics and outcome are separate from rejection). - t.Fatalf("rejection produced a partial body: %s", string(body)) - } + // a rejected encode returns no body bytes (diagnostics and outcome are separate from rejection). + testassert.Require(t, len(body) == 0, "rejection produced a partial body: %s", string(body)) } // TestReservedKeysIdentifiedDespiteMalformedValues pins rule 5's identification guarantee when any layer also carries an invalid JSON value: one presence-only reserved-key pass across all three layers runs before raw-value validation, so malformed values cannot hide present reserved keys. @@ -123,12 +117,9 @@ func TestReservedKeysIdentifiedDespiteMalformedValues(t *testing.T) { body, _, err := Encode(rt, req, runtimeExtras) // rejection names the present reserved key even though another layer fails raw-value validation too. var reserved *ReservedKeyError // typed shape carries exactly the distinct present keys. - if !errors.As(err, &reserved) || !errors.Is(err, ErrReservedKeys) { - t.Fatalf("want typed reserved-key error despite the malformed sibling value, got %v (body=%s)", err, string(body)) - } - if len(reserved.Keys) != 1 || reserved.Keys[0] != "max_tokens" { // one and only this key; the malformed non-reserved entry is not a reservation. - t.Fatalf("keys = %#v want [max_tokens]", reserved.Keys) - } + testassert.Require(t, errors.As(err, &reserved) && errors.Is(err, ErrReservedKeys), "want typed reserved-key error despite the malformed sibling value, got %v (body=%s)", err, string(body)) + // one and only this key; the malformed non-reserved entry is not a reservation. + testassert.Require(t, len(reserved.Keys) == 1 && reserved.Keys[0] == "max_tokens", "keys = %#v want [max_tokens]", reserved.Keys) }) t.Run("malformed-reserved-value-plus-second-layer-key", func(t *testing.T) { @@ -138,23 +129,16 @@ func TestReservedKeysIdentifiedDespiteMalformedValues(t *testing.T) { body, _, err := Encode(rt, req, runtimeExtras) // both present keys identified in one pass before any raw-value validation runs. var reserved *ReservedKeyError // typed shape carries the full distinct set regardless of value validity. - if !errors.As(err, &reserved) || !errors.Is(err, ErrReservedKeys) { - t.Fatalf("want typed reserved-key error despite the malformed reserved value, got %v (body=%s)", err, string(body)) - } + testassert.Require(t, errors.As(err, &reserved) && errors.Is(err, ErrReservedKeys), "want typed reserved-key error despite the malformed reserved value, got %v (body=%s)", err, string(body)) got := map[string]int{} // set comparison with a duplicate check; key order is diagnostic formatting only. for _, k := range reserved.Keys { - if got[k]++; got[k] > 1 { - t.Fatalf("key %q reported more than once: %#v", k, reserved.Keys) - } + got[k]++ + testassert.Require(t, got[k] <= 1, "key %q reported more than once: %#v", k, reserved.Keys) } wantSet := map[string]bool{"stream_options": true, "n": true} // exactly the two present keys; nothing else. - if len(got) != len(wantSet) { - t.Fatalf("keys = %#v want exactly %d distinct present reserved keys", reserved.Keys, len(wantSet)) - } + testassert.Require(t, len(got) == len(wantSet), "keys = %#v want exactly %d distinct present reserved keys", reserved.Keys, len(wantSet)) for k := range got { - if !wantSet[k] { - t.Fatalf("unexpected key %q in error: %#v", k, reserved.Keys) - } + testassert.Require(t, wantSet[k], "unexpected key %q in error: %#v", k, reserved.Keys) } }) } @@ -166,29 +150,19 @@ func TestReservedKeysIdentifiedDespiteMalformedValues(t *testing.T) { func TestReservedBodyKeysReturnsOwnedCopyInFixedOrder(t *testing.T) { want := []string{"model", "messages", "tools", "tool_choice", "stream", "stream_options", "max_tokens", "max_completion_tokens", "n"} got := ReservedBodyKeys() - if len(got) != len(want) { - t.Fatalf("ReservedBodyKeys() = %#v, want %d keys", got, len(want)) - } + testassert.Require(t, len(got) == len(want), "ReservedBodyKeys() = %#v, want %d keys", got, len(want)) for i := range want { - if got[i] != want[i] { - t.Fatalf("ReservedBodyKeys()[%d] = %q, want %q (exact order)", i, got[i], want[i]) - } + testassert.Require(t, got[i] == want[i], "ReservedBodyKeys()[%d] = %q, want %q (exact order)", i, got[i], want[i]) } got[0] = "temperature" // the returned slice is the caller's to modify. again := ReservedBodyKeys() - if len(again) != len(want) || again[0] != want[0] { - t.Fatalf("ReservedBodyKeys() after caller mutation = %#v, want a fresh %#v", again, want) - } + testassert.Require(t, len(again) == len(want) && again[0] == want[0], "ReservedBodyKeys() after caller mutation = %#v, want a fresh %#v", again, want) rt := testResolved() // the key the caller renamed in its copy still rejects at the wire boundary. rt.ProviderExtraBody = Extra{"model": json.RawMessage(`"forged"`)} body, _, err := Encode(rt, Request{Messages: []Message{userText("hi")}}, nil) var reserved *ReservedKeyError - if !errors.As(err, &reserved) || !errors.Is(err, ErrReservedKeys) || len(body) != 0 { - t.Fatalf("encode after caller mutation = (%s, %v), want the typed rejection with no body", string(body), err) - } - if len(reserved.Keys) != 1 || reserved.Keys[0] != "model" { - t.Fatalf("keys = %#v, want [model]", reserved.Keys) - } + testassert.Require(t, errors.As(err, &reserved) && errors.Is(err, ErrReservedKeys) && len(body) == 0, "encode after caller mutation = (%s, %v), want the typed rejection with no body", string(body), err) + testassert.Require(t, len(reserved.Keys) == 1 && reserved.Keys[0] == "model", "keys = %#v, want [model]", reserved.Keys) } diff --git a/model/encode_sidecar_test.go b/model/encode_sidecar_test.go index 774c26bd..5e6098f1 100644 --- a/model/encode_sidecar_test.go +++ b/model/encode_sidecar_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestSidecarLayersShallowMergeInOrder pins rule 6: provider, then model, then runtime extra-body layers shallow-merge over the base in that order with later winning per key; values pass through byte-exact from whichever layer won them. (Reserved keys can never reach this point — their presence rejects encoding first, pinned by TestReservedKeys*.) @@ -16,9 +18,7 @@ func TestSidecarLayersShallowMergeInOrder(t *testing.T) { "temperature": json.RawMessage(`3`), "frequency_penalty": json.RawMessage(`0.25`)} body, _, err := Encode(rtWithLayers(rt, providerLayer, modelLayer), Request{Messages: []Message{userText("hi")}}, runtimeExtras) - if err != nil { - t.Fatalf("Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) obj := decodeObject(t, body) @@ -26,23 +26,21 @@ func TestSidecarLayersShallowMergeInOrder(t *testing.T) { "temperature": `3`, "top_p": `0.5`, "frequency_penalty": `0.25`} for k, v := range want { - if got, ok := obj[k]; !ok || string(got) != v { - t.Fatalf("body[%s] = %v (present=%v), want exact value %q", k, got, ok, v) + { + got, ok := obj[k] + testassert.Require(t, ok && string(got) == v, "body[%s] = %v (present=%v), want exact value %q", k, got, ok, v) } } body2, _, err := Encode(rtWithLayers(rt, Extra{"temperature": json.RawMessage(`1`)}, Extra{"temperature": json.RawMessage(`2`)}), Request{Messages: []Message{userText("hi")}}, nil) - if err != nil { - t.Fatalf("second Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) obj2 := decodeObject(t, body2) - if string(obj2["temperature"]) != `2` { - t.Fatalf("model layer failed to beat provider layer: temperature = %s, want 2", obj2["temperature"]) - } + testassert.Require(t, string(obj2["temperature"]) == `2`, "model layer failed to beat provider layer: temperature = %s, want 2", obj2["temperature"]) - if _, ok := obj2["top_p"]; ok { - t.Fatalf("second body unexpectedly carries top_p from a different fixture: %s", string(body2)) + { + _, ok := obj2["top_p"] + testassert.Require(t, !ok, "second body unexpectedly carries top_p from a different fixture: %s", string(body2)) } } @@ -56,24 +54,14 @@ func TestEncodeCallerImmutability(t *testing.T) { rt.Headers = map[string]string{"X-Shared": "original-header-value"} partExtra := Extra{"part_shared": sharedRuntime} reqMsg, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "body", Extra: partExtra}}}) - if err != nil { - t.Fatalf("NewMessage(immuability fixture): %v", err) - } + testassert.NoError(t, err) req := Request{Messages: []Message{reqMsg}} body1, _, err := Encode(rt, req, map[string]json.RawMessage{"rt_marker": sharedRuntime}) - if err != nil { - t.Fatalf("Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) - if !strings.Contains(string(body1), `"rt_marker":"runtime-shared-value-1"`) { - t.Fatalf("first encode lost expected runtime marker bytes (pre-mutation baseline broken): %s", string(body1)) - } - - if !strings.Contains(string(body1), `"prov_marker":"provider-shared-value-1"`) { - t.Fatalf("first encode lost expected provider marker bytes: %s", string(body1)) - } + testassert.Require(t, strings.Contains(string(body1), `"rt_marker":"runtime-shared-value-1"`) && strings.Contains(string(body1), `"prov_marker":"provider-shared-value-1"`), "first encode lost expected runtime marker bytes (pre-mutation baseline broken): %s; first encode lost expected provider marker bytes: %s", string(body1), string(body1)) copy(sharedRuntime, []byte(`"CORRUPTED--RUNTIME-XXX"`)) copy(sharedProvider, []byte(`"CORRUPTED--PROVIDER-XXX"`)) // same exact-length discipline at provider scope too (inner text must match its original 23-char length for the identical reason above). @@ -81,25 +69,13 @@ func TestEncodeCallerImmutability(t *testing.T) { rt.Headers["X-Shared"] = "mutated-header-value" reqMsg.Content[0].Extra["part_shared"] = json.RawMessage(`"CORRUPTED-PART-XX"`) body2, _, err := Encode(rt, req, map[string]json.RawMessage{"rt_marker": sharedRuntime}) - if err != nil { - t.Fatalf("second Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) - if !strings.Contains(string(body2), `"rt_marker":"CORRUPTED--RUNTIME-XXX"`) { - t.Fatalf("second encode did not reflect mutated runtime input: %s", string(body2)) - } + testassert.Require(t, strings.Contains(string(body2), `"rt_marker":"CORRUPTED--RUNTIME-XXX"`), "second encode did not reflect mutated runtime input: %s", string(body2)) - if strings.Contains(string(body1), "CORRUPTED") { - t.Fatalf("previously returned body was corrupted by post-call input mutation: %s", string(body1)) - } + testassert.Require(t, !strings.Contains(string(body1), "CORRUPTED") && strings.Contains(string(body1), `"part_shared":"runtime-shared-value-1"`), "previously returned body was corrupted by post-call input mutation: %s; previously returned body lost its part-scope shared bytes after mutation: %s", string(body1), string(body1)) - if !strings.Contains(string(body1), `"part_shared":"runtime-shared-value-1"`) { - t.Fatalf("previously returned body lost its part-scope shared bytes after mutation: %s", string(body1)) - } - - if !strings.Contains(string(body2), `"part_shared":"CORRUPTED-PART-XX"`) { - t.Fatalf("second encode did not reflect replaced part-scope extra value: %s", string(body2)) - } + testassert.Require(t, strings.Contains(string(body2), `"part_shared":"CORRUPTED-PART-XX"`), "second encode did not reflect replaced part-scope extra value: %s", string(body2)) } // TestOutputTokenFieldsNeverEmitted pins rule 4: neither max_tokens nor max_completion_tokens may appear in any successfully encoded body under every tools/usage trigger combination, checked as exact decoded top-level keys (never substring scans) so unrelated extra names cannot mask a leak. @@ -108,9 +84,7 @@ func TestOutputTokenFieldsNeverEmitted(t *testing.T) { withTools := Request{Messages: []Message{userText("go")}, Tools: []ToolDefinition{{Name: "noop", Description: "", Parameters: json.RawMessage(`{}`)}}} // minimal valid tool definition (object parameters per the constructor contract; parameter-shape rows are Commit 1's scope, not this encoder test's). plainReq, err := NewRequest(withTools) - if err != nil { - t.Fatalf("NewRequest(output-token fixture): %v", err) - } + testassert.NoError(t, err) cases := []struct { name string @@ -132,14 +106,13 @@ func TestOutputTokenFieldsNeverEmitted(t *testing.T) { } body, _, err := Encode(rowRt, rowReq, nil) - if err != nil { - t.Fatalf("Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) obj := decodeObject(t, body) for _, key := range []string{"max_tokens", "max_completion_tokens"} { - if _, ok := obj[key]; ok { - t.Fatalf("body carries forbidden output-token key %q with triggers {tools:%v usage:%v}: %s", key, tc.useTools, tc.streamedUsage, string(body)) + { + _, ok := obj[key] + testassert.Require(t, !ok, "body carries forbidden output-token key %q with triggers {tools:%v usage:%v}: %s", key, tc.useTools, tc.streamedUsage, string(body)) } } }) @@ -167,18 +140,16 @@ func TestSidecarLayersNeverEmitPrivateKeys(t *testing.T) { } body, _, err := Encode(rt, req, nil) // the private key must be dropped on its way in; nothing here may fail for it. - if err != nil { - t.Fatalf("Encode returned unexpected error for layer %s: %v", tc.name, err) - } + testassert.Require(t, err == nil, "Encode returned unexpected error for layer %s: %v", tc.name, err) obj := decodeObject(t, body) for k := range obj { // no private field of any spelling reaches the wire from this scope. - if strings.HasPrefix(k, "_lightcode_") { - t.Fatalf("body carries private key %q from layer %s: %s", k, tc.name, string(body)) - } + testassert.Require(t, !strings.HasPrefix(k, "_lightcode_"), "body carries private key %q from layer %s: %s", k, tc.name, string(body)) } - if got := obj[tc.wantKey]; len(got) == 0 || string(got) == "" { // the sibling merge must survive stripping exactly as usual. - t.Fatalf("sibling key %q lost while stripping private fields (layer %s): %s", tc.wantKey, tc.name, string(body)) + // the sibling merge must survive stripping exactly as usual. + { + got := obj[tc.wantKey] + testassert.Require(t, len(got) != 0 && string(got) != "", "sibling key %q lost while stripping private fields (layer %s): %s", tc.wantKey, tc.name, string(body)) } }) } @@ -191,18 +162,15 @@ func TestSidecarLayersNeverEmitPrivateKeys(t *testing.T) { } body, _, err := Encode(rt, req, runtimeExtras) // same expectation as every other scope: dropped on entry, no error for it specifically. - if err != nil { - t.Fatalf("Encode returned unexpected error for layer runtime: %v", err) - } + testassert.NoError(t, err) obj := decodeObject(t, body) for k := range obj { // the private floor applies at top level exactly like in message scopes (rule 9 shared). - if strings.HasPrefix(k, "_lightcode_") { - t.Fatalf("body carries private key %q from layer runtime: %s", k, string(body)) - } + testassert.Require(t, !strings.HasPrefix(k, "_lightcode_"), "body carries private key %q from layer runtime: %s", k, string(body)) } - if got := obj["frequency_penalty"]; len(got) == 0 || string(got) != `0.25` { - t.Fatalf("runtime sibling merge disturbed by private stripping: frequency_penalty = %v (body=%s)", obj["frequency_penalty"], string(body)) + { + got := obj["frequency_penalty"] + testassert.Require(t, len(got) != 0 && string(got) == `0.25`, "runtime sibling merge disturbed by private stripping: frequency_penalty = %v (body=%s)", obj["frequency_penalty"], string(body)) } }) @@ -213,15 +181,11 @@ func TestSidecarLayersNeverEmitPrivateKeys(t *testing.T) { runtimeExtras := map[string]json.RawMessage{"_lightcode_c": json.RawMessage(`3`)} body, _, err := Encode(rt, req, runtimeExtras) // all three must vanish; a valid encode with nothing else to say about them. - if err != nil { - t.Fatalf("Encode returned unexpected error for the combined layer set: %v", err) - } + testassert.NoError(t, err) obj := decodeObject(t, body) for k := range obj { // absence across every scope is what this row exists to pin in one shot. - if strings.HasPrefix(k, "_lightcode_") { - t.Fatalf("body carries private key %q: %s", k, string(body)) - } + testassert.Require(t, !strings.HasPrefix(k, "_lightcode_"), "body carries private key %q: %s", k, string(body)) } }) } @@ -260,25 +224,28 @@ func TestCloneResolvedInputClonesEvenWhenEmpty(t *testing.T) { t.Fatalf("cloned Headers not empty: %#v (input was non-nil but had no entries at clone time)", out.Headers) // only the pre-clone contents belong in an independent copy of this input shape. } - if _, ok := out.Drop["k-late"]; ok || len(out.Drop) != 0 { - t.Fatalf("retained Drop aliases caller storage or gained a late key: %#v", out.Drop) // one check covers both the leak (key present) and any phantom pre-clone content for this fixture. + // one check covers both the leak (key present) and any phantom pre-clone content for this fixture. + { + _, ok := out.Drop["k-late"] + testassert.Require(t, !ok && len(out.Drop) == 0, "retained Drop aliases caller storage or gained a late key: %#v", out.Drop) } - if _, ok := out.SourceFamilies[ModelRef{Provider: "p", Model: "m"}]; ok || len(out.SourceFamilies) != 0 { - t.Fatalf("retained SourceFamilies alias caller storage or gained a late entry: %#v", out.SourceFamilies) // same combined assertion as Drop for the family table scope. + // same combined assertion as Drop for the family table scope. + { + _, ok := out.SourceFamilies[ModelRef{Provider: "p", Model: "m"}] + testassert.Require(t, !ok && len(out.SourceFamilies) == 0, "retained SourceFamilies alias caller storage or gained a late entry: %#v", out.SourceFamilies) } - if len(out.MustPreserve) != 0 { // the caller's slice really did grow to one entry by now, so any length visible in the clone means a shared header. - t.Fatalf("retained MustPreserve aliases caller storage: post-clone append visible (len=%d)", len(out.MustPreserve)) // no independent copy can report an appended element — its own head was copied while still empty. - } + // the caller's slice really did grow to one entry by now, so any length visible in the clone means a shared header. + // no independent copy can report an appended element — its own head was copied while still empty. + testassert.Equal(t, len(out.MustPreserve), 0) for _, scope := range []struct { name string layer Extra }{{"ProviderExtraBody", out.ProviderExtraBody}, {"ModelExtraBody", out.ModelExtraBody}} { // retained extra scopes must be empty (or nil) and never see the late caller insertions above in either direction of aliasing. - if len(scope.layer) != 0 { - t.Fatalf("retained %s observed post-clone caller insertion or pre-ghost content: %#v", scope.name, scope.layer) // any entry here means this field's clone state shares storage with the fixture maps above — exactly what the clone-on-entry ownership rule forbids. - } + // any entry here means this field's clone state shares storage with the fixture maps above — exactly what the clone-on-entry ownership rule forbids. + testassert.Require(t, len(scope.layer) == 0, "retained %s observed post-clone caller insertion or pre-ghost content: %#v", scope.name, scope.layer) } } diff --git a/model/encode_streamopts_test.go b/model/encode_streamopts_test.go index 0237d1eb..917c89ba 100644 --- a/model/encode_streamopts_test.go +++ b/model/encode_streamopts_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestEncodeStreamOptionsPinnedByFlagOnly pins rule 3: stream_options appears exactly when the resolved flag is true, carrying precisely {"include_usage":true} and nothing else. Zero-value and explicit-false flags both omit it entirely; message count never changes its shape or occurrence. Forged sidecar copies of the key are reserved-key rejections (covered by TestReservedKeys*), not a way to turn this on. @@ -11,53 +13,44 @@ func TestEncodeStreamOptionsPinnedByFlagOnly(t *testing.T) { req := Request{Messages: []Message{userText("hi")}} // no usage flag set in the baseline resolved input. bodyOff, _, err := Encode(testResolved(), req, nil) // false -> absent from the body entirely. - if err != nil { - t.Fatalf("Encode returned error: %v", err) - } - if strings.Contains(string(bodyOff), `"stream_options"`) { - t.Fatal(`flag false must not emit stream_options: ` + string(bodyOff)) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(bodyOff), `"stream_options"`), "%v", `flag false must not emit stream_options: `+string(bodyOff)) rtOn := testResolved() // the resolved flag is what turns it on. rtOn.StreamedUsage = true bodyOn, _, err := Encode(rtOn, req, nil) - if err != nil { - t.Fatalf("Encode returned error: %v", err) - } + testassert.NoError(t, err) var opts map[string]json.RawMessage // exactly include_usage:true and nothing else may appear inside. rawOpts := bodyField(t, bodyOn, "stream_options") - if err := json.Unmarshal(rawOpts, &opts); err != nil || len(opts) != 1 { - t.Fatalf("stream_options wrong: %s", rawOpts) + { + err := json.Unmarshal(rawOpts, &opts) + testassert.Require(t, err == nil && len(opts) == 1, "stream_options wrong: %s", rawOpts) } - if rawInclude, ok := opts["include_usage"]; !ok || strings.TrimSpace(string(rawInclude)) != "true" { - t.Fatalf(`stream_options = %q want {"include_usage":true}`, string(rawOpts)) + { + rawInclude, ok := opts["include_usage"] + testassert.Require(t, ok && strings.TrimSpace(string(rawInclude)) == "true", `stream_options = %q want {"include_usage":true}`, string(rawOpts)) } bodyZero := mustEncode(t, testResolved(), Request{}) // zero-value flag with an empty request still omits the key. - if strings.Contains(string(bodyZero), `"stream_options"`) { - t.Fatal(`zero-value flag emitted stream_options: ` + string(bodyZero)) - } + testassert.Require(t, !strings.Contains(string(bodyZero), `"stream_options"`), "%v", `zero-value flag emitted stream_options: `+string(bodyZero)) rtManyOn := testResolved() // flag true across multiple messages emits exactly one occurrence of the same object. rtManyOn.StreamedUsage = true bodyMulti, _, err := Encode(rtManyOn, Request{Messages: []Message{userText("a"), userText("b")}}, nil) - if err != nil { - t.Fatalf("Encode returned error: %v", err) - } + testassert.NoError(t, err) assertStreamOptionsOnce(t, bodyMulti) rtEmptyOn := testResolved() // flag true with zero messages emits the same single object too. rtEmptyOn.StreamedUsage = true bodyEmpty, _, err := Encode(rtEmptyOn, Request{}, nil) - if err != nil { - t.Fatalf("Encode returned error: %v", err) - } + testassert.NoError(t, err) assertStreamOptionsOnce(t, bodyEmpty) objOffKeys := decodeObject(t, bodyOff) // base keys stay present regardless of the flag (regression guard against accidental overwrites). for _, key := range []string{"model", "messages", "stream", "n"} { - if _, ok := objOffKeys[key]; !ok { - t.Fatalf("flag-off encode lost a base key %q: %s", key, string(bodyOff)) + { + _, ok := objOffKeys[key] + testassert.Require(t, ok, "flag-off encode lost a base key %q: %s", key, string(bodyOff)) } } } @@ -65,15 +58,18 @@ func TestEncodeStreamOptionsPinnedByFlagOnly(t *testing.T) { // assertStreamOptionsOnce decodes the body and requires exactly one stream_options object equal to {"include_usage":true}. func assertStreamOptionsOnce(t *testing.T, body json.RawMessage) { t.Helper() // occurrence count plus exact shape in one check. - if got := strings.Count(string(body), `"stream_options"`); got != 1 { - t.Fatalf(`body carries %d stream_options keys (want exactly 1): %s`, got, string(body)) + { + got := strings.Count(string(body), `"stream_options"`) + testassert.Require(t, got == 1, `body carries %d stream_options keys (want exactly 1): %s`, got, string(body)) } var opts map[string]json.RawMessage // the single occurrence must be precisely include_usage:true. rawOpts := bodyField(t, body, "stream_options") - if err := json.Unmarshal(rawOpts, &opts); err != nil || len(opts) != 1 { - t.Fatalf("stream_options wrong: %s", rawOpts) + { + err := json.Unmarshal(rawOpts, &opts) + testassert.Require(t, err == nil && len(opts) == 1, "stream_options wrong: %s", rawOpts) } - if rawInclude, ok := opts["include_usage"]; !ok || strings.TrimSpace(string(rawInclude)) != "true" { - t.Fatalf(`stream_options = %q want {"include_usage":true}`, string(rawOpts)) + { + rawInclude, ok := opts["include_usage"] + testassert.Require(t, ok && strings.TrimSpace(string(rawInclude)) == "true", `stream_options = %q want {"include_usage":true}`, string(rawOpts)) } } diff --git a/model/encode_systemrole_warnings_test.go b/model/encode_systemrole_warnings_test.go index 07fb5c39..3c44a80b 100644 --- a/model/encode_systemrole_warnings_test.go +++ b/model/encode_systemrole_warnings_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestWireSystemRoleMapping pins rule 7: canonical system messages encode with the resolved wire role exactly; every other canonical role is unchanged no matter what that value is. Empty resolves to "system"; any non-empty value outside {system, user, developer} rejects encoding before producing a body (ErrInvalidWireSystemRole). @@ -28,21 +30,22 @@ func TestWireSystemRoleMapping(t *testing.T) { req := Request{Messages: []Message{sys, userText("hello"), mustMsg(t, Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "hi"}}})}} body, _, err := Encode(rt, req, nil) - if err != nil { - t.Fatalf("Encode returned unexpected error for wire role %q: %v", tc.role, err) - } + testassert.Require(t, err == nil, "Encode returned unexpected error for wire role %q: %v", tc.role, err) msgs := decodeMessages(t, body) - if raw, ok := wireString(t, msgs[0], "role"); !ok || raw != tc.want { - t.Fatalf("system wire role = %q, want %q", msgs[0]["role"], tc.want) + { + raw, ok := wireString(t, msgs[0], "role") + testassert.Require(t, ok && raw == tc.want, "system wire role = %q, want %q", msgs[0]["role"], tc.want) } - if raw, ok := wireString(t, msgs[1], "role"); !ok || raw != "user" { - t.Fatalf("user wire role = %q, want unchanged user", msgs[1]["role"]) + { + raw, ok := wireString(t, msgs[1], "role") + testassert.Require(t, ok && raw == "user", "user wire role = %q, want unchanged user", msgs[1]["role"]) } - if raw, ok := wireString(t, msgs[2], "role"); !ok || raw != "assistant" { - t.Fatalf("assistant wire role = %q, want unchanged assistant", msgs[2]["role"]) + { + raw, ok := wireString(t, msgs[2], "role") + testassert.Require(t, ok && raw == "assistant", "assistant wire role = %q, want unchanged assistant", msgs[2]["role"]) } }) } @@ -52,12 +55,7 @@ func TestWireSystemRoleMapping(t *testing.T) { rt := testResolved() rt.WireSystemRole = bad _, _, err := Encode(rt, Request{Messages: []Message{sys}}, nil) - if err == nil { - t.Fatalf("Encode accepted invalid wire role %q", bad) - } - if !errors.Is(err, ErrInvalidWireSystemRole) { - t.Fatalf("error %v does not wrap ErrInvalidWireSystemRole", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrInvalidWireSystemRole), "Encode accepted invalid wire role %q; error %v does not wrap ErrInvalidWireSystemRole", bad, err) } }) } @@ -68,9 +66,7 @@ func TestProtocolWarningsOrderAndSuppression(t *testing.T) { rt.MustPreserve = []string{"preserve_a", "preserve_b"} mkAssistant := func(source ModelRef, extraKeys ...string) Message { call, err := NewToolCall(ToolCall{ID: "c-warn", Name: "lookup", Arguments: json.RawMessage(`{}`)}) // completed-call boundary clones argument bytes like production paths do. - if err != nil { - t.Fatalf("NewToolCall(warning fixture call): %v", err) - } + testassert.NoError(t, err) msg := Message{Role: RoleAssistant, Source: source, Content: []ContentPart{{Kind: PartText, Text: "x"}}, ToolCalls: []ToolCall{call}} // assistant carries a completed tool call so rule 12's must-preserve branch applies to this fixture. if len(extraKeys) > 0 { // fresh Message has no Extra map; writing into an absent one panics and NewMessage does not pre-allocate it here. msg.Extra = Extra{} @@ -91,22 +87,14 @@ func TestProtocolWarningsOrderAndSuppression(t *testing.T) { mkAssistant(foreignModel, "preserve_a", "preserve_b")} req, err := NewRequest(Request{Messages: msgs}) - if err != nil { - t.Fatalf("NewRequest(warning fixtures): %v", err) - } + testassert.NoError(t, err) body, warnings, err := Encode(rt, req, nil) - if err != nil { - t.Fatalf("Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) - if body == nil { - t.Fatalf("Encode returned nil body on success path (warnings=%d)", len(warnings)) - } + testassert.Require(t, body != nil, "Encode returned nil body on success path (warnings=%d)", len(warnings)) - if len(warnings) != 3 { - t.Fatalf("got %d warnings, want exactly 3: %#v", len(warnings), warnings) - } + testassert.Require(t, len(warnings) == 3, "got %d warnings, want exactly 3: %#v", len(warnings), warnings) type expectedWarning struct { field string @@ -117,37 +105,23 @@ func TestProtocolWarningsOrderAndSuppression(t *testing.T) { {field: "preserve_b", idx: 0}, {field: "preserve_a", idx: 1}, {field: "preserve_b", idx: 1}} for i, w := range warnings { - if w.Kind != WarningMustPreserveMissing { - t.Fatalf("warnings[%d].Kind = %q, want %q", i, w.Kind, WarningMustPreserveMissing) - } + testassert.Require(t, w.Kind == WarningMustPreserveMissing, "warnings[%d].Kind = %q, want %q", i, w.Kind, WarningMustPreserveMissing) if w.Target != rt.Model { t.Fatalf("warnings[%d].Target = %s, want %s", i, w.Target.String(), rt.Model.String()) } - if w.Field != want[i].field || w.MessageIndex != want[i].idx { - t.Fatalf("warnings[%d] = {field:%q idx:%d}, want {field:%q idx:%d}", i, w.Field, w.MessageIndex, want[i].field, want[i].idx) - } - - if w.MessageIndex < 0 { - t.Fatalf("warnings[%d].MessageIndex negative: %d", i, w.MessageIndex) - } + testassert.Require(t, w.Field == want[i].field && w.MessageIndex == want[i].idx && w.MessageIndex >= 0, "warnings[%d] = {field:%q idx:%d}, want {field:%q idx:%d}; warnings[%d].MessageIndex negative: %d", i, w.Field, w.MessageIndex, want[i].field, want[i].idx, i, w.MessageIndex) } t.Run("no-warnings-when-mustpreserve-unset", func(t *testing.T) { rt2 := testResolved() reqNoWarnings, err := NewRequest(Request{Messages: []Message{mkAssistant(targetRef)}}) - if err != nil { - t.Fatalf("NewRequest(no-warning fixture): %v", err) - } + testassert.NoError(t, err) _, warnings2, err := Encode(rt2, reqNoWarnings, nil) - if err != nil { - t.Fatalf("Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) - if len(warnings2) != 0 { - t.Fatalf("got %d warnings with unset must-preserve list, want 0: %#v", len(warnings2), warnings2) - } + testassert.Require(t, len(warnings2) == 0, "got %d warnings with unset must-preserve list, want 0: %#v", len(warnings2), warnings2) }) } diff --git a/model/encode_test.go b/model/encode_test.go index fd7cb0ea..f4653547 100644 --- a/model/encode_test.go +++ b/model/encode_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // testResolved is the valid resolved-transport baseline for encoder tests: target openai/gpt-test. @@ -20,9 +22,7 @@ var ( func mustEncode(t *testing.T, rt ResolvedTransport, req Request) json.RawMessage { t.Helper() // one logical request plus no per-call layer. body, _, err := Encode(rt, req, nil) - if err != nil { - t.Fatalf("Encode returned unexpected error: %v", err) - } + testassert.NoError(t, err) return body } @@ -30,8 +30,9 @@ func mustEncode(t *testing.T, rt ResolvedTransport, req Request) json.RawMessage func decodeObject(t *testing.T, body json.RawMessage) map[string]json.RawMessage { t.Helper() // the top level is always a single object from this encoder. var obj map[string]json.RawMessage - if err := json.Unmarshal(body, &obj); err != nil { - t.Fatalf("encoded body is not a JSON object: %v (body=%s)", err, string(body)) + { + err := json.Unmarshal(body, &obj) + testassert.Require(t, err == nil, "encoded body is not a JSON object: %v (body=%s)", err, string(body)) } return obj } @@ -40,9 +41,7 @@ func bodyField(t *testing.T, body json.RawMessage, key string) []byte { t.Helper() // missing keys are test errors. obj := decodeObject(t, body) raw, ok := obj[key] - if !ok { - t.Fatalf("body missing %q (body=%s)", key, string(body)) - } + testassert.Require(t, ok, "body missing %q (body=%s)", key, string(body)) return raw } @@ -50,8 +49,9 @@ func bodyField(t *testing.T, body json.RawMessage, key string) []byte { func decodeMessages(t *testing.T, body json.RawMessage) []map[string]json.RawMessage { t.Helper() // order is preserved from the request message list. var out []map[string]json.RawMessage - if err := json.Unmarshal(bodyField(t, body, "messages"), &out); err != nil { - t.Fatalf("body messages not an array of objects: %v", err) + { + err := json.Unmarshal(bodyField(t, body, "messages"), &out) + testassert.NoError(t, err) } return out } @@ -63,8 +63,9 @@ func wireString(t *testing.T, obj map[string]json.RawMessage, key string) (strin return "", false } var s string // wrong JSON types are test errors here because the contract fixes these as strings. - if err := json.Unmarshal(raw, &s); err != nil { - t.Fatalf("wire value %q is not a JSON string: %s", key, raw) + { + err := json.Unmarshal(raw, &s) + testassert.Require(t, err == nil, "wire value %q is not a JSON string: %s", key, raw) } return s, true } @@ -72,18 +73,14 @@ func wireString(t *testing.T, obj map[string]json.RawMessage, key string) (strin func mustMsg(t *testing.T, in Message) Message { t.Helper() // canonical fixtures go through the public constructor. out, err := NewMessage(in) - if err != nil { - t.Fatalf("NewMessage failed for fixture role %s: %v", in.Role, err) - } + testassert.Require(t, err == nil, "NewMessage failed for fixture role %s: %v", in.Role, err) return out } func mustCall(t *testing.T, id, name string, args json.RawMessage) ToolCall { t.Helper() // completed call fixtures go through the public constructor. out, err := NewToolCall(ToolCall{ID: id, Name: name, Arguments: args}) - if err != nil { - t.Fatalf("NewToolCall failed for %s/%s: %v", id, name, err) - } + testassert.Require(t, err == nil, "NewToolCall failed for %s/%s: %v", id, name, err) return out } @@ -98,25 +95,26 @@ func TestEncodeBaseBody(t *testing.T) { body := mustEncode(t, testResolved(), req) want := `{"messages":[{"content":"hi","role":"user"}],"model":"gpt-test","n":1,"stream":true}` // exact wire shape including key order (map marshal). - if string(body) != want { - t.Fatalf("base body = %s\nwant %s", string(body), want) - } + testassert.Equal(t, string(body), want) obj := decodeObject(t, body) // none of the optional top-level keys exist. for _, key := range []string{"tools", "tool_choice", "stream_options", "max_tokens", "max_completion_tokens"} { - if _, ok := obj[key]; ok { - t.Fatalf("base body must not carry %q: %s", key, string(body)) + { + _, ok := obj[key] + testassert.Require(t, !ok, "base body must not carry %q: %s", key, string(body)) } } bodyEmpty := mustEncode(t, testResolved(), Request{}) // zero messages and tools. objEmpty := decodeObject(t, bodyEmpty) - if rawMessages, ok := objEmpty["messages"]; !ok || strings.TrimSpace(string(rawMessages)) != "[]" { - t.Fatalf("empty request messages = %q want []", objEmpty["messages"]) + { + rawMessages, ok := objEmpty["messages"] + testassert.Require(t, ok && strings.TrimSpace(string(rawMessages)) == "[]", "empty request messages = %q want []", objEmpty["messages"]) } for _, key := range []string{"tools", "tool_choice"} { // no tools -> neither key. - if _, ok := objEmpty[key]; ok { - t.Fatalf("body without tools carries %q: %s", key, string(bodyEmpty)) + { + _, ok := objEmpty[key] + testassert.Require(t, !ok, "body without tools carries %q: %s", key, string(bodyEmpty)) } } } @@ -127,12 +125,11 @@ func TestEncodeToolsFunctionShape(t *testing.T) { req := Request{Messages: []Message{userText("go")}, Tools: []ToolDefinition{{Name: "read_file", Description: "reads a file at path", Parameters: params}}} body, _, err := Encode(testResolved(), req, nil) // one tool definition. - if err != nil { - t.Fatalf("Encode returned error: %v", err) - } + testassert.NoError(t, err) obj := decodeObject(t, body) - if choice, _ := wireString(t, obj, "tool_choice"); choice != "auto" { - t.Fatalf(`tool_choice = %q want auto when tools are present`, obj["tool_choice"]) + { + choice, _ := wireString(t, obj, "tool_choice") + testassert.Require(t, choice == "auto", `tool_choice = %q want auto when tools are present`, obj["tool_choice"]) } var tools []struct { // decoded into the exact OpenAI function shape. @@ -143,21 +140,22 @@ func TestEncodeToolsFunctionShape(t *testing.T) { Parameters json.RawMessage `json:"parameters"` } `json:"function"` } - if err := json.Unmarshal(bodyField(t, body, "tools"), &tools); err != nil || len(tools) != 1 { - t.Fatalf("tools array wrong: %s", obj["tools"]) + { + err := json.Unmarshal(bodyField(t, body, "tools"), &tools) + testassert.Require(t, err == nil && len(tools) == 1, "tools array wrong: %s", obj["tools"]) } fn := tools[0].Function // every field of the function object is present. - if tools[0].Type != "function" || fn.Name != "read_file" || fn.Description != "reads a file at path" { - t.Fatalf("tool object wrong: %s (want type function with name/description)", obj["tools"]) - } + testassert.Require(t, tools[0].Type == "function" && fn.Name == "read_file" && fn.Description == "reads a file at path", "tool object wrong: %s (want type function with name/description)", obj["tools"]) var emittedParams json.RawMessage // the parameters value itself, not only its surrounding fields. - if err := json.Unmarshal(fn.Parameters, &emittedParams); err != nil || string(emittedParams) != string(params) { - t.Fatalf("parameters = %s want exactly the supplied schema bytes: %s", fn.Parameters, params) + { + err := json.Unmarshal(fn.Parameters, &emittedParams) + testassert.Require(t, err == nil && string(emittedParams) == string(params), "parameters = %s want exactly the supplied schema bytes: %s", fn.Parameters, params) } var paramObj map[string]json.RawMessage // and it must decode as that object value (type/properties intact), not a re-serialized shape. - if err := json.Unmarshal(emittedParams, ¶mObj); err != nil || string(paramObj["type"]) != `"object"` { - t.Fatalf("parameters did not survive as the supplied JSON object: %s", emittedParams) + { + err := json.Unmarshal(emittedParams, ¶mObj) + testassert.Require(t, err == nil && string(paramObj["type"]) == `"object"`, "parameters did not survive as the supplied JSON object: %s", emittedParams) } req.Tools = append(req.Tools, ToolDefinition{Name: "write_file", Description: "", Parameters: json.RawMessage(`{}`)}) // order preserved. @@ -168,11 +166,11 @@ func TestEncodeToolsFunctionShape(t *testing.T) { Name string `json:"name"` } `json:"function"` } - if err := json.Unmarshal(bodyField(t, body2, "tools"), &two); err != nil || len(two) != 2 || two[0].Function.Name != "read_file" || two[1].Function.Name != "write_file" { - t.Fatalf("multiple tools lost their order or shape: %s", obj["tools"]) + { + err := json.Unmarshal(bodyField(t, body2, "tools"), &two) + testassert.Require(t, err == nil && len(two) == 2 && two[0].Function.Name == "read_file" && two[1].Function.Name == "write_file", "multiple tools lost their order or shape: %s", obj["tools"]) } - if strings.Contains(string(body), `"strict":`) { // strict is never emitted by this encoder. - t.Fatalf("body carries a strict field: %s", string(body)) - } + // strict is never emitted by this encoder. + testassert.Require(t, !strings.Contains(string(body), `"strict":`), "body carries a strict field: %s", string(body)) } diff --git a/model/encode_wireshape_test.go b/model/encode_wireshape_test.go index d3613611..4724b89d 100644 --- a/model/encode_wireshape_test.go +++ b/model/encode_wireshape_test.go @@ -3,6 +3,8 @@ package model import ( "encoding/json" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // targetRef is the identity same-source fixtures in this file carry so replay keeps their extras on the wire; it equals testResolved().Model by construction of that helper's baseline. @@ -11,8 +13,9 @@ var targetRef = ModelRef{Provider: "openai", Model: "gpt-test"} func unmarshalContentParts(t *testing.T, obj map[string]json.RawMessage) []map[string]json.RawMessage { // decodes one wire object's content key into ordered raw part objects (array form only; string-form rows use assertUserTextWire instead). t.Helper() // a missing or non-array content key is the failure mode this helper reports, which is exactly what shape-regression rows want surfaced. var parts []map[string]json.RawMessage // raw objects keep every key visible, so forbidden-key leaks are distinguishable from absence (typed decoding would drop unknowns silently). - if err := json.Unmarshal(obj["content"], &parts); err != nil || parts == nil { - t.Fatalf("content not an object array: %s", obj["content"]) + { + err := json.Unmarshal(obj["content"], &parts) + testassert.Require(t, err == nil && parts != nil, "content not an object array: %s", obj["content"]) } return parts // wire order equals request part order by encoder contract; rows below index into it directly. } @@ -20,12 +23,12 @@ func unmarshalContentParts(t *testing.T, obj map[string]json.RawMessage) []map[s func assertStringField(t *testing.T, obj map[string]json.RawMessage, key, want string) { t.Helper() // shared by every row pinning structural kind/identity fields on otherwise-raw objects in this file. rawVal, ok := obj[key] - if !ok { - t.Fatalf("object missing %q: %#v", key, obj) - } + testassert.Require(t, ok, "object missing %q: %#v", key, obj) var got string - if err := json.Unmarshal(rawVal, &got); err != nil || got != want { // type mismatch and value mismatch share one message so the failure class is obvious from output alone. - t.Fatalf("%s = %s (unmarshal error=%v), want %q", key, rawVal, err, want) + // type mismatch and value mismatch share one message so the failure class is obvious from output alone. + { + err := json.Unmarshal(rawVal, &got) + testassert.Require(t, err == nil && got == want, "%s = %s (unmarshal error=%v), want %q", key, rawVal, err, want) } } @@ -33,12 +36,11 @@ func assertStringField(t *testing.T, obj map[string]json.RawMessage, key, want s func assertUserTextWire(t *testing.T, obj map[string]json.RawMessage, text string) { // an object/number here is a shape regression: this helper fails on any non-string content type too. t.Helper() // shared across the wire-shape and scope tests in this file so every call site has identical strength by delegation to one code path. rawContent, ok := obj["content"] - if !ok { - t.Fatalf("object has no content key: %#v", obj) - } + testassert.Require(t, ok, "object has no content key: %#v", obj) var got string - if err := json.Unmarshal(rawContent, &got); err != nil || got != text { - t.Fatalf("content = %s, want plain string %q (unmarshal error=%v)", rawContent, text, err) + { + err := json.Unmarshal(rawContent, &got) + testassert.Require(t, err == nil && got == text, "content = %s, want plain string %q (unmarshal error=%v)", rawContent, text, err) } } @@ -47,47 +49,38 @@ func TestMessageWireShapesPinsCanonicalFields(t *testing.T) { rt := testResolved() assistant, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "ok"}}}) // empty refusal/name must produce neither key on its wire object. - if err != nil { - t.Fatalf("NewMessage(assistant): %v", err) - } + testassert.NoError(t, err) callArgs := json.RawMessage(`{"path":"a/b","n":[1,2]}`) // non-trivial raw bytes: quoting depth and inner commas are both observable on the wire after encoding. called, err := NewToolCall(ToolCall{ID: "call_9", Name: "read_file", Arguments: callArgs}) // completed-call boundary clones argument bytes like production paths do (ownership pinned separately by TestEncodeCallerImmutability). - if err != nil { - t.Fatalf("NewToolCall: %v", err) - } + testassert.NoError(t, err) calledMsg, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "go"}}, ToolCalls: []ToolCall{called}}) // tool-call-only assistant messages are valid by contract. - if err != nil { - t.Fatalf("NewMessage(assistant with call): %v", err) - } + testassert.NoError(t, err) namedResult := mustMsg(t, Message{Role: RoleTool, ToolCallID: "call_9", Name: "read_file", Content: []ContentPart{{Kind: PartText, Text: "file contents"}}}) // tool result carrying both its required call id and optional name. emptyResult := mustMsg(t, Message{Role: RoleTool, ToolCallID: "call_empty"}) refused, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "no"}}, Refusal: "blocked"}) // positive side of the refusal field (message 1 above pins its negative absence). - if err != nil { - t.Fatalf("NewMessage(refusal): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{userText("hi"), assistant, calledMsg, namedResult}}) // four messages in request order; every row below indexes this single body. msgs := decodeMessages(t, body) // count mismatch is a structural failure reported before any field-level rows run. - if len(msgs) != 4 { - t.Fatalf("encoded %d messages, want 4: %s", len(msgs), string(body)) - } + testassert.Require(t, len(msgs) == 4, "encoded %d messages, want 4: %s", len(msgs), string(body)) - assertUserTextWire(t, msgs[0], "hi") // user text encodes as a plain JSON string with nothing else on the object. - if raw, ok := wireString(t, msgs[1], "role"); !ok || raw != "assistant" { // non-system roles pass through unchanged (system mapping is pinned by its dedicated test). - t.Fatalf("message[1] role = %v want assistant", msgs[1]["role"]) + assertUserTextWire(t, msgs[0], "hi") // user text encodes as a plain JSON string with nothing else on the object. + // non-system roles pass through unchanged (system mapping is pinned by its dedicated test). + { + raw, ok := wireString(t, msgs[1], "role") + testassert.Require(t, ok && raw == "assistant", "message[1] role = %v want assistant", msgs[1]["role"]) } for _, key := range []string{"refusal", "name", "tool_call_id"} { // empty optional fields produce no keys at all (not null, not empty string): absence is the pinned behavior. - if _, ok := msgs[1][key]; ok { - t.Fatalf("message[1] must not carry %q: %#v", key, msgs[1]) + { + _, ok := msgs[1][key] + testassert.Require(t, !ok, "message[1] must not carry %q: %#v", key, msgs[1]) } } rawCalls, ok := msgs[2]["tool_calls"] // the whole array is under test at message 2 (count and per-call shape both pinned here). - if !ok { - t.Fatalf("message[2] missing tool_calls: %#v", msgs[2]) - } + testassert.Require(t, ok, "message[2] missing tool_calls: %#v", msgs[2]) var calls []struct { ID string `json:"id"` // typed view for the three canonical fields; raw key-presence checks cover everything else (extras, private keys). Type string `json:"type"` @@ -96,36 +89,47 @@ func TestMessageWireShapesPinsCanonicalFields(t *testing.T) { Arguments json.RawMessage `json:"arguments"` // must decode back to exactly the raw bytes sent in. } `json:"function"` } - if err := json.Unmarshal(rawCalls, &calls); err != nil || len(calls) != 1 { - t.Fatalf("tool_calls wrong: %s", string(rawCalls)) - } - if calls[0].ID != "call_9" || calls[0].Type != "function" || calls[0].Fn.Name != "read_file" { // id present, explicit function type, nested name (no inner type field per the retained shape). - t.Fatalf("call identity/type/name = %+v, want call_9/function/read_file: %s", calls[0], string(rawCalls)) + { + err := json.Unmarshal(rawCalls, &calls) + testassert.Require(t, err == nil && len(calls) == 1, "tool_calls wrong: %s", string(rawCalls)) } + // id present, explicit function type, nested name (no inner type field per the retained shape). + testassert.Require(t, calls[0].ID == "call_9" && calls[0].Type == "function" && calls[0].Fn.Name == "read_file", "call identity/type/name = %+v, want call_9/function/read_file: %s", calls[0], string(rawCalls)) var argsAsString string - if err := json.Unmarshal(calls[0].Fn.Arguments, &argsAsString); err != nil || argsAsString != `{"path":"a/b","n":[1,2]}` { // its unescaped content must equal the exact argument bytes sent in (byte fidelity of the rendering itself, not merely semantic JSON equality). - t.Fatalf("arguments = %s, want exactly the raw-JSON string form of the original argument bytes", calls[0].Fn.Arguments) + // its unescaped content must equal the exact argument bytes sent in (byte fidelity of the rendering itself, not merely semantic JSON equality). + { + err := json.Unmarshal(calls[0].Fn.Arguments, &argsAsString) + testassert.Require(t, err == nil && argsAsString == `{"path":"a/b","n":[1,2]}`, "arguments = %s, want exactly the raw-JSON string form of the original argument bytes", calls[0].Fn.Arguments) } - if rawID, ok := wireString(t, msgs[3], "tool_call_id"); !ok || rawID != "call_9" { // tool results carry the original call id they answer. - t.Fatalf("message[3] tool_call_id = %v want call_9", msgs[3]["tool_call_id"]) + // tool results carry the original call id they answer. + { + rawID, ok := wireString(t, msgs[3], "tool_call_id") + testassert.Require(t, ok && rawID == "call_9", "message[3] tool_call_id = %v want call_9", msgs[3]["tool_call_id"]) } - if rawName, ok := wireString(t, msgs[3], "name"); !ok || rawName != "read_file" { // optional name emits exactly when present. - t.Fatalf("message[3] name = %v want read_file", msgs[3]["name"]) + // optional name emits exactly when present. + { + rawName, ok := wireString(t, msgs[3], "name") + testassert.Require(t, ok && rawName == "read_file", "message[3] name = %v want read_file", msgs[3]["name"]) } - body2 := mustEncode(t, rt, Request{Messages: []Message{refused}}) // separate encode keeps refusal's positive assertion independent from message 1's negative one above. - msgs2 := decodeMessages(t, body2) // exactly one wire object for this single-message request. - if rawRefusal, ok := wireString(t, msgs2[0], "refusal"); !ok || rawRefusal != "blocked" { // non-empty refusal appears with its exact value on the assistant path. - t.Fatalf("refusal = %v want blocked", msgs2[0]["refusal"]) + body2 := mustEncode(t, rt, Request{Messages: []Message{refused}}) // separate encode keeps refusal's positive assertion independent from message 1's negative one above. + msgs2 := decodeMessages(t, body2) // exactly one wire object for this single-message request. + // non-empty refusal appears with its exact value on the assistant path. + { + rawRefusal, ok := wireString(t, msgs2[0], "refusal") + testassert.Require(t, ok && rawRefusal == "blocked", "refusal = %v want blocked", msgs2[0]["refusal"]) } - if _, ok := msgs2[0]["name"]; ok { // name still absent: empty-value omission does not change when other optional fields are present (per-field independence). - t.Fatalf("message must not carry an empty name key: %#v", msgs2[0]) + // name still absent: empty-value omission does not change when other optional fields are present (per-field independence). + { + _, ok := msgs2[0]["name"] + testassert.Require(t, !ok, "message must not carry an empty name key: %#v", msgs2[0]) } emptyWire := decodeMessages(t, mustEncode(t, rt, Request{Messages: []Message{emptyResult}}))[0] - if content, ok := wireString(t, emptyWire, "content"); !ok || content != "" { - t.Fatalf("empty tool result content = %q, present=%v; want a present empty string", content, ok) + { + content, ok := wireString(t, emptyWire, "content") + testassert.Require(t, ok && content == "", "empty tool result content = %q, present=%v; want a present empty string", content, ok) } } @@ -135,9 +139,7 @@ func TestContentWireShapesPinsStringVsArray(t *testing.T) { t.Run("multiple-parts-ordered-array", func(t *testing.T) { // text then image_url: both objects appear in request order with exact kind fields and no invented keys. m, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "look"}, {Kind: PartImageURL, URL: "https://img/x.png"}}}) // user messages carry the zero source by contract. - if err != nil { - t.Fatalf("NewMessage(multi-part): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{m}}) msgs := decodeMessages(t, body) // one wire object for this request. parts := unmarshalContentParts(t, msgs[0]) // array form is forced by having two parts; a bare string would fail inside the helper above with its diagnostic naming exactly this row. @@ -147,8 +149,9 @@ func TestContentWireShapesPinsStringVsArray(t *testing.T) { var image struct { URL string `json:"url"` } // nested object shape fixed by the retained encoder: exactly one url field. - if err := json.Unmarshal(parts[1]["image_url"], &image); err != nil || image.URL != "https://img/x.png" { - t.Fatalf("image_url = %s, want url https://img/x.png", parts[1]["image_url"]) + { + err := json.Unmarshal(parts[1]["image_url"], &image) + testassert.Require(t, err == nil && image.URL == "https://img/x.png", "image_url = %s, want url https://img/x.png", parts[1]["image_url"]) } }) @@ -159,8 +162,9 @@ func TestContentWireShapesPinsStringVsArray(t *testing.T) { parts := unmarshalContentParts(t, msgs[0]) // must be the array form now — a bare string fails inside that helper with content-not-an-object-array. assertStringField(t, parts[0], "type", "text") // kind field appears in object form (string-form rows never see one at all). assertStringField(t, parts[0], "text", "hi") // text under its canonical key alongside the extra. - if got, ok := parts[0]["part_marker"]; !ok || string(got) != `1` { - t.Fatalf("kept part extra missing or altered from the array-form object: %#v", parts[0]) + { + got, ok := parts[0]["part_marker"] + testassert.Require(t, ok && string(got) == `1`, "kept part extra missing or altered from the array-form object: %#v", parts[0]) } }) @@ -169,20 +173,22 @@ func TestContentWireShapesPinsStringVsArray(t *testing.T) { body2 := mustEncode(t, rt, Request{Messages: []Message{stripped}}) // encode of the stripped fixture produces the observable wire bytes below. msgsA := decodeMessages(t, body2) // one message for this request. assertUserTextWire(t, msgsA[0], "hi") // stripped extras never promote to array form: plain string is exactly what rule 10 mandates when nothing survives at that position. - if _, ok := msgsA[0]["name"]; ok { // guard against invented keys riding alongside content on this minimal fixture (none were set in the source). - t.Fatalf("stripped message must not carry invented keys: %#v", msgsA[0]) + // guard against invented keys riding alongside content on this minimal fixture (none were set in the source). + { + _, ok := msgsA[0]["name"] + testassert.Require(t, !ok, "stripped message must not carry invented keys: %#v", msgsA[0]) } }) t.Run("all-opaque-stripped-omits-content-key", func(t *testing.T) { opaque, err := NewMessage(Message{Role: RoleAssistant, Source: foreignModel, Content: []ContentPart{{Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"summary": json.RawMessage(`"s"`)}}}}) // opaque part with its original wire fields in extras; cross-provider source strips everything. - if err != nil { - t.Fatalf("NewMessage(opaque): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{opaque}}) - msgs := decodeMessages(t, body) // one wire object for this request. - if _, ok := msgs[0]["content"]; ok { // absence of the key entirely is the pinned behavior (null or an empty array would each be a different observable outcome not specified here). - t.Fatalf("stripped all-opaque message must omit content, got %#v", msgs[0]) + msgs := decodeMessages(t, body) // one wire object for this request. + // absence of the key entirely is the pinned behavior (null or an empty array would each be a different observable outcome not specified here). + { + _, ok := msgs[0]["content"] + testassert.Require(t, !ok, "stripped all-opaque message must omit content, got %#v", msgs[0]) } }) } @@ -193,25 +199,22 @@ func TestOpaquePartReplayKeptAndStripped(t *testing.T) { t.Run("kept-reconstructs-original-wire-object", func(t *testing.T) { m, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "preamble"}, {Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"summary": json.RawMessage(`"hidden"`), "_lightcode_private": json.RawMessage(`1`)}}}}) // text first so order is observable; the private field must still be filtered out inside a kept scope. - if err != nil { - t.Fatalf("NewMessage(opaque kept fixture): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{m}}) msgs := decodeMessages(t, body) // one wire object for this request. parts := unmarshalContentParts(t, msgs[0]) // two objects in original order on the wire (count enforced inside the helper's own check). assertStringField(t, parts[1], "type", "thinking") // structural OpaqueWireType lands as type — not the literal internal kind name and not any extra value. assertStringField(t, parts[1], "summary", `hidden`) - if _, ok := parts[1]["_lightcode_private"]; ok { - // private persistence fields never reconstruct even under full retention. - t.Fatalf("private field leaked onto a reconstructed opaque part: %#v", parts[1]) + // private persistence fields never reconstruct even under full retention. + { + _, ok := parts[1]["_lightcode_private"] + testassert.Require(t, !ok, "private field leaked onto a reconstructed opaque part: %#v", parts[1]) } }) t.Run("stripped-drops-opaque-keeps-siblings", func(t *testing.T) { m, err := NewMessage(Message{Role: RoleAssistant, Source: foreignModel, Content: []ContentPart{{Kind: PartText, Text: "preamble"}, {Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"summary": json.RawMessage(`"hidden"`)}}}}) // same shape as above but under a stripping source. - if err != nil { - t.Fatalf("NewMessage(opaque stripped fixture): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{m}}) msgs := decodeMessages(t, body) // one wire object for this request. parts := unmarshalContentParts(t, msgs[0]) // exactly the text part may remain (count 1 enforced inside the helper). @@ -221,13 +224,13 @@ func TestOpaquePartReplayKeptAndStripped(t *testing.T) { t.Run("stripped-all-opaque-omits-content-key", func(t *testing.T) { m, err := NewMessage(Message{Role: RoleAssistant, Source: foreignModel, Content: []ContentPart{{Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"summary": json.RawMessage(`"h"`)}}}}) // single opaque part only under a stripping source (minimal fixture for the absence-of-key outcome class). - if err != nil { - t.Fatalf("NewMessage(all-opaque): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{m}}) - msgs := decodeMessages(t, body) // one wire object for this request. - if _, ok := msgs[0]["content"]; ok { // absence again through a tool-call-free assistant entry shape (the sibling test pins the same outcome from its own fixture construction path). - t.Fatalf("all-opaque stripped message must omit content, got %#v", msgs[0]) + msgs := decodeMessages(t, body) // one wire object for this request. + // absence again through a tool-call-free assistant entry shape (the sibling test pins the same outcome from its own fixture construction path). + { + _, ok := msgs[0]["content"] + testassert.Require(t, !ok, "all-opaque stripped message must omit content, got %#v", msgs[0]) } }) } @@ -245,22 +248,17 @@ func TestToolCallAndContentPartExtraScopesPinned(t *testing.T) { "_lightcode_x": json.RawMessage(`1`), // private floor applies inside a tool call too (rule 9 is shared across every scope). "n": json.RawMessage(`7`), // reserved top-level key: never from extras at any scope including this nested one. }}) - if err != nil { - t.Fatalf("NewToolCall(call-scope fixture): %v", err) - } + testassert.NoError(t, err) m, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{{Kind: PartText, Text: "go"}}, ToolCalls: []ToolCall{call}}) // content present keeps this row focused on call-scope filtering rather than payload-validation rules owned by other suites. - if err != nil { - t.Fatalf("NewMessage(call-scope msg): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{m}}) // single encode covers every call-scope assertion below (subtest stays self-contained by design). msgs := decodeMessages(t, body) // one wire object for this request. rawCalls, ok := msgs[0]["tool_calls"] // the array itself must survive encoding before any per-call field rows can be evaluated against it. - if !ok { - t.Fatalf("message lost its tool_call array: %#v", msgs[0]) - } + testassert.Require(t, ok, "message lost its tool_call array: %#v", msgs[0]) var calls []map[string]json.RawMessage - if err := json.Unmarshal(rawCalls, &calls); err != nil || len(calls) != 1 { - t.Fatalf("tool_calls not a one-object array: %s", string(rawCalls)) + { + err := json.Unmarshal(rawCalls, &calls) + testassert.Require(t, err == nil && len(calls) == 1, "tool_calls not a one-object array: %s", string(rawCalls)) } assertStringField(t, calls[0], "id", "c1") assertStringField(t, calls[0], "type", "function") // explicit function type survives any forged extra attempt for the same dual-mechanism reason as above. @@ -268,15 +266,19 @@ func TestToolCallAndContentPartExtraScopesPinned(t *testing.T) { Name string `json:"name"` Arguments json.RawMessage `json:"arguments"` } - if err := json.Unmarshal(calls[0]["function"], &fn); err != nil || fn.Name != "lookup" { // nested function intact under its canonical key (no inner type field per the retained shape). - t.Fatalf("call.function = %s, want name lookup: %s", calls[0]["function"], string(rawCalls)) + // nested function intact under its canonical key (no inner type field per the retained shape). + { + err := json.Unmarshal(calls[0]["function"], &fn) + testassert.Require(t, err == nil && fn.Name == "lookup", "call.function = %s, want name lookup: %s", calls[0]["function"], string(rawCalls)) } - if got, ok := calls[0]["provider_hint"]; !ok || string(got) != `42` { - t.Fatalf("kept call-scope extra missing or altered: %#v", calls[0]) + { + got, ok := calls[0]["provider_hint"] + testassert.Require(t, ok && string(got) == `42`, "kept call-scope extra missing or altered: %#v", calls[0]) } for _, key := range []string{"_lightcode_x", "n", "index"} { // private floor, reserved top-level keys, and streaming-only call fields never reach this wire object. - if _, ok := calls[0][key]; ok { - t.Fatalf("forbidden key %q leaked onto the wire object of a tool call: %#v", key, calls[0]) + { + _, ok := calls[0][key] + testassert.Require(t, !ok, "forbidden key %q leaked onto the wire object of a tool call: %#v", key, calls[0]) } } }) @@ -295,23 +297,25 @@ func TestToolCallAndContentPartExtraScopesPinned(t *testing.T) { base.Extra = forged // mutation of the owned copy is safe: Encode deep-copies at entry (pinned separately by TestEncodeCallerImmutability for this boundary class). validated, err := NewMessage(base) // re-run construction so the stored value obeys ownership/clone rules like every other fixture reaching an accepting boundary. - if err != nil { - t.Fatalf("NewMessage(forgery-set message): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{validated}}) // one encode of the fully-forged kept message pins every row below at once. msgs := decodeMessages(t, body) // single wire object for this request. obj := msgs[0] // the only message on this wire; all assertions read it directly without further fixtures by design. - if rawRole, ok := wireString(t, obj, "role"); !ok || rawRole != "assistant" { - t.Fatalf("wire role = %v want assistant: %#v", obj["role"], obj) + { + rawRole, ok := wireString(t, obj, "role") + testassert.Require(t, ok && rawRole == "assistant", "wire role = %v want assistant: %#v", obj["role"], obj) } - assertUserTextWire(t, obj, "body") // content is the real part text, not a forged string from an extra of that same key name. - if _, ok := wireString(t, obj, "marker"); !ok { // legitimate retention still happened: marker survived next to every forgery on its message (positive counterweight keeping balance visible). - t.Fatalf("legitimate marker lost while forging siblings present: %#v", obj) + assertUserTextWire(t, obj, "body") // content is the real part text, not a forged string from an extra of that same key name. + // legitimate retention still happened: marker survived next to every forgery on its message (positive counterweight keeping balance visible). + { + _, ok := wireString(t, obj, "marker") + testassert.Require(t, ok, "legitimate marker lost while forging siblings present: %#v", obj) } for _, key := range []string{"_lightcode_state", "refusal", "tool_call_id", "name", "function_call", "system_fingerprint", "service_tier", "id", "created", "object", "stream"} { // the reserved top-level stream forgery must be omitted by the shared message-scope filter exactly like its canonical/private siblings. - if _, ok := obj[key]; ok { - t.Fatalf("forbidden message-scope key %q reached the wire: %#v", key, obj) + { + _, ok := obj[key] + testassert.Require(t, !ok, "forbidden message-scope key %q reached the wire: %#v", key, obj) } } }) @@ -324,20 +328,20 @@ func TestToolCallAndContentPartExtraScopesPinned(t *testing.T) { "n": json.RawMessage(`7`), // reserved top-level name never comes from extras at any scope, including this nested one. }} m, err := NewMessage(Message{Role: RoleAssistant, Source: targetRef, Content: []ContentPart{part}, Name: ""}) - if err != nil { - t.Fatalf("NewMessage(part-scope fixture): %v", err) - } + testassert.NoError(t, err) body := mustEncode(t, rt, Request{Messages: []Message{m}}) // single encode of the fully-forged kept part pins every row below at once (no further fixtures needed for this subtest by design). msgs := decodeMessages(t, body) // one wire message expected back from that request above. parts := unmarshalContentParts(t, msgs[0]) assertStringField(t, parts[0], "type", "text") assertStringField(t, parts[0], "text", "real text") - if got, ok := parts[0]["note"]; !ok || string(got) != `7` { - t.Fatalf("kept part-scope extra missing or altered: %#v", parts[0]) + { + got, ok := parts[0]["note"] + testassert.Require(t, ok && string(got) == `7`, "kept part-scope extra missing or altered: %#v", parts[0]) } for _, key := range []string{"_lightcode_p", "n"} { // private floor and reserved top-level keys never reach this wire object. - if _, ok := parts[0][key]; ok { - t.Fatalf("forbidden key %q leaked onto the wire object of a content part: %#v", key, parts[0]) + { + _, ok := parts[0][key] + testassert.Require(t, !ok, "forbidden key %q leaked onto the wire object of a content part: %#v", key, parts[0]) } } }) diff --git a/model/extra_boundary_test.go b/model/extra_boundary_test.go index 028583e5..3c5c6364 100644 --- a/model/extra_boundary_test.go +++ b/model/extra_boundary_test.go @@ -3,135 +3,118 @@ package model import ( "encoding/json" "testing" -) - -// TestAcceptingBoundariesRejectMalformedExtraJSON pins that every accepting boundary rejects Extra values whose raw bytes are not syntactically valid JSON, while the shared validation path keeps deep-copy ownership on success. Raw tool-call arguments remain the only malformed-JSON exception (pinned in ToolCall tests). -func TestAcceptingBoundariesRejectMalformedExtraJSON(t *testing.T) { - bad := Extra{"raw": json.RawMessage("not json")} - - t.Run("content part boundary rejects", func(t *testing.T) { - if _, err := NewContentPart(ContentPart{Kind: PartText, Text: "a", Extra: bad}); err == nil { - t.Fatal("NewContentPart accepted malformed extra bytes") - } - }) - t.Run("tool call boundary rejects", func(t *testing.T) { - if _, err := NewToolCall(ToolCall{ID: "c1", Name: "f", Extra: bad}); err == nil { - t.Fatal("NewToolCall accepted malformed extra bytes") - } - }) - - msgIn := Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: bad} - t.Run("message boundary rejects", func(t *testing.T) { - if _, err := NewMessage(msgIn); err == nil { - t.Fatal("NewMessage accepted malformed extra bytes") - } - }) - - reqMsgs := []Message{{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: bad}} - reqPartsBad := Request{Messages: []Message{{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Extra: bad}}}}} - t.Run("request boundary rejects message extra", func(t *testing.T) { - if _, err := NewRequest(Request{Messages: reqMsgs}); err == nil { - t.Fatal("NewRequest accepted malformed message extra bytes") - } - }) - t.Run("request boundary rejects part extra", func(t *testing.T) { - if _, err := NewRequest(reqPartsBad); err == nil { - t.Fatal("NewRequest accepted malformed content-part extra bytes") - } - }) + "github.com/MMinasyan/lightcode/internal/testassert" +) - outMsg := assistantMsg(t, func(m *Message) { m.Extra = bad }) - outIn := Output{Status: OutputErrored, Source: fullRef, Message: &outMsg, Detail: "d"} - t.Run("output boundary rejects", func(t *testing.T) { - if _, err := NewOutput(outIn); err == nil { - t.Fatal("NewOutput accepted malformed message extra bytes") - } - }) +// TestAcceptingBoundariesRejectInvalidExtraJSON pins that every accepting +// boundary rejects Extra values whose raw bytes are not complete, syntactically +// valid JSON — both malformed nonempty bytes and empty raw values, which are +// absent data rather than a value and must never reach stored state where +// finalization could count them as payload. The union keeps each original +// boundary/raw-value pair; the two shapes are not a cross product. Raw +// tool-call arguments remain the only malformed-JSON exception (pinned in the +// ToolCall cases). +func TestAcceptingBoundariesRejectInvalidExtraJSON(t *testing.T) { + malformed := func() json.RawMessage { return json.RawMessage("not json") } + empty := func() json.RawMessage { return json.RawMessage{} } + extra := func(raw func() json.RawMessage) Extra { return Extra{"raw": raw()} } - deltaCases := []struct { + cases := []struct { name string - d StreamDelta + raw func() json.RawMessage + call func(Extra) error }{ - {name: "content fragment scope", d: StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Extra: bad}}}}, - {name: "tool call fragment scope", d: StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c1", Name: "f", Extra: bad}}}}, - {name: "message extra scope", d: StreamDelta{MessageExtra: bad}}, - } - for _, tc := range deltaCases { - t.Run("delta boundary rejects "+tc.name, func(t *testing.T) { - if _, err := NewStreamDelta(tc.d); err == nil { - t.Fatalf("NewStreamDelta accepted malformed extra bytes at %s", tc.name) - } - }) - } - - validExtra := Extra{"k": json.RawMessage(`"v"`)} - validIn2 := ContentPart{Kind: PartText, Text: "a", Extra: validExtra} - validPart, err := NewContentPart(validIn2) - if err != nil || validPart.Kind != PartText { - t.Fatalf("NewContentPart rejected a part with valid extra bytes (err %v): %#v", err, validPart) + {"content part boundary rejects", malformed, func(e Extra) error { + _, err := NewContentPart(ContentPart{Kind: PartText, Text: "a", Extra: e}) + return err + }}, + {"content part boundary rejects empty value", empty, func(e Extra) error { + _, err := NewContentPart(ContentPart{Kind: PartText, Text: "a", Extra: e}) + return err + }}, + {"tool call boundary rejects", malformed, func(e Extra) error { + _, err := NewToolCall(ToolCall{ID: "c1", Name: "f", Extra: e}) + return err + }}, + {"tool call boundary rejects empty value", empty, func(e Extra) error { + _, err := NewToolCall(ToolCall{ID: "c1", Name: "f", Extra: e}) + return err + }}, + {"message boundary rejects", malformed, func(e Extra) error { + _, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: e}) + return err + }}, + {"message boundary rejects empty value", empty, func(e Extra) error { + _, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: e}) + return err + }}, + {"request boundary rejects message extra", malformed, func(e Extra) error { + _, err := NewRequest(Request{Messages: []Message{{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: e}}}) + return err + }}, + {"request boundary rejects part extra", malformed, func(e Extra) error { + _, err := NewRequest(Request{Messages: []Message{{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Extra: e}}}}}) + return err + }}, + {"request boundary rejects embedded tool call empty value", empty, func(e Extra) error { + _, err := NewRequest(Request{Messages: []Message{{Role: RoleAssistant, Source: fullRef, ToolCalls: []ToolCall{{ID: "c1", Name: "f", Extra: e}}}}}) + return err + }}, + {"output boundary rejects", malformed, func(e Extra) error { + _, err := NewOutput(Output{Status: OutputErrored, Source: fullRef, Detail: "d", Message: &Message{Role: RoleAssistant, Source: fullRef, Extra: e}}) + return err + }}, + {"output boundary rejects empty value", empty, func(e Extra) error { + _, err := NewOutput(Output{Status: OutputErrored, Source: fullRef, Detail: "d", Message: &Message{Role: RoleAssistant, Source: fullRef, Extra: e}}) + return err + }}, + {"delta boundary rejects content fragment scope", malformed, func(e Extra) error { + _, err := NewStreamDelta(StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Extra: e}}}) + return err + }}, + {"delta boundary rejects content fragment scope empty value", empty, func(e Extra) error { + _, err := NewStreamDelta(StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Extra: e}}}) + return err + }}, + {"delta boundary rejects tool call fragment scope", malformed, func(e Extra) error { + _, err := NewStreamDelta(StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c1", Name: "f", Extra: e}}}) + return err + }}, + {"delta boundary rejects tool call fragment scope empty value", empty, func(e Extra) error { + _, err := NewStreamDelta(StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c1", Name: "f", Extra: e}}}) + return err + }}, + {"delta boundary rejects message extra scope", malformed, func(e Extra) error { + _, err := NewStreamDelta(StreamDelta{MessageExtra: e}) + return err + }}, + {"delta boundary rejects message extra scope empty value", empty, func(e Extra) error { + _, err := NewStreamDelta(StreamDelta{MessageExtra: e}) + return err + }}, } - srcRaw := []byte(`"v"`) - goodIn := ContentPart{Kind: PartText, Text: "b", Extra: Extra{"k": json.RawMessage(srcRaw)}} - outGood, err := NewContentPart(goodIn) - if err != nil { - t.Fatalf("NewContentPart rejected valid extra bytes: %v", err) - } - srcRaw[1] = 'X' // corrupt caller bytes after a successful validation+copy. - stored := string(outGood.Extra["k"]) - if stored[:2] != `"v` { - t.Fatalf("deep-copy ownership lost on the valid path: %s", outGood.Extra["k"]) - } -} - -// TestAcceptingBoundariesRejectEmptyExtraValues pins that an empty json.RawMessage value is not complete JSON and is therefore rejected at every accepting boundary (it must never reach stored state where finalization could count it as payload), while the null literal — which IS complete JSON — still passes construction. -func TestAcceptingBoundariesRejectEmptyExtraValues(t *testing.T) { - empty := Extra{"e": json.RawMessage{}} - - if _, err := NewContentPart(ContentPart{Kind: PartText, Text: "a", Extra: empty}); err == nil { - t.Fatal("NewContentPart accepted an empty extra value") - } - if _, err := NewToolCall(ToolCall{ID: "c1", Name: "f", Extra: empty}); err == nil { - t.Fatal("NewToolCall accepted an empty extra value") - } - msgIn := Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: empty} - if _, err := NewMessage(msgIn); err == nil { - t.Fatal("NewMessage accepted a message with an empty extra value") - } - reqCallMsg := assistantMsg(t, func(m *Message) { m.ToolCalls = []ToolCall{{ID: "c1", Name: "f", Extra: empty}} }) - if _, err := NewRequest(Request{Messages: []Message{reqCallMsg}}); err == nil { - t.Fatal("NewRequest accepted an embedded tool call with an empty extra value") - } - outMsg := assistantMsg(t, func(m *Message) { m.Extra = empty }) - outIn := Output{Status: OutputErrored, Source: fullRef, Message: &outMsg, Detail: "d"} - if _, err := NewOutput(outIn); err == nil { - t.Fatal("NewOutput accepted a message with an empty extra value") - } - deltaCases := []struct { - name string - d StreamDelta - }{ - {name: "content fragment scope", d: StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Extra: empty}}}}, - {name: "tool call fragment scope", d: StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c1", Name: "f", Extra: empty}}}}, - {name: "message extra scope", d: StreamDelta{MessageExtra: empty}}, - } - for _, tc := range deltaCases { - t.Run("delta boundary rejects "+tc.name, func(t *testing.T) { - if _, err := NewStreamDelta(tc.d); err == nil { - t.Fatalf("NewStreamDelta accepted an empty extra value at %s", tc.name) - } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := tc.call(extra(tc.raw)) + testassert.Require(t, err != nil, "%s accepted invalid extra bytes", tc.name) }) } - nullIsCompleteJSON := Extra{"n": json.RawMessage(`null`)} // null is complete JSON; construction accepts it, finalization drops it. - if _, err := NewContentPart(ContentPart{Kind: PartText, Text: "a", Extra: nullIsCompleteJSON}); err != nil { - t.Fatalf("NewContentPart rejected a null extra value (complete JSON): %v", err) + // The null literal is complete JSON: construction accepts it and the + // Message boundary retains the extra entry (finalization drops it later). + nullIsCompleteJSON := Extra{"n": json.RawMessage(`null`)} + { + _, err := NewContentPart(ContentPart{Kind: PartText, Text: "a", Extra: nullIsCompleteJSON}) + testassert.NoError(t, err) } - if m, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: nullIsCompleteJSON}); err != nil || len(m.Extra) != 1 { - t.Fatalf("NewMessage rejected or dropped a null extra value (err %v): %#v", err, m.Extra) + { + m, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}, Extra: nullIsCompleteJSON}) + testassert.Require(t, err == nil && len(m.Extra) == 1, "NewMessage rejected or dropped a null extra value (err %v): %#v", err, m.Extra) } - if _, err := NewStreamDelta(StreamDelta{MessageExtra: nullIsCompleteJSON}); err != nil { - t.Fatalf("NewStreamDelta rejected a null message extra (complete JSON): %v", err) + { + _, err := NewStreamDelta(StreamDelta{MessageExtra: nullIsCompleteJSON}) + testassert.NoError(t, err) } } diff --git a/model/extra_test.go b/model/extra_test.go index 12cd9923..6631e248 100644 --- a/model/extra_test.go +++ b/model/extra_test.go @@ -3,44 +3,55 @@ package model import ( "encoding/json" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestExtraAccumulatorConcatenatesStrings(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("reasoning_content", json.RawMessage(`"hello "`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning_content", json.RawMessage(`"hello "`)) + testassert.NoError(t, err) } - if err := acc.Add("reasoning_content", json.RawMessage(`"world"`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning_content", json.RawMessage(`"world"`)) + testassert.NoError(t, err) } - if got := string(acc.Finalize()["reasoning_content"]); got != `"hello world"` { - t.Fatalf("reasoning_content = %s", got) + { + got := string(acc.Finalize()["reasoning_content"]) + testassert.Equal(t, got, `"hello world"`) } } func TestExtraAccumulatorAppendsArrays(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("reasoning_details", json.RawMessage(`[{"type":"text","text":"a"}]`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning_details", json.RawMessage(`[{"type":"text","text":"a"}]`)) + testassert.NoError(t, err) } - if err := acc.Add("reasoning_details", json.RawMessage(`[{"type":"encrypted","data":"b"}]`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning_details", json.RawMessage(`[{"type":"encrypted","data":"b"}]`)) + testassert.NoError(t, err) } - if got := string(acc.Finalize()["reasoning_details"]); got != `[{"type":"text","text":"a"},{"type":"encrypted","data":"b"}]` { - t.Fatalf("reasoning_details = %s", got) + { + got := string(acc.Finalize()["reasoning_details"]) + testassert.Equal(t, got, `[{"type":"text","text":"a"},{"type":"encrypted","data":"b"}]`) } } func TestExtraAccumulatorObjectLastWriteWins(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("reasoning", json.RawMessage(`{"first":true}`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning", json.RawMessage(`{"first":true}`)) + testassert.NoError(t, err) } - if err := acc.Add("reasoning", json.RawMessage(`{"second":true}`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning", json.RawMessage(`{"second":true}`)) + testassert.NoError(t, err) } - if got := string(acc.Finalize()["reasoning"]); got != `{"second":true}` { - t.Fatalf("reasoning = %s", got) + { + got := string(acc.Finalize()["reasoning"]) + testassert.Equal(t, got, `{"second":true}`) } } @@ -49,21 +60,25 @@ func TestExtraAccumulatorNumberAndBooleanAreOtherKind(t *testing.T) { for _, pair := range pairs { t.Run(pair[0]+" to "+pair[1], func(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("n", json.RawMessage(pair[0])); err != nil { - t.Fatalf("Add(%s) returned error: %v", pair[0], err) + { + err := acc.Add("n", json.RawMessage(pair[0])) + testassert.Require(t, err == nil, "Add(%s) returned error: %v", pair[0], err) } - if err := acc.Add("n", json.RawMessage(pair[1])); err != nil { - t.Fatalf("%s to %s must replace within the other kind without error, got %v", pair[0], pair[1], err) + { + err := acc.Add("n", json.RawMessage(pair[1])) + testassert.Require(t, err == nil, "%s to %s must replace within the other kind without error, got %v", pair[0], pair[1], err) } - if got := string(acc.Finalize()["n"]); got != pair[1] { - t.Fatalf("n = %s, want latest value replacing the previous same-kind one (%s)", got, pair[1]) + { + got := string(acc.Finalize()["n"]) + testassert.Equal(t, got, pair[1]) } }) } mixed := NewExtraAccumulator() // number to boolean is still the same other kind: replace without error. - if err := mixed.Add("m", json.RawMessage(`5`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := mixed.Add("m", json.RawMessage(`5`)) + testassert.NoError(t, err) } if err := mixed.Add("m", json.RawMessage(`true`)); err != nil { t.Fatalf("number to boolean must replace within the other kind without error, got %v", err) @@ -72,8 +87,9 @@ func TestExtraAccumulatorNumberAndBooleanAreOtherKind(t *testing.T) { } toOther := NewExtraAccumulator() // string to number crosses kinds: error plus keep latest. - if err := toOther.Add("o", json.RawMessage(`"text"`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := toOther.Add("o", json.RawMessage(`"text"`)) + testassert.NoError(t, err) } if err := toOther.Add("o", json.RawMessage(`7`)); err == nil { t.Fatal("string to number must return a kind-change accumulator error") @@ -82,54 +98,65 @@ func TestExtraAccumulatorNumberAndBooleanAreOtherKind(t *testing.T) { } fromNull := NewExtraAccumulator() - if err := fromNull.Add("z", json.RawMessage(`null`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := fromNull.Add("z", json.RawMessage(`null`)) + testassert.NoError(t, err) } if err := fromNull.Add("z", json.RawMessage(`"s"`)); err == nil { t.Fatal("null to string must return a kind-change accumulator error") } else if _, ok := fromNull.Finalize()["z"]; !ok { t.Fatalf("latest value after null->string change not kept: %#v", fromNull.Finalize()) } - if got, present := fromNull.Finalize()["z"]; !present || string(got) != `"s"` { - t.Fatalf("finalized z = %q (present=%v), want \"s\"", got, present) + { + got, present := fromNull.Finalize()["z"] + testassert.Require(t, present && string(got) == `"s"`, "finalized z = %q (present=%v), want \"s\"", got, present) } } func TestExtraAccumulatorKindChangeReturnsErrorAndKeepsLastValue(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("reasoning", json.RawMessage(`"text"`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("reasoning", json.RawMessage(`"text"`)) + testassert.NoError(t, err) } - if err := acc.Add("reasoning", json.RawMessage(`[{"type":"text"}]`)); err == nil { - t.Fatal("Add returned nil error for kind change") + { + err := acc.Add("reasoning", json.RawMessage(`[{"type":"text"}]`)) + testassert.Require(t, err != nil, "%v", "Add returned nil error for kind change") } - if got := string(acc.Finalize()["reasoning"]); got != `[{"type":"text"}]` { - t.Fatalf("reasoning = %s", got) + { + got := string(acc.Finalize()["reasoning"]) + testassert.Equal(t, got, `[{"type":"text"}]`) } } func TestExtraAccumulatorOmitsNullValuesOnFinalize(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("drop", json.RawMessage(`null`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("drop", json.RawMessage(`null`)) + testassert.NoError(t, err) } - if err := acc.Add("keep", json.RawMessage(`""`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("keep", json.RawMessage(`""`)) + testassert.NoError(t, err) } out := acc.Finalize() - if _, ok := out["drop"]; ok { - t.Fatalf("null value was kept: %#v", out) + { + _, ok := out["drop"] + testassert.Require(t, !ok, "null value was kept: %#v", out) } - if got := string(out["keep"]); got != `""` { - t.Fatalf("keep = %s", got) + { + got := string(out["keep"]) + testassert.Equal(t, got, `""`) } replacedNull := NewExtraAccumulator() - if err := replacedNull.Add("k", json.RawMessage(`"a"`)); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := replacedNull.Add("k", json.RawMessage(`"a"`)) + testassert.NoError(t, err) } - if err := replacedNull.Add("k", json.RawMessage(`null`)); err == nil { - t.Fatal("string to null must return a kind-change accumulator error") + { + err := replacedNull.Add("k", json.RawMessage(`null`)) + testassert.Require(t, err != nil, "%v", "string to null must return a kind-change accumulator error") } if _, ok := replacedNull.Finalize()["k"]; ok { t.Fatalf("latest value is null and must be omitted: %#v", replacedNull.Finalize()) @@ -138,14 +165,17 @@ func TestExtraAccumulatorOmitsNullValuesOnFinalize(t *testing.T) { func TestExtraAccumulatorIgnoresEmptyKeyAndValue(t *testing.T) { acc := NewExtraAccumulator() - if err := acc.Add("", json.RawMessage(`"x"`)); err != nil { - t.Fatalf("Add empty key returned error: %v", err) + { + err := acc.Add("", json.RawMessage(`"x"`)) + testassert.NoError(t, err) } - if err := acc.Add("k", json.RawMessage(nil)); err != nil { - t.Fatalf("Add empty value returned error: %v", err) + { + err := acc.Add("k", json.RawMessage(nil)) + testassert.NoError(t, err) } - if got := acc.Finalize(); len(got) != 0 { - t.Fatalf("Finalize = %#v, want empty", got) + { + got := acc.Finalize() + testassert.Require(t, len(got) == 0, "Finalize = %#v, want empty", got) } } @@ -153,24 +183,20 @@ func TestExtraCloneDeepCopiesValues(t *testing.T) { srcRaw := json.RawMessage(`"hello"`) in := Extra{"a": srcRaw} // direct assignment shares bytes by caller choice; Clone is the accepting boundary. out := in.Clone() - if string(out["a"]) != `"hello"` { - t.Fatalf("clone a = %s", out["a"]) - } - if len(srcRaw) > 0 && &out["a"][0] == &srcRaw[0] { - t.Fatal("Clone shares bytes with its input") - } + testassert.Require(t, string(out["a"]) == `"hello"`, "clone a = %s", out["a"]) + testassert.Require(t, len(srcRaw) <= 0 || &out["a"][0] != &srcRaw[0], "%v", "Clone shares bytes with its input") out["a"][1] = 'H' // corrupt the clone's retained copy. - if got := string(in["a"]); got != `"hello"` { - t.Fatalf("original extra mutated through a Clone result: %s", in["a"]) + { + got := string(in["a"]) + testassert.Require(t, got == `"hello"`, "original extra mutated through a Clone result: %s", in["a"]) } var nilExtra Extra - if got := nilExtra.Clone(); got != nil { - t.Fatalf("nil Clone = %#v, want nil", got) - } - if len(out) != 1 { - t.Fatalf("clone of one-entry extra wrong: %#v", out) + { + got := nilExtra.Clone() + testassert.Require(t, got == nil, "nil Clone = %#v, want nil", got) } + testassert.Require(t, len(out) == 1, "clone of one-entry extra wrong: %#v", out) } func TestExtraFinalizeDropsNullsAndDeepCopies(t *testing.T) { @@ -179,23 +205,24 @@ func TestExtraFinalizeDropsNullsAndDeepCopies(t *testing.T) { "keep": json.RawMessage(`""`), } clean := extra.Finalize() - if _, ok := clean["drop"]; ok { - t.Fatalf("null value was kept: %#v", clean) + { + _, ok := clean["drop"] + testassert.Require(t, !ok, "null value was kept: %#v", clean) } - if got := string(clean["keep"]); got != `""` { - t.Fatalf("keep = %s", got) + { + got := string(clean["keep"]) + testassert.Equal(t, got, `""`) } srcRaw := json.RawMessage(`"x"`) withNull := Extra{"n": json.RawMessage(`null`), "k": srcRaw} out := withNull.Finalize() - if out["k"] == nil || &out["k"][0] == &srcRaw[0] { - t.Fatal("finalize did not deep copy retained values") - } + testassert.Require(t, out["k"] != nil && &out["k"][0] != &srcRaw[0], "%v", "finalize did not deep copy retained values") var zero Extra - if z := zero.Finalize(); z != nil { - t.Fatalf("zero Finalize = %#v, want nil", z) + { + z := zero.Finalize() + testassert.Require(t, z == nil, "zero Finalize = %#v, want nil", z) } allNull := Extra{"only": json.RawMessage(`null`)} if len(allNull.Finalize()) != 0 { @@ -206,38 +233,32 @@ func TestExtraFinalizeDropsNullsAndDeepCopies(t *testing.T) { func TestCloneRawCopiesBytes(t *testing.T) { src := json.RawMessage(`"abc"`) dst := CloneRaw(src) - if string(dst) != `"abc"` || len(dst) == 0 || &dst[0] == &src[0] { - t.Fatalf("CloneRaw = %s, want independent copy", dst) - } + testassert.Require(t, string(dst) == `"abc"` && len(dst) != 0 && &dst[0] != &src[0], "CloneRaw = %s, want independent copy", dst) var nilSrc json.RawMessage - if got := CloneRaw(nilSrc); len(got) != 0 { - t.Fatalf("CloneRaw(nil) = %#v, want empty", got) + { + got := CloneRaw(nilSrc) + testassert.Require(t, len(got) == 0, "CloneRaw(nil) = %#v, want empty", got) } dst[1] = 'x' - if string(src) != `"abc"` { - t.Fatal("mutating the copy changed the source") - } + testassert.Require(t, string(src) == `"abc"`, "%v", "mutating the copy changed the source") } func TestExtraAccumulatorAddDeepCopiesInput(t *testing.T) { srcRaw := json.RawMessage(`"first"`) acc := NewExtraAccumulator() - if err := acc.Add("k", srcRaw); err != nil { - t.Fatalf("Add returned error: %v", err) + { + err := acc.Add("k", srcRaw) + testassert.NoError(t, err) } srcRaw[1] = 'X' got := acc.Finalize()["k"] - if string(got)[:1] == `"X` || &got[0] == &srcRaw[0] { - t.Fatalf("accumulator stored caller bytes: %s", got) - } + testassert.Require(t, string(got)[:1] != `"X` && &got[0] != &srcRaw[0], "accumulator stored caller bytes: %s", got) out := acc.Finalize() src2 := json.RawMessage(`"second"`) in := Extra{"k": src2} clonedExtra := in.Clone() - if &clonedExtra["k"][0] == &src2[0] { - t.Fatal("Clone shared bytes with input") - } + testassert.Require(t, &clonedExtra["k"][0] != &src2[0], "%v", "Clone shared bytes with input") _ = out } @@ -258,15 +279,14 @@ func TestExtraHasValuesMatchesFinalizedRetention(t *testing.T) { {"object": json.RawMessage(`{}`)}, } for _, e := range extras { - if got, want := e.HasValues(), len(e.Finalize()) > 0; got != want { - t.Fatalf("Extra %#v: HasValues = %v, want %v", map[string]json.RawMessage(e), got, want) + { + got, want := e.HasValues(), len(e.Finalize()) > 0 + testassert.Require(t, got == want, "Extra %#v: HasValues = %v, want %v", map[string]json.RawMessage(e), got, want) } } var nilAcc *ExtraAccumulator - if nilAcc.HasValues() { - t.Fatal("nil accumulator reports values") - } + testassert.Require(t, !nilAcc.HasValues(), "%v", "nil accumulator reports values") type add struct { key string @@ -286,8 +306,9 @@ func TestExtraHasValuesMatchesFinalizedRetention(t *testing.T) { for _, step := range script { _ = acc.Add(step.key, step.value) // accumulation errors keep the latest value; the query sees the same retained state either way. } - if got, want := acc.HasValues(), len(acc.Finalize()) > 0; got != want { - t.Fatalf("accumulator %#v: HasValues = %v, want %v", script, got, want) + { + got, want := acc.HasValues(), len(acc.Finalize()) > 0 + testassert.Require(t, got == want, "accumulator %#v: HasValues = %v, want %v", script, got, want) } } } diff --git a/model/message_test.go b/model/message_test.go index 811144ac..e912dcf3 100644 --- a/model/message_test.go +++ b/model/message_test.go @@ -5,6 +5,8 @@ import ( "errors" "reflect" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) var fullRef = ModelRef{Provider: "openai", Model: "gpt-5.4-mini"} @@ -14,9 +16,7 @@ func call(id, name string) ToolCall { return ToolCall{ID: id, Name: name} } func mustMessage(t *testing.T, m Message) Message { t.Helper() out, err := NewMessage(m) - if err != nil { - t.Fatalf("NewMessage(role=%s) returned error: %v", m.Role, err) - } + testassert.Require(t, err == nil, "NewMessage(role=%s) returned error: %v", m.Role, err) return out } @@ -34,12 +34,8 @@ func TestNewMessageRoleSourceMatrix(t *testing.T) { for _, tc := range valid { t.Run(tc.name+" accepts", func(t *testing.T) { out, err := NewMessage(tc.msg) - if err != nil { - t.Fatalf("NewMessage returned error: %v", err) - } - if out.Role != tc.msg.Role || out.Source != tc.msg.Source || out.ToolCallID != tc.msg.ToolCallID || out.Name != tc.msg.Name { - t.Fatalf("fields not preserved: %#v", out) - } + testassert.NoError(t, err) + testassert.Require(t, out.Role == tc.msg.Role && out.Source == tc.msg.Source && out.ToolCallID == tc.msg.ToolCallID && out.Name == tc.msg.Name, "fields not preserved: %#v", out) }) } @@ -57,15 +53,17 @@ func TestNewMessageRoleSourceMatrix(t *testing.T) { } for _, tc := range cases { t.Run(tc.name+" rejects", func(t *testing.T) { - if _, err := NewMessage(tc.msg); !errors.Is(err, tc.wantIs) { - t.Fatalf("error = %v, want errors.Is(%v)", err, tc.wantIs) + { + _, err := NewMessage(tc.msg) + testassert.Require(t, errors.Is(err, tc.wantIs), "error = %v, want errors.Is(%v)", err, tc.wantIs) } }) } for i, bogus := range []string{"", "developer", "SYSTEM", "tool ", "" + string(rune('x'))} { - if _, err := NewMessage(Message{Role: Role(bogus)}); !errors.Is(err, ErrInvalidRole) { - t.Fatalf("case %d role=%q error = %v, want ErrInvalidRole", i, bogus, err) + { + _, err := NewMessage(Message{Role: Role(bogus)}) + testassert.Require(t, errors.Is(err, ErrInvalidRole), "case %d role=%q error = %v, want ErrInvalidRole", i, bogus, err) } } } @@ -82,39 +80,27 @@ func TestNewMessageFixesEveryField(t *testing.T) { Extra: Extra{"reasoning_content": json.RawMessage(`"thinking"`)}, } m, err := NewMessage(in) - if err != nil { - t.Fatalf("NewMessage returned error: %v", err) - } - if m.Role != RoleAssistant || m.Source != fullRef || m.Name != "worker" || m.Refusal != "cannot do that" { - t.Fatalf("scalar fields not preserved: %#v", m) - } - if len(m.Content) != 1 || m.Content[0].Kind != PartText || m.Content[0].Text != "done" { - t.Fatalf("content = %#v", m.Content) - } - if len(m.ToolCalls) != 1 || m.ToolCalls[0].ID != "call_1" || m.ToolCalls[0].Name != "read_file" { - t.Fatalf("tool calls = %#v", m.ToolCalls) - } - if got := string(m.Extra["reasoning_content"]); got != `"thinking"` { - t.Fatalf("extra = %s", got) + testassert.NoError(t, err) + testassert.Require(t, m.Role == RoleAssistant && m.Source == fullRef && m.Name == "worker" && m.Refusal == "cannot do that" && (len(m.Content) == 1 && m.Content[0].Kind == PartText && m.Content[0].Text == "done") && (len(m.ToolCalls) == 1 && m.ToolCalls[0].ID == "call_1" && m.ToolCalls[0].Name == "read_file"), "scalar fields not preserved: %#v; content = %#v; tool calls = %#v", m, m.Content, m.ToolCalls) + { + got := string(m.Extra["reasoning_content"]) + testassert.Equal(t, got, `"thinking"`) } // Deep ownership: mutating caller inputs after construction must not touch the returned message. in.Content[0].Text = "MUTATED" - if m.Content[0].Text != "done" { - t.Fatal("returned content part is not an independent copy") - } + testassert.Require(t, m.Content[0].Text == "done", "%v", "returned content part is not an independent copy") raw := in.Extra["reasoning_content"] if len(raw) > 3 { raw[1] = 'X' // corrupt the caller's copy after construction. } - if got := string(m.Extra["reasoning_content"]); !isExactJSONString(got, "thinking") { - t.Fatalf("returned extra shares bytes with input: %s", m.Extra["reasoning_content"]) + { + got := string(m.Extra["reasoning_content"]) + testassert.Require(t, isExactJSONString(got, "thinking"), "returned extra shares bytes with input: %s", m.Extra["reasoning_content"]) } out := mustMessage(t, Message{Role: RoleUser}) - if out.Extra != nil || len(out.Content) != 0 || out.ToolCalls != nil { - t.Fatalf("zero-value message should stay empty: %#v", out) - } + testassert.Require(t, out.Extra == nil && len(out.Content) == 0 && out.ToolCalls == nil, "zero-value message should stay empty: %#v", out) } func isExactJSONString(raw, s string) bool { return raw == `"`+s+`"` } @@ -131,8 +117,9 @@ func TestNewMessageRoleFieldCombinations(t *testing.T) { } for _, tc := range valid { t.Run(tc.name+" accepts", func(t *testing.T) { - if _, err := NewMessage(tc.msg); err != nil { - t.Fatalf("NewMessage returned error: %v", err) + { + _, err := NewMessage(tc.msg) + testassert.NoError(t, err) } }) } @@ -151,8 +138,9 @@ func TestNewMessageRoleFieldCombinations(t *testing.T) { } for _, tc := range cases { t.Run(tc.name+" rejects", func(t *testing.T) { - if _, err := NewMessage(tc.msg); !errors.Is(err, tc.wantIs) { - t.Fatalf("error = %v, want errors.Is(%v)", err, tc.wantIs) + { + _, err := NewMessage(tc.msg) + testassert.Require(t, errors.Is(err, tc.wantIs), "error = %v, want errors.Is(%v)", err, tc.wantIs) } }) } @@ -160,77 +148,92 @@ func TestNewMessageRoleFieldCombinations(t *testing.T) { } func TestNewMessageRejectsInvalidEmbeddedPartsAndCalls(t *testing.T) { - if _, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, URL: "https://x"}}}); !errors.Is(err, ErrForbiddenField) { - t.Fatalf("text part carrying a URL error = %v, want ErrForbiddenField", err) + { + _, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, URL: "https://x"}}}) + testassert.Require(t, errors.Is(err, ErrForbiddenField), "text part carrying a URL error = %v, want ErrForbiddenField", err) } - if _, err := NewMessage(Message{Role: RoleAssistant, Source: fullRef, ToolCalls: []ToolCall{{ID: "", Name: "f"}}}); !errors.Is(err, ErrMissingField) { - t.Fatalf("assistant call with empty id error = %v, want ErrMissingField", err) + { + _, err := NewMessage(Message{Role: RoleAssistant, Source: fullRef, ToolCalls: []ToolCall{{ID: "", Name: "f"}}}) + testassert.Require(t, errors.Is(err, ErrMissingField), "assistant call with empty id error = %v, want ErrMissingField", err) } - if _, err := NewMessage(Message{Role: RoleAssistant, Source: fullRef, ToolCalls: []ToolCall{{ID: "c1", Name: ""}}}); !errors.Is(err, ErrMissingField) { - t.Fatalf("assistant call with empty name error = %v, want ErrMissingField", err) + { + _, err := NewMessage(Message{Role: RoleAssistant, Source: fullRef, ToolCalls: []ToolCall{{ID: "c1", Name: ""}}}) + testassert.Require(t, errors.Is(err, ErrMissingField), "assistant call with empty name error = %v, want ErrMissingField", err) } - if _, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartOpaque}}}); !errors.Is(err, ErrMissingField) { - t.Fatalf("opaque part without wire type error = %v, want ErrMissingField", err) + { + _, err := NewMessage(Message{Role: RoleUser, Content: []ContentPart{{Kind: PartOpaque}}}) + testassert.Require(t, errors.Is(err, ErrMissingField), "opaque part without wire type error = %v, want ErrMissingField", err) } } func TestNewContentPartKindsAndExclusivity(t *testing.T) { - valid := []struct { - name string - part ContentPart + // One inventory for the ContentPart accepting boundary: the cross-kind, + // unknown-kind, missing-wire-type and Extra-value shapes, each with its + // original expected verdict. Both entry points must match every row. + rows := []struct { + name string + part ContentPart + wantErr bool + wantIsAny []error }{ - {name: "text", part: ContentPart{Kind: PartText, Text: "hi"}}, - {name: "empty text allowed for later finalization omission", part: ContentPart{Kind: PartText}}, - {name: "image url only", part: ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png"}}, - {name: "opaque wire type with extras", part: ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"data": json.RawMessage(`"x"`)}}}, - } - for _, tc := range valid { - t.Run(tc.name+" accepts", func(t *testing.T) { - out, err := NewContentPart(tc.part) - if err != nil { - t.Fatalf("NewContentPart returned error: %v", err) + {"text", ContentPart{Kind: PartText, Text: "hi"}, false, nil}, + {"rich text", ContentPart{Kind: PartText, Text: "hello", Extra: Extra{"k": json.RawMessage(`"v"`)}}, false, nil}, + {"empty text allowed for later finalization omission", ContentPart{Kind: PartText}, false, nil}, + {"image url only", ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png"}, false, nil}, + {"opaque wire type with extras", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"data": json.RawMessage(`"x"`)}}, false, nil}, + {"opaque with nested extra", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"t": json.RawMessage(`{"n":1}`)}}, false, nil}, + {"null extra value", ContentPart{Kind: PartText, Text: "a", Extra: Extra{"n": json.RawMessage(`null`)}}, false, nil}, + {"unknown kind rejected", ContentPart{Kind: PartKind("image")}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"unknown named kind rejected", ContentPart{Kind: PartKind("audio"), Text: "a"}, true, nil}, + {"empty kind rejected", ContentPart{Text: "hi"}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"text with url rejected", ContentPart{Kind: PartText, Text: "a", URL: "b"}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"text carrying opaque wire type rejected", ContentPart{Kind: PartText, Text: "a", OpaqueWireType: "thinking"}, true, nil}, + {"image carrying text rejected", ContentPart{Kind: PartImageURL, URL: "u", Text: "a"}, true, nil}, + {"image carrying opaque wire type rejected", ContentPart{Kind: PartImageURL, URL: "u", OpaqueWireType: "thinking"}, true, nil}, + {"url without image kind rejected", ContentPart{URL: "https://x"}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"opaque carrying text rejected", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", Text: "a"}, true, nil}, + {"opaque carrying url rejected", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", URL: "u"}, true, nil}, + {"opaque missing wire type rejected", ContentPart{Kind: PartOpaque}, true, []error{ErrMissingField}}, + {"malformed extra", ContentPart{Kind: PartText, Text: "a", Extra: Extra{"raw": json.RawMessage("not json")}}, true, nil}, + {"empty extra value", ContentPart{Kind: PartText, Text: "a", Extra: Extra{"e": json.RawMessage{}}}, true, nil}, + } + + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + out, newErr := NewContentPart(row.part) + validateErr := ValidateContentPart(row.part) + requireEqualConstructorVerdict(t, row.name, newErr, validateErr) + testassert.Require(t, (newErr != nil) == row.wantErr, "fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) + if len(row.wantIsAny) > 0 { + testassert.Require(t, matchesAnySentinel(newErr, row.wantIsAny...) && matchesAnySentinel(validateErr, row.wantIsAny...), "fixture %s: errors = %v / %v, want one of %v", row.name, newErr, validateErr, row.wantIsAny) } - if out.Kind != tc.part.Kind || out.Text != tc.part.Text || out.URL != tc.part.URL || out.OpaqueWireType != tc.part.OpaqueWireType { - t.Fatalf("part not preserved: %#v", out) + if !row.wantErr { + testassert.Require(t, out.Kind == row.part.Kind && out.Text == row.part.Text && out.URL == row.part.URL && out.OpaqueWireType == row.part.OpaqueWireType, "part not preserved: %#v", out) } }) } - cases := []struct { - name string - part ContentPart - }{ - {name: "unknown kind rejected", part: ContentPart{Kind: PartKind("image")}}, - {name: "empty kind rejected", part: ContentPart{Text: "hi"}}, - {name: "text with url rejected", part: ContentPart{Kind: PartText, Text: "a", URL: "b"}}, - {name: "url without image kind rejected", part: ContentPart{URL: "https://x"}}, - {name: "opaque missing wire type rejected", part: ContentPart{Kind: PartOpaque}}, - } - for _, tc := range cases { - t.Run(tc.name+" rejects", func(t *testing.T) { - if _, err := NewContentPart(tc.part); !errors.Is(err, ErrForbiddenField) && !errors.Is(err, ErrMissingField) { - t.Fatalf("error = %v, want ErrForbiddenField or ErrMissingField", err) - } - }) + // The validator mutates nothing: a value-equal sibling still compares + // equal after the call, extras included. + in := ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png", Extra: Extra{"detail": json.RawMessage(`"high"`)}} + before := ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png", Extra: Extra{"detail": json.RawMessage(`"high"`)}} + { + err := ValidateContentPart(in) + testassert.NoError(t, err) } + testassert.Require(t, reflect.DeepEqual(in, before), "%v", "ValidateContentPart mutated its input") opaqueIn := ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking"} outOpaq, err := NewContentPart(opaqueIn) - if err != nil || outOpaq.Kind != PartOpaque || outOpaq.OpaqueWireType != "thinking" { - t.Fatalf("opaque part = %#v (err %v)", outOpaq, err) - } + testassert.Require(t, err == nil && outOpaq.Kind == PartOpaque && outOpaq.OpaqueWireType == "thinking", "opaque part = %#v (err %v)", outOpaq, err) srcRaw := json.RawMessage(`"x"`) partIn := ContentPart{Kind: PartText, Text: "a", Extra: Extra{"k": srcRaw}} out, err := NewContentPart(partIn) - if err != nil { - t.Fatalf("NewContentPart returned error: %v", err) - } + testassert.NoError(t, err) srcRaw[1] = 'X' // corrupt the caller's extra bytes after construction. storedExtra := out.Extra["k"] - if string(storedExtra) != `"x"` || &storedExtra[0] == &srcRaw[0] { - t.Fatalf("part extra not deep copied at accepting boundary: %s", storedExtra) - } + testassert.Require(t, string(storedExtra) == `"x"` && &storedExtra[0] != &srcRaw[0], "part extra not deep copied at accepting boundary: %s", storedExtra) } // TestMessageHasOnlyContractFields pins that Message, Output, Request and the @@ -256,17 +259,13 @@ func expectExactStruct(t *testing.T, typeName string, v any, wantNames []string) t.Helper() got := structFieldNames(v) for _, w := range wantNames { - if !got[w] { - t.Fatalf("%s missing contract field %q: %#v", typeName, w, got) - } + testassert.Require(t, got[w], "%s missing contract field %q: %#v", typeName, w, got) delete(got, w) } for extra := range got { t.Fatalf("%s has non-contract field %q (persistence/display state must be absent)", typeName, extra) } - if len(wantNames) != 0 && reflect.ValueOf(v).Type().NumField() != len(wantNames)+len(got) { - t.Fatalf("%s field count mismatch: %#v", typeName, got) - } + testassert.Require(t, len(wantNames) == 0 || reflect.ValueOf(v).Type().NumField() == len(wantNames)+len(got), "%s field count mismatch: %#v", typeName, got) } func structFieldNames(v any) map[string]bool { @@ -287,31 +286,29 @@ func structFieldNames(v any) map[string]bool { func TestTextHelpers(t *testing.T) { msg := mustMessage(t, Message{Role: RoleUser}) img, err := NewContentPart(ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png"}) - if err != nil { - t.Fatalf("NewContentPart returned error: %v", err) - } + testassert.NoError(t, err) msg.Content = append(msg.Content, ContentPart{Kind: PartText, Text: "hello"}) msg.AppendText(" world") msg.Content = append(msg.Content, img) msg.AppendText(" again") - if got := msg.TextContent(); got != "hello world again" { - t.Fatalf("TextContent = %q, want joined text parts in order", got) + { + got := msg.TextContent() + testassert.Equal(t, got, "hello world again") } empty := mustMessage(t, Message{Role: RoleUser}) empty.AppendText("") - if len(empty.Content) != 0 { - t.Fatal(`AppendText("") created a content part`) - } + testassert.Require(t, len(empty.Content) == 0, "%v", `AppendText("") created a content part`) mixed := mustMessage(t, Message{Role: RoleAssistant, Source: fullRef}) img2, _ := NewContentPart(ContentPart{Kind: PartImageURL, URL: "u"}) opaque, _ := NewContentPart(ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking"}) mixed.Content = []ContentPart{{Kind: PartText, Text: "a"}, img2, opaque} - if got := mixed.TextContent(); got != "a" { - t.Fatalf("non-text parts must be skipped by TextContent: %q", got) + { + got := mixed.TextContent() + testassert.Equal(t, got, "a") } appendedToLastOnly := mustMessage(t, Message{Role: RoleUser}) @@ -319,15 +316,12 @@ func TestTextHelpers(t *testing.T) { img3, _ := NewContentPart(ContentPart{Kind: PartImageURL, URL: "u2"}) appendedToLastOnly.Content = []ContentPart{txt} appendedToLastOnly.AppendText("CDE") // extends the trailing text part. - if appendedToLastOnly.Content[0].Text != "ABCDE" { - t.Fatalf("AppendText must extend the trailing text part: %#v", appendedToLastOnly.Content) - } + testassert.Require(t, appendedToLastOnly.Content[0].Text == "ABCDE", "AppendText must extend the trailing text part: %#v", appendedToLastOnly.Content) appendedToLastOnly.Content = append(appendedToLastOnly.Content, img3) appendedToLastOnly.AppendText("FGH") // creates a new text part after non-text. - if len(appendedToLastOnly.Content) != 3 || appendedToLastOnly.Content[2].Kind != PartText || appendedToLastOnly.Content[2].Text != "FGH" { - t.Fatalf("AppendText must create a text part when the last is not text: %#v", appendedToLastOnly.Content) - } - if got := appendedToLastOnly.TextContent(); got != "ABCDEFGH" { - t.Fatalf("joined = %q, want parts concatenated in order without separators (legacy semantics)", got) + testassert.Require(t, len(appendedToLastOnly.Content) == 3 && appendedToLastOnly.Content[2].Kind == PartText && appendedToLastOnly.Content[2].Text == "FGH", "AppendText must create a text part when the last is not text: %#v", appendedToLastOnly.Content) + { + got := appendedToLastOnly.TextContent() + testassert.Equal(t, got, "ABCDEFGH") } } diff --git a/model/model_ref_test.go b/model/model_ref_test.go index f65491d9..8efed273 100644 --- a/model/model_ref_test.go +++ b/model/model_ref_test.go @@ -4,18 +4,17 @@ import ( "encoding/json" "errors" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestParseSplitsOnFirstSlash(t *testing.T) { ref, err := Parse("openrouter/openai/gpt-5.4-mini") - if err != nil { - t.Fatalf("Parse returned error: %v", err) - } - if ref.Provider != "openrouter" || ref.Model != "openai/gpt-5.4-mini" { - t.Fatalf("unexpected ref: %#v", ref) - } - if got := ref.String(); got != "openrouter/openai/gpt-5.4-mini" { - t.Fatalf("String() = %q", got) + testassert.NoError(t, err) + testassert.Require(t, ref.Provider == "openrouter" && ref.Model == "openai/gpt-5.4-mini", "unexpected ref: %#v", ref) + { + got := ref.String() + testassert.Equal(t, got, "openrouter/openai/gpt-5.4-mini") } } @@ -33,19 +32,13 @@ func TestModelRefStringIsParseableWhenNonEmpty(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := tc.ref.String() - if got != tc.want { - t.Fatalf("String() = %q, want %q", got, tc.want) - } + testassert.Equal(t, got, tc.want) if got == "" { return } parsed, err := Parse(got) - if err != nil { - t.Fatalf("Parse(%q) returned error: %v", got, err) - } - if parsed != tc.ref { - t.Fatalf("parsed = %#v, want %#v", parsed, tc.ref) - } + testassert.Require(t, err == nil, "Parse(%q) returned error: %v", got, err) + testassert.Equal(t, parsed, tc.ref) }) } } @@ -53,20 +46,15 @@ func TestModelRefStringIsParseableWhenNonEmpty(t *testing.T) { func TestModelRefJSONUsesPrefixString(t *testing.T) { ref := ModelRef{Provider: "openai", Model: "gpt-5.4-mini"} b, err := json.Marshal(ref) - if err != nil { - t.Fatalf("Marshal returned error: %v", err) - } - if string(b) != `"openai/gpt-5.4-mini"` { - t.Fatalf("Marshal = %s", b) - } + testassert.NoError(t, err) + testassert.Require(t, string(b) == `"openai/gpt-5.4-mini"`, "Marshal = %s", b) var parsed ModelRef - if err := json.Unmarshal(b, &parsed); err != nil { - t.Fatalf("Unmarshal returned error: %v", err) - } - if parsed != ref { - t.Fatalf("parsed = %#v, want %#v", parsed, ref) + { + err := json.Unmarshal(b, &parsed) + testassert.NoError(t, err) } + testassert.Equal(t, parsed, ref) } func TestModelRefJSONZeroMarshalsAsEmptyString(t *testing.T) { @@ -76,12 +64,8 @@ func TestModelRefJSONZeroMarshalsAsEmptyString(t *testing.T) { ref = ModelRef{Provider: "openai"} } b, err := json.Marshal(ref) - if err != nil { - t.Fatalf("%s Marshal returned error: %v", name, err) - } - if string(b) != `""` { - t.Fatalf("%s Marshal = %s, want \"\"", name, b) - } + testassert.Require(t, err == nil, "%s Marshal returned error: %v", name, err) + testassert.Require(t, string(b) == `""`, "%s Marshal = %s, want \"\"", name, b) } } @@ -89,28 +73,21 @@ func TestModelRefJSONUnmarshalRejectsInvalidStrings(t *testing.T) { for _, input := range []string{`null`, `""`, `"openai"`, `"/gpt-5.4-mini"`, `"openai/"`} { var ref ModelRef err := json.Unmarshal([]byte(input), &ref) - if !errors.Is(err, ErrInvalidModelRef) { - t.Fatalf("Unmarshal(%s) error = %v, want ErrInvalidModelRef", input, err) - } + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "Unmarshal(%s) error = %v, want ErrInvalidModelRef", input, err) } } func TestParseRejectsMissingParts(t *testing.T) { for _, input := range []string{"", "openai", "/gpt-5.4-mini", "openai/"} { - if _, err := Parse(input); !errors.Is(err, ErrInvalidModelRef) { - t.Fatalf("Parse(%q) error = %v, want ErrInvalidModelRef", input, err) + { + _, err := Parse(input) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "Parse(%q) error = %v, want ErrInvalidModelRef", input, err) } } } func TestModelRefIsZero(t *testing.T) { - if !(ModelRef{}).IsZero() { - t.Fatal("zero ref did not report IsZero") - } - if (ModelRef{Provider: "openai"}).IsZero() { - t.Fatal("partial ref reported IsZero") - } - if (ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}).IsZero() { - t.Fatal("complete ref reported IsZero") - } + testassert.Require(t, (ModelRef{}).IsZero(), "%v", "zero ref did not report IsZero") + testassert.Require(t, !(ModelRef{Provider: "openai"}).IsZero(), "%v", "partial ref reported IsZero") + testassert.Require(t, !(ModelRef{Provider: "openai", Model: "gpt-5.4-mini"}).IsZero(), "%v", "complete ref reported IsZero") } diff --git a/model/output_request_test.go b/model/output_request_test.go index 18934673..276d33bf 100644 --- a/model/output_request_test.go +++ b/model/output_request_test.go @@ -3,7 +3,11 @@ package model import ( "encoding/json" "errors" + "reflect" + "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // assistantMsg builds a valid assistant message on the full test source and applies an optional mutation to it. @@ -16,194 +20,116 @@ func assistantMsg(t *testing.T, mutate func(*Message)) Message { return m } -// TestNewOutputStatusAndSourceRules pins the closed statuses, source rules and detail rules. -func TestNewOutputStatusAndSourceRules(t *testing.T) { - textMsg := assistantMsg(t, func(m *Message) { - p, _ := NewContentPart(ContentPart{Kind: PartText, Text: "done"}) - m.Content = []ContentPart{p} +// TestNewOutputCases pins the Output accepting boundary through both entry +// points: one inventory of status, source, message and payload shapes, each +// with its original expected verdict and error class/detail where the owning +// test asserted one. The constructor's field preservation and the validator's +// non-mutation observation stay alongside the table. +func TestNewOutputCases(t *testing.T) { + textMsg := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText, Text: "done"}} }) + noPayloadMsg := assistantMsg(t, nil) + userRoleMsg := mustMessage(t, Message{Role: RoleUser}) + payloadMsg := assistantMsg(t, func(m *Message) { + m.Content = []ContentPart{{Kind: PartText, Text: "x"}} + c, _ := NewToolCall(ToolCall{ID: "c1", Name: "f", Arguments: json.RawMessage(`{oops`)}) + m.ToolCalls = []ToolCall{c} + }) + msgWithCalls := assistantMsg(t, func(m *Message) { + c, _ := NewToolCall(ToolCall{ID: "c1", Name: "f"}) + m.ToolCalls = []ToolCall{c} }) + msgImage := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartImageURL, URL: "https://example.com/a.png"}} }) + msgOpaque := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartOpaque, OpaqueWireType: "thinking"}} }) + extraOnly := Extra{"k": json.RawMessage(`"v"`)} + partWithExtra := ContentPart{Kind: PartText, Extra: extraOnly} + msgExtraOnly := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{partWithExtra} }) + refusalOnly, _ := NewMessage(Message{Role: RoleAssistant, Source: fullRef, Refusal: "no"}) + mismatchedMsg := assistantMsg(t, nil) + msgEmptyPart := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText}} }) + nullExtraOnly := Extra{"n": json.RawMessage(`null`)} + partNullExtra := ContentPart{Kind: PartText, Extra: nullExtraOnly} + msgNullExtraPart := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{partNullExtra} }) + msgNameOnly := assistantMsg(t, func(m *Message) { m.Name = "worker" }) - valid := []struct { - name string - status OutputStatus - msg *Message - detail string + rows := []struct { + name string + out Output + wantErr bool + wantIsAny []error + contains string }{ - {name: "completed with text", status: OutputCompleted, msg: &textMsg, detail: ""}, - { - name: "tool-call-only assistant is valid completed payload", - status: OutputCompleted, - msg: func() *Message { - m := assistantMsg(t, nil) - c, _ := NewToolCall(ToolCall{ID: "c1", Name: "f"}) - m.ToolCalls = []ToolCall{c} - return &m - }(), - detail: "", - }, - {name: "errored omits message, requires detail", status: OutputErrored, msg: nil, detail: "protocol chunk parse"}, - {name: "interrupted with partial text and detail", status: OutputInterrupted, msg: &textMsg, detail: "agent interrupted"}, - } - for _, tc := range valid { - t.Run(tc.name+" accepts", func(t *testing.T) { - out, err := NewOutput(Output{Status: tc.status, Source: fullRef, Message: tc.msg, Detail: tc.detail}) - if err != nil { - t.Fatalf("NewOutput returned error: %v", err) - } - if out.Status != tc.status || out.Source != fullRef || out.Detail != tc.detail { - t.Fatalf("output not preserved: %#v", out) + {"rich completed", richOutputFixture(), false, nil, ""}, + {"completed with text", Output{Status: OutputCompleted, Source: fullRef, Message: &textMsg}, false, nil, ""}, + {"tool-call-only assistant is valid completed payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgWithCalls}, false, nil, ""}, + {"errored without message", Output{Status: OutputErrored, Source: fullRef, Detail: "boom"}, false, nil, ""}, + {"interrupted with partial", Output{Status: OutputInterrupted, Source: fullRef, Message: &textMsg, Detail: "stopped"}, false, nil, ""}, + {"image url only counts as payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgImage}, false, nil, ""}, + {"opaque wire type alone counts as payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgOpaque}, false, nil, ""}, + {"part with only a finalized non-null extra counts as payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgExtraOnly}, false, nil, ""}, + {"refusal only counts as payload", Output{Status: OutputCompleted, Source: fullRef, Message: &refusalOnly}, false, nil, ""}, + {"invalid status", Output{Status: OutputStatus("done"), Source: fullRef, Detail: "d"}, true, []error{ErrInvalidStatus}, "invalid status"}, + {"empty status", Output{Status: OutputStatus(""), Source: fullRef, Detail: "d"}, true, []error{ErrInvalidStatus}, ""}, + {"uppercase status", Output{Status: OutputStatus("COMPLETED"), Source: fullRef, Detail: "d"}, true, []error{ErrInvalidStatus}, ""}, + {"zero source", Output{Status: OutputCompleted, Message: &textMsg}, true, []error{ErrMissingSource}, "complete source model identity"}, + {"partial source", Output{Status: OutputErrored, Source: ModelRef{Provider: "openai"}, Detail: "d"}, true, []error{ErrMissingSource}, ""}, + {"completed without message", Output{Status: OutputCompleted, Source: fullRef}, true, nil, "requires one assistant message"}, + {"completed without payload", Output{Status: OutputCompleted, Source: fullRef, Message: &noPayloadMsg}, true, nil, "requires an assistant payload"}, + {"empty text part with no extras is not a payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgEmptyPart}, true, nil, ""}, + {"part whose only extra finalizes to null is not a payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgNullExtraPart}, true, nil, ""}, + {"name does not count as payload", Output{Status: OutputCompleted, Source: fullRef, Message: &msgNameOnly}, true, nil, ""}, + {"completed with detail", Output{Status: OutputCompleted, Source: fullRef, Message: &textMsg, Detail: "boom"}, true, nil, "must carry empty detail"}, + {"errored without detail", Output{Status: OutputErrored, Source: fullRef}, true, []error{ErrMissingField}, "requires non-empty detail"}, + {"interrupted with tool calls", Output{Status: OutputInterrupted, Source: fullRef, Message: &payloadMsg, Detail: "stopped"}, true, nil, "must not carry tool calls"}, + {"message source mismatch", Output{Status: OutputCompleted, Source: ModelRef{Provider: "acme", Model: "other"}, Message: &mismatchedMsg}, true, []error{ErrSourceMismatch}, "differs from output source"}, + {"message source mismatch openrouter", Output{Status: OutputCompleted, Source: ModelRef{Provider: "openrouter", Model: "other"}, Message: &mismatchedMsg}, true, []error{ErrSourceMismatch}, ""}, + {"non-assistant message", Output{Status: OutputCompleted, Source: fullRef, Message: &userRoleMsg}, true, []error{ErrInvalidRole}, "carry only assistant messages"}, + } + + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + out, newErr := NewOutput(row.out) + validateErr := ValidateOutput(row.out) + requireEqualConstructorVerdict(t, row.name, newErr, validateErr) + testassert.Require(t, (newErr != nil) == row.wantErr, "fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) + if len(row.wantIsAny) > 0 { + testassert.Require(t, matchesAnySentinel(newErr, row.wantIsAny...) && matchesAnySentinel(validateErr, row.wantIsAny...), "fixture %s: errors = %v / %v, want one of %v", row.name, newErr, validateErr, row.wantIsAny) } - if (tc.msg == nil) != (out.Message == nil) { - t.Fatal("message presence changed") + testassert.Require(t, row.contains == "" || (strings.Contains(newErr.Error(), row.contains) && strings.Contains(validateErr.Error(), row.contains)), "fixture %s reached the wrong rule: %v / %v, want a failure naming %q", row.name, newErr, validateErr, row.contains) + if !row.wantErr { + testassert.Require(t, out.Status == row.out.Status && out.Source == row.out.Source && out.Detail == row.out.Detail && (row.out.Message == nil) == (out.Message == nil), "output not preserved: %#v", out) } }) } - cases := []struct { - name string - out Output - wantIs error // nil means any non-nil error is acceptable. - }{ - {name: "zero source rejected", out: Output{Status: OutputCompleted, Message: &textMsg}, wantIs: ErrMissingSource}, - {name: "partial source rejected", out: Output{Status: OutputErrored, Source: ModelRef{Provider: "openai"}, Detail: "d"}, wantIs: ErrMissingSource}, - {name: "completed without message rejected", out: Output{Status: OutputCompleted, Source: fullRef}}, - } - for _, tc := range cases { - t.Run(tc.name+" rejects", func(t *testing.T) { - if _, err := NewOutput(tc.out); err == nil { - t.Fatal("invalid output accepted") - } else if tc.wantIs != nil && !errors.Is(err, tc.wantIs) { - t.Fatalf("error = %v, want errors.Is(%v)", err, tc.wantIs) - } - }) - } - - for i, status := range []string{"", "done", "COMPLETED"} { - outIn := Output{Status: OutputStatus(status), Source: fullRef, Detail: "d"} - if _, err := NewOutput(outIn); !errors.Is(err, ErrInvalidStatus) { - t.Fatalf("case %d status=%q error = %v, want ErrInvalidStatus", i, status, err) - } - } - - mismatchedMsg := assistantMsg(t, nil) - outIn := Output{Status: OutputCompleted, Source: ModelRef{Provider: "openrouter", Model: "other"}, Message: &mismatchedMsg, Detail: ""} - if _, err := NewOutput(outIn); !errors.Is(err, ErrSourceMismatch) { - t.Fatalf("message source != output source error = %v, want ErrSourceMismatch", err) - } - - completedWithDetail := Output{Status: OutputCompleted, Source: fullRef, Message: &textMsg, Detail: "boom"} - if _, err := NewOutput(completedWithDetail); err == nil { - t.Fatal("completed with non-empty detail accepted") - } - erroredNoDetail := Output{Status: OutputErrored, Source: fullRef, Detail: ""} - if _, err := NewOutput(erroredNoDetail); !errors.Is(err, ErrMissingField) { - t.Fatalf("errored without detail error = %v, want ErrMissingField", err) - } - - msgWithCalls := assistantMsg(t, func(m *Message) { - c, _ := NewToolCall(ToolCall{ID: "c1", Name: "f"}) - m.ToolCalls = []ToolCall{c} - }) - interruptedWithCalls := Output{Status: OutputInterrupted, Source: fullRef, Message: &msgWithCalls, Detail: "agent interrupted"} - if _, err := NewOutput(interruptedWithCalls); err == nil { - t.Fatal("interrupted output with tool calls accepted") - } - - userRoleMsg := mustMessage(t, Message{Role: RoleUser}) - wrongRoleOut := Output{Status: OutputCompleted, Source: fullRef, Message: &userRoleMsg, Detail: ""} - if _, err := NewOutput(wrongRoleOut); !errors.Is(err, ErrInvalidRole) { - t.Fatalf("output message with non-assistant role error = %v, want ErrInvalidRole", err) + // The validator mutates nothing: an independently built value-equal + // sibling compares equal after the call, nested reference types included. + in := richOutputFixture() + before := richOutputFixture() + { + err := ValidateOutput(in) + testassert.NoError(t, err) } + testassert.Require(t, reflect.DeepEqual(in, before), "%v", "ValidateOutput mutated its input") var zeroUsage Usage outWithZeroUsage, err := NewOutput(Output{Status: OutputCompleted, Source: fullRef, Message: &textMsg, Usage: &zeroUsage}) - if err != nil || outWithZeroUsage.Usage == nil { - t.Fatalf("present all-zero usage must be retained (known zero): %#v (err %v)", outWithZeroUsage, err) - } + testassert.Require(t, err == nil && outWithZeroUsage.Usage != nil, "present all-zero usage must be retained (known zero): %#v (err %v)", outWithZeroUsage, err) outNoUsage := Output{Status: OutputCompleted, Source: fullRef, Message: &textMsg} gotOut, _ := NewOutput(outNoUsage) - if gotOut.Usage != nil { - t.Fatalf("absent usage must stay absent (unknown/not reported): %#v", gotOut.Usage) - } + testassert.Require(t, gotOut.Usage == nil, "absent usage must stay absent (unknown/not reported): %#v", gotOut.Usage) negIn := Usage{InputTokens: -1, CachedInputTokens: 0, OutputTokens: -2} gotNeg, err := NewOutput(Output{Status: OutputErrored, Source: fullRef, Detail: "d", Usage: &negIn}) - if err != nil || gotNeg.Usage == nil || *gotNeg.Usage != negIn { - t.Fatalf("signed int usage not preserved verbatim: %#v (err %v)", gotNeg.Usage, err) - } + testassert.Require(t, err == nil && gotNeg.Usage != nil && *gotNeg.Usage == negIn, "signed int usage not preserved verbatim: %#v (err %v)", gotNeg.Usage, err) srcRaw := json.RawMessage(`"thinking"`) extraMsgSrc := assistantMsg(t, func(m *Message) { m.Extra = Extra{"reasoning_content": srcRaw} }) gotExtraOut, err := NewOutput(Output{Status: OutputErrored, Source: fullRef, Message: &extraMsgSrc, Detail: "d"}) - if err != nil || gotExtraOut.Message == nil { - t.Fatalf("NewOutput returned error or dropped message: %v", err) - } + testassert.Require(t, err == nil && gotExtraOut.Message != nil, "NewOutput returned error or dropped message: %v", err) srcRaw[1] = 'X' // corrupt caller bytes after construction. stored := string(gotExtraOut.Message.Extra["reasoning_content"]) - if len(stored) < 2 || stored[:2] != `"t` { - t.Fatalf("output message extra not deep copied at accepting boundary: %s", gotExtraOut.Message.Extra["reasoning_content"]) - } - -} - -// TestNewOutputAssistantPayloadDefinition pins what counts as payload and what does not. -func TestNewOutputAssistantPayloadDefinition(t *testing.T) { - msgText := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText, Text: "x"}} }) - msgImage := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartImageURL, URL: "https://example.com/a.png"}} }) - msgOpaque := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartOpaque, OpaqueWireType: "thinking"}} }) - extraOnly := Extra{"k": json.RawMessage(`"v"`)} - partWithExtra := ContentPart{Kind: PartText, Extra: extraOnly} - msgExtraOnly := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{partWithExtra} }) - - valid := []struct { - name string - out Output - }{ - {name: "non-empty text part", out: Output{Status: OutputCompleted, Source: fullRef, Message: &msgText}}, - {name: "image url only counts as payload", out: Output{Status: OutputCompleted, Source: fullRef, Message: &msgImage}}, - {name: "opaque wire type alone counts as payload", out: Output{Status: OutputCompleted, Source: fullRef, Message: &msgOpaque}}, - {name: "part with only a finalized non-null extra counts as payload", out: Output{Status: OutputCompleted, Source: fullRef, Message: &msgExtraOnly}}, - } - for _, tc := range valid { - t.Run(tc.name+" accepts", func(t *testing.T) { - if _, err := NewOutput(tc.out); err != nil { - t.Fatalf("NewOutput returned error: %v", err) - } - }) - } - - refusalOnly, _ := NewMessage(Message{Role: RoleAssistant, Source: fullRef, Refusal: "no"}) - if _, err := NewOutput(Output{Status: OutputCompleted, Source: fullRef, Message: &refusalOnly}); err != nil { - t.Fatalf("non-empty refusal must count as payload: %v", err) - } - - msgEmptyPart := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText}} }) - nullExtraOnly := Extra{"n": json.RawMessage(`null`)} - partNullExtra := ContentPart{Kind: PartText, Extra: nullExtraOnly} - msgNullExtraPart := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{partNullExtra} }) - msgNameOnly := assistantMsg(t, func(m *Message) { m.Name = "worker" }) - - noPayloadCases := []struct { - name string - msg *Message - }{ - {name: "empty text part with no extras is not a payload", msg: &msgEmptyPart}, - {name: "part whose only extra finalizes to null is not a payload", msg: &msgNullExtraPart}, - {name: "name does not count as payload", msg: &msgNameOnly}, - } - for _, tc := range noPayloadCases { - t.Run(tc.name+" rejects completed output", func(t *testing.T) { - if _, err := NewOutput(Output{Status: OutputCompleted, Source: fullRef, Message: tc.msg}); err == nil { - t.Fatal("completed output without eligible payload accepted") - } - }) - } - - bareAssistant := assistantMsg(t, nil) - if _, err := NewOutput(Output{Status: OutputCompleted, Source: fullRef, Message: &bareAssistant}); err == nil { - t.Fatal("bare assistant message (no parts/refusal/calls/extras) accepted as completed payload") - } + testassert.Require(t, len(stored) >= 2 && stored[:2] == `"t`, "output message extra not deep copied at accepting boundary: %s", gotExtraOut.Message.Extra["reasoning_content"]) } // TestAssistantContentAndPayloadQueries pins the two shared scalar queries against their finalization view: content is parts, refusal and surviving extras; payload adds tool calls; a nil message answers false to both; extras are read without validation. @@ -231,79 +157,111 @@ func TestAssistantContentAndPayloadQueries(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - if got := HasAssistantContent(tc.msg); got != tc.content { - t.Fatalf("HasAssistantContent = %v, want %v", got, tc.content) + { + got := HasAssistantContent(tc.msg) + testassert.Equal(t, got, tc.content) } - if got := hasAssistantPayload(tc.msg); got != tc.payload { - t.Fatalf("hasAssistantPayload = %v, want %v", got, tc.payload) + { + got := hasAssistantPayload(tc.msg) + testassert.Equal(t, got, tc.payload) } }) } p := ContentPart{Kind: PartText, Extra: Extra{"k": json.RawMessage(`null`), "m": json.RawMessage(`0`)}} - if !ContentPartHasPayload(p) { - t.Fatal("part with one surviving extra reported empty") - } + testassert.Require(t, ContentPartHasPayload(p), "%v", "part with one surviving extra reported empty") empty := ContentPart{Kind: PartText, Extra: Extra{"k": json.RawMessage(`null`)}} - if ContentPartHasPayload(empty) { - t.Fatal("part with null-only extras reported payload") - } + testassert.Require(t, !ContentPartHasPayload(empty), "%v", "part with null-only extras reported payload") } -// TestNewRequest pins the logical request contract: owned messages + tools only. +// TestNewRequestValidAndOwnership pins the logical request contract through +// both entry points: one inventory of message and tool shapes with their +// original expected verdicts, the message pass ahead of the tools pass, and +// the duplicate-name rejection after the second tool's own validation, plus +// the constructor's owned-copy checks. func TestNewRequestValidAndOwnership(t *testing.T) { + validMessage := func() Message { + return assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText, Text: "hi"}} }) + } + validTool := func(name string) ToolDefinition { + return ToolDefinition{Name: name, Description: "d", Parameters: json.RawMessage(`{"type":"object"}`)} + } + + rows := []struct { + name string + req Request + wantErr bool + wantIsAny []error + prefix string // required leading text of the detail + contains string // required substring of the detail + }{ + {"valid messages and tools", Request{Messages: []Message{validMessage()}, Tools: []ToolDefinition{validTool("echo")}}, false, nil, "", ""}, + {"empty request", Request{}, false, nil, "", ""}, + {"tools only capture shape", Request{Tools: []ToolDefinition{validTool("echo")}}, false, nil, "", ""}, + {"invalid message", Request{Messages: []Message{{Role: Role("bogus")}}}, true, nil, "messages[0]:", ""}, + {"invalid message part", Request{Messages: []Message{{Role: RoleUser, Content: []ContentPart{{Kind: PartText, URL: "u"}}}}}, true, nil, "messages[0]: content[0]:", ""}, + {"message error beats tool error", Request{Messages: []Message{{Role: Role("bogus")}}, Tools: []ToolDefinition{{}}}, true, nil, "messages[0]:", ""}, + {"assistant message without source", Request{Messages: []Message{{Role: RoleAssistant}}}, true, []error{ErrMissingSource}, "", ""}, + {"invalid tool", Request{Tools: []ToolDefinition{{Name: "f"}}}, true, nil, "tools[0]:", ""}, + {"second tool invalid after a valid first", Request{Tools: []ToolDefinition{validTool("a"), {Name: "f"}}}, true, nil, "tools[1]:", ""}, + {"invalid definition with empty name and array parameters", Request{Tools: []ToolDefinition{{Parameters: json.RawMessage(`[1]`)}}}, true, []error{ErrMissingField, ErrInvalidParameters}, "", ""}, + {"duplicate name", Request{Tools: []ToolDefinition{validTool("echo"), validTool("echo")}}, true, []error{ErrDuplicateToolName}, "", "duplicate tool definition name"}, + {"duplicate name t", Request{Tools: []ToolDefinition{{Name: "t", Parameters: json.RawMessage(`{}`)}, {Name: "t", Parameters: json.RawMessage(`{}`)}}}, true, []error{ErrDuplicateToolName}, "", ""}, + {"duplicate reported after the second tool's own validation", Request{Tools: []ToolDefinition{validTool("echo"), {Name: "echo", Parameters: json.RawMessage(`{oops`)}}}, true, nil, "tools[1]:", "malformed JSON parameters"}, + } + + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + out, newErr := NewRequest(row.req) + validateErr := ValidateRequest(row.req) + requireEqualConstructorVerdict(t, row.name, newErr, validateErr) + testassert.Require(t, (newErr != nil) == row.wantErr, "fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) + if len(row.wantIsAny) > 0 { + testassert.Require(t, matchesAnySentinel(newErr, row.wantIsAny...) && matchesAnySentinel(validateErr, row.wantIsAny...), "fixture %s: errors = %v / %v, want one of %v", row.name, newErr, validateErr, row.wantIsAny) + } + testassert.Require(t, (row.prefix == "" || (strings.HasPrefix(newErr.Error(), row.prefix) && strings.HasPrefix(validateErr.Error(), row.prefix))) && (row.contains == "" || (strings.Contains(newErr.Error(), row.contains) && strings.Contains(validateErr.Error(), row.contains))), "fixture %s error = %q / %q, want the %q prefix; fixture %s reached the wrong rule: %v / %v, want a failure naming %q", row.name, newErr, validateErr, row.prefix, row.name, newErr, validateErr, row.contains) + if !row.wantErr { + testassert.Require(t, len(out.Messages) == len(row.req.Messages) && len(out.Tools) == len(row.req.Tools), "request shape not preserved: %#v", out) + for i, msg := range row.req.Messages { + testassert.Require(t, out.Messages[i].Role == msg.Role && out.Messages[i].Name == msg.Name && out.Messages[i].ToolCallID == msg.ToolCallID, "message %d not preserved: %#v", i, out.Messages[i]) + } + for i, def := range row.req.Tools { + testassert.Require(t, out.Tools[i].Name == def.Name && out.Tools[i].Description == def.Description && string(out.Tools[i].Parameters) == string(def.Parameters), "tool %d not preserved: %#v", i, out.Tools[i]) + } + } + }) + } + + // The validator mutates nothing: a value-equal sibling still compares + // equal after the call, nested reference types included. + validated := Request{Messages: []Message{validMessage()}, Tools: []ToolDefinition{validTool("echo")}} + before := Request{Messages: []Message{validMessage()}, Tools: []ToolDefinition{validTool("echo")}} + { + err := ValidateRequest(validated) + testassert.NoError(t, err) + } + testassert.Require(t, reflect.DeepEqual(validated, before), "%v", "ValidateRequest mutated its input") + msgs := []Message{mustMessage(t, Message{Role: RoleUser, Content: []ContentPart{{Kind: PartText, Text: "hi"}}})} d1, _ := NewToolDefinition(ToolDefinition{Name: "read_file", Parameters: json.RawMessage(`{"type":"object"}`)}) defs := []ToolDefinition{d1} reqIn := Request{Messages: msgs, Tools: defs} out, err := NewRequest(reqIn) - if err != nil { - t.Fatalf("NewRequest returned error: %v", err) - } - if len(out.Messages) != 1 || out.Messages[0].TextContent() != "hi" { - t.Fatalf("messages not preserved: %#v", out.Messages) - } - if len(out.Tools) != 1 || out.Tools[0].Name != "read_file" { - t.Fatalf("tools not preserved: %#v", out.Tools) - } + testassert.NoError(t, err) + testassert.Require(t, len(out.Messages) == 1 && out.Messages[0].TextContent() == "hi" && (len(out.Tools) == 1 && out.Tools[0].Name == "read_file"), "messages not preserved: %#v; tools not preserved: %#v", out.Messages, out.Tools) msgs[0].Content = nil // corrupt caller slices after construction. d1.Parameters = json.RawMessage(`{}`) - if len(out.Messages[0].Content) != 1 || out.Messages[0].TextContent() != "hi" { - t.Fatalf("owned request messages must survive input mutation: %#v", out.Messages) - } - - got, err := NewRequest(Request{}) - if err != nil || len(got.Messages) != 0 || len(got.Tools) != 0 { - t.Fatalf("empty request = %#v (err %v), want no messages or tools", got, err) - } + testassert.Require(t, len(out.Messages[0].Content) == 1 && out.Messages[0].TextContent() == "hi", "owned request messages must survive input mutation: %#v", out.Messages) defParams := json.RawMessage(`{"type":"object"}`) dIn := ToolDefinition{Name: "x", Parameters: defParams} out3, err := NewRequest(Request{Tools: []ToolDefinition{dIn}}) - if err != nil { - t.Fatalf("NewRequest returned error: %v", err) - } + testassert.NoError(t, err) defParams[1] = 'X' // corrupt caller bytes after construction. storedParams := string(out3.Tools[0].Parameters) - if len(storedParams) == 0 || storedParams[:2] != `{"` { - t.Fatalf("request tool parameters not deep copied: %s", out3.Tools[0].Parameters) - } - - badMsg := Request{Messages: []Message{{Role: RoleAssistant}}} // assistant without source. - if _, err := NewRequest(badMsg); !errors.Is(err, ErrMissingSource) { - t.Fatalf("request with invalid embedded message error = %v, want ErrMissingSource", err) - } - - if _, err := NewRequest(Request{Tools: []ToolDefinition{{Name: "", Parameters: json.RawMessage(`[1]`)}}}); !errors.Is(err, ErrMissingField) && !errors.Is(err, ErrInvalidParameters) { - t.Fatalf("request with invalid definition error = %v", err) - } - - dupA := ToolDefinition{Name: "t", Parameters: json.RawMessage(`{}`)} - if _, err := NewRequest(Request{Tools: []ToolDefinition{dupA, dupA}}); !errors.Is(err, ErrDuplicateToolName) { - t.Fatalf("duplicate tool name error = %v, want ErrDuplicateToolName", err) - } + testassert.Require(t, len(storedParams) != 0 && storedParams[:2] == `{"`, "request tool parameters not deep copied: %s", out3.Tools[0].Parameters) } // TestNewRequestNeverRetainsZeroLengthSliceBacking pins the ownership rule for request/message slices through their shared clone path (the one Encode re-runs at its trust boundary): a zero-length slice accepted with spare capacity must not retain caller-owned backing storage, so later appends on either side cannot see into each other. Representation is deliberately unpinned — nil or an independently allocated empty both satisfy the alias-free rule this row asserts behaviorally only. @@ -314,14 +272,10 @@ func TestNewRequestNeverRetainsZeroLengthSliceBacking(t *testing.T) { zeroCalls := fullCallsBase[:0] // zero length with that capacity now riding along on the caller's own array. inMsg, err := NewMessage(Message{Role: RoleAssistant, Source: fullRef, Content: zeroContent, ToolCalls: zeroCalls}) // valid assistant shape so the fixture reaches the clone path exactly as Encode's boundary would. - if err != nil { - t.Fatalf("NewRequest(fixture) rejected a valid message with spare-capacity empty slices: %v", err) - } + testassert.NoError(t, err) out, outErr := NewRequest(Request{Messages: []Message{inMsg}}) // same shared clone path the encoder invokes per call — this row judges ITS retention behavior. - if outErr != nil { - t.Fatalf("NewRequest returned error for valid spare-capacity empty slices: %v", outErr) - } + testassert.NoError(t, outErr) out.Messages[0].Content = append(out.Messages[0].Content, ContentPart{Kind: PartText, Text: "out-1"}, ContentPart{Kind: PartText, Text: "out-2"}) // retained-side appends into whatever backing it was given... (must land in storage the caller never owned). inMsg.Content = append(inMsg.Content, ContentPart{Kind: PartText, Text: "in-1"}, ContentPart{Kind: PartText, Text: "in-2"}) // ...and vice versa from the caller side. @@ -349,14 +303,16 @@ func TestNewOutputSourceIdentityComparedByFields(t *testing.T) { msgIn := assistantMsg(t, func(m *Message) { m.Source = splitOther; m.Content = []ContentPart{{Kind: PartText, Text: "x"}} }) outIn := Output{Status: OutputCompleted, Source: colliding, Message: &msgIn} - if _, err := NewOutput(outIn); !errors.Is(err, ErrSourceMismatch) { - t.Fatalf("rendering-colliding identities accepted; error = %v (want ErrSourceMismatch)", err) + { + _, err := NewOutput(outIn) + testassert.Require(t, errors.Is(err, ErrSourceMismatch), "rendering-colliding identities accepted; error = %v (want ErrSourceMismatch)", err) } slashyRef := ModelRef{Provider: "prov/er", Model: "model/x"} // complete identity with slashes in both fields. msgIn2 := assistantMsg(t, func(m *Message) { m.Source = slashyRef; m.Content = []ContentPart{{Kind: PartText, Text: "x"}} }) outIn2 := Output{Status: OutputCompleted, Source: slashyRef, Message: &msgIn2} - if _, err := NewOutput(outIn2); err != nil { - t.Fatalf("field-identical identities rejected: %v", err) + { + _, err := NewOutput(outIn2) + testassert.NoError(t, err) } } diff --git a/model/parse_chunk_test.go b/model/parse_chunk_test.go index 5ae659bf..6f1e6fe5 100644 --- a/model/parse_chunk_test.go +++ b/model/parse_chunk_test.go @@ -5,6 +5,8 @@ import ( "errors" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestParseStreamChunkRaw pins the fixed parser contract row by row: top-level chunk shape typing, usage normalization arithmetic with its single floor at zero, first-choice-only selection, raw pass-through of role/finish reasons and tool-call name/argument pieces, strict typing for every present non-null canonical field while null stays absent at each scope, closed content-kind classification with byte-for-byte extra retention under the retained denylists, per-event lowest-unused-index normalization for omitted tool indexes (with supplied duplicates tolerated), and protocol errors naming their exact offending shape for any wrong type. @@ -21,9 +23,8 @@ func TestParseStreamChunkRaw(t *testing.T) { // one table because every row exer // --- top-level shape and choice selection ----------------------------------------- {name: "emptyObjectIsZeroDelta", raw: json.RawMessage(`{}`), check: func(t *testing.T, d StreamDelta) { // the most degenerate valid chunk: nothing present at all must produce a fully-zero delta rather than any error or invented data. - if d.HasChoice || d.Usage != nil || len(d.ContentFragments) > 0 || len(d.ToolFragments) > 0 || len(d.MessageExtra) > 0 { - t.Fatalf("zero-payload delta = %#v; want every field at its zero value", d) // full rendering so any single invented byte is visible immediately. - } + // full rendering so any single invented byte is visible immediately. + testassert.Require(t, !d.HasChoice && d.Usage == nil && len(d.ContentFragments) <= 0 && len(d.ToolFragments) <= 0 && len(d.MessageExtra) <= 0, "zero-payload delta = %#v; want every field at its zero value", d) }}, {name: "choicelessUsageEventHasNoChoiceMarker", raw: json.RawMessage(`{"usage":{"prompt_tokens":10,"completion_tokens":4}}`), check: func(t *testing.T, d StreamDelta) { // a trailing usage-only chunk carries no choice at all — HasChoice must stay false so assemblers can distinguish "no delta data" from an empty-but-present one. @@ -35,9 +36,8 @@ func TestParseStreamChunkRaw(t *testing.T) { // one table because every row exer }}, {name: "usageNullStaysAbsent", raw: json.RawMessage(`{"choices":[],"usage":null}`), check: func(t *testing.T, d StreamDelta) { // explicit null usage is indistinguishable from absence per the fixed rule — inventing a zero Usage pointer here would make callers unable to tell "reported nothing" from "not reported at all". - if d.Usage != nil || d.HasChoice { - t.Fatalf("null-usage delta = %#v; want no usage and no choice marker", d) // full shape rendered for immediate triage. - } + // full shape rendered for immediate triage. + testassert.Require(t, d.Usage == nil && !d.HasChoice, "null-usage delta = %#v; want no usage and no choice marker", d) }}, {name: "topLevelScalarRejectedAsProtocolError", raw: json.RawMessage(`123`), errSub: "did not decode into the OpenAI-compatible chunk shape"}, @@ -65,35 +65,31 @@ func TestParseStreamChunkRaw(t *testing.T) { // one table because every row exer }}, {name: "usageDetailsAbsentMeansZeroCache", raw: json.RawMessage(`{"usage":{"prompt_tokens":8,"completion_tokens":2}}`), check: func(t *testing.T, d StreamDelta) { // absent details object normalizes to zero cached — the retained producer rule that a missing sub-object is NOT an error and contributes nothing. - if !d.UsageMatches(8, 0, 2) { - t.Fatalf("no-details usage = %+v; want input=8 (full prompt), cached=0, output=2", d.Usage) // full rendering for immediate triage of any arithmetic drift in this direction too. - } + // full rendering for immediate triage of any arithmetic drift in this direction too. + testassert.Require(t, d.UsageMatches(8, 0, 2), "no-details usage = %+v; want input=8 (full prompt), cached=0, output=2", d.Usage) }}, {name: "negativeCachedAndOutputPassThroughUnchanged", raw: json.RawMessage(`{"usage":{"prompt_tokens":5,"completion_tokens":-2,"prompt_tokens_details":{"cached_tokens":-3}}}`), check: func(t *testing.T, d StreamDelta) { // the retained signed-int producer clamps ONLY the uncached derivation: cached and output keep every bit of their as-reported value, negative included (no range or clamping policy may be invented on either). - if !d.UsageMatches(8, -3, -2) { // input = 5 - (-3) = 8 by signed arithmetic; the two negative fields must survive byte-for-byte as ints. - t.Fatalf("signed usage = %+v; want input=8 while cached=-3 and output=-2 pass through unchanged", d.Usage) // all three values rendered so a clamp added to either negative field — or removed from the uncached one — is visible at a glance. - } + // input = 5 - (-3) = 8 by signed arithmetic; the two negative fields must survive byte-for-byte as ints. + // all three values rendered so a clamp added to either negative field — or removed from the uncached one — is visible at a glance. + testassert.Require(t, d.UsageMatches(8, -3, -2), "signed usage = %+v; want input=8 while cached=-3 and output=-2 pass through unchanged", d.Usage) }}, {name: "floorAppliesToUncachedInputOnlyWhileOtherFieldsStayNegative", raw: json.RawMessage(`{"usage":{"prompt_tokens":1,"completion_tokens":-6,"prompt_tokens_details":{"cached_tokens":4}}}`), check: func(t *testing.T, d StreamDelta) { // the single max(prompt-cached, 0) floor fires here (1-4 → 0) while cached=4 and output=-6 remain untouched — proving the clamp's reach is exactly one field even when every other value on the same object is out of the nonnegative range. - if !d.UsageMatches(0, 4, -6) { - t.Fatalf("floored usage = %+v; want input=0 (floored from -3) with cached=4 and output=-6 untouched by any clamp", d.Usage) // full rendering pins each field's exact survival, so an over-reaching clamp trips this row on whichever field it touched. - } + // full rendering pins each field's exact survival, so an over-reaching clamp trips this row on whichever field it touched. + testassert.Require(t, d.UsageMatches(0, 4, -6), "floored usage = %+v; want input=0 (floored from -3) with cached=4 and output=-6 untouched by any clamp", d.Usage) }}, // --- raw pass-through fields -------------------------------------------------------- {name: "roleAndFinishReasonPassThroughVerbatim", raw: json.RawMessage(`{"choices":[{"delta":{"role":"assistant","content":"x"},"finish_reason":"stop"}]}`), check: func(t *testing.T, d StreamDelta) { // both values are RAW wire strings — normalization (developer→system etc.) is assembler work and must never happen at parse time or downstream replay would double-normalize. - if !d.HasChoice || d.Role != "assistant" || d.FinishReason != "stop" { - t.Fatalf("pass-through delta = %#v; want role=assistant finish_reason=stop verbatim", d) // full shape rendered so any accidental transformation is visible in one line of output. - } + // full shape rendered so any accidental transformation is visible in one line of output. + testassert.Require(t, d.HasChoice && d.Role == "assistant" && d.FinishReason == "stop", "pass-through delta = %#v; want role=assistant finish_reason=stop verbatim", d) }}, {name: "nullRoleRefusalAndFinishReasonStayAbsent", raw: json.RawMessage(`{"choices":[{"delta":{"role":null,"refusal":null},"finish_reason":null}]}`), check: func(t *testing.T, d StreamDelta) { // null means absent at every string-typed canonical field — a present-null coerced to "" would be indistinguishable from genuinely-supplied empty values in later assembly. - if !d.HasChoice || d.Role != "" || d.RefusalFragment != "" || d.FinishReason != "" { - t.Fatalf("null-fields delta = %#v; want all three string fields at their absent-empty value", d) // full rendering so any single null→value coercion is attributable to its exact field. - } + // full rendering so any single null→value coercion is attributable to its exact field. + testassert.Require(t, d.HasChoice && d.Role == "" && d.RefusalFragment == "" && d.FinishReason == "", "null-fields delta = %#v; want all three string fields at their absent-empty value", d) }}, {name: "wrongTypedFinishReasonRejectedNamingItsField", raw: json.RawMessage(`{"choices":[{"delta":{"content":"x"},"finish_reason":3}]}`), errSub: `choice.finish_reason has the wrong type (want string)`}, // present non-null values must carry their OpenAI-compatible JSON-string type — numbers are malformed input, never silent drops. @@ -131,9 +127,8 @@ func TestParseStreamChunkRaw(t *testing.T) { // one table because every row exer }}, {name: "presentEmptyStringContentStillProducesAFragment", raw: json.RawMessage(`{"choices":[{"delta":{"content":""}}]}`), check: func(t *testing.T, d StreamDelta) { // present-but-empty string content is NOT absent — it still yields one empty text fragment at position zero (the distinction matters for providers that signal stream phases via deliberate empty pieces). - if len(d.ContentFragments) != 1 || !d.FragIsText(0, "") { - t.Fatalf("empty-string-form fragments = %#v; want exactly ONE fragment with an EMPTY text piece at position zero", d.ContentFragments) // full rendering so "dropped entirely" vs "wrong kind/position" failures are distinguishable without re-running under verbose mode. - } + // full rendering so "dropped entirely" vs "wrong kind/position" failures are distinguishable without re-running under verbose mode. + testassert.Require(t, len(d.ContentFragments) == 1 && d.FragIsText(0, ""), "empty-string-form fragments = %#v; want exactly ONE fragment with an EMPTY text piece at position zero", d.ContentFragments) }}, {name: "arrayFormUsesWireIndicesAsPositionsWithClosedKindsAndExtras", raw: json.RawMessage(`{"choices":[{"delta":{"content":[{"type":"text","text":"visible"},{"type":"image_url","image_url":{"url":"http://x/y.png"}},{"type":"thinking","thoughts":"hidden","signature":"sig"}]}}]}`), check: func(t *testing.T, d StreamDelta) { // THE polymorphic content row: three parts in wire order exercising all three closed kinds plus opaque extra retention — positions are exactly the array indices and nothing else. @@ -175,9 +170,8 @@ func TestParseStreamChunkRaw(t *testing.T) { // one table because every row exer {name: "imageUrlMustBeAnObjectWhenPresent", raw: json.RawMessage(`{"choices":[{"delta":{"content":[{"type":"image_url","image_url":"http://direct"}]}}]}`), errSub: `field "image_url" has a wrong type (want JSON object)`}, // the OpenAI shape nests url inside an object — a bare string is malformed input at this scope and fails before any subfield reading happens. {name: "nullImageUrlLeavesAnEmptyURLFragment", raw: json.RawMessage(`{"choices":[{"delta":{"content":[{"type":"image_url","image_url":null}]}}]}`), check: func(t *testing.T, d StreamDelta) { // null image_url is absent-by-rule and yields an empty-URL fragment rather than inventing a value or erroring — same tolerance as every other optional subfield in this parser. - if len(d.ContentFragments) != 1 || !d.FragIsImageURL(0, "") { - t.Fatalf("null-image-url fragments = %#v; want one image_url fragment at position zero with an EMPTY url value", d.ContentFragments) // full rendering so the empty-vs-missing distinction is visible in output rather than requiring a debugger session to confirm which branch produced what. - } + // full rendering so the empty-vs-missing distinction is visible in output rather than requiring a debugger session to confirm which branch produced what. + testassert.Require(t, len(d.ContentFragments) == 1 && d.FragIsImageURL(0, ""), "null-image-url fragments = %#v; want one image_url fragment at position zero with an EMPTY url value", d.ContentFragments) }}, {name: "textPartNonCanonicalFieldsBecomeExtrasVerbatim", raw: json.RawMessage(`{"choices":[{"delta":{"content":[{"type":"text","text":"a","cache_control":{"type":"ephemeral"}}]}}]}`), check: func(t *testing.T, d StreamDelta) { // non-canonical part-scope keys (denylist is exactly type/text/image_url) are retained byte-for-byte — provider-specific annotations like cache control must survive round-trips for replay fidelity. @@ -291,15 +285,17 @@ func TestParseStreamChunkRaw(t *testing.T) { // one table because every row exer } return } - if err != nil { - t.Fatalf("parse failed unexpectedly: %v", err) // success rows must stay silent — any failure here means the strictness rules over-reached beyond their documented scope. - } + // success rows must stay silent — any failure here means the strictness rules over-reached beyond their documented scope. + testassert.NoError(t, err) r.check(t, d) }) } - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this table was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this table was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). + // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } } diff --git a/model/parse_fuzz_test.go b/model/parse_fuzz_test.go index fc9f8fc2..3be01753 100644 --- a/model/parse_fuzz_test.go +++ b/model/parse_fuzz_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // FuzzParseStreamChunk exercises the fixed parser's invariants over arbitrary bytes: it must never panic, every failure is a protocol error wrapping ErrProtocol with nothing else leaking through untyped, and every successful parse yields a delta that passes its own accepting boundary (NewStreamDelta) — nonnegative positions throughout, closed content kinds, well-formed extras at all scopes. Seeds are the retained legacy fixture payloads ported inline as authoritative real-world wire shapes plus minimal malformed inputs so interesting coverage starts from corpus rather than cold mutation. @@ -48,34 +50,36 @@ func FuzzParseStreamChunk(f *testing.F) { // fuzzing a pure function needs no se delta, err := parseStreamChunkRaw(json.RawMessage(raw)) if err != nil { - if !errors.Is(err, ErrProtocol) { // every failure path in this function wraps the umbrella sentinel — a raw stdlib error escaping here would break upstream classification for ALL stream-level failures at once... (this single check is what makes "typed protocol errors only" an executable guarantee rather than a code-review promise). - t.Fatalf("parseStreamChunkRaw returned non-protocol error: %v", err) // full cause rendered so the exact un-wrapped path that leaked it is visible alongside its input bytes for direct reproduction. - } + // every failure path in this function wraps the umbrella sentinel — a raw stdlib error escaping here would break upstream classification for ALL stream-level failures at once... (this single check is what makes "typed protocol errors only" an executable guarantee rather than a code-review promise). + // full cause rendered so the exact un-wrapped path that leaked it is visible alongside its input bytes for direct reproduction. + testassert.Require(t, errors.Is(err, ErrProtocol), "parseStreamChunkRaw returned non-protocol error: %v", err) return // successful-parse invariants below do not apply to failed parses — their contract IS the sentinel above and nothing more at this boundary... (further shape assertions on a zero delta would just re-encode what "error returned" already implies). } - if _, derr := NewStreamDelta(delta); derr != nil { // THE accepting-boundary invariant: parser output must always be constructible through the public validation path — any drift between internal normalization and external contract (negative positions, open kinds, malformed extras) surfaces here instead of at some later assembly stage where it would be far harder to attribute... (this is what keeps "parser satisfies its own consumer's entry point" continuously proven rather than assumed after each edit). - t.Fatalf("successfully-parsed delta fails NewStreamDelta validation: %v", derr) // both the validating error and implicit input bytes via f.Fuzz reproduction make this immediately actionable without additional instrumentation. + // THE accepting-boundary invariant: parser output must always be constructible through the public validation path — any drift between internal normalization and external contract (negative positions, open kinds, malformed extras) surfaces here instead of at some later assembly stage where it would be far harder to attribute... (this is what keeps "parser satisfies its own consumer's entry point" continuously proven rather than assumed after each edit). + // both the validating error and implicit input bytes via f.Fuzz reproduction make this immediately actionable without additional instrumentation. + { + _, derr := NewStreamDelta(delta) + testassert.NoError(t, derr) } - if delta.Usage != nil && delta.Usage.InputTokens < 0 { // usage normalization's single floor guarantees nonnegative UNCACHED input only; cached and output pass through as-reported signed ints with no range policy on either — pinning the floored field alone here keeps test space from inventing clamps the retained producer never had. - t.Fatalf("normalized uncached input carries a negative value: %+v", delta.Usage) // full struct rendered so any future regression on the one floored field is named directly against its original wire input for comparison. - } + // usage normalization's single floor guarantees nonnegative UNCACHED input only; cached and output pass through as-reported signed ints with no range policy on either — pinning the floored field alone here keeps test space from inventing clamps the retained producer never had. + // full struct rendered so any future regression on the one floored field is named directly against its original wire input for comparison. + testassert.Require(t, delta.Usage == nil || delta.Usage.InputTokens >= 0, "normalized uncached input carries a negative value: %+v", delta.Usage) for i, frag := range delta.ContentFragments { // content positions are nonnegative by construction (array index / fixed zero); NewStreamDelta above already re-validates this but naming it here keeps THIS target's coverage explicit independent of that helper ever changing... (defense-in-depth across two validation layers is cheap insurance against one being weakened while the other compensates silently). - if frag.Position < 0 { - t.Fatalf("content fragment[%d] has negative position %d", i, frag.Position) // index included so multi-fragment payloads attribute their failure to its exact wire slot rather than requiring a re-run with verbose output enabled. - } + // index included so multi-fragment payloads attribute their failure to its exact wire slot rather than requiring a re-run with verbose output enabled. + testassert.Require(t, frag.Position >= 0, "content fragment[%d] has negative position %d", i, frag.Position) } for i, frag := range delta.ToolFragments { // tool-call positions are ALWAYS concrete non-nil values from parsed output (omission is normalized before any delta leaves the parser) — pinning nil-ness here catches a regression where normalization gets skipped for some mutated input shape table tests don't cover... - if frag.Position == nil || *frag.Position < 0 { // grouped as one condition since both halves describe THE SAME contract ("concrete nonnegative position always") from complementary angles: absence-of-normalization vs negative-value-escape. - t.Fatalf("tool call fragment[%d] lacks a concrete normalized position (nil or negative)", i) // names the exact invariant violated so future readers find its source rule quickly instead of re-deriving scope containment relationships from scratch during triage. - } + // grouped as one condition since both halves describe THE SAME contract ("concrete nonnegative position always") from complementary angles: absence-of-normalization vs negative-value-escape. + // names the exact invariant violated so future readers find its source rule quickly instead of re-deriving scope containment relationships from scratch during triage. + testassert.Require(t, frag.Position != nil && *frag.Position >= 0, "tool call fragment[%d] lacks a concrete normalized position (nil or negative)", i) } - if len(delta.ToolFragments) > 0 && !delta.HasChoice { // tool fragments only ever accompany a present choice (they parse from inside that single first-choice's delta object) — the checkable direction of this correlation: fragments WITHOUT their enclosing choice marker would mean normalization ran on a scope whose parent was reported absent. - t.Fatalf("tool call fragments present without any choice in delta output") // names the exact invariant violated for direct attribution to its source rule during triage. - } + // tool fragments only ever accompany a present choice (they parse from inside that single first-choice's delta object) — the checkable direction of this correlation: fragments WITHOUT their enclosing choice marker would mean normalization ran on a scope whose parent was reported absent. + // names the exact invariant violated for direct attribution to its source rule during triage. + testassert.Require(t, len(delta.ToolFragments) <= 0 || delta.HasChoice, "tool call fragments present without any choice in delta output") }) } diff --git a/model/stream_delta_test.go b/model/stream_delta_test.go index 05b2a2aa..657a853b 100644 --- a/model/stream_delta_test.go +++ b/model/stream_delta_test.go @@ -4,10 +4,17 @@ import ( "encoding/json" "errors" "io" + "reflect" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) -// TestNewStreamDeltaDeepCopyAndPositions pins delta construction rules. +// TestNewStreamDeltaDeepCopyAndPositions pins the StreamDelta accepting rules +// through both entry points: one inventory of positioned fragments, closed +// content kinds and Extra scopes with their original expected verdicts, plus +// the constructor's deep-copy/field-preservation probes and the validator's +// non-mutation observation. func TestNewStreamDeltaDeepCopyAndPositions(t *testing.T) { contentSrc := json.RawMessage(`"x"`) pos0 := 1 // non-nil pointer position for the tool fragment. @@ -24,69 +31,83 @@ func TestNewStreamDeltaDeepCopyAndPositions(t *testing.T) { } out, err := NewStreamDelta(in) - if err != nil { - t.Fatalf("NewStreamDelta returned error: %v", err) - } - if !out.HasChoice || out.Role != "developer" || out.RefusalFragment != "no " || len(out.ContentFragments) != 2 || len(out.ToolFragments) != 1 { - t.Fatalf("delta not preserved: %#v", out) - } + testassert.NoError(t, err) + testassert.Require(t, out.HasChoice && out.Role == "developer" && out.RefusalFragment == "no " && len(out.ContentFragments) == 2 && len(out.ToolFragments) == 1, "delta not preserved: %#v", out) contentSrc[0] = 'X' // corrupt caller bytes after construction. stored := string(out.ContentFragments[0].Extra["k"]) - if stored == "" || stored[0] != '"' { - t.Fatalf("content fragment extra not deep copied at accepting boundary: %s", out.ContentFragments[0].Extra["k"]) - } + testassert.Require(t, stored != "" && stored[0] == '"', "content fragment extra not deep copied at accepting boundary: %s", out.ContentFragments[0].Extra["k"]) argSrc := json.RawMessage(`{"p":1}`) in2 := StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", ArgumentFragment: string(argSrc)}}} out2, err := NewStreamDelta(in2) - if err != nil { - t.Fatalf("NewStreamDelta returned error: %v", err) - } + testassert.NoError(t, err) argSrc[0] = 'X' // corrupt caller bytes after construction. gotArg := out2.ToolFragments[0].ArgumentFragment - if gotArg[:1] == "X" { - t.Fatalf("tool fragment argument shares bytes with input: %s", gotArg) - } + testassert.Require(t, gotArg[:1] != "X", "tool fragment argument shares bytes with input: %s", gotArg) usageIn := &Usage{InputTokens: 3, CachedInputTokens: 1, OutputTokens: 4} out3, err := NewStreamDelta(StreamDelta{HasChoice: true, Usage: usageIn}) - if err != nil || out3.Usage == nil || *out3.Usage != *usageIn { - t.Fatalf("present usage not preserved: %#v (err %v)", out3.Usage, err) - } + testassert.Require(t, err == nil && out3.Usage != nil && *out3.Usage == *usageIn, "present usage not preserved: %#v (err %v)", out3.Usage, err) out4, _ := NewStreamDelta(StreamDelta{HasChoice: true}) - if out4.Usage != nil { - t.Fatal("absent delta usage must stay absent") - } - - casesErr := []struct { - name string - d StreamDelta + testassert.Require(t, out4.Usage == nil, "%v", "absent delta usage must stay absent") + + neg := -1 + negTwo := -2 + rows := []struct { + name string + delta StreamDelta + wantErr bool + wantIsAny []error }{ - {name: "negative content fragment position", d: StreamDelta{ContentFragments: []ContentFragment{{Position: -1, Kind: PartText}}}}, - {name: "negative tool fragment pointer position", d: func() StreamDelta { - p := -2 - return StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", Position: &p}}} - }()}, - } - for _, tc := range casesErr { - t.Run(tc.name, func(t *testing.T) { - if _, err := NewStreamDelta(tc.d); !errors.Is(err, ErrInvalidPosition) { - t.Fatalf("error = %v, want ErrInvalidPosition", err) + {"rich choice-bearing", richDeltaFixture(), false, nil}, + {"empty delta", StreamDelta{HasChoice: true}, false, nil}, + {"usage only", StreamDelta{HasChoice: true, Usage: &Usage{InputTokens: 1}}, false, nil}, + {"nil tool pointer position", StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c", Name: "f"}}}, false, nil}, + {"transient empty text at position zero", StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Kind: PartText}}}, false, nil}, + {"transient image URL later", StreamDelta{ContentFragments: []ContentFragment{{Position: 1, Kind: PartImageURL}}}, false, nil}, + {"transient opaque without wire type", StreamDelta{ContentFragments: []ContentFragment{{Position: 2, Kind: PartOpaque}}}, false, nil}, + {"negative content position", StreamDelta{HasChoice: true, ContentFragments: []ContentFragment{{Position: -1, Kind: PartText, Text: "x"}}}, true, nil}, + {"negative content fragment position", StreamDelta{ContentFragments: []ContentFragment{{Position: -1, Kind: PartText}}}, true, []error{ErrInvalidPosition}}, + {"cross-kind fragment", StreamDelta{HasChoice: true, ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Text: "x", URL: "u"}}}, true, nil}, + {"unknown kind", StreamDelta{HasChoice: true, ContentFragments: []ContentFragment{{Position: 0, Kind: PartKind("audio"), Text: "x"}}}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"empty content kind", StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Text: "x"}}}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"text fragment carrying a URL", StreamDelta{ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Text: "a", URL: "b"}}}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"image fragment carrying an opaque wire type", StreamDelta{ContentFragments: []ContentFragment{{Position: 1, Kind: PartImageURL, OpaqueWireType: "thinking"}}}, true, []error{ErrForbiddenField, ErrMissingField}}, + {"negative tool pointer position", StreamDelta{HasChoice: true, ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", Position: &neg}}}, true, nil}, + {"negative tool fragment pointer position", StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", Position: &negTwo}}}, true, []error{ErrInvalidPosition}}, + {"malformed tool extra", StreamDelta{HasChoice: true, ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", Extra: Extra{"k": json.RawMessage(`{oops`)}}}}, true, nil}, + {"malformed message extra", StreamDelta{HasChoice: true, MessageExtra: Extra{"m": json.RawMessage(`[1`)}}, true, nil}, + {"valid-looking text with an invalid sibling", StreamDelta{HasChoice: true, RefusalFragment: "no", ContentFragments: []ContentFragment{{Position: -1, Kind: PartText, Text: "x"}}}, true, nil}, + } + + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + got, newErr := NewStreamDelta(row.delta) + validateErr := ValidateStreamDelta(row.delta) + requireEqualConstructorVerdict(t, row.name, newErr, validateErr) + testassert.Require(t, (newErr != nil) == row.wantErr, "fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) + if len(row.wantIsAny) > 0 { + testassert.Require(t, matchesAnySentinel(newErr, row.wantIsAny...) && matchesAnySentinel(validateErr, row.wantIsAny...), "fixture %s: errors = %v / %v, want one of %v", row.name, newErr, validateErr, row.wantIsAny) + } + if !row.wantErr { + testassert.Require(t, reflect.DeepEqual(got, row.delta), "delta not preserved: %#v", got) } }) } - nilPos := StreamDelta{ToolFragments: []ToolCallFragment{{ID: "c", Name: "f"}}} // omitted position is optional. - if _, err := NewStreamDelta(nilPos); err != nil { - t.Fatalf("omitted tool fragment position must be accepted, got %v", err) + // The validator mutates nothing, nested fragments and extras included. + validated := richDeltaFixture() + before := richDeltaFixture() + { + err := ValidateStreamDelta(validated) + testassert.NoError(t, err) } + testassert.Require(t, reflect.DeepEqual(validated, before), "%v", "ValidateStreamDelta mutated its input") var fake streamFakeImpl out5, _ := NewStreamDelta(StreamDelta{HasChoice: true}) out5.Usage = out3.Usage // value semantics on the returned delta. - if out5.Usage == nil { - t.Fatal("delta usage pointer lost") - } + testassert.Require(t, out5.Usage != nil, "%v", "delta usage pointer lost") _ = fake } @@ -99,31 +120,27 @@ func TestNewStreamDeltaNeverRetainsZeroLengthFragmentBacking(t *testing.T) { zeroContent := make([]ContentFragment, 0, 1) zeroTools := make([]ToolCallFragment, 0, 1) out, err := NewStreamDelta(StreamDelta{HasChoice: true, ContentFragments: zeroContent, ToolFragments: zeroTools}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) out.ContentFragments = append(out.ContentFragments, ContentFragment{Kind: PartText, Text: "out"}) zeroContent = append(zeroContent, ContentFragment{Kind: PartText, Text: "in"}) - if out.ContentFragments[0].Text != "out" || zeroContent[0].Text != "in" { - t.Fatalf("content slices share backing: out=%#v in=%#v", out.ContentFragments, zeroContent) - } + testassert.Require(t, out.ContentFragments[0].Text == "out" && zeroContent[0].Text == "in", "content slices share backing: out=%#v in=%#v", out.ContentFragments, zeroContent) out.ToolFragments = append(out.ToolFragments, ToolCallFragment{ID: "out", Name: "out"}) zeroTools = append(zeroTools, ToolCallFragment{ID: "in", Name: "in"}) - if out.ToolFragments[0].ID != "out" || zeroTools[0].ID != "in" { - t.Fatalf("tool slices share backing: out=%#v in=%#v", out.ToolFragments, zeroTools) - } + testassert.Require(t, out.ToolFragments[0].ID == "out" && zeroTools[0].ID == "in", "tool slices share backing: out=%#v in=%#v", out.ToolFragments, zeroTools) } // TestStreamInterfaceIsSatisfied pins the exact public stream contract shape. func TestStreamInterfaceIsSatisfied(t *testing.T) { var s Stream = streamFakeImpl{} - if _, err := s.Recv(); !errors.Is(err, io.EOF) { - t.Fatalf("Recv error = %v, want io.EOF", err) + { + _, err := s.Recv() + testassert.Require(t, errors.Is(err, io.EOF), "Recv error = %v, want io.EOF", err) } - if err := s.Close(); err != nil { - t.Fatalf("Close returned error: %v", err) + { + err := s.Close() + testassert.NoError(t, err) } var asAny any = StreamDelta{} @@ -138,86 +155,33 @@ func TestStreamInterfaceIsSatisfied(t *testing.T) { func TestNewProtocolWarning(t *testing.T) { in := ProtocolWarning{Kind: "must_preserve", Message: "field x missing on assistant message 2", Target: fullRef, Field: "reasoning_content", MessageIndex: 1} out, err := NewProtocolWarning(in) - if err != nil { - t.Fatalf("NewProtocolWarning returned error: %v", err) - } - if out.Kind != in.Kind || out.Message != in.Message || out.Target != fullRef || out.Field != "reasoning_content" || out.MessageIndex != 1 { - t.Fatalf("warning not preserved: %#v", out) - } + testassert.NoError(t, err) + testassert.Require(t, out.Kind == in.Kind && out.Message == in.Message && out.Target == fullRef && out.Field == "reasoning_content" && out.MessageIndex == 1, "warning not preserved: %#v", out) neg := ProtocolWarning{Kind: "k", Message: "m"} // zero index is valid (zero-based). - if _, err := NewProtocolWarning(neg); err != nil { - t.Fatalf("zero message index rejected: %v", err) + { + _, err := NewProtocolWarning(neg) + testassert.NoError(t, err) } badIdx := -1 negWarn := ProtocolWarning{Kind: "k", Message: "m", MessageIndex: badIdx} - if _, err := NewProtocolWarning(negWarn); !errors.Is(err, ErrInvalidPosition) { - t.Fatalf("negative message index error = %v, want ErrInvalidPosition", err) + { + _, err := NewProtocolWarning(negWarn) + testassert.Require(t, errors.Is(err, ErrInvalidPosition), "negative message index error = %v, want ErrInvalidPosition", err) } var zeroTarget ProtocolWarning // target identity is diagnostic context; no nonzero requirement. outZero, err := NewProtocolWarning(zeroTarget) - if err != nil || !outZero.Target.IsZero() { - t.Fatalf("zero-target warning = %#v (err %v), want accepted with the zero target kept", outZero, err) - } + testassert.Require(t, err == nil && outZero.Target.IsZero(), "zero-target warning = %#v (err %v), want accepted with the zero target kept", outZero, err) var p ProtocolWarning // warnings carry exactly their contract fields. got := structFieldNames(p) for _, name := range []string{"Kind", "Message", "Target", "Field", "MessageIndex"} { - if !got[name] { - t.Fatalf("ProtocolWarning missing field %s: %#v", name, got) - } + testassert.Require(t, got[name], "ProtocolWarning missing field %s: %#v", name, got) delete(got, name) } for extra := range got { t.Fatalf("ProtocolWarning has non-contract field %q", extra) } } - -// TestNewStreamDeltaContentFragmentValidation pins the closed content-kind and field-exclusivity contract at the stream-delta accepting boundary: fragments are validated like content values, while still permitting empty transient text/URL/opaque-type pieces (including a transiently absent opaque wire type — only canonical parts require it). -func TestNewStreamDeltaContentFragmentValidation(t *testing.T) { - rejected := []struct { - name string - frag ContentFragment - }{ - {name: "unknown content kind rejected", frag: ContentFragment{Position: 0, Kind: PartKind("audio")}}, - {name: "empty content kind rejected", frag: ContentFragment{Position: 0, Text: "x"}}, - {name: "text fragment carrying a URL rejected", frag: ContentFragment{Position: 0, Kind: PartText, Text: "a", URL: "b"}}, - {name: "image_url fragment carrying an opaque wire type rejected", frag: ContentFragment{Position: 1, Kind: PartImageURL, OpaqueWireType: "thinking"}}, - } - for _, tc := range rejected { - t.Run(tc.name+" rejects at the delta boundary", func(t *testing.T) { - if _, err := NewStreamDelta(StreamDelta{ContentFragments: append([]ContentFragment{}, tc.frag)}); !errors.Is(err, ErrForbiddenField) && !errors.Is(err, ErrMissingField) { - t.Fatalf("fragment %#v error = %v", tc.frag, err) - } - }) - } - - transient := []struct { - name string - frag ContentFragment - }{ - {name: "empty text piece at position zero allowed transitively", frag: ContentFragment{Position: 0, Kind: PartText}}, - {name: "image URL may arrive in a later delta", frag: ContentFragment{Position: 1, Kind: PartImageURL}}, - } - for _, tc := range transient { - t.Run(tc.name+" accepted at the delta boundary", func(t *testing.T) { - gotOut, err := NewStreamDelta(StreamDelta{ContentFragments: append([]ContentFragment{}, tc.frag)}) - if err != nil || len(gotOut.ContentFragments) != 1 || gotOut.ContentFragments[0].Kind != tc.frag.Kind || gotOut.ContentFragments[0].Position != tc.frag.Position { - t.Fatalf("transient fragment %#v = %#v (err %v)", tc.frag, gotOut.ContentFragments, err) - } - }) - } - - opaqueTransient := ContentFragment{Position: 2, Kind: PartOpaque} // wire type absent for now. - gotOpaq, e2 := NewStreamDelta(StreamDelta{ContentFragments: append([]ContentFragment{}, opaqueTransient)}) - if e2 != nil || gotOpaq.ContentFragments[0].Kind != PartOpaque { - t.Fatalf("transiently empty opaque wire type must be permitted on fragments (err %v): %#v", e2, gotOpaq) - } - - partLevelStillRequiresWireType := ContentPart{Kind: PartOpaque} // the SAME shape is invalid as a canonical part. - if _, pErr := NewContentPart(partLevelStillRequiresWireType); !errors.Is(pErr, ErrMissingField) { - t.Fatalf("canonical opaque part without wire type error = %v, want ErrMissingField (fragment allowance does not loosen parts)", pErr) - } -} diff --git a/model/stream_recv_test.go b/model/stream_recv_test.go index 3e988b34..73949c31 100644 --- a/model/stream_recv_test.go +++ b/model/stream_recv_test.go @@ -4,6 +4,8 @@ import ( "errors" "io" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // tinyChunkReader delivers at most chunk bytes per Read call, proving the framing logic is independent of how the underlying reader happens to split data — arbitrary boundary positions must never change which events are produced. @@ -69,24 +71,30 @@ func TestSSEFramingRules(t *testing.T) { // single combined body per sub-case ke t.Fatalf("received %d deltas, want exactly 3 (one per real payload): %#v", len(got), got) // full slice rendered so any extra-or-missing element is immediately visible with its position in sequence... (count mismatches are always the cheapest class of framing bug to spot this way). } - if !got[0].HasChoice || len(got[0].ContentFragments) != 1 || got[0].ContentFragments[0].Text != "one" { // first event's exact fragment shape: single text piece at position zero carrying our literal value... (same expectation as every other simple-content row across the suite). - t.Fatalf("first delta = %#v, want one choice with content \"one\"", got[0]) // full struct rendered so any mismatch dimension — HasChoice vs fragment count vs actual text — is visible without re-running under verbose mode. - } + // first event's exact fragment shape: single text piece at position zero carrying our literal value... (same expectation as every other simple-content row across the suite). + // full struct rendered so any mismatch dimension — HasChoice vs fragment count vs actual text — is visible without re-running under verbose mode. + testassert.Require(t, got[0].HasChoice && len(got[0].ContentFragments) == 1 && got[0].ContentFragments[0].Text == "one", "first delta = %#v, want one choice with content \"one\"", got[0]) - if got[1].HasChoice || len(got[1].ContentFragments) != 0 { // second REAL event (the mixed-fields one): empty choices array means NO choice exists at all in that delta... (distinguishable from "choice present but empty" which would set HasChoice=true instead). - t.Fatalf("second real delta = %#v, want no-choice marker for the empty-choices payload", got[1]) // naming specifically WHICH structural fact is expected here keeps this row's intent readable even when skimmed quickly later. - } + // second REAL event (the mixed-fields one): empty choices array means NO choice exists at all in that delta... (distinguishable from "choice present but empty" which would set HasChoice=true instead). + // naming specifically WHICH structural fact is expected here keeps this row's intent readable even when skimmed quickly later. + testassert.Require(t, !got[1].HasChoice && len(got[1].ContentFragments) == 0, "second real delta = %#v, want no-choice marker for the empty-choices payload", got[1]) - if !got[2].HasChoice || len(got[2].ContentFragments) != 1 || got[2].ContentFragments[0].Text != "joined" { // THE join proof: two physical data lines became ONE logical payload carrying exactly this assembled content value... (any per-line parsing would have produced either garbage or a different count entirely). - t.Fatalf("third delta = %#v, want the joined-payload text fragment \"joined\"", got[2]) // full rendering again — same rationale as every other row in this file set about making failures self-explanatory on first sight. - } + // THE join proof: two physical data lines became ONE logical payload carrying exactly this assembled content value... (any per-line parsing would have produced either garbage or a different count entirely). + // full rendering again — same rationale as every other row in this file set about making failures self-explanatory on first sight. + testassert.Require(t, got[2].HasChoice && len(got[2].ContentFragments) == 1 && got[2].ContentFragments[0].Text == "joined", "third delta = %#v, want the joined-payload text fragment \"joined\"", got[2]) - if cerr := s.Close(); cerr != nil { // releasing after clean [DONE] termination must be quiet like everywhere else in the suite... (idempotent-friendly cleanup pinned here for parity with sibling rows). - t.Fatalf("Close returned error: %v", cerr) // explicit about which phase produced it so output stays attributable even in dense failure logs later. + // releasing after clean [DONE] termination must be quiet like everywhere else in the suite... (idempotent-friendly cleanup pinned here for parity with sibling rows). + // explicit about which phase produced it so output stays attributable even in dense failure logs later. + { + cerr := s.Close() + testassert.NoError(t, cerr) } - if _, again := s.Recv(); !errors.Is(again, io.EOF) { // post-termination + post-close state: further receives see EOF through done flag regardless of what remains unread (nothing does here anyway)... (pinning it completes this row's end-to-end story about terminal stability). - t.Fatalf("Recv after [DONE]+Close = %v, want io.EOF", again) // consistent phrasing with every other post-terminal receive check in the suite so future readers find them all via identical search terms. + // post-termination + post-close state: further receives see EOF through done flag regardless of what remains unread (nothing does here anyway)... (pinning it completes this row's end-to-end story about terminal stability). + // consistent phrasing with every other post-terminal receive check in the suite so future readers find them all via identical search terms. + { + _, again := s.Recv() + testassert.Require(t, errors.Is(again, io.EOF), "Recv after [DONE]+Close = %v, want io.EOF", again) } }) @@ -104,13 +112,13 @@ func TestSSEFramingRules(t *testing.T) { // single combined body per sub-case ke t.Fatalf("received %d deltas, want exactly 2 under CRLF framing", len(got)) // full count mismatch message mirrors LF-row phrasing for consistency across both line-ending variants. } - if !got[0].HasChoice || got[0].ContentFragments == nil || got[0].ContentFragments[0].Text != "crlf-one" { // first CRLF event's content must survive trimming intact — no stray \r characters may appear inside delivered text values themselves. - t.Fatalf("first CRLF delta = %#v, want content fragment \"crlf-one\" with no carriage-return contamination", got[0]) // naming the exact defect class (leftover CR bytes) makes any such regression immediately recognizable in output without re-reading trimming code first. - } + // first CRLF event's content must survive trimming intact — no stray \r characters may appear inside delivered text values themselves. + // naming the exact defect class (leftover CR bytes) makes any such regression immediately recognizable in output without re-reading trimming code first. + testassert.Require(t, got[0].HasChoice && got[0].ContentFragments != nil && got[0].ContentFragments[0].Text == "crlf-one", "first CRLF delta = %#v, want content fragment \"crlf-one\" with no carriage-return contamination", got[0]) - if !got[1].HasChoice || len(got[1].ContentFragments) != 1 || got[1].ContentFragments[0].Text != "crlf-two" { // second CRLF event — note its body ended with a single trailing newline rather than full double-line terminator before the next comment started... (proves that a lone \r\n after payload content does NOT prematurely terminate an already-complete earlier event). - t.Fatalf("second CRLF delta = %#v, want content fragment \"crlf-two\"", got[1]) // same rendering discipline as every other row in this file: full actual value visible next to expectation for immediate triage. - } + // second CRLF event — note its body ended with a single trailing newline rather than full double-line terminator before the next comment started... (proves that a lone \r\n after payload content does NOT prematurely terminate an already-complete earlier event). + // same rendering discipline as every other row in this file: full actual value visible next to expectation for immediate triage. + testassert.Require(t, got[1].HasChoice && len(got[1].ContentFragments) == 1 && got[1].ContentFragments[0].Text == "crlf-two", "second CRLF delta = %#v, want content fragment \"crlf-two\"", got[1]) if cerr := s.Close(); cerr != nil { t.Fatalf("Close returned error after CRLF sub-case completion: %v", cerr) // quiet release pinned here too — idempotent cleanup behavior is variant-independent by design... (asserting it in BOTH line-ending rows keeps that claim test-backed rather than assumed). @@ -170,8 +178,11 @@ func TestSSEPendsUnterminatedDataEventsAtEOF(t *testing.T) { // two sub-cases be } }) - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "p"}, WireSystemRole: ""}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring sibling files' convention one final time in THIS file as well — construction-time validation surface unchanged since this structural test was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + // closing sanity anchor mirroring sibling files' convention one final time in THIS file as well — construction-time validation surface unchanged since this structural test was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). + // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "p"}, WireSystemRole: ""}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } } @@ -202,8 +213,11 @@ func TestSSEMalformedPayloadsAreTypedProtocolErrorsNotSkipped(t *testing.T) { // t.Fatalf("Close returned error following protocol failure: %v", cerr) // releasing after a failed read must still behave like every other Close in the suite — quiet, idempotent-friendly cleanup with no transport facts surfaced to consumers at this point... (included because close-after-error is exactly the kind of edge where state corruption hides best). } - if _, ok := interface{}(s).(interface{ Retry() bool }); ok { // unreachable type-assertion guard documenting that NO retry-related method surface exists on our stream type under any name — its real value is forcing conscious acknowledgment that skipping/retrying malformed events is categorically impossible by construction rather than merely unimplemented today... (structural tests catch fields; this catches behavioral-method additions specifically at the stream boundary level). - t.Fatal("stream types must never expose retry/skip handles publicly") // if someone ever adds one, fail loudly instead of letting event-skipping creep back in through a public accessor without any visible field-level change first — exactly the regression class this entire test file exists to prevent. + // unreachable type-assertion guard documenting that NO retry-related method surface exists on our stream type under any name — its real value is forcing conscious acknowledgment that skipping/retrying malformed events is categorically impossible by construction rather than merely unimplemented today... (structural tests catch fields; this catches behavioral-method additions specifically at the stream boundary level). + // if someone ever adds one, fail loudly instead of letting event-skipping creep back in through a public accessor without any visible field-level change first — exactly the regression class this entire test file exists to prevent. + { + _, ok := interface{}(s).(interface{ Retry() bool }) + testassert.Require(t, !ok, "%v", "stream types must never expose retry/skip handles publicly") } }) } @@ -223,8 +237,11 @@ func TestSSERecvAfterCloseReportsEOF(t *testing.T) { // modeled on real-world co t.Fatalf("pre-close delta = %#v; want the single expected text fragment proving normal delivery worked first", first) // full rendering so any mismatch dimension is immediately visible without re-running under more detailed logging configuration. } - if cerr := s.Close(); cerr != nil { // THE early-abort action itself: releasing mid-stream with unconsumed content still available on the wire behind us... (this single call is what everything below measures reaction to). - t.Fatalf("early Close returned error: %v", cerr) // a non-nil return here would mean cleanup logic depends on some condition our fixture deliberately did not provide — fail loudly rather than continuing against possibly-corrupted internal state. + // THE early-abort action itself: releasing mid-stream with unconsumed content still available on the wire behind us... (this single call is what everything below measures reaction to). + // a non-nil return here would mean cleanup logic depends on some condition our fixture deliberately did not provide — fail loudly rather than continuing against possibly-corrupted internal state. + { + cerr := s.Close() + testassert.NoError(t, cerr) } if _, againErr := s.Recv(); !errors.Is(againErr, io.EOF) { // FIRST post-close receive must be immediate clean EOF — no buffered events may leak through after release... (this is the core observable guarantee of "close stops delivery" semantics). diff --git a/model/tool_test.go b/model/tool_test.go index 01abb466..7fb9cc43 100644 --- a/model/tool_test.go +++ b/model/tool_test.go @@ -4,167 +4,156 @@ import ( "encoding/json" "errors" "reflect" + "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) -func TestNewToolDefinitionValidCases(t *testing.T) { - cases := []struct { - name string - d ToolDefinition +// TestNewToolDefinitionCases pins the ToolDefinition accepting rules through +// both entry points: one inventory of object-shaped schemas, syntax-only +// huge-number input, unknown object shapes, and every invalid name/top-level +// shape, each with its original expected verdict. +func TestNewToolDefinitionCases(t *testing.T) { + rows := []struct { + name string + def ToolDefinition + wantErr bool + wantIsAny []error }{ - {name: "object parameters", d: ToolDefinition{Name: "read_file", Description: "reads a file", Parameters: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`)}}, - {name: "empty object schema is valid JSON and an object", d: ToolDefinition{Name: "noop", Parameters: json.RawMessage(`{}`)}}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - out, err := NewToolDefinition(tc.d) - if err != nil { - t.Fatalf("NewToolDefinition returned error: %v", err) + {"object parameters", ToolDefinition{Name: "read_file", Description: "reads a file", Parameters: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`)}, false, nil}, + {"empty object schema is valid JSON and an object", ToolDefinition{Name: "noop", Parameters: json.RawMessage(`{}`)}, false, nil}, + {"object parameters with whitespace", ToolDefinition{Name: "f", Parameters: json.RawMessage(" {\"a\": 1} ")}, false, nil}, + {"huge number literal is syntax-valid", ToolDefinition{Name: "f", Parameters: json.RawMessage(`{"n":1e1000}`)}, false, nil}, + {"syntax-only schema with a huge maximum", ToolDefinition{Name: "big", Parameters: json.RawMessage(`{"type":"object","properties":{"n":{"maximum":1e1000}}}`)}, false, nil}, + {"unknown object shape is not dialect-validated", ToolDefinition{Name: "x", Parameters: json.RawMessage(`{"weird":true}`)}, false, nil}, + {"empty name", ToolDefinition{Parameters: json.RawMessage(`{}`)}, true, []error{ErrMissingField}}, + {"empty name precedes malformed parameters", ToolDefinition{Parameters: json.RawMessage(`{oops`)}, true, nil}, + {"empty parameters", ToolDefinition{Name: "f"}, true, nil}, + {"null parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`null`)}, true, []error{ErrInvalidParameters}}, + {"array parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`[1]`)}, true, nil}, + {"two-item array parameters", ToolDefinition{Name: "x", Parameters: json.RawMessage(`[1,2]`)}, true, []error{ErrInvalidParameters}}, + {"string top level", ToolDefinition{Name: "x", Parameters: json.RawMessage(`"params"`)}, true, []error{ErrInvalidParameters}}, + {"number top level", ToolDefinition{Name: "x", Parameters: json.RawMessage(`3`)}, true, []error{ErrInvalidParameters}}, + {"boolean top level", ToolDefinition{Name: "x", Parameters: json.RawMessage(`true`)}, true, []error{ErrInvalidParameters}}, + {"malformed JSON parameters", ToolDefinition{Name: "x", Parameters: json.RawMessage(`{"type":`)}, true, []error{ErrInvalidParameters}}, + {"truncated JSON parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`{oops`)}, true, nil}, + } + + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + out, newErr := NewToolDefinition(row.def) + validateErr := ValidateToolDefinition(row.def) + requireEqualConstructorVerdict(t, row.name, newErr, validateErr) + testassert.Require(t, (newErr != nil) == row.wantErr, "fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) + if len(row.wantIsAny) > 0 { + testassert.Require(t, matchesAnySentinel(newErr, row.wantIsAny...) && matchesAnySentinel(validateErr, row.wantIsAny...), "fixture %s: errors = %v / %v, want one of %v", row.name, newErr, validateErr, row.wantIsAny) } - if out.Name != tc.d.Name || out.Description != tc.d.Description || string(out.Parameters) != string(tc.d.Parameters) { - t.Fatalf("definition not preserved: %#v", out) + if !row.wantErr { + testassert.Require(t, out.Name == row.def.Name && out.Description == row.def.Description && string(out.Parameters) == string(row.def.Parameters), "definition not preserved: %#v", out) } }) } + // The validator mutates nothing, parameter bytes included. + validated := ToolDefinition{Name: "echo", Description: "d", Parameters: json.RawMessage(`{"deep":{"a":[1,2]}}`)} + before := ToolDefinition{Name: "echo", Description: "d", Parameters: json.RawMessage(`{"deep":{"a":[1,2]}}`)} + { + err := ValidateToolDefinition(validated) + testassert.NoError(t, err) + } + testassert.Require(t, reflect.DeepEqual(validated, before), "%v", "ValidateToolDefinition mutated its input") + srcRaw := json.RawMessage(`{"type":"object"}`) in := ToolDefinition{Name: "x", Parameters: srcRaw} out, err := NewToolDefinition(in) - if err != nil { - t.Fatalf("NewToolDefinition returned error: %v", err) - } + testassert.NoError(t, err) srcRaw[1] = 'X' // corrupt caller bytes after construction. got := string(out.Parameters) - if len(got) == 0 || got[:2] != `{"` || &out.Parameters[0] == &srcRaw[0] { - t.Fatalf("parameters not deep copied at the accepting boundary: %s", out.Parameters) - } - - // Phase boundary: no JSON-Schema dialect validation. An unknown object shape is accepted. - if _, err := NewToolDefinition(ToolDefinition{Name: "x", Parameters: json.RawMessage(`{"weird":true}`)}); err != nil { - t.Fatalf("schema-dialect content must not be validated here, got error %v", err) - } + testassert.Require(t, len(got) != 0 && got[:2] == `{"` && &out.Parameters[0] != &srcRaw[0], "parameters not deep copied at the accepting boundary: %s", out.Parameters) } -// TestNewToolDefinitionRejectsInvalid pins empty names and non-object or malformed schemas. -func TestNewToolDefinitionRejectsInvalid(t *testing.T) { - cases := []struct { - name string - d ToolDefinition - wantIs error +// TestNewToolCallPreservesArgumentsRaw pins the ToolCall accepting rules +// through both entry points: raw arguments of every JSON shape (including +// malformed bytes) stay accepted and byte-for-byte owned, id/name failures +// precede Extra validation, and each row carries its original expected verdict. +func TestNewToolCallPreservesArgumentsRaw(t *testing.T) { + rows := []struct { + name string + call ToolCall + wantErr bool + wantIsAny []error + contains string }{ - {name: "empty name", d: ToolDefinition{Name: "", Parameters: json.RawMessage(`{}`)}, wantIs: ErrMissingField}, - {name: "malformed JSON parameters", d: ToolDefinition{Name: "x", Parameters: json.RawMessage(`{"type":`)}, wantIs: ErrInvalidParameters}, - {name: "array top level rejected", d: ToolDefinition{Name: "x", Parameters: json.RawMessage(`[1,2]`)}, wantIs: ErrInvalidParameters}, - {name: "string top level rejected", d: ToolDefinition{Name: "x", Parameters: json.RawMessage(`"params"`)}, wantIs: ErrInvalidParameters}, - {name: "number top level rejected", d: ToolDefinition{Name: "x", Parameters: json.RawMessage(`3`)}, wantIs: ErrInvalidParameters}, - {name: "boolean top level rejected", d: ToolDefinition{Name: "x", Parameters: json.RawMessage(`true`)}, wantIs: ErrInvalidParameters}, - {name: "null top level rejected", d: ToolDefinition{Name: "x", Parameters: json.RawMessage(`null`)}, wantIs: ErrInvalidParameters}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - if _, err := NewToolDefinition(tc.d); !errors.Is(err, tc.wantIs) { - t.Fatalf("error = %v, want errors.Is(%v)", err, tc.wantIs) + {"rich", richToolCallFixture(), false, nil, ""}, + {"object arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`{"path":"foo.txt"}`)}, false, nil, ""}, // schema validity is caller-owned tool preparation. + {"empty arguments", ToolCall{ID: "c", Name: "f"}, false, nil, ""}, // empty arguments stay raw input. + {"malformed arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`not json`)}, false, nil, ""}, // malformed stays raw input for caller-owned validation. + {"null arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`null`)}, false, nil, ""}, // null stays raw input. + {"array arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`[1,2]`)}, false, nil, ""}, // array stays raw input. + {"primitive arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`"x"`)}, false, nil, ""}, // primitive stays raw input. + {"empty id", ToolCall{Name: "f"}, true, []error{ErrMissingField}, ""}, + {"empty name", ToolCall{ID: "c"}, true, nil, ""}, + {"empty name with id", ToolCall{ID: "c1", Name: ""}, true, []error{ErrMissingField}, ""}, + {"malformed extra", ToolCall{ID: "c", Name: "f", Extra: Extra{"k": json.RawMessage(`{oops`)}}, true, nil, ""}, + {"id precedence over malformed extra", ToolCall{Name: "f", Extra: Extra{"k": json.RawMessage(`{oops`)}}, true, nil, "non-empty id and name"}, + } + + for _, row := range rows { + t.Run(row.name, func(t *testing.T) { + out, newErr := NewToolCall(row.call) + validateErr := ValidateToolCall(row.call) + requireEqualConstructorVerdict(t, row.name, newErr, validateErr) + testassert.Require(t, (newErr != nil) == row.wantErr, "fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) + if len(row.wantIsAny) > 0 { + testassert.Require(t, matchesAnySentinel(newErr, row.wantIsAny...) && matchesAnySentinel(validateErr, row.wantIsAny...), "fixture %s: errors = %v / %v, want one of %v", row.name, newErr, validateErr, row.wantIsAny) + } + testassert.Require(t, row.contains == "" || (strings.Contains(newErr.Error(), row.contains) && strings.Contains(validateErr.Error(), row.contains)), "fixture %s reached the wrong rule: %v / %v, want a failure naming %q", row.name, newErr, validateErr, row.contains) + if !row.wantErr { + testassert.Require(t, out.ID == row.call.ID && out.Name == row.call.Name && string(out.Arguments) == string(row.call.Arguments), "call fields not preserved: %#v", out) + testassert.Require(t, len(out.Arguments) == 0 || &out.Arguments[0] != &row.call.Arguments[0], "%v", "stored arguments share memory with the input") } }) } -} -// TestNewToolDefinitionParametersAreSyntaxOnly pins syntax-only parameter validation (no float64 decoding): a well-formed top-level object is accepted even when its numeric literals cannot be represented in Go's float64, and the bytes round-trip unchanged. -func TestNewToolDefinitionParametersAreSyntaxOnly(t *testing.T) { - raw := `{"type":"object","properties":{"n":{"maximum":1e1000}}}` - out, err := NewToolDefinition(ToolDefinition{Name: "big", Parameters: json.RawMessage(raw)}) - if err != nil { - t.Fatalf("well-formed object with non-float64-representable number rejected (decoding-based validation): %v", err) - } - if string(out.Parameters) != raw { - t.Fatalf("parameters not preserved byte-for-byte: got %s want %s", out.Parameters, raw) - } -} - -// TestNewToolCallPreservesArgumentsRaw pins byte-for-byte argument preservation without JSON validation. -func TestNewToolCallPreservesArgumentsRaw(t *testing.T) { - cases := []string{ - `{"path":"foo.txt"}`, // object; schema validity is caller-owned tool preparation. - ``, // empty arguments stay raw input. - `not json`, // malformed stays raw input for caller-owned validation. - `null`, // null stays raw input. - `[1,2]`, // array stays raw input. - `"primitive"`, // primitive stays raw input. + // The validator mutates nothing, argument bytes and extras included. + validated := richToolCallFixture() + before := richToolCallFixture() + { + err := ValidateToolCall(validated) + testassert.NoError(t, err) } - for _, args := range cases { - t.Run("args", func(t *testing.T) { - srcArgs := json.RawMessage(args) - out, err := NewToolCall(ToolCall{ID: "call_1", Name: "read_file", Arguments: srcArgs}) - if err != nil { - t.Fatalf("NewToolCall returned error for raw argument input %q: %v", args, err) - } - if string(out.Arguments) != args { - t.Fatalf("arguments = %s, want byte-for-byte preserved %s", out.Arguments, args) - } - - // Corrupt the caller's bytes after construction; stored arguments must be untouched. - if len(srcArgs) > 0 && &out.Arguments[0] == &srcArgs[0] { - t.Fatal("stored arguments share memory with the input") - } - if len(srcArgs) > 1 { - srcArgs[1] ^= 0xFF - } - out2, err := NewToolCall(ToolCall{ID: "call_1", Name: "read_file", Arguments: json.RawMessage(args)}) - if err != nil || string(out.Arguments) != args { - t.Fatalf("stored arguments changed after caller mutation: %s (err %v), want %s", out.Arguments, err, args) - } - if len(srcArgs) > 0 && &out2.Arguments[0] == &srcArgs[0] { - t.Fatal("second construction shared bytes with the corrupted input") - } + testassert.Require(t, reflect.DeepEqual(validated, before), "%v", "ValidateToolCall mutated its input") - out3, err := NewToolCall(ToolCall{ID: "c", Name: "f"}) - if err != nil || len(out3.Arguments) != 0 { - t.Fatalf("zero arguments = %s (err %v), want empty raw value kept as-is", out3.Arguments, err) - } - }) - } + // Corrupt one representative caller input after construction; the stored + // bytes must survive the mutation. + srcArgs := json.RawMessage(`{"path":"foo.txt"}`) + copied, err := NewToolCall(ToolCall{ID: "call_1", Name: "read_file", Arguments: srcArgs}) + testassert.NoError(t, err) + srcArgs[1] ^= 0xFF + testassert.Require(t, string(copied.Arguments) == `{"path":"foo.txt"}`, "stored arguments changed after caller mutation: %s", copied.Arguments) extraSrc := json.RawMessage(`{"google":{"thought_signature":"sig"}}`) inExtra := Extra{"extra_content": extraSrc} out, err := NewToolCall(ToolCall{ID: "c1", Name: "f", Arguments: json.RawMessage(``), Extra: inExtra}) - if err != nil { - t.Fatalf("NewToolCall returned error: %v", err) - } + testassert.NoError(t, err) extraSrc[0] = 'X' // corrupt the caller's extra bytes after construction. got := string(out.Extra["extra_content"]) - if len(got) == 0 || got[0] != '{' { - t.Fatalf("tool call extra not deep copied at accepting boundary: %s", out.Extra["extra_content"]) - } - - casesErr := []struct { - name string - tc ToolCall - wantIs error - }{ - {name: "empty id rejected", tc: ToolCall{ID: "", Name: "f"}, wantIs: ErrMissingField}, - {name: "empty name rejected", tc: ToolCall{ID: "c1", Name: ""}, wantIs: ErrMissingField}, - } - for _, tc := range casesErr { - t.Run(tc.name, func(t *testing.T) { - if _, err := NewToolCall(tc.tc); !errors.Is(err, tc.wantIs) { - t.Fatalf("error = %v, want errors.Is(%v)", err, tc.wantIs) - } - }) - } + testassert.Require(t, len(got) != 0 && got[0] == '{', "tool call extra not deep copied at accepting boundary: %s", out.Extra["extra_content"]) c1, _ := NewToolCall(ToolCall{ID: "c", Name: "f"}) - if c1.ID != "c" || c1.Name != "f" { - t.Fatalf("call fields not preserved: %#v", c1) - } + testassert.Require(t, c1.ID == "c" && c1.Name == "f", "call fields not preserved: %#v", c1) var tcType ToolCall rt := reflect.TypeOf(tcType) - if _, ok := rt.FieldByName("Type"); ok { - t.Fatal("canonical tool call must not carry a wire-type field") + { + _, ok := rt.FieldByName("Type") + testassert.Require(t, !ok, "%v", "canonical tool call must not carry a wire-type field") } for _, name := range []string{"ID", "Name", "Arguments"} { - if _, ok := rt.FieldByName(name); !ok { - t.Fatalf("ToolCall missing contract field %s: %v", name, rt) + { + _, ok := rt.FieldByName(name) + testassert.Require(t, ok, "ToolCall missing contract field %s: %v", name, rt) } } } @@ -185,12 +174,8 @@ func TestNewToolResultStatusMatrix(t *testing.T) { for _, tc := range valid { t.Run(tc.name+" accepts", func(t *testing.T) { out, err := NewToolResult(ToolResult{CallID: "call_1", Status: tc.status, Content: tc.content}) - if err != nil { - t.Fatalf("NewToolResult returned error: %v", err) - } - if out.CallID != "call_1" || out.Status != tc.status || out.Content != tc.content { - t.Fatalf("result not preserved: %#v", out) - } + testassert.NoError(t, err) + testassert.Require(t, out.CallID == "call_1" && out.Status == tc.status && out.Content == tc.content, "result not preserved: %#v", out) }) } @@ -205,19 +190,22 @@ func TestNewToolResultStatusMatrix(t *testing.T) { } for _, tc := range cases { t.Run(tc.name+" rejects", func(t *testing.T) { - if _, err := NewToolResult(tc.res); !errors.Is(err, tc.wantIs) { - t.Fatalf("error = %v, want errors.Is(%v)", err, tc.wantIs) + { + _, err := NewToolResult(tc.res) + testassert.Require(t, errors.Is(err, tc.wantIs), "error = %v, want errors.Is(%v)", err, tc.wantIs) } }) } for i, status := range []string{"", "ok", "SUCCESS", "success ", string(rune('x')) + "status"} { - if _, err := NewToolResult(ToolResult{CallID: "c1", Status: ToolResultStatus(status), Content: "detail"}); !errors.Is(err, ErrInvalidStatus) { - t.Fatalf("case %d status=%q error = %v, want ErrInvalidStatus", i, status, err) + { + _, err := NewToolResult(ToolResult{CallID: "c1", Status: ToolResultStatus(status), Content: "detail"}) + testassert.Require(t, errors.Is(err, ErrInvalidStatus), "case %d status=%q error = %v, want ErrInvalidStatus", i, status, err) } } - if _, err := NewToolResult(ToolResult{CallID: "", Status: ResultSuccess}); !errors.Is(err, ErrMissingField) { - t.Fatalf("empty call id error = %v, want ErrMissingField", err) + { + _, err := NewToolResult(ToolResult{CallID: "", Status: ResultSuccess}) + testassert.Require(t, errors.Is(err, ErrMissingField), "empty call id error = %v, want ErrMissingField", err) } } diff --git a/model/transport_debug_test.go b/model/transport_debug_test.go index 6c2025d8..0495c9d7 100644 --- a/model/transport_debug_test.go +++ b/model/transport_debug_test.go @@ -11,6 +11,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestTransportWireDiagnosticsWriteReqAndChunksPerDecodedEvent pins the retained wire-diagnostics contract end to end: with a resolved debug directory present, one request dump is written per invocation carrying exactly the encoded body bytes (verified against what the server actually received), and each successfully decoded SSE event appends its raw payload as one JSONL line in delivery order — malformed or [DONE] events never appear there. @@ -47,8 +49,10 @@ func TestTransportWireDiagnosticsWriteReqAndChunksPerDecodedEvent(t *testing.T) t.Fatalf("mid-drain Recv failed: %v", e) // any non-EOF error mid-stream with diagnostics enabled would indicate the debug path corrupted delivery itself... (exactly the class of regression this whole file exists to prevent). } } - if cerr := stream.Close(); cerr != nil { - t.Fatalf("Close returned error: %v", cerr) // idempotent quiet release as everywhere else in the suite — pinning it here too keeps close-after-full-drain covered under diagnostics-on conditions specifically. + // idempotent quiet release as everywhere else in the suite — pinning it here too keeps close-after-full-drain covered under diagnostics-on conditions specifically. + { + cerr := stream.Close() + testassert.NoError(t, cerr) } } @@ -71,8 +75,11 @@ func TestTransportWireDiagnosticsWriteReqAndChunksPerDecodedEvent(t *testing.T) } } - if base := strings.TrimSuffix(filepath.Base(reqFile), "-req.json"); !strings.HasPrefix(filepath.Base(chunksFile), base) { // shared-id pairing: both artifacts derive from ONE exchange id so consumers can correlate them without parsing timestamps... (this is THE property making the pair a unit rather than two coincidentally-similarly-named files). - t.Fatalf("request dump %q and chunks file %q do not share an exchange id prefix", reqFile, chunksFile) // both full names rendered side by side so any formatting divergence between the two write paths is visible character-by-character in output alone. + // shared-id pairing: both artifacts derive from ONE exchange id so consumers can correlate them without parsing timestamps... (this is THE property making the pair a unit rather than two coincidentally-similarly-named files). + // both full names rendered side by side so any formatting divergence between the two write paths is visible character-by-character in output alone. + { + base := strings.TrimSuffix(filepath.Base(reqFile), "-req.json") + testassert.Require(t, strings.HasPrefix(filepath.Base(chunksFile), base), "request dump %q and chunks file %q do not share an exchange id prefix", reqFile, chunksFile) } reqBytes, rerr := os.ReadFile(reqFile) // request dump content must equal EXACTLY what crossed the wire — byte-for-byte against handler-captured body rather than a recomputed expectation... (recomputing via Encode would test encode-twice-consistency which is already covered elsewhere; comparing to reality catches transport-side corruption specifically). @@ -117,9 +124,8 @@ func TestTransportWireDiagnosticsFailureNeverAltersResults(t *testing.T) { // no tr := mustTransport(t, rt) stream, _, err := tr.Stream(context.Background(), baseRequest(), nil) // acceptance MUST still succeed with diagnostics failing — if this ever breaks, operators lose entire chat streams because a debug folder was misconfigured... (the exact catastrophic coupling class that motivated failure isolation as an explicit contract rather than incidental behavior). - if err != nil { - t.Fatalf("Stream failed despite unwritable diagnostics dir: %v", err) // names the injected condition explicitly in output so any future reader immediately knows which fixture aspect this assertion protects against... (self-documenting failure messages keep triage fast even months after initial authorship when context has faded). - } + // names the injected condition explicitly in output so any future reader immediately knows which fixture aspect this assertion protects against... (self-documenting failure messages keep triage fast even months after initial authorship when context has faded). + testassert.NoError(t, err) var deltas int // count delivered fragments as positive proof that delivery proceeded normally end-to-end despite zero successful diagnostic writes anywhere along the way... for { // drain to clean terminal state exactly like sibling tests do — uniformity in how far each row consumes keeps cross-row comparisons meaningful when triaging ordering-sensitive regressions later. @@ -147,8 +153,11 @@ func TestTransportWireDiagnosticsFailureNeverAltersResults(t *testing.T) { // no t.Fatalf("unwritable diagnostics dir left %d filesystem entries behind: %#v", len(entries), entryNames(entries)) // full name list rendered so whichever artifact (or unexpected shape) survived despite its write failing is immediately visible alongside every sibling's for direct pairing analysis of which operation produced what remnant. } - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this diagnostic row was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT specific fixture condition). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite even under verbose aggregated output where individual messages scroll past quickly during triage. + // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this diagnostic row was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT specific fixture condition). + // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite even under verbose aggregated output where individual messages scroll past quickly during triage. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } } diff --git a/model/transport_encode_equivalence_test.go b/model/transport_encode_equivalence_test.go index 5f5bf2cb..3ec3e3c7 100644 --- a/model/transport_encode_equivalence_test.go +++ b/model/transport_encode_equivalence_test.go @@ -9,6 +9,8 @@ import ( "net/http/httptest" "reflect" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // richEquivalenceRequest builds the fixture request through the public constructors, failing the test instead of panicking. @@ -20,16 +22,12 @@ func richEquivalenceRequest(t *testing.T, assistantSource ModelRef) Request { Content: []ContentPart{{Kind: PartText, Text: "working on it"}}, ToolCalls: []ToolCall{func() ToolCall { call, err := NewToolCall(ToolCall{ID: "call_eq_1", Name: "read_file", Arguments: json.RawMessage(`{"path":"a.txt"}`), Extra: Extra{"call_meta": json.RawMessage(`1`)}}) - if err != nil { - t.Fatalf("NewToolCall fixture: %v", err) - } + testassert.NoError(t, err) return call }()}, Extra: Extra{"provider_meta": json.RawMessage(`{"v":2}`)}, }) - if err != nil { - t.Fatalf("NewMessage assistant fixture: %v", err) - } + testassert.NoError(t, err) return Request{ Messages: []Message{ mustMsg(t, Message{Role: RoleSystem, Content: []ContentPart{{Kind: PartText, Text: "system setup"}}}), @@ -126,26 +124,16 @@ func TestEncodeAndStreamProduceIdenticalBodyAndWarnings(t *testing.T) { rowRt.MustPreserve = []string{"reasoning_details"} wantBody, wantWarnings, err := Encode(rowRt, req, tc.runtimeExtras) - if err != nil { - t.Fatalf("Encode returned error for valid input: %v", err) - } - if len(wantWarnings) != tc.wantWarnings { - t.Fatalf("fixture premise: Encode warnings = %d, want %d (%#v)", len(wantWarnings), tc.wantWarnings, wantWarnings) - } + testassert.NoError(t, err) + testassert.Require(t, len(wantWarnings) == tc.wantWarnings, "fixture premise: Encode warnings = %d, want %d (%#v)", len(wantWarnings), tc.wantWarnings, wantWarnings) tr := mustTransport(t, rowRt) stream, gotWarnings, err := tr.Stream(context.Background(), req, tc.runtimeExtras) - if err != nil { - t.Fatalf("Stream returned error for valid input: %v", err) - } + testassert.NoError(t, err) defer stream.Close() - if !bytes.Equal(capturedBody, wantBody) { - t.Fatalf("posted body differs from Encode output:\ngot %s\nwant %s", capturedBody, wantBody) - } - if !reflect.DeepEqual(gotWarnings, wantWarnings) { - t.Fatalf("warnings differ: Stream %#v, Encode %#v", gotWarnings, wantWarnings) - } + testassert.Require(t, bytes.Equal(capturedBody, wantBody), "posted body differs from Encode output:\ngot %s\nwant %s", capturedBody, wantBody) + testassert.Require(t, reflect.DeepEqual(gotWarnings, wantWarnings), "warnings differ: Stream %#v, Encode %#v", gotWarnings, wantWarnings) }) } } @@ -174,13 +162,9 @@ func TestCallerMutationAfterConstructionKeepsNextStream(t *testing.T) { // Pre-mutation baselines: one direct Encode body and one Stream exchange. wantBody, wantWarnings, err := Encode(rt, req, extras) - if err != nil { - t.Fatalf("Encode baseline failed: %v", err) - } - if len(wantWarnings) != 2 || wantWarnings[0].Field != "reasoning_details" || wantWarnings[1].Field != "tool_call_id" || - wantWarnings[0].MessageIndex != 2 || wantWarnings[1].MessageIndex != 2 { // premise: the immutable expectation really is the ordered pair, so the comparison below cannot pass vacuously (the assistant is the third message, index 2). - t.Fatalf("fixture premise: Encode ordered warnings = %#v; want reasoning_details then tool_call_id at message index 2", wantWarnings) - } + testassert.NoError(t, err) + // premise: the immutable expectation really is the ordered pair, so the comparison below cannot pass vacuously (the assistant is the third message, index 2). + testassert.Require(t, len(wantWarnings) == 2 && wantWarnings[0].Field == "reasoning_details" && wantWarnings[1].Field == "tool_call_id" && wantWarnings[0].MessageIndex == 2 && wantWarnings[1].MessageIndex == 2, "fixture premise: Encode ordered warnings = %#v; want reasoning_details then tool_call_id at message index 2", wantWarnings) baseline := append([]byte(nil), wantBody...) // independent copy as the comparison reference; wantBody itself is mutated below as a returned-value stimulus. tr := mustTransport(t, rt) @@ -214,10 +198,6 @@ func TestCallerMutationAfterConstructionKeepsNextStream(t *testing.T) { t.Fatalf("post-mutation stream Close failed: %v", err) } - if !bytes.Equal(capturedBody, baseline) { - t.Fatalf("post-mutation Stream body changed:\ngot %s\nwant %s", capturedBody, baseline) - } - if !reflect.DeepEqual(gotWarnings, wantWarnings) { - t.Fatalf("post-mutation Stream warnings changed: got %#v, want %#v", gotWarnings, wantWarnings) - } + testassert.Require(t, bytes.Equal(capturedBody, baseline), "post-mutation Stream body changed:\ngot %s\nwant %s", capturedBody, baseline) + testassert.Require(t, reflect.DeepEqual(gotWarnings, wantWarnings), "post-mutation Stream warnings changed: got %#v, want %#v", gotWarnings, wantWarnings) } diff --git a/model/transport_http_test.go b/model/transport_http_test.go index f26a7a57..e491c31c 100644 --- a/model/transport_http_test.go +++ b/model/transport_http_test.go @@ -15,15 +15,15 @@ import ( "sync/atomic" "testing" "time" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // mustTransport builds a transport from the given resolved input, failing the test on any construction error. Valid-input fixtures only; every rejection case is asserted explicitly where it belongs (TestNewTransportRejectsInvalidResolvedInput). func mustTransport(t *testing.T, rt ResolvedTransport) *Transport { // shared fixture helper for this file and sibling transport/stream tests in the package — all of them construct before dialing anything. t.Helper() tr, err := NewTransport(rt) - if err != nil { - t.Fatalf("NewTransport returned error for a valid resolved input: %v", err) - } + testassert.NoError(t, err) return tr } @@ -33,32 +33,41 @@ func baseRequest() Request { return Request{Messages: []Message{userText("hi")}} // TestNewTransportRejectsInvalidResolvedInput pins construction-time validation with typed errors naming the offending field: zero and partial target identities fail via ErrInvalidModelRef, an out-of-set wire system role fails via ErrInvalidWireSystemRole (with the value in context), and every closed-set member plus a valid baseline construct cleanly without any I/O side effect. func TestNewTransportRejectsInvalidResolvedInput(t *testing.T) { // resolved-input validity at construction is exactly this matrix — nothing else may fail here, since request-level concerns belong to Stream's trust boundary instead... (that split keeps each error surface owning its own inputs). - if _, err := NewTransport(ResolvedTransport{}); !errors.Is(err, ErrInvalidModelRef) || !errors.Is(err, ErrInvalidInput) { // zero identity: provider and model both empty. - t.Fatalf("zero-model error = %v, want ErrInvalidModelRef and ErrInvalidInput", err) + // zero identity: provider and model both empty. + { + _, err := NewTransport(ResolvedTransport{}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef) && errors.Is(err, ErrInvalidInput), "zero-model error = %v, want ErrInvalidModelRef and ErrInvalidInput", err) } partial := testResolved() partial.Model = ModelRef{Provider: "openai"} // provider-only is not complete — same rule as everywhere else in this package where a nonzero identity is required. - if _, err := NewTransport(partial); !errors.Is(err, ErrInvalidModelRef) || !errors.Is(err, ErrInvalidInput) { - t.Fatalf("partial-model error = %v, want ErrInvalidModelRef and ErrInvalidInput", err) + { + _, err := NewTransport(partial) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef) && errors.Is(err, ErrInvalidInput), "partial-model error = %v, want ErrInvalidModelRef and ErrInvalidInput", err) } badRole := testResolved() badRole.WireSystemRole = "assistant" // outside the closed system/user/developer wire-role set — assistant is a canonical conversation role, not a wire system role. - if _, err := NewTransport(badRole); !errors.Is(err, ErrInvalidWireSystemRole) || !errors.Is(err, ErrInvalidInput) { - t.Fatalf("bad-wire-role error = %v, want ErrInvalidWireSystemRole and ErrInvalidInput", err) + { + _, err := NewTransport(badRole) + testassert.Require(t, errors.Is(err, ErrInvalidWireSystemRole) && errors.Is(err, ErrInvalidInput), "bad-wire-role error = %v, want ErrInvalidWireSystemRole and ErrInvalidInput", err) } for _, role := range []string{"system", "user", "developer"} { // every closed-set member constructs — pinning that validation rejects exactly out-of-set values and nothing else... (an over-strict implementation would fail one of these rows instead of the badRole row above). valid := testResolved() valid.WireSystemRole = role - if _, err := NewTransport(valid); err != nil { - t.Fatalf("wire role %q rejected at construction: %v", role, err) // no I/O happens on any success path — the type performs none by contract (no files appear, nothing dials)... which is why this loop can run without a server. + // no I/O happens on any success path — the type performs none by contract (no files appear, nothing dials)... which is why this loop can run without a server. + { + _, err := NewTransport(valid) + testassert.Require(t, err == nil, "wire role %q rejected at construction: %v", role, err) } } - if _, err := NewTransport(testResolved()); err != nil { // plain valid baseline: the happy construction itself must be an error-free no-op... (it is asserted last so its green cannot mask any rejection-row failure above). - t.Fatalf("valid resolved input rejected at construction: %v", err) // a regression here would mean NewTransport started validating something outside its contract surface — worth failing loudly and specifically rather than discovering mid-suite. + // plain valid baseline: the happy construction itself must be an error-free no-op... (it is asserted last so its green cannot mask any rejection-row failure above). + // a regression here would mean NewTransport started validating something outside its contract surface — worth failing loudly and specifically rather than discovering mid-suite. + { + _, err := NewTransport(testResolved()) + testassert.NoError(t, err) } } @@ -97,9 +106,7 @@ func TestNewTransportRejectsInvalidResolvedExtras(t *testing.T) { // constructio rt := testResolved() rt.ModelExtraBody = Extra{"stream_options": json.RawMessage(`{broken`)} // a reserved key whose own value cannot parse: identification must never depend on parsing it. _, err := NewTransport(rt) - if !errors.Is(err, ErrReservedKeys) || errors.Is(err, ErrInvalidInput) { - t.Fatalf("error = %v; want the reserved-key identity alone even though the reserved value itself is malformed", err) - } + testassert.Require(t, errors.Is(err, ErrReservedKeys) && !errors.Is(err, ErrInvalidInput), "error = %v; want the reserved-key identity alone even though the reserved value itself is malformed", err) }) t.Run("reservedPresencePrecedesMalformedSiblingLayer", func(t *testing.T) { @@ -107,9 +114,7 @@ func TestNewTransportRejectsInvalidResolvedExtras(t *testing.T) { // constructio rt.ProviderExtraBody = Extra{"n": json.RawMessage(`1`)} // reserved key in provider layer... rt.ModelExtraBody = Extra{"junk": json.RawMessage(`not-json`)} // ...and a malformed non-reserved value in the model layer: the reserved-key pass runs first across both layers. _, err := NewTransport(rt) - if !errors.Is(err, ErrReservedKeys) || errors.Is(err, ErrInvalidInput) { - t.Fatalf("error = %v; want ErrReservedKeys alone when a reserved key coexists with a malformed sibling value", err) - } + testassert.Require(t, errors.Is(err, ErrReservedKeys) && !errors.Is(err, ErrInvalidInput), "error = %v; want ErrReservedKeys alone when a reserved key coexists with a malformed sibling value", err) }) t.Run("malformedProviderExtraClassifiesAsTypedInvalidInput", func(t *testing.T) { @@ -154,8 +159,10 @@ func TestNewTransportRejectsInvalidResolvedExtras(t *testing.T) { // constructio rt := testResolved() rt.ProviderExtraBody = Extra{"temperature": json.RawMessage(`0.1`)} // well-formed non-reserved values succeed... rt.ModelExtraBody = Extra{"top_k": json.RawMessage(`40`)} - if _, err := NewTransport(rt); err != nil { - t.Fatalf("valid resolved extras rejected at construction: %v", err) // ...the checks above must reject exactly malformed/reserved input and nothing else. + // ...the checks above must reject exactly malformed/reserved input and nothing else. + { + _, err := NewTransport(rt) + testassert.NoError(t, err) } }) } @@ -179,9 +186,8 @@ func TestNewTransportDeepCopiesResolvedInput(t *testing.T) { // one mutation pas } tr, err := NewTransport(rt) // construction must own deep copies before any of the mutations below can observe anything about them... (this is also why BaseURL was set pre-construction above rather than via mutation after). - if err != nil { - t.Fatalf("NewTransport returned error: %v", err) // a failure here would make every later assertion meaningless — stop with context instead of chasing phantom wire mismatches below. - } + // a failure here would make every later assertion meaningless — stop with context instead of chasing phantom wire mismatches below. + testassert.NoError(t, err) rt.Headers["X-From"] = "mutated" // mutating the original header map after construction... (must not change what this transport posts next). delete(rt.ProviderExtraBody, "temperature") // ...and deleting a layer key from the caller's own copy of it... (the retained body must still carry temperature on its request bytes). @@ -200,8 +206,11 @@ func TestNewTransportDeepCopiesResolvedInput(t *testing.T) { // one mutation pas } var posted map[string]json.RawMessage - if err := json.Unmarshal(rawBody, &posted); err != nil { // decode what was actually sent so field-level comparisons below read naturally in failure messages too. - t.Fatalf("posted body is not a JSON object: %v (%s)", err, rawBody) // unparseable bytes here would mean the ownership copy corrupted structure — fail loudly with both sides visible for diagnosis... (nothing else in this test can distinguish that shape from others). + // decode what was actually sent so field-level comparisons below read naturally in failure messages too. + // unparseable bytes here would mean the ownership copy corrupted structure — fail loudly with both sides visible for diagnosis... (nothing else in this test can distinguish that shape from others). + { + err := json.Unmarshal(rawBody, &posted) + testassert.Require(t, err == nil, "posted body is not a JSON object: %v (%s)", err, rawBody) } rawTemp, ok := posted["temperature"] // temperature must still ride on the wire exactly as constructed: present AND byte-identical to 0.1's raw form after every caller mutation above. @@ -211,12 +220,18 @@ func TestNewTransportDeepCopiesResolvedInput(t *testing.T) { // one mutation pas t.Fatalf("posted temperature = %s, want exactly 0.1's bytes (caller rewrote its own copy to another digit)", rawTemp) // a different-but-valid number here means in-place byte mutation crossed an ownership boundary somewhere between construction and Do... (narrowing it further is the suite reader's job once this row names where). } - if got := posted["model"]; string(got) != `"gpt-test"` { // sanity anchor that we captured THIS transport's request at all — a stray capture from elsewhere would make every other assertion above vacuous... (kept cheap: one field comparison, no server round trip needed to prove identity of origin). - t.Fatalf("captured body model = %s; test may have observed the wrong exchange", got) // this guard exists purely so failures above are trustworthy evidence about THIS transport's retained state and nothing else running in parallel could explain them. + // sanity anchor that we captured THIS transport's request at all — a stray capture from elsewhere would make every other assertion above vacuous... (kept cheap: one field comparison, no server round trip needed to prove identity of origin). + // this guard exists purely so failures above are trustworthy evidence about THIS transport's retained state and nothing else running in parallel could explain them. + { + got := posted["model"] + testassert.Require(t, string(got) == `"gpt-test"`, "captured body model = %s; test may have observed the wrong exchange", got) } - if _, ok := posted["X-From"]; ok { // negative anchor: header names must not leak into body keys — they travel on the wire separately by construction design... (a merged capture bug between the two channels would otherwise pass every positive check above while being structurally wrong). - t.Fatalf("header name found inside request body keys; headers and extras were conflated somewhere") // no production code path writes header names into extra-body layers, so any hit here is a test-harness or transport defect rather than legitimate content... (failing it keeps the two capture channels provably distinct for all other rows in this file). + // negative anchor: header names must not leak into body keys — they travel on the wire separately by construction design... (a merged capture bug between the two channels would otherwise pass every positive check above while being structurally wrong). + // no production code path writes header names into extra-body layers, so any hit here is a test-harness or transport defect rather than legitimate content... (failing it keeps the two capture channels provably distinct for all other rows in this file). + { + _, ok := posted["X-From"] + testassert.Require(t, !ok, "header name found inside request body keys; headers and extras were conflated somewhere") } _ = rewritten // retained only to keep the mutation pass above readable as one coherent block; its effect is fully exercised through posted["temperature"] assertions already made. The variable itself carries no further state worth asserting on directly (its bytes are what temperature should NOT show, which that check proves by positive comparison instead). @@ -245,9 +260,8 @@ func TestStreamPostsEncodedBodyWithBuiltHeaders(t *testing.T) { // request-shape tr := mustTransport(t, rt) // construction happens once per sub-case as everywhere else in this file; shared servers would blur captured state between rows and make failures unattributable. wantBody, _, err := Encode(rt, baseRequest(), nil) // independent direct encoder invocation is the byte oracle: same inputs through two different public entry points must produce identical posted bytes... (any divergence means transport and encoder disagree on ownership/copying somewhere). - if err != nil { - t.Fatalf("Encode returned error for an otherwise-valid fixture set: %v", err) // failing here rather than comparing against a zero-value body keeps the assertion below meaningful — no silent empty-vs-empty pass is possible. - } + // failing here rather than comparing against a zero-value body keeps the assertion below meaningful — no silent empty-vs-empty pass is possible. + testassert.NoError(t, err) stream, warnings, streamErr := tr.Stream(context.Background(), baseRequest(), nil) if streamErr != nil { @@ -263,8 +277,10 @@ func TestStreamPostsEncodedBodyWithBuiltHeaders(t *testing.T) { // request-shape } for key, value := range map[string]string{"Content-Type": "application/json", "Accept": "text/event-stream"} { // both defaults present at exactly their contract values — BuildHeaders owns them and nothing else in this transport may override or drop one... (resolved-header overwrite behavior is pinned by a sibling row with its own server rather than mixed into these baseline assertions). - if got := gotHeader.Get(key); got != value { - t.Fatalf("header %s = %q, want %q", key, got, value) // per-key message keeps the failing name visible without dumping the whole header map on every mismatch... (the full clone is still available in scope if a human wants it during triage of this specific row). + // per-key message keeps the failing name visible without dumping the whole header map on every mismatch... (the full clone is still available in scope if a human wants it during triage of this specific row). + { + got := gotHeader.Get(key) + testassert.Require(t, got == value, "header %s = %q, want %q", key, got, value) } } @@ -279,12 +295,18 @@ func TestStreamPostsEncodedBodyWithBuiltHeaders(t *testing.T) { // request-shape t.Fatalf("parsed first delta = %#v, want one text fragment \"ok\"", delta) // anything else means parser normalization drifted from its documented contract — this row is the cheapest place that drift would surface first... (deeper structural cases have their own dedicated files; keep comparing only what THIS response actually contains). } - if _, err := stream.Recv(); !errors.Is(err, io.EOF) { // [DONE] terminator lands as EOF exactly per contract — pinning it here too keeps this row self-contained rather than depending on sibling tests for the terminal behavior of its own fixture... (cheap: one extra receive call with an exact expected sentinel). - t.Fatalf("Recv after [DONE] = %v, want io.EOF", err) // message mirrors every other terminator assertion in the suite so cross-file greps stay consistent when hunting related regressions later. + // [DONE] terminator lands as EOF exactly per contract — pinning it here too keeps this row self-contained rather than depending on sibling tests for the terminal behavior of its own fixture... (cheap: one extra receive call with an exact expected sentinel). + // message mirrors every other terminator assertion in the suite so cross-file greps stay consistent when hunting related regressions later. + { + _, err := stream.Recv() + testassert.Require(t, errors.Is(err, io.EOF), "Recv after [DONE] = %v, want io.EOF", err) } - if cerr := stream.Close(); cerr != nil { // full consumption then release: Close on a cleanly terminated body must be quiet — no cleanup path may surface transport facts to consumers at this point... (any non-nil here would indicate state corruption beyond what the read assertions above could have caught). - t.Fatalf("Close returned error after clean consumption: %v", cerr) // included for completeness of THIS row's end-to-end story rather than as independent coverage — sibling close-behavior tests pin pathological shapes separately. + // full consumption then release: Close on a cleanly terminated body must be quiet — no cleanup path may surface transport facts to consumers at this point... (any non-nil here would indicate state corruption beyond what the read assertions above could have caught). + // included for completeness of THIS row's end-to-end story rather than as independent coverage — sibling close-behavior tests pin pathological shapes separately. + { + cerr := stream.Close() + testassert.NoError(t, cerr) } }) @@ -310,12 +332,14 @@ func TestStreamPostsEncodedBodyWithBuiltHeaders(t *testing.T) { // request-shape tr := mustTransport(t, rt) // standard construction helper — the auth dimension is just another plain string field flowing through retained state normally. stream, _, err := tr.Stream(context.Background(), baseRequest(), nil) - if err != nil { - t.Fatalf("Stream returned error for key state %q: %v", tc.name, err) // names which branch failed so a table-row mixup during editing is caught immediately rather than discovered much later in output. - } - - if got := capturedAuth; (got != "") != tc.wantAuth || (tc.wantAuth && got != "Bearer "+tc.apiKey) { // one compound check covering presence, absence, AND exact value where present — partial failures like "present but wrong value" must be caught as clearly as total absence would be. - t.Fatalf("Authorization = %q for key state %q; want presence=%v and exact form when present", got, tc.name, tc.wantAuth) // both actual and expected shapes rendered so triage never needs to reconstruct which branch ran from the test name alone. + // names which branch failed so a table-row mixup during editing is caught immediately rather than discovered much later in output. + testassert.Require(t, err == nil, "Stream returned error for key state %q: %v", tc.name, err) + + // one compound check covering presence, absence, AND exact value where present — partial failures like "present but wrong value" must be caught as clearly as total absence would be. + // both actual and expected shapes rendered so triage never needs to reconstruct which branch ran from the test name alone. + { + got := capturedAuth + testassert.Require(t, (got != "") == tc.wantAuth && (!tc.wantAuth || got == "Bearer "+tc.apiKey), "Authorization = %q for key state %q; want presence=%v and exact form when present", got, tc.name, tc.wantAuth) } if _, rerr := stream.Recv(); !errors.Is(rerr, io.EOF) { // 204 carries no body: first receive must be a clean EOF — pinning that an empty accepted response does not error the consumer path either. @@ -327,8 +351,11 @@ func TestStreamPostsEncodedBodyWithBuiltHeaders(t *testing.T) { // request-shape } }) - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "p"}, WireSystemRole: ""}); !errors.Is(err, ErrInvalidModelRef) { // trailing sanity anchor reused from sibling file's conventions — confirms this test's own assumptions about which validation layer owns which error have not drifted since it was written... (cheap insurance that a future refactor doesn't silently move model-completeness checking into Stream and leave construction accepting partial refs here). - t.Fatalf("premise broken: NewTransport no longer rejects incomplete identities at construction") // if this fires, every other row in THIS file set still passes but the suite's overall story about where errors surface has changed — flagging it explicitly keeps that decision visible rather than implicit. + // trailing sanity anchor reused from sibling file's conventions — confirms this test's own assumptions about which validation layer owns which error have not drifted since it was written... (cheap insurance that a future refactor doesn't silently move model-completeness checking into Stream and leave construction accepting partial refs here). + // if this fires, every other row in THIS file set still passes but the suite's overall story about where errors surface has changed — flagging it explicitly keeps that decision visible rather than implicit. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "p"}, WireSystemRole: ""}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: NewTransport no longer rejects incomplete identities at construction") } } @@ -380,9 +407,8 @@ func TestStreamIssuesExactlyOnePhysicalAttempt(t *testing.T) { // no alternate D tr := mustTransport(t, rt) // standard construction helper — no auth or extras configured so the only interesting dimension remains attempt-counting itself. stream, _, err := tr.Stream(context.Background(), baseRequest(), nil) // 2xx acceptance: Stream returns BEFORE payload validity is known anywhere... (this ordering is precisely why mid-stream failures exist as a distinct category from request-level ones). - if err != nil { - t.Fatalf("Stream returned error for an accepted response: %v", err) // failing here would mean something broke before we even got to test the interesting part — stop with context rather than letting later assertions run against a missing stream. - } + // failing here would mean something broke before we even got to test the interesting part — stop with context rather than letting later assertions run against a missing stream. + testassert.NoError(t, err) var recvErr error // captured separately so both its sentinel identity AND absence-of-retry can be asserted in distinct checks below... (one combined check would hide which half failed when output is skimmed). if _, recvErr = stream.Recv(); !errors.Is(recvErr, ErrProtocol) { // the malformed event must surface as a typed protocol failure — nothing more exotic (raw unmarshal error unwrapped...) may reach consumers here. @@ -391,12 +417,18 @@ func TestStreamIssuesExactlyOnePhysicalAttempt(t *testing.T) { // no alternate D t.Fatalf("%d attempts recorded after a mid-stream protocol failure, want exactly 1 for that whole invocation", got) // any number above one here would be direct evidence of retry behavior living somewhere in this package — which is forbidden by contract and worth failing loudly on the first sight... (this row exists specifically because such regressions are invisible everywhere else). } - if cerr := stream.Close(); cerr != nil { // releasing after a failed read must still behave like every other Close: quiet, idempotent-friendly cleanup with no transport facts surfaced to consumers at this point. - t.Fatalf("Close returned error following protocol failure: %v", cerr) // included because close-after-error is exactly the kind of edge where state corruption hides best — asserting it here costs one line and removes an entire class of silent bugs from later review... (not redundant with sibling close tests since those cover different prior-state shapes). + // releasing after a failed read must still behave like every other Close: quiet, idempotent-friendly cleanup with no transport facts surfaced to consumers at this point. + // included because close-after-error is exactly the kind of edge where state corruption hides best — asserting it here costs one line and removes an entire class of silent bugs from later review... (not redundant with sibling close tests since those cover different prior-state shapes). + { + cerr := stream.Close() + testassert.NoError(t, cerr) } - if _, againErr := stream.Recv(); !errors.Is(againErr, io.EOF) { // post-failure terminal state: further receives see EOF through the done flag rather than re-attempting reads or errors... (pinning it here completes this row's end-to-end story about what a mid-stream failure leaves behind). - t.Fatalf("Recv after protocol failure + Close = %v, want io.EOF", againErr) // mirrors every other terminal-state assertion in the suite for consistency — future readers should never have to wonder whether THIS particular shape was intentionally left unasserted. + // post-failure terminal state: further receives see EOF through the done flag rather than re-attempting reads or errors... (pinning it here completes this row's end-to-end story about what a mid-stream failure leaves behind). + // mirrors every other terminal-state assertion in the suite for consistency — future readers should never have to wonder whether THIS particular shape was intentionally left unasserted. + { + _, againErr := stream.Recv() + testassert.Require(t, errors.Is(againErr, io.EOF), "Recv after protocol failure + Close = %v, want io.EOF", againErr) } }) } @@ -446,18 +478,21 @@ func TestStreamReturnsHTTPStatusErrorWithProviderMessage(t *testing.T) { // one } } - if errors.Is(err, ErrAuthFailed) { // no auth sentinel for ANY of these ordinary failure codes: 429/503/502/504 are all non-auth by contract... (pinning it once per table iteration rather than only in the dedicated auth row because forgetting to check here would let a blanket-join regression hide inside otherwise-green extraction tests). - t.Fatalf("ErrAuthFailed present on ordinary %d failure", tc.wantCode) // message names the specific status so output stays actionable without cross-referencing which table row ran... (cheap specificity costs nothing per iteration given how cheap this check itself is). - } + // no auth sentinel for ANY of these ordinary failure codes: 429/503/502/504 are all non-auth by contract... (pinning it once per table iteration rather than only in the dedicated auth row because forgetting to check here would let a blanket-join regression hide inside otherwise-green extraction tests). + // message names the specific status so output stays actionable without cross-referencing which table row ran... (cheap specificity costs nothing per iteration given how cheap this check itself is). + testassert.Require(t, !errors.Is(err, ErrAuthFailed), "ErrAuthFailed present on ordinary %d failure", tc.wantCode) - if len(warnings) != 0 { // warning-free fixtures must stay silent on failed attempts too — same negative anchor as elsewhere in this file with identical rationale about noise vs signal distinguishability later. - t.Fatalf("unexpected protocol warnings from a failure-path encoding: %#v", warnings) // included for parity with sibling rows' discipline rather than because extraction itself could produce them (it cannot today, but the guard is free and future-proofs against accidental coupling). - } + // warning-free fixtures must stay silent on failed attempts too — same negative anchor as elsewhere in this file with identical rationale about noise vs signal distinguishability later. + // included for parity with sibling rows' discipline rather than because extraction itself could produce them (it cannot today, but the guard is free and future-proofs against accidental coupling). + testassert.Require(t, len(warnings) == 0, "unexpected protocol warnings from a failure-path encoding: %#v", warnings) }) } - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai", Model: ""}}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring the one in sibling test files — confirms construction-time validation surface has not drifted since this file was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // if this fires, the suite's story about where validation happens has changed and every file using that assumption needs a re-read — flagging it here makes that maintenance debt visible immediately rather than buried in unrelated later failures. + // closing sanity anchor mirroring the one in sibling test files — confirms construction-time validation surface has not drifted since this file was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). + // if this fires, the suite's story about where validation happens has changed and every file using that assumption needs a re-read — flagging it here makes that maintenance debt visible immediately rather than buried in unrelated later failures. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai", Model: ""}}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } } @@ -494,9 +529,9 @@ func TestStreamReadsErrorBodyThroughOneMiBLimit(t *testing.T) { // one dedicated t.Fatalf("ErrAuthFailed present on ordinary 503 failure") // same non-auth negative anchor as sibling rows — cheap and consistent across the whole file set. } - if len(warnings) != 0 { // warning-free fixtures stay silent even under oversized-body conditions — identical rationale to every other such anchor in this suite about noise vs signal distinguishability later on. - t.Fatalf("unexpected protocol warnings from an oversized error body: %#v", warnings) // included for parity with sibling rows' discipline rather than because size-bounding logic itself could produce them (it cannot today, but the guard is free and future-proofs against accidental coupling). - } + // warning-free fixtures stay silent even under oversized-body conditions — identical rationale to every other such anchor in this suite about noise vs signal distinguishability later on. + // included for parity with sibling rows' discipline rather than because size-bounding logic itself could produce them (it cannot today, but the guard is free and future-proofs against accidental coupling). + testassert.Require(t, len(warnings) == 0, "unexpected protocol warnings from an oversized error body: %#v", warnings) var statusAgain *HTTPStatusError // pointer to the concrete error type so errors.As can fill its fields on success — Error() is defined on the pointer, a value here would be an invalid target. _, _, againErr := tr.Stream(context.Background(), baseRequest(), nil) // a second invocation on the same transport must fail identically and independently — proving the bound applies per-invocation rather than being some one-shot state that only worked once by accident of ordering... (this is the row a future "cache truncated bodies across calls" optimization would break first). @@ -546,8 +581,11 @@ func TestStreamJoinsAuthSentinelOn401And403(t *testing.T) { // auth-failure iden }) } - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}, WireSystemRole: "system"}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this file was written... (cheap local insurance against silent drift in where errors are produced). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // same explicit framing as the equivalent anchors elsewhere so a grep for that phrase finds every such guard consistently across the suite. + // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this file was written... (cheap local insurance against silent drift in where errors are produced). + // same explicit framing as the equivalent anchors elsewhere so a grep for that phrase finds every such guard consistently across the suite. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}, WireSystemRole: "system"}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } } @@ -602,9 +640,8 @@ func TestStreamMidFlightCancellationUnblocksBodyRead(t *testing.T) { // modeled ctx, cancel := context.WithCancel(context.Background()) // fresh cancellable parent; live through most of this test's body on purpose... (its lifetime IS what we're measuring behavior against). stream, _, err := tr.Stream(ctx, baseRequest(), nil) // acceptance must succeed while ctx is still healthy — any failure here means something broke before the interesting part even started. - if err != nil { - t.Fatalf("Stream returned error before cancellation: %v", err) // stop with context rather than letting later assertions run against a missing stream object entirely... (cheap early exit keeps output focused on whichever phase actually failed). - } + // stop with context rather than letting later assertions run against a missing stream object entirely... (cheap early exit keeps output focused on whichever phase actually failed). + testassert.NoError(t, err) first, recvErr := stream.Recv() // the flushed event arrives normally as any other well-formed first delta would in this suite's fixtures... (pinning its exact parsed shape below doubles as proof that pre-cancel reads behave identically to non-canceled ones everywhere else). if recvErr != nil { @@ -680,9 +717,8 @@ func TestStreamHonorsStandardRedirects(t *testing.T) { // modeled as: first hit tr := mustTransport(t, rt) // construction helper — same rationale throughout this file for one shared path to valid transport values. stream, _, err := tr.Stream(context.Background(), baseRequest(), nil) // the entire redirect dance happens inside THIS ONE invocation's lifetime... (no separate "follow-up call" exists or may be invented by later edits). - if err != nil { - t.Fatalf("Stream failed while following a standard library-supported redirect: %v", err) // failure here would mean default client behavior regressed somewhere — fail with that specific framing since everything else in this row is trivially correct by construction... (message names the capability under test explicitly). - } + // failure here would mean default client behavior regressed somewhere — fail with that specific framing since everything else in this row is trivially correct by construction... (message names the capability under test explicitly). + testassert.NoError(t, err) delta, recvErr := stream.Recv() // acceptance must come from post-redirect target's body specifically... (proven next via content marker below rather than assumed from status codes alone which both hops share nominally as 2xx-equivalent outcomes differently worded). if recvErr != nil { @@ -717,9 +753,8 @@ func TestStreamReturnsEncoderWarningsOnAttemptFailure(t *testing.T) { // warning Arguments: json.RawMessage(`{}`), // empty object arguments fine here since NO validation of argument content happens at warning-trigger level... (they just need to exist structurally as a completed call). }}, // single call suffices — multiple would only add noise without changing whether the one expected warning appears or not. }) - if err != nil { - t.Fatalf("NewMessage fixture failed: %v", err) // stop with context rather than continuing against an invalid trigger message whose absence of warnings could be misread as transport behavior... (premises must hold before conclusions can mean anything). - } + // stop with context rather than continuing against an invalid trigger message whose absence of warnings could be misread as transport behavior... (premises must hold before conclusions can mean anything). + testassert.NoError(t, err) req := Request{Messages: []Message{userText("hi"), assistant}} // two-message request puts the warning-bearing assistant at index 1 specifically — assertion below pins that exact position rather than just count for unambiguous attribution. @@ -795,8 +830,11 @@ func TestStreamReturnsEncoderWarningsOnAttemptFailure(t *testing.T) { // warning } }) - if _, err := NewMessage(Message{Role: RoleAssistant}); !errors.Is(err, ErrMissingSource) { // sanity guard on the fixture's own assumptions used above (assistant without source is invalid — our real one carries it deliberately)... if this fires then warning-trigger reasoning in wantWarning no longer holds and every row must be re-derived from scratch. - t.Fatalf("fixture premise broken: assistant message WITHOUT complete source unexpectedly accepted") // explicit about which assumption failed rather than generic "something wrong" phrasing that would leave triage starting over from zero each time... (cheap specificity keeps maintenance of this file tractable long-term). + // sanity guard on the fixture's own assumptions used above (assistant without source is invalid — our real one carries it deliberately)... if this fires then warning-trigger reasoning in wantWarning no longer holds and every row must be re-derived from scratch. + // explicit about which assumption failed rather than generic "something wrong" phrasing that would leave triage starting over from zero each time... (cheap specificity keeps maintenance of this file tractable long-term). + { + _, err := NewMessage(Message{Role: RoleAssistant}) + testassert.Require(t, errors.Is(err, ErrMissingSource), "fixture premise broken: assistant message WITHOUT complete source unexpectedly accepted") } } @@ -956,8 +994,11 @@ func TestStreamClassifiesInvalidInputErrors(t *testing.T) { // one transport ove } }) - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this classification row was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT specific error-class question). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + // closing sanity anchor mirroring sibling files' convention — construction-time validation surface unchanged since this classification row was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT specific error-class question). + // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } } @@ -967,8 +1008,10 @@ func TestTransportHasNoPublicClientSeam(t *testing.T) { // reflection over the t structType := typ.Elem() // field checks run against the underlying struct shape; a pointer type itself has no fields to inspect. for i := 0; i < structType.NumField(); i++ { // every stored field must stay package-private — the seam this test forbids would have to be a FIELD (behavior pinned separately by method loop below). - if f := structType.Field(i); f.PkgPath == "" { - t.Fatalf("Transport exposes public field %s; no alternate client or RoundTripper seam may exist on this type", f.Name) // naming the exact offending member makes any future accidental export immediately visible and attributable in CI output without re-reading source... (this is a structural invariant worth failing loudly on first sight). + // naming the exact offending member makes any future accidental export immediately visible and attributable in CI output without re-reading source... (this is a structural invariant worth failing loudly on first sight). + { + f := structType.Field(i) + testassert.Require(t, f.PkgPath != "", "Transport exposes public field %s; no alternate client or RoundTripper seam may exist on this type", f.Name) } } @@ -979,17 +1022,23 @@ func TestTransportHasNoPublicClientSeam(t *testing.T) { // reflection over the t } // unexported helpers never appear here by definition — only genuinely public API surface enters consideration at all. } - sort.Strings(exportedMethods) // deterministic ordering before comparison so element-wise equality below is meaningful regardless of iteration order from reflection internals... (cheap insurance against flaky-looking failures that are really just nondeterministic enumeration). - want := []string{"Stream"} // THE complete expected public method set on this type: nothing more, nothing less than exactly Stream itself. - if !reflect.DeepEqual(exportedMethods, want) { // exact-set comparison — any extra export (a Do alias, Client getter...) would reopen the seam this test exists to keep closed forever... (and its absence is equally pinned here since removing Stream entirely would also fail loudly rather than silently). - t.Fatalf("public methods on Transport = %v; want exactly [Stream]", exportedMethods) // rendered both sides side-by-side so whatever drifted — added OR removed member alike — is immediately obvious from output alone. - } - - if _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}, WireSystemRole: ""}); !errors.Is(err, ErrInvalidModelRef) { // closing sanity anchor mirroring sibling files' convention one final time in THIS file as well — construction-time validation surface unchanged since this structural test was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). - t.Fatalf("premise broken: incomplete model identity no longer rejected at NewTransport") // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + sort.Strings(exportedMethods) // deterministic ordering before comparison so element-wise equality below is meaningful regardless of iteration order from reflection internals... (cheap insurance against flaky-looking failures that are really just nondeterministic enumeration). + want := []string{"Stream"} // THE complete expected public method set on this type: nothing more, nothing less than exactly Stream itself. + // exact-set comparison — any extra export (a Do alias, Client getter...) would reopen the seam this test exists to keep closed forever... (and its absence is equally pinned here since removing Stream entirely would also fail loudly rather than silently). + // rendered both sides side-by-side so whatever drifted — added OR removed member alike — is immediately obvious from output alone. + testassert.Require(t, reflect.DeepEqual(exportedMethods, want), "public methods on Transport = %v; want exactly [Stream]", exportedMethods) + + // closing sanity anchor mirroring sibling files' convention one final time in THIS file as well — construction-time validation surface unchanged since this structural test was written... (kept deliberately cheap and local rather than extracted into a shared helper because each call site's context makes its failure message most useful when phrased for THAT row set specifically). + // same explicit framing as equivalent anchors elsewhere so grep finds every such guard consistently across the suite. + { + _, err := NewTransport(ResolvedTransport{Model: ModelRef{Provider: "openai"}, WireSystemRole: ""}) + testassert.Require(t, errors.Is(err, ErrInvalidModelRef), "premise broken: incomplete model identity no longer rejected at NewTransport") } - if _, ok := interface{}(mustTransport(t, testResolved())).(*http.Client); ok { // unreachability guard documenting that Transport itself is never an http.Client under any aliasing or embedding trick — kept more as compile-time reminder than runtime check... (its real value is forcing anyone reading this line to consciously acknowledge type-identity distinction). - t.Fatal("Transport must not be or masquerade as *http.Client by type identity") // if someone ever aliases/embeds these types, fail loudly instead of letting the forbidden seam creep back in through pure type-substitution without any visible field-level change... (structural tests catch fields; this one catches whole-type substitution attempts specifically). + // unreachability guard documenting that Transport itself is never an http.Client under any aliasing or embedding trick — kept more as compile-time reminder than runtime check... (its real value is forcing anyone reading this line to consciously acknowledge type-identity distinction). + // if someone ever aliases/embeds these types, fail loudly instead of letting the forbidden seam creep back in through pure type-substitution without any visible field-level change... (structural tests catch fields; this one catches whole-type substitution attempts specifically). + { + _, ok := interface{}(mustTransport(t, testResolved())).(*http.Client) + testassert.Require(t, !ok, "%v", "Transport must not be or masquerade as *http.Client by type identity") } } diff --git a/model/validators_test.go b/model/validators_test.go index e10b23f0..20aef11f 100644 --- a/model/validators_test.go +++ b/model/validators_test.go @@ -1,16 +1,17 @@ package model -// Validation-only validator differential: ValidateOutput, ValidateStreamDelta -// and ValidateToolCall must accept and reject exactly what their owning -// constructors do — same error classes, precedence and detail — while -// retaining and mutating nothing. The allocation benchmarks at the bottom are -// the evidence that the read-only path no longer pays the ownership copy. +// Validation-only validator differential: every model validator must accept +// and reject exactly what its owning constructor does — same error classes, +// precedence and detail — while retaining and mutating nothing. The +// allocation benchmarks at the bottom are the evidence that the read-only path +// no longer pays the ownership copy. import ( "encoding/json" - "reflect" - "strings" + "errors" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) // richOutputFixture builds one deterministic completed output carrying every @@ -106,332 +107,21 @@ var benchRichRequest = Request{ // verdict with byte-identical error detail for one input. func requireEqualConstructorVerdict(t *testing.T, name string, newErr, validateErr error) { t.Helper() - if (newErr == nil) != (validateErr == nil) { - t.Fatalf("%s: New error = %v, Validate error = %v; verdicts diverge", name, newErr, validateErr) - } + testassert.Require(t, (newErr == nil) == (validateErr == nil), "%s: New error = %v, Validate error = %v; verdicts diverge", name, newErr, validateErr) if newErr != nil && newErr.Error() != validateErr.Error() { t.Fatalf("%s: New detail = %q, Validate detail = %q", name, newErr.Error(), validateErr.Error()) } } -// TestValidateOutputMatchesNewOutput pins the Output differential over valid -// and invalid shapes: identical verdicts and detail, and an input the -// validator leaves exactly as it received it. -func TestValidateOutputMatchesNewOutput(t *testing.T) { - textMsg := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText, Text: "done"}} }) - payloadMsg := assistantMsg(t, func(m *Message) { - m.Content = []ContentPart{{Kind: PartText, Text: "x"}} - c, _ := NewToolCall(ToolCall{ID: "c1", Name: "f", Arguments: json.RawMessage(`{oops`)}) - m.ToolCalls = []ToolCall{c} - }) - userRoleMsg := mustMessage(t, Message{Role: RoleUser}) - noPayloadMsg := assistantMsg(t, nil) - - // Every row carrying an assistantMsg-built message sits on fullRef — the - // same complete identity that message already carries — so the Output - // domain's matched-source premise holds and the row reaches the rule it - // names instead of stopping at ErrSourceMismatch. errContains pins that - // actual rule; the mismatch row is the one intentional exception. - rows := []struct { - name string - out Output - wantErr bool - errContains string - }{ - {"rich completed", richOutputFixture(), false, ""}, - {"errored without message", Output{Status: OutputErrored, Source: fullRef, Detail: "boom"}, false, ""}, - {"interrupted with partial", Output{Status: OutputInterrupted, Source: fullRef, Message: &textMsg, Detail: "stopped"}, false, ""}, - {"invalid status", Output{Status: OutputStatus("done"), Source: fullRef, Detail: "d"}, true, "invalid status"}, - {"zero source", Output{Status: OutputCompleted, Message: &textMsg}, true, "complete source model identity"}, - {"completed without message", Output{Status: OutputCompleted, Source: fullRef}, true, "requires one assistant message"}, - {"completed without payload", Output{Status: OutputCompleted, Source: fullRef, Message: &noPayloadMsg}, true, "requires an assistant payload"}, - {"completed with detail", Output{Status: OutputCompleted, Source: fullRef, Message: &textMsg, Detail: "boom"}, true, "must carry empty detail"}, - {"errored without detail", Output{Status: OutputErrored, Source: fullRef}, true, "requires non-empty detail"}, - {"interrupted with tool calls", Output{Status: OutputInterrupted, Source: fullRef, Message: &payloadMsg, Detail: "stopped"}, true, "must not carry tool calls"}, - {"message source mismatch", func() Output { - msg := assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText, Text: "x"}} }) - return Output{Status: OutputCompleted, Source: ModelRef{Provider: "acme", Model: "other"}, Message: &msg} - }(), true, "differs from output source"}, - {"non-assistant message", Output{Status: OutputCompleted, Source: fullRef, Message: &userRoleMsg}, true, "carry only assistant messages"}, - } - - for _, row := range rows { - t.Run(row.name, func(t *testing.T) { - _, newErr := NewOutput(row.out) - validateErr := ValidateOutput(row.out) - requireEqualConstructorVerdict(t, row.name, newErr, validateErr) - if (newErr != nil) != row.wantErr { - t.Fatalf("fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) - } - if row.errContains != "" && !strings.Contains(newErr.Error(), row.errContains) { - t.Fatalf("fixture %s reached the wrong rule: %v, want a failure naming %q", row.name, newErr, row.errContains) - } - }) - } - - // The validator mutates nothing: an independently built value-equal - // sibling compares equal after the call, nested reference types included. - in := richOutputFixture() - before := richOutputFixture() - if err := ValidateOutput(in); err != nil { - t.Fatalf("ValidateOutput rejected the rich fixture: %v", err) - } - if !reflect.DeepEqual(in, before) { - t.Fatal("ValidateOutput mutated its input") - } -} - -// TestValidateStreamDeltaMatchesNewStreamDelta pins the StreamDelta -// differential over valid and invalid shapes with identical verdicts and -// detail, and an untouched input. -func TestValidateStreamDeltaMatchesNewStreamDelta(t *testing.T) { - neg := -1 - rows := []struct { - name string - delta StreamDelta - wantErr bool - }{ - {"rich choice-bearing", richDeltaFixture(), false}, - {"empty delta", StreamDelta{HasChoice: true}, false}, - {"usage only", StreamDelta{HasChoice: true, Usage: &Usage{InputTokens: 1}}, false}, - {"negative content position", StreamDelta{HasChoice: true, ContentFragments: []ContentFragment{{Position: -1, Kind: PartText, Text: "x"}}}, true}, - {"cross-kind fragment", StreamDelta{HasChoice: true, ContentFragments: []ContentFragment{{Position: 0, Kind: PartText, Text: "x", URL: "u"}}}, true}, - {"unknown kind", StreamDelta{HasChoice: true, ContentFragments: []ContentFragment{{Position: 0, Kind: PartKind("audio"), Text: "x"}}}, true}, - {"negative tool pointer position", StreamDelta{HasChoice: true, ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", Position: &neg}}}, true}, - {"malformed tool extra", StreamDelta{HasChoice: true, ToolFragments: []ToolCallFragment{{ID: "c", Name: "f", Extra: Extra{"k": json.RawMessage(`{oops`)}}}}, true}, - {"malformed message extra", StreamDelta{HasChoice: true, MessageExtra: Extra{"m": json.RawMessage(`[1`)}}, true}, - {"valid-looking text with an invalid sibling", StreamDelta{HasChoice: true, RefusalFragment: "no", ContentFragments: []ContentFragment{{Position: -1, Kind: PartText, Text: "x"}}}, true}, - } - - for _, row := range rows { - t.Run(row.name, func(t *testing.T) { - _, newErr := NewStreamDelta(row.delta) - validateErr := ValidateStreamDelta(row.delta) - requireEqualConstructorVerdict(t, row.name, newErr, validateErr) - if (newErr != nil) != row.wantErr { - t.Fatalf("fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) - } - }) - } - - // The validator mutates nothing, nested fragments and extras included. - in := richDeltaFixture() - before := richDeltaFixture() - if err := ValidateStreamDelta(in); err != nil { - t.Fatalf("ValidateStreamDelta rejected the rich fixture: %v", err) - } - if !reflect.DeepEqual(in, before) { - t.Fatal("ValidateStreamDelta mutated its input") - } -} - -// TestValidateToolCallMatchesNewToolCall pins the ToolCall differential: the -// id/name check precedes the Extra check under both entry points, raw -// arguments of every JSON shape stay accepted, and the validator leaves the -// input untouched. -func TestValidateToolCallMatchesNewToolCall(t *testing.T) { - rows := []struct { - name string - call ToolCall - wantErr bool - }{ - {"rich", richToolCallFixture(), false}, - {"object arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`{"a":1}`)}, false}, - {"empty arguments", ToolCall{ID: "c", Name: "f"}, false}, - {"malformed arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`{oops`)}, false}, - {"null arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`null`)}, false}, - {"array arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`[1,2]`)}, false}, - {"primitive arguments", ToolCall{ID: "c", Name: "f", Arguments: json.RawMessage(`"x"`)}, false}, - {"empty id", ToolCall{Name: "f"}, true}, - {"empty name", ToolCall{ID: "c"}, true}, - {"malformed extra", ToolCall{ID: "c", Name: "f", Extra: Extra{"k": json.RawMessage(`{oops`)}}, true}, - {"id precedence over malformed extra", ToolCall{Name: "f", Extra: Extra{"k": json.RawMessage(`{oops`)}}, true}, - } - - for _, row := range rows { - t.Run(row.name, func(t *testing.T) { - _, newErr := NewToolCall(row.call) - validateErr := ValidateToolCall(row.call) - requireEqualConstructorVerdict(t, row.name, newErr, validateErr) - if (newErr != nil) != row.wantErr { - t.Fatalf("fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) - } - if row.name == "id precedence over malformed extra" && !strings.Contains(newErr.Error(), "non-empty id and name") { - t.Fatalf("precedence row reported the wrong failure: %v", newErr) - } - }) - } - - // The validator mutates nothing, argument bytes and extras included. - in := richToolCallFixture() - before := richToolCallFixture() - if err := ValidateToolCall(in); err != nil { - t.Fatalf("ValidateToolCall rejected the rich fixture: %v", err) - } - if !reflect.DeepEqual(in, before) { - t.Fatal("ValidateToolCall mutated its input") - } -} - -// TestValidateContentPartMatchesNewContentPart pins the ContentPart -// differential over the cross-kind, unknown-kind, missing-wire-type and Extra -// boundary shapes: identical verdicts and detail, and an input the validator -// leaves exactly as it received it. -func TestValidateContentPartMatchesNewContentPart(t *testing.T) { - rows := []struct { - name string - part ContentPart - wantErr bool - }{ - {"rich text", ContentPart{Kind: PartText, Text: "hello", Extra: Extra{"k": json.RawMessage(`"v"`)}}, false}, - {"empty text", ContentPart{Kind: PartText}, false}, - {"image url", ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png"}, false}, - {"opaque with extra", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", Extra: Extra{"t": json.RawMessage(`{"n":1}`)}}, false}, - {"text carrying url", ContentPart{Kind: PartText, Text: "a", URL: "u"}, true}, - {"text carrying opaque wire type", ContentPart{Kind: PartText, Text: "a", OpaqueWireType: "thinking"}, true}, - {"image carrying text", ContentPart{Kind: PartImageURL, URL: "u", Text: "a"}, true}, - {"image carrying opaque wire type", ContentPart{Kind: PartImageURL, URL: "u", OpaqueWireType: "thinking"}, true}, - {"opaque carrying text", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", Text: "a"}, true}, - {"opaque carrying url", ContentPart{Kind: PartOpaque, OpaqueWireType: "thinking", URL: "u"}, true}, - {"opaque without wire type", ContentPart{Kind: PartOpaque}, true}, - {"unknown kind", ContentPart{Kind: PartKind("audio"), Text: "a"}, true}, - {"malformed extra", ContentPart{Kind: PartText, Text: "a", Extra: Extra{"raw": json.RawMessage("not json")}}, true}, - {"empty extra value", ContentPart{Kind: PartText, Text: "a", Extra: Extra{"e": json.RawMessage{}}}, true}, - {"null extra value", ContentPart{Kind: PartText, Text: "a", Extra: Extra{"n": json.RawMessage(`null`)}}, false}, - } - - for _, row := range rows { - t.Run(row.name, func(t *testing.T) { - _, newErr := NewContentPart(row.part) - validateErr := ValidateContentPart(row.part) - requireEqualConstructorVerdict(t, row.name, newErr, validateErr) - if (newErr != nil) != row.wantErr { - t.Fatalf("fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) - } - }) - } - - // The validator mutates nothing: a value-equal sibling still compares - // equal after the call, extras included. - in := ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png", Extra: Extra{"detail": json.RawMessage(`"high"`)}} - before := ContentPart{Kind: PartImageURL, URL: "https://example.com/a.png", Extra: Extra{"detail": json.RawMessage(`"high"`)}} - if err := ValidateContentPart(in); err != nil { - t.Fatalf("ValidateContentPart rejected the rich fixture: %v", err) - } - if !reflect.DeepEqual(in, before) { - t.Fatal("ValidateContentPart mutated its input") - } -} - -// TestValidateToolDefinitionMatchesNewToolDefinition pins the ToolDefinition -// differential: the name check precedes the parameter check, the parameter -// shape is syntax-only (a huge number literal stays accepted), and an input -// with parameter bytes is left exactly as the validator received it. -func TestValidateToolDefinitionMatchesNewToolDefinition(t *testing.T) { - rows := []struct { - name string - def ToolDefinition - wantErr bool - }{ - {"object parameters", ToolDefinition{Name: "f", Description: "d", Parameters: json.RawMessage(`{"type":"object"}`)}, false}, - {"object parameters with whitespace", ToolDefinition{Name: "f", Parameters: json.RawMessage(" {\"a\": 1} ")}, false}, - {"huge number literal is syntax-valid", ToolDefinition{Name: "f", Parameters: json.RawMessage(`{"n":1e1000}`)}, false}, - {"empty name", ToolDefinition{Parameters: json.RawMessage(`{}`)}, true}, - {"empty name precedes malformed parameters", ToolDefinition{Parameters: json.RawMessage(`{oops`)}, true}, - {"empty parameters", ToolDefinition{Name: "f"}, true}, - {"null parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`null`)}, true}, - {"array parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`[1]`)}, true}, - {"primitive parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`"x"`)}, true}, - {"malformed parameters", ToolDefinition{Name: "f", Parameters: json.RawMessage(`{oops`)}, true}, - } - - for _, row := range rows { - t.Run(row.name, func(t *testing.T) { - _, newErr := NewToolDefinition(row.def) - validateErr := ValidateToolDefinition(row.def) - requireEqualConstructorVerdict(t, row.name, newErr, validateErr) - if (newErr != nil) != row.wantErr { - t.Fatalf("fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) - } - }) - } - - // The validator mutates nothing, parameter bytes included. - in := ToolDefinition{Name: "echo", Description: "d", Parameters: json.RawMessage(`{"deep":{"a":[1,2]}}`)} - before := ToolDefinition{Name: "echo", Description: "d", Parameters: json.RawMessage(`{"deep":{"a":[1,2]}}`)} - if err := ValidateToolDefinition(in); err != nil { - t.Fatalf("ValidateToolDefinition rejected the rich fixture: %v", err) - } - if !reflect.DeepEqual(in, before) { - t.Fatal("ValidateToolDefinition mutated its input") - } -} - -// TestValidateRequestMatchesNewRequest pins the Request differential over the -// message and tool passes: identical verdicts and detail, the message pass -// first in index order, the per-tool validation ahead of the duplicate-name -// rejection inside the tools pass, and an input the validator leaves exactly -// as it received it. -func TestValidateRequestMatchesNewRequest(t *testing.T) { - validMessage := func() Message { - return assistantMsg(t, func(m *Message) { m.Content = []ContentPart{{Kind: PartText, Text: "hi"}} }) - } - validTool := func(name string) ToolDefinition { - return ToolDefinition{Name: name, Description: "d", Parameters: json.RawMessage(`{"type":"object"}`)} - } - - rows := []struct { - name string - req Request - wantErr bool - prefix string // required leading text of the shared detail - contains string // required substring of the shared detail - }{ - {"valid messages and tools", Request{Messages: []Message{validMessage()}, Tools: []ToolDefinition{validTool("echo")}}, false, "", ""}, - {"empty request", Request{}, false, "", ""}, - {"tools only capture shape", Request{Tools: []ToolDefinition{validTool("echo")}}, false, "", ""}, - {"invalid message", Request{Messages: []Message{{Role: Role("bogus")}}}, true, "messages[0]:", ""}, - {"invalid message part", Request{Messages: []Message{{Role: RoleUser, Content: []ContentPart{{Kind: PartText, URL: "u"}}}}}, true, "messages[0]: content[0]:", ""}, - {"message error beats tool error", Request{Messages: []Message{{Role: Role("bogus")}}, Tools: []ToolDefinition{{}}}, true, "messages[0]:", ""}, - {"invalid tool", Request{Tools: []ToolDefinition{{Name: "f"}}}, true, "tools[0]:", ""}, - {"second tool invalid after a valid first", Request{Tools: []ToolDefinition{validTool("a"), {Name: "f"}}}, true, "tools[1]:", ""}, - {"duplicate name", Request{Tools: []ToolDefinition{validTool("echo"), validTool("echo")}}, true, "", "duplicate tool definition name"}, - {"duplicate reported after the second tool's own validation", Request{Tools: []ToolDefinition{validTool("echo"), {Name: "echo", Parameters: json.RawMessage(`{oops`)}}}, true, "tools[1]:", "malformed JSON parameters"}, - } - - for _, row := range rows { - t.Run(row.name, func(t *testing.T) { - _, newErr := NewRequest(row.req) - validateErr := ValidateRequest(row.req) - requireEqualConstructorVerdict(t, row.name, newErr, validateErr) - if (newErr != nil) != row.wantErr { - t.Fatalf("fixture %s: error = %v, wantErr = %v", row.name, newErr, row.wantErr) - } - if row.prefix != "" && !strings.HasPrefix(newErr.Error(), row.prefix) { - t.Fatalf("fixture %s error = %q, want the %q prefix", row.name, newErr, row.prefix) - } - if row.contains != "" && !strings.Contains(newErr.Error(), row.contains) { - t.Fatalf("fixture %s reached the wrong rule: %v, want a failure naming %q", row.name, newErr, row.contains) - } - }) - } - - // The validator mutates nothing: a value-equal sibling still compares - // equal after the call, nested reference types included. - in := Request{ - Messages: []Message{validMessage()}, - Tools: []ToolDefinition{validTool("echo")}, - } - before := Request{ - Messages: []Message{validMessage()}, - Tools: []ToolDefinition{validTool("echo")}, - } - if err := ValidateRequest(in); err != nil { - t.Fatalf("ValidateRequest rejected the rich fixture: %v", err) - } - if !reflect.DeepEqual(in, before) { - t.Fatal("ValidateRequest mutated its input") +// matchesAnySentinel reports whether err matches one of the acceptable +// sentinels a row's original owner asserted. +func matchesAnySentinel(err error, sentinels ...error) bool { + for _, sentinel := range sentinels { + if errors.Is(err, sentinel) { + return true + } } + return false } // Benchmarks: the validation-only path against the owning constructor over diff --git a/plugins/jobs/harness_test.go b/plugins/jobs/harness_test.go index cd4cb193..04d982d4 100644 --- a/plugins/jobs/harness_test.go +++ b/plugins/jobs/harness_test.go @@ -9,6 +9,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" ) // TestStopJobReaperBarrierAndHarnessStopDelivery proves the ordered @@ -42,25 +43,19 @@ func TestStopJobReaperBarrierAndHarnessStopDelivery(t *testing.T) { }, Jobs: j, }) - if err != nil { - t.Fatalf("harness.New: %v", err) - } + testassert.NoError(t, err) session, err := h.CreateSession(context.Background(), harness.CreateSessionRequest{ Workspace: t.TempDir(), AgentType: "coder", }) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } + testassert.NoError(t, err) sid := session.Identity.SessionID jobID, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } + testassert.NoError(t, err) callbackEntered := make(chan struct{}) callbackGate := make(chan struct{}) deliveryErr := make(chan error, 1) - if err := h.StartJob(context.Background(), sid, jobID, func(_ context.Context, completionID string) error { + testassert.NoError(t, h.StartJob(context.Background(), sid, jobID, func(_ context.Context, completionID string) error { return j.Start(StartRequest{ JobID: jobID, SessionID: sid, Workspace: t.TempDir(), Command: "sleep 30", Env: os.Environ(), @@ -70,9 +65,7 @@ func TestStopJobReaperBarrierAndHarnessStopDelivery(t *testing.T) { deliveryErr <- h.DeliverBackgroundCompletion(context.Background(), sid, completionID, "job completed") }, }) - }); err != nil { - t.Fatalf("StartJob: %v", err) - } + })) // The parent stops being receivable: the gated delivery commits the // durable signal entry on the closed path, like managed shutdown. @@ -132,13 +125,9 @@ func TestStopJobReaperBarrierAndHarnessStopDelivery(t *testing.T) { close(callbackGate) select { case err := <-stopDone: - if err != nil { - t.Fatalf("Stop: %v", err) - } + testassert.NoError(t, err) case <-time.After(15 * time.Second): t.Fatal("Stop did not converge after the delivery finished the member") } - if err := <-deliveryErr; err != nil { - t.Fatalf("DeliverBackgroundCompletion: %v", err) - } + testassert.NoError(t, <-deliveryErr) } diff --git a/plugins/jobs/jobs_test.go b/plugins/jobs/jobs_test.go index 91d6e481..520962ed 100644 --- a/plugins/jobs/jobs_test.go +++ b/plugins/jobs/jobs_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/runtime" ) @@ -29,31 +30,16 @@ var jobIDShape = regexp.MustCompile(`^[0-9a-f]{8}$`) func openJobs(t *testing.T, dataDir string) *instance { t.Helper() p := Plugin() - if p.ID != "jobs" || p.Scope != runtime.ScopeRuntime || p.ValidateConfig == nil || p.Open == nil || len(p.Requires) != 0 { - t.Fatalf("plugin declaration = %+v, want the Runtime-scoped jobs plugin with a validator and no dependencies", p) - } - if len(p.Provides) != 2 { - t.Fatalf("plugin declares %d exports, want the jobs and job-stopper views", len(p.Provides)) - } + testassert.Require(t, p.ID == "jobs" && p.Scope == runtime.ScopeRuntime && p.ValidateConfig != nil && p.Open != nil && len(p.Requires) == 0 && len(p.Provides) == 2, "plugin declaration = %+v, want the Runtime-scoped jobs plugin with a validator and no dependencies; got %v, want %v", p, len(p.Provides), 2) inst, err := p.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dataDir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("Open: %v", err) - } - if inst.Close == nil || len(inst.Values) != 2 { - t.Fatalf("instance = %d exports with a nil Close %v, want exactly the two views and a closer", len(inst.Values), inst.Close == nil) - } + testassert.NoError(t, err) + testassert.Require(t, inst.Close != nil && len(inst.Values) == 2, "instance = %d exports with a nil Close %v, want exactly the two views and a closer", len(inst.Values), inst.Close == nil) jobsView, ok := inst.Values["jobs"].(Jobs) - if !ok { - t.Fatalf("jobs export supplies %T, not a Jobs", inst.Values["jobs"]) - } + testassert.Require(t, ok, "jobs export supplies %T, not a Jobs", inst.Values["jobs"]) stopper, ok := inst.Values["job-stopper"].(interface{ StopJob(string, string) }) - if !ok { - t.Fatalf("job-stopper export supplies %T, not a job stopper", inst.Values["job-stopper"]) - } + testassert.Require(t, ok, "job-stopper export supplies %T, not a job stopper", inst.Values["job-stopper"]) core := jobsView.(*instance) - if stopper.(*instance) != core { - t.Fatal("the two declared views do not share one instance") - } + testassert.Require(t, stopper.(*instance) == core, "the two declared views do not share one instance") t.Cleanup(func() { _ = inst.Close() }) return core } @@ -82,8 +68,9 @@ func armTestReaper(t *testing.T, inst *instance) (release func()) { // start runs one reserved start, failing the test on error. func start(t *testing.T, j *instance, req StartRequest) string { t.Helper() - if err := j.Start(req); err != nil { - t.Fatalf("Start(%q): %v", req.Command, err) + { + err := j.Start(req) + testassert.Require(t, err == nil, "Start(%q): %v", req.Command, err) } return req.JobID } @@ -92,9 +79,7 @@ func start(t *testing.T, j *instance, req StartRequest) string { func reserveStart(t *testing.T, j *instance, req StartRequest) string { t.Helper() id, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } + testassert.NoError(t, err) req.JobID = id return start(t, j, req) } @@ -156,28 +141,24 @@ func TestStartReadKillListLiveRetainedTexts(t *testing.T) { SessionID: sid, Command: "sleep 30", Env: os.Environ(), OnExit: func(r ExitResult) { emptyExit <- r }, }) - if !jobIDShape.MatchString(emptyID) { - t.Fatalf("Reserve minted %q, want an 8-hex job ID", emptyID) - } + testassert.Require(t, jobIDShape.MatchString(emptyID), "Reserve minted %q, want an 8-hex job ID", emptyID) got, err := j.Read(sid, emptyID) - if err != nil || got != "(No output yet)" { - t.Fatalf("Read of a silent running job = (%q, %v), want (No output yet)", got, err) - } + testassert.Require(t, err == nil && got == "(No output yet)", "Read of a silent running job = (%q, %v), want (No output yet)", got, err) line := j.List(sid) wantLine := fmt.Sprintf("%s sleep 30 (running for ", emptyID) - if !strings.HasPrefix(line, wantLine) || !strings.HasSuffix(line, ")") || strings.Count(line, "\n") != 0 { - t.Fatalf("List = %q, want the single running line for %q without a trailing newline", line, emptyID) - } - if got := j.Live(sid); !slices.Equal(got, []string{emptyID}) { - t.Fatalf("Live = %v, want the running job", got) + testassert.Require(t, strings.HasPrefix(line, wantLine) && strings.HasSuffix(line, ")") && strings.Count(line, "\n") == 0, "List = %q, want the single running line for %q without a trailing newline", line, emptyID) + { + got := j.Live(sid) + testassert.Require(t, slices.Equal(got, []string{emptyID}), "Live = %v, want the running job", got) } // A second Start over an already-running record fails the retained // unknown-ID check and leaves the record in place. if err := j.Start(StartRequest{JobID: emptyID, SessionID: sid, Command: "printf hijack", Env: os.Environ()}); err == nil || err.Error() != unknownIDText(emptyID) { t.Fatalf("Start over a running record = %v, want %q", err, unknownIDText(emptyID)) } - if got := j.Live(sid); !slices.Equal(got, []string{emptyID}) { - t.Fatalf("Live after the rejected Start = %v, want the record untouched", got) + { + got := j.Live(sid) + testassert.Require(t, slices.Equal(got, []string{emptyID}), "Live after the rejected Start = %v, want the record untouched", got) } exit := make(chan ExitResult, 1) @@ -186,15 +167,7 @@ func TestStartReadKillListLiveRetainedTexts(t *testing.T) { OnExit: func(r ExitResult) { exit <- r }, }) result := waitExit(t, exit) - if result.ID != shortID || result.Command != "printf hello" || result.Reason != "completed" || result.ExitCode != 0 { - t.Fatalf("completed ExitResult = %+v, want the short job's identity, completed reason, and exit code 0", result) - } - if result.Output != "hello" { - t.Fatalf("completed Output = %q, want %q", result.Output, "hello") - } - if result.MaxOutputBytes != 15360 { - t.Fatalf("completed MaxOutputBytes = %d, want the default 15360", result.MaxOutputBytes) - } + testassert.Require(t, result.ID == shortID && result.Command == "printf hello" && result.Reason == "completed" && result.ExitCode == 0 && result.Output == "hello" && result.MaxOutputBytes == 15360, "completed ExitResult = %+v, want the short job's identity, completed reason, and exit code 0; got %v, want %v; got %v, want %v", result, result.Output, "hello", result.MaxOutputBytes, 15360) waitRemoved(t, j, shortID) if _, err := j.Read(sid, shortID); err == nil || err.Error() != unknownIDText(shortID) { t.Fatalf("Read after removal = %v, want %q", err, unknownIDText(shortID)) @@ -222,11 +195,10 @@ func TestStartReadKillListLiveRetainedTexts(t *testing.T) { } exitedLine := j.List(sid) wantExited := fmt.Sprintf("%s printf bye (running for ", gatedID) - if !strings.Contains(exitedLine, wantExited) || !strings.Contains(exitedLine, " (exited with code 0))") { - t.Fatalf("List = %q, want the exited line for %q with the exited suffix", exitedLine, gatedID) - } - if got, err := j.Read(sid, gatedID); err != nil || got != "bye" { - t.Fatalf("Read of the exited-but-retained record = (%q, %v), want the final output", got, err) + testassert.Require(t, strings.Contains(exitedLine, wantExited) && strings.Contains(exitedLine, " (exited with code 0))"), "List = %q, want the exited line for %q with the exited suffix", exitedLine, gatedID) + { + got, err := j.Read(sid, gatedID) + testassert.Require(t, err == nil && got == "bye", "Read of the exited-but-retained record = (%q, %v), want the final output", got, err) } close(gatedRelease) waitExit(t, gatedExit) @@ -234,22 +206,17 @@ func TestStartReadKillListLiveRetainedTexts(t *testing.T) { // Kill on the running silent job returns nil and delivers the killed // result; Kill on the absent short job reports the retained unknown ID. - if err := j.Kill(sid, emptyID); err != nil { - t.Fatalf("Kill of a running job = %v, want nil", err) - } + testassert.NoError(t, j.Kill(sid, emptyID)) killed := waitExit(t, emptyExit) - if killed.Reason != "killed" { - t.Fatalf("killed ExitResult = %+v, want the killed reason", killed) - } + testassert.Require(t, killed.Reason == "killed", "killed ExitResult = %+v, want the killed reason", killed) waitRemoved(t, j, emptyID) if err := j.Kill(sid, shortID); err == nil || err.Error() != unknownIDText(shortID) { t.Fatalf("Kill after removal = %v, want %q", err, unknownIDText(shortID)) } - if got := j.List(sid); got != "No background processes." { - t.Fatalf("List = %q, want the retained empty text", got) - } - if got := j.Live(sid); len(got) != 0 { - t.Fatalf("Live = %v, want no jobs", got) + testassert.Equal(t, j.List(sid), "No background processes.") + { + got := j.Live(sid) + testassert.Require(t, len(got) == 0, "Live = %v, want no jobs", got) } } @@ -275,9 +242,7 @@ func TestKillExitedShortCircuits(t *testing.T) { case <-time.After(15 * time.Second): t.Fatal("the gated exit callback did not run") } - if err := j.Kill(sid, id); err != nil { - t.Fatalf("Kill of an exited job = %v, want nil", err) - } + testassert.NoError(t, j.Kill(sid, id)) close(release) waitExit(t, exit) waitRemoved(t, j, id) @@ -290,9 +255,7 @@ func TestKillReservedIsUnknownIDWithoutRemoval(t *testing.T) { j := openJobs(t, t.TempDir()) const sid = "session-kill-reserved" id, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } + testassert.NoError(t, err) if err := j.Kill(sid, id); err == nil || err.Error() != unknownIDText(id) { t.Fatalf("Kill of a reserved job = %v, want %q", err, unknownIDText(id)) } @@ -319,12 +282,14 @@ func TestLiveReturnsSortedIDs(t *testing.T) { } want := slices.Clone(ids) slices.Sort(want) - if got := j.Live(sid); !slices.Equal(got, want) { - t.Fatalf("Live = %v, want the sorted IDs %v", got, want) + { + got := j.Live(sid) + testassert.Require(t, slices.Equal(got, want), "Live = %v, want the sorted IDs %v", got, want) } for i, id := range ids { - if err := j.Kill(sid, id); err != nil { - t.Fatalf("Kill(%q): %v", id, err) + { + err := j.Kill(sid, id) + testassert.Require(t, err == nil, "Kill(%q): %v", id, err) } waitExit(t, exits[i]) waitRemoved(t, j, id) @@ -357,28 +322,28 @@ func TestSessionAttributionHidesForeignJobs(t *testing.T) { if err := j.Kill(foreign, ownerID); err == nil || err.Error() != unknownIDText(ownerID) { t.Fatalf("foreign Kill = %v, want %q", err, unknownIDText(ownerID)) } - if got := j.Live(foreign); !slices.Equal(got, []string{foreignID}) { - t.Fatalf("foreign Live = %v, want only the foreign session's job", got) + { + got := j.Live(foreign) + testassert.Require(t, slices.Equal(got, []string{foreignID}), "foreign Live = %v, want only the foreign session's job", got) } - if list := j.List(foreign); strings.Contains(list, ownerID) || !strings.Contains(list, foreignID) { - t.Fatalf("foreign List = %q, want only the foreign session's line", list) + { + list := j.List(foreign) + testassert.Require(t, !strings.Contains(list, ownerID) && strings.Contains(list, foreignID), "foreign List = %q, want only the foreign session's line", list) } - if list := j.List(owner); !strings.Contains(list, ownerID) || strings.Contains(list, foreignID) { - t.Fatalf("owner List = %q, want only the owner session's line", list) + { + list := j.List(owner) + testassert.Require(t, strings.Contains(list, ownerID) && !strings.Contains(list, foreignID), "owner List = %q, want only the owner session's line", list) } // The foreign Kill left the owner's job running. - if got := j.Live(owner); !slices.Equal(got, []string{ownerID}) { - t.Fatalf("owner Live after a foreign Kill = %v, want the job untouched", got) + { + got := j.Live(owner) + testassert.Require(t, slices.Equal(got, []string{ownerID}), "owner Live after a foreign Kill = %v, want the job untouched", got) } - if err := j.Kill(owner, ownerID); err != nil { - t.Fatalf("Kill(owner): %v", err) - } + testassert.NoError(t, j.Kill(owner, ownerID)) waitExit(t, ownerExit) waitRemoved(t, j, ownerID) - if err := j.Kill(foreign, foreignID); err != nil { - t.Fatalf("Kill(foreign): %v", err) - } + testassert.NoError(t, j.Kill(foreign, foreignID)) waitExit(t, foreignExit) waitRemoved(t, j, foreignID) } @@ -401,17 +366,13 @@ func TestStartRejectedAtSessionLimit(t *testing.T) { blockedExit := make(chan ExitResult, 1) blockedID, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } + testassert.NoError(t, err) err = j.Start(StartRequest{ JobID: blockedID, SessionID: first, Command: "sleep 30", Env: os.Environ(), Config: cfg, OnExit: func(r ExitResult) { blockedExit <- r }, }) wantErr := "process: background process limit reached (1/1). Kill existing processes or wait for them to exit" - if err == nil || err.Error() != wantErr { - t.Fatalf("Start at the cap = %v, want %q", err, wantErr) - } + testassert.Require(t, err != nil && err.Error() == wantErr, "Start at the cap = %v, want %q", err, wantErr) // The limit failure consumed the reservation: the same identity fails the // retained unknown-ID error. err = j.Start(StartRequest{ @@ -429,9 +390,7 @@ func TestStartRejectedAtSessionLimit(t *testing.T) { }) // Once the first session's job exits it no longer counts against the cap. - if err := j.Kill(first, firstID); err != nil { - t.Fatalf("Kill: %v", err) - } + testassert.NoError(t, j.Kill(first, firstID)) waitExit(t, firstExit) retryExit := make(chan ExitResult, 1) retryID := reserveStart(t, j, StartRequest{ @@ -440,8 +399,9 @@ func TestStartRejectedAtSessionLimit(t *testing.T) { }) for sid, id := range map[string]string{first: retryID, second: otherID} { - if err := j.Kill(sid, id); err != nil { - t.Fatalf("Kill(%q): %v", id, err) + { + err := j.Kill(sid, id) + testassert.Require(t, err == nil, "Kill(%q): %v", id, err) } } waitExit(t, retryExit) @@ -472,8 +432,9 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { `{"unknown_member":{"deep":[1,2]}}`, `{"unknown_member":"many","max_background_processes":5}`, } { - if err := validate(raw); err != nil { - t.Errorf("ValidateConfig(%s) = %v, want accepted", raw, err) + { + err := validate(raw) + testassert.Check(t, err == nil, "ValidateConfig(%s) = %v, want accepted", raw, err) } } for _, row := range []struct{ raw, want string }{ @@ -484,8 +445,9 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { {`{"read_line_max_chars":99}`, "jobs: read_line_max_chars must be between 100 and 100000"}, {`{"read_line_max_chars":100001}`, "jobs: read_line_max_chars must be between 100 and 100000"}, } { - if err := validate(row.raw); err == nil || err.Error() != row.want { - t.Errorf("ValidateConfig(%s) = %v, want %q", row.raw, err, row.want) + { + err := validate(row.raw) + testassert.Check(t, err != nil && err.Error() == row.want, "ValidateConfig(%s) = %v, want %q", row.raw, err, row.want) } } for _, raw := range []string{ @@ -496,9 +458,7 @@ func TestValidateConfigSettingsRangesAndShapes(t *testing.T) { `[]`, `"jobs"`, `5`, } { err := validate(raw) - if err == nil || !strings.HasPrefix(err.Error(), "jobs: invalid settings: ") { - t.Errorf("ValidateConfig(%s) = %v, want the jobs: invalid settings wrap", raw, err) - } + testassert.Check(t, err != nil && strings.HasPrefix(err.Error(), "jobs: invalid settings: "), "ValidateConfig(%s) = %v, want the jobs: invalid settings wrap", raw, err) } } @@ -512,16 +472,12 @@ func TestStartInvalidConfigConsumesReservation(t *testing.T) { {`{not json`, "jobs: invalid settings: "}, } { id, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } + testassert.NoError(t, err) err = j.Start(StartRequest{ JobID: id, SessionID: "session-bad-config", Command: "printf x", Env: os.Environ(), Config: []byte(row.config), }) - if err == nil || !strings.HasPrefix(err.Error(), row.want) { - t.Fatalf("Start with Config %s = %v, want prefix %q", row.config, err, row.want) - } + testassert.Require(t, err != nil && strings.HasPrefix(err.Error(), row.want), "Start with Config %s = %v, want prefix %q", row.config, err, row.want) err = j.Start(StartRequest{ JobID: id, SessionID: "session-bad-config", Command: "printf x", Env: os.Environ(), Config: []byte(row.config), @@ -539,12 +495,8 @@ func TestReserveAbortThenStartFailsUnknownID(t *testing.T) { j := openJobs(t, t.TempDir()) const sid = "session-abort" id, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } - if !jobIDShape.MatchString(id) { - t.Fatalf("Reserve minted %q, want an 8-hex job ID", id) - } + testassert.NoError(t, err) + testassert.Require(t, jobIDShape.MatchString(id), "Reserve minted %q, want an 8-hex job ID", id) j.Abort(id) j.Abort(id) // idempotent j.Abort("00000000") // absent stays a no-op @@ -559,12 +511,11 @@ func TestReserveAbortThenStartFailsUnknownID(t *testing.T) { OnExit: func(r ExitResult) { exit <- r }, }) j.Abort(runningID) - if got := j.Live(sid); !slices.Equal(got, []string{runningID}) { - t.Fatalf("Live after Abort over a running record = %v, want the record untouched", got) - } - if err := j.Kill(sid, runningID); err != nil { - t.Fatalf("Kill: %v", err) + { + got := j.Live(sid) + testassert.Require(t, slices.Equal(got, []string{runningID}), "Live after Abort over a running record = %v, want the record untouched", got) } + testassert.NoError(t, j.Kill(sid, runningID)) waitExit(t, exit) waitRemoved(t, j, runningID) } @@ -582,12 +533,11 @@ func TestStopJobAcrossStates(t *testing.T) { // Reserved: removed; the pending Start fails the same unknown-ID error. reservedID, err := j.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } + testassert.NoError(t, err) j.StopJob(sid, reservedID) - if _, ok := stateOf(j, reservedID); ok { - t.Fatal("StopJob left the reservation in place") + { + _, ok := stateOf(j, reservedID) + testassert.Require(t, !ok, "StopJob left the reservation in place") } err = j.Start(StartRequest{JobID: reservedID, SessionID: sid, Command: "printf x", Env: os.Environ()}) if err == nil || err.Error() != unknownIDText(reservedID) { @@ -602,8 +552,9 @@ func TestStopJobAcrossStates(t *testing.T) { OnExit: func(r ExitResult) { runningExit <- r }, }) j.StopJob(sid, runningID) - if got := waitExit(t, runningExit); got.Reason != "killed" { - t.Fatalf("StopJob exit result = %+v, want the killed reason", got) + { + got := waitExit(t, runningExit) + testassert.Require(t, got.Reason == "killed", "StopJob exit result = %+v, want the killed reason", got) } waitRemoved(t, j, runningID) @@ -626,8 +577,9 @@ func TestStopJobAcrossStates(t *testing.T) { } j.StopJob(sid, exitedID) close(release) - if got := waitExit(t, exitedExit); got.Reason != "completed" { - t.Fatalf("StopJob over an exited record changed the result to %+v, want completed", got) + { + got := waitExit(t, exitedExit) + testassert.Require(t, got.Reason == "completed", "StopJob over an exited record changed the result to %+v, want completed", got) } waitRemoved(t, j, exitedID) @@ -641,8 +593,9 @@ func TestStopJobAcrossStates(t *testing.T) { OnExit: func(r ExitResult) { ownerExit <- r }, }) j.StopJob(foreign, ownerID) - if got := j.Live(sid); !slices.Equal(got, []string{ownerID}) { - t.Fatalf("Live after a foreign StopJob = %v, want the job untouched", got) + { + got := j.Live(sid) + testassert.Require(t, slices.Equal(got, []string{ownerID}), "Live after a foreign StopJob = %v, want the job untouched", got) } j.StopJob(sid, ownerID) waitExit(t, ownerExit) @@ -664,12 +617,11 @@ func TestTimeoutEscalatesToKill(t *testing.T) { }) started := time.Now() result := waitExit(t, exit) - if elapsed := time.Since(started); elapsed > 10*time.Second { - t.Fatalf("the timed job ended after %v, want the timeout escalation to converge promptly", elapsed) - } - if result.ID != id || result.Reason != "timeout" { - t.Fatalf("timeout ExitResult = %+v, want the timeout reason", result) + { + elapsed := time.Since(started) + testassert.Require(t, elapsed <= 10*time.Second, "the timed job ended after %v, want the timeout escalation to converge promptly", elapsed) } + testassert.Require(t, result.ID == id && result.Reason == "timeout", "timeout ExitResult = %+v, want the timeout reason", result) waitRemoved(t, j, id) } @@ -705,25 +657,14 @@ func TestCaptureBoundsSpillUnderSessionOutputDir(t *testing.T) { // same bounded format the callback receives. wantDir := fmt.Sprintf("saved to: %s", filepath.Join(dataDir, "code", sid, "output")) got, err := j.Read(sid, id) - if err != nil { - t.Fatalf("Read: %v", err) - } - if !strings.Contains(got, "[Output truncated. Full output (") || !strings.Contains(got, wantDir) { - t.Fatalf("Read = %q, want the truncation marker and a spill under %s", got, wantDir) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(got, "[Output truncated. Full output (") && strings.Contains(got, wantDir), "Read = %q, want the truncation marker and a spill under %s", got, wantDir) close(release) result := waitExit(t, exit) - if result.MaxOutputBytes != 1024 { - t.Fatalf("MaxOutputBytes = %d, want the per-call 1024", result.MaxOutputBytes) - } - if !strings.Contains(result.Output, "[Output truncated. Full output (") || !strings.Contains(result.Output, wantDir) { - t.Fatalf("Output = %q, want the truncation marker and a spill under %s", result.Output, wantDir) - } + testassert.Require(t, result.MaxOutputBytes == 1024 && strings.Contains(result.Output, "[Output truncated. Full output (") && strings.Contains(result.Output, wantDir), "got %v, want %v; Output = %q, want the truncation marker and a spill under %s", result.MaxOutputBytes, 1024, result.Output, wantDir) waitRemoved(t, j, id) entries, err := os.ReadDir(filepath.Join(dataDir, "code", sid, "output")) - if err != nil { - t.Fatalf("ReadDir of the session output directory: %v", err) - } + testassert.NoError(t, err) found := false for _, e := range entries { if strings.HasPrefix(e.Name(), "proc_output_") && strings.HasSuffix(e.Name(), ".txt") { @@ -797,9 +738,7 @@ func TestCloseKillsStraysAndGatesCallbacks(t *testing.T) { } time.Sleep(5 * time.Millisecond) } - if !sawSpill { - t.Fatalf("no private spill appeared under %s before Close", outputDir) - } + testassert.Require(t, sawSpill, "no private spill appeared under %s before Close", outputDir) closeDone := make(chan error, 1) go func() { closeDone <- j.Close() }() @@ -812,9 +751,7 @@ func TestCloseKillsStraysAndGatesCallbacks(t *testing.T) { waitExit(t, aExit) select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(15 * time.Second): t.Fatal("Close did not converge after the admitted callback was released") } @@ -833,27 +770,26 @@ func TestCloseKillsStraysAndGatesCallbacks(t *testing.T) { remaining = append(remaining, id) } j.mu.Unlock() - if len(remaining) != 0 { - t.Fatalf("records remaining after Close = %v, want none", remaining) - } + testassert.Require(t, len(remaining) == 0, "records remaining after Close = %v, want none", remaining) if entries, err := os.ReadDir(outputDir); err != nil { t.Fatalf("ReadDir after Close: %v", err) } else if len(entries) != 0 { t.Fatalf("capture files remaining after Close = %v, want none", names(entries)) } - if _, ok := stateOf(j, aID); ok { - t.Fatalf("record %q survived Close", aID) + { + _, ok := stateOf(j, aID) + testassert.Require(t, !ok, "record %q survived Close", aID) } - if _, ok := stateOf(j, bID); ok { - t.Fatalf("record %q survived Close", bID) + { + _, ok := stateOf(j, bID) + testassert.Require(t, !ok, "record %q survived Close", bID) } - if _, err := j.Reserve(); err == nil || err.Error() != "process: manager is closed" { - t.Fatalf("Reserve after Close = %v, want the retained closed error", err) + { + _, err := j.Reserve() + testassert.Require(t, err != nil && err.Error() == "process: manager is closed", "Reserve after Close = %v, want the retained closed error", err) } err := j.Start(StartRequest{JobID: "deadbeef", SessionID: sid, Command: "printf x", Env: os.Environ()}) - if err == nil || err.Error() != "process: manager is closed" { - t.Fatalf("Start after Close = %v, want the retained closed error", err) - } + testassert.Require(t, err != nil && err.Error() == "process: manager is closed", "Start after Close = %v, want the retained closed error", err) } // TestKillRetainsFiveSecondDiagnoseAndAbandon proves the user-facing Kill @@ -868,9 +804,7 @@ func TestKillRetainsFiveSecondDiagnoseAndAbandon(t *testing.T) { }) pipeR, pipeW, err := os.Pipe() - if err != nil { - t.Fatalf("Pipe: %v", err) - } + testassert.NoError(t, err) old := os.Stderr os.Stderr = pipeW started := time.Now() @@ -881,18 +815,9 @@ func TestKillRetainsFiveSecondDiagnoseAndAbandon(t *testing.T) { diagnosis, _ := io.ReadAll(pipeR) _ = pipeR.Close() - if killErr != nil { - t.Fatalf("Kill = %v, want nil from the abandon outcome", killErr) - } - if elapsed < 5*time.Second { - t.Fatalf("Kill returned after %v, want the retained 5-second diagnose-and-abandon bound", elapsed) - } - if elapsed > 15*time.Second { - t.Fatalf("Kill returned after %v, want no unbounded wait", elapsed) - } + testassert.Require(t, killErr == nil, "Kill = %v, want nil from the abandon outcome", killErr) + testassert.Require(t, elapsed >= 5*time.Second && elapsed <= 15*time.Second, "Kill returned after %v, want the retained 5-second diagnose-and-abandon bound; Kill returned after %v, want no unbounded wait", elapsed, elapsed) want := fmt.Sprintf("lightcode: process %s not reaped within 5s after SIGKILL\n", id) - if !strings.Contains(string(diagnosis), want) { - t.Fatalf("Kill stderr = %q, want %q", diagnosis, want) - } + testassert.Require(t, strings.Contains(string(diagnosis), want), "Kill stderr = %q, want %q", diagnosis, want) release() // the exit goroutine reaps during the registered cleanup } diff --git a/process_test.go b/process_test.go index 9f32c1dc..2a0cdfb1 100644 --- a/process_test.go +++ b/process_test.go @@ -20,6 +20,7 @@ import ( "time" "github.com/MMinasyan/lightcode/client" + "github.com/MMinasyan/lightcode/internal/testassert" ) // rootExecSelector is the private test environment marker: when inherited, @@ -77,20 +78,14 @@ func newRootSandbox(t *testing.T) *rootSandbox { home := t.TempDir() workspace := t.TempDir() dataDir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dataDir, 0o700); err != nil { - t.Fatalf("prepare data root: %v", err) - } + testassert.NoError(t, os.MkdirAll(dataDir, 0o700)) configPath := filepath.Join(home, "config.json") - if err := os.WriteFile(configPath, []byte(rootFixtureConfig), 0o600); err != nil { - t.Fatalf("prepare config: %v", err) - } + testassert.NoError(t, os.WriteFile(configPath, []byte(rootFixtureConfig), 0o600)) t.Setenv("HOME", home) t.Setenv("LIGHTCODE_CONFIG", configPath) isolateBundledCredentials(t) exe, err := os.Executable() - if err != nil { - t.Fatalf("resolve executable: %v", err) - } + testassert.NoError(t, err) s := &rootSandbox{t: t, exe: exe, home: home, dataDir: dataDir, configPath: configPath, workspace: workspace} s.seedOldState() return s @@ -104,9 +99,7 @@ func (s *rootSandbox) seedOldState() { hash := hex.EncodeToString(sum[:]) sessionDir := filepath.Join(s.dataDir, "projects", "p-"+hash, "sessions", oldSessionID) snapEntry := filepath.Join(sessionDir, "snapshots", "1", "0123456789abcdef") - if err := os.MkdirAll(snapEntry, 0o700); err != nil { - s.t.Fatal(err) - } + testassert.NoError(s.t, os.MkdirAll(snapEntry, 0o700)) meta := fmt.Sprintf(`{"id":%q,"created_at":"2026-01-01T00:00:00Z","project_path":%q,"project_hash":%q,"lightcode_version":"v0.0.0","state":"active","archived_at":0,"last_activity":0}`, oldSessionID, filepath.Clean(s.workspace), hash[:16]) files := map[string]string{ filepath.Join(sessionDir, "meta.json"): meta, @@ -115,8 +108,9 @@ func (s *rootSandbox) seedOldState() { filepath.Join(snapEntry, "sentinel.txt"): "old snapshot sentinel\n", } for path, body := range files { - if err := os.WriteFile(path, []byte(body), 0o600); err != nil { - s.t.Fatalf("seed old state %s: %v", path, err) + { + err := os.WriteFile(path, []byte(body), 0o600) + testassert.Require(s.t, err == nil, "seed old state %s: %v", path, err) } } } @@ -194,14 +188,13 @@ func (s *rootSandbox) start(args ...string) *rootChild { cmd.Dir = s.workspace cmd.Env = s.childEnv() stdin, err := cmd.StdinPipe() - if err != nil { - s.t.Fatalf("%v: StdinPipe: %v", args, err) - } + testassert.Require(s.t, err == nil, "%v: StdinPipe: %v", args, err) out := &lockedBuffer{} cmd.Stdout = out cmd.Stderr = out - if err := cmd.Start(); err != nil { - s.t.Fatalf("%v: start: %v", args, err) + { + err := cmd.Start() + testassert.Require(s.t, err == nil, "%v: start: %v", args, err) } c := &rootChild{t: s.t, args: args, cmd: cmd, stdin: stdin, out: out, done: make(chan struct{})} go func() { @@ -215,8 +208,9 @@ func (s *rootSandbox) start(args ...string) *rootChild { // signal delivers one shutdown signal to the known child process. func (c *rootChild) signal(sig os.Signal) { c.t.Helper() - if err := c.cmd.Process.Signal(sig); err != nil { - c.t.Fatalf("%v: signal: %v", c.args, err) + { + err := c.cmd.Process.Signal(sig) + testassert.Require(c.t, err == nil, "%v: signal: %v", c.args, err) } } @@ -261,9 +255,7 @@ func (s *rootSandbox) awaitReady() *client.Client { if err == nil { return conn } - if time.Now().After(deadline) { - s.t.Fatalf("owner never became ready: %v", err) - } + testassert.Require(s.t, !time.Now().After(deadline), "owner never became ready: %v", err) time.Sleep(100 * time.Millisecond) } } @@ -272,8 +264,9 @@ func (s *rootSandbox) awaitReady() *client.Client { func (s *rootSandbox) withdrawn() { s.t.Helper() recordPath := filepath.Join(s.dataDir, "runtime.json") - if _, err := os.Lstat(recordPath); !os.IsNotExist(err) { - s.t.Fatalf("discovery record still present after shutdown: %v", err) + { + _, err := os.Lstat(recordPath) + testassert.Require(s.t, os.IsNotExist(err), "discovery record still present after shutdown: %v", err) } } @@ -285,19 +278,22 @@ func (s *rootSandbox) cliAsk(command, want string) string { child := s.start("cli") deadline := time.Now().Add(45 * time.Second) for { - if _, err := io.WriteString(child.stdin, command+"\n"); err != nil { - child.t.Fatalf("cli stdin: %v", err) + { + _, err := io.WriteString(child.stdin, command+"\n") + testassert.Require(child.t, err == nil, "cli stdin: %v", err) } for { out := child.out.String() switch { case strings.Contains(out, want): - if _, err := io.WriteString(child.stdin, "/exit\n"); err != nil { - child.t.Fatalf("cli stdin: %v", err) + { + _, err := io.WriteString(child.stdin, "/exit\n") + testassert.Require(child.t, err == nil, "cli stdin: %v", err) } _ = child.stdin.Close() - if code := child.awaitExit(); code != 0 { - child.t.Fatalf("cli exit = %d\noutput=%s", code, out) + { + code := child.awaitExit() + testassert.Require(child.t, code == 0, "cli exit = %d\noutput=%s", code, out) } return child.out.String() case strings.Contains(out, "Runtime is not connected"): @@ -330,8 +326,9 @@ func (s *rootSandbox) acpExchange() (healthInstance, sessionID string) { s.t.Helper() child := s.start("acp") send := func(body string) { - if _, err := io.WriteString(child.stdin, body+"\n"); err != nil { - child.t.Fatalf("acp stdin: %v", err) + { + _, err := io.WriteString(child.stdin, body+"\n") + testassert.Require(child.t, err == nil, "acp stdin: %v", err) } } read := func(id string, into any) { @@ -356,17 +353,14 @@ func (s *rootSandbox) acpExchange() (healthInstance, sessionID string) { if resp.Error != nil { child.t.Fatalf("acp id %s error %d %s", id, resp.Error.Code, resp.Error.Message) } - if err := json.Unmarshal(resp.Result, into); err != nil { - child.t.Fatalf("acp id %s result: %v (%s)", id, err, resp.Result) + { + err := json.Unmarshal(resp.Result, into) + testassert.Require(child.t, err == nil, "acp id %s result: %v (%s)", id, err, resp.Result) } return } - if time.Now().After(deadline) { - child.t.Fatalf("acp id %s never answered\noutput=%s", id, out) - } - if childExited(child) { - child.t.Fatalf("acp exited before answering id %s\noutput=%s", id, out) - } + testassert.Require(child.t, !time.Now().After(deadline), "acp id %s never answered\noutput=%s", id, out) + testassert.Require(child.t, !childExited(child), "acp exited before answering id %s\noutput=%s", id, out) time.Sleep(50 * time.Millisecond) } } @@ -407,118 +401,84 @@ func TestRootRuntimeComposition(t *testing.T) { owner := s.start(s.runtimeArgs(s.configPath)...) first := s.awaitReady() instance := first.InstanceID() - if instance == "" { - t.Fatal("owner reported an empty instance") - } + testassert.Require(t, instance != "", "owner reported an empty instance") t.Run("contended startup exits 0 without replacing the record", func(t *testing.T) { _, stderr, code := s.runOne(s.runtimeArgs(s.configPath)...) - if code != 0 { - t.Fatalf("contender exit = %d, want 0\nstderr=%s", code, stderr) - } + testassert.Require(t, code == 0, "contender exit = %d, want 0\nstderr=%s", code, stderr) again := s.awaitReady() - if got := again.InstanceID(); got != instance { - t.Fatalf("record instance = %q, want the first owner %q", got, instance) - } + testassert.Equal(t, again.InstanceID(), instance) }) t.Run("models prints the configured provider projection", func(t *testing.T) { stdout, stderr, code := s.runOne("models", "--json", "--all") - if code != 0 { - t.Fatalf("exit = %d\nstderr=%s", code, stderr) - } + testassert.Require(t, code == 0, "exit = %d\nstderr=%s", code, stderr) var rows []map[string]any - if err := json.Unmarshal([]byte(stdout), &rows); err != nil { - t.Fatalf("stdout not JSON: %v\n%q", err, stdout) + { + err := json.Unmarshal([]byte(stdout), &rows) + testassert.Require(t, err == nil, "stdout not JSON: %v\n%q", err, stdout) } want := []map[string]any{ {"ref": "fixtureprov/hid", "provider": "fixtureprov", "providerName": "fixtureprov", "model": "hid", "displayName": "Hidden", "contextWindow": float64(16384), "maxOutputTokens": float64(512), "hidden": true, "providerHidden": false, "incomplete": false, "default": false, "source": "user"}, {"ref": "fixtureprov/inc", "provider": "fixtureprov", "providerName": "fixtureprov", "model": "inc", "displayName": "Incomplete", "contextWindow": float64(0), "maxOutputTokens": float64(0), "hidden": false, "providerHidden": false, "incomplete": true, "default": false, "source": "user"}, {"ref": "fixtureprov/vis", "provider": "fixtureprov", "providerName": "fixtureprov", "model": "vis", "displayName": "Visible", "contextWindow": float64(8192), "maxOutputTokens": float64(1024), "hidden": false, "providerHidden": false, "incomplete": false, "default": false, "source": "user"}, } - if len(rows) != len(want) { - t.Fatalf("rows = %v", rows) - } + testassert.Require(t, len(rows) == len(want), "rows = %v", rows) for i := range want { for field, value := range want[i] { - if rows[i][field] != value { - t.Fatalf("rows[%d][%q] = %v, want %v", i, field, rows[i][field], value) - } + testassert.Require(t, rows[i][field] == value, "rows[%d][%q] = %v, want %v", i, field, rows[i][field], value) } - if _, present := rows[i]["cost"]; present { - t.Fatalf("rows[%d] carries an unset cost: %v", i, rows[i]) + { + _, present := rows[i]["cost"] + testassert.Require(t, !present, "rows[%d] carries an unset cost: %v", i, rows[i]) } } }) t.Run("models keeps ordinary trailing flags and hides incomplete rows by default", func(t *testing.T) { stdout, stderr, code := s.runOne("models", "fixtureprov", "--all") - if code != 0 { - t.Fatalf("exit = %d\nstderr=%s", code, stderr) - } + testassert.Require(t, code == 0, "exit = %d\nstderr=%s", code, stderr) want := "fixtureprov/hid\t16384\t512\thidden\n" + "fixtureprov/inc\t0\t0\tincomplete\n" + "fixtureprov/vis\t8192\t1024\t-\n" - if stdout != want { - t.Fatalf("stdout = %q, want %q", stdout, want) - } + testassert.Equal(t, stdout, want) stdout, stderr, code = s.runOne("models", "--json") - if code != 0 { - t.Fatalf("exit = %d\nstderr=%s", code, stderr) - } + testassert.Require(t, code == 0, "exit = %d\nstderr=%s", code, stderr) var rows []map[string]any - if err := json.Unmarshal([]byte(stdout), &rows); err != nil { - t.Fatalf("stdout not JSON: %v\n%q", err, stdout) - } - if len(rows) != 1 || rows[0]["ref"] != "fixtureprov/vis" { - t.Fatalf("default rows = %v, want only the visible complete model", rows) + { + err := json.Unmarshal([]byte(stdout), &rows) + testassert.Require(t, err == nil, "stdout not JSON: %v\n%q", err, stdout) } + testassert.Require(t, len(rows) == 1 && rows[0]["ref"] == "fixtureprov/vis", "default rows = %v, want only the visible complete model", rows) }) t.Run("terminated flag-like operands stay provider operands", func(t *testing.T) { stdout, stderr, code := s.runOne("models", "--", "--json=true") - if code != 2 { - t.Fatalf("exit = %d, want 2\nstdout=%s\nstderr=%s", code, stdout, stderr) - } - if !strings.Contains(stderr, `unknown provider "--json=true"`) { - t.Fatalf("stderr = %q, want the operand reported as an unknown provider", stderr) - } + testassert.Require(t, code == 2, "exit = %d, want 2\nstdout=%s\nstderr=%s", code, stdout, stderr) + testassert.Require(t, strings.Contains(stderr, `unknown provider "--json=true"`), "stderr = %q, want the operand reported as an unknown provider", stderr) }) t.Run("cli exposes the same instance through the typed dispatcher", func(t *testing.T) { out := s.cliAsk("/runtime GetHealth {}", instance) - if !strings.Contains(out, instance) { - t.Fatalf("cli output = %q, want instance %q", out, instance) - } + testassert.Require(t, strings.Contains(out, instance), "cli output = %q, want instance %q", out, instance) }) var session string t.Run("acp initialize reports the generated Runtime health", func(t *testing.T) { healthInstance, sessionID := s.acpExchange() - if healthInstance != instance { - t.Fatalf("acp health instance = %q, want %q", healthInstance, instance) - } - if sessionID == "" { - t.Fatal("acp composed no session") - } + testassert.Equal(t, healthInstance, instance) + testassert.Require(t, sessionID != "", "acp composed no session") session = sessionID }) t.Run("target session reads never enumerate the old conversation", func(t *testing.T) { out := s.cliAsk(`/runtime ListSessions {"query":{"workspace":`+jsonString(s.workspace)+`,"lifecycle":"open"}}`, session) - if !strings.Contains(out, session) { - t.Fatalf("session list = %q, want the committed session %q", out, session) - } - if strings.Contains(out, oldSessionID) { - t.Fatalf("session list = %q, want no old filesystem conversation %q", out, oldSessionID) - } + testassert.Require(t, strings.Contains(out, session) && !strings.Contains(out, oldSessionID), "session list = %q, want the committed session %q; session list = %q, want no old filesystem conversation %q", out, session, out, oldSessionID) }) t.Run("client exit leaves the owner reachable", func(t *testing.T) { again := s.awaitReady() - if got := again.InstanceID(); got != instance { - t.Fatalf("owner instance after client exits = %q, want %q", got, instance) - } + testassert.Equal(t, again.InstanceID(), instance) }) t.Run("SIGINT joins managed shutdown and a fresh owner reads the same session", func(t *testing.T) { @@ -530,13 +490,12 @@ func TestRootRuntimeComposition(t *testing.T) { replacement := s.start(s.runtimeArgs(s.configPath)...) fresh := s.awaitReady() - if got := fresh.InstanceID(); got == instance { - t.Fatalf("replacement instance = %q, want a fresh instance", got) + { + got := fresh.InstanceID() + testassert.Require(t, got != instance, "replacement instance = %q, want a fresh instance", got) } out := s.cliAsk(`/runtime ListSessions {"query":{"workspace":`+jsonString(s.workspace)+`,"lifecycle":"open"}}`, session) - if !strings.Contains(out, session) { - t.Fatalf("replacement session list = %q, want the committed session %q", out, session) - } + testassert.Require(t, strings.Contains(out, session), "replacement session list = %q, want the committed session %q", out, session) replacement.signal(syscall.SIGTERM) if code := replacement.awaitExit(); code != 0 { t.Fatalf("SIGTERM exit = %d, want 0\noutput=%s", code, replacement.out.String()) @@ -544,8 +503,9 @@ func TestRootRuntimeComposition(t *testing.T) { s.withdrawn() }) - if after := snapshotHome(t, oldState); !maps.Equal(before, after) { - t.Fatalf("old filesystem conversation moved: before=%v after=%v", before, after) + { + after := snapshotHome(t, oldState) + testassert.Require(t, maps.Equal(before, after), "old filesystem conversation moved: before=%v after=%v", before, after) } } @@ -556,8 +516,9 @@ func TestRootRuntimeComposition(t *testing.T) { func TestRootACPSignalExit(t *testing.T) { s := newRootSandbox(t) child := s.start("acp") - if _, err := io.WriteString(child.stdin, "not-json\n"); err != nil { - t.Fatalf("acp stdin: %v", err) + { + _, err := io.WriteString(child.stdin, "not-json\n") + testassert.NoError(t, err) } deadline := time.Now().Add(30 * time.Second) for !strings.Contains(child.out.String(), "parse error") { @@ -582,38 +543,25 @@ func TestRootRuntimeStartupFailures(t *testing.T) { t.Run("invalid config exits 1", func(t *testing.T) { s := newRootSandbox(t) badPath := filepath.Join(s.home, "bad-config.json") - if err := os.WriteFile(badPath, []byte(`{"providers": [`), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(badPath, []byte(`{"providers": [`), 0o600)) stdout, stderr, code := s.runOne(s.runtimeArgs(badPath)...) - if code != 1 { - t.Fatalf("exit = %d, want 1\nstdout=%s\nstderr=%s", code, stdout, stderr) - } - if !strings.HasPrefix(stderr, "lightcode: ") { - t.Fatalf("stderr = %q, want the dispatcher diagnostic", stderr) - } + testassert.Require(t, code == 1, "exit = %d, want 1\nstdout=%s\nstderr=%s", code, stdout, stderr) + testassert.Require(t, strings.HasPrefix(stderr, "lightcode: "), "stderr = %q, want the dispatcher diagnostic", stderr) s.withdrawn() }) t.Run("non-file discovery destination exits 1", func(t *testing.T) { s := newRootSandbox(t) recordPath := filepath.Join(s.dataDir, "runtime.json") - if err := os.MkdirAll(recordPath, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(recordPath, 0o700)) sentinel := filepath.Join(recordPath, "keep.txt") - if err := os.WriteFile(sentinel, []byte("sentinel"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(sentinel, []byte("sentinel"), 0o600)) stdout, stderr, code := s.runOne(s.runtimeArgs(s.configPath)...) - if code != 1 { - t.Fatalf("exit = %d, want 1\nstdout=%s\nstderr=%s", code, stdout, stderr) - } - if !strings.HasPrefix(stderr, "lightcode: ") { - t.Fatalf("stderr = %q, want the dispatcher diagnostic", stderr) - } - if _, err := os.Stat(sentinel); err != nil { - t.Fatalf("destination sentinel lost: %v", err) + testassert.Require(t, code == 1, "exit = %d, want 1\nstdout=%s\nstderr=%s", code, stdout, stderr) + testassert.Require(t, strings.HasPrefix(stderr, "lightcode: "), "stderr = %q, want the dispatcher diagnostic", stderr) + { + _, err := os.Stat(sentinel) + testassert.NoError(t, err) } }) } @@ -627,16 +575,10 @@ func TestRootRuntimeCleanupFailure(t *testing.T) { s.awaitReady() recordPath := filepath.Join(s.dataDir, "runtime.json") - if err := os.Remove(recordPath); err != nil { - t.Fatalf("remove record: %v", err) - } - if err := os.MkdirAll(recordPath, 0o700); err != nil { - t.Fatalf("replace record with directory: %v", err) - } + testassert.NoError(t, os.Remove(recordPath)) + testassert.NoError(t, os.MkdirAll(recordPath, 0o700)) sentinel := filepath.Join(recordPath, "keep.txt") - if err := os.WriteFile(sentinel, []byte("sentinel"), 0o600); err != nil { - t.Fatalf("write sentinel: %v", err) - } + testassert.NoError(t, os.WriteFile(sentinel, []byte("sentinel"), 0o600)) owner.signal(os.Interrupt) if code := owner.awaitExit(); code != 1 { @@ -645,8 +587,9 @@ func TestRootRuntimeCleanupFailure(t *testing.T) { if !strings.Contains(owner.out.String(), "lightcode: ") { t.Fatalf("output = %q, want the dispatcher diagnostic", owner.out.String()) } - if _, err := os.Stat(sentinel); err != nil { - t.Fatalf("blocking sentinel lost: %v", err) + { + _, err := os.Stat(sentinel) + testassert.NoError(t, err) } } @@ -666,9 +609,7 @@ func isolateBundledCredentials(t *testing.T) { t.Helper() catalogDir := filepath.Join("internal", "catalog", "builtin") entries, err := os.ReadDir(catalogDir) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue diff --git a/protocol/contracts_test.go b/protocol/contracts_test.go index 7e6a9ef5..ffb715a6 100644 --- a/protocol/contracts_test.go +++ b/protocol/contracts_test.go @@ -12,6 +12,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -32,32 +33,30 @@ func schema(t *testing.T) *openapi3.T { schemaErr = schemaDoc.Validate(context.Background()) } }) - if schemaErr != nil { - t.Fatalf("loading/validating protocol/openapi.yaml: %v", schemaErr) - } + testassert.Require(t, schemaErr == nil, "loading/validating protocol/openapi.yaml: %v", schemaErr) return schemaDoc } func componentSchema(t *testing.T, name string) *openapi3.Schema { t.Helper() component := schema(t).Components.Schemas[name] - if component == nil { - t.Fatalf("schema component %q is missing", name) - } + testassert.Require(t, component != nil, "schema component %q is missing", name) return component.Value } func acceptJSON(t *testing.T, s *openapi3.Schema, value string) { t.Helper() - if err := s.VisitJSON(decodeSchemaJSON(t, value)); err != nil { - t.Fatalf("schema rejected valid value %s: %v", value, err) + { + err := s.VisitJSON(decodeSchemaJSON(t, value)) + testassert.Require(t, err == nil, "schema rejected valid value %s: %v", value, err) } } func rejectJSON(t *testing.T, s *openapi3.Schema, value string) { t.Helper() - if err := s.VisitJSON(decodeSchemaJSON(t, value)); err == nil { - t.Fatalf("schema accepted invalid value %s", value) + { + err := s.VisitJSON(decodeSchemaJSON(t, value)) + testassert.Require(t, err != nil, "schema accepted invalid value %s", value) } } @@ -68,9 +67,7 @@ func decodeSchemaJSON(t *testing.T, value string) any { decoder := json.NewDecoder(strings.NewReader(value)) decoder.UseNumber() var decoded any - if err := decoder.Decode(&decoded); err != nil { - t.Fatalf("decoding test value: %v", err) - } + testassert.NoError(t, decoder.Decode(&decoded)) return decoded } @@ -84,16 +81,13 @@ const ( // documents and fails here. func TestPluginsSettingsDocumentsStayNamedOpaqueStrings(t *testing.T) { plugins := componentSchema(t, "PluginsSettings") - if ref := plugins.AdditionalProperties.Schema; ref == nil || ref.Ref != "#/components/schemas/PluginConfigDocument" { - t.Fatalf("PluginsSettings additionalProperties = %+v, want the named PluginConfigDocument scalar", ref) - } - if len(plugins.Properties) != 0 { - t.Fatalf("PluginsSettings declares named members %v, want the opaque map only", plugins.Properties) + { + ref := plugins.AdditionalProperties.Schema + testassert.Require(t, ref != nil && ref.Ref == "#/components/schemas/PluginConfigDocument", "PluginsSettings additionalProperties = %+v, want the named PluginConfigDocument scalar", ref) } + testassert.Require(t, len(plugins.Properties) == 0, "PluginsSettings declares named members %v, want the opaque map only", plugins.Properties) document := componentSchema(t, "PluginConfigDocument") - if document.Type == nil || !document.Type.Is("string") { - t.Fatalf("PluginConfigDocument type = %v, want string", document.Type) - } + testassert.Require(t, document.Type != nil && document.Type.Is("string"), "PluginConfigDocument type = %v, want string", document.Type) } // TestScopeEventSharedPayload keeps the scope_opened and scope_closed kinds @@ -107,12 +101,11 @@ func TestScopeEventSharedPayload(t *testing.T) { rejectJSON(t, event, `{"kind": "scope_opened", "scope": {"kind": "runtime"}, "extra": 1}`) discriminator := event.Discriminator - if discriminator == nil || discriminator.PropertyName != "kind" { - t.Fatal("Event discriminator is missing") - } + testassert.Require(t, discriminator != nil && discriminator.PropertyName == "kind", "Event discriminator is missing") for _, kind := range []string{"scope_opened", "scope_closed"} { - if got := discriminator.Mapping[kind].Ref; got != "#/components/schemas/ScopeEvent" { - t.Fatalf("Event discriminator mapping %q = %q, want the shared ScopeEvent component", kind, got) + { + got := discriminator.Mapping[kind].Ref + testassert.Require(t, got == "#/components/schemas/ScopeEvent", "Event discriminator mapping %q = %q, want the shared ScopeEvent component", kind, got) } } scopeEventRefs := 0 @@ -121,9 +114,7 @@ func TestScopeEventSharedPayload(t *testing.T) { scopeEventRefs++ } } - if scopeEventRefs != 1 { - t.Fatalf("Event oneOf references ScopeEvent %d times, want exactly once", scopeEventRefs) - } + testassert.Equal(t, scopeEventRefs, 1) } func TestGeneratedScopeEventRoundTripsBothKinds(t *testing.T) { @@ -135,19 +126,14 @@ func TestGeneratedScopeEventRoundTripsBothKinds(t *testing.T) { {protocol.ScopeClosed, "scope_closed"}, } { var event protocol.ScopeEvent - if err := json.Unmarshal([]byte(`{"kind": "`+tc.wire+`", "scope": {"kind": "runtime"}}`), &event); err != nil { - t.Fatalf("decoding generated ScopeEvent %s: %v", tc.wire, err) - } - if event.Kind != tc.kind { - t.Fatalf("decoded ScopeEvent kind = %q, want %q", event.Kind, tc.wire) + { + err := json.Unmarshal([]byte(`{"kind": "`+tc.wire+`", "scope": {"kind": "runtime"}}`), &event) + testassert.Require(t, err == nil, "decoding generated ScopeEvent %s: %v", tc.wire, err) } + testassert.Require(t, event.Kind == tc.kind, "decoded ScopeEvent kind = %q, want %q", event.Kind, tc.wire) data, err := json.Marshal(event) - if err != nil { - t.Fatalf("marshaling generated ScopeEvent: %v", err) - } - if !strings.Contains(string(data), `"`+tc.wire+`"`) { - t.Fatalf("generated ScopeEvent JSON = %s, want the %s kind preserved", data, tc.wire) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(data), `"`+tc.wire+`"`), "generated ScopeEvent JSON = %s, want the %s kind preserved", data, tc.wire) } } @@ -162,16 +148,13 @@ func TestRevertDefaultsShareTheCodeRevertErrorContract(t *testing.T) { {"/v1/retained-code-snapshots/revert"}, } { item := doc.Paths.Find(row.path) - if item == nil || item.Post == nil { - t.Fatalf("%s has no POST operation", row.path) - } + testassert.Require(t, item != nil && item.Post != nil, "%s has no POST operation", row.path) defaultResponse := item.Post.Responses.Default() - if defaultResponse == nil || defaultResponse.Value == nil { - t.Fatalf("%s POST has no default response", row.path) - } + testassert.Require(t, defaultResponse != nil && defaultResponse.Value != nil, "%s POST has no default response", row.path) ref := defaultResponse.Value.Content.Get("application/json").Schema.Ref - if want := "#/components/schemas/CodeRevertError"; ref != want { - t.Fatalf("%s default schema ref = %q, want %q", row.path, ref, want) + { + want := "#/components/schemas/CodeRevertError" + testassert.Require(t, ref == want, "%s default schema ref = %q, want %q", row.path, ref, want) } } @@ -189,12 +172,8 @@ func TestRevertDefaultsShareTheCodeRevertErrorContract(t *testing.T) { // api_key_env member keeps its gosec-safe generated identifier. func TestProviderAndModelFieldSetsShareEditAndResetPaths(t *testing.T) { field := protocol.ProviderFieldEnvironmentVariable - if !field.Valid() || field != "api_key_env" { - t.Fatalf("generated identifier %q is not the valid api_key_env member", field) - } - if !protocol.ProviderFieldHidden.Valid() || !protocol.ModelFieldHidden.Valid() { - t.Fatal("the shared field enums dropped the hidden member") - } + testassert.Require(t, field.Valid() && field == "api_key_env", "generated identifier %q is not the valid api_key_env member", field) + testassert.Require(t, protocol.ProviderFieldHidden.Valid() && protocol.ModelFieldHidden.Valid(), "the shared field enums dropped the hidden member") doc := schema(t) for _, row := range []struct { @@ -212,9 +191,7 @@ func TestProviderAndModelFieldSetsShareEditAndResetPaths(t *testing.T) { }}, } { item := doc.Paths.Find(row.path) - if item == nil || item.Delete == nil { - t.Fatalf("%s has no DELETE operation", row.path) - } + testassert.Require(t, item != nil && item.Delete != nil, "%s has no DELETE operation", row.path) var param *openapi3.Parameter for _, p := range item.Delete.Parameters { if p.Value != nil && p.Value.Name == "field" { @@ -222,20 +199,15 @@ func TestProviderAndModelFieldSetsShareEditAndResetPaths(t *testing.T) { break } } - if param == nil { - t.Fatalf("%s field path parameter is missing", row.path) - } - if ref := param.Schema.Ref; ref != "#/components/schemas/"+row.component { - t.Fatalf("%s field parameter ref = %q, want the shared %s component", row.path, ref, row.component) + testassert.Require(t, param != nil, "%s field path parameter is missing", row.path) + { + ref := param.Schema.Ref + testassert.Require(t, ref == "#/components/schemas/"+row.component, "%s field parameter ref = %q, want the shared %s component", row.path, ref, row.component) } component := componentSchema(t, row.component) - if len(component.Enum) != len(row.wantEnum) { - t.Fatalf("%s enum holds %d values, want %d", row.component, len(component.Enum), len(row.wantEnum)) - } + testassert.Require(t, len(component.Enum) == len(row.wantEnum), "%s enum holds %d values, want %d", row.component, len(component.Enum), len(row.wantEnum)) for i, v := range row.wantEnum { - if component.Enum[i] != v { - t.Fatalf("%s enum[%d] = %v, want %q", row.component, i, component.Enum[i], v) - } + testassert.Require(t, component.Enum[i] == v, "%s enum[%d] = %v, want %q", row.component, i, component.Enum[i], v) } // The edit request's property names are exactly the field enum: one // vocabulary shared by both paths. @@ -248,9 +220,7 @@ func TestProviderAndModelFieldSetsShareEditAndResetPaths(t *testing.T) { sort.Strings(properties) want := append([]string(nil), row.wantEnum...) sort.Strings(want) - if strings.Join(properties, ",") != strings.Join(want, ",") { - t.Fatalf("%s properties %v, want exactly the %s enum %v", editName, properties, row.component, want) - } + testassert.Require(t, strings.Join(properties, ",") == strings.Join(want, ","), "%s properties %v, want exactly the %s enum %v", editName, properties, row.component, want) } } @@ -263,11 +233,7 @@ func TestGeneratedSystemRoleSharedAcrossEditsAndViews(t *testing.T) { modelEdit := protocol.ModelEdit{SystemRole: &role} modelView := protocol.ModelView{SystemRole: role} provider := protocol.Provider{SystemRole: role} - if providerEdit.SystemRole == nil || modelEdit.SystemRole == nil || - modelView.SystemRole != protocol.SystemRoleDeveloper || - provider.SystemRole != protocol.SystemRoleDeveloper { - t.Fatal("generated SystemRole is not shared across provider/model edits and views") - } + testassert.Require(t, providerEdit.SystemRole != nil && modelEdit.SystemRole != nil && modelView.SystemRole == protocol.SystemRoleDeveloper && provider.SystemRole == protocol.SystemRoleDeveloper, "generated SystemRole is not shared across provider/model edits and views") } // TestConversationRawObjectFieldsShareOneComponent keeps the conversation @@ -297,9 +263,7 @@ func TestConversationRawObjectFieldsShareOneComponent(t *testing.T) { "ToolCallView.metadata=ToolMetadata", "ToolCallView.normalized_arguments=JSONObject", } - if !reflect.DeepEqual(refs, want) { - t.Fatalf("shared raw-object component references = %v, want exactly %v", refs, want) - } + testassert.Require(t, reflect.DeepEqual(refs, want), "shared raw-object component references = %v, want exactly %v", refs, want) } // TestGeneratedJSONObjectPreservesNumbersThroughUnionConstructors proves the @@ -309,9 +273,7 @@ func TestConversationRawObjectFieldsShareOneComponent(t *testing.T) { func TestGeneratedJSONObjectPreservesNumbersThroughUnionConstructors(t *testing.T) { raw := contractComponentFixture(t, loadContractCorpus(t), "JSONObject", "raw-numbers") var members map[string]json.RawMessage - if err := json.Unmarshal([]byte(raw), &members); err != nil { - t.Fatalf("decoding the raw JSONObject fixture: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(raw), &members)) normalized := protocol.JSONObject(members) metadata := protocol.ToolMetadata(raw) item := protocol.AssistantItem{ @@ -331,29 +293,17 @@ func TestGeneratedJSONObjectPreservesNumbersThroughUnionConstructors(t *testing. }}, } var wire protocol.ConversationItem - if err := wire.FromAssistantItem(item); err != nil { - t.Fatalf("FromAssistantItem: %v", err) - } + testassert.NoError(t, wire.FromAssistantItem(item)) data, err := json.Marshal(wire) - if err != nil { - t.Fatalf("marshaling generated ConversationItem: %v", err) - } + testassert.NoError(t, err) for _, want := range []string{string(members["n"]), string(members["big"])} { - if !strings.Contains(string(data), want) { - t.Fatalf("wire JSON = %s, want %q preserved", data, want) - } + testassert.Require(t, strings.Contains(string(data), want), "wire JSON = %s, want %q preserved", data, want) } decoded, err := wire.AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } - if decoded.ToolCalls[0].NormalizedArguments == nil || string((*decoded.ToolCalls[0].NormalizedArguments)["n"]) != string(members["n"]) { - t.Fatalf("decoded normalized arguments = %v, want the raw number preserved", decoded.ToolCalls[0].NormalizedArguments) - } + testassert.NoError(t, err) + testassert.Require(t, decoded.ToolCalls[0].NormalizedArguments != nil && string((*decoded.ToolCalls[0].NormalizedArguments)["n"]) == string(members["n"]), "decoded normalized arguments = %v, want the raw number preserved", decoded.ToolCalls[0].NormalizedArguments) // A raw-valued object re-encodes canonically (sorted members) while every // value stays byte-exact: the fixture's opaque numbers survive untouched. want := "{\"big\":" + string(members["big"]) + ",\"n\":" + string(members["n"]) + "}" - if decoded.ToolCalls[0].Metadata == nil || string(*decoded.ToolCalls[0].Metadata) != want { - t.Fatalf("decoded metadata = %v, want the raw value preserved", decoded.ToolCalls[0].Metadata) - } + testassert.Require(t, decoded.ToolCalls[0].Metadata != nil && string(*decoded.ToolCalls[0].Metadata) == want, "decoded metadata = %v, want the raw value preserved", decoded.ToolCalls[0].Metadata) } diff --git a/protocol/corpus_test.go b/protocol/corpus_test.go index 4243fd19..34ef771e 100644 --- a/protocol/corpus_test.go +++ b/protocol/corpus_test.go @@ -10,6 +10,7 @@ import ( "github.com/getkin/kin-openapi/openapi3" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -52,13 +53,9 @@ type contractCorpus struct { func loadContractCorpus(t *testing.T) contractCorpus { t.Helper() data, err := os.ReadFile(contractCorpusPath) - if err != nil { - t.Fatalf("reading the shared contract corpus: %v", err) - } + testassert.NoError(t, err) var corpus contractCorpus - if err := json.Unmarshal(data, &corpus); err != nil { - t.Fatalf("decoding the shared contract corpus: %v", err) - } + testassert.NoError(t, json.Unmarshal(data, &corpus)) return corpus } @@ -96,8 +93,9 @@ func contractFixtureObjects(t *testing.T, corpus contractCorpus, component strin objects := make([]map[string]json.RawMessage, 0, len(entry.Accept)) for _, fixture := range entry.Accept { var members map[string]json.RawMessage - if err := json.Unmarshal([]byte(fixture.JSON), &members); err != nil { - t.Fatalf("%s accept %q: %v", component, fixture.Name, err) + { + err := json.Unmarshal([]byte(fixture.JSON), &members) + testassert.Require(t, err == nil, "%s accept %q: %v", component, fixture.Name, err) } objects = append(objects, members) } @@ -123,9 +121,7 @@ func requireSameSet(t *testing.T, what string, want, have map[string]bool) { } sort.Strings(missing) sort.Strings(extra) - if len(missing) != 0 || len(extra) != 0 { - t.Fatalf("%s fixture coverage missing=%v extra=%v", what, missing, extra) - } + testassert.Require(t, len(missing) == 0 && len(extra) == 0, "%s fixture coverage missing=%v extra=%v", what, missing, extra) } // TestContractCorpusMatchesSchemaComponents proves the corpus names exactly @@ -152,9 +148,7 @@ func TestContractCorpusMatchesSchemaComponents(t *testing.T) { } sort.Strings(missing) sort.Strings(unknown) - if len(missing) != 0 || len(unknown) != 0 { - t.Fatalf("schema↔corpus component set mismatch: missing from corpus=%v, unknown to schema=%v", missing, unknown) - } + testassert.Require(t, len(missing) == 0 && len(unknown) == 0, "schema↔corpus component set mismatch: missing from corpus=%v, unknown to schema=%v", missing, unknown) } // TestContractCorpusFixtureNamesUnique rejects a repeated fixture name inside @@ -182,16 +176,13 @@ func TestContractCorpusGeneratedTypeCoverage(t *testing.T) { corpus := loadContractCorpus(t) seen := map[string]bool{} for _, entry := range corpus.Components { - if seen[entry.Schema] { - t.Fatalf("component %q appears twice in the corpus", entry.Schema) - } + testassert.Require(t, !seen[entry.Schema], "component %q appears twice in the corpus", entry.Schema) seen[entry.Schema] = true - if _, ok := contractGeneratedTypes[entry.Schema]; !ok { - t.Fatalf("component %q has no generated Go target", entry.Schema) - } - if len(entry.Accept) == 0 { - t.Fatalf("component %q has no accepted fixture", entry.Schema) + { + _, ok := contractGeneratedTypes[entry.Schema] + testassert.Require(t, ok, "component %q has no generated Go target", entry.Schema) } + testassert.Require(t, len(entry.Accept) != 0, "component %q has no accepted fixture", entry.Schema) } var untested []string for name := range contractGeneratedTypes { @@ -200,9 +191,7 @@ func TestContractCorpusGeneratedTypeCoverage(t *testing.T) { } } sort.Strings(untested) - if len(untested) != 0 { - t.Fatalf("generated targets missing from the corpus: %v", untested) - } + testassert.Require(t, len(untested) == 0, "generated targets missing from the corpus: %v", untested) } // TestContractCorpusAcceptedFixturesRoundTrip drives every accepted fixture @@ -228,8 +217,9 @@ func TestContractCorpusAcceptedFixturesRoundTrip(t *testing.T) { t.Errorf("%s accept %q: re-encoding generated value: %v", entry.Schema, fixture.Name, err) continue } - if err := schemaRef.VisitJSON(decodeSchemaJSON(t, string(encoded))); err != nil { - t.Errorf("%s accept %q: schema rejected the re-encoded value %s: %v", entry.Schema, fixture.Name, encoded, err) + { + err := schemaRef.VisitJSON(decodeSchemaJSON(t, string(encoded))) + testassert.Check(t, err == nil, "%s accept %q: schema rejected the re-encoded value %s: %v", entry.Schema, fixture.Name, encoded, err) } } } @@ -242,8 +232,9 @@ func TestContractCorpusRejectedFixturesFailSchema(t *testing.T) { for _, entry := range corpus.Components { schemaRef := componentSchema(t, entry.Schema) for _, fixture := range entry.Reject { - if err := schemaRef.VisitJSON(decodeSchemaJSON(t, fixture.JSON)); err == nil { - t.Errorf("%s reject %q: schema accepted an invalid value: %s", entry.Schema, fixture.Name, fixture.JSON) + { + err := schemaRef.VisitJSON(decodeSchemaJSON(t, fixture.JSON)) + testassert.Check(t, err != nil, "%s reject %q: schema accepted an invalid value: %s", entry.Schema, fixture.Name, fixture.JSON) } } } @@ -256,32 +247,24 @@ func TestContractCorpusEnumValueCoverage(t *testing.T) { corpus := loadContractCorpus(t) codeProperty := componentSchema(t, "Error").Properties["code"] - if codeProperty == nil || codeProperty.Value == nil { - t.Fatal("Error.code has no schema property") - } + testassert.Require(t, codeProperty != nil && codeProperty.Value != nil, "Error.code has no schema property") allowedCodes := map[string]bool{} for _, value := range codeProperty.Value.Enum { code, ok := value.(string) - if !ok { - t.Fatalf("Error.code enum value %v is not a string", value) - } + testassert.Require(t, ok, "Error.code enum value %v is not a string", value) allowedCodes[code] = true } haveCodes := map[string]bool{} for _, members := range contractFixtureObjects(t, corpus, "Error") { var code string - if err := json.Unmarshal(members["code"], &code); err != nil { - t.Fatalf("Error accept fixture code: %v", err) - } + testassert.NoError(t, json.Unmarshal(members["code"], &code)) haveCodes[code] = true } requireSameSet(t, "Error.code", allowedCodes, haveCodes) for _, union := range []string{"ConversationItem", "Event"} { unionSchema := componentSchema(t, union) - if unionSchema.Discriminator == nil { - t.Fatalf("%s has no discriminator", union) - } + testassert.Require(t, unionSchema.Discriminator != nil, "%s has no discriminator", union) allowedKinds := map[string]bool{} for kind := range unionSchema.Discriminator.Mapping { allowedKinds[kind] = true @@ -289,8 +272,9 @@ func TestContractCorpusEnumValueCoverage(t *testing.T) { haveKinds := map[string]bool{} for _, members := range contractFixtureObjects(t, corpus, union) { var kind string - if err := json.Unmarshal(members["kind"], &kind); err != nil { - t.Fatalf("%s accept fixture kind: %v", union, err) + { + err := json.Unmarshal(members["kind"], &kind) + testassert.Require(t, err == nil, "%s accept fixture kind: %v", union, err) } haveKinds[kind] = true } @@ -329,9 +313,7 @@ func TestContractCorpusOptionalMembersPresentAndAbsent(t *testing.T) { absent = true } } - if !present || !absent { - t.Errorf("%s optional member %q is not pinned by accepted present and absent fixtures (present=%v absent=%v)", entry.Schema, member, present, absent) - } + testassert.Check(t, present && absent, "%s optional member %q is not pinned by accepted present and absent fixtures (present=%v absent=%v)", entry.Schema, member, present, absent) } } } @@ -344,8 +326,9 @@ func TestContractCorpusOperationsMatchSchema(t *testing.T) { corpus := loadContractCorpus(t) have := map[string]contractOperation{} for _, operation := range corpus.Operations { - if _, duplicate := have[operation.OperationID]; duplicate { - t.Fatalf("operation %q appears twice in the corpus", operation.OperationID) + { + _, duplicate := have[operation.OperationID] + testassert.Require(t, !duplicate, "operation %q appears twice in the corpus", operation.OperationID) } have[operation.OperationID] = operation } @@ -362,12 +345,8 @@ func TestContractCorpusOperationsMatchSchema(t *testing.T) { Responses: operationResponseComponents(doc, operation), } found, ok := have[operation.OperationID] - if !ok { - t.Fatalf("operation %q is missing from the contract corpus", operation.OperationID) - } - if !reflect.DeepEqual(found, want) { - t.Fatalf("operation %q corpus mapping = %+v, want %+v", operation.OperationID, found, want) - } + testassert.Require(t, ok, "operation %q is missing from the contract corpus", operation.OperationID) + testassert.Require(t, reflect.DeepEqual(found, want), "operation %q corpus mapping = %+v, want %+v", operation.OperationID, found, want) delete(have, operation.OperationID) } } @@ -482,23 +461,22 @@ func TestContractCorpusRawFidelity(t *testing.T) { for _, entry := range corpus.RawFidelity { schemaRef := componentSchema(t, entry.Schema) original := decodeSchemaJSON(t, entry.JSON) - if err := schemaRef.VisitJSON(original); err != nil { - t.Fatalf("%s raw fixture %s: schema rejected it: %v", entry.Schema, entry.JSON, err) + { + err := schemaRef.VisitJSON(original) + testassert.Require(t, err == nil, "%s raw fixture %s: schema rejected it: %v", entry.Schema, entry.JSON, err) } target, ok := contractGeneratedTypes[entry.Schema] - if !ok { - t.Fatalf("raw fixture schema %q has no generated Go target", entry.Schema) - } + testassert.Require(t, ok, "raw fixture schema %q has no generated Go target", entry.Schema) value := target() - if err := json.Unmarshal([]byte(entry.JSON), value); err != nil { - t.Fatalf("%s raw fixture: generated decode: %v", entry.Schema, err) + { + err := json.Unmarshal([]byte(entry.JSON), value) + testassert.Require(t, err == nil, "%s raw fixture: generated decode: %v", entry.Schema, err) } encoded, err := json.Marshal(value) - if err != nil { - t.Fatalf("%s raw fixture: re-encode: %v", entry.Schema, err) - } - if reencoded := decodeSchemaJSON(t, string(encoded)); !reflect.DeepEqual(original, reencoded) { - t.Fatalf("%s raw fixture re-encoded to %s, want the decoded original %s preserved exactly", entry.Schema, encoded, entry.JSON) + testassert.Require(t, err == nil, "%s raw fixture: re-encode: %v", entry.Schema, err) + { + reencoded := decodeSchemaJSON(t, string(encoded)) + testassert.Require(t, reflect.DeepEqual(original, reencoded), "%s raw fixture re-encoded to %s, want the decoded original %s preserved exactly", entry.Schema, encoded, entry.JSON) } } } @@ -522,18 +500,22 @@ func TestContractCorpusSSEIdentity(t *testing.T) { {"Event", contractComponentFixture(t, corpus, "Event", component)}, } { var members map[string]json.RawMessage - if err := json.Unmarshal([]byte(fixture.json), &members); err != nil { - t.Fatalf("%s fixture %q: %v", component, fixture.name, err) + { + err := json.Unmarshal([]byte(fixture.json), &members) + testassert.Require(t, err == nil, "%s fixture %q: %v", component, fixture.name, err) } - if _, duplicate := members["instance_id"]; duplicate { - t.Fatalf("%s fixture %q carries a top-level instance_id", component, fixture.name) + { + _, duplicate := members["instance_id"] + testassert.Require(t, !duplicate, "%s fixture %q carries a top-level instance_id", component, fixture.name) } var revision map[string]json.RawMessage - if err := json.Unmarshal(members[revisionMember], &revision); err != nil { - t.Fatalf("%s fixture %q: revision member %q: %v", component, fixture.name, revisionMember, err) + { + err := json.Unmarshal(members[revisionMember], &revision) + testassert.Require(t, err == nil, "%s fixture %q: revision member %q: %v", component, fixture.name, revisionMember, err) } - if _, ok := revision["instance_id"]; !ok { - t.Fatalf("%s fixture %q: revision %q has no instance_id", component, fixture.name, revisionMember) + { + _, ok := revision["instance_id"] + testassert.Require(t, ok, "%s fixture %q: revision %q has no instance_id", component, fixture.name, revisionMember) } } } @@ -541,11 +523,13 @@ func TestContractCorpusSSEIdentity(t *testing.T) { for _, component := range []string{"TextDeltaEvent", "RefusalDeltaEvent", "ToolStartedEvent", "ToolFinishedEvent"} { fixture := contractComponentFixture(t, corpus, component, "progress") var members map[string]json.RawMessage - if err := json.Unmarshal([]byte(fixture), &members); err != nil { - t.Fatalf("%s progress fixture: %v", component, err) + { + err := json.Unmarshal([]byte(fixture), &members) + testassert.Require(t, err == nil, "%s progress fixture: %v", component, err) } - if _, ok := members["instance_id"]; ok { - t.Fatalf("%s progress fixture carries an instance identity", component) + { + _, ok := members["instance_id"] + testassert.Require(t, !ok, "%s progress fixture carries an instance identity", component) } } } diff --git a/protocol/freshness_test.go b/protocol/freshness_test.go index dc976414..9ef13bff 100644 --- a/protocol/freshness_test.go +++ b/protocol/freshness_test.go @@ -10,76 +10,46 @@ import ( "strings" "testing" + "github.com/MMinasyan/lightcode/internal/testassert" "gopkg.in/yaml.v3" ) -// TestGeneratedContractsFresh regenerates the Go protocol artifact from the -// committed schema with the committed generator configuration and compares -// the bytes against the committed protocol/protocol.gen.go: any hand edit or -// schema change without regeneration fails. The TypeScript artifact is not -// covered here — its regeneration needs the frontend toolchain and is +// TestGeneratedContractsFresh regenerates the SDK, the native operation +// bindings and the test-only native invocation contract from the committed +// schema with the committed build command and compares all three byte-for-byte +// against the committed artifacts. Two runs must agree, so a hand edit, a stale +// artifact or a nondeterministic generator fails here. The TypeScript artifact +// is not covered here — its regeneration needs the frontend toolchain and is // gated by the make check-protocol target. func TestGeneratedContractsFresh(t *testing.T) { - // go test runs the test binary with the package directory as its working - // directory, so the module root is exactly "..". - committed, err := os.ReadFile(filepath.Join("..", "protocol", "protocol.gen.go")) - if err != nil { - t.Fatalf("reading committed generated contracts: %v", err) - } - generated := filepath.Join(t.TempDir(), "protocol.gen.go") - cmd := exec.Command("go", "tool", "oapi-codegen", - "--config", "protocol/oapi-codegen.yaml", - "-o", generated, - "protocol/openapi.yaml") - cmd.Dir = ".." - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("regenerating protocol contracts: %v\n%s", err, out) - } - regenerated, err := os.ReadFile(generated) - if err != nil { - t.Fatalf("reading regenerated contracts: %v", err) - } - if !bytes.Equal(committed, regenerated) { - t.Fatalf("committed protocol/protocol.gen.go is stale: regenerate with make generate-protocol (%d committed bytes, %d regenerated)", len(committed), len(regenerated)) - } -} - -// TestGeneratedNativeBindingsFresh regenerates the native operation bindings -// and the test-only native invocation contract from the committed schema with -// the committed build command and compares both byte-for-byte against the -// committed artifacts. Two runs must agree, so a hand edit, a stale artifact -// or a nondeterministic generator fails here. The TypeScript artifact is not -// covered here — its regeneration needs the frontend toolchain and is gated by -// the make check-protocol target. -func TestGeneratedNativeBindingsFresh(t *testing.T) { + committedSDK := committedArtifact(t, "protocol/protocol.gen.go") committedSource := committedArtifact(t, "internal/clientops/operations.gen.go") committedContract := committedArtifact(t, "frontend/src/generated/native-invocations.json") - generate := func(t *testing.T) ([]byte, []byte) { + generate := func(t *testing.T) ([]byte, []byte, []byte) { t.Helper() dir := t.TempDir() + sdk := filepath.Join(dir, "protocol.gen.go") source := filepath.Join(dir, "operations.gen.go") contract := filepath.Join(dir, "native-invocations.json") cmd := exec.Command("go", "run", "./internal/protocolgen", "-schema", "protocol/openapi.yaml", + "-config", "protocol/oapi-codegen.yaml", + "-sdk-out", sdk, "-out", source, "-native-contract-out", contract) cmd.Dir = ".." - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("regenerating native bindings: %v\n%s", err, out) + { + out, err := cmd.CombinedOutput() + testassert.Require(t, err == nil, "regenerating protocol contracts: %v\n%s", err, out) } - return readArtifact(t, source), readArtifact(t, contract) - } - source, contract := generate(t) - againSource, againContract := generate(t) - if !bytes.Equal(source, againSource) || !bytes.Equal(contract, againContract) { - t.Fatal("the native bindings generator is not deterministic") - } - if !bytes.Equal(committedSource, source) { - t.Fatalf("committed internal/clientops/operations.gen.go is stale: regenerate with make generate-protocol (%d committed bytes, %d regenerated)", len(committedSource), len(source)) - } - if !bytes.Equal(committedContract, contract) { - t.Fatalf("committed frontend/src/generated/native-invocations.json is stale: regenerate with make generate-protocol (%d committed bytes, %d regenerated)", len(committedContract), len(contract)) - } + return readArtifact(t, sdk), readArtifact(t, source), readArtifact(t, contract) + } + sdk, source, contract := generate(t) + againSDK, againSource, againContract := generate(t) + testassert.Require(t, bytes.Equal(sdk, againSDK) && bytes.Equal(source, againSource) && bytes.Equal(contract, againContract), "the protocol generator is not deterministic") + testassert.Require(t, bytes.Equal(committedSDK, sdk), "committed protocol/protocol.gen.go is stale: regenerate with make generate-protocol (%d committed bytes, %d regenerated)", len(committedSDK), len(sdk)) + testassert.Require(t, bytes.Equal(committedSource, source), "committed internal/clientops/operations.gen.go is stale: regenerate with make generate-protocol (%d committed bytes, %d regenerated)", len(committedSource), len(source)) + testassert.Require(t, bytes.Equal(committedContract, contract), "committed frontend/src/generated/native-invocations.json is stale: regenerate with make generate-protocol (%d committed bytes, %d regenerated)", len(committedContract), len(contract)) } // committedArtifact reads one committed artifact relative to the module root. @@ -92,9 +62,7 @@ func committedArtifact(t *testing.T, rel string) []byte { func readArtifact(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("reading %s: %v", path, err) - } + testassert.Require(t, err == nil, "reading %s: %v", path, err) return data } @@ -108,9 +76,7 @@ func readArtifact(t *testing.T, path string) []byte { // fixture. func TestGeneratedFileSetGate(t *testing.T) { script, err := filepath.Abs(filepath.Join("..", "scripts", "check-generated-files.sh")) - if err != nil { - t.Fatalf("resolving the freshness script: %v", err) - } + testassert.NoError(t, err) // The two real invocations: make check-protocol names the four protocol // artifacts (directories included) and make check-wails-bindings names the @@ -141,35 +107,27 @@ func TestGeneratedFileSetGate(t *testing.T) { dir := t.TempDir() for _, output := range outputs { path := filepath.Join(dir, filepath.FromSlash(output.path)) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - t.Fatalf("creating fixture directory: %v", err) - } - if err := os.WriteFile(path, []byte("generated"), 0o644); err != nil { - t.Fatalf("writing fixture artifact: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) + testassert.NoError(t, os.WriteFile(path, []byte("generated"), 0o644)) } // Ignored output the gates must never flag: the ignored Wails runtime // tree and an ignored sibling inside the checked bindings directory. - if err := os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("frontend/client-bindings/wailsjs/runtime/\n*.ignored\n"), 0o644); err != nil { - t.Fatalf("writing fixture ignore file: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("frontend/client-bindings/wailsjs/runtime/\n*.ignored\n"), 0o644)) for _, file := range []string{ "frontend/client-bindings/wailsjs/runtime/runtime.js", "frontend/client-bindings/wailsjs/go/desktopclient/Host.js.ignored", } { path := filepath.Join(dir, filepath.FromSlash(file)) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - t.Fatalf("creating ignored fixture directory: %v", err) - } - if err := os.WriteFile(path, []byte("ignored"), 0o644); err != nil { - t.Fatalf("writing ignored fixture file: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) + testassert.NoError(t, os.WriteFile(path, []byte("ignored"), 0o644)) } - if out, err := exec.Command("git", "-C", dir, "init", "-q").CombinedOutput(); err != nil { - t.Fatalf("fixture git init: %v\n%s", err, out) + { + out, err := exec.Command("git", "-C", dir, "init", "-q").CombinedOutput() + testassert.Require(t, err == nil, "fixture git init: %v\n%s", err, out) } - if out, err := exec.Command("git", "-C", dir, "add", ".").CombinedOutput(); err != nil { - t.Fatalf("fixture git add: %v\n%s", err, out) + { + out, err := exec.Command("git", "-C", dir, "add", ".").CombinedOutput() + testassert.Require(t, err == nil, "fixture git add: %v\n%s", err, out) } return dir } @@ -186,17 +144,14 @@ func TestGeneratedFileSetGate(t *testing.T) { t.Run("matching staged bytes pass", func(t *testing.T) { dir := fixture(t) - if err := run(t, dir, protocolPaths...); err != nil { - t.Fatalf("gate rejected matching staged protocol artifacts: %v", err) - } - if err := run(t, dir, wailsPaths...); err != nil { - t.Fatalf("gate rejected matching staged Wails bindings: %v", err) - } + testassert.NoError(t, run(t, dir, protocolPaths...)) + testassert.NoError(t, run(t, dir, wailsPaths...)) }) t.Run("no path arguments fail", func(t *testing.T) { - if err := run(t, fixture(t)); err == nil { - t.Fatal("gate accepted an invocation without path arguments") + { + err := run(t, fixture(t)) + testassert.Require(t, err != nil, "gate accepted an invocation without path arguments") } }) @@ -204,11 +159,10 @@ func TestGeneratedFileSetGate(t *testing.T) { for _, output := range outputs { dir := fixture(t) path := filepath.Join(dir, filepath.FromSlash(output.path)) - if err := os.WriteFile(path, []byte("hand-edited"), 0o644); err != nil { - t.Fatalf("modifying fixture artifact: %v", err) - } - if err := run(t, dir, output.paths...); err == nil { - t.Fatalf("gate accepted a modified tracked artifact: %s", output.path) + testassert.NoError(t, os.WriteFile(path, []byte("hand-edited"), 0o644)) + { + err := run(t, dir, output.paths...) + testassert.Require(t, err != nil, "gate accepted a modified tracked artifact: %s", output.path) } } }) @@ -217,11 +171,10 @@ func TestGeneratedFileSetGate(t *testing.T) { for _, output := range outputs { dir := fixture(t) path := filepath.Join(dir, filepath.FromSlash(output.path)) - if err := os.Remove(path); err != nil { - t.Fatalf("deleting fixture artifact: %v", err) - } - if err := run(t, dir, output.paths...); err == nil { - t.Fatalf("gate accepted a deleted tracked artifact: %s", output.path) + testassert.NoError(t, os.Remove(path)) + { + err := run(t, dir, output.paths...) + testassert.Require(t, err != nil, "gate accepted a deleted tracked artifact: %s", output.path) } } }) @@ -230,11 +183,13 @@ func TestGeneratedFileSetGate(t *testing.T) { for _, output := range outputs { dir := fixture(t) // The staged artifact becomes untracked output again. - if out, err := exec.Command("git", "-C", dir, "rm", "--cached", "-q", "--", output.path).CombinedOutput(); err != nil { - t.Fatalf("unstaging fixture artifact %s: %v\n%s", output.path, err, out) + { + out, err := exec.Command("git", "-C", dir, "rm", "--cached", "-q", "--", output.path).CombinedOutput() + testassert.Require(t, err == nil, "unstaging fixture artifact %s: %v\n%s", output.path, err, out) } - if err := run(t, dir, output.paths...); err == nil { - t.Fatalf("gate accepted a newly untracked artifact: %s", output.path) + { + err := run(t, dir, output.paths...) + testassert.Require(t, err != nil, "gate accepted a newly untracked artifact: %s", output.path) } } for _, output := range []struct { @@ -246,39 +201,31 @@ func TestGeneratedFileSetGate(t *testing.T) { } { dir := fixture(t) path := filepath.Join(dir, filepath.FromSlash(output.path)) - if err := os.WriteFile(path, []byte("generated"), 0o644); err != nil { - t.Fatalf("emitting untracked fixture artifact %s: %v", output.path, err) + { + err := os.WriteFile(path, []byte("generated"), 0o644) + testassert.Require(t, err == nil, "emitting untracked fixture artifact %s: %v", output.path, err) } - if err := run(t, dir, output.paths...); err == nil { - t.Fatalf("gate accepted a newly emitted untracked artifact: %s", output.path) + { + err := run(t, dir, output.paths...) + testassert.Require(t, err != nil, "gate accepted a newly emitted untracked artifact: %s", output.path) } } }) t.Run("output outside the supplied paths passes", func(t *testing.T) { dir := fixture(t) - if err := os.WriteFile(filepath.Join(dir, "protocol", "protocol.gen.go"), []byte("hand-edited"), 0o644); err != nil { - t.Fatalf("modifying fixture artifact: %v", err) - } - if err := run(t, dir, wailsPaths...); err != nil { - t.Fatalf("gate flagged output outside its path arguments: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "protocol", "protocol.gen.go"), []byte("hand-edited"), 0o644)) + testassert.NoError(t, run(t, dir, wailsPaths...)) }) t.Run("unrelated untracked file passes", func(t *testing.T) { dir := fixture(t) - if err := os.WriteFile(filepath.Join(dir, "unrelated.txt"), []byte("junk"), 0o644); err != nil { - t.Fatalf("writing unrelated fixture file: %v", err) - } - if err := run(t, dir, protocolPaths...); err != nil { - t.Fatalf("gate rejected an unrelated untracked file: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(dir, "unrelated.txt"), []byte("junk"), 0o644)) + testassert.NoError(t, run(t, dir, protocolPaths...)) }) t.Run("ignored output passes", func(t *testing.T) { - if err := run(t, fixture(t), wailsPaths...); err != nil { - t.Fatalf("gate flagged ignored output: %v", err) - } + testassert.NoError(t, run(t, fixture(t), wailsPaths...)) }) } @@ -312,12 +259,11 @@ type workflowFile struct { func parseWorkflow(t *testing.T, path string) workflowFile { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("reading workflow %s: %v", path, err) - } + testassert.Require(t, err == nil, "reading workflow %s: %v", path, err) var workflow workflowFile - if err := yaml.Unmarshal(data, &workflow); err != nil { - t.Fatalf("decoding workflow %s: %v", path, err) + { + err := yaml.Unmarshal(data, &workflow) + testassert.Require(t, err == nil, "decoding workflow %s: %v", path, err) } return workflow } @@ -328,50 +274,61 @@ func parsedSteps(t *testing.T, path, job string) []workflowStep { t.Helper() workflow := parseWorkflow(t, path) parsed, ok := workflow.Jobs[job] - if !ok { - t.Fatalf("workflow %s has no %s job", path, job) - } + testassert.Require(t, ok, "workflow %s has no %s job", path, job) return parsed.Steps } -// findRunStep returns the index of the first step whose trimmed run command is -// exactly want, or -1. -func findRunStep(steps []workflowStep, want string) int { - for i, step := range steps { - if strings.TrimSpace(step.Run) == want { - return i - } - } - return -1 +// publicationStepNames are the release steps both delivery gates must precede. +var publicationStepNames = []string{"Package release assets", "Upload release assets"} + +// deliveryWiring is one job's single scan for the steps the delivery rules +// name: the tagged Wails build, the two exact gates and every publication +// step. The first matching step owns each slot, so the scan is the one place +// step lookup is defined; the rules below read only this result. +type deliveryWiring struct { + steps []workflowStep + wails int + bindings int + protocol int + publication map[string]int } -// findContainingRunStep returns the index of the first step whose run command -// contains want, or -1. -func findContainingRunStep(steps []workflowStep, want string) int { - for i, step := range steps { - if strings.Contains(step.Run, want) { - return i +// scanDeliveryWiring collects the required step indices in one pass. +func scanDeliveryWiring(steps []workflowStep) deliveryWiring { + wiring := deliveryWiring{ + steps: steps, + wails: -1, + bindings: -1, + protocol: -1, + publication: make(map[string]int, len(publicationStepNames)), + } + for index, step := range steps { + if wiring.wails < 0 && strings.Contains(step.Run, wailsBuildRun) { + wiring.wails = index } - } - return -1 -} - -// findNamedStep returns the index of the first step named want, or -1. -func findNamedStep(steps []workflowStep, want string) int { - for i, step := range steps { - if step.Name == want { - return i + if wiring.bindings < 0 && strings.TrimSpace(step.Run) == bindingsGateRun { + wiring.bindings = index + } + if wiring.protocol < 0 && strings.TrimSpace(step.Run) == protocolGateRun { + wiring.protocol = index + } + for _, name := range publicationStepNames { + if _, ok := wiring.publication[name]; !ok && step.Name == name { + wiring.publication[name] = index + } } } - return -1 + return wiring } // stepBypass names the bypass on one step, or "" when it is unconditional. +// continue-on-error accepts only an explicit false as equivalent to omission; +// any true value still allows the step to fail. func stepBypass(step workflowStep) string { if step.If != "" { return "conditionally skipped" } - if step.ContinueOnError != nil { + if step.ContinueOnError != nil && *step.ContinueOnError { return "allowed to fail" } return "" @@ -384,40 +341,37 @@ func stepBypass(step workflowStep) string { // gates must precede every Package release assets or Upload release assets step // the job declares. The protocol check has no ordering constraint against the // Wails build, and a job without publication steps (Test) skips that last rule. -func checkDeliveryGates(steps []workflowStep) error { - wails := findContainingRunStep(steps, wailsBuildRun) - if wails < 0 { +func checkDeliveryGates(wiring deliveryWiring) error { + if wiring.wails < 0 { return fmt.Errorf("missing the tagged Wails build command") } - if bypass := stepBypass(steps[wails]); bypass != "" { + if bypass := stepBypass(wiring.steps[wiring.wails]); bypass != "" { return fmt.Errorf("Wails build is %s", bypass) } - bindings := findRunStep(steps, bindingsGateRun) - if bindings < 0 { + if wiring.bindings < 0 { return fmt.Errorf("missing the exact run step %s", bindingsGateRun) } - if bypass := stepBypass(steps[bindings]); bypass != "" { + if bypass := stepBypass(wiring.steps[wiring.bindings]); bypass != "" { return fmt.Errorf("%s is %s", bindingsGateRun, bypass) } - if bindings < wails { + if wiring.bindings < wiring.wails { return fmt.Errorf("%s runs before the Wails build", bindingsGateRun) } - protocol := findRunStep(steps, protocolGateRun) - if protocol < 0 { + if wiring.protocol < 0 { return fmt.Errorf("missing the exact run step %s", protocolGateRun) } - if bypass := stepBypass(steps[protocol]); bypass != "" { + if bypass := stepBypass(wiring.steps[wiring.protocol]); bypass != "" { return fmt.Errorf("%s is %s", protocolGateRun, bypass) } - for _, publication := range []string{"Package release assets", "Upload release assets"} { - index := findNamedStep(steps, publication) - if index < 0 { + for _, publication := range publicationStepNames { + index, ok := wiring.publication[publication] + if !ok { continue } - if protocol > index { + if wiring.protocol > index { return fmt.Errorf("%s does not precede %s", protocolGateRun, publication) } - if bindings > index { + if wiring.bindings > index { return fmt.Errorf("%s does not precede %s", bindingsGateRun, publication) } } @@ -432,32 +386,34 @@ func checkDeliveryGates(steps []workflowStep) error { // constrains the protocol check against the Wails build, and the Release // workflow must still declare its two publication steps. Mutation rows prove // removed, conditionally skipped, allowed-to-fail, suppressed and reordered -// gates are rejected, and that a protocol check before the build is accepted +// gates are rejected, that an explicit continue-on-error false is equivalent +// to an omitted key, and that a protocol check before the build is accepted // in both workflows. This verifies repository wiring, not GitHub branch // protection or hosted execution. func TestDeliveryGateWorkflowWiring(t *testing.T) { testPath := filepath.Join("..", ".github", "workflows", "test.yml") releasePath := filepath.Join("..", ".github", "workflows", "release.yml") - testSteps := parsedSteps(t, testPath, "test") - if err := checkDeliveryGates(testSteps); err != nil { - t.Errorf("test workflow: %v", err) + testWiring := scanDeliveryWiring(parsedSteps(t, testPath, "test")) + { + err := checkDeliveryGates(testWiring) + testassert.Check(t, err == nil, "test workflow: %v", err) } - releaseSteps := parsedSteps(t, releasePath, "build") - if err := checkDeliveryGates(releaseSteps); err != nil { - t.Errorf("release workflow: %v", err) + releaseWiring := scanDeliveryWiring(parsedSteps(t, releasePath, "build")) + { + err := checkDeliveryGates(releaseWiring) + testassert.Check(t, err == nil, "release workflow: %v", err) } - for _, publication := range []string{"Package release assets", "Upload release assets"} { - if findNamedStep(releaseSteps, publication) < 0 { - t.Errorf("release workflow is missing the %s step", publication) - } + for _, publication := range publicationStepNames { + _, ok := releaseWiring.publication[publication] + testassert.Check(t, ok, "release workflow is missing the %s step", publication) } // protocolBeforeWails moves the exact protocol gate step to just before the // tagged Wails build: the order both workflows must accept. protocolBeforeWails := func(steps []workflowStep) []workflowStep { - from := findRunStep(steps, protocolGateRun) - to := findContainingRunStep(steps, wailsBuildRun) + wiring := scanDeliveryWiring(steps) + from, to := wiring.protocol, wiring.wails step := steps[from] steps = slices.Delete(steps, from, from+1) if from < to { @@ -466,6 +422,7 @@ func TestDeliveryGateWorkflowWiring(t *testing.T) { return slices.Insert(steps, to, step) } allowedToFail := true + allowedToFailFalse := false for _, mutation := range []struct { name string @@ -477,69 +434,69 @@ func TestDeliveryGateWorkflowWiring(t *testing.T) { {"protocol gate before the Wails build in Test is accepted", testPath, "test", true, protocolBeforeWails}, {"protocol gate before the Wails build in Release is accepted", releasePath, "build", true, protocolBeforeWails}, {"removed binding gate", testPath, "test", false, func(steps []workflowStep) []workflowStep { - index := findRunStep(steps, bindingsGateRun) + index := scanDeliveryWiring(steps).bindings return slices.Delete(steps, index, index+1) }}, {"conditional binding gate", testPath, "test", false, func(steps []workflowStep) []workflowStep { - steps[findRunStep(steps, bindingsGateRun)].If = "${{ false }}" + steps[scanDeliveryWiring(steps).bindings].If = "${{ false }}" return steps }}, {"continue-on-error binding gate", testPath, "test", false, func(steps []workflowStep) []workflowStep { - steps[findRunStep(steps, bindingsGateRun)].ContinueOnError = &allowedToFail + steps[scanDeliveryWiring(steps).bindings].ContinueOnError = &allowedToFail + return steps + }}, + {"continue-on-error false binding gate is accepted", testPath, "test", true, func(steps []workflowStep) []workflowStep { + steps[scanDeliveryWiring(steps).bindings].ContinueOnError = &allowedToFailFalse return steps }}, {"suppressed binding gate", testPath, "test", false, func(steps []workflowStep) []workflowStep { - steps[findRunStep(steps, bindingsGateRun)].Run += " || true" + steps[scanDeliveryWiring(steps).bindings].Run += " || true" return steps }}, {"removed protocol gate", testPath, "test", false, func(steps []workflowStep) []workflowStep { - index := findRunStep(steps, protocolGateRun) + index := scanDeliveryWiring(steps).protocol return slices.Delete(steps, index, index+1) }}, {"binding gate before the Wails build", testPath, "test", false, func(steps []workflowStep) []workflowStep { - bindings, wails := findRunStep(steps, bindingsGateRun), findContainingRunStep(steps, wailsBuildRun) - steps[bindings], steps[wails] = steps[wails], steps[bindings] + wiring := scanDeliveryWiring(steps) + steps[wiring.bindings], steps[wiring.wails] = steps[wiring.wails], steps[wiring.bindings] return steps }}, {"removed release binding gate", releasePath, "build", false, func(steps []workflowStep) []workflowStep { - index := findRunStep(steps, bindingsGateRun) + index := scanDeliveryWiring(steps).bindings return slices.Delete(steps, index, index+1) }}, {"continue-on-error release protocol gate", releasePath, "build", false, func(steps []workflowStep) []workflowStep { - steps[findRunStep(steps, protocolGateRun)].ContinueOnError = &allowedToFail + steps[scanDeliveryWiring(steps).protocol].ContinueOnError = &allowedToFail return steps }}, {"suppressed release protocol gate", releasePath, "build", false, func(steps []workflowStep) []workflowStep { - steps[findRunStep(steps, protocolGateRun)].Run += " || true" + steps[scanDeliveryWiring(steps).protocol].Run += " || true" return steps }}, {"release binding gate after packaging", releasePath, "build", false, func(steps []workflowStep) []workflowStep { - index := findRunStep(steps, bindingsGateRun) + index := scanDeliveryWiring(steps).bindings step := steps[index] return append(slices.Delete(steps, index, index+1), step) }}, {"release binding gate before the Wails build", releasePath, "build", false, func(steps []workflowStep) []workflowStep { - bindings, wails := findRunStep(steps, bindingsGateRun), findContainingRunStep(steps, wailsBuildRun) - steps[bindings], steps[wails] = steps[wails], steps[bindings] + wiring := scanDeliveryWiring(steps) + steps[wiring.bindings], steps[wiring.wails] = steps[wiring.wails], steps[wiring.bindings] return steps }}, {"release Wails build allowed to fail", releasePath, "build", false, func(steps []workflowStep) []workflowStep { - steps[findContainingRunStep(steps, wailsBuildRun)].ContinueOnError = &allowedToFail + steps[scanDeliveryWiring(steps).wails].ContinueOnError = &allowedToFail return steps }}, } { t.Run(mutation.name, func(t *testing.T) { steps := mutation.mutate(parsedSteps(t, mutation.path, mutation.job)) - err := checkDeliveryGates(steps) + err := checkDeliveryGates(scanDeliveryWiring(steps)) if mutation.accept { - if err != nil { - t.Fatalf("accepted workflow mutation reported %v", err) - } + testassert.NoError(t, err) return } - if err == nil { - t.Fatal("mutated workflow was accepted") - } + testassert.Require(t, err != nil, "mutated workflow was accepted") }) } } diff --git a/protocol/mounted_test.go b/protocol/mounted_test.go index 419e876d..19ffb314 100644 --- a/protocol/mounted_test.go +++ b/protocol/mounted_test.go @@ -12,6 +12,7 @@ import ( "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/runtime" ) @@ -32,11 +33,13 @@ const mountedDiscoveryEnv = "LIGHTCODE_PROTOCOL_DISCOVERY" func writeMountedFile(t *testing.T, path, content string) { t.Helper() - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatalf("create directory for %s: %v", path, err) + { + err := os.MkdirAll(filepath.Dir(path), 0o700) + testassert.Require(t, err == nil, "create directory for %s: %v", path, err) } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatalf("write %s: %v", path, err) + { + err := os.WriteFile(path, []byte(content), 0o600) + testassert.Require(t, err == nil, "write %s: %v", path, err) } } @@ -46,9 +49,7 @@ func writeMountedFile(t *testing.T, path, content string) { func isolateMountedBundledCredentials(t *testing.T) { t.Helper() entries, err := os.ReadDir(filepath.Join("..", "internal", "catalog", "builtin")) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue @@ -96,9 +97,7 @@ func mountedReporterPlugin() runtime.Plugin { // transport tests. func TestMountedGeneratedTypeScriptClient(t *testing.T) { frontendDir, err := filepath.Abs(filepath.Join("..", "frontend")) - if err != nil { - t.Fatalf("resolve frontend directory: %v", err) - } + testassert.NoError(t, err) vitest := filepath.Join(frontendDir, "node_modules", ".bin", "vitest") if _, err := os.Stat(vitest); err != nil { t.Skipf("frontend toolchain is not installed: %v", err) @@ -119,35 +118,27 @@ func TestMountedGeneratedTypeScriptClient(t *testing.T) { ConfigPath: configPath, Plugins: append(builtin.Plugins(), mountedReporterPlugin()), }) - if err != nil { - t.Fatalf("runtime.Open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := owner.Close(context.Background()); err != nil { - t.Errorf("runtime.Close: %v", err) + { + err := owner.Close(context.Background()) + testassert.Check(t, err == nil, "runtime.Close: %v", err) } }() server, err := owner.OpenProtocol(context.Background()) - if err != nil { - t.Fatalf("OpenProtocol: %v", err) - } + testassert.NoError(t, err) record := filepath.Join(t.TempDir(), "discovery.json") - if err := server.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, server.PublishDiscovery(record)) recordBytes, err := os.ReadFile(record) - if err != nil { - t.Fatalf("read discovery record: %v", err) - } + testassert.NoError(t, err) var discovery struct { Credential string `json:"credential"` } - if err := json.Unmarshal(recordBytes, &discovery); err != nil { - t.Fatalf("decode discovery record: %v", err) - } - if info, err := os.Stat(record); err != nil || info.Mode().Perm() != 0o600 { - t.Fatalf("discovery record mode = %v (%v), want 0600", info, err) + testassert.NoError(t, json.Unmarshal(recordBytes, &discovery)) + { + info, err := os.Stat(record) + testassert.Require(t, err == nil && info.Mode().Perm() == 0o600, "discovery record mode = %v (%v), want 0600", info, err) } // The generated TypeScript client: one Node process reads the discovery @@ -166,13 +157,7 @@ func TestMountedGeneratedTypeScriptClient(t *testing.T) { // The credential guard runs before any diagnostic prints the output, so a // failing process that echoes the credential is reported as a leak and // never echoed itself. - if strings.Contains(string(output), discovery.Credential) { - t.Fatal("the mounted generated TypeScript client output contains the credential") - } - if err != nil { - t.Fatalf("mounted generated TypeScript client: %v\n%s", err, output) - } - if !strings.Contains(string(output), "1 passed") { - t.Fatalf("mounted TypeScript row did not pass:\n%s", output) - } + testassert.Require(t, !strings.Contains(string(output), discovery.Credential), "the mounted generated TypeScript client output contains the credential") + testassert.Require(t, err == nil, "mounted generated TypeScript client: %v\n%s", err, output) + testassert.Require(t, strings.Contains(string(output), "1 passed"), "mounted TypeScript row did not pass:\n%s", output) } diff --git a/protocol/oapi-codegen.yaml b/protocol/oapi-codegen.yaml index e3dd5023..2513dbf6 100644 --- a/protocol/oapi-codegen.yaml +++ b/protocol/oapi-codegen.yaml @@ -3,3 +3,5 @@ generate: models: true std-http-server: true client: true +compatibility: + preserve-original-operation-id-casing-in-embedded-spec: true diff --git a/protocol/protocol.gen.go b/protocol/protocol.gen.go index 6cde9e6b..4077f177 100644 --- a/protocol/protocol.gen.go +++ b/protocol/protocol.gen.go @@ -2,7 +2,7 @@ // Package protocol provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.8.0 DO NOT EDIT. +// Code generated by github.com/MMinasyan/lightcode version (devel) DO NOT EDIT. package protocol import ( diff --git a/readme_test.go b/readme_test.go index 39eed877..9b671a7a 100644 --- a/readme_test.go +++ b/readme_test.go @@ -4,20 +4,16 @@ import ( "os" "strings" "testing" + + "github.com/MMinasyan/lightcode/internal/testassert" ) func TestREADMEConfigExamplesDoNotUseRemovedModelFields(t *testing.T) { data, err := os.ReadFile("README.md") - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) text := string(data) for _, field := range []string{`"default_model"`, `"summarizer_model"`, `"subagents.model"`} { - if strings.Contains(text, field) { - t.Fatalf("README.md must not advertise removed model ownership field %s", field) - } - } - if !strings.Contains(text, `"primary"`) || !strings.Contains(text, `"model": "openrouter/z-ai/glm-5.1"`) { - t.Fatal("README.md must show primary.model in agents.json for model selection") + testassert.Require(t, !strings.Contains(text, field), "README.md must not advertise removed model ownership field %s", field) } + testassert.Require(t, strings.Contains(text, `"primary"`) && strings.Contains(text, `"model": "openrouter/z-ai/glm-5.1"`), "README.md must show primary.model in agents.json for model selection") } diff --git a/runtime/adaptation_test.go b/runtime/adaptation_test.go index 4a8b664f..6ab59ef4 100644 --- a/runtime/adaptation_test.go +++ b/runtime/adaptation_test.go @@ -13,6 +13,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/prompt" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -47,9 +48,7 @@ func adaptationPlugin(id, exportID string, scope ScopeKind) Plugin { func TestCompositionRejectsMisplacedModelAdaptationScope(t *testing.T) { for _, scope := range []ScopeKind{ScopeWorkspace, ScopeOperation, ScopeAgent} { _, err := newComposition([]Plugin{adaptationPlugin("adapt", "model_adaptation", scope)}) - if err == nil || !errors.Is(err, ErrComposition) || !strings.Contains(err.Error(), "requires Runtime scope") { - t.Errorf("newComposition with a %s-scoped ModelAdaptation = %v, want a Runtime-scope composition failure", scope, err) - } + testassert.Check(t, err != nil && errors.Is(err, ErrComposition) && strings.Contains(err.Error(), "requires Runtime scope"), "newComposition with a %s-scoped ModelAdaptation = %v, want a Runtime-scope composition failure", scope, err) } } @@ -58,21 +57,15 @@ func TestCompositionRejectsAmbiguousModelAdaptation(t *testing.T) { adaptationPlugin("adapt-a", "model_adaptation_a", ScopeRuntime), adaptationPlugin("adapt-b", "model_adaptation_b", ScopeRuntime), }) - if err == nil || !errors.Is(err, ErrComposition) || !strings.Contains(err.Error(), "declared as runtime.ModelAdaptation") { - t.Fatalf("newComposition with two ModelAdaptation exports = %v, want an ambiguity composition failure", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrComposition) && strings.Contains(err.Error(), "declared as runtime.ModelAdaptation"), "newComposition with two ModelAdaptation exports = %v, want an ambiguity composition failure", err) } func TestCompositionSelectsSingleModelAdaptationAndLeavesOtherExportsUnselected(t *testing.T) { c, err := newComposition([]Plugin{ adaptationPlugin("adapt", "model_adaptation", ScopeRuntime), }) - if err != nil { - t.Fatalf("newComposition: %v", err) - } - if c.modelAdaptation != "model_adaptation" { - t.Fatalf("composition adaptation export = %q, want model_adaptation", c.modelAdaptation) - } + testassert.NoError(t, err) + testassert.Equal(t, c.modelAdaptation, "model_adaptation") // A plugin exporting ModelAdaptation plus another capability: both exports // stay in the ordinary capability universe for explicit selection, but // only the ModelAdaptation ID becomes the composition's default. @@ -87,17 +80,11 @@ func TestCompositionSelectsSingleModelAdaptationAndLeavesOtherExportsUnselected( return Instance{Values: map[string]any{"model_adaptation": &fakeAdaptation{}, "extra": "extra"}}, nil }, }}) - if err != nil { - t.Fatalf("newComposition(mixed): %v", err) - } - if mixed.modelAdaptation != "model_adaptation" { - t.Fatalf("mixed composition adaptation export = %q, want model_adaptation", mixed.modelAdaptation) - } - if !reflect.DeepEqual(mixed.capabilityIDs, []string{"model_adaptation", "extra"}) { - t.Fatalf("capability universe = %q, want both exports selectable", mixed.capabilityIDs) - } - if got := defaultCapabilityIDsFor(t, mixed); !reflect.DeepEqual(got, []string{"model_adaptation"}) { - t.Fatalf("default capability list for the mixed plugin = %q, want [model_adaptation] only", got) + testassert.NoError(t, err) + testassert.Require(t, mixed.modelAdaptation == "model_adaptation" && reflect.DeepEqual(mixed.capabilityIDs, []string{"model_adaptation", "extra"}), "got %v, want %v; capability universe = %q, want both exports selectable", mixed.modelAdaptation, "model_adaptation", mixed.capabilityIDs) + { + got := defaultCapabilityIDsFor(t, mixed) + testassert.Require(t, reflect.DeepEqual(got, []string{"model_adaptation"}), "default capability list for the mixed plugin = %q, want [model_adaptation] only", got) } } @@ -112,13 +99,15 @@ func defaultCapabilityIDsFor(t *testing.T, c *composition) []string { func TestCompositionAdaptationDefaultProjection(t *testing.T) { // One provider: the default list is exactly the single export ID. c := mustComposition(t, adaptationPlugin("adapt", "model_adaptation", ScopeRuntime)) - if got := defaultCapabilityIDsFor(t, c); !reflect.DeepEqual(got, []string{"model_adaptation"}) { - t.Fatalf("default capability list = %q, want [model_adaptation]", got) + { + got := defaultCapabilityIDsFor(t, c) + testassert.Require(t, reflect.DeepEqual(got, []string{"model_adaptation"}), "default capability list = %q, want [model_adaptation]", got) } // No provider: empty defaults, so every definition keeps its baseline. plain := mustComposition(t, servicePlugin("alpha", nil, nil)) - if got := defaultCapabilityIDsFor(t, plain); got != nil { - t.Fatalf("default capability list without adaptation = %q, want empty", got) + { + got := defaultCapabilityIDsFor(t, plain) + testassert.Require(t, got == nil, "default capability list without adaptation = %q, want empty", got) } } @@ -132,20 +121,14 @@ func TestConfigurationServiceProjectsDefaultCapabilities(t *testing.T) { }`) svc := h.service(context.Background(), adaptationPlugin("adaptation", "model_adaptation", ScopeRuntime)) snapshot, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish: %v", err) - } + testassert.NoError(t, err) want := []string{"model_adaptation"} for _, def := range snapshot.snapshot.definitions { switch def.Name { case "clear": - if def.Capabilities != nil { - t.Fatalf("%s capabilities = %q, want the explicit empty clear", def.Name, def.Capabilities) - } + testassert.Require(t, def.Capabilities == nil, "%s capabilities = %q, want the explicit empty clear", def.Name, def.Capabilities) default: - if !reflect.DeepEqual(def.Capabilities, want) { - t.Fatalf("%s capabilities = %q, want the projected default %q", def.Name, def.Capabilities, want) - } + testassert.Require(t, reflect.DeepEqual(def.Capabilities, want), "%s capabilities = %q, want the projected default %q", def.Name, def.Capabilities, want) } } } @@ -215,19 +198,13 @@ func TestComposeSurfacePromptSizesSkipRulesUnderNone(t *testing.T) { r := req r.promptSize = size result, _, err := composeSurface(r) - if err != nil { - t.Fatalf("composeSurface(%s): %v", size, err) - } + testassert.Require(t, err == nil, "composeSurface(%s): %v", size, err) direct := prompt.NewService(req.home).Assemble(req.workspace, req.sessionStart, prompt.Spec{Size: size}) - if result.Prompt != direct.Prompt { - t.Fatalf("composeSurface(%s) prompt diverged from the direct assembly", size) - } + testassert.Require(t, result.Prompt == direct.Prompt, "composeSurface(%s) prompt diverged from the direct assembly", size) if size == prompt.SizeNone { // None skips the rules reads: no warnings even though no rules // file exists under the isolated home. - if len(result.Warnings) != 0 { - t.Fatalf("none-size warnings = %#v, want none", result.Warnings) - } + testassert.Require(t, len(result.Warnings) == 0, "none-size warnings = %#v, want none", result.Warnings) } else if len(result.Warnings) != 1 || result.Warnings[0].Kind != prompt.WarnRulesNotFound { t.Fatalf("%s-size warnings = %#v, want the rules_not_found warning", size, result.Warnings) } @@ -236,43 +213,23 @@ func TestComposeSurfacePromptSizesSkipRulesUnderNone(t *testing.T) { func TestComposeSurfaceRulesPrecedenceAndOverrides(t *testing.T) { home := t.TempDir() - if err := os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(home, ".lightcode", "AGENTS.md"), []byte("GLOBAL CONTENT"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(home, ".lightcode"), 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(home, ".lightcode", "AGENTS.md"), []byte("GLOBAL CONTENT"), 0o600)) workspace := t.TempDir() - if err := os.WriteFile(filepath.Join(workspace, "AGENTS.md"), []byte("PROJECT CONTENT"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "AGENTS.md"), []byte("PROJECT CONTENT"), 0o600)) req := baseSurfaceRequest(t) req.home, req.workspace = home, workspace result, _, err := composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if !strings.Contains(result.Prompt, "GLOBAL CONTENT") || !strings.Contains(result.Prompt, "PROJECT CONTENT") { - t.Fatal("the composed prompt lost a rules layer") - } - if strings.Index(result.Prompt, "GLOBAL CONTENT") >= strings.Index(result.Prompt, "PROJECT CONTENT") { - t.Fatal("project rules did not render after global rules") - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result.Prompt, "GLOBAL CONTENT") && strings.Contains(result.Prompt, "PROJECT CONTENT"), "the composed prompt lost a rules layer") + testassert.Require(t, strings.Index(result.Prompt, "GLOBAL CONTENT") < strings.Index(result.Prompt, "PROJECT CONTENT"), "project rules did not render after global rules") // A user heading overrides the built-in section while the rest of the // assembly is unchanged. - if err := os.WriteFile(filepath.Join(workspace, "AGENTS.md"), []byte("## Tone\n\nProject tone rule."), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "AGENTS.md"), []byte("## Tone\n\nProject tone rule."), 0o600)) result, _, err = composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if strings.Contains(result.Prompt, "Be direct and concise") { - t.Fatal("the built-in tone section survived a user tone heading override") - } - if !strings.Contains(result.Prompt, "Project tone rule.") { - t.Fatal("the overriding project tone rule is missing") - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(result.Prompt, "Be direct and concise"), "the built-in tone section survived a user tone heading override") + testassert.Require(t, strings.Contains(result.Prompt, "Project tone rule."), "the overriding project tone rule is missing") } func TestComposeSurfaceBlocksAndAdditionsRender(t *testing.T) { @@ -283,15 +240,9 @@ func TestComposeSurfaceBlocksAndAdditionsRender(t *testing.T) { Additions: map[string]string{"safety": "ADDITION SAFETY"}, }} result, _, err := composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if !strings.Contains(result.Prompt, "COACHING BLOCK") || !strings.Contains(result.Prompt, "ADDITION SAFETY") { - t.Fatal("the adaptation blocks or additions did not render") - } - if strings.Index(result.Prompt, "COACHING BLOCK") >= strings.Index(result.Prompt, "## Your Role and Instructions") { - t.Fatal("coaching blocks did not render before the agent body and rules") - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result.Prompt, "COACHING BLOCK") && strings.Contains(result.Prompt, "ADDITION SAFETY"), "the adaptation blocks or additions did not render") + testassert.Require(t, strings.Index(result.Prompt, "COACHING BLOCK") < strings.Index(result.Prompt, "## Your Role and Instructions"), "coaching blocks did not render before the agent body and rules") } func TestComposeSurfaceDescriptionReplacements(t *testing.T) { @@ -301,24 +252,16 @@ func TestComposeSurfaceDescriptionReplacements(t *testing.T) { // No adaptation: the bundled default replacement applies. _, advertised, err := composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if advertised[0].Description != "Edit edit_file or write_file via one call." { - t.Fatalf("baseline description = %q, want the bundled default replacement", advertised[0].Description) - } + testassert.NoError(t, err) + testassert.Equal(t, advertised[0].Description, "Edit edit_file or write_file via one call.") // The shipped GPT row's entry override applies instead. req.adaptation = &fakeAdaptation{value: Adaptation{ ToolDescriptionReplacements: map[string]string{"": "apply_patch"}, }} _, advertised, err = composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if advertised[0].Description != "Edit apply_patch via one call." { - t.Fatalf("adapted description = %q, want the entry override replacement", advertised[0].Description) - } + testassert.NoError(t, err) + testassert.Equal(t, advertised[0].Description, "Edit apply_patch via one call.") } func TestComposeSurfaceAdvertisement(t *testing.T) { @@ -326,21 +269,19 @@ func TestComposeSurfaceAdvertisement(t *testing.T) { // Unselected: only the available, non-hidden tool is advertised. _, advertised, err := composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if names := advertisedNames(advertised); !reflect.DeepEqual(names, []string{"read_file"}) { - t.Fatalf("baseline advertisement = %q, want [read_file]", names) + testassert.NoError(t, err) + { + names := advertisedNames(advertised) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file"}), "baseline advertisement = %q, want [read_file]", names) } // The hidden tool appears only through the include list. req.adaptation = &fakeAdaptation{value: Adaptation{IncludeTools: []string{"apply_patch"}}} _, advertised, err = composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if names := advertisedNames(advertised); !reflect.DeepEqual(names, []string{"read_file", "apply_patch"}) { - t.Fatalf("included advertisement = %q, want the declaration order", names) + testassert.NoError(t, err) + { + names := advertisedNames(advertised) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file", "apply_patch"}), "included advertisement = %q, want the declaration order", names) } // Exclusion always wins over inclusion. @@ -349,11 +290,10 @@ func TestComposeSurfaceAdvertisement(t *testing.T) { IncludeTools: []string{"read_file", "apply_patch"}, }} _, advertised, err = composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if names := advertisedNames(advertised); !reflect.DeepEqual(names, []string{"apply_patch"}) { - t.Fatalf("exclusion-wins advertisement = %q, want [apply_patch]", names) + testassert.NoError(t, err) + { + names := advertisedNames(advertised) + testassert.Require(t, reflect.DeepEqual(names, []string{"apply_patch"}), "exclusion-wins advertisement = %q, want [apply_patch]", names) } } @@ -370,21 +310,19 @@ func TestComposeSurfaceReadonlyCannotRegainMutationTools(t *testing.T) { IncludeTools: []string{"write_file", "edit_file", "apply_patch"}, }} _, advertised, err := composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if names := advertisedNames(advertised); !reflect.DeepEqual(names, []string{"read_file"}) { - t.Fatalf("readonly advertisement = %q, want only the eligible read_file", names) + testassert.NoError(t, err) + { + names := advertisedNames(advertised) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file"}), "readonly advertisement = %q, want only the eligible read_file", names) } // The nearest sibling: with a write dir the tools are eligible and the // include surfaces the hidden one. req.constraints = ToolConstraints{Readonly: true, WriteDir: "/tmp/plan"} _, advertised, err = composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if names := advertisedNames(advertised); !reflect.DeepEqual(names, []string{"read_file", "write_file", "edit_file", "apply_patch"}) { - t.Fatalf("write-dir advertisement = %q, want every eligible tool in declaration order", names) + testassert.NoError(t, err) + { + names := advertisedNames(advertised) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file", "write_file", "edit_file", "apply_patch"}), "write-dir advertisement = %q, want every eligible tool in declaration order", names) } } @@ -392,24 +330,19 @@ func TestComposeSurfaceDescribeFailureAborts(t *testing.T) { req := baseSurfaceRequest(t) req.toolSpecs = []CapabilitySpec{failingToolSpec("broken")} _, advertised, err := composeSurface(req) - if err == nil || !strings.Contains(err.Error(), "broken describe failure") { - t.Fatalf("composeSurface = (%#v, %v), want the describe error", advertised, err) - } - if advertised != nil { - t.Fatalf("a failed description still advertised %d tools", len(advertised)) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "broken describe failure"), "composeSurface = (%#v, %v), want the describe error", advertised, err) + testassert.Require(t, advertised == nil, "a failed description still advertised %d tools", len(advertised)) } func TestComposeSurfaceResolvesExactlyOnce(t *testing.T) { req := baseSurfaceRequest(t) fake := &fakeAdaptation{} req.adaptation = fake - if _, _, err := composeSurface(req); err != nil { - t.Fatalf("composeSurface: %v", err) - } - if fake.calls != 1 { - t.Fatalf("Resolve calls = %d, want exactly 1", fake.calls) + { + _, _, err := composeSurface(req) + testassert.NoError(t, err) } + testassert.Equal(t, fake.calls, 1) if fake.ref != req.modelRef { t.Fatalf("Resolve received %s, want %s", fake.ref.String(), req.modelRef.String()) } @@ -419,12 +352,8 @@ func TestComposeSurfaceResolveErrorPropagates(t *testing.T) { req := baseSurfaceRequest(t) req.adaptation = &fakeAdaptation{err: errors.New("adaptation unavailable")} result, advertised, err := composeSurface(req) - if err == nil || !strings.Contains(err.Error(), "adaptation unavailable") { - t.Fatalf("composeSurface = (%+v, %v), want the Resolve error", result, err) - } - if advertised != nil { - t.Fatalf("a failed Resolve still advertised %d tools", len(advertised)) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "adaptation unavailable"), "composeSurface = (%+v, %v), want the Resolve error", result, err) + testassert.Require(t, advertised == nil, "a failed Resolve still advertised %d tools", len(advertised)) } func TestComposeSurfaceUnselectedAdaptationKeepsBaselinePrompt(t *testing.T) { @@ -432,15 +361,12 @@ func TestComposeSurfaceUnselectedAdaptationKeepsBaselinePrompt(t *testing.T) { start := time.Now().Add(-time.Hour) req.sessionStart = start result, advertised, err := composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } + testassert.NoError(t, err) direct := prompt.NewService(req.home).Assemble(req.workspace, start, prompt.Spec{Size: prompt.SizeFull}) - if result.Prompt != direct.Prompt { - t.Fatal("the unselected adaptation changed the baseline prompt") - } - if names := advertisedNames(advertised); !reflect.DeepEqual(names, []string{"read_file"}) { - t.Fatalf("baseline advertisement = %q, want the full eligible surface", names) + testassert.Require(t, result.Prompt == direct.Prompt, "the unselected adaptation changed the baseline prompt") + { + names := advertisedNames(advertised) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file"}), "baseline advertisement = %q, want the full eligible surface", names) } } @@ -449,34 +375,22 @@ func TestComposeSurfaceZeroSessionStartSamplesComposeTime(t *testing.T) { before := time.Now().Add(-time.Second) result, _, err := composeSurface(req) after := time.Now().Add(time.Second) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } + testassert.NoError(t, err) const marker = "Session started: " i := strings.Index(result.Prompt, marker) - if i < 0 { - t.Fatalf("the composed prompt lost the session start: %q", result.Prompt) - } + testassert.Require(t, i >= 0, "the composed prompt lost the session start: %q", result.Prompt) line := result.Prompt[i+len(marker):] if j := strings.IndexByte(line, '\n'); j >= 0 { line = line[:j] } sampled, err := time.ParseInLocation("2006-01-02 15:04:05 MST", line, time.Local) - if err != nil { - t.Fatalf("parsing the sampled session start %q: %v", line, err) - } - if sampled.Before(before) || sampled.After(after) { - t.Fatalf("sampled session start = %s, want a compose-time value within [%s, %s]", sampled, before, after) - } + testassert.Require(t, err == nil, "parsing the sampled session start %q: %v", line, err) + testassert.Require(t, !sampled.Before(before) && !sampled.After(after), "sampled session start = %s, want a compose-time value within [%s, %s]", sampled, before, after) // An explicit session start is used verbatim. req.sessionStart = time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) result, _, err = composeSurface(req) - if err != nil { - t.Fatalf("composeSurface: %v", err) - } - if !strings.Contains(result.Prompt, "Session started: 2026-01-02 03:04:05 UTC") { - t.Fatalf("the explicit session start did not render verbatim: %q", result.Prompt) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(result.Prompt, "Session started: 2026-01-02 03:04:05 UTC"), "the explicit session start did not render verbatim: %q", result.Prompt) } // TestComposeSurfaceExcludesLineageUnavailableTools proves the lineage @@ -490,21 +404,19 @@ func TestComposeSurfaceExcludesLineageUnavailableTools(t *testing.T) { req.toolSpecs = append(req.toolSpecs, lineageToolSpec("task")) _, root, err := composeSurface(req) - if err != nil { - t.Fatalf("root composeSurface: %v", err) - } - if names := advertisedNames(root); !reflect.DeepEqual(names, []string{"read_file", "task"}) { - t.Fatalf("root advertisement = %q, want the lineage-gated tool present alongside its sibling", names) + testassert.NoError(t, err) + { + names := advertisedNames(root) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file", "task"}), "root advertisement = %q, want the lineage-gated tool present alongside its sibling", names) } childReq := req childReq.identity = harness.SessionIdentity{SessionID: "child-session", ParentSessionID: "root-session"} _, child, err := composeSurface(childReq) - if err != nil { - t.Fatalf("child composeSurface: %v", err) - } - if names := advertisedNames(child); !reflect.DeepEqual(names, []string{"read_file"}) { - t.Fatalf("child advertisement = %q, want the lineage-gated tool excluded", names) + testassert.NoError(t, err) + { + names := advertisedNames(child) + testassert.Require(t, reflect.DeepEqual(names, []string{"read_file"}), "child advertisement = %q, want the lineage-gated tool excluded", names) } } diff --git a/runtime/artifacts_test.go b/runtime/artifacts_test.go index 9018dd06..c452f2ba 100644 --- a/runtime/artifacts_test.go +++ b/runtime/artifacts_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) // assertRegistryState asserts the entry's settled state directly: waiting is @@ -44,9 +45,7 @@ func awaitRegistryState(t *testing.T, a *artifactIntervals, key string, wantRefs if held && refs == wantRefs { return } - if time.Now().After(deadline) { - t.Fatalf("the held entry for %q never reached %d references", key, wantRefs) - } + testassert.Require(t, !time.Now().After(deadline), "the held entry for %q never reached %d references", key, wantRefs) time.Sleep(5 * time.Millisecond) } } @@ -59,9 +58,7 @@ func TestArtifactIntervalRegistrySingleHolderAndSharedEntry(t *testing.T) { a := newArtifactIntervals() key := "code/session" releaseHolder, err := a.acquireExecution(context.Background(), key) - if err != nil { - t.Fatalf("first acquire: %v", err) - } + testassert.NoError(t, err) assertRegistryState(t, a, key, 1, 1, true) type waiterResult struct { @@ -79,16 +76,16 @@ func TestArtifactIntervalRegistrySingleHolderAndSharedEntry(t *testing.T) { var releaseWaiter func() select { case res := <-done: - if res.err != nil { // an unexpected waiter failure is asserted, never ignored - t.Fatalf("waiter acquire failed: %v", res.err) - } + // an unexpected waiter failure is asserted, never ignored + testassert.Require(t, res.err == nil, "waiter acquire failed: %v", res.err) releaseWaiter = res.release case <-time.After(10 * time.Second): t.Fatal("the waiter never won the released interval") } - if _, err := a.acquireTry(key); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("contender acquireTry = %v, want the conflict class", err) + { + _, err := a.acquireTry(key) + testassert.Require(t, errors.Is(err, harness.ErrConflict), "contender acquireTry = %v, want the conflict class", err) } assertRegistryState(t, a, key, 1, 1, true) // the contender's failed attempt dropped its one reference @@ -103,15 +100,14 @@ func TestArtifactIntervalRegistryCanceledWaiterDropsReference(t *testing.T) { a := newArtifactIntervals() key := "code/session" releaseHolder, err := a.acquireExecution(context.Background(), key) - if err != nil { - t.Fatalf("first acquire: %v", err) - } + testassert.NoError(t, err) assertRegistryState(t, a, key, 1, 1, true) ctx, cancel := context.WithCancel(context.Background()) cancel() - if _, err := a.acquireExecution(ctx, key); !errors.Is(err, context.Canceled) { - t.Fatalf("canceled acquire = %v, want context.Canceled", err) + { + _, err := a.acquireExecution(ctx, key) + testassert.Require(t, errors.Is(err, context.Canceled), "canceled acquire = %v, want context.Canceled", err) } assertRegistryState(t, a, key, 1, 1, true) // the canceled attempt dropped its one reference releaseHolder() @@ -124,11 +120,10 @@ func TestArtifactIntervalRegistryNonblockingContenderDropsReference(t *testing.T a := newArtifactIntervals() key := "code/session" releaseHolder, err := a.acquireExecution(context.Background(), key) - if err != nil { - t.Fatalf("first acquire: %v", err) - } - if _, err := a.acquireTry(key); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("contender acquireTry = %v, want the conflict class", err) + testassert.NoError(t, err) + { + _, err := a.acquireTry(key) + testassert.Require(t, errors.Is(err, harness.ErrConflict), "contender acquireTry = %v, want the conflict class", err) } assertRegistryState(t, a, key, 1, 1, true) // only the holder's reference remains releaseHolder() @@ -142,16 +137,12 @@ func TestArtifactIntervalRegistryReclaimsAtZeroReferences(t *testing.T) { a := newArtifactIntervals() key := "code/session" release, err := a.acquireExecution(context.Background(), key) - if err != nil { - t.Fatalf("first acquire: %v", err) - } + testassert.NoError(t, err) release() assertRegistryState(t, a, key, 0, 0, false) again, err := a.acquireExecution(context.Background(), key) - if err != nil { - t.Fatalf("second acquire: %v", err) - } + testassert.NoError(t, err) assertRegistryState(t, a, key, 1, 1, true) // the fresh entry's own held/reference state again() } @@ -167,11 +158,10 @@ func TestArtifactIntervalRegistryCanceledWinnerDropsReference(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() release, err := a.acquireExecution(ctx, key) - if release != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("canceled winner returned a lease (%t) with err %v, want no lease and the typed cancellation", release != nil, err) - } + testassert.Require(t, release == nil && errors.Is(err, context.Canceled), "canceled winner returned a lease (%t) with err %v, want no lease and the typed cancellation", release != nil, err) assertRegistryState(t, a, key, 0, 0, false) - if _, err := a.acquireExecution(context.Background(), key); err != nil { - t.Fatalf("later acquire: %v", err) + { + _, err := a.acquireExecution(context.Background(), key) + testassert.NoError(t, err) } } diff --git a/runtime/background_lifecycle_test.go b/runtime/background_lifecycle_test.go index 7881d207..abd73e7d 100644 --- a/runtime/background_lifecycle_test.go +++ b/runtime/background_lifecycle_test.go @@ -13,6 +13,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -32,15 +33,17 @@ const lifecycleAgentsDocument = `{ // test server's keyless local endpoint and carries the hooks plugin section // the selected preparation hook validates. func backgroundLifecycleConfigDocument(endpoint string) string { - return `{"providers":{"prov":{"transport":{"base_url":"` + endpoint + `","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":262144,"max_output_tokens":4096}}}},"plugins":{"hooks":{"tag":"T1"}}}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"m":{"name":"M","context_window":262144,"max_output_tokens":4096}}`) + `},"plugins":{"hooks":{"tag":"T1"}}}` } // newLifecycleID mints one fresh 32-lowercase-hex identity. func newLifecycleID(t *testing.T) string { t.Helper() var buf [16]byte - if _, err := rand.Read(buf[:]); err != nil { - t.Fatalf("random id: %v", err) + { + _, err := rand.Read(buf[:]) + testassert.NoError(t, err) } return hex.EncodeToString(buf[:]) } @@ -257,16 +260,12 @@ func corruptSessionRegister(t *testing.T, store harness.Storage, sessionID strin t.Helper() key := harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession} reg, err := store.ReadRegister(context.Background(), key) - if err != nil { - t.Fatalf("ReadRegister(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadRegister(%s): %v", sessionID, err) err = store.Transact(context.Background(), func(tx harness.Transaction) error { _, err := tx.ReplaceRegister(key, reg.Revision, json.RawMessage(`{"not":"a session register"}`)) return err }) - if err != nil { - t.Fatalf("corrupt register: %v", err) - } + testassert.NoError(t, err) } // --- raw durable-state seeders --- @@ -277,11 +276,12 @@ const lifecycleSeedToolDefinition = `{"name":"echo","description":"echoes","para // storage contract. func lifecycleInsertRegister(t *testing.T, store harness.Storage, key harness.RegisterKey, payload string) { t.Helper() - if err := store.Transact(context.Background(), func(tx harness.Transaction) error { - _, err := tx.InsertRegister(harness.RegisterDraft{Key: key, Payload: json.RawMessage(payload)}) - return err - }); err != nil { - t.Fatalf("insert register %v: %v", key, err) + { + err := store.Transact(context.Background(), func(tx harness.Transaction) error { + _, err := tx.InsertRegister(harness.RegisterDraft{Key: key, Payload: json.RawMessage(payload)}) + return err + }) + testassert.Require(t, err == nil, "insert register %v: %v", key, err) } } @@ -289,11 +289,12 @@ func lifecycleInsertRegister(t *testing.T, store harness.Storage, key harness.Re // storage contract. The addressed Session must already exist. func lifecycleInsertEntry(t *testing.T, store harness.Storage, sessionID, id, operationID string, kind harness.EntryKind, payload string) { t.Helper() - if err := store.Transact(context.Background(), func(tx harness.Transaction) error { - _, err := tx.InsertEntry(harness.EntryDraft{SessionID: sessionID, ID: id, OperationID: operationID, Kind: kind, Payload: json.RawMessage(payload)}) - return err - }); err != nil { - t.Fatalf("insert entry %s: %v", id, err) + { + err := store.Transact(context.Background(), func(tx harness.Transaction) error { + _, err := tx.InsertEntry(harness.EntryDraft{SessionID: sessionID, ID: id, OperationID: operationID, Kind: kind, Payload: json.RawMessage(payload)}) + return err + }) + testassert.Require(t, err == nil, "insert entry %s: %v", id, err) } } @@ -321,22 +322,20 @@ func seedLifecycleChild(t *testing.T, store harness.Storage, parentID, childID, `"state":{"lifecycle":"open","current_agent_type":"worker","current_operation_id":%q,"usage":{"by_model":[]},"last_activity":%q}}`, childID, now, parentID, operationID, now) entryID := newLifecycleID(t) - inputPayload := fmt.Sprintf( - `{"session_id":%q,"entry_id":%q,"operation_id":%q,"origin":"plugin","content":[{"kind":"text","text":"child work"}]}`, - childID, entryID, operationID) - admission := fmt.Sprintf( - `{"session_id":%q,"operation_id":%q,"request_kind":"message",`+ - `"admitted_entry":{"session_id":%q,"entry_id":%q},"agent_type":"worker",`+ - `"execution":{"configuration_revision":"rev-1","model":{"provider":"prov","model":"gpt-x"},`+ - `"context_window":4096,"output_reserve":2048,`+ - `"system_prompt":"system","tools":[%s],"readonly":false,"write_dir":"",`+ - `"compact":{"model":{"provider":"cprov","model":"compact-x"},"context_window":2048,"output_reserve":1024,"system_prompt":"summarize"}},"admitted_at":%q}`, - childID, operationID, childID, entryID, lifecycleSeedToolDefinition, now) - operationPayload := fmt.Sprintf( - `{"admission":%s,"state":{"status":"running","started_at":%q,"pending_tool_calls":[],"usage":{"by_model":[]}}}`, - admission, now) lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: childID, Kind: harness.RegisterSession}, sessionPayload) - lifecycleInsertEntry(t, store, childID, entryID, operationID, harness.EntryInput, inputPayload) + lifecycleInsertEntry(t, store, childID, entryID, operationID, harness.EntryInput, + compactRawInputEntry(childID, entryID, operationID, "plugin", "child work")) + admission := rawMessageAdmission(childID, operationID, entryID, rawAdmissionValues{ + agentType: "worker", + model: usageWireRef{Provider: "prov", Model: "gpt-x"}, + outputReserve: 2048, + systemPrompt: "system", + tools: "[" + lifecycleSeedToolDefinition + "]", + compactModel: usageWireRef{Provider: "cprov", Model: "compact-x"}, + compactPrompt: "summarize", + }) + operationPayload := compactRawOperationRegister(admission, + fmt.Sprintf(`{"status":"running","started_at":%q,"pending_tool_calls":[],"usage":{"by_model":[]}}`, now)) lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: childID, Kind: harness.RegisterOperation, OperationID: operationID}, operationPayload) } @@ -413,9 +412,7 @@ func openBackgroundRuntime(t *testing.T, store harness.Storage, stopper harness. jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper), hookPlugin(hook), lifecycleToolsPlugin(tools)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) if s, ok := stopper.(*lifecycleStopper); ok { s.arm(r.harness) } @@ -476,35 +473,13 @@ func (b *bgRuntime) startJob(sessionID, jobID string) string { return completionID } -// stop stops one Session through the Runtime's admitted-call gate. -func (b *bgRuntime) stop(sessionID string) error { - b.t.Helper() - return b.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - return h.Stop(ctx, sessionID) - }) -} - // submitMode submits one message in the given mode and returns its // disposition. func (b *bgRuntime) submitMode(sessionID, operationID, text string, mode harness.MessageMode) harness.SubmitDisposition { b.t.Helper() - var disposition harness.SubmitDisposition - err := b.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: mode, - }) - if err != nil { - return err - } - disposition = res.Disposition - return nil - }) + res, err := submitRawThroughRuntime(context.Background(), b.r, sessionID, operationID, text, mode) if err != nil { b.t.Fatalf("Submit(%s): %v", operationID, err) } - return disposition + return res.Disposition } diff --git a/runtime/background_lifecycle_tools_test.go b/runtime/background_lifecycle_tools_test.go index 87619650..4dc233de 100644 --- a/runtime/background_lifecycle_tools_test.go +++ b/runtime/background_lifecycle_tools_test.go @@ -13,6 +13,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/config" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/plugins/jobs" "github.com/MMinasyan/lightcode/runtime" @@ -106,9 +107,7 @@ func lastMessageRole(req model.Request) model.Role { func lifecycleFindRuntimeInput(t *testing.T, th *toolsHarness, sessionID, want string) string { t.Helper() entries, err := th.store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.Kind != harness.EntryInput { continue @@ -121,9 +120,7 @@ func lifecycleFindRuntimeInput(t *testing.T, th *toolsHarness, sessionID, want s Text string `json:"text"` } `json:"content"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode input payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) if wire.Origin != "runtime" { continue } @@ -161,9 +158,7 @@ func TestBackgroundLifecycleJobsAcrossSessions(t *testing.T) { // external key is present. func testJobsAcrossSessions(t *testing.T, store harness.Storage) { t.Setenv("LIGHTCODE_BG_EXTERNAL", "external-value") - if err := os.Unsetenv("LIGHTCODE_BG_MANAGED"); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Unsetenv("LIGHTCODE_BG_MANAGED")) background := &harnessBackground{} // The managed key connects through a real manager AFTER the tools // plugin opened: the call-time subprocess-environment producer, not any @@ -219,9 +214,7 @@ func testJobsAcrossSessions(t *testing.T, store harness.Storage) { } } invocation, err := runtime.ConfiguredInvocationForTest(map[string]string{"jobs": `{"max_background_processes":1}`}) - if err != nil { - t.Fatalf("ConfiguredInvocationForTest: %v", err) - } + testassert.NoError(t, err) th := newToolsHarnessWith(t, modelFn, toolsHarnessOpts{ jobsPlugin: jobs.Plugin(), advertise: []string{"run_command", "process"}, @@ -232,34 +225,27 @@ func testJobsAcrossSessions(t *testing.T, store harness.Storage) { }) // The connect happens after the plugin scope opened, before any command // starts. - if err := managed.TrySet("LIGHTCODE_BG_MANAGED", "managed-secret"); err != nil { - t.Fatalf("managed TrySet: %v", err) - } + testassert.NoError(t, managed.TrySet("LIGHTCODE_BG_MANAGED", "managed-secret")) sessionA := th.createSession() sessionB := th.createSession() // Session A starts one real background process and is rejected at the // per-Session limit on the second start. th.submit(sessionA, "op-a1", "start a") - if rec := th.awaitSettled(sessionA, "op-a1"); rec.State.Status != harness.OperationSuccess { - t.Fatalf("op-a1 settled %q: %+v", rec.State.Status, rec.State.Terminal) + { + rec := th.awaitSettled(sessionA, "op-a1") + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "op-a1 settled %q: %+v", rec.State.Status, rec.State.Terminal) } resultsA := th.readToolResults(sessionA) immediate := resultsA["call-a-start"].Content - if !strings.HasPrefix(immediate, "Command running in the background with ID: `") { - t.Fatalf("A immediate result = %q, want the retained background template", immediate) - } + testassert.Require(t, strings.HasPrefix(immediate, "Command running in the background with ID: `"), "A immediate result = %q, want the retained background template", immediate) jobA := backgroundJobID(immediate) - if jobA == "" { - t.Fatalf("A immediate result = %q carries no job identity", immediate) - } + testassert.Require(t, jobA != "", "A immediate result = %q carries no job identity", immediate) th.submitAllowBuffered(sessionA, "op-a2", "limit a") th.awaitSettled(sessionA, "op-a2") resultsA = th.readToolResults(sessionA) wantLimit := "run_command: background start: process: background process limit reached (1/1). Kill existing processes or wait for them to exit" - if got := resultsA["call-a-limit"].Content; got != wantLimit { - t.Fatalf("A limit result = %q, want %q", got, wantLimit) - } + testassert.Equal(t, resultsA["call-a-limit"].Content, wantLimit) // Session B's own start succeeds: the limit is per-Session. th.submit(sessionB, "op-b1", "start b") @@ -267,24 +253,15 @@ func testJobsAcrossSessions(t *testing.T, store harness.Storage) { resultsB := th.readToolResults(sessionB) immediateB := resultsB["call-b-start"].Content jobB := backgroundJobID(immediateB) - if jobB == "" || jobB == jobA { - t.Fatalf("B immediate result = %q carries no distinct job identity", immediateB) - } + testassert.Require(t, jobB != "" && jobB != jobA, "B immediate result = %q carries no distinct job identity", immediateB) th.submitAllowBuffered(sessionB, "op-b2", "list b") th.awaitSettled(sessionB, "op-b2") resultsB = th.readToolResults(sessionB) listB := resultsB["call-b-list"].Content - if !strings.Contains(listB, jobB) || !strings.Contains(listB, "(running for") { - t.Fatalf("B list = %q, want the running line of B's own job %s", listB, jobB) - } - if strings.Contains(listB, jobA) { - t.Fatalf("B list = %q leaks A's job %s", listB, jobA) - } + testassert.Require(t, strings.Contains(listB, jobB) && strings.Contains(listB, "(running for") && !strings.Contains(listB, jobA), "B list = %q, want the running line of B's own job %s; B list = %q leaks A's job %s", listB, jobB, listB, jobA) // Stopping Session A kills only A's job; B's job stays live. - if err := th.h.Stop(context.Background(), sessionA); err != nil { - t.Fatalf("Stop(A): %v", err) - } + testassert.NoError(t, th.h.Stop(context.Background(), sessionA)) completionOp := lifecycleFindRuntimeInput(t, th, sessionA, "Background process "+jobA) th.awaitSettled(sessionA, completionOp) @@ -292,29 +269,24 @@ func testJobsAcrossSessions(t *testing.T, store harness.Storage) { th.submit(sessionB, "op-b3", "list b again") th.awaitSettled(sessionB, "op-b3") resultsB = th.readToolResults(sessionB) - if listAfter := resultsB["call-b-list-again"].Content; !strings.Contains(listAfter, jobB) || !strings.Contains(listAfter, "(running for") { - t.Fatalf("B list after A's stop = %q, want B's job %s still running", listAfter, jobB) + { + listAfter := resultsB["call-b-list-again"].Content + testassert.Require(t, strings.Contains(listAfter, jobB) && strings.Contains(listAfter, "(running for"), "B list after A's stop = %q, want B's job %s still running", listAfter, jobB) } th.submitAllowBuffered(sessionB, "op-b4", "read b") rec := th.awaitSettled(sessionB, "op-b4") - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("op-b4 settled %q: %+v", rec.State.Status, rec.State) - } + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "op-b4 settled %q: %+v", rec.State.Status, rec.State) resultsB = th.readToolResults(sessionB) // The read loop terminated only after its final call observed the real // output, so the last call's result is the readiness proof. got := resultsB[readCallID].Content - if !strings.Contains(got, "b-out|external-value|") || strings.Contains(got, "managed-secret") { - t.Fatalf("%s = %q, want the live job's output with the external key present and the managed key absent", readCallID, got) - } + testassert.Require(t, strings.Contains(got, "b-out|external-value|") && !strings.Contains(got, "managed-secret"), "%s = %q, want the live job's output with the external key present and the managed key absent", readCallID, got) // A's killed job left no records behind. th.submitAllowBuffered(sessionA, "op-a3", "list a") th.awaitSettled(sessionA, "op-a3") resultsA = th.readToolResults(sessionA) - if got := resultsA["call-a-list"].Content; got != "No background processes." { - t.Fatalf("A list after the stop = %q, want the retained empty text", got) - } + testassert.Equal(t, resultsA["call-a-list"].Content, "No background processes.") // The managed shutdown kills B's stray job through the capability's own // scope close (the test's composed-scope cleanup), so the suite leaves no diff --git a/runtime/background_shutdown_test.go b/runtime/background_shutdown_test.go index 0151b9f5..13c44eeb 100644 --- a/runtime/background_shutdown_test.go +++ b/runtime/background_shutdown_test.go @@ -11,6 +11,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" ) // The job-stop seam fixtures: one stub records stop invocations and, when @@ -68,19 +69,16 @@ func startLiveJobRuntime(t *testing.T, store harness.Storage, content string) (* e := newOwnerEnv(t) stopper := &stubStopper{events: e.events, content: content} r, err := e.open(ctx, e.storagePlugin(store), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(ctx, filepath.Join(e.home, "live-job-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) var completionID string - if err := startJobThrough(r, session.Identity.SessionID, "0a1b2c3d", func(_ context.Context, id string) error { - completionID = id // the job stays live: terminal delivery is still owed - return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) + { + err := startJobThrough(r, session.Identity.SessionID, "0a1b2c3d", func(_ context.Context, id string) error { + completionID = id // the job stays live: terminal delivery is still owed + return nil + }) + testassert.NoError(t, err) } stopper.h = r.harness stopper.completionID = completionID @@ -99,9 +97,7 @@ func TestRuntimeJobsCapabilityWiring(t *testing.T) { startFor := func(t *testing.T, e *ownerEnv, r *Runtime, spawned *bool) error { t.Helper() session, err := r.createSession(ctx, filepath.Join(e.home, "wiring-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) return startJobThrough(r, session.Identity.SessionID, "0a1b2c3d", func(context.Context, string) error { *spawned = true return nil @@ -109,26 +105,18 @@ func TestRuntimeJobsCapabilityWiring(t *testing.T) { } assertUnbound := func(t *testing.T, err error, spawned bool) { t.Helper() - if !errors.Is(err, harness.ErrInvalid) || !strings.Contains(err.Error(), "no job stopper configured") { - t.Fatalf("StartJob = %v, want the unbound-seam rejection", err) - } - if spawned { - t.Fatal("spawn ran without a bound seam") - } + testassert.Require(t, errors.Is(err, harness.ErrInvalid) && strings.Contains(err.Error(), "no job stopper configured"), "StartJob = %v, want the unbound-seam rejection", err) + testassert.Require(t, !spawned, "spawn ran without a bound seam") } closeOK := func(t *testing.T, r *Runtime) { t.Helper() - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) } t.Run("absent export leaves the seam nil", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(ctx, e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeOK(t, r) spawned := false assertUnbound(t, startFor(t, e, r, &spawned), spawned) @@ -138,14 +126,10 @@ func TestRuntimeJobsCapabilityWiring(t *testing.T) { e := newOwnerEnv(t) stopper := &stubStopper{events: e.events} r, err := e.open(ctx, e.storagePlugin(storage.NewMemory()), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeOK(t, r) session, err := r.createSession(ctx, filepath.Join(e.home, "wiring-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) var completionID string err = startJobThrough(r, session.Identity.SessionID, "0a1b2c3d", func(_ context.Context, id string) error { completionID = id @@ -154,12 +138,8 @@ func TestRuntimeJobsCapabilityWiring(t *testing.T) { // The bound seam admitted the call: spawn received the reserved // completion identity and its error returns unchanged (the // spawn-failure path finishes the member, so shutdown stops nothing). - if !errors.Is(err, errSpawn) { - t.Fatalf("StartJob = %v, want the spawn error through the bound seam", err) - } - if completionID == "" { - t.Fatal("spawn received no completion identity") - } + testassert.Require(t, errors.Is(err, errSpawn), "StartJob = %v, want the spawn error through the bound seam", err) + testassert.Require(t, completionID != "", "spawn received no completion identity") }) t.Run("a narrower declared type leaves the seam nil", func(t *testing.T) { @@ -173,9 +153,7 @@ func TestRuntimeJobsCapabilityWiring(t *testing.T) { }, } r, err := e.open(ctx, e.storagePlugin(storage.NewMemory()), narrow) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeOK(t, r) spawned := false assertUnbound(t, startFor(t, e, r, &spawned), spawned) @@ -220,17 +198,11 @@ func TestRuntimeShutdownDeliversLiveJobCompletion(t *testing.T) { ctx := context.Background() _, r, store, stopper, sessionID := startLiveJobRuntime(t, storage.NewMemory(), "job stopped") - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - if stopper.deliverErr != nil { - t.Fatalf("stopped job delivery: %v", stopper.deliverErr) - } + testassert.NoError(t, r.Close(ctx)) + testassert.Require(t, stopper.deliverErr == nil, "stopped job delivery: %v", stopper.deliverErr) entries, err := store.ReadEntries(ctx, sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) found := false for _, entry := range entries { if entry.Kind != harness.EntrySignal { @@ -244,23 +216,14 @@ func TestRuntimeShutdownDeliversLiveJobCompletion(t *testing.T) { ID string `json:"id"` } `json:"related_member"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode signal payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) if wire.Signal != "background_completion" { continue } found = true - if wire.Content != "job stopped" { - t.Fatalf("completion signal content = %q, want the delivered %q", wire.Content, "job stopped") - } - if wire.RelatedMember == nil || wire.RelatedMember.Kind != "job" || wire.RelatedMember.ID != "0a1b2c3d" { - t.Fatalf("completion signal member = %+v, want the job member 0a1b2c3d", wire.RelatedMember) - } - } - if !found { - t.Fatal("shutdown with a live job recorded no background_completion signal entry") + testassert.Require(t, wire.Content == "job stopped" && wire.RelatedMember != nil && wire.RelatedMember.Kind == "job" && wire.RelatedMember.ID == "0a1b2c3d", "got %v, want %v; completion signal member = %+v, want the job member 0a1b2c3d", wire.Content, "job stopped", wire.RelatedMember) } + testassert.Require(t, found, "shutdown with a live job recorded no background_completion signal entry") } // TestRuntimeJobStopperDisposesBeforeStorage pins the disposal order of one @@ -278,16 +241,10 @@ func TestRuntimeJobStopperDisposesBeforeStorage(t *testing.T) { t.Run("the stop runs while the scopes are open", func(t *testing.T) { e, r, _, stopper, _ := startLiveJobRuntime(t, storage.NewMemory(), "job stopped") - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - if stopper.deliverErr != nil { - t.Fatalf("stopped job delivery: %v", stopper.deliverErr) - } + testassert.NoError(t, r.Close(ctx)) + testassert.Require(t, stopper.deliverErr == nil, "stopped job delivery: %v", stopper.deliverErr) events := e.events.all() - if !orderedSubset(events, "stopjob", "close:jobs", "close:core") { - t.Fatalf("shutdown events = %v, want the stop, then the jobs capability close, then the storage close", events) - } + testassert.Require(t, orderedSubset(events, "stopjob", "close:jobs", "close:core"), "shutdown events = %v, want the stop, then the jobs capability close, then the storage close", events) }) eachPrepStore(t, func(t *testing.T, store harness.Storage) { @@ -313,10 +270,7 @@ func TestRuntimeJobStopperDisposesBeforeStorage(t *testing.T) { // The committed completion is durable at the frozen return // boundary, while shutdown is still joined behind it. completions := completionsOf(gated, sessionID) - if len(completions) != 1 || completions[0].memberKind != "job" || - completions[0].memberID != "0a1b2c3d" || completions[0].content != "job stopped" { - t.Fatalf("committed completion while held = %+v, want the job 0a1b2c3d with its report", completions) - } + testassert.Require(t, len(completions) == 1 && completions[0].memberKind == "job" && completions[0].memberID == "0a1b2c3d" && completions[0].content == "job stopped", "committed completion while held = %+v, want the job 0a1b2c3d with its report", completions) select { case err := <-closeDone: t.Fatalf("Close returned while the terminal completion transaction was still held (%v)", err) @@ -324,29 +278,22 @@ func TestRuntimeJobStopperDisposesBeforeStorage(t *testing.T) { } // The stop already ran, and no plugin scope Close has started // before the held terminal commit returns. - if n := eventsNamed(e.events.all(), "stopjob"); n != 1 { - t.Fatalf("stopjob events while held = %d, want exactly the one stop", n) - } - if closes := eventsNamedList(e.events.all(), "close"); len(closes) != 0 { - t.Fatalf("scope closes before the held terminal commit = %v, want none", closes) + testassert.Equal(t, eventsNamed(e.events.all(), "stopjob"), 1) + { + closes := eventsNamedList(e.events.all(), "close") + testassert.Require(t, len(closes) == 0, "scope closes before the held terminal commit = %v, want none", closes) } gated.releaseGate() select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("Close never converged after the terminal commit was released") } - if stopper.deliverErr != nil { - t.Fatalf("stopped job delivery: %v", stopper.deliverErr) - } + testassert.Require(t, stopper.deliverErr == nil, "stopped job delivery: %v", stopper.deliverErr) events := e.events.all() - if !orderedSubset(events, "stopjob", "close:jobs", "close:core") { - t.Fatalf("shutdown events = %v, want the stop, then the jobs capability close, then the storage close", events) - } + testassert.Require(t, orderedSubset(events, "stopjob", "close:jobs", "close:core"), "shutdown events = %v, want the stop, then the jobs capability close, then the storage close", events) }) }) } diff --git a/runtime/background_test.go b/runtime/background_test.go index 34a77af6..180f0c98 100644 --- a/runtime/background_test.go +++ b/runtime/background_test.go @@ -11,6 +11,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -24,15 +25,11 @@ func TestBackgroundBridgeMethodsForward(t *testing.T) { store := storage.NewMemory() stopper := &stubStopper{events: e.events} r, err := e.open(ctx, e.storagePlugin(store), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) bridge := &backgroundBridge{h: r.harness} parent, err := r.createSession(ctx, filepath.Join(e.home, "forward-parent"), "solo") - if err != nil { - t.Fatalf("createSession(parent): %v", err) - } + testassert.NoError(t, err) res, err := bridge.LaunchChild(ctx, harness.LaunchChildRequest{ ParentSessionID: parent.Identity.SessionID, AgentType: "solo", @@ -41,45 +38,29 @@ func TestBackgroundBridgeMethodsForward(t *testing.T) { MaxConcurrent: 5, OutputLimit: 4096, }) - if err != nil { - t.Fatalf("LaunchChild: %v", err) - } - if res.ChildSessionID == "" { - t.Fatal("LaunchChild returned no child session id") - } + testassert.NoError(t, err) + testassert.Require(t, res.ChildSessionID != "", "LaunchChild returned no child session id") jobSession, err := r.createSession(ctx, filepath.Join(e.home, "forward-job"), "solo") - if err != nil { - t.Fatalf("createSession(job): %v", err) - } + testassert.NoError(t, err) var completionID string - if err := bridge.StartJob(ctx, jobSession.Identity.SessionID, "0a1b2c3d", func(_ context.Context, id string) error { + testassert.NoError(t, bridge.StartJob(ctx, jobSession.Identity.SessionID, "0a1b2c3d", func(_ context.Context, id string) error { completionID = id return nil - }); err != nil { - t.Fatalf("StartJob: %v", err) - } + })) - if err := bridge.DeliverCompletion(ctx, jobSession.Identity.SessionID, completionID, "job report"); err != nil { - t.Fatalf("DeliverCompletion: %v", err) - } + testassert.NoError(t, bridge.DeliverCompletion(ctx, jobSession.Identity.SessionID, completionID, "job report")) entries, err := store.ReadEntries(ctx, jobSession.Identity.SessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) admitted := false for _, entry := range entries { if entry.Kind == harness.EntryInput && strings.Contains(string(entry.Payload), "job report") { admitted = true } } - if !admitted { - t.Fatal("the forwarded completion delivery left no admitted input entry") - } + testassert.Require(t, admitted, "the forwarded completion delivery left no admitted input entry") - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) } // backgroundProbe records the background services bridge of every prepared @@ -150,45 +131,29 @@ func TestBackgroundBridgeArmingAcrossPublication(t *testing.T) { writeServiceFile(t, agents.PathForConfig(configPath), `{"prober": {"model": "prov/m", "system_prompt": "simple", "tools": ["bg_probe"]}}`) workspace := filepath.Join(home, "probe-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) probe := &backgroundProbe{} r, err := Open(ctx, Options{DataDir: dataDir, ConfigPath: configPath, Plugins: []Plugin{ coreStoragePlugin(store), backgroundProbePlugin(probe), }}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) // The unarmed-to-armed window ends before publication: construction // and publication observe nothing. - if len(probe.seen) != 0 { - t.Fatalf("bridge observations across construction = %d, want none before the first admitted call", len(probe.seen)) - } + testassert.Equal(t, len(probe.seen), 0) session, err := r.createSession(ctx, workspace, "prober") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "probe") awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) - if len(probe.seen) == 0 { - t.Fatal("no prepared call observed the bridge") - } + testassert.Require(t, len(probe.seen) != 0, "no prepared call observed the bridge") for i, background := range probe.seen { - if background == nil { - t.Fatalf("prepared call %d carried no background services bridge", i) - } + testassert.Require(t, background != nil, "prepared call %d carried no background services bridge", i) // Complete after publication: the production-armed instance // reaches the Harness (an unknown completion is not new work). - if err := background.DeliverCompletion(ctx, session.Identity.SessionID, "00000000000000000000000000000000", "ignored"); err != nil { - t.Fatalf("armed bridge delivery: %v", err) - } + testassert.NoError(t, background.DeliverCompletion(ctx, session.Identity.SessionID, "00000000000000000000000000000000", "ignored")) } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) } diff --git a/runtime/compact_capture_test.go b/runtime/compact_capture_test.go index 29367546..10cff526 100644 --- a/runtime/compact_capture_test.go +++ b/runtime/compact_capture_test.go @@ -12,6 +12,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -44,27 +45,17 @@ Be specific — include file paths, function names, error messages, line numbers // from one revision, with the compact selection falling back to the // conversation model when the compact type's model does not resolve. -const compactPrepConfigTemplate = `{ - "providers": { - "prov": { - "transport": {"base_url": "ENDPOINT", "api_key_env": "PREP_COMPACT_KEY"}, - "discovery": false, - "models": { - "m": {"name": "M", "context_window": 4096}, - "m2": {"name": "M2", "context_window": 2048, "max_output_tokens": 1000}, - "mzero": {"name": "MZ", "context_window": 0, "max_output_tokens": 500} - } - }, - "p2": { - "transport": {"base_url": "ENDPOINT", "api_key_env": "PREP_COMPACT_ABSENT_KEY"}, - "discovery": false, - "models": { - "m3": {"name": "M3", "context_window": 1024, "max_output_tokens": 200} - } - } - }, - "sessions": {} -}` +// compactPrepConfigDocument is the compaction-capture fixture document: the +// referenced keyed provider beside one provider whose credential stays +// absent, both pointed at the given endpoint. +func compactPrepConfigDocument(endpoint string) string { + return `{"providers":{"prov":` + nominalProvMember(endpoint, "PREP_COMPACT_KEY", + `{"m":{"name":"M","context_window":4096},`+ + `"m2":{"name":"M2","context_window":2048,"max_output_tokens":1000},`+ + `"mzero":{"name":"MZ","context_window":0,"max_output_tokens":500}}`) + + `,"p2":` + nominalProvMember(endpoint, "PREP_COMPACT_ABSENT_KEY", + `{"m3":{"name":"M3","context_window":1024,"max_output_tokens":200}}`) + `},"sessions":{}}` +} // agentsWithCompact appends one compact builtin overlay to the retained // preparation agents document. @@ -79,7 +70,7 @@ func compactPrep(t *testing.T, agentsDoc, endpoint string) (configurationCapture t.Helper() sh := newServiceHarness(t) t.Setenv("PREP_COMPACT_KEY", "compact-secret") - writeServiceFile(t, sh.configPath, strings.Replace(compactPrepConfigTemplate, "ENDPOINT", endpoint, -1)) + writeServiceFile(t, sh.configPath, compactPrepConfigDocument(endpoint)) writeServiceFile(t, agents.PathForConfig(sh.configPath), agentsDoc) owner, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -89,9 +80,7 @@ func compactPrep(t *testing.T, agentsDoc, endpoint string) (configurationCapture ws := newWorkspaceScopes(owner, comp, []*scope{runtimeScope}, obs) svc := newConfigurationService(owner, comp, sh.loader, sh.configPath, obs) captured, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish: %v", err) - } + testassert.NoError(t, err) return captured, newPreparation(svc, comp, runtimeScope, ws, sh.home, nil, nil, nil, nil) } @@ -119,9 +108,7 @@ func compactPrepRequest() harness.PreparationRequest { func compactPrepSelection(t *testing.T, captured configurationCapture, agentName string) selection { t.Helper() agent, err := harness.ResolveAgentType(agentName, captured.snapshot.agentTypes()) - if err != nil { - t.Fatalf("ResolveAgentType(%q): %v", agentName, err) - } + testassert.Require(t, err == nil, "ResolveAgentType(%q): %v", agentName, err) return selection{agent: agent, bindings: Bindings{}, invocation: Invocation{snapshot: captured.snapshot}, credentials: captured.credentials} } @@ -136,10 +123,8 @@ func compactBaseCapture() harness.ExecutionCapture { ContextWindow: 4096, OutputReserve: 131072, Compact: harness.CompactCapture{ - Model: model.ModelRef{Provider: "prov", Model: "m2"}, - ContextWindow: 2048, - OutputReserve: 1000, - SystemPrompt: "summarize", + Model: model.ModelRef{Provider: "prov", Model: "m2"}, + SystemPrompt: "summarize", }, } } @@ -151,90 +136,76 @@ func testToolDefinitionRuntime() model.ToolDefinition { // TestConcretePrepareCompactCapture pins every capture field the compaction // configuration must carry from one revision: the conversation window, the // output reserve with the 131072 fallback, the compact selection with the -// conversation-model default, the compact window fallback, and the compact -// prompt equal to the resolved compact type's Prompt. +// conversation-model default, and the compact prompt equal to the resolved +// compact type's Prompt. func TestConcretePrepareCompactCapture(t *testing.T) { conversationRef := model.ModelRef{Provider: "prov", Model: "m"} m2Ref := model.ModelRef{Provider: "prov", Model: "m2"} cases := []struct { - name string - agentsDoc string - agent string - wantModel model.ModelRef - wantWindow int - wantReserve int - wantCompactRef model.ModelRef - wantCompWindow int - wantCompReserve int + name string + agentsDoc string + agent string + wantModel model.ModelRef + wantWindow int + wantReserve int + wantCompactRef model.ModelRef }{ { - name: "conversation model with the reserve fallback", - agentsDoc: prepAgentsDocument, - agent: "solo", - wantModel: conversationRef, - wantWindow: 4096, - wantReserve: 131072, - wantCompactRef: conversationRef, - wantCompWindow: 4096, - wantCompReserve: 131072, + name: "conversation model with the reserve fallback", + agentsDoc: prepAgentsDocument, + agent: "solo", + wantModel: conversationRef, + wantWindow: 4096, + wantReserve: 131072, + wantCompactRef: conversationRef, }, { - name: "compact model override via the roster", - agentsDoc: agentsWithCompact(`{"model": "prov/m2"}`), - agent: "solo", - wantModel: conversationRef, - wantWindow: 4096, - wantReserve: 131072, - wantCompactRef: m2Ref, - wantCompWindow: 2048, - wantCompReserve: 1000, + name: "compact model override via the roster", + agentsDoc: agentsWithCompact(`{"model": "prov/m2"}`), + agent: "solo", + wantModel: conversationRef, + wantWindow: 4096, + wantReserve: 131072, + wantCompactRef: m2Ref, }, { - name: "compact model that does not resolve falls back", - agentsDoc: agentsWithCompact(`{"model": "prov/absent"}`), - agent: "solo", - wantModel: conversationRef, - wantWindow: 4096, - wantReserve: 131072, - wantCompactRef: conversationRef, - wantCompWindow: 4096, - wantCompReserve: 131072, + name: "compact model that does not resolve falls back", + agentsDoc: agentsWithCompact(`{"model": "prov/absent"}`), + agent: "solo", + wantModel: conversationRef, + wantWindow: 4096, + wantReserve: 131072, + wantCompactRef: conversationRef, }, { - name: "compact key that does not resolve falls back", - agentsDoc: agentsWithCompact(`{"model": "p2/m3"}`), - agent: "solo", - wantModel: conversationRef, - wantWindow: 4096, - wantReserve: 131072, - wantCompactRef: conversationRef, - wantCompWindow: 4096, - wantCompReserve: 131072, + name: "compact key that does not resolve falls back", + agentsDoc: agentsWithCompact(`{"model": "p2/m3"}`), + agent: "solo", + wantModel: conversationRef, + wantWindow: 4096, + wantReserve: 131072, + wantCompactRef: conversationRef, }, { // catalog.Lookup rejects the zero-window model, so the // conversation-model default applies. - name: "compact entry with no positive window falls back", - agentsDoc: agentsWithCompact(`{"model": "prov/mzero"}`), - agent: "solo", - wantModel: conversationRef, - wantWindow: 4096, - wantReserve: 131072, - wantCompactRef: conversationRef, - wantCompWindow: 4096, - wantCompReserve: 131072, + name: "compact entry with no positive window falls back", + agentsDoc: agentsWithCompact(`{"model": "prov/mzero"}`), + agent: "solo", + wantModel: conversationRef, + wantWindow: 4096, + wantReserve: 131072, + wantCompactRef: conversationRef, }, { - name: "conversation reserve from the entry's max output tokens", - agentsDoc: strings.TrimSuffix(prepAgentsDocument, "}") + ",\n \"reservable\": {\"model\": \"prov/m2\", \"system_prompt\": \"simple\", \"tools\": [\"echo\"]}}", - agent: "reservable", - wantModel: m2Ref, - wantWindow: 2048, - wantReserve: 1000, - wantCompactRef: m2Ref, - wantCompWindow: 2048, - wantCompReserve: 1000, + name: "conversation reserve from the entry's max output tokens", + agentsDoc: strings.TrimSuffix(prepAgentsDocument, "}") + ",\n \"reservable\": {\"model\": \"prov/m2\", \"system_prompt\": \"simple\", \"tools\": [\"echo\"]}}", + agent: "reservable", + wantModel: m2Ref, + wantWindow: 2048, + wantReserve: 1000, + wantCompactRef: m2Ref, }, } for _, tc := range cases { @@ -242,33 +213,16 @@ func TestConcretePrepareCompactCapture(t *testing.T) { captured, prep := compactPrep(t, tc.agentsDoc, "https://prov.test/v1") sel := compactPrepSelection(t, captured, tc.agent) capture, opener, err := prep.concretePrepare(context.Background(), compactPrepRequest(), sel) - if err != nil { - t.Fatalf("concretePrepare: %v", err) - } - if opener == nil { - t.Fatalf("concretePrepare returned no opener") - } + testassert.NoError(t, err) + testassert.Require(t, opener != nil, "concretePrepare returned no opener") if capture.Model != tc.wantModel { t.Fatalf("capture Model = %s, want %s", capture.Model.String(), tc.wantModel.String()) } - if capture.ContextWindow != tc.wantWindow { - t.Fatalf("capture ContextWindow = %d, want %d", capture.ContextWindow, tc.wantWindow) - } - if capture.OutputReserve != tc.wantReserve { - t.Fatalf("capture OutputReserve = %d, want %d", capture.OutputReserve, tc.wantReserve) - } + testassert.Require(t, capture.ContextWindow == tc.wantWindow && capture.OutputReserve == tc.wantReserve, "got %v, want %v; got %v, want %v", capture.ContextWindow, tc.wantWindow, capture.OutputReserve, tc.wantReserve) if capture.Compact.Model != tc.wantCompactRef { t.Fatalf("capture Compact.Model = %s, want %s", capture.Compact.Model.String(), tc.wantCompactRef.String()) } - if capture.Compact.ContextWindow != tc.wantCompWindow { - t.Fatalf("capture Compact.ContextWindow = %d, want %d", capture.Compact.ContextWindow, tc.wantCompWindow) - } - if capture.Compact.OutputReserve != tc.wantCompReserve { - t.Fatalf("capture Compact.OutputReserve = %d, want %d", capture.Compact.OutputReserve, tc.wantCompReserve) - } - if capture.Compact.SystemPrompt != retainedSummarizerPrompt { - t.Fatalf("capture Compact.SystemPrompt = %q, want the compact type's retained prompt", capture.Compact.SystemPrompt) - } + testassert.Equal(t, capture.Compact.SystemPrompt, retainedSummarizerPrompt) }) } } @@ -318,9 +272,7 @@ func TestConcreteOpenerCompactModel(t *testing.T) { captured, prep := compactPrep(t, tc.agentsDoc, server.URL) sel := compactPrepSelection(t, captured, "solo") capture, opener, err := prep.concretePrepare(context.Background(), compactPrepRequest(), sel) - if err != nil { - t.Fatalf("concretePrepare: %v", err) - } + testassert.NoError(t, err) exec, err := opener(context.Background(), harness.OperationAdmission{ SessionID: "000000000000000000000000000000aa", OperationID: "op-1", @@ -328,46 +280,27 @@ func TestConcreteOpenerCompactModel(t *testing.T) { Execution: capture, AdmittedAt: time.Now(), }, sel) - if err != nil { - t.Fatalf("opener: %v", err) - } - if exec.CompactModel == nil { - t.Fatalf("opened execution carries no CompactModel callback") - } + testassert.NoError(t, err) + testassert.Require(t, exec.CompactModel != nil, "opened execution carries no CompactModel callback") req, err := model.NewRequest(model.Request{Messages: []model.Message{{ Role: "user", Content: []model.ContentPart{{Kind: model.PartText, Text: "summarize"}}, }}}) - if err != nil { - t.Fatalf("NewRequest: %v", err) - } + testassert.NoError(t, err) stream, err := exec.CompactModel(context.Background(), req) - if err != nil { - t.Fatalf("CompactModel: %v", err) - } + testassert.NoError(t, err) stream.Close() - if len(server.models) != 1 || server.models[0] != tc.wantCompactOn { - t.Fatalf("CompactModel wire models = %v, want exactly one request on %q", server.models, tc.wantCompactOn) - } - if len(server.auths) != 1 || server.auths[0] != "Bearer compact-secret" { - t.Fatalf("CompactModel authorization = %v, want the compact transport's resolved credential", server.auths) - } + testassert.Require(t, len(server.models) == 1 && server.models[0] == tc.wantCompactOn && len(server.auths) == 1 && server.auths[0] == "Bearer compact-secret", "CompactModel wire models = %v, want exactly one request on %q; CompactModel authorization = %v, want the compact transport's resolved credential", server.models, tc.wantCompactOn, server.auths) // The conversation callback rides its own transport in the same // execution; when the compact model is the conversation model the // two closures are built identically over the one shared // transport, so both hit the same wire model and credential. stream, err = exec.Model(context.Background(), req) - if err != nil { - t.Fatalf("Model: %v", err) - } + testassert.NoError(t, err) stream.Close() - if len(server.models) != 2 || server.models[1] != "m" { - t.Fatalf("Model wire models = %v, want the conversation model second", server.models) - } - if capture.Compact.Model == conversationRef && server.auths[1] != server.auths[0] { - t.Fatalf("conversation authorization = %q differs from the compact one %q on the shared-model case", server.auths[1], server.auths[0]) - } + testassert.Require(t, len(server.models) == 2 && server.models[1] == "m", "Model wire models = %v, want the conversation model second", server.models) + testassert.Require(t, capture.Compact.Model != conversationRef || server.auths[1] == server.auths[0], "conversation authorization = %q differs from the compact one %q on the shared-model case", server.auths[1], server.auths[0]) }) } } @@ -382,17 +315,13 @@ func TestValidateHookedCaptureProtectsCompaction(t *testing.T) { // The prompt freedom still holds. prompted := base prompted.SystemPrompt = "replaced prompt" - if err := validateHookedCapture(base, prompted); err != nil { - t.Fatalf("prompt replacement rejected: %v", err) - } + testassert.NoError(t, validateHookedCapture(base, prompted)) // The tool-definition replacement freedom still holds: same names, new // description. tooled := base tooled.Tools = append([]model.ToolDefinition(nil), base.Tools...) tooled.Tools[0].Description = "replaced description" - if err := validateHookedCapture(base, tooled); err != nil { - t.Fatalf("tool definition replacement rejected: %v", err) - } + testassert.NoError(t, validateHookedCapture(base, tooled)) for _, mutation := range []struct { name string @@ -401,15 +330,14 @@ func TestValidateHookedCaptureProtectsCompaction(t *testing.T) { {"context window", func(c *harness.ExecutionCapture) { c.ContextWindow++ }}, {"output reserve", func(c *harness.ExecutionCapture) { c.OutputReserve++ }}, {"compact model", func(c *harness.ExecutionCapture) { c.Compact.Model = model.ModelRef{Provider: "other", Model: "m"} }}, - {"compact context window", func(c *harness.ExecutionCapture) { c.Compact.ContextWindow++ }}, - {"compact output reserve", func(c *harness.ExecutionCapture) { c.Compact.OutputReserve++ }}, {"compact system prompt", func(c *harness.ExecutionCapture) { c.Compact.SystemPrompt = "changed" }}, } { t.Run(mutation.name, func(t *testing.T) { next := base mutation.mutate(&next) - if err := validateHookedCapture(base, next); err == nil { - t.Fatalf("hook changing the %s accepted, want rejection", mutation.name) + { + err := validateHookedCapture(base, next) + testassert.Require(t, err != nil, "hook changing the %s accepted, want rejection", mutation.name) } }) } diff --git a/runtime/compact_lifecycle_test.go b/runtime/compact_lifecycle_test.go index 777b3b93..c56324d9 100644 --- a/runtime/compact_lifecycle_test.go +++ b/runtime/compact_lifecycle_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -26,13 +27,13 @@ import ( // the paging and recovery rows seed with, and the exported-surface pin. // compactObservationConfigDocument configures the conversation model with a -// window the sized overflow input exceeds while the post-compaction -// projection fits, and the compact model as a distinct catalog identity with a -// generous window, so the wire distinguishes the transports by model ID. +// window whose trigger threshold the seed turn's reported usage overflows, +// and the compact model as a distinct catalog identity, so the wire +// distinguishes the transports by model ID. func compactObservationConfigDocument(endpoint string) string { - return `{"providers":{"prov":{"transport":{"base_url":"` + endpoint + `","api_key_env":""},"discovery":false,"models":{` + - `"m":{"name":"M","context_window":8192,"max_output_tokens":4096},` + - `"k":{"name":"K","context_window":32768,"max_output_tokens":4096,"usage_in_stream":true}}}}}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"m":{"name":"M","context_window":8192,"max_output_tokens":4096},`+ + `"k":{"name":"K","context_window":32768,"max_output_tokens":4096,"usage_in_stream":true}}`) + `}}` } // compactObservationAgentsDocument selects the conversation model for the @@ -43,16 +44,10 @@ const compactObservationAgentsDocument = `{ "compact": {"model": "prov/k"} }` -// compactFillerRepeats sizes the overflow input. The repeated unit's pinned -// cl100k_base cost over the bundled data is 8194 tokens for these repetitions -// — 4096 repetitions cost only 4098 — comfortably past the 4096-token trigger -// threshold of the configured window (8192) minus reserve (4096). The -// post-compaction projection stays far below it. -const compactFillerRepeats = 8192 - // TestCompactLifecycleNativeCompactionObservation keeps the Runtime observer -// oracle through the native pipeline: a sized overflowing admission triggers -// one automatic compaction whose piece request reaches the compact model's +// oracle through the native pipeline: the first turn's reported conversation +// usage overflows the captured window, the next admission's checkpoint runs +// one automatic compaction whose summary request reaches the compact model's // transport — distinguished by its model ID, with the captured compact // prompt and no tools — the committed summary turns the conversation // projection, and the observation bus carries only well-scoped facts: the @@ -64,104 +59,87 @@ func TestCompactLifecycleNativeCompactionObservation(t *testing.T) { writeServiceFile(t, e.configPath, compactObservationConfigDocument(e.server.URL)) writeServiceFile(t, agents.PathForConfig(e.configPath), compactObservationAgentsDocument) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) ctx := context.Background() - // The overflow input exceeds the window minus the reserve, while the - // post-compaction projection — the composed prompt plus the summary — - // fits. - overflowInput := strings.Repeat("filler ", compactFillerRepeats) e.server.setScript(func(_ context.Context, body string) []string { if !strings.Contains(body, `"model":"k"`) && !strings.Contains(body, `"model": "k"`) { + if lastUserText(body) == "first question" { + // The seed report: {5000,10} overflows the window 8192 + // minus the reserve 4096. + return append(textTurnEvents("seeded answer"), + `{"usage":{"prompt_tokens":5000,"completion_tokens":10,"total_tokens":5010}}`) + } return nil // conversation turns: the default completed text turn } - return []string{ - `{"choices":[{"delta":{"role":"assistant","content":"native summary one"},"finish_reason":null}]}`, - `{"choices":[{"delta":{},"finish_reason":"stop"}]}`, - `{"usage":{"prompt_tokens":10,"completion_tokens":2,"total_tokens":12}}`, - } + return append(textTurnEvents("native summary one"), + `{"usage":{"prompt_tokens":10,"completion_tokens":2,"total_tokens":12}}`) }) sub, err := r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(ctx, e.dataDir, "tight") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID - submitThroughRuntime(t, r, sessionID, "op-1", overflowInput) - op1 := awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) - if op1.State.Status != harness.OperationSuccess { - t.Fatalf("overflowing operation = %+v, want the compacted success", op1.State) - } - submitConvergedThroughRuntime(t, r, sessionID, "op-2", "second question", harness.OperationSuccess) - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - - // The wire: one compact piece on the compact model, then the two - // conversation requests against the summary projection. - var compactPiece string + submitThroughRuntime(t, r, sessionID, "op-1", "first question") + awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) // the seed turn commits the report + submitThroughRuntime(t, r, sessionID, "op-2", "the overflowing checkpoint") + op2 := awaitOperation(t, r, sessionID, "op-2", harness.OperationSuccess) + testassert.Require(t, op2.State.Status == harness.OperationSuccess, "overflowing operation = %+v, want the compacted success", op2.State) + submitConvergedThroughRuntime(t, r, sessionID, "op-3", "second question", harness.OperationSuccess) + testassert.NoError(t, r.Close(ctx)) + + // The wire: the seed conversation request, one compact summary on + // the compact model, then the two conversation requests against the + // summary projection. + var compactSummary string var conversation []string for i := 0; i < e.server.requests(); i++ { body := e.server.bodyAt(i) doc := decodeWireChatBody(t, body) if doc.Model == "k" { - compactPiece = body + compactSummary = body continue } - if doc.Model != "m" { - t.Fatalf("wire request %d names model %q, want one of the configured identities", i, doc.Model) - } + testassert.Require(t, doc.Model == "m", "wire request %d names model %q, want one of the configured identities", i, doc.Model) conversation = append(conversation, body) } - if compactPiece == "" { - t.Fatal("the compaction never reached the compact model's transport") - } - if len(conversation) != 2 { - t.Fatalf("conversation requests = %d, want the compacted turn plus the later one", len(conversation)) - } - piece := decodeWireChatBody(t, compactPiece) - if len(piece.Tools) != 0 { - t.Fatalf("compact piece advertised %d tools, want none", len(piece.Tools)) - } - if len(piece.Messages) == 0 || piece.Messages[0].Role != "system" { - t.Fatalf("compact piece = %+v, want the leading compact system prompt", piece.Messages) + testassert.Require(t, compactSummary != "", "the compaction never reached the compact model's transport") + testassert.Equal(t, len(conversation), 3) + seed := decodeWireChatBody(t, conversation[0]) + { + roles := wireRoles(seed) + testassert.Require(t, slices.Equal(roles, []string{"system", "user"}), "seed request roles = %v, want the prompt and the first message", roles) } + summary := decodeWireChatBody(t, compactSummary) + testassert.Require(t, len(summary.Tools) == 0 && len(summary.Messages) != 0 && summary.Messages[0].Role == "system", "got %v, want %v; compact summary request = %+v, want the leading compact system prompt", len(summary.Tools), 0, summary.Messages) var systemText string - if err := json.Unmarshal(piece.Messages[0].Content, &systemText); err != nil || systemText != op1.Admission.Execution.Compact.SystemPrompt { - t.Fatalf("compact piece system = %q (err %v), want the captured compact prompt %q", systemText, err, op1.Admission.Execution.Compact.SystemPrompt) - } - if op1.Admission.Execution.Compact.Model != (model.ModelRef{Provider: "prov", Model: "k"}) { - t.Fatalf("captured compact model = %+v, want the overlaid prov/k", op1.Admission.Execution.Compact.Model) + { + err := json.Unmarshal(summary.Messages[0].Content, &systemText) + testassert.Require(t, err == nil && systemText == op2.Admission.Execution.Compact.SystemPrompt, "compact summary system = %q (err %v), want the captured compact prompt %q", systemText, err, op2.Admission.Execution.Compact.SystemPrompt) } - first := decodeWireChatBody(t, conversation[0]) - if roles := wireRoles(first); !slices.Equal(roles, []string{"system", "assistant"}) { - t.Fatalf("post-compaction request roles = %v, want the prompt and the summary alone", roles) + testassert.Require(t, op2.Admission.Execution.Compact.Model == (model.ModelRef{Provider: "prov", Model: "k"}), "captured compact model = %+v, want the overlaid prov/k", op2.Admission.Execution.Compact.Model) + first := decodeWireChatBody(t, conversation[1]) + { + roles := wireRoles(first) + testassert.Require(t, slices.Equal(roles, []string{"system", "assistant"}), "post-compaction request roles = %v, want the prompt and the summary alone", roles) } var summaryText string if len(first.Messages) > 1 { _ = json.Unmarshal(first.Messages[1].Content, &summaryText) } - if !strings.Contains(summaryText, "native summary one") { - t.Fatalf("post-compaction assistant = %q, want the committed summary", summaryText) - } - second := decodeWireChatBody(t, conversation[1]) - if roles := wireRoles(second); !slices.Equal(roles, []string{"system", "assistant", "assistant", "user"}) { - t.Fatalf("second request roles = %v, want the prompt, the summary, the first closing turn and the new message", roles) + testassert.Require(t, strings.Contains(summaryText, "native summary one"), "post-compaction assistant = %q, want the committed summary", summaryText) + second := decodeWireChatBody(t, conversation[2]) + { + roles := wireRoles(second) + testassert.Require(t, slices.Equal(roles, []string{"system", "assistant", "assistant", "user"}), "second request roles = %v, want the prompt, the summary, the first closing turn and the new message", roles) } // The durable state: exactly one committed compaction entry owned by // the overflowing Operation. entries, err := store.ReadEntries(ctx, sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) compactions := 0 for _, entry := range entries { if entry.Kind != harness.EntryCompaction { @@ -172,16 +150,10 @@ func TestCompactLifecycleNativeCompactionObservation(t *testing.T) { OperationID string `json:"operation_id"` Summary string `json:"summary"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode compaction entry: %v", err) - } - if wire.OperationID != "op-1" || wire.Summary != "native summary one" { - t.Fatalf("compaction entry = %+v, want op-1's committed summary", wire) - } - } - if compactions != 1 { - t.Fatalf("compaction entries = %d, want exactly the overflowing Operation's one", compactions) + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) + testassert.Require(t, wire.OperationID == "op-2" && wire.Summary == "native summary one", "compaction entry = %+v, want op-2's committed summary", wire) } + testassert.Equal(t, compactions, 1) // The observation set: every session invalidation names the real // Session, every text progress the real running Operation, and no @@ -191,21 +163,13 @@ func TestCompactLifecycleNativeCompactionObservation(t *testing.T) { switch eventKind(t, event) { case "session_changed": body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) scope, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("session invalidation scope: %v", err) - } - if scope.SessionId != sessionID { - t.Fatalf("session invalidation for %q, want the real session", scope.SessionId) - } + testassert.NoError(t, err) + testassert.Equal(t, scope.SessionId, sessionID) sawInvalidation = true case "text_delta": - if err := assertProgressScopedToOperations(t, event, sessionID, "op-1", "op-2"); err != nil { - t.Fatalf("text progress: %v", err) - } + testassert.NoError(t, assertProgressScopedToOperations(t, event, sessionID, "op-1", "op-2", "op-3")) sawDelta = true case "scope_opened", "scope_closed", "warning_changed", "configuration_changed": // scope lifecycle and publication facts stay allowed beside the passive set @@ -213,9 +177,7 @@ func TestCompactLifecycleNativeCompactionObservation(t *testing.T) { t.Fatalf("unexpected event %s", eventJSON(t, event)) } } - if !sawInvalidation || !sawDelta { - t.Fatalf("observation set: invalidation %v delta %v, want both", sawInvalidation, sawDelta) - } + testassert.Require(t, sawInvalidation && sawDelta, "observation set: invalidation %v delta %v, want both", sawInvalidation, sawDelta) }) } @@ -237,9 +199,7 @@ func assertProgressScopedToOperations(t *testing.T, event Event, sessionID strin return errors.New("progress carries a non-operation scope") } body, err := scope.AsOperationScope() - if err != nil { - t.Fatalf("operation scope: %v", err) - } + testassert.NoError(t, err) if body.SessionId != sessionID { return errors.New("progress carries a foreign session identity") } @@ -251,17 +211,47 @@ func assertProgressScopedToOperations(t *testing.T, event Event, sessionID strin return fmt.Errorf("progress carries operation %q outside %v", body.OperationId, operationIDs) } -// compactRawMessageAdmission builds one message-kind admission payload. -func compactRawMessageAdmission(sessionID, operationID, entryID string) string { +// rawAdmissionValues carries the authored execution values of one raw +// message-kind admission seed. +type rawAdmissionValues struct { + agentType string + model usageWireRef + outputReserve int + systemPrompt string + tools string + compactModel usageWireRef + compactPrompt string +} + +// rawMessageAdmission builds one message-kind admission payload from the +// caller's authored execution values. +func rawMessageAdmission(sessionID, operationID, entryID string, v rawAdmissionValues) string { now := time.Now().UTC().Format(time.RFC3339Nano) return fmt.Sprintf( `{"session_id":%q,"operation_id":%q,"request_kind":"message",`+ - `"admitted_entry":{"session_id":%q,"entry_id":%q},"agent_type":"solo",`+ - `"execution":{"configuration_revision":"rev-1","model":{"provider":"prov","model":"m"},`+ - `"context_window":4096,"output_reserve":1024,`+ - `"system_prompt":"compact-sys","tools":[{"name":"noop","description":"tool noop","parameters":{"type":"object"}}],"readonly":false,"write_dir":"",`+ - `"compact":{"model":{"provider":"prov","model":"m"},"context_window":2048,"output_reserve":1024,"system_prompt":"summarize"}},"admitted_at":%q}`, - sessionID, operationID, sessionID, entryID, now) + `"admitted_entry":{"session_id":%q,"entry_id":%q},"agent_type":%q,`+ + `"execution":{"configuration_revision":"rev-1","model":{"provider":%q,"model":%q},`+ + `"context_window":4096,"output_reserve":%d,`+ + `"system_prompt":%q,"tools":%s,"readonly":false,"write_dir":"",`+ + `"compact":{"model":{"provider":%q,"model":%q},"system_prompt":%q}},"admitted_at":%q}`, + sessionID, operationID, sessionID, entryID, v.agentType, + v.model.Provider, v.model.Model, v.outputReserve, + v.systemPrompt, v.tools, + v.compactModel.Provider, v.compactModel.Model, v.compactPrompt, now) +} + +// compactRawMessageAdmission builds the compact and paging fixtures' +// message-kind admission payload. +func compactRawMessageAdmission(sessionID, operationID, entryID string) string { + return rawMessageAdmission(sessionID, operationID, entryID, rawAdmissionValues{ + agentType: "solo", + model: usageWireRef{Provider: "prov", Model: "m"}, + outputReserve: 1024, + systemPrompt: "compact-sys", + tools: `[{"name":"noop","description":"tool noop","parameters":{"type":"object"}}]`, + compactModel: usageWireRef{Provider: "prov", Model: "m"}, + compactPrompt: "summarize", + }) } func compactRawOperationRegister(admission, state string) string { @@ -274,16 +264,16 @@ func compactRawRunningModelState(now, reserved string) string { now, reserved) } -func compactRawSuccessState(now, settlementEntry, sessionID string) string { +func compactRawSuccessState(now, settlementEntry, sessionID, usage string) string { return fmt.Sprintf( - `{"status":"success","started_at":%q,"settled_at":%q,"pending_tool_calls":[],"usage":{"by_model":[]},"terminal":{"settlement_entry":{"session_id":%q,"entry_id":%q}}}`, - now, now, sessionID, settlementEntry) + `{"status":"success","started_at":%q,"settled_at":%q,"pending_tool_calls":[],"usage":%s,"terminal":{"settlement_entry":{"session_id":%q,"entry_id":%q}}}`, + now, now, usage, sessionID, settlementEntry) } -func compactRawInputEntry(sessionID, entryID, operationID, text string) string { +func compactRawInputEntry(sessionID, entryID, operationID, origin, text string) string { return fmt.Sprintf( - `{"session_id":%q,"entry_id":%q,"operation_id":%q,"origin":"user","content":[{"kind":"text","text":%q}]}`, - sessionID, entryID, operationID, text) + `{"session_id":%q,"entry_id":%q,"operation_id":%q,"origin":%q,"content":[{"kind":"text","text":%q}]}`, + sessionID, entryID, operationID, origin, text) } func compactRawAssistantEntry(sessionID, entryID, operationID, text string) string { @@ -293,10 +283,10 @@ func compactRawAssistantEntry(sessionID, entryID, operationID, text string) stri sessionID, entryID, operationID, text) } -func compactRawSettlementEntry(sessionID, entryID, operationID string) string { +func compactRawSettlementEntry(sessionID, entryID, operationID, extra string) string { return fmt.Sprintf( - `{"session_id":%q,"entry_id":%q,"operation_id":%q,"status":"success"}`, - sessionID, entryID, operationID) + `{"session_id":%q,"entry_id":%q,"operation_id":%q,"status":"success"%s}`, + sessionID, entryID, operationID, extra) } func compactRawCompactionEntry(sessionID, entryID, operationID, summary, boundaryID string) string { @@ -315,11 +305,11 @@ func compactRawSuccessOperation(t *testing.T, store harness.Storage, sessionID, t.Helper() now := time.Now().UTC().Format(time.RFC3339Nano) inputID, assistantID, settlementID := newLifecycleID(t), newLifecycleID(t), newLifecycleID(t) - lifecycleInsertEntry(t, store, sessionID, inputID, operationID, harness.EntryInput, compactRawInputEntry(sessionID, inputID, operationID, inputText)) + lifecycleInsertEntry(t, store, sessionID, inputID, operationID, harness.EntryInput, compactRawInputEntry(sessionID, inputID, operationID, "user", inputText)) lifecycleInsertEntry(t, store, sessionID, assistantID, operationID, harness.EntryAssistant, compactRawAssistantEntry(sessionID, assistantID, operationID, assistantText)) - lifecycleInsertEntry(t, store, sessionID, settlementID, operationID, harness.EntryOperationSettlement, compactRawSettlementEntry(sessionID, settlementID, operationID)) + lifecycleInsertEntry(t, store, sessionID, settlementID, operationID, harness.EntryOperationSettlement, compactRawSettlementEntry(sessionID, settlementID, operationID, "")) lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: operationID}, - compactRawOperationRegister(compactRawMessageAdmission(sessionID, operationID, inputID), compactRawSuccessState(now, settlementID, sessionID))) + compactRawOperationRegister(compactRawMessageAdmission(sessionID, operationID, inputID), compactRawSuccessState(now, settlementID, sessionID, `{"by_model":[]}`))) return assistantID } @@ -347,44 +337,24 @@ func TestCompactLifecycleCapturedCompactionConfiguration(t *testing.T) { freedHook := &recordHook{name: "hook.freed", events: &traceLog{}} writeServiceFile(t, agents.PathForConfig(e.configPath), productionAgentsWithFreedHook()) r, err := e.openWithPlugins(ctx, hook, &parkingHook{}, freedHookPlugin(freedHook)) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("compact-capture"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID // The capture carries the compact configuration from one revision. submitThroughRuntime(t, r, sessionID, "op-1", "please write") awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) awaitIdleSession(t, r, sessionID) - var durable harness.ExecutionCapture - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - rec, err := h.ReadOperation(ctx, sessionID, "op-1") - if err != nil { - return err - } - durable = rec.Admission.Execution - return nil - }); err != nil { - t.Fatalf("ReadOperation: %v", err) - } + durable := readOperation(t, r, sessionID, "op-1").Admission.Execution seenMu.Lock() seen := seenCompact revision := seenRevision seenMu.Unlock() - if seen != durable.Compact { - t.Fatalf("hook-saw compact capture %+v differs from the durable %+v", seen, durable.Compact) - } - if revision != durable.ConfigurationRevision { - t.Fatalf("hook-saw revision %q differs from the durable %q", revision, durable.ConfigurationRevision) - } - if seen.Model.Provider == "" || seen.Model.Model == "" || seen.ContextWindow <= 0 || seen.OutputReserve <= 0 || seen.SystemPrompt == "" { - t.Fatalf("captured compact configuration incomplete: %+v", seen) - } + testassert.Equal(t, seen, durable.Compact) + testassert.Equal(t, revision, durable.ConfigurationRevision) + testassert.Require(t, seen.Model.Provider != "" && seen.Model.Model != "" && seen.SystemPrompt != "", "captured compact configuration incomplete: %+v", seen) // A hook attempting to change a compact member is rejected. The // mutation stays installed on this session for the phase's whole @@ -402,23 +372,11 @@ func TestCompactLifecycleCapturedCompactionConfiguration(t *testing.T) { // capture, whose admission rejects and whose item is dropped — the // never-admitted check below pins every path, so only an accepted // admission fails the row. - var rejected harness.SubmitResult - err = r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - var serr error - rejected, serr = h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: "op-2", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "please write"}}, - Mode: harness.MessageModeRegular, - }) - return serr - }) - if err == nil && rejected.Disposition == harness.DispositionAdmitted { - t.Fatalf("a hook changing the compact system prompt was accepted (disposition %q), want rejection", rejected.Disposition) - } - if _, rerr := e.store.ReadRegister(ctx, harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: "op-2"}); !errors.Is(rerr, harness.ErrNotFound) { - t.Fatalf("the rejected admission left a register (%v), want none", rerr) + rejected, err := submitRawThroughRuntime(ctx, r, sessionID, "op-2", "please write", harness.MessageModeRegular) + testassert.Require(t, err != nil || rejected.Disposition != harness.DispositionAdmitted, "a hook changing the compact system prompt was accepted (disposition %q), want rejection", rejected.Disposition) + { + _, rerr := e.store.ReadRegister(ctx, harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: "op-2"}) + testassert.Require(t, errors.Is(rerr, harness.ErrNotFound), "the rejected admission left a register (%v), want none", rerr) } // The prompt and tool-definition replacement freedoms hold on a @@ -437,44 +395,14 @@ func TestCompactLifecycleCapturedCompactionConfiguration(t *testing.T) { return c }) freedSession, err := r.createSession(ctx, e.workspace("compact-freed"), "freedworker") - if err != nil { - t.Fatalf("createSession(freed): %v", err) - } + testassert.NoError(t, err) freedID := freedSession.Identity.SessionID - var freed harness.SubmitResult - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - var serr error - freed, serr = h.Submit(ctx, harness.SubmitRequest{ - SessionID: freedID, - OperationID: "op-3", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "please write"}}, - Mode: harness.MessageModeRegular, - }) - return serr - }); err != nil { - t.Fatalf("submit op-3: %v", err) - } - if freed.Disposition != harness.DispositionAdmitted { - t.Fatalf("submit op-3 disposition = %q, want admission on the fresh session", freed.Disposition) - } + submitThroughRuntime(t, r, freedID, "op-3", "please write") awaitOperation(t, r, freedID, "op-3", harness.OperationSuccess) - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - rec, err := h.ReadOperation(ctx, freedID, "op-3") - if err != nil { - return err - } - if !strings.Contains(rec.Admission.Execution.SystemPrompt, "|freed") { - t.Fatalf("capture prompt = %q, want the hook's replacement", rec.Admission.Execution.SystemPrompt) - } - for _, tool := range rec.Admission.Execution.Tools { - if tool.Description != "freed description" { - t.Fatalf("tool %q description = %q, want the hook's replacement", tool.Name, tool.Description) - } - } - return nil - }); err != nil { - t.Fatalf("ReadOperation(op-3): %v", err) + rec := readOperation(t, r, freedID, "op-3") + testassert.Require(t, strings.Contains(rec.Admission.Execution.SystemPrompt, "|freed"), "capture prompt = %q, want the hook's replacement", rec.Admission.Execution.SystemPrompt) + for _, tool := range rec.Admission.Execution.Tools { + testassert.Require(t, tool.Description == "freed description", "tool %q description = %q, want the hook's replacement", tool.Name, tool.Description) } }) } @@ -564,18 +492,14 @@ func TestCompactLifecycleRuntimeSurfaceUnchanged(t *testing.T) { } fset := token.NewFileSet() files, err := filepath.Glob("*.go") - if err != nil { - t.Fatalf("glob package sources: %v", err) - } + testassert.NoError(t, err) got := map[string]string{} for _, file := range files { if strings.HasSuffix(file, "_test.go") { continue } parsed, err := parser.ParseFile(fset, file, nil, 0) - if err != nil { - t.Fatalf("parse %s: %v", file, err) - } + testassert.Require(t, err == nil, "parse %s: %v", file, err) for _, decl := range parsed.Decls { switch d := decl.(type) { case *ast.FuncDecl: @@ -618,17 +542,16 @@ func TestCompactLifecycleRuntimeSurfaceUnchanged(t *testing.T) { } } } - if len(files) == 0 { - t.Fatal("the package source scan found no production files") - } + testassert.Require(t, len(files) != 0, "the package source scan found no production files") for name := range got { if !want[name] { t.Fatalf("exported declaration %q (%s) is outside the fixed pre-compaction surface", name, got[name]) } } for name := range want { - if _, ok := got[name]; !ok { - t.Fatalf("pinned exported declaration %q missing from the package source scan", name) + { + _, ok := got[name] + testassert.Require(t, ok, "pinned exported declaration %q missing from the package source scan", name) } } } diff --git a/runtime/composition_test.go b/runtime/composition_test.go index f450f742..12b2a849 100644 --- a/runtime/composition_test.go +++ b/runtime/composition_test.go @@ -14,6 +14,7 @@ import ( "testing/synctest" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -120,18 +121,14 @@ func workspaceScopeInfo(workspace string) ScopeInfo { func mustComposition(t *testing.T, plugins ...Plugin) *composition { t.Helper() c, err := newComposition(plugins) - if err != nil { - t.Fatalf("newComposition: %v", err) - } + testassert.NoError(t, err) return c } func mustOpenScope(t *testing.T, c *composition, ctx context.Context, info ScopeInfo, ancestors []*scope) *scope { t.Helper() sc, err := c.openScope(ctx, info, ancestors) - if err != nil { - t.Fatalf("openScope(%s %q): %v", info.Kind, info.Workspace, err) - } + testassert.Require(t, err == nil, "openScope(%s %q): %v", info.Kind, info.Workspace, err) return sc } @@ -217,12 +214,8 @@ func TestCompositionRejectsInvalidDeclarations(t *testing.T) { t.Run(row.name, func(t *testing.T) { opened = 0 _, err := newComposition(row.plugins) - if !errors.Is(err, ErrComposition) { - t.Fatalf("newComposition error = %v, want ErrComposition", err) - } - if opened != 0 { - t.Fatalf("%d factories ran for a rejected declaration set; validation must precede every factory", opened) - } + testassert.Require(t, errors.Is(err, ErrComposition), "newComposition error = %v, want ErrComposition", err) + testassert.Equal(t, opened, 0) }) } } @@ -246,18 +239,21 @@ func TestCompositionAcceptsLongLivedHookDeclarations(t *testing.T) { } c := mustComposition(t, plugins...) for _, kind := range []ScopeKind{ScopeRuntime, ScopeWorkspace, ScopeOperation, ScopeAgent} { - if got := len(c.plan[kind]); got != 1 { - t.Fatalf("%s plan holds %d plugins, want 1", kind, got) + { + got := len(c.plan[kind]) + testassert.Require(t, got == 1, "%s plan holds %d plugins, want 1", kind, got) } } ids := append([]string(nil), c.capabilityIDs...) slices.Sort(ids) - if want := []string{"ag.byvalue", "ag.g", "hook.iface", "hook.ptr", "hook.value", "op.g"}; !slices.Equal(ids, want) { - t.Fatalf("capability universe = %v, want every export including the ordinary short-scope ones", ids) + { + want := []string{"ag.byvalue", "ag.g", "hook.iface", "hook.ptr", "hook.value", "op.g"} + testassert.Require(t, slices.Equal(ids, want), "capability universe = %v, want every export including the ordinary short-scope ones", ids) } forbidden := append(append([]Plugin(nil), plugins...), hookPlugin("op-hook", ScopeOperation, Spec[hookValue]("hook.op"))) - if _, err := newComposition(forbidden); !errors.Is(err, ErrComposition) { - t.Fatalf("newComposition with an Operation-scoped hook export = %v, want ErrComposition", err) + { + _, err := newComposition(forbidden) + testassert.Require(t, errors.Is(err, ErrComposition), "newComposition with an Operation-scoped hook export = %v, want ErrComposition", err) } } @@ -276,9 +272,7 @@ func TestCompositionAcceptsLongerLivedAndSameScopeDependencies(t *testing.T) { return Instance{Values: map[string]any{"ag.g": loudGreeter{}}}, nil }}, ) - if got := len(c.plan[ScopeWorkspace]); got != 2 { - t.Fatalf("workspace plan holds %d plugins, want 2", got) - } + testassert.Equal(t, len(c.plan[ScopeWorkspace]), 2) if c.plan[ScopeWorkspace][0].ID != "wa" || c.plan[ScopeWorkspace][1].ID != "wb" { t.Fatalf("workspace plan = %s before %s, want the provider first", c.plan[ScopeWorkspace][0].ID, c.plan[ScopeWorkspace][1].ID) } @@ -304,15 +298,15 @@ func TestCompositionConstructsDependenciesFirstInStableOrder(t *testing.T) { ) sc := mustOpenScope(t, c, context.Background(), runtimeScopeInfo(), nil) want := []string{"open:early", "open:late", "open:ties"} - if got := trace.all(); !reflect.DeepEqual(got, want) { - t.Fatalf("construction order = %q, want %q", got, want) - } - if err := sc.close(); err != nil { - t.Fatalf("close: %v", err) + { + got := trace.all() + testassert.Require(t, reflect.DeepEqual(got, want), "construction order = %q, want %q", got, want) } + testassert.NoError(t, sc.close()) want = append(want, "close:ties", "close:late", "close:early") - if got := trace.all(); !reflect.DeepEqual(got, want) { - t.Fatalf("close order = %q, want reverse of construction %q", got, want) + { + got := trace.all() + testassert.Require(t, reflect.DeepEqual(got, want), "close order = %q, want reverse of construction %q", got, want) } } @@ -337,29 +331,34 @@ func TestScopeBindingsCarryOnlyDeclaredDependencies(t *testing.T) { rt := mustOpenScope(t, c, context.Background(), runtimeScopeInfo(), nil) ws := mustOpenScope(t, c, context.Background(), workspaceScopeInfo("/ws/one"), []*scope{rt}) - if len(captured.entries) != 3 || len(ws.bindings.entries) != 1 { - t.Fatalf("consumer received %d entries (want its 3 declared), scope exports %d (want its 1)", len(captured.entries), len(ws.bindings.entries)) - } - if g, err := Bind[greeter](captured, "rt.g"); err != nil || g.Greet() != "HI" { - t.Fatalf("Bind assignable dependency: err = %v, value = %v", err, g) + testassert.Require(t, len(captured.entries) == 3 && len(ws.bindings.entries) == 1, "consumer received %d entries (want its 3 declared), scope exports %d (want its 1)", len(captured.entries), len(ws.bindings.entries)) + { + g, err := Bind[greeter](captured, "rt.g") + testassert.Require(t, err == nil && g.Greet() == "HI", "Bind assignable dependency: err = %v, value = %v", err, g) } - if n, err := Bind[int](captured, "rt.n"); err != nil || n != 0 { - t.Fatalf("Bind zero concrete value: err = %v, value = %d, want the ordinary zero value accepted", err, n) + { + n, err := Bind[int](captured, "rt.n") + testassert.Require(t, err == nil && n == 0, "Bind zero concrete value: err = %v, value = %d, want the ordinary zero value accepted", err, n) } - if _, err := Bind[counted](captured, "rt.c"); err != nil { - t.Fatalf("Bind typed nil dependency: %v, want the ordinary type rule to accept it", err) + { + _, err := Bind[counted](captured, "rt.c") + testassert.NoError(t, err) } - if _, err := Bind[greeter](captured, "rt.hidden"); !errors.Is(err, ErrComposition) { - t.Errorf("Bind undeclared capability: %v, want ErrComposition (no ambient lookup)", err) + { + _, err := Bind[greeter](captured, "rt.hidden") + testassert.Check(t, errors.Is(err, ErrComposition), "Bind undeclared capability: %v, want ErrComposition (no ambient lookup)", err) } - if _, err := Bind[greeter](captured, "unneeded.g"); !errors.Is(err, ErrComposition) { - t.Errorf("Bind another plugin's export: %v, want ErrComposition (binding isolation)", err) + { + _, err := Bind[greeter](captured, "unneeded.g") + testassert.Check(t, errors.Is(err, ErrComposition), "Bind another plugin's export: %v, want ErrComposition (binding isolation)", err) } - if _, err := Bind[counted](captured, "rt.g"); !errors.Is(err, ErrComposition) { - t.Errorf("Bind wrong requested type: %v, want ErrComposition", err) + { + _, err := Bind[counted](captured, "rt.g") + testassert.Check(t, errors.Is(err, ErrComposition), "Bind wrong requested type: %v, want ErrComposition", err) } - if _, err := Bind[greeter](Bindings{}, "rt.g"); !errors.Is(err, ErrComposition) { - t.Errorf("Bind on zero Bindings: %v, want ErrComposition", err) + { + _, err := Bind[greeter](Bindings{}, "rt.g") + testassert.Check(t, errors.Is(err, ErrComposition), "Bind on zero Bindings: %v, want ErrComposition", err) } } @@ -435,23 +434,18 @@ func TestScopeConstructionFailuresRollBackWithoutPublishing(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() c, err := newComposition(row.plugins(trace, cancel)) - if err != nil { - t.Fatalf("newComposition: %v", err) - } + testassert.NoError(t, err) if row.cancelFirst { cancel() } sc, err := c.openScope(ctx, runtimeScopeInfo(), nil) - if sc != nil { - t.Fatalf("failed construction returned a scope") - } + testassert.Require(t, sc == nil, "failed construction returned a scope") for _, want := range row.wantIs { - if !errors.Is(err, want) { - t.Errorf("error = %v, want identity %v", err, want) - } + testassert.Check(t, errors.Is(err, want), "error = %v, want identity %v", err, want) } - if got := trace.all(); !reflect.DeepEqual(got, append([]string(nil), row.wantEvents...)) { - t.Fatalf("disposal trace = %q, want %q", got, row.wantEvents) + { + got := trace.all() + testassert.Require(t, reflect.DeepEqual(got, append([]string(nil), row.wantEvents...)), "disposal trace = %q, want %q", got, row.wantEvents) } }) } @@ -463,36 +457,35 @@ func TestScopeAcceptsZeroTypedAndCoreSeamValues(t *testing.T) { return Instance{Values: map[string]any{"ok.n": 0, "ok.c": (*counterService)(nil), "ok.store": stubCoreStorage{}, "ok.stop": (*stubStopper)(nil)}}, nil }}, ) - if len(c.capabilityIDs) != 2 || c.capabilityIDs[0] != "ok.n" || c.capabilityIDs[1] != "ok.c" { - t.Fatalf("capability universe = %q, want the ordinary IDs without the Core seam exports", c.capabilityIDs) - } - if len(c.coreExports) != 1 || c.coreExports[0].id != "ok.store" || c.coreExports[0].plugin != "ok" || c.coreExports[0].scope != ScopeRuntime { - t.Fatalf("Core storage declarations = %+v, want the one Runtime export", c.coreExports) - } - if len(c.jobStoppers) != 1 || c.jobStoppers[0].id != "ok.stop" || c.jobStoppers[0].plugin != "ok" || c.jobStoppers[0].scope != ScopeRuntime { - t.Fatalf("job-stop declarations = %+v, want the one Runtime export", c.jobStoppers) - } + testassert.Require(t, len(c.capabilityIDs) == 2 && c.capabilityIDs[0] == "ok.n" && c.capabilityIDs[1] == "ok.c" && len(c.coreExports) == 1 && c.coreExports[0].id == "ok.store" && c.coreExports[0].plugin == "ok" && c.coreExports[0].scope == ScopeRuntime && len(c.jobStoppers) == 1 && c.jobStoppers[0].id == "ok.stop" && c.jobStoppers[0].plugin == "ok" && c.jobStoppers[0].scope == ScopeRuntime, "capability universe = %q, want the ordinary IDs without the Core seam exports; Core storage declarations = %+v, want the one Runtime export; job-stop declarations = %+v, want the one Runtime export", c.capabilityIDs, c.coreExports, c.jobStoppers) sc := mustOpenScope(t, c, context.Background(), runtimeScopeInfo(), nil) - if _, ok := sc.storages["ok.store"].(stubCoreStorage); !ok { - t.Fatalf("Core storage resolution = %#v, want the supplied stub", sc.storages) + { + _, ok := sc.storages["ok.store"].(stubCoreStorage) + testassert.Require(t, ok, "Core storage resolution = %#v, want the supplied stub", sc.storages) } - if _, ok := sc.jobStoppers["ok.stop"]; !ok { - t.Fatalf("job-stop resolution = %#v, want the supplied value", sc.jobStoppers) + { + _, ok := sc.jobStoppers["ok.stop"] + testassert.Require(t, ok, "job-stop resolution = %#v, want the supplied value", sc.jobStoppers) } - if _, leaked := sc.bindings.entries["ok.store"]; leaked { - t.Fatalf("Core storage export leaked into ordinary bindings") + { + _, leaked := sc.bindings.entries["ok.store"] + testassert.Require(t, !leaked, "Core storage export leaked into ordinary bindings") } - if _, leaked := sc.bindings.entries["ok.stop"]; leaked { - t.Fatalf("job-stop export leaked into ordinary bindings") + { + _, leaked := sc.bindings.entries["ok.stop"] + testassert.Require(t, !leaked, "job-stop export leaked into ordinary bindings") } - if _, err := selectCapabilities([]*scope{sc}, []string{"ok.store"}); !errors.Is(err, ErrComposition) { - t.Errorf("selecting the Core storage export: %v, want ErrComposition", err) + { + _, err := selectCapabilities([]*scope{sc}, []string{"ok.store"}) + testassert.Check(t, errors.Is(err, ErrComposition), "selecting the Core storage export: %v, want ErrComposition", err) } - if _, err := selectCapabilities([]*scope{sc}, []string{"ok.stop"}); !errors.Is(err, ErrComposition) { - t.Errorf("selecting the job-stop export: %v, want ErrComposition", err) + { + _, err := selectCapabilities([]*scope{sc}, []string{"ok.stop"}) + testassert.Check(t, errors.Is(err, ErrComposition), "selecting the job-stop export: %v, want ErrComposition", err) } - if _, err := selectCapabilities([]*scope{sc}, []string{"ok.n", "ok.c"}); err != nil { - t.Errorf("selecting ordinary exports: %v", err) + { + _, err := selectCapabilities([]*scope{sc}, []string{"ok.n", "ok.c"}) + testassert.Check(t, err == nil, "selecting ordinary exports: %v", err) } } @@ -521,13 +514,15 @@ func TestCompositionRejectsScopeInfoShapes(t *testing.T) { {"unknown kind", mutate(func(i *ScopeInfo) { i.Kind = ScopeKind("project") })}, } { t.Run(row.name, func(t *testing.T) { - if _, err := c.openScope(context.Background(), row.info, nil); !errors.Is(err, ErrComposition) { - t.Fatalf("openScope error = %v, want ErrComposition", err) + { + _, err := c.openScope(context.Background(), row.info, nil) + testassert.Require(t, errors.Is(err, ErrComposition), "openScope error = %v, want ErrComposition", err) } }) } - if _, err := c.openScope(context.Background(), ScopeInfo{Kind: ScopeOperation, DataDir: "/owner/data", Workspace: "/ws", SessionID: "s", OperationID: "o"}, nil); err != nil { - t.Fatalf("fully attributed short scope: %v", err) + { + _, err := c.openScope(context.Background(), ScopeInfo{Kind: ScopeOperation, DataDir: "/owner/data", Workspace: "/ws", SessionID: "s", OperationID: "o"}, nil) + testassert.NoError(t, err) } } @@ -560,9 +555,7 @@ func openGuardFixture(t *testing.T, owner context.Context) *guardFixture { func startBlockedCall(t *testing.T, f *guardFixture) (gctx context.Context, released, done chan struct{}) { t.Helper() gctx, release, err := f.ws.enter(context.Background()) - if err != nil { - t.Fatalf("guard entry: %v", err) - } + testassert.NoError(t, err) started := make(chan struct{}) released = make(chan struct{}) done = make(chan struct{}) @@ -596,38 +589,43 @@ func TestScopeGuardOwnsAdmittedCallsThroughClose(t *testing.T) { closeResult := make(chan error, 1) go func() { closeResult <- f.ws.close() }() synctest.Wait() - if !errors.Is(gctx.Err(), context.Canceled) { - t.Fatal("scope closure did not cancel the admitted call context") - } + testassert.Require(t, errors.Is(gctx.Err(), context.Canceled), "scope closure did not cancel the admitted call context") select { case err := <-closeResult: t.Fatalf("close completed without joining the admitted call: %v", err) default: } - if got := f.trace.all(); !slices.Contains(got, "call-start") || slices.Contains(got, "close:cons") { - t.Fatalf("state during the blocked call = %q, want the admitted call not yet disposed", got) + { + got := f.trace.all() + testassert.Require(t, slices.Contains(got, "call-start") && !slices.Contains(got, "close:cons"), "state during the blocked call = %q, want the admitted call not yet disposed", got) } close(released) released = nil <-done - if err := <-closeResult; !errors.Is(err, errCloser) { - t.Fatalf("close = %v, want the cleanup error", err) + { + err := <-closeResult + testassert.Require(t, errors.Is(err, errCloser), "close = %v, want the cleanup error", err) } want := []string{"call-start", "greet:HI", "call-end", "close:cons"} - if got := f.trace.all(); !reflect.DeepEqual(got, want) { - t.Fatalf("guard trace = %q, want %q", got, want) + { + got := f.trace.all() + testassert.Require(t, reflect.DeepEqual(got, want), "guard trace = %q, want %q", got, want) } - if _, _, err := f.ws.enter(context.Background()); !errors.Is(err, ErrClosed) { - t.Errorf("enter after closure: %v, want ErrClosed", err) + { + _, _, err := f.ws.enter(context.Background()) + testassert.Check(t, errors.Is(err, ErrClosed), "enter after closure: %v, want ErrClosed", err) } - if _, err := Bind[int](f.ws.bindings, "cons.out"); !errors.Is(err, ErrClosed) { - t.Errorf("binding from the closed supplying scope: %v, want ErrClosed", err) + { + _, err := Bind[int](f.ws.bindings, "cons.out") + testassert.Check(t, errors.Is(err, ErrClosed), "binding from the closed supplying scope: %v, want ErrClosed", err) } - if _, err := Bind[greeter](f.rt.bindings, "dep.g"); err != nil { - t.Errorf("binding from the still-open supplying scope: %v", err) + { + _, err := Bind[greeter](f.rt.bindings, "dep.g") + testassert.Check(t, err == nil, "binding from the still-open supplying scope: %v", err) } - if err := f.rt.close(); err != nil { - t.Errorf("runtime close: %v", err) + { + err := f.rt.close() + testassert.Check(t, err == nil, "runtime close: %v", err) } }) }) @@ -636,22 +634,27 @@ func TestScopeGuardOwnsAdmittedCallsThroughClose(t *testing.T) { owner, cancelOwner := context.WithCancel(context.Background()) f := openGuardFixture(t, owner) cancelOwner() - if _, _, err := f.ws.enter(context.Background()); !errors.Is(err, ErrClosed) { - t.Errorf("enter on an owner-canceled scope: %v, want ErrClosed", err) + { + _, _, err := f.ws.enter(context.Background()) + testassert.Check(t, errors.Is(err, ErrClosed), "enter on an owner-canceled scope: %v, want ErrClosed", err) } - if _, _, err := f.rt.enter(context.Background()); !errors.Is(err, ErrClosed) { - t.Errorf("enter on an owner-canceled ancestor scope: %v, want ErrClosed", err) + { + _, _, err := f.rt.enter(context.Background()) + testassert.Check(t, errors.Is(err, ErrClosed), "enter on an owner-canceled ancestor scope: %v, want ErrClosed", err) } - if _, err := Bind[int](f.ws.bindings, "cons.out"); !errors.Is(err, ErrClosed) { - t.Errorf("Bind from an owner-canceled supplying scope: %v, want ErrClosed", err) + { + _, err := Bind[int](f.ws.bindings, "cons.out") + testassert.Check(t, errors.Is(err, ErrClosed), "Bind from an owner-canceled supplying scope: %v, want ErrClosed", err) } - if _, err := Bind[greeter](f.consDeps, "dep.g"); !errors.Is(err, ErrClosed) { - t.Errorf("Bind from an owner-canceled ancestor supplying scope: %v, want ErrClosed", err) + { + _, err := Bind[greeter](f.consDeps, "dep.g") + testassert.Check(t, errors.Is(err, ErrClosed), "Bind from an owner-canceled ancestor supplying scope: %v, want ErrClosed", err) } // Rejected admissions registered no work, so closure joins and // disposes synchronously with no registered call to wait on. - if err := f.ws.close(); !errors.Is(err, errCloser) { - t.Errorf("close after rejected admissions: %v, want prompt completion with the shared cleanup error", err) + { + err := f.ws.close() + testassert.Check(t, errors.Is(err, errCloser), "close after rejected admissions: %v, want prompt completion with the shared cleanup error", err) } }) @@ -659,16 +662,19 @@ func TestScopeGuardOwnsAdmittedCallsThroughClose(t *testing.T) { f := openGuardFixture(t, context.Background()) caller, cancelCaller := context.WithCancel(context.Background()) cancelCaller() - if _, _, err := f.ws.enter(caller); !errors.Is(err, context.Canceled) { - t.Errorf("enter from a canceled caller on a live scope: %v, want context.Canceled", err) + { + _, _, err := f.ws.enter(caller) + testassert.Check(t, errors.Is(err, context.Canceled), "enter from a canceled caller on a live scope: %v, want context.Canceled", err) } // Rejected admissions registered no work, so closure joins and // disposes synchronously with no registered call to wait on. - if err := f.ws.close(); !errors.Is(err, errCloser) { - t.Errorf("close after rejected admission: %v, want prompt completion with the shared cleanup error", err) + { + err := f.ws.close() + testassert.Check(t, errors.Is(err, errCloser), "close after rejected admission: %v, want prompt completion with the shared cleanup error", err) } - if err := f.rt.close(); err != nil { - t.Errorf("runtime close: %v", err) + { + err := f.rt.close() + testassert.Check(t, err == nil, "runtime close: %v", err) } }) @@ -680,24 +686,29 @@ func TestScopeGuardOwnsAdmittedCallsThroughClose(t *testing.T) { defer cancelOwner() f := openGuardFixture(t, owner) cancelCaller() // cancels both the caller and the scope derived from it - if _, _, err := f.ws.enter(caller); !errors.Is(err, context.Canceled) { - t.Errorf("enter from a canceled caller on its canceled derived scope: %v, want context.Canceled", err) + { + _, _, err := f.ws.enter(caller) + testassert.Check(t, errors.Is(err, context.Canceled), "enter from a canceled caller on its canceled derived scope: %v, want context.Canceled", err) } - if _, _, err := f.rt.enter(caller); !errors.Is(err, context.Canceled) { - t.Errorf("enter from a canceled caller on a canceled derived ancestor scope: %v, want context.Canceled", err) + { + _, _, err := f.rt.enter(caller) + testassert.Check(t, errors.Is(err, context.Canceled), "enter from a canceled caller on a canceled derived ancestor scope: %v, want context.Canceled", err) } // A live caller on the same canceled scope still sees its own closed // identity. - if _, _, err := f.ws.enter(context.Background()); !errors.Is(err, ErrClosed) { - t.Errorf("enter from a live caller on the canceled derived scope: %v, want ErrClosed", err) + { + _, _, err := f.ws.enter(context.Background()) + testassert.Check(t, errors.Is(err, ErrClosed), "enter from a live caller on the canceled derived scope: %v, want ErrClosed", err) } // Rejected admissions registered no work, so closure joins and // disposes synchronously with no registered call to wait on. - if err := f.ws.close(); !errors.Is(err, errCloser) { - t.Errorf("close after rejected admissions: %v, want prompt completion with the shared cleanup error", err) + { + err := f.ws.close() + testassert.Check(t, errors.Is(err, errCloser), "close after rejected admissions: %v, want prompt completion with the shared cleanup error", err) } - if err := f.rt.close(); err != nil { - t.Errorf("runtime close: %v", err) + { + err := f.rt.close() + testassert.Check(t, err == nil, "runtime close: %v", err) } }) } @@ -815,15 +826,8 @@ func TestWorkspaceScopeAttemptsAreSharedPerKey(t *testing.T) { res2 := f.getAsync(context.Background(), "/ws/x") close(release) r1, r2 := <-res1, <-res2 - if r1.err != nil || r2.err != nil { - t.Fatalf("shared construction errors: %v, %v", r1.err, r2.err) - } - if r1.sc != r2.sc || r1.sc == nil { - t.Fatalf("same-key callers received %p and %p, want one shared scope", r1.sc, r2.sc) - } - if got := f.openCount("/ws/x"); got != 1 { - t.Fatalf("workspace factory ran %d times, want 1", got) - } + testassert.Require(t, r1.err == nil && r2.err == nil && r1.sc == r2.sc && r1.sc != nil, "shared construction errors: %v, %v; same-key callers received %p and %p, want one shared scope", r1.err, r2.err, r1.sc, r2.sc) + testassert.Equal(t, f.openCount("/ws/x"), 1) }) t.Run("other keys proceed while one is held", func(t *testing.T) { @@ -832,14 +836,10 @@ func TestWorkspaceScopeAttemptsAreSharedPerKey(t *testing.T) { resA := f.getAsync(context.Background(), "/ws/a") <-entered scB, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/b")) - if err != nil || scB == nil { - t.Fatalf("unrelated key: %v", err) - } + testassert.Require(t, err == nil && scB != nil, "unrelated key: %v", err) close(release) rA := <-resA - if rA.err != nil || rA.sc == scB { - t.Fatalf("held key: %v %p", rA.err, rA.sc) - } + testassert.Require(t, rA.err == nil && rA.sc != scB, "held key: %v %p", rA.err, rA.sc) }) t.Run("initiator cancellation preserves the attempt for surviving waiters", func(t *testing.T) { @@ -850,21 +850,16 @@ func TestWorkspaceScopeAttemptsAreSharedPerKey(t *testing.T) { <-entered cancel1() res2 := f.getAsync(context.Background(), "/ws/c") - if r := <-res1; !errors.Is(r.err, context.Canceled) || r.sc != nil { - t.Fatalf("canceled initiator: %v %p", r.err, r.sc) + { + r := <-res1 + testassert.Require(t, errors.Is(r.err, context.Canceled) && r.sc == nil, "canceled initiator: %v %p", r.err, r.sc) } close(release) r2 := <-res2 - if r2.err != nil || r2.sc == nil { - t.Fatalf("surviving waiter: %v", r2.err) - } + testassert.Require(t, r2.err == nil && r2.sc != nil, "surviving waiter: %v", r2.err) sc3, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/c")) - if err != nil || sc3 != r2.sc { - t.Fatalf("later same-key request: %v %p vs %p, want the published scope", err, sc3, r2.sc) - } - if got := f.openCount("/ws/c"); got != 1 { - t.Fatalf("workspace factory ran %d times after the shared attempt, want 1", got) - } + testassert.Require(t, err == nil && sc3 == r2.sc, "later same-key request: %v %p vs %p, want the published scope", err, sc3, r2.sc) + testassert.Equal(t, f.openCount("/ws/c"), 1) }) t.Run("failed attempt shares one result and a later request retries", func(t *testing.T) { @@ -876,9 +871,7 @@ func TestWorkspaceScopeAttemptsAreSharedPerKey(t *testing.T) { res2 := f.getAsync(context.Background(), "/ws/d") close(release) r1, r2 := <-res1, <-res2 - if !errors.Is(r1.err, errFactory) || !errors.Is(r2.err, errFactory) || r1.sc != nil || r2.sc != nil { - t.Fatalf("shared failure: %v vs %v", r1.err, r2.err) - } + testassert.Require(t, errors.Is(r1.err, errFactory) && errors.Is(r2.err, errFactory) && r1.sc == nil && r2.sc == nil, "shared failure: %v vs %v", r1.err, r2.err) f.w.mu.Lock() published := len(f.w.live) > 0 || len(f.w.attempts) > 0 f.w.mu.Unlock() @@ -892,18 +885,15 @@ func TestWorkspaceScopeAttemptsAreSharedPerKey(t *testing.T) { before := f.openCount("/ws/d") res3 := f.getAsync(context.Background(), "/ws/d") <-entered2 - if got := f.openCount("/ws/d"); got != before+1 { - t.Fatalf("retry started %d constructions, want exactly one", got-before) + { + got := f.openCount("/ws/d") + testassert.Require(t, got == before+1, "retry started %d constructions, want exactly one", got-before) } close(release2) r3 := <-res3 - if r3.err != nil || r3.sc == nil { - t.Fatalf("later independent attempt: %v", r3.err) - } + testassert.Require(t, r3.err == nil && r3.sc != nil, "later independent attempt: %v", r3.err) sc4, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/d")) - if err != nil || sc4 != r3.sc { - t.Fatalf("request after the successful retry: %v, want the published retry scope", err) - } + testassert.Require(t, err == nil && sc4 == r3.sc, "request after the successful retry: %v, want the published retry scope", err) }) } @@ -927,17 +917,18 @@ func TestWorkspaceScopesShutdownJoinsConstructionAndDisposesSorted(t *testing.T) default: } f.stopOwner() - if r := <-resJ; !errors.Is(r.err, context.Canceled) || r.sc != nil { - tt.Fatalf("held construction after owner cancellation: %v %p", r.err, r.sc) - } - if err := <-shutdownResult; err != nil { - tt.Fatalf("joined shutdown: %v", err) + { + r := <-resJ + testassert.Require(tt, errors.Is(r.err, context.Canceled) && r.sc == nil, "held construction after owner cancellation: %v %p", r.err, r.sc) } - if _, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/new")); !errors.Is(err, ErrClosed) { - tt.Fatalf("get after shutdown admission closed: %v, want ErrClosed", err) + testassert.NoError(tt, <-shutdownResult) + { + _, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/new")) + testassert.Require(tt, errors.Is(err, ErrClosed), "get after shutdown admission closed: %v, want ErrClosed", err) } - if got := f.trace.all(); slices.Contains(got, "close:/ws/j") { - tt.Fatalf("an unpublished construction was disposed: %q", got) + { + got := f.trace.all() + testassert.Require(tt, !slices.Contains(got, "close:/ws/j"), "an unpublished construction was disposed: %q", got) } }) }) @@ -945,21 +936,19 @@ func TestWorkspaceScopesShutdownJoinsConstructionAndDisposesSorted(t *testing.T) t.Run("live workspaces close in sorted order and errors join", func(t *testing.T) { f := newWorkspaceFixture(t) for _, key := range []string{"/ws/2", "/ws/1"} { - if _, err := f.w.get(context.Background(), workspaceScopeInfo(key)); err != nil { - t.Fatalf("get %s: %v", key, err) + { + _, err := f.w.get(context.Background(), workspaceScopeInfo(key)) + testassert.Require(t, err == nil, "get %s: %v", key, err) } } err := f.w.shutdown() - if !errors.Is(err, errWorkspaceClo) { - t.Fatalf("shutdown error = %v, want the joined closer failures", err) - } + testassert.Require(t, errors.Is(err, errWorkspaceClo), "shutdown error = %v, want the joined closer failures", err) got := f.trace.all() first, second := slices.Index(got, "close:/ws/1"), slices.Index(got, "close:/ws/2") - if first < 0 || second < 0 || first > second { - t.Fatalf("workspace close order = %q, want /ws/1 before /ws/2", got) - } - if _, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/1")); !errors.Is(err, ErrClosed) { - t.Fatalf("get after shutdown: %v, want ErrClosed", err) + testassert.Require(t, first >= 0 && second >= 0 && first <= second, "workspace close order = %q, want /ws/1 before /ws/2", got) + { + _, err := f.w.get(context.Background(), workspaceScopeInfo("/ws/1")) + testassert.Require(t, errors.Is(err, ErrClosed), "get after shutdown: %v, want ErrClosed", err) } }) } @@ -979,29 +968,21 @@ func TestInvocationCarriesOneRevisionAcrossNestedDependencyCalls(t *testing.T) { ) ws := mustOpenScope(t, c, context.Background(), workspaceScopeInfo("/ws/config"), nil) view, err := selectCapabilities([]*scope{ws}, []string{"alpha.work"}) - if err != nil { - t.Fatalf("selectCapabilities: %v", err) - } + testassert.NoError(t, err) a, err := Bind[worker](view, "alpha.work") - if err != nil { - t.Fatalf("Bind selected: %v", err) - } + testassert.NoError(t, err) second, err := selectCapabilities([]*scope{ws}, []string{"alpha.work"}) - if err != nil { - t.Fatalf("second selection: %v", err) - } + testassert.NoError(t, err) a2, err := Bind[worker](second, "alpha.work") - if err != nil { - t.Fatalf("second Bind: %v", err) + testassert.NoError(t, err) + testassert.Equal(t, a, a2) + { + _, err := Bind[worker](view, "beta.work") + testassert.Check(t, errors.Is(err, ErrComposition), "Bind unselected dependency: %v, want ErrComposition", err) } - if a != a2 { - t.Fatalf("two selected views supplied different instances, want the reused ones") - } - if _, err := Bind[worker](view, "beta.work"); !errors.Is(err, ErrComposition) { - t.Errorf("Bind unselected dependency: %v, want ErrComposition", err) - } - if _, err := selectCapabilities([]*scope{ws}, []string{"nope.work"}); !errors.Is(err, ErrComposition) { - t.Errorf("select unknown capability: %v, want ErrComposition", err) + { + _, err := selectCapabilities([]*scope{ws}, []string{"nope.work"}) + testassert.Check(t, errors.Is(err, ErrComposition), "select unknown capability: %v, want ErrComposition", err) } revisionOne := &configuration{generation: 1, plugins: map[string]json.RawMessage{ @@ -1013,11 +994,13 @@ func TestInvocationCarriesOneRevisionAcrossNestedDependencyCalls(t *testing.T) { }} invOne := Invocation{snapshot: revisionOne} invTwo := Invocation{snapshot: revisionTwo} - if got, want := a.Work(invOne), `a:1 acfg:{"x":1} | b:1 bcfg:{"y":2}`; got != want { - t.Fatalf("revision 1 nested call = %q, want %q", got, want) + { + got, want := a.Work(invOne), `a:1 acfg:{"x":1} | b:1 bcfg:{"y":2}` + testassert.Equal(t, got, want) } - if got, want := a.Work(invTwo), `a:2 acfg:{"x":9} | b:2 bcfg:none`; got != want { - t.Fatalf("revision 2 on the same instance = %q, want %q", got, want) + { + got, want := a.Work(invTwo), `a:2 acfg:{"x":9} | b:2 bcfg:none` + testassert.Equal(t, got, want) } var zero Invocation @@ -1026,9 +1009,7 @@ func TestInvocationCarriesOneRevisionAcrossNestedDependencyCalls(t *testing.T) { } handed := invOne.Config("alpha") handed[2] = '0' - if string(revisionOne.plugins["alpha"]) != `{"x":1}` { - t.Fatalf("Config handout aliases the snapshot: %s", revisionOne.plugins["alpha"]) - } + testassert.Require(t, string(revisionOne.plugins["alpha"]) == `{"x":1}`, "Config handout aliases the snapshot: %s", revisionOne.plugins["alpha"]) if invOne.Config("missing") != nil { t.Errorf("Config for an unconfigured plugin = %v, want nil", invOne.Config("missing")) } @@ -1051,12 +1032,8 @@ func TestAcceptSettingsAppliesTheNoSettingsRule(t *testing.T) { {"number", json.RawMessage(`1`), false}, } { err := acceptSettings(Plugin{ID: "plain"}, row.raw) - if row.ok && err != nil { - t.Errorf("%s: %v, want acceptance", row.name, err) - } - if !row.ok && !errors.Is(err, ErrComposition) { - t.Errorf("%s: %v, want ErrComposition", row.name, err) - } + testassert.Check(t, !row.ok || err == nil, "%s: %v, want acceptance", row.name, err) + testassert.Check(t, row.ok || errors.Is(err, ErrComposition), "%s: %v, want ErrComposition", row.name, err) } var seen json.RawMessage validator := func(raw json.RawMessage) error { @@ -1066,27 +1043,20 @@ func TestAcceptSettingsAppliesTheNoSettingsRule(t *testing.T) { } return errValidator } - if err := acceptSettings(Plugin{ID: "cfg", ValidateConfig: validator}, json.RawMessage(`null`)); err != nil { - t.Fatalf("validator input: %v", err) - } - if string(seen) != `null` { - t.Fatalf("validator saw %s, want the owned input unchanged", seen) - } - if err := acceptSettings(Plugin{ID: "cfg", ValidateConfig: validator}, json.RawMessage(`{"a":1}`)); !errors.Is(err, errValidator) { - t.Fatalf("validator failure = %v, want the source identity", err) + testassert.NoError(t, acceptSettings(Plugin{ID: "cfg", ValidateConfig: validator}, json.RawMessage(`null`))) + testassert.Require(t, string(seen) == `null`, "validator saw %s, want the owned input unchanged", seen) + { + err := acceptSettings(Plugin{ID: "cfg", ValidateConfig: validator}, json.RawMessage(`{"a":1}`)) + testassert.Require(t, errors.Is(err, errValidator), "validator failure = %v, want the source identity", err) } // A non-nil validator receives an independent clone: in-place scratch // never reaches the caller's bytes. caller := json.RawMessage(`{"a":"12345"}`) - if err := acceptSettings(Plugin{ID: "cfg", ValidateConfig: func(got json.RawMessage) error { + testassert.NoError(t, acceptSettings(Plugin{ID: "cfg", ValidateConfig: func(got json.RawMessage) error { tamperScratch(got, "12345", "xxxxx") return nil - }}, caller); err != nil { - t.Fatalf("cloned validator input: %v", err) - } - if string(caller) != `{"a":"12345"}` { - t.Fatalf("caller bytes after validator scratch = %s, want unchanged", caller) - } + }}, caller)) + testassert.Require(t, string(caller) == `{"a":"12345"}`, "caller bytes after validator scratch = %s, want unchanged", caller) } // noopTool is a concrete Tool implementation for declaration tests; no @@ -1131,8 +1101,9 @@ func TestCompositionRejectsToolDeclarationWithoutDescription(t *testing.T) { {"ToolSpec with a nil function", []Plugin{{ID: "tools", Scope: ScopeRuntime, Provides: []CapabilitySpec{ToolSpec("tool.x", nil)}, Open: noop}}}, } { t.Run(row.name, func(t *testing.T) { - if _, err := newComposition(row.plugins); !errors.Is(err, ErrComposition) { - t.Fatalf("newComposition error = %v, want ErrComposition", err) + { + _, err := newComposition(row.plugins) + testassert.Require(t, errors.Is(err, ErrComposition), "newComposition error = %v, want ErrComposition", err) } }) } @@ -1149,8 +1120,9 @@ func TestCompositionRejectsToolDeclarationWithoutDescription(t *testing.T) { }} provider := Plugin{ID: "tools", Scope: ScopeRuntime, Provides: []CapabilitySpec{ToolSpec("tool.x", staticToolDescription("tool.x"))}, Open: noop} - if _, err := newComposition([]Plugin{provider, consumer}); err != nil { - t.Fatalf("Requires-side composition error = %v, want the ordinary Requires declaration to compose", err) + { + _, err := newComposition([]Plugin{provider, consumer}) + testassert.NoError(t, err) } } @@ -1179,19 +1151,16 @@ func TestToolDeclarationsComposeAtAllScopes(t *testing.T) { for kind := range map[ScopeKind]string{ ScopeRuntime: "", ScopeWorkspace: "", ScopeOperation: "", ScopeAgent: "", } { - if got := len(c.plan[kind]); got != 1 { - t.Fatalf("%s plan holds %d plugins, want 1", kind, got) + { + got := len(c.plan[kind]) + testassert.Require(t, got == 1, "%s plan holds %d plugins, want 1", kind, got) } } slices.Sort(wantIDs) gotIDs := append([]string(nil), c.toolIDs...) slices.Sort(gotIDs) - if !slices.Equal(gotIDs, wantIDs) { - t.Fatalf("tool universe = %v, want every declared tool ID %v", gotIDs, wantIDs) - } - if opened != 0 { - t.Fatalf("%d factories ran during declaration validation", opened) - } + testassert.Require(t, slices.Equal(gotIDs, wantIDs), "tool universe = %v, want every declared tool ID %v", gotIDs, wantIDs) + testassert.Equal(t, opened, 0) } // TestToolSpecDescribeValidatesAndCopies proves the recorded description @@ -1212,42 +1181,37 @@ func TestToolSpecDescribeValidatesAndCopies(t *testing.T) { wantInvocation := Invocation{} wantConstraints := ToolConstraints{Readonly: true, WriteDir: "/w"} description, err := spec.describe(wantInvocation, wantConstraints, harness.SessionIdentity{}) - if err != nil { - t.Fatalf("describe: %v", err) - } + testassert.NoError(t, err) if gotInvocation.Revision() != wantInvocation.Revision() || gotConstraints != wantConstraints { t.Fatalf("describe inputs = (%q, %+v), want the supplied invocation and constraints echoed", gotInvocation.Revision(), gotConstraints) } - if !description.Available || !description.DefaultHidden || description.Definition.Description != "reads" || description.Definition.Name != "tool.x" { - t.Fatalf("description = %+v, want the flags and validated definition passed through", description) - } + testassert.Require(t, description.Available && description.DefaultHidden && description.Definition.Description == "reads" && description.Definition.Name == "tool.x", "description = %+v, want the flags and validated definition passed through", description) // The returned definition is an owned copy in both directions. source.Parameters = json.RawMessage(`{"a":2}`) - if string(description.Definition.Parameters) != `{"a":1}` { - t.Fatalf("returned parameters = %s, want the copied bytes", description.Definition.Parameters) - } + testassert.Require(t, string(description.Definition.Parameters) == `{"a":1}`, "returned parameters = %s, want the copied bytes", description.Definition.Parameters) description.Definition.Parameters = json.RawMessage(`{"a":3}`) - if string(source.Parameters) != `{"a":2}` { - t.Fatalf("source parameters = %s, want the returned copy owned by the caller", source.Parameters) - } + testassert.Require(t, string(source.Parameters) == `{"a":2}`, "source parameters = %s, want the returned copy owned by the caller", source.Parameters) mismatch := ToolSpec("tool.y", func(Invocation, ToolConstraints, harness.SessionIdentity) (ToolDescription, error) { return ToolDescription{Definition: model.ToolDefinition{Name: "other", Parameters: json.RawMessage(`{}`)}}, nil }) - if _, err := mismatch.describe(Invocation{}, ToolConstraints{}, harness.SessionIdentity{}); err == nil || !strings.Contains(err.Error(), "tool.y") { - t.Fatalf("mismatched describe error = %v, want the ID/name match rejection", err) + { + _, err := mismatch.describe(Invocation{}, ToolConstraints{}, harness.SessionIdentity{}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "tool.y"), "mismatched describe error = %v, want the ID/name match rejection", err) } invalid := ToolSpec("tool.x", func(Invocation, ToolConstraints, harness.SessionIdentity) (ToolDescription, error) { return ToolDescription{Definition: model.ToolDefinition{Name: "tool.x", Parameters: json.RawMessage(`[1]`)}}, nil }) - if _, err := invalid.describe(Invocation{}, ToolConstraints{}, harness.SessionIdentity{}); err == nil { - t.Fatal("invalid parameters accepted by the recorded description") + { + _, err := invalid.describe(Invocation{}, ToolConstraints{}, harness.SessionIdentity{}) + testassert.Require(t, err != nil, "invalid parameters accepted by the recorded description") } failing := ToolSpec("tool.x", func(Invocation, ToolConstraints, harness.SessionIdentity) (ToolDescription, error) { return ToolDescription{}, errValidator }) - if _, err := failing.describe(Invocation{}, ToolConstraints{}, harness.SessionIdentity{}); !errors.Is(err, errValidator) { - t.Fatalf("describe failure = %v, want the source error", err) + { + _, err := failing.describe(Invocation{}, ToolConstraints{}, harness.SessionIdentity{}) + testassert.Require(t, errors.Is(err, errValidator), "describe failure = %v, want the source error", err) } } @@ -1277,9 +1241,7 @@ func TestCompositionBindsReportWarningPerRuntimeFactory(t *testing.T) { owner, cancel := context.WithCancel(context.Background()) defer cancel() mustOpenScope(t, isolated, owner, runtimeScopeInfo(), nil) - if captures["isolated"] != nil { - t.Fatal("an isolated composition supplied a ReportWarning callback") - } + testassert.Require(t, captures["isolated"] == nil, "an isolated composition supplied a ReportWarning callback") // The owner's composition binds each Runtime-scoped factory to its own // registered ID; a Workspace-scoped factory never receives the ingress. @@ -1290,23 +1252,21 @@ func TestCompositionBindsReportWarningPerRuntimeFactory(t *testing.T) { } runtimeScope := mustOpenScope(t, c, owner, runtimeScopeInfo(), nil) mustOpenScope(t, c, owner, workspaceScopeInfo("/ws"), []*scope{runtimeScope}) - if captures["alpha"] == nil || captures["beta"] == nil { - t.Fatal("a Runtime-scoped factory received no ReportWarning callback") - } - if captures["gamma"] != nil { - t.Fatal("a Workspace-scoped factory received the Runtime warning ingress") - } + testassert.Require(t, captures["alpha"] != nil && captures["beta"] != nil, "a Runtime-scoped factory received no ReportWarning callback") + testassert.Require(t, captures["gamma"] == nil, "a Workspace-scoped factory received the Runtime warning ingress") captures["alpha"]("a_kind", "a_message") captures["beta"]("b_kind", "b_message") - if want := []string{"alpha|a_kind|a_message", "beta|b_kind|b_message"}; !slices.Equal(reported, want) { - t.Fatalf("reported = %v, want each factory's own registered ID", reported) + { + want := []string{"alpha|a_kind|a_message", "beta|b_kind|b_message"} + testassert.Require(t, slices.Equal(reported, want), "reported = %v, want each factory's own registered ID", reported) } // The scope's cancellation is the callback's boundary: a canceled scope // drops the report. cancel() captures["alpha"]("late_kind", "late_message") - if want := []string{"alpha|a_kind|a_message", "beta|b_kind|b_message"}; !slices.Equal(reported, want) { - t.Fatalf("a canceled scope's report reached the sink: %v", reported) + { + want := []string{"alpha|a_kind|a_message", "beta|b_kind|b_message"} + testassert.Require(t, slices.Equal(reported, want), "a canceled scope's report reached the sink: %v", reported) } } diff --git a/runtime/configuration_connection_test.go b/runtime/configuration_connection_test.go index 96f31a15..1a4376bd 100644 --- a/runtime/configuration_connection_test.go +++ b/runtime/configuration_connection_test.go @@ -22,6 +22,7 @@ import ( "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/config" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/tool" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" @@ -154,11 +155,30 @@ const connectionAgentsDocument = `{"primary": {"model": "usablep/m"}}` // store, with the embedded bundled catalog and the isolated HOME giving the // Runtime its own real managed env and discovery cache. The startup refresh // carries no credential yet and fails harmlessly on the gated endpoint. -func openConnectionRuntime(t *testing.T, store harness.Storage, endpoint string, extra ...Plugin) (*Runtime, *ownerEnv) { +func openConnectionRuntime(t *testing.T, store harness.Storage, endpoint string) (*Runtime, *ownerEnv) { t.Helper() reserveEnvKey(t, "CONNECTION_DISC_KEY") reserveEnvKey(t, "CONNECTION_USABLE_KEY") - return openConfigurationRuntimeWithEnv(t, store, newOwnerEnv(t), connectionProvidersDocument(endpoint), connectionAgentsDocument, extra...) + return openConfigurationRuntimeWithEnv(t, store, newOwnerEnv(t), connectionProvidersDocument(endpoint), connectionAgentsDocument) +} + +// openConnectionSubscription opens the connection fixture's Runtime and +// subscribes with the given capacity. The returned close runs inside the +// store-runner callback — subscription first, then Runtime, then SQLite — so +// callers defer it there rather than with t.Cleanup. A failed subscription +// closes the opened Runtime before failing the test. +func openConnectionSubscription(t *testing.T, store harness.Storage, endpoint string, capacity int) (*Runtime, *ownerEnv, *Subscription, func()) { + t.Helper() + r, e := openConnectionRuntime(t, store, endpoint) + sub, err := r.Subscribe(capacity) + if err != nil { + closeProjectionRuntime(r) + t.Fatalf("Subscribe: %v", err) + } + return r, e, sub, func() { + sub.Close() + closeProjectionRuntime(r) + } } // reserveEnvKey starts one env name unset and restores its exact prior @@ -192,23 +212,21 @@ func assertConnectionRefused(t *testing.T, r *Runtime, sub *Subscription, before // retained manager never owns it. func assertConnectionEnvSilent(t *testing.T, r *Runtime) { t.Helper() - if value := os.Getenv("CONNECTION_DISC_KEY"); value != "" { - t.Fatalf("a refused connection operator wrote the key env var (%q)", value) - } - if r.managedEnv.IsManaged("CONNECTION_DISC_KEY") { - t.Fatal("a refused connection operator managed the key") - } + testassert.Equal(t, os.Getenv("CONNECTION_DISC_KEY"), "") + testassert.Require(t, !r.managedEnv.IsManaged("CONNECTION_DISC_KEY"), "a refused connection operator managed the key") } // assertNoConnectionPublication asserts zero publication and event silence // without re-checking file bytes the test itself legitimately changed. func assertNoConnectionPublication(t *testing.T, r *Runtime, sub *Subscription, generation uint64, warnRev uint64) { t.Helper() - if current := r.config.current(); current == nil || current.generation != generation { - t.Fatalf("generation advanced to %v", current) + { + current := r.config.current() + testassert.Require(t, current != nil && current.generation == generation, "generation advanced to %v", current) } - if got, _ := r.warnings.snapshot(); got != warnRev { - t.Fatalf("warning revision advanced: %d → %d", warnRev, got) + { + got, _ := r.warnings.snapshot() + testassert.Equal(t, got, warnRev) } assertNoEvent(t, sub) assertConnectionEnvSilent(t, r) @@ -226,9 +244,7 @@ func assertFollowUpEditProvesReleasedOwnership(t *testing.T, r *Runtime, provide }() select { case err := <-done: - if err != nil { - t.Fatalf("follow-up edit after the failed writer: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the failed writer leaked publication ownership") } @@ -268,8 +284,9 @@ func assertDiscoveryCacheLacks(t *testing.T, r *Runtime, providerID, modelID str if id != providerID { continue } - if _, ok := record.Models[modelID]; ok { - t.Fatalf("discovery cache for %q carries %q", providerID, modelID) + { + _, ok := record.Models[modelID] + testassert.Require(t, !ok, "discovery cache for %q carries %q", providerID, modelID) } } } @@ -279,46 +296,32 @@ func assertDiscoveryCacheLacks(t *testing.T, r *Runtime, providerID, modelID str func assertDotenvUnchanged(t *testing.T, r *Runtime, before []byte, what string) { t.Helper() after, err := os.ReadFile(r.managedEnv.Path()) - if err != nil || string(after) != string(before) { - t.Fatalf(".env after %s = (%q, %v), want the exact prior bytes", what, after, err) - } + testassert.Require(t, err == nil && string(after) == string(before), ".env after %s = (%q, %v), want the exact prior bytes", what, after, err) } func TestConnectProviderDiscoveryBackedManagedKey(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, _, _ := runtimeMutationBaseline(t, r) key := connectionKey mutation, err := r.connectProvider(ctx, "groq", &key) - if err != nil { - t.Fatalf("connectProvider: %v", err) - } - if mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("connect generation = %q, want 2", mutation.ConfigurationRevision.Generation) - } - if got := mutation.Result; !got.Connected || got.KeySource != protocol.ProviderKeySource(config.KeySourceManaged) || got.ApiKeyEnv != "CONNECTION_DISC_KEY" { - t.Fatalf("connected provider view = %+v, want connected with a managed key", got) - } - if len(mutation.Result.Models) == 0 || !mutation.Result.Models[0].Usable { - t.Fatalf("connected provider models = %+v, want the fetched usable model", mutation.Result.Models) + testassert.NoError(t, err) + testassert.Equal(t, mutation.ConfigurationRevision.Generation, "2") + { + got := mutation.Result + testassert.Require(t, got.Connected && got.KeySource == protocol.ProviderKeySource(config.KeySourceManaged) && got.ApiKeyEnv == "CONNECTION_DISC_KEY", "connected provider view = %+v, want connected with a managed key", got) } + testassert.Require(t, len(mutation.Result.Models) != 0 && mutation.Result.Models[0].Usable, "connected provider models = %+v, want the fetched usable model", mutation.Result.Models) drainConnectionEvent(t, r.warnings, sub, "2") // The existing provider's connection owns no file: the configured // main and agents bytes are untouched while the generation advanced. after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("connect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(after) == string(before), "connect changed the owning config (%v)", err) // The managed key: written to the .env, applied to the process env, // owned by the retained manager, and already scrubbed from the @@ -327,13 +330,9 @@ func TestConnectProviderDiscoveryBackedManagedKey(t *testing.T) { t.Fatalf("managed key state = (%q, %v), want the exact value set and managed", os.Getenv("CONNECTION_DISC_KEY"), r.managedEnv.IsManaged("CONNECTION_DISC_KEY")) } envData, err := os.ReadFile(r.managedEnv.Path()) - if err != nil || !strings.Contains(string(envData), "CONNECTION_DISC_KEY="+connectionKey+"\n") { - t.Fatalf(".env = (%q, %v), want the managed key line", envData, err) - } + testassert.Require(t, err == nil && strings.Contains(string(envData), "CONNECTION_DISC_KEY="+connectionKey+"\n"), ".env = (%q, %v), want the managed key line", envData, err) for _, entry := range r.managedEnv.SubprocessEnv() { - if strings.HasPrefix(entry, "CONNECTION_DISC_KEY=") { - t.Fatalf("the connected key leaked into the subprocess snapshot: %q", entry) - } + testassert.Require(t, !strings.HasPrefix(entry, "CONNECTION_DISC_KEY="), "the connected key leaked into the subprocess snapshot: %q", entry) } // The fetch carried the transient Authorization of the resolved @@ -351,60 +350,39 @@ func TestConnectProviderDiscoveryBackedManagedKey(t *testing.T) { } records, _ := catalog.ReadDiscoveryCache(r.config.loader.Home()) record, ok := records["groq"] - if !ok || len(record.Models) == 0 { - t.Fatalf("discovery cache records = %+v, want the persisted discovery", records) - } + testassert.Require(t, ok && len(record.Models) != 0, "discovery cache records = %+v, want the persisted discovery", records) transport := r.config.current().catalog.Providers["groq"].Transport - if !record.BoundTo(transport) { - t.Fatal("cache record does not bind to the configured transport") - } + testassert.Require(t, record.BoundTo(transport), "cache record does not bind to the configured transport") provisional := transport provisional.APIKeyEnv = "" - if record.BoundTo(provisional) { - t.Fatal("cache record binds to the provisional fetch copy") - } + testassert.Require(t, !record.BoundTo(provisional), "cache record binds to the provisional fetch copy") // No secret value in the published mutation bytes. raw, merr := json.Marshal(mutation) - if merr != nil || strings.Contains(string(raw), connectionKey) { - t.Fatalf("mutation bytes carry the key value (%v)", merr) - } + testassert.Require(t, merr == nil && !strings.Contains(string(raw), connectionKey), "mutation bytes carry the key value (%v)", merr) }) } func TestConnectProviderRepeatStillPublishes(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() t.Setenv("CONNECTION_USABLE_KEY", "usable-key-value") // A repeat connection of an already-connected usable provider is a // real publication under the one shared successful-edit rule. first, err := r.connectProvider(ctx, "usablep", nil) - if err != nil || first.ConfigurationRevision.Generation != "2" { - t.Fatalf("first connect = (%v, %q), want generation 2", err, first.ConfigurationRevision.Generation) - } + testassert.Require(t, err == nil && first.ConfigurationRevision.Generation == "2", "first connect = (%v, %q), want generation 2", err, first.ConfigurationRevision.Generation) before, err := os.ReadFile(r.config.configPath) - if err != nil { - t.Fatalf("read config: %v", err) - } + testassert.NoError(t, err) drainConnectionEvent(t, r.warnings, sub, "2") second, err := r.connectProvider(ctx, "usablep", nil) - if err != nil || second.ConfigurationRevision.Generation != "3" { - t.Fatalf("repeat connect = (%v, %q), want generation 3", err, second.ConfigurationRevision.Generation) - } + testassert.Require(t, err == nil && second.ConfigurationRevision.Generation == "3", "repeat connect = (%v, %q), want generation 3", err, second.ConfigurationRevision.Generation) drainConnectionEvent(t, r.warnings, sub, "3") after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("repeat connect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(after) == string(before), "repeat connect changed the owning config (%v)", err) }) } @@ -416,14 +394,9 @@ func TestConnectProviderRepeatStillPublishes(t *testing.T) { func TestConnectProviderUsableWindowRemovedRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_STATIC_KEY") // The external hand edit removes staticp's only model window: the @@ -437,28 +410,22 @@ func TestConnectProviderUsableWindowRemovedRefuses(t *testing.T) { fetches := server.requests() key := connectionKey - _, err = r.connectProvider(ctx, "staticp", &key) + _, err := r.connectProvider(ctx, "staticp", &key) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) - if got := server.requests(); got != fetches { - t.Fatalf("usable connect performed %d fetches, want none", got-fetches) - } - if r.managedEnv.IsManaged("CONNECTION_STATIC_KEY") || os.Getenv("CONNECTION_STATIC_KEY") != "" { - t.Fatal("a refused connect persisted the key") + { + got := server.requests() + testassert.Require(t, got == fetches, "usable connect performed %d fetches, want none", got-fetches) } + testassert.Require(t, !r.managedEnv.IsManaged("CONNECTION_STATIC_KEY") && os.Getenv("CONNECTION_STATIC_KEY") == "", "a refused connect persisted the key") }) } func TestConnectProviderUsableSkipsFetch(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_STATIC_KEY") fetchesAtOpen := server.requests() @@ -466,16 +433,12 @@ func TestConnectProviderUsableSkipsFetch(t *testing.T) { // a supplied key still updates the managed key through TrySet. key := connectionKey mutation, err := r.connectProvider(ctx, "usablep", &key) - if err != nil { - t.Fatalf("connectProvider: %v", err) - } - if got := server.requests(); got != fetchesAtOpen { - t.Fatalf("usable connect performed %d fetches, want none", got-fetchesAtOpen) - } - if mutation.ConfigurationRevision.Generation != "2" || !mutation.Result.Connected || - mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceManaged) { - t.Fatalf("usable connect = (%q, %+v), want generation 2 and a managed connected provider", mutation.ConfigurationRevision.Generation, mutation.Result) + testassert.NoError(t, err) + { + got := server.requests() + testassert.Require(t, got == fetchesAtOpen, "usable connect performed %d fetches, want none", got-fetchesAtOpen) } + testassert.Require(t, mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Connected && mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceManaged), "usable connect = (%q, %+v), want generation 2 and a managed connected provider", mutation.ConfigurationRevision.Generation, mutation.Result) event := drainConnectionEvent(t, r.warnings, sub, "2") if os.Getenv("CONNECTION_USABLE_KEY") != connectionKey || !r.managedEnv.IsManaged("CONNECTION_USABLE_KEY") { t.Fatalf("supplied key state = (%q, %v), want it TrySet through the manager", os.Getenv("CONNECTION_USABLE_KEY"), r.managedEnv.IsManaged("CONNECTION_USABLE_KEY")) @@ -484,24 +447,17 @@ func TestConnectProviderUsableSkipsFetch(t *testing.T) { // The key value is absent from every observable byte: the mutation // envelope, the provider read, a later typed error, and the event. mutationBytes, merr := json.Marshal(mutation) - if merr != nil || strings.Contains(string(mutationBytes), connectionKey) { - t.Fatalf("mutation bytes carry the key value (%v)", merr) - } + testassert.Require(t, merr == nil && !strings.Contains(string(mutationBytes), connectionKey), "mutation bytes carry the key value (%v)", merr) detail, err := r.getProvider(ctx, "usablep") - if err != nil { - t.Fatalf("getProvider: %v", err) - } + testassert.NoError(t, err) detailBytes, derr := json.Marshal(detail) - if derr != nil || strings.Contains(string(detailBytes), connectionKey) { - t.Fatalf("provider read bytes carry the key value (%v)", derr) - } - if _, err := r.connectProvider(ctx, "staticp", nil); err == nil || strings.Contains(err.Error(), connectionKey) { - t.Fatalf("refusal error = %v, want a typed error without the key value", err) + testassert.Require(t, derr == nil && !strings.Contains(string(detailBytes), connectionKey), "provider read bytes carry the key value (%v)", derr) + { + _, err := r.connectProvider(ctx, "staticp", nil) + testassert.Require(t, err != nil && !strings.Contains(err.Error(), connectionKey), "refusal error = %v, want a typed error without the key value", err) } eventBytes, everr := json.Marshal(event) - if everr != nil || strings.Contains(string(eventBytes), connectionKey) { - t.Fatalf("event bytes carry the key value (%v)", everr) - } + testassert.Require(t, everr == nil && !strings.Contains(string(eventBytes), connectionKey), "event bytes carry the key value (%v)", everr) }) } @@ -515,55 +471,39 @@ func TestConnectProviderExternalKeyNeverPersisted(t *testing.T) { t.Setenv("CONNECTION_DISC_KEY", "shell-exported-value") before, _, _ := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) // Without a supplied credential the external value is used as-is and // never persisted, removed, or overwritten. mutation, err := r.connectProvider(ctx, "groq", nil) - if err != nil { - t.Fatalf("connectProvider: %v", err) - } - if !mutation.Result.Connected || mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceExternal) { - t.Fatalf("connected provider view = %+v, want an external key source", mutation.Result) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.Result.Connected && mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceExternal), "connected provider view = %+v, want an external key source", mutation.Result) sawShell := false for _, auth := range server.authorizations() { if auth == "Bearer shell-exported-value" { sawShell = true } - if strings.Contains(auth, connectionKey) { - t.Fatalf("discovery authorization %q carried a supplied key", auth) - } + testassert.Require(t, !strings.Contains(auth, connectionKey), "discovery authorization %q carried a supplied key", auth) } if !sawShell { t.Fatalf("discovery authorizations = %v, want the shell key's bearer", server.authorizations()) } - if r.managedEnv.IsManaged("CONNECTION_DISC_KEY") { - t.Fatal("the external key became managed") - } + testassert.Require(t, !r.managedEnv.IsManaged("CONNECTION_DISC_KEY"), "the external key became managed") assertDotenvUnchanged(t, r, envBefore, "the external-key connect") after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("connect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(after) == string(before), "connect changed the owning config (%v)", err) // A supplied nonempty key on the same external binding refuses before // any configuration or cache side effect: the external variable is // not silently substituted for the supplied key. supplied := connectionKey _, err = r.connectProvider(ctx, "groq", &supplied) - if err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("supplied key on an external binding = %v, want the pre-effect refusal", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration), "supplied key on an external binding = %v, want the pre-effect refusal", err) if os.Getenv("CONNECTION_DISC_KEY") != "shell-exported-value" || r.managedEnv.IsManaged("CONNECTION_DISC_KEY") { t.Fatalf("external key after the refusal = (%q, %v), want it untouched and unmanaged", os.Getenv("CONNECTION_DISC_KEY"), r.managedEnv.IsManaged("CONNECTION_DISC_KEY")) } after, err = os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("the refused connect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(after) == string(before), "the refused connect changed the owning config (%v)", err) assertDotenvUnchanged(t, r, envBefore, "the refused connect") }) } @@ -576,43 +516,35 @@ func TestConnectProviderExternalKeyNeverPersisted(t *testing.T) { func TestConnectProviderTransientKeyOverridesConfiguredAuthorization(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() doc := connectionProvidersDocument(server.URL) oldGroq := `"groq": {"transport": {"base_url": "` + server.URL + `", "api_key_env": "CONNECTION_DISC_KEY"}, "discovery": true, "models": {"ghost": {"name": "Ghost"}}}` newGroq := `"groq": {"transport": {"base_url": "` + server.URL + `", "api_key_env": "CONNECTION_DISC_KEY", "headers": {"authorization": "Bearer configured-lower", "AUTHORIZATION": "Bearer configured-upper"}}, "discovery": true, "models": {"ghost": {"name": "Ghost"}}}` edited := strings.Replace(doc, oldGroq, newGroq, 1) - if edited == doc { - t.Fatal("fixture replacement did not apply") - } + testassert.Require(t, edited != doc, "fixture replacement did not apply") writeServiceFile(t, r.config.configPath, edited) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") fetches := server.requests() key := connectionKey - if _, err := r.connectProvider(ctx, "groq", &key); err != nil { - t.Fatalf("connectProvider: %v", err) - } - if got := server.requests(); got != fetches+1 { - t.Fatalf("connect performed %d fetches, want one", got-fetches) + { + _, err := r.connectProvider(ctx, "groq", &key) + testassert.NoError(t, err) } - if auth := server.authorizations()[fetches]; auth != "Bearer "+connectionKey { - t.Fatalf("connect Authorization = %q, want the resolved transient bearer", auth) + { + got := server.requests() + testassert.Require(t, got == fetches+1, "connect performed %d fetches, want one", got-fetches) } + testassert.Equal(t, server.authorizations()[fetches], "Bearer "+connectionKey) headers := r.config.current().catalog.Providers["groq"].Transport.Headers - if len(headers) != 2 || headers["authorization"] != "Bearer configured-lower" || headers["AUTHORIZATION"] != "Bearer configured-upper" { - t.Fatalf("captured transport headers = %v, want the raw spellings intact", headers) - } + testassert.Require(t, len(headers) == 2 && headers["authorization"] == "Bearer configured-lower" && headers["AUTHORIZATION"] == "Bearer configured-upper", "captured transport headers = %v, want the raw spellings intact", headers) drainConnectionEvent(t, r.warnings, sub, "3") }) } @@ -624,14 +556,9 @@ func TestConnectProviderTransientKeyOverridesConfiguredAuthorization(t *testing. func TestConnectProviderKeylessFetchKeepsConfiguredAuthorization(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() server.retarget(func(string) string { return connectionEmptyDiscovery }) doc := connectionProvidersDocument(server.URL) @@ -639,47 +566,38 @@ func TestConnectProviderKeylessFetchKeepsConfiguredAuthorization(t *testing.T) { newKeyless := `"moonshot": {"transport": {"base_url": "` + server.URL + `", "api_key_env": "", "headers": {"authorization": "Bearer keyless-configured"}}, "discovery": true, "models": {"ghost": {}}}, ` + oldKeyless edited := strings.Replace(doc, oldKeyless, newKeyless, 1) - if edited == doc { - t.Fatal("fixture replacement did not apply") - } + testassert.Require(t, edited != doc, "fixture replacement did not apply") writeServiceFile(t, r.config.configPath, edited) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") before, generation, warnRev := runtimeMutationBaseline(t, r) fetches := server.requests() - _, err = r.connectProvider(ctx, "moonshot", nil) + _, err := r.connectProvider(ctx, "moonshot", nil) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) - if got := server.requests(); got != fetches+1 { - t.Fatalf("keyless connect performed %d fetches, want one", got-fetches) - } - if auth := server.authorizations()[fetches]; auth != "Bearer keyless-configured" { - t.Fatalf("keyless connect Authorization = %q, want the configured header unchanged", auth) + { + got := server.requests() + testassert.Require(t, got == fetches+1, "keyless connect performed %d fetches, want one", got-fetches) } + testassert.Equal(t, server.authorizations()[fetches], "Bearer keyless-configured") headers := r.config.current().catalog.Providers["moonshot"].Transport.Headers - if len(headers) != 1 || headers["authorization"] != "Bearer keyless-configured" { - t.Fatalf("captured transport headers = %v, want the configured spelling intact", headers) - } + testassert.Require(t, len(headers) == 1 && headers["authorization"] == "Bearer keyless-configured", "captured transport headers = %v, want the configured spelling intact", headers) }) } func TestConnectProviderKeyResolutionRefusals(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) // Unset env, no supplied key: the required credential is missing. - _, err = r.connectProvider(ctx, "discp", nil) + _, err := r.connectProvider(ctx, "discp", nil) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) // A set-but-empty external value fails uniformly. @@ -694,10 +612,7 @@ func TestConnectProviderKeyResolutionRefusals(t *testing.T) { // An unknown provider keeps the read path's typed error. _, err = r.connectProvider(ctx, "ghost-provider", nil) - if !errors.Is(err, catalog.ErrUnknownProvider) { - t.Fatalf("unknown provider connect = %v, want ErrUnknownProvider", err) - } - assertNoConnectionPublication(t, r, sub, generation, warnRev) + assertConnectionRefused(t, r, sub, before, generation, warnRev, err, catalog.ErrUnknownProvider) }) } @@ -710,18 +625,11 @@ func TestConnectProviderKeyResolutionRefusals(t *testing.T) { func TestConnectProviderKeylessSuppliedKeyRefuses(t *testing.T) { store := storage.NewMemory() server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) before, generation, warnRev := runtimeMutationBaseline(t, r) // The usable keyless provider currently ignores a supplied key and @@ -735,20 +643,19 @@ func TestConnectProviderKeylessSuppliedKeyRefuses(t *testing.T) { // The unusable keyless discovery provider takes the same pre-fetch // refusal: no discovery record is written for it. - if _, err := r.connectProvider(ctx, "keylessp", &key); !errors.Is(err, ErrConfiguration) { - t.Fatalf("unusable keyless connect = %v, want the configuration failure", err) + { + _, err := r.connectProvider(ctx, "keylessp", &key) + assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) } assertDiscoveryCacheLacks(t, r, "keylessp", "ghost") - assertNoConnectionPublication(t, r, sub, generation, warnRev) // Without a supplied key the keyless connect is the retained // no-credential connection: no key action, one publication. mutation, err := r.connectProvider(ctx, "keylessusable", nil) - if err != nil || !mutation.Result.Connected || mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceKeyless) { - t.Fatalf("keyless connect without a key = (%v, %+v), want the connected keyless view", err, mutation.Result) - } - if want := strconv.FormatUint(generation+1, 10); mutation.ConfigurationRevision.Generation != want { - t.Fatalf("keyless connect generation = %q, want %q", mutation.ConfigurationRevision.Generation, want) + testassert.Require(t, err == nil && mutation.Result.Connected && mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceKeyless), "keyless connect without a key = (%v, %+v), want the connected keyless view", err, mutation.Result) + { + want := strconv.FormatUint(generation+1, 10) + testassert.Equal(t, mutation.ConfigurationRevision.Generation, want) } drainConnectionEvent(t, r.warnings, sub, strconv.FormatUint(generation+1, 10)) } @@ -761,38 +668,30 @@ func TestConnectProviderKeylessSuppliedKeyRefuses(t *testing.T) { func TestConnectProviderUnusableDiscoveryDisabledRefusals(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_STATIC_KEY") t.Setenv("CONNECTION_STATIC_KEY", "valid-external-key") before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) fetches := server.requests() // The keyed unusable provider has a valid external credential: the // guard still fires before any key resolution or fetch. _, err = r.connectProvider(ctx, "staticempty", nil) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) - if r.managedEnv.IsManaged("CONNECTION_STATIC_KEY") { - t.Fatal("the refused keyed connect managed the external key") - } + testassert.Require(t, !r.managedEnv.IsManaged("CONNECTION_STATIC_KEY"), "the refused keyed connect managed the external key") // The keyless unusable provider takes the same guard with no // credential involved. _, err = r.connectProvider(ctx, "keylessempty", nil) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) - if got := server.requests(); got != fetches { - t.Fatalf("the guard performed %d fetches, want none", got-fetches) + { + got := server.requests() + testassert.Require(t, got == fetches, "the guard performed %d fetches, want none", got-fetches) } assertDotenvUnchanged(t, r, envBefore, "the refused guard") }) @@ -805,12 +704,11 @@ func TestConnectProviderUpdatesManagedKey(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() // Seed a stale managed key first. - if err := r.managedEnv.TrySet("CONNECTION_DISC_KEY", "stale-managed-value"); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_DISC_KEY", "stale-managed-value")) updated := connectionKey - if _, err := r.connectProvider(ctx, "groq", &updated); err != nil { - t.Fatalf("connectProvider: %v", err) + { + _, err := r.connectProvider(ctx, "groq", &updated) + testassert.NoError(t, err) } if os.Getenv("CONNECTION_DISC_KEY") != connectionKey { t.Fatalf("managed key = %q, want the supplied update", os.Getenv("CONNECTION_DISC_KEY")) @@ -818,9 +716,7 @@ func TestConnectProviderUpdatesManagedKey(t *testing.T) { // The connected-after-Open key is scrubbed from the live call-time // snapshot. for _, entry := range r.managedEnv.SubprocessEnv() { - if strings.HasPrefix(entry, "CONNECTION_DISC_KEY=") { - t.Fatalf("the connected key leaked into the subprocess snapshot: %q", entry) - } + testassert.Require(t, !strings.HasPrefix(entry, "CONNECTION_DISC_KEY="), "the connected key leaked into the subprocess snapshot: %q", entry) } }) } @@ -843,11 +739,11 @@ type connectRaceFixture struct { warnRev uint64 } -func newConnectRaceFixture(t *testing.T, store harness.Storage) *connectRaceFixture { +func newConnectRaceFixture(t *testing.T, store harness.Storage) (*connectRaceFixture, *Subscription, func()) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) arrive, release := server.park() - return &connectRaceFixture{r: r, endpoint: server.URL, arrive: arrive, release: release} + return &connectRaceFixture{r: r, endpoint: server.URL, arrive: arrive, release: release}, sub, closeRuntime } // start launches one parked connect whose outcome lands in the fixture. @@ -890,23 +786,16 @@ type connectRaceResult struct { // fetched payload never in the cache — the refusal preceded every write. func (f *connectRaceFixture) assertRefused(t *testing.T, sub *Subscription) { t.Helper() - if f.result.err == nil || !errors.Is(f.result.err, ErrConfiguration) { - t.Fatalf("raced connect = %v, want the typed refusal", f.result.err) - } + testassert.Require(t, f.result.err != nil && errors.Is(f.result.err, ErrConfiguration), "raced connect = %v, want the typed refusal", f.result.err) assertConnectionRefused(t, f.r, sub, f.before, f.generation, f.warnRev, f.result.err, ErrConfiguration) assertDiscoveryCacheLacks(t, f.r, "discp", "ghost") } func TestConnectProviderRaceRemovedRefusesBeforeWrites(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { - f := newConnectRaceFixture(t, store) - defer closeProjectionRuntime(f.r) + f, sub, closeRuntime := newConnectRaceFixture(t, store) + defer closeRuntime() ctx := context.Background() - sub, err := f.r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() key := connectionKey f.start("discp", &key) @@ -917,8 +806,9 @@ func TestConnectProviderRaceRemovedRefusesBeforeWrites(t *testing.T) { // parked; an unconnected keyed custom provider is removable. The // refusal baseline is the state that writer left, captured before // the parked connect is released. - if _, err := f.r.deleteProvider(ctx, "discp"); err != nil { - t.Fatalf("deleteProvider during the parked fetch: %v", err) + { + _, err := f.r.deleteProvider(ctx, "discp") + testassert.NoError(t, err) } drainConnectionEvent(t, f.r.warnings, sub, "2") f.captureBaseline(t) @@ -930,14 +820,9 @@ func TestConnectProviderRaceRemovedRefusesBeforeWrites(t *testing.T) { func TestConnectProviderRaceTransportChangedRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { - f := newConnectRaceFixture(t, store) - defer closeProjectionRuntime(f.r) + f, sub, closeRuntime := newConnectRaceFixture(t, store) + defer closeRuntime() ctx := context.Background() - sub, err := f.r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() key := connectionKey f.start("discp", &key) @@ -947,8 +832,9 @@ func TestConnectProviderRaceTransportChangedRefuses(t *testing.T) { // The concurrent writer changes the provider's transport identity; // the baseline is the published state it left. moved := "https://moved.test/v1" - if _, err := f.r.updateProvider(ctx, "discp", protocol.ProviderEdit{BaseUrl: &moved}); err != nil { - t.Fatalf("updateProvider during the parked fetch: %v", err) + { + _, err := f.r.updateProvider(ctx, "discp", protocol.ProviderEdit{BaseUrl: &moved}) + testassert.NoError(t, err) } drainConnectionEvent(t, f.r.warnings, sub, "2") f.captureBaseline(t) @@ -964,13 +850,8 @@ func TestConnectProviderRaceTransportChangedRefuses(t *testing.T) { // — the candidate build drops it, so the connect refuses before any write. func TestConnectProviderRaceInvalidLatestTransportRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { - f := newConnectRaceFixture(t, store) - defer closeProjectionRuntime(f.r) - sub, err := f.r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() + f, sub, closeRuntime := newConnectRaceFixture(t, store) + defer closeRuntime() key := connectionKey f.start("discp", &key) @@ -983,9 +864,7 @@ func TestConnectProviderRaceInvalidLatestTransportRefuses(t *testing.T) { oldDiscp := `"discp": {"transport": {"base_url": "` + f.endpoint + `", "api_key_env": "CONNECTION_DISC_KEY"}, "discovery": true, "models": {"ghost": {"name": "Ghost"}}}` newDiscp := `"discp": {"transport": {}, "discovery": true, "models": {"ghost": {"name": "Ghost"}}}` edited := strings.Replace(connectionProvidersDocument(f.endpoint), oldDiscp, newDiscp, 1) - if edited == connectionProvidersDocument(f.endpoint) { - t.Fatal("fixture replacement did not apply") - } + testassert.Require(t, edited != connectionProvidersDocument(f.endpoint), "fixture replacement did not apply") writeServiceFile(t, f.r.config.configPath, edited) f.captureBaseline(t) f.proceed() @@ -997,14 +876,9 @@ func TestConnectProviderRaceInvalidLatestTransportRefuses(t *testing.T) { func TestConnectProviderRaceConcurrentModelsSkipsCacheWrite(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, _, _ := runtimeMutationBaseline(t, r) arrive, release := server.park() @@ -1024,8 +898,9 @@ func TestConnectProviderRaceConcurrentModelsSkipsCacheWrite(t *testing.T) { // identity: the overlay AND the cache write are skipped, and the // connect still publishes. window := 4096 - if _, err := r.saveModel(ctx, "discp", "ghost", protocol.ModelEdit{ContextWindow: &window}); err != nil { - t.Fatalf("saveModel during the parked fetch: %v", err) + { + _, err := r.saveModel(ctx, "discp", "ghost", protocol.ModelEdit{ContextWindow: &window}) + testassert.NoError(t, err) } proceed() <-done @@ -1035,16 +910,12 @@ func TestConnectProviderRaceConcurrentModelsSkipsCacheWrite(t *testing.T) { // and the sequence ends with one silence check. nextConfigurationEvent(t, sub, "2") consumeOptionalWarningEvent(t, sub, r.warnings) - if result.err != nil || result.mutation.ConfigurationRevision.Generation != "3" { - t.Fatalf("raced connect = (%v, %+v), want success at generation 3", result.err, result.mutation) - } + testassert.Require(t, result.err == nil && result.mutation.ConfigurationRevision.Generation == "3", "raced connect = (%v, %+v), want success at generation 3", result.err, result.mutation) nextConfigurationEvent(t, sub, "3") consumeOptionalWarningEvent(t, sub, r.warnings) assertNoEvent(t, sub) after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) == string(before) { - t.Fatalf("the connect published over the wrong bytes (%v)", err) - } + testassert.Require(t, err == nil && string(after) != string(before), "the connect published over the wrong bytes (%v)", err) // The fetched payload never reached the cache: the concurrent // writer's models made the write unnecessary. assertDiscoveryCacheLacks(t, r, "discp", "ghost") @@ -1059,18 +930,13 @@ func TestConnectProviderDiscoveryFilteredRefuses(t *testing.T) { // The fetch succeeds but never fills the declared windowless model: // no usable effective candidate, no publication, no cache write. server := newConnectionDiscoveryServer(t, connectionFullDiscovery) // the fetched model id never fills the declared ghost - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) key := connectionKey - _, err = r.connectProvider(ctx, "discp", &key) + _, err := r.connectProvider(ctx, "discp", &key) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) assertDiscoveryCacheLacks(t, r, "discp", "ghost") }) @@ -1079,19 +945,12 @@ func TestConnectProviderDiscoveryFilteredRefuses(t *testing.T) { func TestConnectProviderCacheContentionRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) // A foreign holder keeps the per-provider discovery lock across the // connect's cache write. @@ -1123,19 +982,12 @@ func TestConnectProviderCacheContentionRefuses(t *testing.T) { func TestConnectProviderCacheWrittenKeyWriteFails(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) // Fail only the .env atomic write's temp sync: the discovery cache // write is a different directory and completes first. The scoped @@ -1150,18 +1002,10 @@ func TestConnectProviderCacheWrittenKeyWriteFails(t *testing.T) { records, _ := catalog.ReadDiscoveryCache(r.config.loader.Home()) record, ok := records["groq"] - if !ok || record.Models["ghost"].ContextWindow != 4096 { - t.Fatalf("cache record after the failed key write = %+v, want the fetched ghost model", record) - } - if !record.BoundTo(r.config.current().catalog.Providers["groq"].Transport) { - t.Fatal("the partial cache record lost its configured transport binding") - } - if record.FetchedAt.IsZero() || record.AttemptedAt.IsZero() { - t.Fatalf("partial cache record timestamps = (%v, %v), want both stamped", record.FetchedAt, record.AttemptedAt) - } - if r.managedEnv.IsManaged("CONNECTION_DISC_KEY") || os.Getenv("CONNECTION_DISC_KEY") != "" { - t.Fatal("the failed key write left managed/env state behind") - } + testassert.Require(t, ok && record.Models["ghost"].ContextWindow == 4096, "cache record after the failed key write = %+v, want the fetched ghost model", record) + testassert.Require(t, record.BoundTo(r.config.current().catalog.Providers["groq"].Transport), "the partial cache record lost its configured transport binding") + testassert.Require(t, !record.FetchedAt.IsZero() && !record.AttemptedAt.IsZero(), "partial cache record timestamps = (%v, %v), want both stamped", record.FetchedAt, record.AttemptedAt) + testassert.Require(t, !r.managedEnv.IsManaged("CONNECTION_DISC_KEY") && os.Getenv("CONNECTION_DISC_KEY") == "", "the failed key write left managed/env state behind") assertDotenvUnchanged(t, r, envBefore, "the failed key write") }) } @@ -1169,14 +1013,9 @@ func TestConnectProviderCacheWrittenKeyWriteFails(t *testing.T) { func TestConnectProviderCacheUnsafeIdentityRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_BAD_KEY") // A provider identity the catalog's own rules accept — the separate @@ -1186,14 +1025,15 @@ func TestConnectProviderCacheUnsafeIdentityRefuses(t *testing.T) { `"keylessp"`, `"bad\\id": {"transport": {"base_url": "`+server.URL+`", "api_key_env": "CONNECTION_BAD_KEY"}, "discovery": true, "models": {"ghost": {}}}, "keylessp"`, 1) writeServiceFile(t, r.config.configPath, raw) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") before, generation, warnRev := runtimeMutationBaseline(t, r) key := connectionKey - _, err = r.connectProvider(ctx, `bad\id`, &key) + _, err := r.connectProvider(ctx, `bad\id`, &key) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) assertDiscoveryCacheLacks(t, r, `bad\id`, "fresh") }) @@ -1202,45 +1042,29 @@ func TestConnectProviderCacheUnsafeIdentityRefuses(t *testing.T) { func TestConnectProviderNULKeyRefusedWithoutFileEffect(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) // The NUL value fails the native env-value preflight before any // owning env-file write: no line, no Setenv, no managed membership. broken := "sk-\x00-broken" _, err = r.connectProvider(ctx, "usablep", &broken) assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) - if os.Getenv("CONNECTION_USABLE_KEY") != "" || r.managedEnv.IsManaged("CONNECTION_USABLE_KEY") { - t.Fatal("the refused NUL write left env state behind") - } + testassert.Require(t, os.Getenv("CONNECTION_USABLE_KEY") == "" && !r.managedEnv.IsManaged("CONNECTION_USABLE_KEY"), "the refused NUL write left env state behind") assertDotenvUnchanged(t, r, envBefore, "the NUL refusal") - if strings.Contains(err.Error(), broken) { - t.Fatal("the refusal error carries the key value") - } + testassert.Require(t, !strings.Contains(err.Error(), broken), "the refusal error carries the key value") }) } func TestConnectProviderFetchCancellationBeforePersistence(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() _, generation, warnRev := runtimeMutationBaseline(t, r) arrive, release := server.park() @@ -1254,10 +1078,8 @@ func TestConnectProviderFetchCancellationBeforePersistence(t *testing.T) { <-arrive cancel() close(release) - err = <-done - if !errors.Is(err, context.Canceled) { - t.Fatalf("canceled fetch = %v, want the unwrapped context error", err) - } + err := <-done + testassert.Require(t, errors.Is(err, context.Canceled), "canceled fetch = %v, want the unwrapped context error", err) assertNoConnectionPublication(t, r, sub, generation, warnRev) assertDiscoveryCacheLacks(t, r, "discp", "ghost") }) @@ -1266,13 +1088,8 @@ func TestConnectProviderFetchCancellationBeforePersistence(t *testing.T) { func TestConnectProviderPublicationSurvivesLateCancellation(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() // Park the discovery-cache write's temp sync: the caller cancels // after the final cancellation check, once persistence has begun. @@ -1294,9 +1111,7 @@ func TestConnectProviderPublicationSurvivesLateCancellation(t *testing.T) { cancel() probe.park = false probe.release <- struct{}{} - if err := <-done; err != nil { - t.Fatalf("connect after late cancellation = %v, want the ready publication", err) - } + testassert.NoError(t, <-done) drainConnectionEvent(t, r.warnings, sub, "2") if os.Getenv("CONNECTION_DISC_KEY") != connectionKey || !r.managedEnv.IsManaged("CONNECTION_DISC_KEY") { t.Fatalf("late-canceled connect key state = (%q, %v)", os.Getenv("CONNECTION_DISC_KEY"), r.managedEnv.IsManaged("CONNECTION_DISC_KEY")) @@ -1311,12 +1126,11 @@ func TestConnectProviderClosedOwnerRefuses(t *testing.T) { ctx := context.Background() // After Close: the admission gate refuses. - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) key := connectionKey - if _, err := r.connectProvider(ctx, "discp", &key); !errors.Is(err, ErrClosed) { - t.Fatalf("connect after Close = %v, want ErrClosed", err) + { + _, err := r.connectProvider(ctx, "discp", &key) + testassert.Require(t, errors.Is(err, ErrClosed), "connect after Close = %v, want ErrClosed", err) } assertDiscoveryCacheLacks(t, r, "discp", "ghost") }) @@ -1328,9 +1142,7 @@ func TestConnectProviderOwnerCloseDuringFetchRefuses(t *testing.T) { r, _ := openConnectionRuntime(t, store, server.URL) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() _, generation, _ := runtimeMutationBaseline(t, r) arrive, release := server.park() @@ -1348,14 +1160,16 @@ func TestConnectProviderOwnerCloseDuringFetchRefuses(t *testing.T) { close(closeDone) }() close(release) - if err := <-done; !errors.Is(err, ErrClosed) { - t.Fatalf("connect under owner close = %v, want ErrClosed", err) + { + err := <-done + testassert.Require(t, errors.Is(err, ErrClosed), "connect under owner close = %v, want ErrClosed", err) } <-closeDone // The owner's shutdown publishes its own scope events; only the // configuration publication is asserted silent. - if current := r.config.current(); current == nil || current.generation != generation { - t.Fatalf("generation advanced to %v", current) + { + current := r.config.current() + testassert.Require(t, current != nil && current.generation == generation, "generation advanced to %v", current) } assertConnectionEnvSilent(t, r) assertDiscoveryCacheLacks(t, r, "discp", "ghost") @@ -1385,13 +1199,9 @@ func TestDisconnectProviderManagedKeyTable(t *testing.T) { name: "TestDisconnectProviderManagedKey", providerID: "usablep", envName: "CONNECTION_USABLE_KEY", seed: "managed-secret", check: func(t *testing.T, r *Runtime, mutation protocol.ProviderMutation) { - if mutation.Result.Connected || mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceNone) { - t.Fatalf("disconnected provider view = %+v, want the disconnected status", mutation.Result) - } + testassert.Require(t, !mutation.Result.Connected && mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceNone), "disconnected provider view = %+v, want the disconnected status", mutation.Result) envData, err := os.ReadFile(r.managedEnv.Path()) - if err != nil || strings.Contains(string(envData), "CONNECTION_USABLE_KEY=") { - t.Fatalf(".env after disconnect = (%q, %v), want the line removed", envData, err) - } + testassert.Require(t, err == nil && !strings.Contains(string(envData), "CONNECTION_USABLE_KEY="), ".env after disconnect = (%q, %v), want the line removed", envData, err) }, }, } @@ -1399,31 +1209,18 @@ func TestDisconnectProviderManagedKeyTable(t *testing.T) { t.Run(row.name, func(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() - if err := r.managedEnv.TrySet(row.envName, row.seed); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet(row.envName, row.seed)) before, _, _ := runtimeMutationBaseline(t, r) mutation, err := r.disconnectProvider(ctx, row.providerID) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("disconnectProvider = (%v, %+v), want generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "disconnectProvider = (%v, %+v), want generation 2", err, mutation) drainConnectionEvent(t, r.warnings, sub, "2") - if os.Getenv(row.envName) != "" || r.managedEnv.IsManaged(row.envName) { - t.Fatal("the managed key survived the disconnect") - } + testassert.Require(t, os.Getenv(row.envName) == "" && !r.managedEnv.IsManaged(row.envName), "the managed key survived the disconnect") after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(after) != string(before) { - t.Fatalf("disconnect changed the owning config (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(after) == string(before), "disconnect changed the owning config (%v)", rerr) if row.check != nil { row.check(t, r, mutation) } @@ -1445,9 +1242,7 @@ func awaitStackMatch(t *testing.T, what string, match func(string) bool) { return } } - if time.Now().After(deadline) { - t.Fatalf("%s never reached its owner wait", what) - } + testassert.Require(t, !time.Now().After(deadline), "%s never reached its owner wait", what) goruntime.Gosched() } } @@ -1487,12 +1282,8 @@ drain: break drain } body, err := event.MarshalJSON() - if err != nil { - t.Fatalf("event body: %v", err) - } - if strings.Contains(string(body), secret) { - t.Fatalf("an event carries the credential value: %s", body) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(body), secret), "an event carries the credential value: %s", body) default: break drain } @@ -1508,14 +1299,9 @@ drain: func TestConnectProviderOverlappingReadSeesPublishedPair(t *testing.T) { store := storage.NewMemory() server := newConnectionDiscoveryServer(t, connectionGhostDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 16) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(16) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_USABLE_KEY") probe := installOwningSyncProbe(t, r.managedEnv.Path()) defer probe.restore() @@ -1548,23 +1334,12 @@ func TestConnectProviderOverlappingReadSeesPublishedPair(t *testing.T) { release() connect := <-connectDone - if connect.err != nil { - t.Fatalf("connect: %v", connect.err) - } + testassert.Require(t, connect.err == nil, "connect: %v", connect.err) read := <-readDone - if read.err != nil { - t.Fatal(read.err) - } - if read.view.Provider.KeySource != protocol.Managed || !read.view.Provider.Connected { - t.Fatalf("overlapping read = source %v connected %v, want the published managed pair", - read.view.Provider.KeySource, read.view.Provider.Connected) - } - if read.view.ConfigurationRevision.Generation != connect.mutation.ConfigurationRevision.Generation { - t.Fatalf("overlapping read revision = %s, want the connect's published %s", - read.view.ConfigurationRevision.Generation, connect.mutation.ConfigurationRevision.Generation) - } - if body, _ := json.Marshal(read.view); strings.Contains(string(body), key) { - t.Fatal("the provider read carries the credential value") + testassert.Require(t, read.err == nil && read.view.Provider.KeySource == protocol.Managed && read.view.Provider.Connected && read.view.ConfigurationRevision.Generation == connect.mutation.ConfigurationRevision.Generation, "%v; overlapping read = source %v connected %v, want the published managed pair; got %v, want %v", read.err, read.view.Provider.KeySource, read.view.Provider.Connected, read.view.ConfigurationRevision.Generation, connect.mutation.ConfigurationRevision.Generation) + { + body, _ := json.Marshal(read.view) + testassert.Require(t, !strings.Contains(string(body), key), "the provider read carries the credential value") } assertEventStreamSecretFree(t, sub, key) } @@ -1577,21 +1352,12 @@ func TestConnectProviderOverlappingReadSeesPublishedPair(t *testing.T) { func TestDisconnectProviderOverlappingReadStaysCoherent(t *testing.T) { store := storage.NewMemory() server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 16) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(16) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() - if err := r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret"); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret")) before, err := r.getProvider(ctx, "usablep") - if err != nil || before.Provider.KeySource != protocol.Managed || !before.Provider.Connected { - t.Fatalf("initial provider = (%v, %+v), want the managed connected state", err, before.Provider) - } + testassert.Require(t, err == nil && before.Provider.KeySource == protocol.Managed && before.Provider.Connected, "initial provider = (%v, %+v), want the managed connected state", err, before.Provider) probe := installOwningSyncProbe(t, r.managedEnv.Path()) defer probe.restore() probe.park = true @@ -1622,29 +1388,11 @@ func TestDisconnectProviderOverlappingReadStaysCoherent(t *testing.T) { release() disconnect := <-disconnectDone - if disconnect.err != nil { - t.Fatalf("disconnect: %v", disconnect.err) - } + testassert.Require(t, disconnect.err == nil, "disconnect: %v", disconnect.err) read := <-readDone - if read.err != nil { - t.Fatal(read.err) - } - if read.view.Provider.KeySource == protocol.External { - t.Fatalf("read fabricated external ownership: source %v connected %v; the key was managed then absent, never external", - read.view.Provider.KeySource, read.view.Provider.Connected) - } - if read.view.Provider.KeySource != protocol.None || read.view.Provider.Connected { - t.Fatalf("overlapping read = source %v connected %v, want the published removed state", - read.view.Provider.KeySource, read.view.Provider.Connected) - } - if read.view.ConfigurationRevision.Generation != disconnect.mutation.ConfigurationRevision.Generation { - t.Fatalf("overlapping read revision = %s, want the disconnect's published %s", - read.view.ConfigurationRevision.Generation, disconnect.mutation.ConfigurationRevision.Generation) - } + testassert.Require(t, read.err == nil && read.view.Provider.KeySource != protocol.External && read.view.Provider.KeySource == protocol.None && !read.view.Provider.Connected && read.view.ConfigurationRevision.Generation == disconnect.mutation.ConfigurationRevision.Generation, "%v; read fabricated external ownership: source %v connected %v; the key was managed then absent, never external; overlapping read = source %v connected %v, want the published removed state; got %v, want %v", read.err, read.view.Provider.KeySource, read.view.Provider.Connected, read.view.Provider.KeySource, read.view.Provider.Connected, read.view.ConfigurationRevision.Generation, disconnect.mutation.ConfigurationRevision.Generation) after, err := r.getProvider(ctx, "usablep") - if err != nil || after.Provider.KeySource != protocol.None || after.Provider.Connected { - t.Fatalf("final provider = (%v, %+v), want the removed key", err, after.Provider) - } + testassert.Require(t, err == nil && after.Provider.KeySource == protocol.None && !after.Provider.Connected, "final provider = (%v, %+v), want the removed key", err, after.Provider) assertEventStreamSecretFree(t, sub, "managed-secret") } @@ -1661,8 +1409,9 @@ func TestMutationResultStaysFrozenAfterNextPublication(t *testing.T) { svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) svc.attachWarnings(newWarningStore()) svc.attachEnv(env) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } ctx := context.Background() @@ -1672,26 +1421,24 @@ func TestMutationResultStaysFrozenAfterNextPublication(t *testing.T) { baseURL := "https://frozen.test/v1" captureN, err := svc.mutate(ctx, svc.editProviderCreate("frozen", protocol.ProviderEdit{BaseUrl: &baseURL}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &key)) - if err != nil { - t.Fatalf("create: %v", err) - } + testassert.NoError(t, err) provN := captureN.snapshot.catalog.Providers["frozen"] - if provN == nil { - t.Fatal("created provider missing from the capture") - } + testassert.Require(t, provN != nil, "created provider missing from the capture") keyEnv := provN.Transport.APIKeyEnv - if observed := captureN.credentials[keyEnv]; !observed.Managed || observed.Value != key { - t.Fatalf("capture N credential = {defined:%v managed:%v valueMatch:%v}, want the managed supplied key", - observed.Defined, observed.Managed, observed.Value == key) + { + observed := captureN.credentials[keyEnv] + testassert.Require(t, observed.Managed && observed.Value == key, "capture N credential = {defined:%v managed:%v valueMatch:%v}, want the managed supplied key", observed.Defined, observed.Managed, observed.Value == key) } generationN := strconv.FormatUint(captureN.snapshot.generation, 10) // N+1: another real credential mutation removes the key and publishes. - if _, err := svc.mutate(ctx, svc.editProviderDisconnect("frozen", &connectionEffects{})); err != nil { - t.Fatalf("disconnect: %v", err) + { + _, err := svc.mutate(ctx, svc.editProviderDisconnect("frozen", &connectionEffects{})) + testassert.NoError(t, err) } - if current := svc.current(); current == nil || current.generation != captureN.snapshot.generation+1 { - t.Fatalf("N+1 publication = %+v, want the next generation", current) + { + current := svc.current() + testassert.Require(t, current != nil && current.generation == captureN.snapshot.generation+1, "N+1 publication = %+v, want the next generation", current) } // The actual shared projector, invoked with N's returned capture after @@ -1700,66 +1447,40 @@ func TestMutationResultStaysFrozenAfterNextPublication(t *testing.T) { ConfigurationRevision: configurationRevision(captureN.snapshot), Result: providerPostState(captureN, provN.ID), }) - if err != nil { - t.Fatalf("encode N's response: %v", err) - } + testassert.NoError(t, err) var decoded protocol.ProviderMutation - if err := json.Unmarshal(encoded, &decoded); err != nil { - t.Fatalf("decode N's response: %v", err) - } - if decoded.ConfigurationRevision.Generation != generationN { - t.Fatalf("N response revision = %s, want %s", decoded.ConfigurationRevision.Generation, generationN) - } - if decoded.Result.KeySource != protocol.Managed || !decoded.Result.Connected { - t.Fatalf("N response labels = source %v connected %v, want the pre-change managed pair", - decoded.Result.KeySource, decoded.Result.Connected) - } + testassert.NoError(t, json.Unmarshal(encoded, &decoded)) + testassert.Require(t, decoded.ConfigurationRevision.Generation == generationN && decoded.Result.KeySource == protocol.Managed && decoded.Result.Connected, "got %v, want %v; N response labels = source %v connected %v, want the pre-change managed pair", decoded.ConfigurationRevision.Generation, generationN, decoded.Result.KeySource, decoded.Result.Connected) // Nearest forbidden sibling: N+1's own capture projects the removed pair. after, err := svc.capture(ctx) - if err != nil { - t.Fatalf("capture after N+1: %v", err) - } + testassert.NoError(t, err) afterProv := after.snapshot.catalog.Providers["frozen"] - if afterProv == nil { - t.Fatal("the disconnected provider left the catalog") - } - if view := projectProvider(after, afterProv); view.KeySource != protocol.None || view.Connected { - t.Fatalf("N+1 projection = %+v, want the removed key", view) + testassert.Require(t, afterProv != nil, "the disconnected provider left the catalog") + { + view := projectProvider(after, afterProv) + testassert.Require(t, view.KeySource == protocol.None && !view.Connected, "N+1 projection = %+v, want the removed key", view) } } func TestDisconnectProviderRefusals(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) // A keyless provider is removed, not disconnected: the nearest // forbidden sibling of the successful disconnect. - _, err = r.disconnectProvider(ctx, "keylessp") - if err == nil || !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("keyless disconnect = %v, want the invalid refusal", err) - } + _, err := r.disconnectProvider(ctx, "keylessp") assertRuntimeMutationRefused(t, r, sub, before, generation, warnRev, err, harness.ErrInvalid) // An external set key refuses, naming the env variable alone — no // value exists in a Lightcode store, and none is invented. t.Setenv("CONNECTION_STATIC_KEY", "shell-exported-value") _, err = r.disconnectProvider(ctx, "staticp") - if err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("external disconnect = %v, want the typed refusal", err) - } - if !strings.Contains(err.Error(), "CONNECTION_STATIC_KEY") || strings.Contains(err.Error(), "shell-exported-value") { - t.Fatalf("external disconnect error %q, want the env name without any value", err) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "CONNECTION_STATIC_KEY") && !strings.Contains(err.Error(), "shell-exported-value"), "external disconnect error %q, want the env name without any value", err) assertRuntimeMutationRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) }) } @@ -1767,55 +1488,38 @@ func TestDisconnectProviderRefusals(t *testing.T) { func TestDisconnectProviderAbsentUnmanagedSucceeds(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, _, _ := runtimeMutationBaseline(t, r) // The env name is set nowhere: the disconnect publishes the ready // next generation through the same path, touching nothing. mutation, err := r.disconnectProvider(ctx, "staticp") - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("absent-key disconnect = (%v, %+v), want generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "absent-key disconnect = (%v, %+v), want generation 2", err, mutation) drainConnectionEvent(t, r.warnings, sub, "2") after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("absent-key disconnect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(after) == string(before), "absent-key disconnect changed the owning config (%v)", err) }) } func TestDisconnectProviderRepeatStillPublishes(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() - if err := r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret"); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret")) - if _, err := r.disconnectProvider(ctx, "usablep"); err != nil { - t.Fatalf("first disconnect: %v", err) + { + _, err := r.disconnectProvider(ctx, "usablep") + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") // The second disconnect takes the absent-unmanaged path and STILL // publishes the ready next generation. second, err := r.disconnectProvider(ctx, "usablep") - if err != nil || second.ConfigurationRevision.Generation != "3" { - t.Fatalf("repeat disconnect = (%v, %+v), want generation 3", err, second) - } + testassert.Require(t, err == nil && second.ConfigurationRevision.Generation == "3", "repeat disconnect = (%v, %+v), want generation 3", err, second) drainConnectionEvent(t, r.warnings, sub, "3") }) } @@ -1827,44 +1531,27 @@ func TestDisconnectProviderRepeatStillPublishes(t *testing.T) { func TestDisconnectProviderLatestRawTransportChangePublishes(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() - if err := r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret"); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret")) moved := "https://moved.test/v1" oldLine := `"usablep": {"transport": {"base_url": "` + server.URL + `", "api_key_env": "CONNECTION_USABLE_KEY"}, "discovery": true, "models": {"m": {"context_window": 4096}}}` newLine := `"usablep": {"transport": {"base_url": "` + moved + `", "api_key_env": "CONNECTION_USABLE_KEY"}, "discovery": true, "models": {"m": {"context_window": 4096}}}` doc := connectionProvidersDocument(server.URL) edited := strings.Replace(doc, oldLine, newLine, 1) - if edited == doc { - t.Fatal("fixture replacement did not apply") - } + testassert.Require(t, edited != doc, "fixture replacement did not apply") writeServiceFile(t, r.config.configPath, edited) before, _, _ := runtimeMutationBaseline(t, r) mutation, err := r.disconnectProvider(ctx, "usablep") - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("latest-raw disconnect = (%v, %+v), want generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "latest-raw disconnect = (%v, %+v), want generation 2", err, mutation) drainConnectionEvent(t, r.warnings, sub, "2") - if mutation.Result.BaseUrl != moved || mutation.Result.ApiKeyEnv != "CONNECTION_USABLE_KEY" || mutation.Result.Connected { - t.Fatalf("disconnect result = %+v, want the latest raw transport and the disconnected status", mutation.Result) - } - if os.Getenv("CONNECTION_USABLE_KEY") != "" || r.managedEnv.IsManaged("CONNECTION_USABLE_KEY") { - t.Fatal("the published binding's managed key survived the disconnect") - } + testassert.Require(t, mutation.Result.BaseUrl == moved && mutation.Result.ApiKeyEnv == "CONNECTION_USABLE_KEY" && !mutation.Result.Connected, "disconnect result = %+v, want the latest raw transport and the disconnected status", mutation.Result) + testassert.Require(t, os.Getenv("CONNECTION_USABLE_KEY") == "" && !r.managedEnv.IsManaged("CONNECTION_USABLE_KEY"), "the published binding's managed key survived the disconnect") after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(after) != string(before) { - t.Fatalf("disconnect changed the externally edited config (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(after) == string(before), "disconnect changed the externally edited config (%v)", rerr) }) } @@ -1875,17 +1562,10 @@ func TestDisconnectProviderLatestRawTransportChangePublishes(t *testing.T) { func TestDisconnectProviderLatestRawEnvRebindKeepsExternalSibling(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() - if err := r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret"); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret")) reserveEnvKey(t, "CONNECTION_REBOUND_KEY") t.Setenv("CONNECTION_REBOUND_KEY", "external-rebound-value") @@ -1893,34 +1573,22 @@ func TestDisconnectProviderLatestRawEnvRebindKeepsExternalSibling(t *testing.T) newLine := `"usablep": {"transport": {"base_url": "` + server.URL + `", "api_key_env": "CONNECTION_REBOUND_KEY"}, "discovery": true, "models": {"m": {"context_window": 4096}}}` doc := connectionProvidersDocument(server.URL) edited := strings.Replace(doc, oldLine, newLine, 1) - if edited == doc { - t.Fatal("fixture replacement did not apply") - } + testassert.Require(t, edited != doc, "fixture replacement did not apply") writeServiceFile(t, r.config.configPath, edited) before, _, _ := runtimeMutationBaseline(t, r) mutation, err := r.disconnectProvider(ctx, "usablep") - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("rebound disconnect = (%v, %+v), want generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "rebound disconnect = (%v, %+v), want generation 2", err, mutation) drainConnectionEvent(t, r.warnings, sub, "2") - if mutation.Result.ApiKeyEnv != "CONNECTION_REBOUND_KEY" || mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceExternal) || !mutation.Result.Connected { - t.Fatalf("rebound disconnect result = %+v, want the latest external binding", mutation.Result) - } - if os.Getenv("CONNECTION_USABLE_KEY") != "" || r.managedEnv.IsManaged("CONNECTION_USABLE_KEY") { - t.Fatal("the published binding's managed key survived the rebinding disconnect") - } + testassert.Require(t, mutation.Result.ApiKeyEnv == "CONNECTION_REBOUND_KEY" && mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceExternal) && mutation.Result.Connected, "rebound disconnect result = %+v, want the latest external binding", mutation.Result) + testassert.Require(t, os.Getenv("CONNECTION_USABLE_KEY") == "" && !r.managedEnv.IsManaged("CONNECTION_USABLE_KEY"), "the published binding's managed key survived the rebinding disconnect") if os.Getenv("CONNECTION_REBOUND_KEY") != "external-rebound-value" || r.managedEnv.IsManaged("CONNECTION_REBOUND_KEY") { t.Fatalf("external sibling state = (%q, %v), want it untouched and unmanaged", os.Getenv("CONNECTION_REBOUND_KEY"), r.managedEnv.IsManaged("CONNECTION_REBOUND_KEY")) } envAfter, rerr := os.ReadFile(r.managedEnv.Path()) - if rerr != nil || strings.Contains(string(envAfter), "CONNECTION_USABLE_KEY=") || strings.Contains(string(envAfter), "CONNECTION_REBOUND_KEY=") { - t.Fatalf(".env after the rebinding disconnect = (%q, %v), want the old managed line removed and no external line", envAfter, rerr) - } + testassert.Require(t, rerr == nil && !strings.Contains(string(envAfter), "CONNECTION_USABLE_KEY=") && !strings.Contains(string(envAfter), "CONNECTION_REBOUND_KEY="), ".env after the rebinding disconnect = (%q, %v), want the old managed line removed and no external line", envAfter, rerr) after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(after) != string(before) { - t.Fatalf("disconnect changed the externally edited config (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(after) == string(before), "disconnect changed the externally edited config (%v)", rerr) }) } @@ -1931,36 +1599,23 @@ func TestDisconnectProviderLatestRawEnvRebindKeepsExternalSibling(t *testing.T) func TestDisconnectProviderLatestCandidateRemovedRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() - if err := r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret"); err != nil { - t.Fatalf("seed TrySet: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_USABLE_KEY", "managed-secret")) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) oldTransport := `"transport": {"base_url": "` + server.URL + `", "api_key_env": "CONNECTION_USABLE_KEY"}` doc := connectionProvidersDocument(server.URL) edited := strings.Replace(doc, oldTransport, `"transport": {}`, 1) - if edited == doc { - t.Fatal("fixture replacement did not apply") - } + testassert.Require(t, edited != doc, "fixture replacement did not apply") writeServiceFile(t, r.config.configPath, edited) before, generation, warnRev := runtimeMutationBaseline(t, r) _, err = r.disconnectProvider(ctx, "usablep") assertRuntimeMutationRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) - if os.Getenv("CONNECTION_USABLE_KEY") == "" || !r.managedEnv.IsManaged("CONNECTION_USABLE_KEY") { - t.Fatal("a refused disconnect removed the published binding's key") - } + testassert.Require(t, os.Getenv("CONNECTION_USABLE_KEY") != "" && r.managedEnv.IsManaged("CONNECTION_USABLE_KEY"), "a refused disconnect removed the published binding's key") assertDotenvUnchanged(t, r, envBefore, "the refused disconnect") }) } @@ -1970,14 +1625,9 @@ func TestDisconnectProviderLatestCandidateRemovedRefuses(t *testing.T) { func TestCreateProviderGeneratedEnvNamePersistsExactKey(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "LIGHTCODE_NEWP_API_KEY") key := "sk-live-add-1" @@ -1985,35 +1635,23 @@ func TestCreateProviderGeneratedEnvNamePersistsExactKey(t *testing.T) { baseURL := "https://new.test/v1" mutation, err := r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &key) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("addProvider = (%v, %+v), want generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "addProvider = (%v, %+v), want generation 2", err, mutation) event := drainConnectionEvent(t, r.warnings, sub, "2") // The generated name went to the raw layer, the exact (untrimmed) // value only to the managed env. - if mutation.Result.ApiKeyEnv != "LIGHTCODE_NEWP_API_KEY" { - t.Fatalf("created provider env name = %q, want the generated name", mutation.Result.ApiKeyEnv) - } + testassert.Equal(t, mutation.Result.ApiKeyEnv, "LIGHTCODE_NEWP_API_KEY") raw, err := os.ReadFile(r.config.configPath) - if err != nil || !strings.Contains(string(raw), `"api_key_env": "LIGHTCODE_NEWP_API_KEY"`) || strings.Contains(string(raw), key) { - t.Fatalf("raw config = (%q, %v), want the generated name and never the key value", raw, err) - } + testassert.Require(t, err == nil && strings.Contains(string(raw), `"api_key_env": "LIGHTCODE_NEWP_API_KEY"`) && !strings.Contains(string(raw), key), "raw config = (%q, %v), want the generated name and never the key value", raw, err) if os.Getenv("LIGHTCODE_NEWP_API_KEY") != key || !r.managedEnv.IsManaged("LIGHTCODE_NEWP_API_KEY") { t.Fatalf("managed key state = (%q, %v)", os.Getenv("LIGHTCODE_NEWP_API_KEY"), r.managedEnv.IsManaged("LIGHTCODE_NEWP_API_KEY")) } envData, err := os.ReadFile(r.managedEnv.Path()) - if err != nil || !strings.Contains(string(envData), "LIGHTCODE_NEWP_API_KEY="+key+"\n") { - t.Fatalf(".env = (%q, %v), want the exact untrimmed value", envData, err) - } + testassert.Require(t, err == nil && strings.Contains(string(envData), "LIGHTCODE_NEWP_API_KEY="+key+"\n"), ".env = (%q, %v), want the exact untrimmed value", envData, err) mutBytes, merr := json.Marshal(mutation) - if merr != nil || strings.Contains(string(mutBytes), key) { - t.Fatalf("mutation bytes carry the key value (%v)", merr) - } + testassert.Require(t, merr == nil && !strings.Contains(string(mutBytes), key), "mutation bytes carry the key value (%v)", merr) eventBytes, eerr := json.Marshal(event) - if eerr != nil || strings.Contains(string(eventBytes), key) { - t.Fatalf("event bytes carry the key value (%v)", eerr) - } + testassert.Require(t, eerr == nil && !strings.Contains(string(eventBytes), key), "event bytes carry the key value (%v)", eerr) }) } @@ -2024,18 +1662,11 @@ func TestCreateProviderGeneratedEnvNamePersistsExactKey(t *testing.T) { func TestCreateProviderExplicitEmptyEnvKeylessRefuses(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) before, generation, warnRev := runtimeMutationBaseline(t, r) emptyEnv := "" @@ -2050,9 +1681,10 @@ func TestCreateProviderExplicitEmptyEnvKeylessRefuses(t *testing.T) { assertDotenvUnchanged(t, r, envBefore, "the refused create") // Without a supplied key the explicit keyless create stays keyless. - if _, err := r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &emptyEnv}, - map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, nil); err != nil { - t.Fatalf("keyless create without a supplied key: %v", err) + { + _, err := r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &emptyEnv}, + map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, nil) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") assertDotenvUnchanged(t, r, envBefore, "the keyless create") @@ -2066,20 +1698,13 @@ func TestCreateProviderExplicitEmptyEnvKeylessRefuses(t *testing.T) { func TestCreateProviderExternalBindingWithKeyRefusesBeforeWrites(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_SHELL_KEY") t.Setenv("CONNECTION_SHELL_KEY", "shell-exported-value") envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) before, generation, warnRev := runtimeMutationBaseline(t, r) key := connectionKey @@ -2126,26 +1751,18 @@ func TestCreateProviderKeySourceTable(t *testing.T) { key: ptrTo(orphanReplacement), arrange: func(t *testing.T, r *Runtime) { reserveEnvKey(t, "CONNECTION_ORPHAN_KEY") - if err := r.managedEnv.TrySet("CONNECTION_ORPHAN_KEY", "stale-managed-value"); err != nil { - t.Fatalf("seed managed orphan: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_ORPHAN_KEY", "stale-managed-value")) }, check: func(t *testing.T, r *Runtime, mutation protocol.ProviderMutation) { if os.Getenv("CONNECTION_ORPHAN_KEY") != orphanReplacement || !r.managedEnv.IsManaged("CONNECTION_ORPHAN_KEY") { t.Fatalf("managed orphan state = (%q, %v), want the supplied replacement", os.Getenv("CONNECTION_ORPHAN_KEY"), r.managedEnv.IsManaged("CONNECTION_ORPHAN_KEY")) } envData, rerr := os.ReadFile(r.managedEnv.Path()) - if rerr != nil || !strings.Contains(string(envData), "CONNECTION_ORPHAN_KEY="+orphanReplacement+"\n") || strings.Contains(string(envData), "stale-managed-value") { - t.Fatalf(".env = (%q, %v), want the replacement line only", envData, rerr) - } + testassert.Require(t, rerr == nil && strings.Contains(string(envData), "CONNECTION_ORPHAN_KEY="+orphanReplacement+"\n") && !strings.Contains(string(envData), "stale-managed-value"), ".env = (%q, %v), want the replacement line only", envData, rerr) raw, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || !strings.Contains(string(raw), `"api_key_env": "CONNECTION_ORPHAN_KEY"`) || strings.Contains(string(raw), orphanReplacement) { - t.Fatalf("raw config = (%q, %v), want the name only and never the value", raw, rerr) - } + testassert.Require(t, rerr == nil && strings.Contains(string(raw), `"api_key_env": "CONNECTION_ORPHAN_KEY"`) && !strings.Contains(string(raw), orphanReplacement), "raw config = (%q, %v), want the name only and never the value", raw, rerr) mutBytes, merr := json.Marshal(mutation) - if merr != nil || strings.Contains(string(mutBytes), orphanReplacement) { - t.Fatalf("mutation bytes carry the key value (%v)", merr) - } + testassert.Require(t, merr == nil && !strings.Contains(string(mutBytes), orphanReplacement), "mutation bytes carry the key value (%v)", merr) }, }, { @@ -2153,9 +1770,7 @@ func TestCreateProviderKeySourceTable(t *testing.T) { key: ptrTo(emptyOrphanReplacement), arrange: func(t *testing.T, r *Runtime) { reserveEnvKey(t, "CONNECTION_EMPTY_ORPHAN_KEY") - if err := r.managedEnv.TrySet("CONNECTION_EMPTY_ORPHAN_KEY", ""); err != nil { - t.Fatalf("seed managed-empty name: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_EMPTY_ORPHAN_KEY", "")) }, check: func(t *testing.T, r *Runtime, mutation protocol.ProviderMutation) { if os.Getenv("CONNECTION_EMPTY_ORPHAN_KEY") != emptyOrphanReplacement || !r.managedEnv.IsManaged("CONNECTION_EMPTY_ORPHAN_KEY") { @@ -2167,14 +1782,10 @@ func TestCreateProviderKeySourceTable(t *testing.T) { name: "TestCreateProviderManagedNameWithoutSuppliedKeyUsesExisting", envName: "CONNECTION_EXISTING_KEY", arrange: func(t *testing.T, r *Runtime) { reserveEnvKey(t, "CONNECTION_EXISTING_KEY") - if err := r.managedEnv.TrySet("CONNECTION_EXISTING_KEY", "existing-managed-value"); err != nil { - t.Fatalf("seed managed name: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("CONNECTION_EXISTING_KEY", "existing-managed-value")) }, check: func(t *testing.T, r *Runtime, mutation protocol.ProviderMutation) { - if mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceManaged) { - t.Fatalf("created provider key source = %q, want managed", mutation.Result.KeySource) - } + testassert.Require(t, mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceManaged), "created provider key source = %q, want managed", mutation.Result.KeySource) if os.Getenv("CONNECTION_EXISTING_KEY") != "existing-managed-value" || !r.managedEnv.IsManaged("CONNECTION_EXISTING_KEY") { t.Fatalf("managed value changed without a supplied key: (%q, %v)", os.Getenv("CONNECTION_EXISTING_KEY"), r.managedEnv.IsManaged("CONNECTION_EXISTING_KEY")) } @@ -2187,12 +1798,8 @@ func TestCreateProviderKeySourceTable(t *testing.T) { t.Setenv("CONNECTION_SHELL_KEY", "shell-exported-value") }, check: func(t *testing.T, r *Runtime, mutation protocol.ProviderMutation) { - if mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceExternal) { - t.Fatalf("created provider key source = %q, want external", mutation.Result.KeySource) - } - if r.managedEnv.IsManaged("CONNECTION_SHELL_KEY") { - t.Fatal("the no-key create managed the external variable") - } + testassert.Require(t, mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceExternal), "created provider key source = %q, want external", mutation.Result.KeySource) + testassert.Require(t, !r.managedEnv.IsManaged("CONNECTION_SHELL_KEY"), "the no-key create managed the external variable") }, }, { @@ -2201,11 +1808,10 @@ func TestCreateProviderKeySourceTable(t *testing.T) { reserveEnvKey(t, "CONNECTION_MISSING_KEY") }, check: func(t *testing.T, r *Runtime, mutation protocol.ProviderMutation) { - if mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceNone) || mutation.Result.ApiKeyEnv != "CONNECTION_MISSING_KEY" { - t.Fatalf("created provider = (%q, %q), want the none source with the named binding", mutation.Result.KeySource, mutation.Result.ApiKeyEnv) - } - if _, present := metadataRawProvider(t, r.config.configPath, "newp")["transport"]; !present { - t.Fatal("the created provider missing from the raw layer") + testassert.Require(t, mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceNone) && mutation.Result.ApiKeyEnv == "CONNECTION_MISSING_KEY", "created provider = (%q, %q), want the none source with the named binding", mutation.Result.KeySource, mutation.Result.ApiKeyEnv) + { + _, present := metadataRawProvider(t, r.config.configPath, "newp")["transport"] + testassert.Require(t, present, "the created provider missing from the raw layer") } }, }, @@ -2214,33 +1820,25 @@ func TestCreateProviderKeySourceTable(t *testing.T) { t.Run(row.name, func(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() row.arrange(t, r) // Only the creates without a supplied key promise silence: the // supplied-key rows write a value by design. var envBefore []byte + var err error if row.key == nil { envBefore, err = os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) } window := 4096 baseURL := "https://new.test/v1" mutation, err := r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &row.envName}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, row.key) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("key-source create = (%v, %+v), want generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "key-source create = (%v, %+v), want generation 2", err, mutation) drainConnectionEvent(t, r.warnings, sub, "2") if row.key == nil { assertDotenvUnchanged(t, r, envBefore, row.name) @@ -2261,14 +1859,9 @@ func TestCreateProviderKeySourceTable(t *testing.T) { func TestCreateProviderGeneratedNameSkipsOccupiedNames(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() window := 4096 baseURL := "https://new.test/v1" @@ -2277,10 +1870,11 @@ func TestCreateProviderGeneratedNameSkipsOccupiedNames(t *testing.T) { // references that base name. holderEnv := "LIGHTCODE_CATALOGREF_API_KEY" reserveEnvKey(t, holderEnv) - if _, err := r.addProvider(ctx, "catrefholder", - protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &holderEnv}, - map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, nil); err != nil { - t.Fatalf("catalog-reference holder: %v", err) + { + _, err := r.addProvider(ctx, "catrefholder", + protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &holderEnv}, + map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, nil) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") @@ -2293,9 +1887,7 @@ func TestCreateProviderGeneratedNameSkipsOccupiedNames(t *testing.T) { }{ {"managed orphan", "newp", "LIGHTCODE_NEWP_API_KEY", func(t *testing.T) { - if err := r.managedEnv.TrySet("LIGHTCODE_NEWP_API_KEY", "stale-managed-value"); err != nil { - t.Fatalf("seed managed orphan: %v", err) - } + testassert.NoError(t, r.managedEnv.TrySet("LIGHTCODE_NEWP_API_KEY", "stale-managed-value")) }, func(t *testing.T, base string) { if os.Getenv(base) != "stale-managed-value" || !r.managedEnv.IsManaged(base) { @@ -2320,9 +1912,7 @@ func TestCreateProviderGeneratedNameSkipsOccupiedNames(t *testing.T) { {"catalog referenced", "catalogref", "LIGHTCODE_CATALOGREF_API_KEY", func(t *testing.T) {}, func(t *testing.T, base string) { - if got := r.config.current().catalog.Providers["catrefholder"].Transport.APIKeyEnv; got != base { - t.Fatalf("holder binding after allocation = %q, want the reference intact", got) - } + testassert.Equal(t, r.config.current().catalog.Providers["catrefholder"].Transport.APIKeyEnv, base) if value, defined := os.LookupEnv(base); defined || value != "" || r.managedEnv.IsManaged(base) { t.Fatalf("the referenced name gained a value: (%q, defined %v, managed %v)", value, defined, r.managedEnv.IsManaged(base)) } @@ -2338,17 +1928,11 @@ func TestCreateProviderGeneratedNameSkipsOccupiedNames(t *testing.T) { key := "sk-live-" + row.id mutation, err := r.addProvider(ctx, row.id, protocol.ProviderEdit{BaseUrl: &baseURL}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &key) - if err != nil { - t.Fatalf("generated-collision create = %v, want the first free suffixed allocation", err) - } + testassert.NoError(t, err) generation++ - if mutation.ConfigurationRevision.Generation != strconv.Itoa(generation) { - t.Fatalf("create generation = %q, want %d", mutation.ConfigurationRevision.Generation, generation) - } + testassert.Require(t, mutation.ConfigurationRevision.Generation == strconv.Itoa(generation), "create generation = %q, want %d", mutation.ConfigurationRevision.Generation, generation) drainConnectionEvent(t, r.warnings, sub, strconv.Itoa(generation)) - if mutation.Result.ApiKeyEnv != row.base+"_2" || mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceManaged) { - t.Fatalf("generated result = %+v, want the actual %q binding under the managed source", mutation.Result, row.base+"_2") - } + testassert.Require(t, mutation.Result.ApiKeyEnv == row.base+"_2" && mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceManaged), "generated result = %+v, want the actual %q binding under the managed source", mutation.Result, row.base+"_2") if os.Getenv(row.base+"_2") != key || !r.managedEnv.IsManaged(row.base+"_2") { t.Fatalf("persisted binding = (%q, %v), want the exact supplied bytes under the fresh name", os.Getenv(row.base+"_2"), r.managedEnv.IsManaged(row.base+"_2")) } @@ -2361,20 +1945,13 @@ func TestCreateProviderGeneratedNameSkipsOccupiedNames(t *testing.T) { func TestCreateProviderInvalidCandidateNoWrites(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_RESTORE_KEY") before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) // The candidate-build refusal: a reserved extra_body key drops the // provider from the shared validation, so the pre-write subject @@ -2395,20 +1972,13 @@ func TestCreateProviderInvalidCandidateNoWrites(t *testing.T) { func TestCreateProviderKeyFailureRestoresExactBytes(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_RESTORE_KEY") before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) // The NUL value fails the create's native env-value preflight after // the owning file was written: the exact prior bytes are restored, @@ -2420,18 +1990,12 @@ func TestCreateProviderKeyFailureRestoresExactBytes(t *testing.T) { restoreEnv := "CONNECTION_RESTORE_KEY" _, err = r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &restoreEnv}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &broken) - if err == nil || !errors.Is(err, ErrConfiguration) || !strings.Contains(err.Error(), "persist API key") { - t.Fatalf("create with a failing key write = %v, want the joined typed failure", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration) && strings.Contains(err.Error(), "persist API key"), "create with a failing key write = %v, want the joined typed failure", err) assertRuntimeMutationRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(after) != string(before) { - t.Fatalf("the failed create did not restore the exact prior bytes (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(after) == string(before), "the failed create did not restore the exact prior bytes (%v)", rerr) assertDotenvUnchanged(t, r, envBefore, "the NUL refusal") - if os.Getenv("CONNECTION_RESTORE_KEY") != "" { - t.Fatal("the failed NUL write left the process env behind") - } + testassert.Require(t, os.Getenv("CONNECTION_RESTORE_KEY") == "", "the failed NUL write left the process env behind") assertFollowUpEditProvesReleasedOwnership(t, r, "usablep") }) } @@ -2439,19 +2003,12 @@ func TestCreateProviderKeyFailureRestoresExactBytes(t *testing.T) { func TestCreateProviderKeyFailureRestoresPriorAbsence(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_RESTORE_KEY") _, generation, warnRev := runtimeMutationBaseline(t, r) - if err := os.Remove(r.config.configPath); err != nil { - t.Fatalf("remove config: %v", err) - } + testassert.NoError(t, os.Remove(r.config.configPath)) // The owning file was absent before the create: the failed key // write restores that absence. @@ -2459,13 +2016,12 @@ func TestCreateProviderKeyFailureRestoresPriorAbsence(t *testing.T) { window := 4096 baseURL := "https://new.test/v1" restoreEnv := "CONNECTION_RESTORE_KEY" - _, err = r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &restoreEnv}, + _, err := r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &restoreEnv}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &broken) - if err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("create with a failing key write = %v, want the typed failure", err) - } - if _, rerr := os.Stat(r.config.configPath); !os.IsNotExist(rerr) { - t.Fatalf("the prior file absence was not restored (%v)", rerr) + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration), "create with a failing key write = %v, want the typed failure", err) + { + _, rerr := os.Stat(r.config.configPath) + testassert.Require(t, os.IsNotExist(rerr), "the prior file absence was not restored (%v)", rerr) } assertNoConnectionPublication(t, r, sub, generation, warnRev) }) @@ -2474,14 +2030,9 @@ func TestCreateProviderKeyFailureRestoresPriorAbsence(t *testing.T) { func TestCreateProviderKeyFailureJoinedRestoreError(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() reserveEnvKey(t, "CONNECTION_RESTORE_KEY") before, generation, warnRev := runtimeMutationBaseline(t, r) @@ -2506,34 +2057,23 @@ func TestCreateProviderKeyFailureJoinedRestoreError(t *testing.T) { window := 4096 baseURL := "https://new.test/v1" restoreEnv := "CONNECTION_RESTORE_KEY" - _, err = r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &restoreEnv}, + _, err := r.addProvider(ctx, "newp", protocol.ProviderEdit{BaseUrl: &baseURL, ApiKeyEnv: &restoreEnv}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &broken) - if err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("create with a failing restore = %v, want the typed failure", err) - } - if !strings.Contains(err.Error(), "persist API key") || !strings.Contains(err.Error(), "restore") { - t.Fatalf("joined failure = %v, want the key failure and the restore failure joined", err) - } + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration), "create with a failing restore = %v, want the typed failure", err) + testassert.Require(t, strings.Contains(err.Error(), "persist API key") && strings.Contains(err.Error(), "restore"), "joined failure = %v, want the key failure and the restore failure joined", err) assertNoConnectionPublication(t, r, sub, generation, warnRev) // The restore failed: the written create bytes are the honest state // on disk, never a claimed rollback. after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(after) == string(before) || !strings.Contains(string(after), "newp") { - t.Fatalf("disk state after the failed restore = (%q, %v), want the written create kept", after, rerr) - } + testassert.Require(t, rerr == nil && string(after) != string(before) && strings.Contains(string(after), "newp"), "disk state after the failed restore = (%q, %v), want the written create kept", after, rerr) }) } func TestCreateProviderRestoreDespiteCancellation(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() before, generation, warnRev := runtimeMutationBaseline(t, r) // Park the create write's own temp sync and cancel the caller: the @@ -2560,15 +2100,11 @@ func TestCreateProviderRestoreDespiteCancellation(t *testing.T) { cancel() probe.park = false probe.release <- struct{}{} - err = <-done - if err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("canceled create = %v, want the typed failure", err) - } + err := <-done + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration), "canceled create = %v, want the typed failure", err) assertRuntimeMutationRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(after) != string(before) { - t.Fatalf("the canceled create did not restore the exact prior bytes (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(after) == string(before), "the canceled create did not restore the exact prior bytes (%v)", rerr) }) } @@ -2577,20 +2113,13 @@ func TestCreateProviderRestoreDespiteCancellation(t *testing.T) { func TestDiscoverCustomProviderPureRead(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() _, generation, warnRev := runtimeMutationBaseline(t, r) t.Setenv("CONNECTION_DISC_REQ_KEY", "request-key-value") envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) fetches := server.requests() headers := map[string]string{"X-Trace": "t1"} @@ -2599,37 +2128,32 @@ func TestDiscoverCustomProviderPureRead(t *testing.T) { ApiKeyEnv: "CONNECTION_DISC_REQ_KEY", Headers: &headers, }) - if err != nil { - t.Fatalf("discoverProvider: %v", err) - } - if len(candidates) != 1 || candidates[0].Id != "fresh" || !candidates[0].Usable || candidates[0].ContextWindow != 4096 { - t.Fatalf("candidates = %+v, want the one usable fetched model", candidates) - } - if candidates[0].Cost == nil || candidates[0].Cost.Input == nil || *candidates[0].Cost.Input != 1.5 { - t.Fatalf("candidate cost = %+v, want the owned fetched cost", candidates[0].Cost) - } - if got := server.requests(); got != fetches+1 { - t.Fatalf("discovery read performed %d fetches, want exactly one", got-fetches) + testassert.NoError(t, err) + testassert.Require(t, len(candidates) == 1 && candidates[0].Id == "fresh" && candidates[0].Usable && candidates[0].ContextWindow == 4096, "candidates = %+v, want the one usable fetched model", candidates) + testassert.Require(t, candidates[0].Cost != nil && candidates[0].Cost.Input != nil && *candidates[0].Cost.Input == 1.5, "candidate cost = %+v, want the owned fetched cost", candidates[0].Cost) + { + got := server.requests() + testassert.Require(t, got == fetches+1, "discovery read performed %d fetches, want exactly one", got-fetches) } if !slices.Contains(server.authorizations(), "Bearer request-key-value") { t.Fatalf("discovery authorizations = %v, want the referenced env's bearer", server.authorizations()) } // The pure read wrote nothing: no cache, no env, no config, no // generation, no event. - if _, err := os.Stat(filepath.Join(r.config.loader.Home(), ".lightcode", "cache", "discovery", "custom.json")); !os.IsNotExist(err) { - t.Fatalf("the discovery read wrote a cache file: %v", err) + { + _, err := os.Stat(filepath.Join(r.config.loader.Home(), ".lightcode", "cache", "discovery", "custom.json")) + testassert.Require(t, os.IsNotExist(err), "the discovery read wrote a cache file: %v", err) } assertNoConnectionPublication(t, r, sub, generation, warnRev) assertDotenvUnchanged(t, r, envBefore, "the discovery read") // The credential-header refusal fires before any fetch. credHeaders := map[string]string{"Authorization": "Bearer forged"} - if _, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{BaseUrl: server.URL + "/v1", Headers: &credHeaders}); err == nil || !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("credential-header discovery = %v, want the invalid refusal", err) - } - if got := server.requests(); got != fetches+1 { - t.Fatalf("the refused discovery fetched (%d -> %d)", fetches+1, got) + { + _, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{BaseUrl: server.URL + "/v1", Headers: &credHeaders}) + testassert.Require(t, err != nil && errors.Is(err, harness.ErrInvalid), "credential-header discovery = %v, want the invalid refusal", err) } + testassert.Equal(t, server.requests(), fetches+1) // A set-but-empty referenced env is no credential: the read fetches // unauthenticated and answers the endpoint, matching saved-provider @@ -2638,21 +2162,19 @@ func TestDiscoverCustomProviderPureRead(t *testing.T) { server.retarget(func(string) string { return connectionFullDiscovery }) t.Setenv("CONNECTION_EMPTY_REQ_KEY", "") emptyAuthCandidates, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{BaseUrl: server.URL + "/v1", ApiKeyEnv: "CONNECTION_EMPTY_REQ_KEY"}) - if err != nil || len(emptyAuthCandidates) != 1 || emptyAuthCandidates[0].Id != "fresh" { - t.Fatalf("set-empty env discovery = (%v, %+v), want the unauthenticated fetch's candidates", err, emptyAuthCandidates) - } + testassert.Require(t, err == nil && len(emptyAuthCandidates) == 1 && emptyAuthCandidates[0].Id == "fresh", "set-empty env discovery = (%v, %+v), want the unauthenticated fetch's candidates", err, emptyAuthCandidates) if auths := server.authorizations(); auths[len(auths)-1] != "" { t.Fatalf("set-empty discovery authorization = %q, want the unauthenticated fetch", auths[len(auths)-1]) } - if got := server.requests(); got != fetches+2 { - t.Fatalf("the set-empty discovery fetched (%d -> %d)", fetches+1, got) - } - if _, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{}); err == nil || !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty base URL discovery = %v, want the invalid refusal", err) + { + got := server.requests() + testassert.Require(t, got == fetches+2, "the set-empty discovery fetched (%d -> %d)", fetches+1, got) } - if got := server.requests(); got != fetches+2 { - t.Fatalf("the refused discovery fetched (%d -> %d)", fetches+2, got) + { + _, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{}) + testassert.Require(t, err != nil && errors.Is(err, harness.ErrInvalid), "empty base URL discovery = %v, want the invalid refusal", err) } + testassert.Equal(t, server.requests(), fetches+2) }) } @@ -2676,28 +2198,19 @@ func TestDiscoverCustomProviderSuppliedKey(t *testing.T) { } return connectionEmptyDiscovery }) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, err := os.ReadFile(r.managedEnv.Path()) - if err != nil { - t.Fatalf("read .env: %v", err) - } + testassert.NoError(t, err) reserveEnvKey(t, "CONNECTION_SUPPLIED_EXT_KEY") reserveEnvKey(t, "CONNECTION_SUPPLIED_UNSET_KEY") t.Setenv("CONNECTION_SUPPLIED_EXT_KEY", external) fetches := server.requests() assertCandidates := func(candidates []protocol.DiscoveredModelCandidate, wantID string) { t.Helper() - if len(candidates) != 1 || candidates[0].Id != wantID { - t.Fatalf("candidates = %+v, want exactly the fetched %q", candidates, wantID) - } + testassert.Require(t, len(candidates) == 1 && candidates[0].Id == wantID, "candidates = %+v, want exactly the fetched %q", candidates, wantID) } // Pre-save pasted-key discovery with no reference: the supplied @@ -2706,9 +2219,7 @@ func TestDiscoverCustomProviderSuppliedKey(t *testing.T) { BaseUrl: server.URL + "/v1", ApiKey: ptrTo(pasted), }) - if err != nil { - t.Fatalf("keyless-reference supplied-key discovery: %v", err) - } + testassert.NoError(t, err) assertCandidates(pastedCandidates, "fresh") // Pre-save pasted-key discovery over an externally owned reference: @@ -2719,9 +2230,7 @@ func TestDiscoverCustomProviderSuppliedKey(t *testing.T) { ApiKeyEnv: "CONNECTION_SUPPLIED_EXT_KEY", ApiKey: ptrTo(pasted), }) - if err != nil { - t.Fatalf("supplied-key discovery: %v", err) - } + testassert.NoError(t, err) assertCandidates(pastedCandidates, "fresh") // Without a supplied key the referenced owner's captured value @@ -2733,18 +2242,14 @@ func TestDiscoverCustomProviderSuppliedKey(t *testing.T) { return connectionEmptyDiscovery }) fallbackCandidates, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{BaseUrl: server.URL + "/v1", ApiKeyEnv: "CONNECTION_SUPPLIED_EXT_KEY"}) - if err != nil { - t.Fatalf("fallback discovery: %v", err) - } + testassert.NoError(t, err) assertCandidates(fallbackCandidates, "ghost") emptySuppliedCandidates, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{ BaseUrl: server.URL + "/v1", ApiKeyEnv: "CONNECTION_SUPPLIED_EXT_KEY", ApiKey: ptrTo(""), }) - if err != nil { - t.Fatalf("empty-supplied-key discovery: %v", err) - } + testassert.NoError(t, err) assertCandidates(emptySuppliedCandidates, "ghost") // A named-unset reference carries no credential: the read fetches @@ -2756,9 +2261,7 @@ func TestDiscoverCustomProviderSuppliedKey(t *testing.T) { return connectionEmptyDiscovery }) unsetCandidates, err := r.discoverProvider(ctx, protocol.DiscoveryRequest{BaseUrl: server.URL + "/v1", ApiKeyEnv: "CONNECTION_SUPPLIED_UNSET_KEY"}) - if err != nil { - t.Fatalf("unset-reference discovery: %v", err) - } + testassert.NoError(t, err) assertCandidates(unsetCandidates, "fresh") // The exact wire credentials per row: the pasted key twice, the @@ -2778,20 +2281,17 @@ func TestDiscoverCustomProviderSuppliedKey(t *testing.T) { assertDotenvUnchanged(t, r, envBefore, "the supplied-key discovery reads") assertNoConnectionPublication(t, r, sub, generation, warnRev) after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("the supplied-key discovery reads changed the owning config (%v)", err) - } - if _, err := os.Stat(filepath.Join(r.config.loader.Home(), ".lightcode", "cache", "discovery", "custom.json")); !os.IsNotExist(err) { - t.Fatalf("the supplied-key discovery reads wrote a cache file: %v", err) + testassert.Require(t, err == nil && string(after) == string(before), "the supplied-key discovery reads changed the owning config (%v)", err) + { + _, err := os.Stat(filepath.Join(r.config.loader.Home(), ".lightcode", "cache", "discovery", "custom.json")) + testassert.Require(t, os.IsNotExist(err), "the supplied-key discovery reads wrote a cache file: %v", err) } for _, row := range []struct { what string candidates []protocol.DiscoveredModelCandidate }{{"pasted", pastedCandidates}, {"fallback", fallbackCandidates}, {"empty-supplied", emptySuppliedCandidates}, {"unset", unsetCandidates}} { raw, merr := json.Marshal(row.candidates) - if merr != nil || strings.Contains(string(raw), pasted) || strings.Contains(string(raw), external) { - t.Fatalf("the %s row's candidates carry a credential value: (%s, %v)", row.what, raw, merr) - } + testassert.Require(t, merr == nil && !strings.Contains(string(raw), pasted) && !strings.Contains(string(raw), external), "the %s row's candidates carry a credential value: (%s, %v)", row.what, raw, merr) } }) } @@ -2804,14 +2304,9 @@ func TestDiscoverProviderModelsFiltersUsable(t *testing.T) { } return `{"data":[{"id":"m","name":"Included","context_window":4096},{"id":"a","name":"A","context_window":0},{"id":"b","name":"B","context_window":8192,"cost":{"input":0.5}}]}` }) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() t.Setenv("CONNECTION_USABLE_KEY", "usable-key-value") _, generation, warnRev := runtimeMutationBaseline(t, r) records, _ := catalog.ReadDiscoveryCache(r.config.loader.Home()) @@ -2819,29 +2314,22 @@ func TestDiscoverProviderModelsFiltersUsable(t *testing.T) { fetches := server.requests() candidates, err := r.discoverProviderModels(ctx, "usablep") - if err != nil { - t.Fatalf("discoverProviderModels: %v", err) - } - if len(candidates) != 2 || candidates[0].Id != "a" || candidates[1].Id != "b" { - t.Fatalf("candidates = %+v, want the sorted filtered pair a,b — the already-included usable m excluded", candidates) - } - if candidates[0].Usable || !candidates[1].Usable || candidates[1].Cost == nil || candidates[1].Cost.Input == nil || *candidates[1].Cost.Input != 0.5 { - t.Fatalf("candidate flags = %+v, want windowless a unusable and usable b with its owned cost", candidates) - } + testassert.NoError(t, err) + testassert.Require(t, len(candidates) == 2 && candidates[0].Id == "a" && candidates[1].Id == "b" && !candidates[0].Usable && candidates[1].Usable && candidates[1].Cost != nil && candidates[1].Cost.Input != nil && *candidates[1].Cost.Input == 0.5, "candidates = %+v, want the sorted filtered pair a,b — the already-included usable m excluded; candidate flags = %+v, want windowless a unusable and usable b with its owned cost", candidates, candidates) // The read wrote nothing: no cache write, no attempt marker, no // generation, no event. - if got := server.requests(); got != fetches+1 { - t.Fatalf("model discovery performed %d fetches, want exactly one", got-fetches) + { + got := server.requests() + testassert.Require(t, got == fetches+1, "model discovery performed %d fetches, want exactly one", got-fetches) } records, _ = catalog.ReadDiscoveryCache(r.config.loader.Home()) - if !records["usablep"].AttemptedAt.Equal(attemptBefore) { - t.Fatal("the failed-attempt marker changed on the model discovery read") - } + testassert.Require(t, records["usablep"].AttemptedAt.Equal(attemptBefore), "the failed-attempt marker changed on the model discovery read") assertNoConnectionPublication(t, r, sub, generation, warnRev) // The typed unknown-provider error and the fetch failure class. - if _, err := r.discoverProviderModels(ctx, "ghost-provider"); !errors.Is(err, catalog.ErrUnknownProvider) { - t.Fatalf("unknown provider discovery = %v, want ErrUnknownProvider", err) + { + _, err := r.discoverProviderModels(ctx, "ghost-provider") + testassert.Require(t, errors.Is(err, catalog.ErrUnknownProvider), "unknown provider discovery = %v, want ErrUnknownProvider", err) } server.retarget(func(auth string) string { return connectionEmptyDiscovery }) // the endpoint stops serving models @@ -2850,23 +2338,18 @@ func TestDiscoverProviderModelsFiltersUsable(t *testing.T) { // .env, config bytes, generation, warnings, and events stay unchanged. before, generation, warnRev := runtimeMutationBaseline(t, r) envBefore, rerr := os.ReadFile(r.managedEnv.Path()) - if rerr != nil { - t.Fatalf("read .env: %v", rerr) - } + testassert.NoError(t, rerr) attemptBefore = records["usablep"].AttemptedAt fetches = server.requests() _, err = r.discoverProviderModels(ctx, "usablep") - if err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("failed model discovery = %v, want the typed refusal", err) - } - if got := server.requests(); got != fetches+1 { - t.Fatalf("failed model discovery performed %d fetches, want exactly one", got-fetches) + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration), "failed model discovery = %v, want the typed refusal", err) + { + got := server.requests() + testassert.Require(t, got == fetches+1, "failed model discovery performed %d fetches, want exactly one", got-fetches) } assertConnectionRefused(t, r, sub, before, generation, warnRev, err, ErrConfiguration) records, _ = catalog.ReadDiscoveryCache(r.config.loader.Home()) - if !records["usablep"].AttemptedAt.Equal(attemptBefore) { - t.Fatal("the attempt marker changed on the failed model discovery read") - } + testassert.Require(t, records["usablep"].AttemptedAt.Equal(attemptBefore), "the attempt marker changed on the failed model discovery read") assertDotenvUnchanged(t, r, envBefore, "the failed model discovery read") // A returned candidate list is a caller copy: mutation cannot affect @@ -2875,15 +2358,11 @@ func TestDiscoverProviderModelsFiltersUsable(t *testing.T) { return `{"data":[{"id":"m","name":"Included","context_window":4096},{"id":"a","name":"A","context_window":0},{"id":"b","name":"B","context_window":8192,"cost":{"input":0.5}}]}` }) copied, err := r.discoverProviderModels(ctx, "usablep") - if err != nil || len(copied) != 2 { - t.Fatalf("copied candidates = (%+v, %v), want the sorted filtered pair", copied, err) - } + testassert.Require(t, err == nil && len(copied) == 2, "copied candidates = (%+v, %v), want the sorted filtered pair", copied, err) copied[0].Id = "mutated" copied[0].Name = "mutated" reread, err := r.discoverProviderModels(ctx, "usablep") - if err != nil || len(reread) != 2 || reread[0].Id != "a" || reread[0].Name != "A" || reread[1].Id != "b" { - t.Fatalf("re-read candidates = (%+v, %v), want the immutable fetched pair", reread, err) - } + testassert.Require(t, err == nil && len(reread) == 2 && reread[0].Id == "a" && reread[0].Name == "A" && reread[1].Id == "b", "re-read candidates = (%+v, %v), want the immutable fetched pair", reread, err) // The skip-all empty result is the non-nil [] shape: every fetched // model is already included and usable. @@ -2891,9 +2370,7 @@ func TestDiscoverProviderModelsFiltersUsable(t *testing.T) { return `{"data":[{"id":"m","name":"Included","context_window":4096}]}` }) empty, err := r.discoverProviderModels(ctx, "usablep") - if err != nil || empty == nil || len(empty) != 0 { - t.Fatalf("skip-all candidates = (%+v, %v), want the non-nil empty list", empty, err) - } + testassert.Require(t, err == nil && empty != nil && len(empty) == 0, "skip-all candidates = (%+v, %v), want the non-nil empty list", empty, err) }) } @@ -2907,36 +2384,28 @@ func TestCreateProviderNilManagedEnvTypedFailure(t *testing.T) { writeServiceFile(t, h.configPath, providerConfigFile("One")) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) svc.attachWarnings(newWarningStore()) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(32) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() + before, first, warnRev := metadataBaseline(t, svc, h.configPath) key := "sk-live-nil-manager" window := 4096 baseURL := "https://nil.test/v1" candidate, err := svc.mutate(context.Background(), svc.editProviderCreate("np", protocol.ProviderEdit{BaseUrl: &baseURL}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, &key)) - if err == nil || !errors.Is(err, ErrConfiguration) || !strings.Contains(err.Error(), "managed env is not initialized") { - t.Fatalf("nil-manager create = (%v, %+v), want the manager's typed failure", err, candidate) - } + testassert.Require(t, err != nil && strings.Contains(err.Error(), "managed env is not initialized"), "nil-manager create = (%v, %+v), want the manager's typed failure", err, candidate) // The exact prior bytes were restored and nothing was published. - data, rerr := os.ReadFile(h.configPath) - if rerr != nil || !strings.Contains(string(data), `"prov"`) || strings.Contains(string(data), `"np"`) { - t.Fatalf("nil-manager create left the file = (%q, %v)", data, rerr) - } - if svc.current().generation != 1 { - t.Fatalf("nil-manager create published generation %d", svc.current().generation) - } - assertNoEvent(t, sub) + assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, err, ErrConfiguration) // resolveConnectKey without a manager or key: the absent path decides. - if _, _, err := resolveConnectKey("CONNECTION_NIL_MGR_KEY", nil, config.EnvValue{}); err == nil { - t.Fatal("resolveConnectKey without a manager or key succeeded") + { + _, _, err := resolveConnectKey("CONNECTION_NIL_MGR_KEY", nil, config.EnvValue{}) + testassert.Require(t, err != nil, "resolveConnectKey without a manager or key succeeded") } // A connect-level key action without the retained manager is the same @@ -2949,16 +2418,8 @@ func TestCreateProviderNilManagedEnvTypedFailure(t *testing.T) { keyEnv: "CONNECTION_NIL_MGR_KEY", keyValue: "sk-nil-manager-connect", })) - if err == nil || !errors.Is(err, ErrConfiguration) || !strings.Contains(err.Error(), "managed env is not initialized") { - t.Fatalf("nil-manager connect = %v, want the manager's typed failure", err) - } - if svc.current().generation != 1 { - t.Fatalf("nil-manager connect published generation %d", svc.current().generation) - } - if data, rerr = os.ReadFile(h.configPath); rerr != nil || !strings.Contains(string(data), `"prov"`) { - t.Fatalf("nil-manager connect changed the file = (%q, %v)", data, rerr) - } - assertNoEvent(t, sub) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "managed env is not initialized"), "nil-manager connect = %v, want the manager's typed failure", err) + assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, err, ErrConfiguration) } // --- full chain --- @@ -3026,51 +2487,37 @@ func TestProductionConnectThenCommandEnvironment(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() server := newChainServer(t, - chainTurn{respond: func(w http.ResponseWriter) { chainToolCallTurn(w, "call-1", "env_echo", `{}`) }}, + chainTurn{respond: func(w http.ResponseWriter) { writeToolCallTurn(w, "call-1", "env_echo", `{}`) }}, chainTurn{respond: func(w http.ResponseWriter) { writeTextTurn(w, "done") }}, ) t.Setenv("CONNECT_CHAIN_EXTERNAL", "sibling-value") reserveEnvKey(t, "CONNECT_CHAIN_MANAGED") - writeServiceFile(t, e.configPath, `{ - "providers": { - "prov": {"transport": {"base_url": "`+server.URL+`", "api_key_env": "PRODUCTION_TEST_KEY"}, "discovery": false, "models": {"m": {"name": "M", "context_window": 262144}}}, - "discp": {"transport": {"base_url": "https://disc.test/v1", "api_key_env": "CONNECT_CHAIN_MANAGED"}, "discovery": false, "models": {"m": {"name": "M", "context_window": 4096}}} - } -}`) + writeServiceFile(t, e.configPath, `{"providers":{`+ + `"prov":`+nominalProvMember(server.URL, "PRODUCTION_TEST_KEY", `{"m":{"name":"M","context_window":262144}}`)+`,`+ + `"discp":`+nominalProvMember("https://disc.test/v1", "CONNECT_CHAIN_MANAGED", `{"m":{"name":"M","context_window":4096}}`)+`}}`) writeServiceFile(t, agents.PathForConfig(e.configPath), `{"runner": {"model": "prov/m", "system_prompt": "simple", "tools": ["env_echo"]}}`) - r, err := Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: []Plugin{ - coreStoragePlugin(e.store), connectEnvEchoPlugin(), - }}) - if err != nil { - t.Fatalf("Open: %v", err) - } + r, err := e.openPlugins(ctx, coreStoragePlugin(e.store), connectEnvEchoPlugin()) + testassert.NoError(t, err) defer r.Close(ctx) // The provider connects after every plugin scope opened. key := "chain-managed-secret" - if _, err := r.connectProvider(ctx, "discp", &key); err != nil { - t.Fatalf("connectProvider: %v", err) + { + _, err := r.connectProvider(ctx, "discp", &key) + testassert.NoError(t, err) } session, err := r.createSession(ctx, e.workspace("connect-ws"), "runner") - if err != nil { - t.Fatalf("createSession: %v", err) - } - if err := os.MkdirAll(e.workspace("connect-ws"), 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, os.MkdirAll(e.workspace("connect-ws"), 0o700)) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "echo") awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) results := chainToolResults(t, e.store, session.Identity.SessionID) settled, ok := results["call-1"] - if !ok || settled.Status != model.ResultSuccess { - t.Fatalf("env_echo result = %+v (ok %v), want success", settled, ok) - } - if settled.Content != "|sibling-value" { - t.Fatalf("child environment markers = %q, want the connected key scrubbed and the shell sibling present", settled.Content) - } + testassert.Require(t, ok && settled.Status == model.ResultSuccess, "env_echo result = %+v (ok %v), want success", settled, ok) + testassert.Equal(t, settled.Content, "|sibling-value") }) } @@ -3082,30 +2529,19 @@ func TestProductionConnectThenCommandEnvironment(t *testing.T) { func TestConnectProviderSpecialIDsAndKeyless(t *testing.T) { eachPrepStoreOnce(t, func(t *testing.T, store harness.Storage) { server := newConnectionDiscoveryServer(t, connectionFullDiscovery) - r, _ := openConnectionRuntime(t, store, server.URL) - defer closeProjectionRuntime(r) + r, _, sub, closeRuntime := openConnectionSubscription(t, store, server.URL, 8) + defer closeRuntime() ctx := context.Background() - sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - defer sub.Close() before, _, _ := runtimeMutationBaseline(t, r) // The keyless usable provider: no credential exists, none is needed, // nothing is written but the ready publication. mutation, err := r.connectProvider(ctx, "keylessusable", nil) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("keyless connect = (%v, %+v), want generation 2", err, mutation) - } - if mutation.Result.KeySource != protocol.ProviderKeySource(config.KeySourceKeyless) || !mutation.Result.Connected { - t.Fatalf("keyless provider view = %+v, want connected keyless", mutation.Result) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "keyless connect = (%v, %+v), want generation 2", err, mutation) + testassert.Require(t, mutation.Result.KeySource == protocol.ProviderKeySource(config.KeySourceKeyless) && mutation.Result.Connected, "keyless provider view = %+v, want connected keyless", mutation.Result) drainConnectionEvent(t, r.warnings, sub, "2") after, err := os.ReadFile(r.config.configPath) - if err != nil || string(after) != string(before) { - t.Fatalf("keyless connect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(after) == string(before), "keyless connect changed the owning config (%v)", err) // The `..` identity is a retained-valid provider ID: addressable // through the connect operator like any other, with its own exact @@ -3114,24 +2550,19 @@ func TestConnectProviderSpecialIDsAndKeyless(t *testing.T) { `"keylessp"`, `"..": {"transport": {"base_url": "https://dots.test/v1", "api_key_env": "CONNECTION_DOTS_KEY"}, "discovery": false, "models": {"m": {"context_window": 4096}}}, "keylessp"`, 1) writeServiceFile(t, r.config.configPath, raw) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "3") dotsBefore, _, _ := runtimeMutationBaseline(t, r) reserveEnvKey(t, "CONNECTION_DOTS_KEY") t.Setenv("CONNECTION_DOTS_KEY", "dots-key-value") dotsMutation, err := r.connectProvider(ctx, "..", nil) - if err != nil || !dotsMutation.Result.Connected || dotsMutation.Result.Id != ".." { - t.Fatalf("dotdot connect = (%v, %+v), want the connected `..` view", err, dotsMutation) - } - if dotsMutation.ConfigurationRevision.Generation != "4" { - t.Fatalf("dotdot connect generation = %q, want 4", dotsMutation.ConfigurationRevision.Generation) - } + testassert.Require(t, err == nil && dotsMutation.Result.Connected && dotsMutation.Result.Id == "..", "dotdot connect = (%v, %+v), want the connected `..` view", err, dotsMutation) + testassert.Equal(t, dotsMutation.ConfigurationRevision.Generation, "4") drainConnectionEvent(t, r.warnings, sub, "4") dotsAfter, err := os.ReadFile(r.config.configPath) - if err != nil || string(dotsAfter) != string(dotsBefore) { - t.Fatalf("dotdot connect changed the owning config (%v)", err) - } + testassert.Require(t, err == nil && string(dotsAfter) == string(dotsBefore), "dotdot connect changed the owning config (%v)", err) }) } diff --git a/runtime/configuration_metadata_test.go b/runtime/configuration_metadata_test.go index 853f1531..8b6c91fd 100644 --- a/runtime/configuration_metadata_test.go +++ b/runtime/configuration_metadata_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/catalog" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -110,13 +111,12 @@ func metadataService(t *testing.T, h *serviceHarness) (*configurationService, *S t.Helper() svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) svc.attachWarnings(newWarningStore()) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(32) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) return svc, sub } @@ -126,21 +126,16 @@ func metadataService(t *testing.T, h *serviceHarness) (*configurationService, *S // generation, and event silence. func assertMetadataRefused(t *testing.T, svc *configurationService, sub *Subscription, configPath string, before []byte, first *configuration, warnRev uint64, err error, want error) { t.Helper() - if err == nil { - t.Fatal("metadata edit succeeded, want a refusal") - } - if want != nil && !errors.Is(err, want) { - t.Fatalf("refusal = %v, want %v", err, want) - } + testassert.Require(t, err != nil, "metadata edit succeeded, want a refusal") + testassert.Require(t, want == nil || errors.Is(err, want), "refusal = %v, want %v", err, want) after, rerr := os.ReadFile(configPath) - if rerr != nil || string(before) != string(after) { - t.Fatalf("a refused metadata edit changed the owning file (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(before) == string(after), "a refused metadata edit changed the owning file (%v)", rerr) if svc.current() != first { t.Fatalf("a refused metadata edit replaced the publication (generation %d)", svc.current().generation) } - if got, _ := svc.warnings.snapshot(); got != warnRev { - t.Fatalf("warning revision advanced on a refused metadata edit: %d → %d", warnRev, got) + { + got, _ := svc.warnings.snapshot() + testassert.Equal(t, got, warnRev) } assertNoEvent(t, sub) } @@ -150,9 +145,7 @@ func assertMetadataRefused(t *testing.T, svc *configurationService, sub *Subscri func metadataBaseline(t *testing.T, svc *configurationService, configPath string) ([]byte, *configuration, uint64) { t.Helper() data, err := os.ReadFile(configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + testassert.NoError(t, err) warnRev, _ := svc.warnings.snapshot() return data, svc.current(), warnRev } @@ -172,21 +165,15 @@ func bigLexeme(t *testing.T, data []byte, path string) string { var value map[string]any decoder := json.NewDecoder(strings.NewReader(string(data))) decoder.UseNumber() - if err := decoder.Decode(&value); err != nil { - t.Fatalf("decode with UseNumber: %v", err) - } + testassert.NoError(t, decoder.Decode(&value)) current := any(value) for _, member := range strings.Split(path, ".") { object, ok := current.(map[string]any) - if !ok { - t.Fatalf("member path %q is not an object at %q", path, member) - } + testassert.Require(t, ok, "member path %q is not an object at %q", path, member) current = object[member] } number, ok := current.(json.Number) - if !ok { - t.Fatalf("member path %q = %v, want an exact json.Number lexeme", path, current) - } + testassert.Require(t, ok, "member path %q = %v, want an exact json.Number lexeme", path, current) return number.String() } @@ -202,11 +189,8 @@ func TestMetadataEditCreateCustomProvider(t *testing.T) { h := newMetadataHarness(t) writeServiceFile(t, h.configPath, metadataConfigDocument) agentsPath := filepath.Join(h.dataDir, "agents.json") - writeServiceFile(t, agentsPath, `{"worker":{"system_prompt":"simple"}}`) - agentsBefore, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } + writeServiceFile(t, agentsPath, workerAgentsDocument) + agentsBefore := mustRead(t, agentsPath) svc, sub := metadataService(t, h) unsetenv(t, "NEW_KEY") // The fixture is a genuine credential consumer: the referenced env @@ -214,106 +198,74 @@ func TestMetadataEditCreateCustomProvider(t *testing.T) { // result, or any read — the create stores the env NAME only. t.Setenv("NEW_KEY", "sk-real-credential-98765") window := 4096 - cost := protocol.Cost{Input: &[]float64{1.5}[0]} - meta := protocol.ProtocolMetadata{Family: &[]string{"testfam"}[0]} + cost := protocol.Cost{Input: ptrTo(1.5)} + meta := protocol.ProtocolMetadata{Family: ptrTo("testfam")} models := map[string]protocol.ModelEdit{ - "m": {Name: &[]string{"One"}[0], ContextWindow: &window, Cost: &cost}, - "incomplete": {Name: &[]string{"I"}[0]}, + "m": {Name: ptrTo("One"), ContextWindow: &window, Cost: &cost}, + "incomplete": {Name: ptrTo("I")}, "a/b": {ContextWindow: &window}, } patch := protocol.ProviderEdit{ - Name: &[]string{"Created"}[0], - BaseUrl: &[]string{"https://new.test/v1"}[0], - ApiKeyEnv: &[]string{"NEW_KEY"}[0], + Name: ptrTo("Created"), + BaseUrl: ptrTo("https://new.test/v1"), + ApiKeyEnv: ptrTo("NEW_KEY"), Headers: &map[string]string{"X-Custom": "c"}, - SystemRole: &[]protocol.SystemRole{"user"}[0], - UsageInStream: &[]bool{false}[0], + SystemRole: ptrTo(protocol.SystemRole("user")), + UsageInStream: ptrTo(false), ExtraBody: &map[string]any{"side": json.Number("9007199254740993")}, - Discovery: &[]bool{false}[0], + Discovery: ptrTo(false), ProtocolMetadata: &meta, } candidate, err := svc.mutate(context.Background(), svc.editProviderCreate(" new ", patch, models, nil)) - if err != nil { - t.Fatalf("create: %v", err) - } - if candidate.snapshot.generation != 2 || svc.current() != candidate.snapshot { - t.Fatalf("create = generation %d, want 2 published", candidate.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Require(t, candidate.snapshot.generation == 2 && svc.current() == candidate.snapshot, "create = generation %d, want 2 published", candidate.snapshot.generation) prov := candidate.snapshot.catalog.Providers["new"] - if prov == nil || prov.Builtin { - t.Fatalf("created provider = %+v, want a non-builtin catalog member", prov) - } - if prov.Transport.BaseURL != "https://new.test/v1" || prov.Transport.APIKeyEnv != "NEW_KEY" || - prov.Name != "Created" || prov.SystemRole != catalog.SystemRole("user") || - prov.UsageInStream || prov.Discovery { - t.Fatalf("created provider = %+v, want the provided members", prov) - } + testassert.Require(t, prov != nil && !prov.Builtin, "created provider = %+v, want a non-builtin catalog member", prov) + testassert.Require(t, prov.Transport.BaseURL == "https://new.test/v1" && prov.Transport.APIKeyEnv == "NEW_KEY" && prov.Name == "Created" && prov.SystemRole == catalog.SystemRole("user") && !prov.UsageInStream && !prov.Discovery, "created provider = %+v, want the provided members", prov) if usableModelCount(prov) != 2 { t.Fatalf("created usable models = %d, want the two usable entries", usableModelCount(prov)) } entry := prov.Models["a/b"] - if entry == nil || entry.Source != catalog.SourceUser || entry.Name != "a/b" { - t.Fatalf("slash model = %+v, want a user-sourced entry named by its ID", entry) - } + testassert.Require(t, entry != nil && entry.Source == catalog.SourceUser && entry.Name == "a/b", "slash model = %+v, want a user-sourced entry named by its ID", entry) drainMutationEventWithWarning(t, sub, "2", svc.warnings) // The raw file: the written members, an untouched unowned member, the // untouched sibling's exact numeric lexeme, and no secret value anywhere. - data, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + data := mustRead(t, h.configPath) var root map[string]json.RawMessage - if err := json.Unmarshal(data, &root); err != nil { - t.Fatalf("decode owning file: %v", err) - } - if got := bigLexeme(t, data, "providers.other.models.o.context_window"); got != "9007199254740993" { - t.Fatalf("untouched sibling lexeme = %s, want 9007199254740993", got) - } - if !strings.Contains(string(root["providers"]), `"NEW_KEY"`) { - t.Fatalf("created transport = %s, want the named env binding", root["providers"]) - } + testassert.NoError(t, json.Unmarshal(data, &root)) + testassert.Equal(t, bigLexeme(t, data, "providers.other.models.o.context_window"), "9007199254740993") + testassert.Require(t, strings.Contains(string(root["providers"]), `"NEW_KEY"`), "created transport = %s, want the named env binding", root["providers"]) // The owning file and the generated mutation result carry the env name // and never the referenced secret value. - if strings.Contains(string(data), "sk-real-credential-98765") { - t.Fatalf("the create wrote secret bytes: %s", data) - } + testassert.Require(t, !strings.Contains(string(data), "sk-real-credential-98765"), "the create wrote secret bytes: %s", data) mutationBytes, err := json.Marshal(protocol.ProviderMutation{ ConfigurationRevision: configurationRevision(candidate.snapshot), Result: providerPostState(candidate, "new"), }) - if err != nil { - t.Fatalf("marshal mutation: %v", err) - } - if !strings.Contains(string(mutationBytes), "NEW_KEY") || strings.Contains(string(mutationBytes), "sk-real-credential-98765") { - t.Fatalf("mutation result carries the wrong credential bytes: %s", mutationBytes) - } - if got := bigLexeme(t, data, "providers.new.extra_body.side"); got != "9007199254740993" { - t.Fatalf("created extra_body lexeme = %s, want 9007199254740993 preserved", got) - } - agentsAfter, err := os.ReadFile(agentsPath) - if err != nil || string(agentsBefore) != string(agentsAfter) { - t.Fatalf("the metadata edit changed the agents file (%v)", err) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(mutationBytes), "NEW_KEY") && !strings.Contains(string(mutationBytes), "sk-real-credential-98765"), "mutation result carries the wrong credential bytes: %s", mutationBytes) + testassert.Equal(t, bigLexeme(t, data, "providers.new.extra_body.side"), "9007199254740993") + testassert.Require(t, string(mustRead(t, agentsPath)) == string(agentsBefore), "the metadata edit changed the agents file") // The provided empty headers map goes through the same wholesale writer // as the update: the raw headers member stays absent, not an empty // object. emptyHeaders := map[string]string{} window2 := 1 - if _, err := svc.mutate(context.Background(), svc.editProviderCreate("noheaders", - protocol.ProviderEdit{BaseUrl: &[]string{"https://nh.test/v1"}[0], Headers: &emptyHeaders}, - map[string]protocol.ModelEdit{"m": {ContextWindow: &window2}}, nil)); err != nil { - t.Fatalf("empty-headers create: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderCreate("noheaders", + protocol.ProviderEdit{BaseUrl: ptrTo("https://nh.test/v1"), Headers: &emptyHeaders}, + map[string]protocol.ModelEdit{"m": {ContextWindow: &window2}}, nil)) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "3") createdProvider := metadataRawProvider(t, h.configPath, "noheaders") var createdTransport map[string]json.RawMessage - if err := json.Unmarshal(createdProvider["transport"], &createdTransport); err != nil { - t.Fatalf("decode created transport: %v", err) - } - if _, present := createdTransport["headers"]; present { - t.Fatalf("created transport = %s, want the empty provided headers member absent", createdProvider["transport"]) + testassert.NoError(t, json.Unmarshal(createdProvider["transport"], &createdTransport)) + { + _, present := createdTransport["headers"] + testassert.Require(t, !present, "created transport = %s, want the empty provided headers member absent", createdProvider["transport"]) } } @@ -390,8 +342,8 @@ func TestMetadataEditCreateRegistersIncompleteMetadata(t *testing.T) { }{ {"zero-window model", "incomplete", protocol.ProviderEdit{BaseUrl: &base}, zeroWindow}, {"empty models map", "nomodels", protocol.ProviderEdit{BaseUrl: &base}, empty}, - {"occupied binding", "occupiedp", protocol.ProviderEdit{BaseUrl: &base, ApiKeyEnv: &[]string{"OTHER_TEST_KEY"}[0]}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}}, - {"absent binding", "missingp", protocol.ProviderEdit{BaseUrl: &base, ApiKeyEnv: &[]string{"MISSING_PROBE_KEY"}[0]}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}}, + {"occupied binding", "occupiedp", protocol.ProviderEdit{BaseUrl: &base, ApiKeyEnv: ptrTo("OTHER_TEST_KEY")}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}}, + {"absent binding", "missingp", protocol.ProviderEdit{BaseUrl: &base, ApiKeyEnv: ptrTo("MISSING_PROBE_KEY")}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}}, {"no binding member", "nobinding", protocol.ProviderEdit{BaseUrl: &base}, map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}}, } { t.Run(row.name, func(t *testing.T) { @@ -400,34 +352,24 @@ func TestMetadataEditCreateRegistersIncompleteMetadata(t *testing.T) { // typed refusal, so the row's success itself proves no // credential action was planned. candidate, err := svc.mutate(context.Background(), svc.editProviderCreate(row.id, row.patch, row.models, nil)) - if err != nil { - t.Fatalf("create %s: %v", row.id, err) - } + testassert.Require(t, err == nil, "create %s: %v", row.id, err) generation++ - if candidate.snapshot.generation != uint64(generation) { - t.Fatalf("create generation = %d, want %d", candidate.snapshot.generation, generation) - } + testassert.Require(t, candidate.snapshot.generation == uint64(generation), "create generation = %d, want %d", candidate.snapshot.generation, generation) drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) created := candidate.snapshot.catalog.Providers[row.id] - if created == nil { - t.Fatalf("created provider %s missing from the candidate", row.id) - } + testassert.Require(t, created != nil, "created provider %s missing from the candidate", row.id) // The real binding truth: the created transport carries exactly // the supplied binding — empty for the keyless shapes — never an // invented one. var transport struct { APIKeyEnv string `json:"api_key_env"` } - if err := json.Unmarshal(metadataRawProvider(t, h.configPath, row.id)["transport"], &transport); err != nil { - t.Fatalf("decode created transport: %v", err) - } + testassert.NoError(t, json.Unmarshal(metadataRawProvider(t, h.configPath, row.id)["transport"], &transport)) wantEnv := "" if row.patch.ApiKeyEnv != nil { wantEnv = *row.patch.ApiKeyEnv } - if transport.APIKeyEnv != wantEnv || created.Transport.APIKeyEnv != wantEnv { - t.Fatalf("%s binding = (raw %q, effective %q), want exactly the supplied %q", row.id, transport.APIKeyEnv, created.Transport.APIKeyEnv, wantEnv) - } + testassert.Require(t, transport.APIKeyEnv == wantEnv && created.Transport.APIKeyEnv == wantEnv, "%s binding = (raw %q, effective %q), want exactly the supplied %q", row.id, transport.APIKeyEnv, created.Transport.APIKeyEnv, wantEnv) }) } } @@ -448,131 +390,96 @@ func TestMetadataEditUpdateCustomProviderPatchSemantics(t *testing.T) { // Pointer absence: an empty patch still publishes (identical bytes // included) but changes no raw member. - unchanged, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + unchanged := mustRead(t, h.configPath) candidate, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{})) - if err != nil || candidate.snapshot.generation != 2 { - t.Fatalf("empty patch = (%v, generation %d), want a published edit at 2", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 2, "empty patch = (%v, generation %d), want a published edit at 2", err, candidate.snapshot.generation) var beforeRoot, afterRoot map[string]json.RawMessage - if err := json.Unmarshal(unchanged, &beforeRoot); err != nil { - t.Fatalf("decode before: %v", err) - } - if err := json.Unmarshal(mustRead(t, h.configPath), &afterRoot); err != nil { - t.Fatalf("decode after: %v", err) - } + testassert.NoError(t, json.Unmarshal(unchanged, &beforeRoot)) + testassert.NoError(t, json.Unmarshal(mustRead(t, h.configPath), &afterRoot)) var beforeValue, afterValue any beforeDecoder := json.NewDecoder(strings.NewReader(string(beforeRoot["providers"]))) beforeDecoder.UseNumber() afterDecoder := json.NewDecoder(strings.NewReader(string(afterRoot["providers"]))) afterDecoder.UseNumber() - if err := beforeDecoder.Decode(&beforeValue); err != nil { - t.Fatalf("decode before providers: %v", err) - } - if err := afterDecoder.Decode(&afterValue); err != nil { - t.Fatalf("decode after providers: %v", err) - } - if !reflect.DeepEqual(beforeValue, afterValue) { - t.Fatal("the empty patch changed the raw providers member") - } + testassert.NoError(t, beforeDecoder.Decode(&beforeValue)) + testassert.NoError(t, afterDecoder.Decode(&afterValue)) + testassert.Require(t, reflect.DeepEqual(beforeValue, afterValue), "the empty patch changed the raw providers member") drainMutationEvent(t, sub, "2") // Wholesale replaces: false and an empty extra_body object are written // as provided — the validators allow them. falseValue := false emptyObject := map[string]any{} - if candidate, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ - UsageInStream: &falseValue, - ExtraBody: &emptyObject, - })); err != nil { - t.Fatalf("wholesale patch: %v", err) + { + candidate, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ + UsageInStream: &falseValue, + ExtraBody: &emptyObject, + })) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "3") - var prov struct { - Transport struct { - Headers map[string]string `json:"headers"` - } `json:"transport"` - ExtraBody *map[string]any `json:"extra_body"` - } - var providersRoot map[string]json.RawMessage - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &providersRoot); err != nil { - t.Fatalf("decode providers: %v", err) - } - if err := json.Unmarshal(providersRoot["prov"], &prov); err != nil { - t.Fatalf("decode prov: %v", err) - } - if prov.ExtraBody == nil || len(*prov.ExtraBody) != 0 { - t.Fatalf("extra_body = %v, want the provided empty object", prov.ExtraBody) - } - if prov.Transport.Headers["X-Trace"] != "t1" { - t.Fatalf("headers = %v, want the absent member untouched", prov.Transport.Headers) + rawProvider := metadataRawProvider(t, h.configPath, "prov") + var headerProbe struct { + Headers map[string]string `json:"headers"` } + testassert.NoError(t, json.Unmarshal(rawProvider["transport"], &headerProbe)) + var extraBody map[string]any + testassert.NoError(t, json.Unmarshal(rawProvider["extra_body"], &extraBody)) + testassert.Require(t, extraBody != nil && len(extraBody) == 0 && headerProbe.Headers["X-Trace"] == "t1", "extra_body = %v, want the provided empty object; headers = %v, want the absent member untouched", extraBody, headerProbe.Headers) // The headers exception: an empty provided object deletes the raw user // headers member — the file, not merely the filtered GET. emptyHeaders := map[string]string{} - if candidate, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{Headers: &emptyHeaders})); err != nil { - t.Fatalf("empty headers patch: %v", err) + { + candidate, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{Headers: &emptyHeaders})) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "4") - rawProvider := metadataRawProvider(t, h.configPath, "prov") + rawProvider = metadataRawProvider(t, h.configPath, "prov") var transport map[string]json.RawMessage - if err := json.Unmarshal(rawProvider["transport"], &transport); err != nil { - t.Fatalf("decode transport: %v", err) - } - if _, present := transport["headers"]; present { - t.Fatalf("raw transport = %s, want the headers member deleted", rawProvider["transport"]) + testassert.NoError(t, json.Unmarshal(rawProvider["transport"], &transport)) + { + _, present := transport["headers"] + testassert.Require(t, !present, "raw transport = %s, want the headers member deleted", rawProvider["transport"]) } // The wholesale header replacement: a provided map replaces the whole // raw value, so the headers member is exactly the provided map. replaced := map[string]string{"X-New": "n"} - if candidate, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{Headers: &replaced})); err != nil { - t.Fatalf("wholesale headers patch: %v", err) + { + candidate, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{Headers: &replaced})) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "5") transport = nil rawProvider = metadataRawProvider(t, h.configPath, "prov") - if err := json.Unmarshal(rawProvider["transport"], &transport); err != nil { - t.Fatalf("decode transport: %v", err) - } + testassert.NoError(t, json.Unmarshal(rawProvider["transport"], &transport)) var headersMember map[string]string - if err := json.Unmarshal(transport["headers"], &headersMember); err != nil { - t.Fatalf("decode headers: %v", err) - } - if len(headersMember) != 1 || headersMember["X-New"] != "n" { - t.Fatalf("raw headers = %s, want the wholesale provided map", transport["headers"]) - } + testassert.NoError(t, json.Unmarshal(transport["headers"], &headersMember)) + testassert.Require(t, len(headersMember) == 1 && headersMember["X-New"] == "n", "raw headers = %s, want the wholesale provided map", transport["headers"]) // The malformed field value refuses through the candidate check without // a write (the credential-header refusal row is pinned at the direct // Runtime entrypoint). bogusRole := protocol.SystemRole("assistant") before, first, warnRev := metadataBaseline(t, svc, h.configPath) - if _, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{SystemRole: &bogusRole})); err == nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("malformed system role = %v, want a rejected candidate", err) + { + _, err = svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{SystemRole: &bogusRole})) + testassert.Require(t, err != nil && errors.Is(err, ErrConfiguration), "malformed system role = %v, want a rejected candidate", err) } // The existing state oracle: both refusals left the file, the // publication, warning revision, and event stream untouched. assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, err, ErrConfiguration) // The untouched unowned members and numeric lexemes survive every write. - if got := bigLexeme(t, mustRead(t, h.configPath), "providers.other.models.o.context_window"); got != "9007199254740993" { - t.Fatalf("untouched sibling lexeme = %s, want 9007199254740993", got) - } - if got := string(fileRoot(t, h.configPath)["custom_flag"]); got != "true" { - t.Fatalf("unowned root member = %s, want true", got) - } + testassert.Equal(t, bigLexeme(t, mustRead(t, h.configPath), "providers.other.models.o.context_window"), "9007199254740993") + testassert.Equal(t, string(fileRoot(t, h.configPath)["custom_flag"]), "true") } func mustRead(t *testing.T, path string) []byte { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s: %v", path, err) - } + testassert.Require(t, err == nil, "read %s: %v", path, err) return data } @@ -594,13 +501,12 @@ func TestMetadataEditUpdateEnvRules(t *testing.T) { // to keyless are both user-layer edits, and the referenced credential // itself is never unset. newEnv := "NEW_TEST_KEY" - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ApiKeyEnv: &newEnv})); err != nil { - t.Fatalf("connected env change: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ApiKeyEnv: &newEnv})) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "2") - if got := svc.current().catalog.Providers["prov"].Transport.APIKeyEnv; got != "NEW_TEST_KEY" { - t.Fatalf("effective api_key_env = %q, want NEW_TEST_KEY", got) - } + testassert.Equal(t, svc.current().catalog.Providers["prov"].Transport.APIKeyEnv, "NEW_TEST_KEY") if os.Getenv("META_TEST_KEY") != "secret-value" { t.Fatalf("the rebinding unset the old credential: %q", os.Getenv("META_TEST_KEY")) } @@ -622,13 +528,12 @@ func TestMetadataEditUpdateEnvRules(t *testing.T) { // The clear to keyless lands. clear := "" - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ApiKeyEnv: &clear})); err != nil { - t.Fatalf("clear to keyless: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ApiKeyEnv: &clear})) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "3") - if got := svc.current().catalog.Providers["prov"].Transport.APIKeyEnv; got != "" { - t.Fatalf("cleared api_key_env = %q, want keyless", got) - } + testassert.Equal(t, svc.current().catalog.Providers["prov"].Transport.APIKeyEnv, "") if os.Getenv("NEW_TEST_KEY") != "" { t.Fatalf("the clear unset the previously referenced credential: %q", os.Getenv("NEW_TEST_KEY")) } @@ -652,41 +557,27 @@ func TestMetadataEditUpdateBuiltinProvider(t *testing.T) { patch protocol.ProviderEdit check func(*catalog.Provider) }{ - {"name", protocol.ProviderEdit{Name: &[]string{"Renamed"}[0]}, func(p *catalog.Provider) { - if p.Name != "Renamed" { - t.Fatalf("builtin name = %q, want the override", p.Name) - } + {"name", protocol.ProviderEdit{Name: ptrTo("Renamed")}, func(p *catalog.Provider) { + testassert.Equal(t, p.Name, "Renamed") }}, - {"base_url", protocol.ProviderEdit{BaseUrl: &[]string{"https://override.test/v1"}[0]}, func(p *catalog.Provider) { - if p.Transport.BaseURL != "https://override.test/v1" { - t.Fatalf("builtin base_url = %q, want the override", p.Transport.BaseURL) - } + {"base_url", protocol.ProviderEdit{BaseUrl: ptrTo("https://override.test/v1")}, func(p *catalog.Provider) { + testassert.Equal(t, p.Transport.BaseURL, "https://override.test/v1") }}, - {"system_role", protocol.ProviderEdit{SystemRole: &[]protocol.SystemRole{"user"}[0]}, func(p *catalog.Provider) { - if p.SystemRole != catalog.SystemRole("user") { - t.Fatalf("builtin system_role = %q, want the override", p.SystemRole) - } + {"system_role", protocol.ProviderEdit{SystemRole: ptrTo(protocol.SystemRole("user"))}, func(p *catalog.Provider) { + testassert.Require(t, p.SystemRole == catalog.SystemRole("user"), "builtin system_role = %q, want the override", p.SystemRole) }}, - {"usage_in_stream", protocol.ProviderEdit{UsageInStream: &[]bool{false}[0]}, func(p *catalog.Provider) { - if p.UsageInStream { - t.Fatal("builtin usage_in_stream = true, want the override") - } + {"usage_in_stream", protocol.ProviderEdit{UsageInStream: ptrTo(false)}, func(p *catalog.Provider) { + testassert.Require(t, !p.UsageInStream, "builtin usage_in_stream = true, want the override") }}, - {"protocol_metadata", protocol.ProviderEdit{ProtocolMetadata: &protocol.ProtocolMetadata{Family: &[]string{"fam"}[0]}}, func(p *catalog.Provider) { - if p.ProtocolMetadata == nil || p.ProtocolMetadata.Family != "fam" { - t.Fatalf("builtin protocol_metadata = %+v, want the override", p.ProtocolMetadata) - } + {"protocol_metadata", protocol.ProviderEdit{ProtocolMetadata: &protocol.ProtocolMetadata{Family: ptrTo("fam")}}, func(p *catalog.Provider) { + testassert.Require(t, p.ProtocolMetadata != nil && p.ProtocolMetadata.Family == "fam", "builtin protocol_metadata = %+v, want the override", p.ProtocolMetadata) }}, } { t.Run(row.name, func(t *testing.T) { candidate, err := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", row.patch)) - if err != nil { - t.Fatalf("builtin %s patch: %v", row.name, err) - } + testassert.Require(t, err == nil, "builtin %s patch: %v", row.name, err) generation++ - if candidate.snapshot.generation != uint64(generation) { - t.Fatalf("builtin %s patch generation = %d, want %d", row.name, candidate.snapshot.generation, generation) - } + testassert.Require(t, candidate.snapshot.generation == uint64(generation), "builtin %s patch generation = %d, want %d", row.name, candidate.snapshot.generation, generation) drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) row.check(candidate.snapshot.catalog.Providers["bstatic"]) }) @@ -701,8 +592,9 @@ func TestMetadataEditUpdateBuiltinProvider(t *testing.T) { `"bstatic": {"transport": {"headers": {"x-title": "leak", "X-Keep": "k"}}}, "other": {`, 1) writeServiceFile(t, h.configPath, external) headers := map[string]string{"X-Custom": "c"} - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", protocol.ProviderEdit{Headers: &headers})); err != nil { - t.Fatalf("builtin headers patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", protocol.ProviderEdit{Headers: &headers})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) @@ -710,31 +602,20 @@ func TestMetadataEditUpdateBuiltinProvider(t *testing.T) { Headers map[string]string `json:"headers"` } rawProviders := map[string]map[string]json.RawMessage{} - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } - if err := json.Unmarshal(rawProviders["bstatic"]["transport"], &transport); err != nil { - t.Fatalf("decode builtin transport: %v", err) - } - if len(transport.Headers) != 1 || transport.Headers["X-Custom"] != "c" { - t.Fatalf("builtin raw headers = %v, want the wholesale provided map", transport.Headers) - } + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) + testassert.NoError(t, json.Unmarshal(rawProviders["bstatic"]["transport"], &transport)) + testassert.Require(t, len(transport.Headers) == 1 && transport.Headers["X-Custom"] == "c", "builtin raw headers = %v, want the wholesale provided map", transport.Headers) colliding := map[string]string{"x-title": "user", "X-Other": "o"} - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("openrouter", protocol.ProviderEdit{Headers: &colliding})); err != nil { - t.Fatalf("bundled-collision patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("openrouter", protocol.ProviderEdit{Headers: &colliding})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) transport.Headers = nil - if err := json.Unmarshal(rawProviders["openrouter"]["transport"], &transport); err != nil { - t.Fatalf("decode builtin transport: %v", err) - } - if len(transport.Headers) != 2 || transport.Headers["x-title"] != "user" || transport.Headers["X-Other"] != "o" { - t.Fatalf("builtin raw headers = %v, want the collision key stored as provided", transport.Headers) - } + testassert.NoError(t, json.Unmarshal(rawProviders["openrouter"]["transport"], &transport)) + testassert.Require(t, len(transport.Headers) == 2 && transport.Headers["x-title"] == "user" && transport.Headers["X-Other"] == "o", "builtin raw headers = %v, want the collision key stored as provided", transport.Headers) // The forbidden credential header is refused on a builtin too. forbidden := map[string]string{"Proxy-Authorization": "x"} @@ -749,9 +630,7 @@ func TestMetadataEditUpdateBuiltinProvider(t *testing.T) { before, first, warnRev = metadataBaseline(t, svc, h.configPath) _, reservedErr := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", protocol.ProviderEdit{ExtraBody: &reserved})) assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, reservedErr, ErrConfiguration) - if svc.current().catalog.Providers["bstatic"] == nil { - t.Fatal("the refused edit dropped the builtin from the published catalog") - } + testassert.Require(t, svc.current().catalog.Providers["bstatic"] != nil, "the refused edit dropped the builtin from the published catalog") } // --- delete --- @@ -769,38 +648,30 @@ func TestMetadataEditDeleteProvider(t *testing.T) { // The builtin delete removes only the user node: the bundled base // remains the effective subject. candidate, err := svc.mutate(context.Background(), svc.editProviderDelete("bstatic")) - if err != nil || candidate.snapshot.generation != 2 { - t.Fatalf("builtin delete = (%v, generation %d), want a publication at 2", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 2, "builtin delete = (%v, generation %d), want a publication at 2", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "2") - if got := candidate.snapshot.catalog.Providers["bstatic"]; got == nil || !got.Builtin { - t.Fatalf("builtin delete post-state = %+v, want the surviving bundled base", got) - } - if got := candidate.snapshot.catalog.Providers["bstatic"].Transport.BaseURL; got != "http://bstatic.test/v1" { - t.Fatalf("revealed base_url = %q, want the bundled URL", got) + { + got := candidate.snapshot.catalog.Providers["bstatic"] + testassert.Require(t, got != nil && got.Builtin, "builtin delete post-state = %+v, want the surviving bundled base", got) } + testassert.Equal(t, candidate.snapshot.catalog.Providers["bstatic"].Transport.BaseURL, "http://bstatic.test/v1") var rawProviders map[string]json.RawMessage - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) if _, present := rawProviders["bstatic"]; present { t.Fatalf("raw providers = %s, want the user node gone", fileRoot(t, h.configPath)["providers"]) } - if _, present := rawProviders["prov"]; !present { - t.Fatal("the delete touched an unrelated provider") + { + _, present := rawProviders["prov"] + testassert.Require(t, present, "the delete touched an unrelated provider") } // The connected env-keyed custom is removable too. unsetenv(t, "META_TEST_KEY") t.Setenv("META_TEST_KEY", "secret-value") candidate, err = svc.mutate(context.Background(), svc.editProviderDelete("prov")) - if err != nil || candidate.snapshot.generation != 3 { - t.Fatalf("connected delete = (%v, generation %d), want a publication at 3", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 3, "connected delete = (%v, generation %d), want a publication at 3", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "3") - if candidate.snapshot.catalog.Providers["prov"] != nil { - t.Fatal("the deleted custom provider stayed in the candidate catalog") - } + testassert.Require(t, candidate.snapshot.catalog.Providers["prov"] == nil, "the deleted custom provider stayed in the candidate catalog") if os.Getenv("META_TEST_KEY") != "secret-value" { t.Fatalf("the delete unset the referenced credential: %q", os.Getenv("META_TEST_KEY")) } @@ -808,20 +679,14 @@ func TestMetadataEditDeleteProvider(t *testing.T) { // The successful standalone delete removes only the owning definition. candidate, err = svc.mutate(context.Background(), svc.editProviderDelete("other")) - if err != nil || candidate.snapshot.generation != 4 { - t.Fatalf("delete = (%v, generation %d), want a publication at 4", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 4, "delete = (%v, generation %d), want a publication at 4", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "4") rawProviders = map[string]json.RawMessage{} - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) if _, present := rawProviders["other"]; present { t.Fatalf("raw providers = %s, want the deleted entry gone", fileRoot(t, h.configPath)["providers"]) } - if got := string(fileRoot(t, h.configPath)["custom_flag"]); got != "true" { - t.Fatalf("unowned root member = %s, want true", got) - } + testassert.Equal(t, string(fileRoot(t, h.configPath)["custom_flag"]), "true") // A custom definition deleted from the latest raw layer by an external // editor (without a reload) is not reconstructed: the identity is still @@ -830,21 +695,18 @@ func TestMetadataEditDeleteProvider(t *testing.T) { // shared writer's own formatting. window := 1 valid := map[string]protocol.ModelEdit{"m": {ContextWindow: &window}} - if _, err := svc.mutate(context.Background(), svc.editProviderCreate("temp", - protocol.ProviderEdit{BaseUrl: &[]string{"https://temp.test/v1"}[0]}, valid, nil)); err != nil { - t.Fatalf("create temp: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderCreate("temp", + protocol.ProviderEdit{BaseUrl: ptrTo("https://temp.test/v1")}, valid, nil)) + testassert.NoError(t, err) } drainConnectionEvent(t, svc.warnings, sub, "5") - external := `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":"META_TEST_KEY"},"discovery":false,"models":{"m":{"name":"M","context_window":4096}}}},"custom_flag":true}` + external := `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "META_TEST_KEY", `{"m":{"name":"M","context_window":4096}}`) + `},"custom_flag":true}` writeServiceFile(t, h.configPath, external) candidate, err = svc.mutate(context.Background(), svc.editProviderDelete("temp")) - if err != nil || candidate.snapshot.generation != 6 { - t.Fatalf("externally-removed delete = (%v, generation %d), want a publication at 6", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 6, "externally-removed delete = (%v, generation %d), want a publication at 6", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "6") - if candidate.snapshot.catalog.Providers["temp"] != nil { - t.Fatal("the externally-removed provider stayed in the candidate catalog") - } + testassert.Require(t, candidate.snapshot.catalog.Providers["temp"] == nil, "the externally-removed provider stayed in the candidate catalog") // The unknown subject fails its typed not-found without a write. before, first, warnRev := metadataBaseline(t, svc, h.configPath) @@ -874,14 +736,15 @@ func TestMetadataEditResetProviderFields(t *testing.T) { extra := map[string]any{"x": 1} stream := false role := protocol.SystemRole("developer") - meta := protocol.ProtocolMetadata{Family: &[]string{"fam"}[0]} + meta := protocol.ProtocolMetadata{Family: ptrTo("fam")} discovery := false hidden := true - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ - Name: &name, ExtraBody: &extra, SystemRole: &role, UsageInStream: &stream, - ProtocolMetadata: &meta, Discovery: &discovery, Hidden: &hidden, - })); err != nil { - t.Fatalf("override seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{ + Name: &name, ExtraBody: &extra, SystemRole: &role, UsageInStream: &stream, + ProtocolMetadata: &meta, Discovery: &discovery, Hidden: &hidden, + })) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "2") @@ -897,14 +760,13 @@ func TestMetadataEditResetProviderFields(t *testing.T) { protocol.ProviderFieldHidden, } { candidate, err := svc.mutate(context.Background(), svc.editProviderFieldReset("prov", field)) - if err != nil || candidate.snapshot.generation != svc.current().generation { - t.Fatalf("reset %q = (%v, generation %d), want a publication", field, err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == svc.current().generation, "reset %q = (%v, generation %d), want a publication", field, err, candidate.snapshot.generation) generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) prov := metadataRawProvider(t, h.configPath, "prov") - if _, present := prov[string(field)]; present { - t.Fatalf("reset %q left the raw override: %s", field, prov) + { + _, present := prov[string(field)] + testassert.Require(t, !present, "reset %q left the raw override: %s", field, prov) } } @@ -912,18 +774,18 @@ func TestMetadataEditResetProviderFields(t *testing.T) { for _, field := range []protocol.ProviderField{ protocol.ProviderFieldHeaders, } { - if _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("prov", field)); err != nil { - t.Fatalf("reset %q: %v", field, err) + { + _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("prov", field)) + testassert.Require(t, err == nil, "reset %q: %v", field, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) rawProvider := metadataRawProvider(t, h.configPath, "prov") var transport map[string]json.RawMessage - if err := json.Unmarshal(rawProvider["transport"], &transport); err != nil { - t.Fatalf("decode transport: %v", err) - } - if _, present := transport[string(field)]; present { - t.Fatalf("reset %q left the raw transport override: %s", field, rawProvider["transport"]) + testassert.NoError(t, json.Unmarshal(rawProvider["transport"], &transport)) + { + _, present := transport[string(field)] + testassert.Require(t, !present, "reset %q left the raw transport override: %s", field, rawProvider["transport"]) } } @@ -945,61 +807,58 @@ func TestMetadataEditResetProviderFields(t *testing.T) { // resets to the bundled URL: only its own raw member changes. unsetenv(t, "BUILTIN_OVERRIDE_KEY") // the builtin env override is disconnected builtinBefore := metadataRawProvider(t, h.configPath, "bstatic") - if _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldBaseUrl)); err != nil { - t.Fatalf("builtin base_url reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldBaseUrl)) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) - if got := svc.current().catalog.Providers["bstatic"].Transport.BaseURL; got != "http://bstatic.test/v1" { - t.Fatalf("builtin base_url after reset = %q, want the bundled URL restored", got) - } + testassert.Equal(t, svc.current().catalog.Providers["bstatic"].Transport.BaseURL, "http://bstatic.test/v1") var beforeTransport, afterTransport map[string]json.RawMessage - if err := json.Unmarshal(builtinBefore["transport"], &beforeTransport); err != nil { - t.Fatalf("decode builtin transport: %v", err) - } - if err := json.Unmarshal(metadataRawProvider(t, h.configPath, "bstatic")["transport"], &afterTransport); err != nil { - t.Fatalf("decode builtin transport: %v", err) - } - if _, present := beforeTransport["base_url"]; !present { - t.Fatal("fixture base_url override missing") + testassert.NoError(t, json.Unmarshal(builtinBefore["transport"], &beforeTransport)) + testassert.NoError(t, json.Unmarshal(metadataRawProvider(t, h.configPath, "bstatic")["transport"], &afterTransport)) + { + _, present := beforeTransport["base_url"] + testassert.Require(t, present, "fixture base_url override missing") } if _, present := afterTransport["base_url"]; present { t.Fatalf("raw transport = %s, want the base_url override gone", metadataRawProvider(t, h.configPath, "bstatic")["transport"]) } - if len(afterTransport) != len(beforeTransport)-1 { - t.Fatalf("the reset changed members beyond its own field: %v → %v", beforeTransport, afterTransport) - } - if _, present := afterTransport["api_key_env"]; !present { - t.Fatal("the reset touched the env override") + testassert.Require(t, len(afterTransport) == len(beforeTransport)-1, "the reset changed members beyond its own field: %v → %v", beforeTransport, afterTransport) + { + _, present := afterTransport["api_key_env"] + testassert.Require(t, present, "the reset touched the env override") } // The builtin DISCONNECTED api_key_env override resets to the bundled // empty name: no connection state gates the reset. - if _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldEnvironmentVariable)); err != nil { - t.Fatalf("builtin api_key_env reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldEnvironmentVariable)) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) - if got := svc.current().catalog.Providers["bstatic"].Transport.APIKeyEnv; got != "" { - t.Fatalf("builtin api_key_env after reset = %q, want the bundled empty name restored", got) - } - if got := metadataRawProvider(t, h.configPath, "bstatic")["transport"]; got == nil { - t.Fatal("the builtin transport member vanished") + testassert.Equal(t, svc.current().catalog.Providers["bstatic"].Transport.APIKeyEnv, "") + { + got := metadataRawProvider(t, h.configPath, "bstatic")["transport"] + testassert.Require(t, got != nil, "the builtin transport member vanished") } // A second base_url reset has no override left: the shared successful // edit rule still rewrites the owning file and publishes the next // generation with its event. - if _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldBaseUrl)); err != nil { - t.Fatalf("second builtin base_url reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldBaseUrl)) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) // An absent override is the same successful edit: no member to remove, // yet the owning file is rewritten and the next generation published. - if _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("prov", protocol.ProviderFieldName)); err != nil { - t.Fatalf("absent-override reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("prov", protocol.ProviderFieldName)) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) @@ -1008,18 +867,18 @@ func TestMetadataEditResetProviderFields(t *testing.T) { // bundled value; a builtin with no raw entry follows the same shared // successful-edit rule. userHeaders := map[string]string{"X-Custom": "c"} - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", protocol.ProviderEdit{Headers: &userHeaders})); err != nil { - t.Fatalf("builtin override seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", protocol.ProviderEdit{Headers: &userHeaders})) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) - if _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldHeaders)); err != nil { - t.Fatalf("builtin reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderFieldReset("bstatic", protocol.ProviderFieldHeaders)) + testassert.NoError(t, err) } bundled := svc.current().catalog.Providers["bstatic"].Transport.Headers - if bundled["HTTP-Referer"] == "" || bundled["X-Title"] == "" || len(bundled) != 2 { - t.Fatalf("builtin reset headers = %v, want the bundled attribution headers restored", bundled) - } + testassert.Require(t, bundled["HTTP-Referer"] != "" && bundled["X-Title"] != "" && len(bundled) == 2, "builtin reset headers = %v, want the bundled attribution headers restored", bundled) generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) } @@ -1046,41 +905,27 @@ func TestMetadataEditModelUpsert(t *testing.T) { patch protocol.ModelEdit check func(*catalog.Model) }{ - {"name", protocol.ModelEdit{Name: &[]string{"Renamed"}[0]}, func(m *catalog.Model) { - if m.Name != "Renamed" { - t.Fatalf("bundled model name = %q, want the override", m.Name) - } + {"name", protocol.ModelEdit{Name: ptrTo("Renamed")}, func(m *catalog.Model) { + testassert.Equal(t, m.Name, "Renamed") }}, - {"system_role", protocol.ModelEdit{SystemRole: &[]protocol.SystemRole{"user"}[0]}, func(m *catalog.Model) { - if m.SystemRole != catalog.SystemRole("user") { - t.Fatalf("bundled model system_role = %q, want the override", m.SystemRole) - } + {"system_role", protocol.ModelEdit{SystemRole: ptrTo(protocol.SystemRole("user"))}, func(m *catalog.Model) { + testassert.Require(t, m.SystemRole == catalog.SystemRole("user"), "bundled model system_role = %q, want the override", m.SystemRole) }}, - {"usage_in_stream", protocol.ModelEdit{UsageInStream: &[]bool{false}[0]}, func(m *catalog.Model) { - if m.UsageInStream { - t.Fatal("bundled model usage_in_stream = true, want the override") - } + {"usage_in_stream", protocol.ModelEdit{UsageInStream: ptrTo(false)}, func(m *catalog.Model) { + testassert.Require(t, !m.UsageInStream, "bundled model usage_in_stream = true, want the override") }}, {"input_modalities", protocol.ModelEdit{InputModalities: &[]protocol.InputModality{"text"}}, func(m *catalog.Model) { - if len(m.InputModalities) != 1 || m.InputModalities[0] != catalog.Modality("text") { - t.Fatalf("bundled model modalities = %v, want the override", m.InputModalities) - } + testassert.Require(t, len(m.InputModalities) == 1 && m.InputModalities[0] == catalog.Modality("text"), "bundled model modalities = %v, want the override", m.InputModalities) }}, - {"protocol_metadata", protocol.ModelEdit{ProtocolMetadata: &protocol.ProtocolMetadata{Family: &[]string{"fam"}[0]}}, func(m *catalog.Model) { - if m.ProtocolMetadata == nil || m.ProtocolMetadata.Family != "fam" { - t.Fatalf("bundled model protocol_metadata = %+v, want the override", m.ProtocolMetadata) - } + {"protocol_metadata", protocol.ModelEdit{ProtocolMetadata: &protocol.ProtocolMetadata{Family: ptrTo("fam")}}, func(m *catalog.Model) { + testassert.Require(t, m.ProtocolMetadata != nil && m.ProtocolMetadata.Family == "fam", "bundled model protocol_metadata = %+v, want the override", m.ProtocolMetadata) }}, } { t.Run(row.name, func(t *testing.T) { candidate, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", row.patch)) - if err != nil { - t.Fatalf("bundled %s patch: %v", row.name, err) - } + testassert.Require(t, err == nil, "bundled %s patch: %v", row.name, err) generation++ - if candidate.snapshot.generation != uint64(generation) { - t.Fatalf("bundled %s patch generation = %d, want %d", row.name, candidate.snapshot.generation, generation) - } + testassert.Require(t, candidate.snapshot.generation == uint64(generation), "bundled %s patch generation = %d, want %d", row.name, candidate.snapshot.generation, generation) drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) row.check(candidate.snapshot.catalog.Providers["bstatic"].Models["bm"]) }) @@ -1089,14 +934,13 @@ func TestMetadataEditModelUpsert(t *testing.T) { // The correctable members land on the bundled model and the reserved // extra_body key refuses through the candidate check. window := 2000 - if _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ContextWindow: &window})); err != nil { - t.Fatalf("bundled correctable patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ContextWindow: &window})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) - if got := svc.current().catalog.Providers["bstatic"].Models["bm"].ContextWindow; got != 2000 { - t.Fatalf("bundled window = %d, want 2000", got) - } + testassert.Equal(t, svc.current().catalog.Providers["bstatic"].Models["bm"].ContextWindow, 2000) reserved := map[string]any{"stream": true} before, first, warnRev := metadataBaseline(t, svc, h.configPath) _, saveErr := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ExtraBody: &reserved})) @@ -1105,35 +949,32 @@ func TestMetadataEditModelUpsert(t *testing.T) { // A missing model creates a user model; the second save of the same ID // is a legitimate edit, not a duplicate error. created := "Created" - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "new-model", protocol.ModelEdit{Name: &created, ContextWindow: &window})); err != nil { - t.Fatalf("user model create: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "new-model", protocol.ModelEdit{Name: &created, ContextWindow: &window})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) entry := svc.current().catalog.Providers["prov"].Models["new-model"] - if entry == nil || entry.Source != catalog.SourceUser || entry.Name != "Created" { - t.Fatalf("created model = %+v, want a user-sourced entry", entry) - } + testassert.Require(t, entry != nil && entry.Source == catalog.SourceUser && entry.Name == "Created", "created model = %+v, want a user-sourced entry", entry) renamed := "Renamed" - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "new-model", protocol.ModelEdit{Name: &renamed})); err != nil { - t.Fatalf("user model edit: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "new-model", protocol.ModelEdit{Name: &renamed})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) edited := svc.current().catalog.Providers["prov"].Models["new-model"] - if edited == nil || edited.Name != "Renamed" || edited.ContextWindow != 2000 { - t.Fatalf("edited model = %+v, want the renamed entry with its created window 2000 preserved", edited) - } + testassert.Require(t, edited != nil && edited.Name == "Renamed" && edited.ContextWindow == 2000, "edited model = %+v, want the renamed entry with its created window 2000 preserved", edited) // A slash-containing model ID is an exact query value. - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "org/model", protocol.ModelEdit{ContextWindow: &window})); err != nil { - t.Fatalf("slash model upsert: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "org/model", protocol.ModelEdit{ContextWindow: &window})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) - if svc.current().catalog.Providers["prov"].Models["org/model"] == nil { - t.Fatal("the slash model ID is not a catalog member") - } + testassert.Require(t, svc.current().catalog.Providers["prov"].Models["org/model"] != nil, "the slash model ID is not a catalog member") // Missing provider and the unknown-model identity rules. before, first, warnRev = metadataBaseline(t, svc, h.configPath) @@ -1141,8 +982,9 @@ func TestMetadataEditModelUpsert(t *testing.T) { assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, ghostErr, catalog.ErrUnknownProvider) // The empty patch on a new model is a valid upsert. - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "bare", protocol.ModelEdit{})); err != nil { - t.Fatalf("empty patch upsert: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "bare", protocol.ModelEdit{})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) @@ -1151,15 +993,14 @@ func TestMetadataEditModelUpsert(t *testing.T) { // the catalog admit 0/incomplete models; there is no positivity rule): // the patch writes it and the entry stays a catalog member, unusable. zero := 0 - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{ContextWindow: &zero})); err != nil { - t.Fatalf("explicit zero window patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{ContextWindow: &zero})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) zeroEntry := svc.current().catalog.Providers["prov"].Models["m"] - if zeroEntry == nil || zeroEntry.ContextWindow != 0 { - t.Fatalf("model after explicit zero window = %+v, want the written 0 window retained as incomplete", zeroEntry) - } + testassert.Require(t, zeroEntry != nil && zeroEntry.ContextWindow == 0, "model after explicit zero window = %+v, want the written 0 window retained as incomplete", zeroEntry) } // --- model delete --- @@ -1181,23 +1022,18 @@ func TestMetadataEditModelDelete(t *testing.T) { // The bundled model's override removal reveals the bundled base: the // raw entry leaves, the effective model stays. hidden := true - if _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{Hidden: &hidden})); err != nil { - t.Fatalf("override seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{Hidden: &hidden})) + testassert.NoError(t, err) } drainConnectionEvent(t, svc.warnings, sub, "2") candidate, err := svc.mutate(context.Background(), svc.editModelDelete("bstatic", "bm")) - if err != nil || candidate.snapshot.generation != 3 { - t.Fatalf("bundled model delete = (%v, generation %d), want a publication at 3", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 3, "bundled model delete = (%v, generation %d), want a publication at 3", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "3") entry := candidate.snapshot.catalog.Providers["bstatic"].Models["bm"] - if entry == nil || entry.Source != catalog.SourceBundled { - t.Fatalf("bundled model after delete = %+v, want the surviving bundled base", entry) - } + testassert.Require(t, entry != nil && entry.Source == catalog.SourceBundled, "bundled model after delete = %+v, want the surviving bundled base", entry) var rawModels map[string]json.RawMessage - if err := json.Unmarshal(metadataRawProvider(t, h.configPath, "bstatic")["models"], &rawModels); err != nil { - t.Fatalf("decode models: %v", err) - } + testassert.NoError(t, json.Unmarshal(metadataRawProvider(t, h.configPath, "bstatic")["models"], &rawModels)) if _, present := rawModels["bm"]; present { t.Fatalf("raw models = %s, want the user node gone", metadataRawProvider(t, h.configPath, "bstatic")["models"]) } @@ -1210,27 +1046,27 @@ func TestMetadataEditModelDelete(t *testing.T) { assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, err, catalog.ErrUnknownModel) // The successful user-model delete removes exactly the raw entry. - if _, err := svc.mutate(context.Background(), svc.editModelDelete("prov", "wide")); err != nil { - t.Fatalf("user model delete: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelDelete("prov", "wide")) + testassert.NoError(t, err) } drainConnectionEvent(t, svc.warnings, sub, "4") - root := fileRoot(t, h.configPath) - var rawProviders map[string]map[string]json.RawMessage - if err := json.Unmarshal(root["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + rawProv := metadataRawProvider(t, h.configPath, "prov") var models map[string]json.RawMessage - if err := json.Unmarshal(rawProviders["prov"]["models"], &models); err != nil { - t.Fatalf("decode models: %v", err) - } - if _, present := models["wide"]; present { - t.Fatalf("raw models = %s, want the deleted entry gone", rawProviders["prov"]["models"]) + testassert.NoError(t, json.Unmarshal(rawProv["models"], &models)) + { + _, present := models["wide"] + testassert.Require(t, !present, "raw models = %s, want the deleted entry gone", rawProv["models"]) } - if _, present := models["m"]; !present { - t.Fatal("the delete touched an unrelated model") + { + _, present := models["m"] + testassert.Require(t, present, "the delete touched an unrelated model") } - if _, present := rawProviders["other"]; !present { - t.Fatal("the delete touched an unrelated provider") + var rawProviders map[string]json.RawMessage + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) + { + _, present := rawProviders["other"] + testassert.Require(t, present, "the delete touched an unrelated provider") } // A user model already deleted from the latest raw layer by an external @@ -1240,13 +1076,9 @@ func TestMetadataEditModelDelete(t *testing.T) { external := `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":"META_TEST_KEY","headers":{"X-Trace":"t1"}},"discovery":false,"extra_body":{"side":1,"big":9007199254740993},"models":{"wide":{"context_window":8192}}},"other":{"transport":{"base_url":"https://other.test/v1","api_key_env":"OTHER_TEST_KEY"},"discovery":false,"models":{"o":{"context_window":9007199254740993}}}},"custom_flag":true}` writeServiceFile(t, h.configPath, external) candidate, err = svc.mutate(context.Background(), svc.editModelDelete("prov", "m")) - if err != nil || candidate.snapshot.generation != 5 { - t.Fatalf("externally-removed model delete = (%v, generation %d), want a publication at 5", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 5, "externally-removed model delete = (%v, generation %d), want a publication at 5", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "5") - if candidate.snapshot.catalog.Providers["prov"].Models["m"] != nil { - t.Fatal("the externally-removed model stayed in the candidate catalog") - } + testassert.Require(t, candidate.snapshot.catalog.Providers["prov"].Models["m"] == nil, "the externally-removed model stayed in the candidate catalog") } // --- model field reset --- @@ -1266,29 +1098,26 @@ func TestMetadataEditResetModelFields(t *testing.T) { // leaves a valid incomplete model that cannot make a new admission // usable. candidate, err := svc.mutate(context.Background(), svc.editModelFieldReset("prov", "m", protocol.ModelFieldContextWindow)) - if err != nil || candidate.snapshot.generation != 2 { - t.Fatalf("user context_window reset = (%v, generation %d), want a publication at 2", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 2, "user context_window reset = (%v, generation %d), want a publication at 2", err, candidate.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "2") entry := candidate.snapshot.catalog.Providers["prov"].Models["m"] - if entry == nil || entry.ContextWindow != 0 { - t.Fatalf("user model after the window reset = %+v, want the incomplete model retained", entry) - } + testassert.Require(t, entry != nil && entry.ContextWindow == 0, "user model after the window reset = %+v, want the incomplete model retained", entry) // Seed every resettable override on the user model, then reset each one. name := "M2" maxOut := 55 stream := true extra := map[string]any{"x": 1} - cost := protocol.Cost{Input: &[]float64{2.5}[0]} + cost := protocol.Cost{Input: ptrTo(2.5)} role := protocol.SystemRole("user") modalities := []protocol.InputModality{"text", "image"} - meta := protocol.ProtocolMetadata{Family: &[]string{"fam"}[0]} - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{ - Name: &name, MaxOutputTokens: &maxOut, UsageInStream: &stream, ExtraBody: &extra, - Cost: &cost, SystemRole: &role, InputModalities: &modalities, ProtocolMetadata: &meta, - })); err != nil { - t.Fatalf("override seed: %v", err) + meta := protocol.ProtocolMetadata{Family: ptrTo("fam")} + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{ + Name: &name, MaxOutputTokens: &maxOut, UsageInStream: &stream, ExtraBody: &extra, + Cost: &cost, SystemRole: &role, InputModalities: &modalities, ProtocolMetadata: &meta, + })) + testassert.NoError(t, err) } drainConnectionEvent(t, svc.warnings, sub, "3") @@ -1307,29 +1136,25 @@ func TestMetadataEditResetModelFields(t *testing.T) { protocol.ModelFieldHidden, } { candidate, err := svc.mutate(context.Background(), svc.editModelFieldReset("prov", "m", field)) - if err != nil || candidate.snapshot.generation != svc.current().generation { - t.Fatalf("reset %q = (%v, generation %d), want a publication", field, err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == svc.current().generation, "reset %q = (%v, generation %d), want a publication", field, err, candidate.snapshot.generation) generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) var rawModels map[string]json.RawMessage - if err := json.Unmarshal(metadataRawProvider(t, h.configPath, "prov")["models"], &rawModels); err != nil { - t.Fatalf("decode models: %v", err) - } + testassert.NoError(t, json.Unmarshal(metadataRawProvider(t, h.configPath, "prov")["models"], &rawModels)) var rawModel map[string]json.RawMessage - if err := json.Unmarshal(rawModels["m"], &rawModel); err != nil { - t.Fatalf("decode model: %v", err) - } - if _, present := rawModel[string(field)]; present { - t.Fatalf("reset %q left the raw override: %s", field, rawModels["m"]) + testassert.NoError(t, json.Unmarshal(rawModels["m"], &rawModel)) + { + _, present := rawModel[string(field)] + testassert.Require(t, !present, "reset %q left the raw override: %s", field, rawModels["m"]) } } // A bundled model's override reset restores the bundled value; an // absent override publishes under the same shared successful-edit rule. window := 50 - if _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ContextWindow: &window})); err != nil { - t.Fatalf("bundled override seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ContextWindow: &window})) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) @@ -1341,8 +1166,9 @@ func TestMetadataEditResetModelFields(t *testing.T) { // An absent override is the shared successful edit too: no member to // remove, yet the owning file is rewritten and the next generation // published. - if _, err := svc.mutate(context.Background(), svc.editModelFieldReset("bstatic", "bm", protocol.ModelFieldCost)); err != nil { - t.Fatalf("absent-override reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelFieldReset("bstatic", "bm", protocol.ModelFieldCost)) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) @@ -1351,20 +1177,20 @@ func TestMetadataEditResetModelFields(t *testing.T) { // discovery record's window: the override lands first, the reset removes // only its own raw member, and the source stays discovered. recordWindow := 1024 - if _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{ContextWindow: &recordWindow})); err != nil { - t.Fatalf("discovered override seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{ContextWindow: &recordWindow})) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) - if _, err := svc.mutate(context.Background(), svc.editModelFieldReset("disco", "disc-model", protocol.ModelFieldContextWindow)); err != nil { - t.Fatalf("discovered context_window reset: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelFieldReset("disco", "disc-model", protocol.ModelFieldContextWindow)) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) entry = svc.current().catalog.Providers["disco"].Models["disc-model"] - if entry == nil || entry.ContextWindow != 2048 || entry.Source != catalog.SourceDiscovered { - t.Fatalf("discovered model after reset = %+v, want the record window 2048 restored with its source", entry) - } + testassert.Require(t, entry != nil && entry.ContextWindow == 2048 && entry.Source == catalog.SourceDiscovered, "discovered model after reset = %+v, want the record window 2048 restored with its source", entry) if _, present := metadataRawModel(t, h.configPath, "disco", "disc-model")["context_window"]; present { t.Fatalf("discovered raw model = %s, want the reset context_window member gone", metadataRawModel(t, h.configPath, "disco", "disc-model")) } @@ -1380,18 +1206,15 @@ func TestMetadataEditResetModelFields(t *testing.T) { func seedDiscoveredModel(t *testing.T, h *serviceHarness) { t.Helper() preBuilt, err := h.loader.LoadCaptured(context.Background(), map[string]any{}) - if err != nil { - t.Fatalf("pre-build: %v", err) - } + testassert.NoError(t, err) disco := preBuilt.Catalog.Providers["disco"] - if disco == nil { - t.Fatalf("bundled disco provider missing: %+v", preBuilt.Catalog.Providers) - } - if ok, err := catalog.TryWriteDiscoveryCache(h.home, "disco", disco.Transport, - catalog.DiscoveredProvider{Models: map[string]catalog.DiscoveredModel{ - "disc-model": {Name: "Disc", ContextWindow: 2048, MaxOutputTokens: 256}, - }}, time.Now().UTC()); err != nil || !ok { - t.Fatalf("seed discovery cache: %v %v", ok, err) + testassert.Require(t, disco != nil, "bundled disco provider missing: %+v", preBuilt.Catalog.Providers) + { + ok, err := catalog.TryWriteDiscoveryCache(h.home, "disco", disco.Transport, + catalog.DiscoveredProvider{Models: map[string]catalog.DiscoveredModel{ + "disc-model": {Name: "Disc", ContextWindow: 2048, MaxOutputTokens: 256}, + }}, time.Now().UTC()) + testassert.Require(t, err == nil && ok, "seed discovery cache: %v %v", ok, err) } } @@ -1403,9 +1226,7 @@ func TestMetadataEditDiscoveredModelSource(t *testing.T) { _, svc, sub := newOverlayFixture(t) entry := svc.current().catalog.Providers["disco"].Models["disc-model"] - if entry == nil || entry.Source != catalog.SourceDiscovered { - t.Fatalf("discovered model = %+v, want a discovered-source entry", entry) - } + testassert.Require(t, entry != nil && entry.Source == catalog.SourceDiscovered, "discovered model = %+v, want a discovered-source entry", entry) // No source-specific lock applies: the identity/protocol members land // on the discovered model as its user override. @@ -1415,37 +1236,25 @@ func TestMetadataEditDiscoveredModelSource(t *testing.T) { patch protocol.ModelEdit check func(*catalog.Model) }{ - {"name", protocol.ModelEdit{Name: &[]string{"Renamed"}[0]}, func(m *catalog.Model) { - if m.Name != "Renamed" { - t.Fatalf("discovered model name = %q, want the override", m.Name) - } + {"name", protocol.ModelEdit{Name: ptrTo("Renamed")}, func(m *catalog.Model) { + testassert.Equal(t, m.Name, "Renamed") }}, - {"system_role", protocol.ModelEdit{SystemRole: &[]protocol.SystemRole{"user"}[0]}, func(m *catalog.Model) { - if m.SystemRole != catalog.SystemRole("user") { - t.Fatalf("discovered model system_role = %q, want the override", m.SystemRole) - } + {"system_role", protocol.ModelEdit{SystemRole: ptrTo(protocol.SystemRole("user"))}, func(m *catalog.Model) { + testassert.Require(t, m.SystemRole == catalog.SystemRole("user"), "discovered model system_role = %q, want the override", m.SystemRole) }}, - {"usage_in_stream", protocol.ModelEdit{UsageInStream: &[]bool{false}[0]}, func(m *catalog.Model) { - if m.UsageInStream { - t.Fatal("discovered model usage_in_stream = true, want the override") - } + {"usage_in_stream", protocol.ModelEdit{UsageInStream: ptrTo(false)}, func(m *catalog.Model) { + testassert.Require(t, !m.UsageInStream, "discovered model usage_in_stream = true, want the override") }}, {"input_modalities", protocol.ModelEdit{InputModalities: &[]protocol.InputModality{"text"}}, func(m *catalog.Model) { - if len(m.InputModalities) != 1 || m.InputModalities[0] != catalog.Modality("text") { - t.Fatalf("discovered model modalities = %v, want the override", m.InputModalities) - } + testassert.Require(t, len(m.InputModalities) == 1 && m.InputModalities[0] == catalog.Modality("text"), "discovered model modalities = %v, want the override", m.InputModalities) }}, - {"protocol_metadata", protocol.ModelEdit{ProtocolMetadata: &protocol.ProtocolMetadata{Family: &[]string{"fam"}[0]}}, func(m *catalog.Model) { - if m.ProtocolMetadata == nil || m.ProtocolMetadata.Family != "fam" { - t.Fatalf("discovered model protocol_metadata = %+v, want the override", m.ProtocolMetadata) - } + {"protocol_metadata", protocol.ModelEdit{ProtocolMetadata: &protocol.ProtocolMetadata{Family: ptrTo("fam")}}, func(m *catalog.Model) { + testassert.Require(t, m.ProtocolMetadata != nil && m.ProtocolMetadata.Family == "fam", "discovered model protocol_metadata = %+v, want the override", m.ProtocolMetadata) }}, } { t.Run(row.name, func(t *testing.T) { candidate, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", row.patch)) - if err != nil { - t.Fatalf("discovered %s patch: %v", row.name, err) - } + testassert.Require(t, err == nil, "discovered %s patch: %v", row.name, err) generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) row.check(candidate.snapshot.catalog.Providers["disco"].Models["disc-model"]) @@ -1454,28 +1263,23 @@ func TestMetadataEditDiscoveredModelSource(t *testing.T) { // The override deletion reveals the discovered base. candidate, err := svc.mutate(context.Background(), svc.editModelDelete("disco", "disc-model")) - if err != nil { - t.Fatalf("discovered model delete: %v", err) - } + testassert.NoError(t, err) generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) entry = candidate.snapshot.catalog.Providers["disco"].Models["disc-model"] - if entry == nil || entry.Source != catalog.SourceDiscovered || entry.MaxOutputTokens != 256 { - t.Fatalf("discovered model after delete = %+v, want the discovered base restored", entry) - } + testassert.Require(t, entry != nil && entry.Source == catalog.SourceDiscovered && entry.MaxOutputTokens == 256, "discovered model after delete = %+v, want the discovered base restored", entry) // The correctable member lands again; the override resets to the // discovered value. maxOut := 100 - if _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{MaxOutputTokens: &maxOut})); err != nil { - t.Fatalf("discovered correctable patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{MaxOutputTokens: &maxOut})) + testassert.NoError(t, err) } generation++ drainConnectionEvent(t, svc.warnings, sub, strconv.Itoa(generation)) candidate, err = svc.mutate(context.Background(), svc.editModelFieldReset("disco", "disc-model", protocol.ModelFieldMaxOutputTokens)) - if err != nil || candidate.snapshot.catalog.Providers["disco"].Models["disc-model"].MaxOutputTokens != 256 { - t.Fatalf("discovered reset = (%v, max output %d), want the discovered 256 restored", err, candidate.snapshot.catalog.Providers["disco"].Models["disc-model"].MaxOutputTokens) - } + testassert.Require(t, err == nil && candidate.snapshot.catalog.Providers["disco"].Models["disc-model"].MaxOutputTokens == 256, "discovered reset = (%v, max output %d), want the discovered 256 restored", err, candidate.snapshot.catalog.Providers["disco"].Models["disc-model"].MaxOutputTokens) } // --- Runtime operators over the composed runtime --- @@ -1492,56 +1296,43 @@ func TestRuntimeProviderMetadataOperators(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() window := 4096 models := map[string]protocol.ModelEdit{"m": {ContextWindow: &window}} // The create: the projected result is the new provider view. mutation, err := r.addProvider(ctx, "newp", - protocol.ProviderEdit{BaseUrl: &[]string{"https://new.test/v1"}[0]}, models, nil) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("addProvider = (%v, %+v), want generation 2", err, mutation.ConfigurationRevision) - } - if mutation.Result.Id != "newp" || mutation.Result.Builtin || len(mutation.Result.Models) != 1 || mutation.Result.Models[0].Source != protocol.ModelSource("user") { - t.Fatalf("created result = %+v, want the newp provider view with its user model", mutation.Result) - } + protocol.ProviderEdit{BaseUrl: ptrTo("https://new.test/v1")}, models, nil) + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "addProvider = (%v, %+v), want generation 2", err, mutation.ConfigurationRevision) + testassert.Require(t, mutation.Result.Id == "newp" && !mutation.Result.Builtin && len(mutation.Result.Models) == 1 && mutation.Result.Models[0].Source == protocol.ModelSource("user"), "created result = %+v, want the newp provider view with its user model", mutation.Result) drainMutationEventWithWarning(t, sub, "2", r.warnings) // The patch: the result projects the returned candidate. name := "Patched" mutation, err = r.updateProvider(ctx, "prov", protocol.ProviderEdit{Name: &name}) - if err != nil || mutation.ConfigurationRevision.Generation != "3" || mutation.Result.Name != "Patched" { - t.Fatalf("updateProvider = (%v, %+v), want the patched view at generation 3", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "3" && mutation.Result.Name == "Patched", "updateProvider = (%v, %+v), want the patched view at generation 3", err, mutation) drainMutationEventWithWarning(t, sub, "3", r.warnings) // The reset: the previous effective name is restored. mutation, err = r.resetProviderField(ctx, "prov", protocol.ProviderFieldName) - if err != nil || mutation.ConfigurationRevision.Generation != "4" || mutation.Result.Name == "Patched" { - t.Fatalf("resetProviderField = (%v, %+v), want the restored view at generation 4", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "4" && mutation.Result.Name != "Patched", "resetProviderField = (%v, %+v), want the restored view at generation 4", err, mutation) drainMutationEvent(t, sub, "4") // The builtin user node's delete reveals the bundled base: the // result carries the surviving effective post-state. mutation, err = r.deleteProvider(ctx, "openrouter") - if err != nil || mutation.ConfigurationRevision.Generation != "5" || mutation.Result == nil || mutation.Result.Id != "openrouter" || !mutation.Result.Builtin { - t.Fatalf("builtin delete = (%v, %+v), want the revealed bundled base at generation 5", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "5" && mutation.Result != nil && mutation.Result.Id == "openrouter" && mutation.Result.Builtin, "builtin delete = (%v, %+v), want the revealed bundled base at generation 5", err, mutation) drainConnectionEvent(t, r.warnings, sub, "5") // The hidden reset removes its own override. - if _, err := r.updateProvider(ctx, "prov", protocol.ProviderEdit{Hidden: &[]bool{true}[0]}); err != nil { - t.Fatalf("hidden seed: %v", err) + { + _, err := r.updateProvider(ctx, "prov", protocol.ProviderEdit{Hidden: ptrTo(true)}) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "6") mutation, err = r.resetProviderField(ctx, "prov", protocol.ProviderFieldHidden) - if err != nil || mutation.ConfigurationRevision.Generation != "7" || mutation.Result.Hidden { - t.Fatalf("hidden reset = (%v, %+v), want the removed override at generation 7", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "7" && !mutation.Result.Hidden, "hidden reset = (%v, %+v), want the removed override at generation 7", err, mutation) drainConnectionEvent(t, r.warnings, sub, "7") // The refusals, each with the full state oracle: the typed failure @@ -1552,7 +1343,7 @@ func TestRuntimeProviderMetadataOperators(t *testing.T) { want error }{ {"duplicate create", func() error { - _, err := r.addProvider(ctx, "prov", protocol.ProviderEdit{BaseUrl: &[]string{"https://x.test/v1"}[0]}, models, nil) + _, err := r.addProvider(ctx, "prov", protocol.ProviderEdit{BaseUrl: ptrTo("https://x.test/v1")}, models, nil) return err }, harness.ErrInvalid}, {"unknown delete", func() error { @@ -1575,9 +1366,7 @@ func TestRuntimeProviderMetadataOperators(t *testing.T) { // The standalone user definition's delete: the same operator with a // null post-state — no subject remains. mutation, err = r.deleteProvider(ctx, "newp") - if err != nil || mutation.ConfigurationRevision.Generation != "8" || mutation.Result != nil { - t.Fatalf("deleteProvider = (%v, %+v), want the null post-state at generation 8", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "8" && mutation.Result == nil, "deleteProvider = (%v, %+v), want the null post-state at generation 8", err, mutation) drainMutationEvent(t, sub, "8") }) } @@ -1601,67 +1390,55 @@ func TestRuntimeModelMetadataOperators(t *testing.T) { // model's user override. lockedName := "x" mutation, err := r.saveModel(ctx, "openrouter", bundledModel, protocol.ModelEdit{Name: &lockedName}) - if err != nil || mutation.ConfigurationRevision.Generation != "2" || mutation.Result.Name != lockedName { - t.Fatalf("bundled rename = (%v, %+v), want the landed override at generation 2", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Name == lockedName, "bundled rename = (%v, %+v), want the landed override at generation 2", err, mutation) // The correctable member lands on the bundled model. window := 2048 mutation, err = r.saveModel(ctx, "openrouter", bundledModel, protocol.ModelEdit{ContextWindow: &window}) - if err != nil || mutation.ConfigurationRevision.Generation != "3" || mutation.Result.ContextWindow != 2048 { - t.Fatalf("bundled correctable patch = (%v, %+v), want the new window at generation 3", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "3" && mutation.Result.ContextWindow == 2048, "bundled correctable patch = (%v, %+v), want the new window at generation 3", err, mutation) // The override's delete reveals the bundled base; the reset restores // the bundled window too. mutation2, err := r.deleteModel(ctx, "openrouter", bundledModel) - if err != nil || mutation2.ConfigurationRevision.Generation != "4" || mutation2.Result == nil || mutation2.Result.Hidden { - t.Fatalf("bundled delete = (%v, %+v), want the revealed base at generation 4", err, mutation2) - } + testassert.Require(t, err == nil && mutation2.ConfigurationRevision.Generation == "4" && mutation2.Result != nil && !mutation2.Result.Hidden, "bundled delete = (%v, %+v), want the revealed base at generation 4", err, mutation2) mutation, err = r.resetModelField(ctx, "openrouter", bundledModel, protocol.ModelFieldContextWindow) - if err != nil || mutation.ConfigurationRevision.Generation != "5" { - t.Fatalf("bundled reset = (%v, %+v), want generation 5", err, mutation) - } - if mutation.Result.ContextWindow == 2048 || mutation.Result.ContextWindow == 0 { - t.Fatalf("reset window = %d, want the restored bundled value", mutation.Result.ContextWindow) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "5", "bundled reset = (%v, %+v), want generation 5", err, mutation) + testassert.Require(t, mutation.Result.ContextWindow != 2048 && mutation.Result.ContextWindow != 0, "reset window = %d, want the restored bundled value", mutation.Result.ContextWindow) // The slash-containing model ID is an exact query value; the same ID // upserts twice without a duplicate error. slashWindow := 10 for _, generation := range []string{"6", "7"} { mutation, err = r.saveModel(ctx, "prov", "org/model", protocol.ModelEdit{ContextWindow: &slashWindow}) - if err != nil || mutation.ConfigurationRevision.Generation != generation || mutation.Result.Id != "org/model" { - t.Fatalf("slash upsert = (%v, %+v), want the model at generation %s", err, mutation, generation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == generation && mutation.Result.Id == "org/model", "slash upsert = (%v, %+v), want the model at generation %s", err, mutation, generation) } // The standalone user model's delete: the same operator with a null // post-state. deletion, err := r.deleteModel(ctx, "prov", "org/model") - if err != nil || deletion.ConfigurationRevision.Generation != "8" || deletion.Result != nil { - t.Fatalf("deleteModel = (%v, %+v), want the null post-state at generation 8", err, deletion) - } + testassert.Require(t, err == nil && deletion.ConfigurationRevision.Generation == "8" && deletion.Result == nil, "deleteModel = (%v, %+v), want the null post-state at generation 8", err, deletion) // The typed unknown failures. - if _, err := r.saveModel(ctx, "ghost", "m", protocol.ModelEdit{}); !errors.Is(err, catalog.ErrUnknownProvider) { - t.Fatalf("unknown provider = %v, want catalog.ErrUnknownProvider", err) + { + _, err := r.saveModel(ctx, "ghost", "m", protocol.ModelEdit{}) + testassert.Require(t, errors.Is(err, catalog.ErrUnknownProvider), "unknown provider = %v, want catalog.ErrUnknownProvider", err) } - if _, err := r.deleteModel(ctx, "prov", "ghost"); !errors.Is(err, catalog.ErrUnknownModel) { - t.Fatalf("unknown model = %v, want catalog.ErrUnknownModel", err) + { + _, err := r.deleteModel(ctx, "prov", "ghost") + testassert.Require(t, errors.Is(err, catalog.ErrUnknownModel), "unknown model = %v, want catalog.ErrUnknownModel", err) } - if _, err := r.saveModel(ctx, "", "m", protocol.ModelEdit{}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty provider id = %v, want harness.ErrInvalid", err) + { + _, err := r.saveModel(ctx, "", "m", protocol.ModelEdit{}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty provider id = %v, want harness.ErrInvalid", err) } // The owning file carried every published edit and nothing else. root := fileRoot(t, e.configPath) var providers map[string]json.RawMessage - if err := json.Unmarshal(root["providers"], &providers); err != nil { - t.Fatalf("decode providers: %v", err) - } - if _, present := providers["org/model"]; present { - t.Fatal("a slash model ID became a provider-level member") + testassert.NoError(t, json.Unmarshal(root["providers"], &providers)) + { + _, present := providers["org/model"] + testassert.Require(t, !present, "a slash model ID became a provider-level member") } }) } @@ -1677,9 +1454,7 @@ func TestRuntimeModelMetadataMutationAffectsNextAdmissions(t *testing.T) { writeServiceFile(t, e.configPath, ownerConfigDocument(e.server.URL)) writeServiceFile(t, agents.PathForConfig(e.configPath), `{"worker":{"model":"prov/m","system_prompt":"simple"}}`) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) ctx := context.Background() @@ -1697,28 +1472,20 @@ func TestRuntimeModelMetadataMutationAffectsNextAdmissions(t *testing.T) { // boundary is under test, not a compaction trigger. window := 131072 mutation, err := r.saveModel(ctx, "prov", "m", protocol.ModelEdit{ContextWindow: &window}) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("saveModel = (%v, %+v), want generation 2", err, mutation.ConfigurationRevision) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "saveModel = (%v, %+v), want generation 2", err, mutation.ConfigurationRevision) // Session B's next admission commits its capture under the new // generation while the active Operation has not re-captured. submitThroughRuntime(t, r, b.Identity.SessionID, "op-b1", "b turn") - if got := committedAdmission(t, r, b.Identity.SessionID, "op-b1"); got != "worker=prov/m@2" { - t.Fatalf("B's committed capture = %q, want the admission under generation 2", got) - } - if got := committedAdmission(t, r, a.Identity.SessionID, "op-active"); got != "worker=prov/m@1" { - t.Fatalf("A's committed capture = %q, want the original capture kept by the running Operation", got) - } + testassert.Equal(t, committedAdmission(t, r, b.Identity.SessionID, "op-b1"), "worker=prov/m@2") + testassert.Equal(t, committedAdmission(t, r, a.Identity.SessionID, "op-active"), "worker=prov/m@1") // Both captures settle; A's next admission uses the new publication. close(gate) awaitIdleSession(t, r, a.Identity.SessionID) awaitIdleSession(t, r, b.Identity.SessionID) submitConvergedThroughRuntime(t, r, a.Identity.SessionID, "op-a2", "a turn", harness.OperationSuccess) - if got := committedAdmission(t, r, a.Identity.SessionID, "op-a2"); got != "worker=prov/m@2" { - t.Fatalf("A's next committed capture = %q, want the admission under the mutated publication", got) - } + testassert.Equal(t, committedAdmission(t, r, a.Identity.SessionID, "op-a2"), "worker=prov/m@2") }) } @@ -1736,86 +1503,83 @@ func TestMetadataEditModelPatchPreservesUnsuppliedValues(t *testing.T) { // Seed the user model with every meaningful writable member. name := "M2" - cost := protocol.Cost{Input: &[]float64{2.5}[0]} + cost := protocol.Cost{Input: ptrTo(2.5)} extra := map[string]any{"x": 1} - meta := protocol.ProtocolMetadata{Family: &[]string{"fam"}[0]} + meta := protocol.ProtocolMetadata{Family: ptrTo("fam")} stream := true hidden := true - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{ - Name: &name, ContextWindow: &[]int{4096}[0], MaxOutputTokens: &[]int{100}[0], - Cost: &cost, ExtraBody: &extra, ProtocolMetadata: &meta, UsageInStream: &stream, Hidden: &hidden, - })); err != nil { - t.Fatalf("user seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{ + Name: &name, ContextWindow: ptrTo(4096), MaxOutputTokens: ptrTo(100), + Cost: &cost, ExtraBody: &extra, ProtocolMetadata: &meta, UsageInStream: &stream, Hidden: &hidden, + })) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "2") // The one-member patch: everything unsupplied survives in the raw entry // and in the catalog projection. renamed := "M3" - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{Name: &renamed})); err != nil { - t.Fatalf("user one-member patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{Name: &renamed})) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "3") entry := svc.current().catalog.Providers["prov"].Models["m"] - if entry == nil || entry.Name != "M3" || entry.ContextWindow != 4096 || entry.MaxOutputTokens != 100 || - entry.Cost == nil || entry.Cost.Input == nil || *entry.Cost.Input != 2.5 || - fmt.Sprint(entry.ExtraBody["x"]) != "1" || entry.ProtocolMetadata == nil || entry.ProtocolMetadata.Family != "fam" || - !entry.UsageInStream || !entry.Hidden { - t.Fatalf("user model after one-member patch = %+v, want every unsupplied value preserved", entry) - } + testassert.Require(t, entry != nil && entry.Name == "M3" && entry.ContextWindow == 4096 && entry.MaxOutputTokens == 100 && entry.Cost != nil && entry.Cost.Input != nil && *entry.Cost.Input == 2.5 && fmt.Sprint(entry.ExtraBody["x"]) == "1" && entry.ProtocolMetadata != nil && entry.ProtocolMetadata.Family == "fam" && entry.UsageInStream && entry.Hidden, "user model after one-member patch = %+v, want every unsupplied value preserved", entry) rawModel := metadataRawModel(t, h.configPath, "prov", "m") for _, member := range []string{"context_window", "max_output_tokens", "cost", "extra_body", "protocol_metadata", "usage_in_stream", "hidden"} { - if _, present := rawModel[member]; !present { - t.Fatalf("user raw model = %s, want the unsupplied %s member preserved", rawModel, member) + { + _, present := rawModel[member] + testassert.Require(t, present, "user raw model = %s, want the unsupplied %s member preserved", rawModel, member) } } // An empty patch wipes nothing: the user model's definition — its usable // window included — is retained, not cleared. - if _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{})); err != nil { - t.Fatalf("empty user patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("prov", "m", protocol.ModelEdit{})) + testassert.NoError(t, err) } - if entry = svc.current().catalog.Providers["prov"].Models["m"]; entry == nil || entry.ContextWindow != 4096 || entry.Name != "M3" { - t.Fatalf("user model after empty patch = %+v, want the whole definition retained", entry) + { + entry = svc.current().catalog.Providers["prov"].Models["m"] + testassert.Require(t, entry != nil && entry.ContextWindow == 4096 && entry.Name == "M3", "user model after empty patch = %+v, want the whole definition retained", entry) } drainMutationEvent(t, sub, "4") // The bundled override: a one-member patch preserves the earlier // override members and the bundled defaults of the unsupplied ones. - if _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ - ContextWindow: &[]int{2000}[0], MaxOutputTokens: &[]int{50}[0], Cost: &cost, ExtraBody: &extra, Hidden: &hidden, - })); err != nil { - t.Fatalf("bundled seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{ + ContextWindow: ptrTo(2000), MaxOutputTokens: ptrTo(50), Cost: &cost, ExtraBody: &extra, Hidden: &hidden, + })) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "5") - if _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{MaxOutputTokens: &[]int{60}[0]})); err != nil { - t.Fatalf("bundled one-member patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("bstatic", "bm", protocol.ModelEdit{MaxOutputTokens: ptrTo(60)})) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "6") entry = svc.current().catalog.Providers["bstatic"].Models["bm"] - if entry == nil || entry.ContextWindow != 2000 || entry.MaxOutputTokens != 60 || - entry.Cost == nil || entry.Cost.Input == nil || *entry.Cost.Input != 2.5 || - fmt.Sprint(entry.ExtraBody["x"]) != "1" || !entry.Hidden { - t.Fatalf("bundled model after one-member patch = %+v, want the earlier override members preserved", entry) - } + testassert.Require(t, entry != nil && entry.ContextWindow == 2000 && entry.MaxOutputTokens == 60 && entry.Cost != nil && entry.Cost.Input != nil && *entry.Cost.Input == 2.5 && fmt.Sprint(entry.ExtraBody["x"]) == "1" && entry.Hidden, "bundled model after one-member patch = %+v, want the earlier override members preserved", entry) // The discovered override: the same preservation rule. unsetenv(t, "DISCO_TEST_KEY") // never ready: no network anywhere below seedDiscoveredModel(t, h) - if _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{ - MaxOutputTokens: &[]int{100}[0], Cost: &cost, ExtraBody: &extra, Hidden: &hidden, - })); err != nil { - t.Fatalf("discovered seed: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{ + MaxOutputTokens: ptrTo(100), Cost: &cost, ExtraBody: &extra, Hidden: &hidden, + })) + testassert.NoError(t, err) } drainMutationEvent(t, sub, "7") - if _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{Cost: &protocol.Cost{Output: &[]float64{3}[0]}})); err != nil { - t.Fatalf("discovered one-member patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editModelSave("disco", "disc-model", protocol.ModelEdit{Cost: &protocol.Cost{Output: ptrTo(3.0)}})) + testassert.NoError(t, err) } entry = svc.current().catalog.Providers["disco"].Models["disc-model"] - if entry == nil || entry.MaxOutputTokens != 100 || entry.Cost == nil || entry.Cost.Output == nil || - *entry.Cost.Output != 3 || entry.Cost.Input != nil || fmt.Sprint(entry.ExtraBody["x"]) != "1" || !entry.Hidden { - t.Fatalf("discovered model after one-member patch = %+v, want the earlier override members preserved", entry) - } + testassert.Require(t, entry != nil && entry.MaxOutputTokens == 100 && entry.Cost != nil && entry.Cost.Output != nil && *entry.Cost.Output == 3 && entry.Cost.Input == nil && fmt.Sprint(entry.ExtraBody["x"]) == "1" && entry.Hidden, "discovered model after one-member patch = %+v, want the earlier override members preserved", entry) } // metadataRawModel decodes one provider's raw model entry from the owning @@ -1824,17 +1588,11 @@ func metadataRawModel(t *testing.T, configPath, providerID, modelID string) map[ t.Helper() root := fileRoot(t, configPath) var rawProviders map[string]map[string]json.RawMessage - if err := json.Unmarshal(root["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(root["providers"], &rawProviders)) var rawModels map[string]json.RawMessage - if err := json.Unmarshal(rawProviders[providerID]["models"], &rawModels); err != nil { - t.Fatalf("decode models: %v", err) - } + testassert.NoError(t, json.Unmarshal(rawProviders[providerID]["models"], &rawModels)) var rawModel map[string]json.RawMessage - if err := json.Unmarshal(rawModels[modelID], &rawModel); err != nil { - t.Fatalf("decode model: %v", err) - } + testassert.NoError(t, json.Unmarshal(rawModels[modelID], &rawModel)) return rawModel } @@ -1869,8 +1627,9 @@ func TestMetadataEditHiddenVisibility(t *testing.T) { drainMutationEvent(t, sub, strconv.Itoa(generation)) assertNoEvent(t, sub) rawModel := metadataRawProvider(t, h.configPath, row.providerID) - if got, present := rawModel["hidden"]; !present || string(got) != strconv.FormatBool(hidden) { - t.Fatalf("%s raw hidden = %s (present %v), want %v", row.providerID, got, present, hidden) + { + got, present := rawModel["hidden"] + testassert.Require(t, present && string(got) == strconv.FormatBool(hidden), "%s raw hidden = %s (present %v), want %v", row.providerID, got, present, hidden) } } } @@ -1884,31 +1643,31 @@ func TestMetadataEditHiddenVisibility(t *testing.T) { } { for _, hidden := range []bool{true, false} { candidate, err := svc.mutate(context.Background(), svc.editModelSave(row.providerID, row.modelID, protocol.ModelEdit{Hidden: &hidden})) - if err != nil || candidate.snapshot.catalog.Providers[row.providerID].Models[row.modelID] == nil || - candidate.snapshot.catalog.Providers[row.providerID].Models[row.modelID].Hidden != hidden { - t.Fatalf("%s/%s hidden %v patch = %v, want the written state", row.providerID, row.modelID, hidden, err) - } + testassert.Require(t, err == nil && candidate.snapshot.catalog.Providers[row.providerID].Models[row.modelID] != nil && candidate.snapshot.catalog.Providers[row.providerID].Models[row.modelID].Hidden == hidden, "%s/%s hidden %v patch = %v, want the written state", row.providerID, row.modelID, hidden, err) generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) assertNoEvent(t, sub) rawModel := metadataRawModel(t, h.configPath, row.providerID, row.modelID) - if got, present := rawModel["hidden"]; !present || string(got) != strconv.FormatBool(hidden) { - t.Fatalf("%s/%s raw hidden = %s (present %v), want %v", row.providerID, row.modelID, got, present, hidden) + { + got, present := rawModel["hidden"] + testassert.Require(t, present && string(got) == strconv.FormatBool(hidden), "%s/%s raw hidden = %s (present %v), want %v", row.providerID, row.modelID, got, present, hidden) } } } // An absent hidden member leaves the written value untouched (the patch // rule, not a visibility setter). - if _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{Name: &[]string{"Renamed"}[0]})); err != nil { - t.Fatalf("absent-hidden patch: %v", err) + { + _, err := svc.mutate(context.Background(), svc.editProviderUpdate("prov", protocol.ProviderEdit{Name: ptrTo("Renamed")})) + testassert.NoError(t, err) } generation++ drainMutationEvent(t, sub, strconv.Itoa(generation)) assertNoEvent(t, sub) rawModel := metadataRawProvider(t, h.configPath, "prov") - if got, present := rawModel["hidden"]; !present || string(got) != "false" { - t.Fatalf("prov raw hidden after absent patch = %s (present %v), want false untouched", got, present) + { + got, present := rawModel["hidden"] + testassert.Require(t, present && string(got) == "false", "prov raw hidden after absent patch = %s (present %v), want false untouched", got, present) } } @@ -1918,13 +1677,9 @@ func metadataRawProvider(t *testing.T, configPath, providerID string) map[string t.Helper() root := fileRoot(t, configPath) var rawProviders map[string]json.RawMessage - if err := json.Unmarshal(root["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(root["providers"], &rawProviders)) var rawProvider map[string]json.RawMessage - if err := json.Unmarshal(rawProviders[providerID], &rawProvider); err != nil { - t.Fatalf("decode provider: %v", err) - } + testassert.NoError(t, json.Unmarshal(rawProviders[providerID], &rawProvider)) return rawProvider } @@ -1942,66 +1697,52 @@ func TestRuntimeHiddenVisibilityPicker(t *testing.T) { // The connected keyless custom provider: its model is visible. mutation, err := r.addProvider(ctx, "keyless2", - protocol.ProviderEdit{BaseUrl: &[]string{"https://k2.test/v1"}[0]}, models, nil) - if err != nil || mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("addProvider = (%v, %+v), want generation 2", err, mutation.ConfigurationRevision) - } + protocol.ProviderEdit{BaseUrl: ptrTo("https://k2.test/v1")}, models, nil) + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2", "addProvider = (%v, %+v), want generation 2", err, mutation.ConfigurationRevision) visible, err := r.listModels(ctx, protocol.ListModelsParams{}) - if err != nil || !modelListHas(visible, "keyless2", "m") { - t.Fatalf("visible picker = %+v (%v), want the connected keyless model", visible, err) - } + testassert.Require(t, err == nil && modelListHas(visible, "keyless2", "m"), "visible picker = %+v (%v), want the connected keyless model", visible, err) // hidden=true: the model leaves the visible picker, the full list // keeps it, and the raw file carries the owned member. hidden := true mutation, err = r.updateProvider(ctx, "keyless2", protocol.ProviderEdit{Hidden: &hidden}) - if err != nil || mutation.ConfigurationRevision.Generation != "3" || !mutation.Result.Hidden { - t.Fatalf("hidden patch = (%v, %+v), want the hidden view at generation 3", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "3" && mutation.Result.Hidden, "hidden patch = (%v, %+v), want the hidden view at generation 3", err, mutation) visible, err = r.listModels(ctx, protocol.ListModelsParams{}) - if err != nil || modelListHas(visible, "keyless2", "m") { - t.Fatalf("visible picker after hide = %+v (%v), want the model excluded", visible, err) - } + testassert.Require(t, err == nil && !modelListHas(visible, "keyless2", "m"), "visible picker after hide = %+v (%v), want the model excluded", visible, err) full, err := r.listModels(ctx, protocol.ListModelsParams{All: true}) - if err != nil || !modelListHas(full, "keyless2", "m") { - t.Fatalf("full picker after hide = %+v (%v), want the model retained", full, err) - } - if got := metadataRawProvider(t, e.configPath, "keyless2")["hidden"]; string(got) != "true" { - t.Fatalf("raw hidden = %s, want true", got) + testassert.Require(t, err == nil && modelListHas(full, "keyless2", "m"), "full picker after hide = %+v (%v), want the model retained", full, err) + { + got := metadataRawProvider(t, e.configPath, "keyless2")["hidden"] + testassert.Require(t, string(got) == "true", "raw hidden = %s, want true", got) } // hidden=false: the model rejoins the visible picker. hidden = false mutation, err = r.updateProvider(ctx, "keyless2", protocol.ProviderEdit{Hidden: &hidden}) - if err != nil || mutation.ConfigurationRevision.Generation != "4" || mutation.Result.Hidden { - t.Fatalf("unhide patch = (%v, %+v), want the visible view at generation 4", err, mutation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "4" && !mutation.Result.Hidden, "unhide patch = (%v, %+v), want the visible view at generation 4", err, mutation) visible, err = r.listModels(ctx, protocol.ListModelsParams{}) - if err != nil || !modelListHas(visible, "keyless2", "m") { - t.Fatalf("visible picker after unhide = %+v (%v), want the model back", visible, err) - } - if got := metadataRawProvider(t, e.configPath, "keyless2")["hidden"]; string(got) != "false" { - t.Fatalf("raw hidden = %s, want false", got) + testassert.Require(t, err == nil && modelListHas(visible, "keyless2", "m"), "visible picker after unhide = %+v (%v), want the model back", visible, err) + { + got := metadataRawProvider(t, e.configPath, "keyless2")["hidden"] + testassert.Require(t, string(got) == "false", "raw hidden = %s, want false", got) } // The builtin provider hides through the same patch; its raw // override entry carries the member. hidden = true mutation, err = r.updateProvider(ctx, "openrouter", protocol.ProviderEdit{Hidden: &hidden}) - if err != nil || mutation.ConfigurationRevision.Generation != "5" || !mutation.Result.Hidden { - t.Fatalf("builtin hide = (%v, %+v), want the hidden view at generation 5", err, mutation) - } - if got := metadataRawProvider(t, e.configPath, "openrouter")["hidden"]; string(got) != "true" { - t.Fatalf("builtin raw hidden = %s, want true", got) + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "5" && mutation.Result.Hidden, "builtin hide = (%v, %+v), want the hidden view at generation 5", err, mutation) + { + got := metadataRawProvider(t, e.configPath, "openrouter")["hidden"] + testassert.Require(t, string(got) == "true", "builtin raw hidden = %s, want true", got) } // The user model hides through the model patch. modelMutation, err := r.saveModel(ctx, "prov", "m", protocol.ModelEdit{Hidden: &hidden}) - if err != nil || modelMutation.ConfigurationRevision.Generation != "6" || !modelMutation.Result.Hidden { - t.Fatalf("model hide = (%v, %+v), want the hidden view at generation 6", err, modelMutation) - } - if got := metadataRawModel(t, e.configPath, "prov", "m")["hidden"]; string(got) != "true" { - t.Fatalf("model raw hidden = %s, want true", got) + testassert.Require(t, err == nil && modelMutation.ConfigurationRevision.Generation == "6" && modelMutation.Result.Hidden, "model hide = (%v, %+v), want the hidden view at generation 6", err, modelMutation) + { + got := metadataRawModel(t, e.configPath, "prov", "m")["hidden"] + testassert.Require(t, string(got) == "true", "model raw hidden = %s, want true", got) } }) } @@ -2034,49 +1775,36 @@ func TestRuntimeSpecialProviderIDsDirectOperators(t *testing.T) { window := 1 models := map[string]protocol.ModelEdit{"m": {ContextWindow: &window}} - mutation, err := r.addProvider(ctx, id, protocol.ProviderEdit{BaseUrl: &[]string{"https://id.test/v1"}[0]}, models, nil) - if err != nil || mutation.ConfigurationRevision.Generation != "2" || mutation.Result.Id != id { - t.Fatalf("addProvider(%q) = (%v, %+v), want the exact ID at generation 2", id, err, mutation.Result) - } - if _, present := metadataRawProvider(t, e.configPath, id)["transport"]; !present { - t.Fatalf("raw providers key %q missing", id) + mutation, err := r.addProvider(ctx, id, protocol.ProviderEdit{BaseUrl: ptrTo("https://id.test/v1")}, models, nil) + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Id == id, "addProvider(%q) = (%v, %+v), want the exact ID at generation 2", id, err, mutation.Result) + { + _, present := metadataRawProvider(t, e.configPath, id)["transport"] + testassert.Require(t, present, "raw providers key %q missing", id) } name := "Ident" mutation, err = r.updateProvider(ctx, id, protocol.ProviderEdit{Name: &name}) - if err != nil || mutation.Result.Id != id || mutation.Result.Name != name { - t.Fatalf("updateProvider(%q) = (%v, %+v), want the exact ID", id, err, mutation.Result) - } + testassert.Require(t, err == nil && mutation.Result.Id == id && mutation.Result.Name == name, "updateProvider(%q) = (%v, %+v), want the exact ID", id, err, mutation.Result) modelMutation, err := r.saveModel(ctx, id, "m", protocol.ModelEdit{MaxOutputTokens: &window}) - if err != nil || modelMutation.Result.Id != "m" || modelMutation.Result.ProviderHidden { - t.Fatalf("saveModel under %q = (%v, %+v), want the model view", id, err, modelMutation.Result) - } + testassert.Require(t, err == nil && modelMutation.Result.Id == "m" && !modelMutation.Result.ProviderHidden, "saveModel under %q = (%v, %+v), want the model view", id, err, modelMutation.Result) mutation, err = r.resetProviderField(ctx, id, protocol.ProviderFieldName) - if err != nil || mutation.Result.Id != id || mutation.Result.Name == name { - t.Fatalf("resetProviderField(%q) = (%v, %+v), want the restored name", id, err, mutation.Result) - } + testassert.Require(t, err == nil && mutation.Result.Id == id && mutation.Result.Name != name, "resetProviderField(%q) = (%v, %+v), want the restored name", id, err, mutation.Result) // The model-field reset and the user-model delete keep the // exact identities too: all seven operators. modelMutation, err = r.resetModelField(ctx, id, "m", protocol.ModelFieldMaxOutputTokens) - if err != nil || modelMutation.Result.Id != "m" || modelMutation.Result.MaxOutputTokens != 0 { - t.Fatalf("resetModelField(%q, m) = (%v, %+v), want the restored default", id, err, modelMutation.Result) - } + testassert.Require(t, err == nil && modelMutation.Result.Id == "m" && modelMutation.Result.MaxOutputTokens == 0, "resetModelField(%q, m) = (%v, %+v), want the restored default", id, err, modelMutation.Result) modelDeletion, err := r.deleteModel(ctx, id, "m") - if err != nil || modelDeletion.ConfigurationRevision.Generation != "7" || modelDeletion.Result != nil { - t.Fatalf("deleteModel(%q, m) = (%v, %+v), want the null deletion at generation 7", id, err, modelDeletion) - } + testassert.Require(t, err == nil && modelDeletion.ConfigurationRevision.Generation == "7" && modelDeletion.Result == nil, "deleteModel(%q, m) = (%v, %+v), want the null deletion at generation 7", id, err, modelDeletion) deletion, err := r.deleteProvider(ctx, id) - if err != nil || deletion.ConfigurationRevision.Generation != "8" || deletion.Result != nil { - t.Fatalf("deleteProvider(%q) = (%v, %+v), want the null deletion at generation 8", id, err, deletion) - } - if _, present := fileRoot(t, e.configPath)["providers"]; !present { - t.Fatal("providers member missing") + testassert.Require(t, err == nil && deletion.ConfigurationRevision.Generation == "8" && deletion.Result == nil, "deleteProvider(%q) = (%v, %+v), want the null deletion at generation 8", id, err, deletion) + { + _, present := fileRoot(t, e.configPath)["providers"] + testassert.Require(t, present, "providers member missing") } var providers map[string]json.RawMessage - if err := json.Unmarshal(fileRoot(t, e.configPath)["providers"], &providers); err != nil { - t.Fatalf("decode providers: %v", err) - } - if _, present := providers[id]; present { - t.Fatalf("raw providers key %q survived the delete", id) + testassert.NoError(t, json.Unmarshal(fileRoot(t, e.configPath)["providers"], &providers)) + { + _, present := providers[id] + testassert.Require(t, !present, "raw providers key %q survived the delete", id) } }) }) @@ -2092,13 +1820,9 @@ func TestRuntimeSpecialProviderIDsDirectOperators(t *testing.T) { func runtimeMutationBaseline(t *testing.T, r *Runtime) ([]byte, uint64, uint64) { t.Helper() data, err := os.ReadFile(r.config.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + testassert.NoError(t, err) current := r.config.current() - if current == nil { - t.Fatal("no published configuration") - } + testassert.Require(t, current != nil, "no published configuration") warnRev, _ := r.warnings.snapshot() return data, current.generation, warnRev } @@ -2108,21 +1832,17 @@ func runtimeMutationBaseline(t *testing.T, r *Runtime) ([]byte, uint64, uint64) // revision, and event silence. func assertRuntimeMutationRefused(t *testing.T, r *Runtime, sub *Subscription, before []byte, generation uint64, warnRev uint64, err error, want error) { t.Helper() - if err == nil { - t.Fatal("Runtime metadata operator succeeded, want a refusal") - } - if !errors.Is(err, want) { - t.Fatalf("refusal = %v, want class %v", err, want) - } + testassert.Require(t, err != nil, "Runtime metadata operator succeeded, want a refusal") + testassert.Require(t, errors.Is(err, want), "refusal = %v, want class %v", err, want) after, rerr := os.ReadFile(r.config.configPath) - if rerr != nil || string(before) != string(after) { - t.Fatalf("a refused Runtime operator changed the owning file (%v)", rerr) - } - if current := r.config.current(); current == nil || current.generation != generation { - t.Fatalf("a refused Runtime operator advanced the generation to %v", current) + testassert.Require(t, rerr == nil && string(before) == string(after), "a refused Runtime operator changed the owning file (%v)", rerr) + { + current := r.config.current() + testassert.Require(t, current != nil && current.generation == generation, "a refused Runtime operator advanced the generation to %v", current) } - if got, _ := r.warnings.snapshot(); got != warnRev { - t.Fatalf("warning revision advanced on a refused operator: %d → %d", warnRev, got) + { + got, _ := r.warnings.snapshot() + testassert.Equal(t, got, warnRev) } assertNoEvent(t, sub) } @@ -2140,13 +1860,9 @@ func TestRuntimeMetadataCorruptRawTypesTypedFailure(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() - if r.config.current().catalog.Providers["prov"] == nil { - t.Fatal("fixture provider not published") - } + testassert.Require(t, r.config.current().catalog.Providers["prov"] != nil, "fixture provider not published") rows := []struct { name string @@ -2223,9 +1939,7 @@ func newOverlayFixture(t *testing.T) (*serviceHarness, *configurationService, *S func overlayJSON(t *testing.T, view any) string { t.Helper() data, err := json.Marshal(view) - if err != nil { - t.Fatalf("marshal overlay view: %v", err) - } + testassert.NoError(t, err) return string(data) } @@ -2299,25 +2013,16 @@ func TestMetadataEditFieldValuesExactAndFileAPIEquivalent(t *testing.T) { // Through the edit API. _, svcA, subA := newOverlayFixture(t) viaAPI, err := row.edit(svcA) - if err != nil { - t.Fatalf("api overlay: %v", err) - } + testassert.NoError(t, err) drainConnectionEvent(t, svcA.warnings, subA, "2") // Through the file plus reload. hB, svcB, _ := newOverlayFixture(t) writeServiceFile(t, hB.configPath, row.fileDoc(metadataConfigDocument)) viaFile, err := svcB.publish(context.Background()) - if err != nil { - t.Fatalf("file overlay publish: %v", err) - } + testassert.NoError(t, err) fileView := projectProvider(viaFile, viaFile.snapshot.catalog.Providers[row.providerID]) apiJSON, fileJSON := overlayJSON(t, viaAPI), overlayJSON(t, fileView) - if apiJSON != fileJSON { - t.Fatalf("the file-written overlay differs from the api-written one:\napi = %s\nfile = %s", apiJSON, fileJSON) - } - if !strings.Contains(apiJSON, row.exactValue) { - t.Fatalf("the overlay view = %s, want the exact provided value %q", apiJSON, row.exactValue) - } + testassert.Require(t, apiJSON == fileJSON && strings.Contains(apiJSON, row.exactValue), "got %v, want %v; the overlay view = %s, want the exact provided value %q", apiJSON, fileJSON, apiJSON, row.exactValue) }) } @@ -2355,9 +2060,7 @@ func TestMetadataEditFieldValuesExactAndFileAPIEquivalent(t *testing.T) { return projectModelView(candidate.snapshot.catalog.Providers["bstatic"], candidate.snapshot.catalog.Providers["bstatic"].Models["bm"]), nil }}, {"bundled multi-member name and context window", "bstatic", "bm", "Renamed", func(t *testing.T, view protocol.ModelView) { - if view.ContextWindow != 2000 { - t.Fatalf("bundled context_window = %d, want the retained multi-member overlay 2000", view.ContextWindow) - } + testassert.Equal(t, view.ContextWindow, 2000) }, func(doc string) string { return strings.Replace(doc, `"other": {`, `"bstatic": {"models": {"bm": {"name": "Renamed", "context_window": 2000}}}, "other": {`, 1) @@ -2384,9 +2087,7 @@ func TestMetadataEditFieldValuesExactAndFileAPIEquivalent(t *testing.T) { return projectModelView(candidate.snapshot.catalog.Providers["disco"], candidate.snapshot.catalog.Providers["disco"].Models["disc-model"]), nil }}, {"discovered max_output_tokens", "disco", "disc-model", `"max_output_tokens":100`, func(t *testing.T, view protocol.ModelView) { - if view.MaxOutputTokens != 100 { - t.Fatalf("discovered max_output_tokens = %d, want the retained overlay 100", view.MaxOutputTokens) - } + testassert.Equal(t, view.MaxOutputTokens, 100) }, func(doc string) string { return strings.Replace(doc, `"other": {`, `"disco": {"models": {"disc-model": {"max_output_tokens": 100}}}, "other": {`, 1) @@ -2404,24 +2105,15 @@ func TestMetadataEditFieldValuesExactAndFileAPIEquivalent(t *testing.T) { t.Run(row.name, func(t *testing.T) { _, svcA, subA := newOverlayFixture(t) viaAPI, err := row.edit(svcA) - if err != nil { - t.Fatalf("api overlay: %v", err) - } + testassert.NoError(t, err) drainConnectionEvent(t, svcA.warnings, subA, "2") hB, svcB, _ := newOverlayFixture(t) writeServiceFile(t, hB.configPath, row.fileDoc(metadataConfigDocument)) viaFile, err := svcB.publish(context.Background()) - if err != nil { - t.Fatalf("file overlay publish: %v", err) - } + testassert.NoError(t, err) fileView := projectModelView(viaFile.snapshot.catalog.Providers[row.providerID], viaFile.snapshot.catalog.Providers[row.providerID].Models[row.modelID]) apiJSON, fileJSON := overlayJSON(t, viaAPI), overlayJSON(t, fileView) - if apiJSON != fileJSON { - t.Fatalf("the file-written overlay differs from the api-written one:\napi = %s\nfile = %s", apiJSON, fileJSON) - } - if !strings.Contains(apiJSON, row.exactValue) { - t.Fatalf("the overlay view = %s, want the exact provided value %q", apiJSON, row.exactValue) - } + testassert.Require(t, apiJSON == fileJSON && strings.Contains(apiJSON, row.exactValue), "got %v, want %v; the overlay view = %s, want the exact provided value %q", apiJSON, fileJSON, apiJSON, row.exactValue) if row.check != nil { row.check(t, viaAPI) row.check(t, fileView) @@ -2444,11 +2136,11 @@ func TestMetadataEditPaddedInvalidValuesNeverBecomeValid(t *testing.T) { fileDoc func(doc string) string rawProbe string }{ - {"padded api_key_env", protocol.ProviderEdit{ApiKeyEnv: &[]string{" NEW_TEST_KEY "}[0]}, + {"padded api_key_env", protocol.ProviderEdit{ApiKeyEnv: ptrTo(" NEW_TEST_KEY ")}, func(doc string) string { return strings.Replace(doc, `"api_key_env": "META_TEST_KEY"`, `"api_key_env": " NEW_TEST_KEY "`, 1) }, `"api_key_env": " NEW_TEST_KEY "`}, - {"padded system_role", protocol.ProviderEdit{SystemRole: &[]protocol.SystemRole{" developer "}[0]}, + {"padded system_role", protocol.ProviderEdit{SystemRole: ptrTo(protocol.SystemRole(" developer "))}, func(doc string) string { return strings.Replace(doc, `"prov": {`, `"prov": {"system_role": " developer ",`, 1) }, `"system_role": " developer "`}, @@ -2473,14 +2165,11 @@ func TestMetadataEditPaddedInvalidValuesNeverBecomeValid(t *testing.T) { h, svc, _ := newOverlayFixture(t) writeServiceFile(t, h.configPath, row.fileDoc(metadataConfigDocument)) viaFile, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("file-side publish of the padded value: %v", err) - } - if viaFile.snapshot.catalog.Providers["prov"] != nil { - t.Fatalf("the padded %s became effective through the file path", row.name) - } - if data, rerr := os.ReadFile(h.configPath); rerr != nil || !strings.Contains(string(data), row.rawProbe) { - t.Fatalf("the raw file = (%q, %v), want the padded value retained", data, rerr) + testassert.NoError(t, err) + testassert.Require(t, viaFile.snapshot.catalog.Providers["prov"] == nil, "the padded %s became effective through the file path", row.name) + { + data := mustRead(t, h.configPath) + testassert.Require(t, strings.Contains(string(data), row.rawProbe), "the raw file = %q, want the padded value retained", data) } }) } @@ -2508,18 +2197,13 @@ func TestMetadataEditRetiredProviderControlsFollowUnknownFieldPolicy(t *testing. h, svc, _ := newOverlayFixture(t) writeServiceFile(t, h.configPath, row.fileDoc(metadataConfigDocument)) viaFile, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("file-side publish of the retired member: %v", err) - } - if viaFile.snapshot.catalog.Providers["prov"] != nil { - t.Fatalf("the %s became effective through the file path", row.name) - } + testassert.NoError(t, err) + testassert.Require(t, viaFile.snapshot.catalog.Providers["prov"] == nil, "the %s became effective through the file path", row.name) warnings := viaFile.snapshot.catalogWarnings - if len(warnings) == 0 || warnings[0].Kind != "user_config_skip" || warnings[0].Provider != "prov" { - t.Fatalf("catalog warnings = %#v, want the shared user_config_skip drop", warnings) - } - if data, rerr := os.ReadFile(h.configPath); rerr != nil || !strings.Contains(string(data), row.rawProbe) { - t.Fatalf("the raw file = (%q, %v), want the retired member retained", data, rerr) + testassert.Require(t, len(warnings) != 0 && warnings[0].Kind == "user_config_skip" && warnings[0].Provider == "prov", "catalog warnings = %#v, want the shared user_config_skip drop", warnings) + { + data := mustRead(t, h.configPath) + testassert.Require(t, strings.Contains(string(data), row.rawProbe), "the raw file = %q, want the retired member retained", data) } }) } @@ -2562,9 +2246,7 @@ func TestMetadataEditExternalRemovalRefusesWithoutReconstruction(t *testing.T) { // No partial patch reconstructed the removed definition. var rawProviders map[string]json.RawMessage - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) if _, present := rawProviders["prov"]; present { t.Fatalf("the refused edits reconstructed the removed definition: %s", fileRoot(t, h.configPath)["providers"]) } @@ -2582,9 +2264,7 @@ func TestMetadataEditRawOnlyIdentityAddressable(t *testing.T) { h := newMetadataHarness(t) writeServiceFile(t, h.configPath, rawOnlyDoc) svc, sub := metadataService(t, h) - if svc.current().catalog.Providers["brokenp"] != nil || svc.current().catalog.Providers["brokenq"] != nil { - t.Fatal("the raw-only providers were not dropped by the initial publication") - } + testassert.Require(t, svc.current().catalog.Providers["brokenp"] == nil && svc.current().catalog.Providers["brokenq"] == nil, "the raw-only providers were not dropped by the initial publication") // A non-repairing update applies to the raw node and refuses through the // candidate check: the identity stays addressable, the file unchanged. @@ -2596,33 +2276,24 @@ func TestMetadataEditRawOnlyIdentityAddressable(t *testing.T) { // The repairing update publishes the now-valid subject. fixed := "https://fixed.test/v1" candidate, err := svc.mutate(context.Background(), svc.editProviderUpdate("brokenp", protocol.ProviderEdit{BaseUrl: &fixed})) - if err != nil || candidate.snapshot.generation != 2 { - t.Fatalf("repairing update = (%v, generation %d), want the repaired publication at 2", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 2, "repairing update = (%v, generation %d), want the repaired publication at 2", err, candidate.snapshot.generation) repaired := candidate.snapshot.catalog.Providers["brokenp"] - if repaired == nil || repaired.Transport.BaseURL != fixed { - t.Fatalf("repaired provider = %+v, want the valid subject with its supplied base URL", repaired) - } + testassert.Require(t, repaired != nil && repaired.Transport.BaseURL == fixed, "repaired provider = %+v, want the valid subject with its supplied base URL", repaired) drainConnectionEvent(t, svc.warnings, sub, "2") // The raw-only delete removes exactly the raw node; no subject remains. deletion, err := svc.mutate(context.Background(), svc.editProviderDelete("brokenq")) - if err != nil || deletion.snapshot.generation != 3 { - t.Fatalf("raw-only delete = (%v, generation %d), want a publication at 3", err, deletion.snapshot.generation) - } + testassert.Require(t, err == nil && deletion.snapshot.generation == 3, "raw-only delete = (%v, generation %d), want a publication at 3", err, deletion.snapshot.generation) drainConnectionEvent(t, svc.warnings, sub, "3") - if deletion.snapshot.catalog.Providers["brokenq"] != nil { - t.Fatal("the raw-only delete left the subject in the candidate catalog") - } + testassert.Require(t, deletion.snapshot.catalog.Providers["brokenq"] == nil, "the raw-only delete left the subject in the candidate catalog") var rawProviders map[string]json.RawMessage - if err := json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders); err != nil { - t.Fatalf("decode providers: %v", err) - } + testassert.NoError(t, json.Unmarshal(fileRoot(t, h.configPath)["providers"], &rawProviders)) if _, present := rawProviders["brokenq"]; present { t.Fatalf("raw providers = %s, want the raw-only node gone", fileRoot(t, h.configPath)["providers"]) } - if _, present := rawProviders["brokenp"]; !present { - t.Fatal("the raw-only delete touched its repaired sibling") + { + _, present := rawProviders["brokenp"] + testassert.Require(t, present, "the raw-only delete touched its repaired sibling") } } @@ -2666,11 +2337,10 @@ func TestMetadataEditProviderTargetUsesSharedRawObjectAccess(t *testing.T) { writeServiceFile(t, h.configPath, `{"providers":{"bstatic":null}}`) name := "Null Override Replaced" candidate, err := svc.mutate(context.Background(), svc.editProviderUpdate("bstatic", protocol.ProviderEdit{Name: &name})) - if err != nil || candidate.snapshot.catalog.Providers["bstatic"] == nil || candidate.snapshot.catalog.Providers["bstatic"].Name != name { - t.Fatalf("builtin null override update = (%v, %+v), want the ordinary scaffolded override", err, candidate.snapshot.catalog.Providers["bstatic"]) - } - if _, present := metadataRawProvider(t, h.configPath, "bstatic")["name"]; !present { - t.Fatal("the builtin null raw member was not replaced by the user override") + testassert.Require(t, err == nil && candidate.snapshot.catalog.Providers["bstatic"] != nil && candidate.snapshot.catalog.Providers["bstatic"].Name == name, "builtin null override update = (%v, %+v), want the ordinary scaffolded override", err, candidate.snapshot.catalog.Providers["bstatic"]) + { + _, present := metadataRawProvider(t, h.configPath, "bstatic")["name"] + testassert.Require(t, present, "the builtin null raw member was not replaced by the user override") } } @@ -2704,9 +2374,7 @@ func TestRuntimeBuiltinWritablePositiveTable(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() rows := []struct { @@ -2714,32 +2382,27 @@ func TestRuntimeBuiltinWritablePositiveTable(t *testing.T) { patch protocol.ProviderEdit check func(protocol.ProviderMutation) }{ - {"disconnected api_key_env", protocol.ProviderEdit{ApiKeyEnv: &[]string{"NEW_BUILTIN_TEST_KEY"}[0]}, + {"disconnected api_key_env", protocol.ProviderEdit{ApiKeyEnv: ptrTo("NEW_BUILTIN_TEST_KEY")}, func(m protocol.ProviderMutation) { - if m.Result.ApiKeyEnv != "NEW_BUILTIN_TEST_KEY" || m.Result.Connected { - t.Fatalf("builtin env patch result = %+v, want the new name while disconnected", m.Result) - } + testassert.Require(t, m.Result.ApiKeyEnv == "NEW_BUILTIN_TEST_KEY" && !m.Result.Connected, "builtin env patch result = %+v, want the new name while disconnected", m.Result) if !strings.Contains(string(metadataRawProvider(t, e.configPath, "openrouter")["transport"]), `"NEW_BUILTIN_TEST_KEY"`) { t.Fatalf("builtin raw transport = %s, want the owned env name", metadataRawProvider(t, e.configPath, "openrouter")["transport"]) } }}, {"harmless extra_body", protocol.ProviderEdit{ExtraBody: &map[string]any{"provider_hint": "x"}}, func(m protocol.ProviderMutation) { - if got := *m.Result.ExtraBody; got["provider_hint"] != "x" { - t.Fatalf("builtin extra_body result = %v, want the written member", got) + { + got := *m.Result.ExtraBody + testassert.Require(t, got["provider_hint"] == "x", "builtin extra_body result = %v, want the written member", got) } }}, - {"discovery false", protocol.ProviderEdit{Discovery: &[]bool{false}[0]}, + {"discovery false", protocol.ProviderEdit{Discovery: ptrTo(false)}, func(m protocol.ProviderMutation) { - if m.Result.Discovery { - t.Fatal("builtin discovery result = true, want false") - } + testassert.Require(t, !m.Result.Discovery, "builtin discovery result = true, want false") }}, - {"discovery true", protocol.ProviderEdit{Discovery: &[]bool{true}[0]}, + {"discovery true", protocol.ProviderEdit{Discovery: ptrTo(true)}, func(m protocol.ProviderMutation) { - if !m.Result.Discovery { - t.Fatal("builtin discovery result = false, want true") - } + testassert.Require(t, m.Result.Discovery, "builtin discovery result = false, want true") }}, } generation := 1 @@ -2747,9 +2410,7 @@ func TestRuntimeBuiltinWritablePositiveTable(t *testing.T) { t.Run(row.name, func(t *testing.T) { generation++ mutation, err := r.updateProvider(ctx, "openrouter", row.patch) - if err != nil || mutation.ConfigurationRevision.Generation != strconv.Itoa(generation) { - t.Fatalf("builtin writable patch = (%v, %+v), want generation %d", err, mutation.ConfigurationRevision, generation) - } + testassert.Require(t, err == nil && mutation.ConfigurationRevision.Generation == strconv.Itoa(generation), "builtin writable patch = (%v, %+v), want generation %d", err, mutation.ConfigurationRevision, generation) row.check(mutation) drainMutationEvent(t, sub, strconv.Itoa(generation)) assertNoEvent(t, sub) diff --git a/runtime/configuration_mutation_composed_test.go b/runtime/configuration_mutation_composed_test.go index 91f2eb32..76b0eecc 100644 --- a/runtime/configuration_mutation_composed_test.go +++ b/runtime/configuration_mutation_composed_test.go @@ -12,6 +12,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" ) @@ -43,49 +44,31 @@ func TestComposedSettingsMutationThroughRealValidators(t *testing.T) { ctx := context.Background() e := newComposeEnv(t) r, err := runtime.OpenForTest(ctx, e.dataDir, e.configPath, builtin.Plugins()) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } + testassert.NoError(t, err) sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() view, err := runtime.GetConfigurationForTest(r) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } - if view.ConfigurationRevision.Generation != "1" { - t.Fatalf("initial revision = %+v, want generation 1", view.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, view.ConfigurationRevision.Generation == "1", "initial revision = %+v, want generation 1", view.ConfigurationRevision) // The valid write: the real validators accept the marshaled sections. mutation, err := runtime.UpdateSettingsForTest(r, composedMutationSettings()) - if err != nil { - t.Fatalf("updateSettings: %v", err) - } - if mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("mutation revision = %+v, want generation 2", mutation.ConfigurationRevision) - } - if mutation.Result.Sessions.ArchiveAfterDays != 14 || compactComposed(t, []byte(mutation.Result.Plugins["jobs"])) != compactComposed(t, []byte(composedMutationSettings().Plugins["jobs"])) { - t.Fatalf("mutation result = %+v, want the written shape", mutation.Result) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Sessions.ArchiveAfterDays == 14 && compactComposed(t, []byte(mutation.Result.Plugins["jobs"])) == compactComposed(t, []byte(composedMutationSettings().Plugins["jobs"])), "mutation revision = %+v, want generation 2; mutation result = %+v, want the written shape", mutation.ConfigurationRevision, mutation.Result) data, err := os.ReadFile(e.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + testassert.NoError(t, err) var root struct { Plugins struct { Tools json.RawMessage `json:"tools"` } `json:"plugins"` Sessions json.RawMessage `json:"sessions"` } - if err := json.Unmarshal(data, &root); err != nil { - t.Fatalf("decode owning file: %v", err) - } - if got := compactComposed(t, root.Plugins.Tools); !strings.Contains(got, `"max_output_bytes":4096`) { - t.Fatalf("plugins.tools member = %s, want the written section", got) + testassert.NoError(t, json.Unmarshal(data, &root)) + { + got := compactComposed(t, root.Plugins.Tools) + testassert.Require(t, strings.Contains(got, `"max_output_bytes":4096`), "plugins.tools member = %s, want the written section", got) } if event, ok := nextComposedEvent(t, sub); !ok || composedEventKind(t, event) != "configuration_changed" || composedEventGeneration(t, event) != "2" { t.Fatalf("event = %s (ok=%v), want the generation 2 configuration event", composedEventJSON(event), ok) @@ -95,41 +78,26 @@ func TestComposedSettingsMutationThroughRealValidators(t *testing.T) { // max_output_bytes — no file write, no generation, no warning change, no // event. before, err := os.ReadFile(e.configPath) - if err != nil { - t.Fatalf("read owning file again: %v", err) - } + testassert.NoError(t, err) warnings, err := runtime.GetWarningsForTest(r) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } + testassert.NoError(t, err) rejected := composedMutationSettings() rejected.Plugins["tools"] = `{"command_timeout":0,"max_output_bytes":0,"read_line_max_chars":0,"read_max_lines":0}` - if mutation, err := runtime.UpdateSettingsForTest(r, rejected); err == nil { - t.Fatalf("rejected mutation = %+v, want the real tools validator's failure", mutation) + { + mutation, err := runtime.UpdateSettingsForTest(r, rejected) + testassert.Require(t, err != nil, "rejected mutation = %+v, want the real tools validator's failure", mutation) } after, err := os.ReadFile(e.configPath) - if err != nil || string(before) != string(after) { - t.Fatalf("a rejected candidate changed the owning file (%v)", err) - } + testassert.Require(t, err == nil && string(before) == string(after), "a rejected candidate changed the owning file (%v)", err) fresh, err := runtime.GetConfigurationForTest(r) - if err != nil { - t.Fatalf("getConfiguration after rejection: %v", err) - } - if fresh.ConfigurationRevision.Generation != "2" { - t.Fatalf("generation after rejection = %+v, want 2", fresh.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, fresh.ConfigurationRevision.Generation == "2", "generation after rejection = %+v, want 2", fresh.ConfigurationRevision) freshWarnings, err := runtime.GetWarningsForTest(r) - if err != nil { - t.Fatalf("getWarnings after rejection: %v", err) - } - if freshWarnings.WarningsRevision.Revision != warnings.WarningsRevision.Revision { - t.Fatalf("warning revision advanced on a rejected candidate: %s → %s", warnings.WarningsRevision.Revision, freshWarnings.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, freshWarnings.WarningsRevision.Revision, warnings.WarningsRevision.Revision) select { case event, ok := <-sub.Events(): - if ok { - t.Fatalf("a rejected candidate published event %+v, want silence", event) - } + testassert.Require(t, !ok, "a rejected candidate published event %+v, want silence", event) default: } } @@ -142,54 +110,37 @@ func TestComposedAgentModelMutationThroughRealRoster(t *testing.T) { ctx := context.Background() e := newComposeEnv(t) r, err := runtime.OpenForTest(ctx, e.dataDir, e.configPath, builtin.Plugins()) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } + testassert.NoError(t, err) agentsPath := filepath.Join(filepath.Dir(e.configPath), "agents.json") before, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } + testassert.NoError(t, err) mutation, err := runtime.SetAgentTypeModelForTest(r, "primary", "prov/m") - if err != nil { - t.Fatalf("setAgentTypeModel(primary): %v", err) - } - if mutation.ConfigurationRevision.Generation != "2" || mutation.Result.Model != "prov/m" || mutation.Result.Name != "primary" { - t.Fatalf("mutation = %+v, want the primary override at generation 2", mutation) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Model == "prov/m" && mutation.Result.Name == "primary", "mutation = %+v, want the primary override at generation 2", mutation) var root map[string]json.RawMessage - if err := json.Unmarshal(before, &root); err != nil { - t.Fatalf("decode agents file: %v", err) - } - if _, ok := root["primary"]; ok { - t.Fatalf("agents file already had a primary entry: %s", before) + testassert.NoError(t, json.Unmarshal(before, &root)) + { + _, ok := root["primary"] + testassert.Require(t, !ok, "agents file already had a primary entry: %s", before) } afterData, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file after edit: %v", err) - } + testassert.NoError(t, err) var after map[string]json.RawMessage - if err := json.Unmarshal(afterData, &after); err != nil { - t.Fatalf("decode agents file after edit: %v", err) - } - if got := compactComposed(t, after["primary"]); got != `{"model":"prov/m"}` { - t.Fatalf("primary entry = %s, want the bare model-only overlay", got) - } + testassert.NoError(t, json.Unmarshal(afterData, &after)) + testassert.Equal(t, compactComposed(t, after["primary"]), `{"model":"prov/m"}`) - if _, err := runtime.SetAgentTypeModelForTest(r, "ghost", "prov/m"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("unknown type = %v, want the shared invalid failure", err) + { + _, err := runtime.SetAgentTypeModelForTest(r, "ghost", "prov/m") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "unknown type = %v, want the shared invalid failure", err) } final, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file after failure: %v", err) - } + testassert.NoError(t, err) var finalRoot map[string]json.RawMessage - if err := json.Unmarshal(final, &finalRoot); err != nil { - t.Fatalf("decode agents file after failure: %v", err) - } - if _, ok := finalRoot["ghost"]; ok { - t.Fatalf("a failed mutation created a bare entry: %s", final) + testassert.NoError(t, json.Unmarshal(final, &finalRoot)) + { + _, ok := finalRoot["ghost"] + testassert.Require(t, !ok, "a failed mutation created a bare entry: %s", final) } } @@ -197,8 +148,9 @@ func TestComposedAgentModelMutationThroughRealRoster(t *testing.T) { func compactComposed(t *testing.T, data []byte) string { t.Helper() var out bytes.Buffer - if err := json.Compact(&out, data); err != nil { - t.Fatalf("compact %s: %v", data, err) + { + err := json.Compact(&out, data) + testassert.Require(t, err == nil, "compact %s: %v", data, err) } return out.String() } diff --git a/runtime/configuration_mutation_test.go b/runtime/configuration_mutation_test.go index c6a0f99e..a0b0fcde 100644 --- a/runtime/configuration_mutation_test.go +++ b/runtime/configuration_mutation_test.go @@ -20,6 +20,7 @@ import ( "github.com/MMinasyan/lightcode/internal/atomicfs" "github.com/MMinasyan/lightcode/internal/config" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -31,7 +32,11 @@ import ( // mutationConfigDocument is the settings-mutation fixture document: an // unowned providers member with an exact big-integer lexeme, an unowned // permissions member, and an unowned unknown top-level member. -const mutationConfigDocument = `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"One","context_window":9007199254740993}}}},"permissions":{"rules":[{"permission":"file.write","target":"*","access":"allow"}]},"custom_flag":true}` +func mutationConfigDocument() string { + return `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "", + `{"m":{"name":"One","context_window":9007199254740993}}`) + + `},"permissions":{"rules":[{"permission":"file.write","target":"*","access":"allow"}]},"custom_flag":true}` +} // mutationSettings is one complete settings shape the tests write: the // sessions policy beside one opaque tools document string. @@ -52,8 +57,9 @@ func acceptValidator(json.RawMessage) error { return nil } func compactJSON(t *testing.T, data []byte) string { t.Helper() var buf bytes.Buffer - if err := json.Compact(&buf, data); err != nil { - t.Fatalf("compact %s: %v", data, err) + { + err := json.Compact(&buf, data) + testassert.Require(t, err == nil, "compact %s: %v", data, err) } return buf.String() } @@ -62,15 +68,14 @@ func compactJSON(t *testing.T, data []byte) string { // plugin sections carrying fields common Runtime does not know plus one // int64 lexeme beyond JS number precision, one empty compiled section, and // one unowned provider model carrying the same kind of lexeme. -const opaqueSettingsDocument = `{ - "providers": {"prov": {"transport": {"base_url": "https://prov.test/v1", "api_key_env": ""}, "discovery": false, "models": {"m": {"name": "One", "context_window": 9007199254740993}}}}, - "sessions": {"archive_after_days": 3}, - "plugins": { - "alpha": {"kept": 1, "exact": 9007199254740993}, - "tools": {"max_output_bytes": 42, "exact": 9007199254740993}, - "tasks": {} - } -}` +func opaqueSettingsDocument() string { + return `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "", + `{"m":{"name":"One","context_window":9007199254740993}}`) + + `},"sessions":{"archive_after_days":3},"plugins":{` + + `"alpha":{"kept":1,"exact":9007199254740993},` + + `"tools":{"max_output_bytes":42,"exact":9007199254740993},` + + `"tasks":{}}}` +} // TestRuntimeSettingsReadSaveRoundTripPreservesOwnedPluginDocuments proves // the representable-domain rule across a read-to-save round trip: every @@ -80,60 +85,52 @@ const opaqueSettingsDocument = `{ func TestRuntimeSettingsReadSaveRoundTripPreservesOwnedPluginDocuments(t *testing.T) { store := storage.NewMemory() var opens atomic.Int64 - r, e := openConfigurationRuntime(t, store, opaqueSettingsDocument, configurationAgentsDocument, + r, e := openConfigurationRuntime(t, store, opaqueSettingsDocument(), configurationAgentsDocument, append(settingsPlugins(), servicePlugin("alpha", &opens, acceptValidator))...) defer closeProjectionRuntime(r) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() view, err := r.getConfiguration(ctx) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) read := view.Settings.Plugins want := map[string]string{ "alpha": `{"kept":1,"exact":9007199254740993}`, "tools": `{"max_output_bytes":42,"exact":9007199254740993}`, "tasks": `{}`, } - if len(read) != len(want) { - t.Fatalf("settings plugins = %v, want exactly the owned sections %v", read, want) - } + testassert.Require(t, len(read) == len(want), "settings plugins = %v, want exactly the owned sections %v", read, want) for id, wantDoc := range want { got, ok := read[id] - if !ok { - t.Fatalf("plugins.%s is absent from the read; want the owned document %s", id, wantDoc) - } - if got := compactJSON(t, []byte(got)); got != wantDoc { - t.Fatalf("plugins.%s read document = %s, want the owned %s", id, got, wantDoc) + testassert.Require(t, ok, "plugins.%s is absent from the read; want the owned document %s", id, wantDoc) + { + got := compactJSON(t, []byte(got)) + testassert.Require(t, got == wantDoc, "plugins.%s read document = %s, want the owned %s", id, got, wantDoc) } } // Read-to-save: the whole plugins member round-trips its documents // and the unowned provider lexeme survives the owning write. - if _, err := r.updateSettings(ctx, view.Settings); err != nil { - t.Fatalf("updateSettings: %v", err) + { + _, err := r.updateSettings(ctx, view.Settings) + testassert.NoError(t, err) } nextConfigurationEvent(t, sub, "2") root := fileRoot(t, e.configPath) var plugins map[string]json.RawMessage - if err := json.Unmarshal(root["plugins"], &plugins); err != nil { - t.Fatalf("decode written plugins member: %v", err) - } - if len(plugins) != len(want) { - t.Fatalf("written plugins = %v, want exactly %v", plugins, want) - } + testassert.NoError(t, json.Unmarshal(root["plugins"], &plugins)) + testassert.Require(t, len(plugins) == len(want), "written plugins = %v, want exactly %v", plugins, want) for id, wantDoc := range want { - if got := compactJSON(t, plugins[id]); got != wantDoc { - t.Fatalf("written plugins.%s = %s, want the round-tripped %s", id, got, wantDoc) + { + got := compactJSON(t, plugins[id]) + testassert.Require(t, got == wantDoc, "written plugins.%s = %s, want the round-tripped %s", id, got, wantDoc) } } - if got := string(root["providers"]); !strings.Contains(got, "9007199254740993") { - t.Fatalf("written providers = %s, want the unowned int64 lexeme preserved", got) + { + got := string(root["providers"]) + testassert.Require(t, strings.Contains(got, "9007199254740993"), "written providers = %s, want the unowned int64 lexeme preserved", got) } } @@ -142,9 +139,7 @@ func assertNoEvent(t *testing.T, sub *Subscription) { t.Helper() select { case event, ok := <-sub.Events(): - if ok { - t.Fatalf("published event %+v, want silence", event) - } + testassert.Require(t, !ok, "published event %+v, want silence", event) default: } } @@ -153,12 +148,11 @@ func assertNoEvent(t *testing.T, sub *Subscription) { func fileRoot(t *testing.T, path string) map[string]json.RawMessage { t.Helper() data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read %s: %v", path, err) - } + testassert.Require(t, err == nil, "read %s: %v", path, err) var root map[string]json.RawMessage - if err := json.Unmarshal(data, &root); err != nil { - t.Fatalf("decode %s: %v", path, err) + { + err := json.Unmarshal(data, &root) + testassert.Require(t, err == nil, "decode %s: %v", path, err) } return root } @@ -261,45 +255,41 @@ func (p *writeSyncProbe) awaitWriteStarted(t *testing.T) { // written shape. func TestConfigurationMutateSettingsReplacesWholeShapeAndRetainsUnowned(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) wantSettings := mutationSettings() candidate, err := svc.mutate(context.Background(), editSettings(wantSettings)) - if err != nil { - t.Fatalf("mutate: %v", err) - } + testassert.NoError(t, err) if candidate.snapshot.generation != 2 || svc.current() != candidate.snapshot { t.Fatalf("mutate = generation %d (current published %v), want generation 2 published", candidate.snapshot.generation, svc.current() == candidate.snapshot) } - if got := projectSettings(candidate.snapshot.sessions, candidate.snapshot.plugins).Sessions; got != wantSettings.Sessions { - t.Fatalf("published sessions = %+v, want the written shape", got) - } + testassert.Equal(t, projectSettings(candidate.snapshot.sessions, candidate.snapshot.plugins).Sessions, wantSettings.Sessions) // The owning file: the written members are the marshaled shape, the // unowned members and their exact numeric lexeme survive. root := fileRoot(t, h.configPath) - if got, want := compactJSON(t, root["sessions"]), compactJSON(t, mustMarshal(t, wantSettings.Sessions)); got != want { - t.Fatalf("sessions member = %s, want %s", got, want) + { + got, want := compactJSON(t, root["sessions"]), compactJSON(t, mustMarshal(t, wantSettings.Sessions)) + testassert.Equal(t, got, want) } var plugins map[string]json.RawMessage - if err := json.Unmarshal(root["plugins"], &plugins); err != nil { - t.Fatalf("decode plugins member: %v", err) - } - if got, want := compactJSON(t, plugins["tools"]), compactJSON(t, []byte(wantSettings.Plugins["tools"])); got != want { - t.Fatalf("plugins.tools member = %s, want %s", got, want) + testassert.NoError(t, json.Unmarshal(root["plugins"], &plugins)) + { + got, want := compactJSON(t, plugins["tools"]), compactJSON(t, []byte(wantSettings.Plugins["tools"])) + testassert.Equal(t, got, want) } for _, omitted := range []string{"jobs", "tasks"} { - if _, ok := plugins[omitted]; ok { - t.Fatalf("plugins.%s member = %s, want it removed by the omitted optional section", omitted, plugins[omitted]) + { + _, ok := plugins[omitted] + testassert.Require(t, !ok, "plugins.%s member = %s, want it removed by the omitted optional section", omitted, plugins[omitted]) } } var providers struct { @@ -311,17 +301,15 @@ func TestConfigurationMutateSettingsReplacesWholeShapeAndRetainsUnowned(t *testi } decoder := json.NewDecoder(bytes.NewReader(root["providers"])) decoder.UseNumber() - if err := decoder.Decode(&providers); err != nil { - t.Fatalf("decode providers member: %v", err) + testassert.NoError(t, decoder.Decode(&providers)) + testassert.Equal(t, providers.Prov.Models["m"].ContextWindow.String(), "9007199254740993") + { + got, want := compactJSON(t, root["permissions"]), `{"rules":[{"permission":"file.write","target":"*","access":"allow"}]}` + testassert.Equal(t, got, want) } - if got := providers.Prov.Models["m"].ContextWindow.String(); got != "9007199254740993" { - t.Fatalf("unowned numeric lexeme = %s, want 9007199254740993 preserved", got) - } - if got, want := compactJSON(t, root["permissions"]), `{"rules":[{"permission":"file.write","target":"*","access":"allow"}]}`; got != want { - t.Fatalf("unowned permissions member = %s, want %s", got, want) - } - if got := root["custom_flag"]; string(got) != "true" { - t.Fatalf("unowned unknown member = %s, want true", got) + { + got := root["custom_flag"] + testassert.Require(t, string(got) == "true", "unowned unknown member = %s, want true", got) } // One event for the successful edit, at the written generation. @@ -337,71 +325,54 @@ func TestConfigurationMutateSettingsReplacesWholeShapeAndRetainsUnowned(t *testi // event. func TestConfigurationMutateIdenticalBytesStillWriteAndPublish(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(8) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) probe := installOwningSyncProbe(t, h.configPath) defer probe.restore() settings := mutationSettings() - if _, err := svc.mutate(context.Background(), editSettings(settings)); err != nil { - t.Fatalf("first mutate: %v", err) + { + _, err := svc.mutate(context.Background(), editSettings(settings)) + testassert.NoError(t, err) } nextConfigurationEvent(t, sub, "2") - if syncs := probe.count(); syncs != 1 { - t.Fatalf("owning writes so far = %d, want 1", syncs) - } + testassert.Equal(t, probe.count(), 1) before := fileRoot(t, h.configPath) // Identical settings bytes: still a real owning write and publication. candidate, err := svc.mutate(context.Background(), editSettings(settings)) - if err != nil { - t.Fatalf("identical mutate: %v", err) - } - if candidate.snapshot.generation != 3 || svc.current() != candidate.snapshot { - t.Fatalf("identical mutate = generation %d, want 3 published", candidate.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Require(t, candidate.snapshot.generation == 3 && svc.current() == candidate.snapshot, "identical mutate = generation %d, want 3 published", candidate.snapshot.generation) nextConfigurationEvent(t, sub, "3") - if syncs := probe.count(); syncs != 2 { - t.Fatalf("owning writes after the identical edit = %d, want 2", syncs) - } - if got := fileRoot(t, h.configPath); compactJSON(t, got["sessions"]) != compactJSON(t, before["sessions"]) { - t.Fatal("the identical edit changed the written sessions member") + testassert.Equal(t, probe.count(), 2) + { + got := fileRoot(t, h.configPath) + testassert.Require(t, compactJSON(t, got["sessions"]) == compactJSON(t, before["sessions"]), "the identical edit changed the written sessions member") } // An external editor writing the requested bytes without a Runtime // reload must not let the next valid edit skip the write or publication. data, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + testassert.NoError(t, err) writeServiceFile(t, h.configPath, string(data)) candidate, err = svc.mutate(context.Background(), editSettings(settings)) - if err != nil { - t.Fatalf("mutate over externally matching bytes: %v", err) - } - if candidate.snapshot.generation != 4 { - t.Fatalf("generation = %d, want 4 — external byte equality must not skip publication", candidate.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Equal(t, candidate.snapshot.generation, 4) nextConfigurationEvent(t, sub, "4") - if syncs := probe.count(); syncs != 3 { - t.Fatalf("owning writes after the external-editor edit = %d, want 3", syncs) - } + testassert.Equal(t, probe.count(), 3) } func mustMarshal(t *testing.T, value any) []byte { t.Helper() data, err := json.Marshal(value) - if err != nil { - t.Fatalf("marshal: %v", err) - } + testassert.NoError(t, err) return data } @@ -421,11 +392,9 @@ func TestConfigurationMutateSettingsOwnsOpaquePluginDocuments(t *testing.T) { var mu sync.Mutex // The fixture document carries the old alpha section the stale save must // remove wholesale. - writeServiceFile(t, h.configPath, strings.TrimSuffix(mutationConfigDocument, "}")+`,"plugins":{"alpha":{"kept":1},"tools":{"max_output_bytes":42}}}`) - if err := os.MkdirAll(h.dataDir, 0o700); err != nil { - t.Fatalf("MkdirAll: %v", err) - } - writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"worker":{"system_prompt":"simple"}}`) + writeServiceFile(t, h.configPath, strings.TrimSuffix(mutationConfigDocument(), "}")+`,"plugins":{"alpha":{"kept":1},"tools":{"max_output_bytes":42}}}`) + testassert.NoError(t, os.MkdirAll(h.dataDir, 0o700)) + writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), workerAgentsDocument) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, func(raw json.RawMessage) error { mu.Lock() @@ -436,19 +405,16 @@ func TestConfigurationMutateSettingsOwnsOpaquePluginDocuments(t *testing.T) { servicePlugin("tools", &h.opens, acceptValidator), ) svc.attachWarnings(newWarningStore()) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(8) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) agentsPath := filepath.Join(h.dataDir, "agents.json") agentsBefore, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } + testassert.NoError(t, err) // The omission domain: the target carries only the tools document; the // old alpha section is removed wholesale even though it is valid. @@ -459,44 +425,40 @@ func TestConfigurationMutateSettingsOwnsOpaquePluginDocuments(t *testing.T) { }, } candidate, err := svc.mutate(context.Background(), editSettings(stale)) - if err != nil { - t.Fatalf("mutate: %v", err) - } + testassert.NoError(t, err) published := projectSettings(candidate.snapshot.sessions, candidate.snapshot.plugins) - if len(published.Plugins) != 1 || compactJSON(t, []byte(published.Plugins["tools"])) != compactJSON(t, []byte(stale.Plugins["tools"])) { - t.Fatalf("published plugins = %v, want only the written tools document", published.Plugins) - } + testassert.Require(t, len(published.Plugins) == 1 && compactJSON(t, []byte(published.Plugins["tools"])) == compactJSON(t, []byte(stale.Plugins["tools"])), "published plugins = %v, want only the written tools document", published.Plugins) root := fileRoot(t, h.configPath) var plugins map[string]json.RawMessage - if err := json.Unmarshal(root["plugins"], &plugins); err != nil { - t.Fatalf("decode plugins member: %v", err) - } - if got, want := compactJSON(t, plugins["tools"]), compactJSON(t, []byte(stale.Plugins["tools"])); got != want { - t.Fatalf("plugins.tools member = %s, want %s", got, want) + testassert.NoError(t, json.Unmarshal(root["plugins"], &plugins)) + { + got, want := compactJSON(t, plugins["tools"]), compactJSON(t, []byte(stale.Plugins["tools"])) + testassert.Equal(t, got, want) } - if _, ok := plugins["alpha"]; ok { - t.Fatalf("plugins.alpha member = %s, want it removed by the omitted section", plugins["alpha"]) + { + _, ok := plugins["alpha"] + testassert.Require(t, !ok, "plugins.alpha member = %s, want it removed by the omitted section", plugins["alpha"]) } - if got, want := compactJSON(t, root["sessions"]), compactJSON(t, mustMarshal(t, stale.Sessions)); got != want { - t.Fatalf("sessions member = %s, want %s", got, want) + { + got, want := compactJSON(t, root["sessions"]), compactJSON(t, mustMarshal(t, stale.Sessions)) + testassert.Equal(t, got, want) } // Unowned top-level members survive; the agents file is untouched. - if got, want := compactJSON(t, root["permissions"]), `{"rules":[{"permission":"file.write","target":"*","access":"allow"}]}`; got != want { - t.Fatalf("unowned permissions member = %s, want %s", got, want) - } - if got := root["custom_flag"]; string(got) != "true" { - t.Fatalf("unowned unknown member = %s, want true", got) + { + got, want := compactJSON(t, root["permissions"]), `{"rules":[{"permission":"file.write","target":"*","access":"allow"}]}` + testassert.Equal(t, got, want) } - if string(root["providers"]) == "" { - t.Fatal("unowned providers member missing") + { + got := root["custom_flag"] + testassert.Require(t, string(got) == "true", "unowned unknown member = %s, want true", got) } - if _, ok := root["alpha"]; ok { - t.Fatal("a removed plugin section leaked to the root") + testassert.Require(t, string(root["providers"]) != "", "unowned providers member missing") + { + _, ok := root["alpha"] + testassert.Require(t, !ok, "a removed plugin section leaked to the root") } agentsAfter, err := os.ReadFile(agentsPath) - if err != nil || string(agentsBefore) != string(agentsAfter) { - t.Fatalf("the settings edit changed the agents file (%v)", err) - } + testassert.Require(t, err == nil && string(agentsBefore) == string(agentsAfter), "the settings edit changed the agents file (%v)", err) // The removed section is gone for its plugin too: the edit build's alpha // validator received nil, so defaults govern. mu.Lock() @@ -506,9 +468,7 @@ func TestConfigurationMutateSettingsOwnsOpaquePluginDocuments(t *testing.T) { alphaEditRaw = received[1] } mu.Unlock() - if alphaCalls != 2 || alphaEditRaw != nil { - t.Fatalf("alpha validator calls = %d (edit build received %s), want nil defaults on the omitted section", alphaCalls, alphaEditRaw) - } + testassert.Require(t, alphaCalls == 2 && alphaEditRaw == nil, "alpha validator calls = %d (edit build received %s), want nil defaults on the omitted section", alphaCalls, alphaEditRaw) nextConfigurationEvent(t, sub, "2") // The refusal domain: an unknown ID, invalid inner JSON, and a valid but @@ -532,16 +492,13 @@ func TestConfigurationMutateSettingsOwnsOpaquePluginDocuments(t *testing.T) { // An empty complete map is valid: it removes every owned section. candidate, err = svc.mutate(context.Background(), editSettings(protocol.Settings{Sessions: stale.Sessions, Plugins: protocol.PluginsSettings{}})) - if err != nil { - t.Fatalf("empty plugins mutate: %v", err) - } - if got := projectSettings(candidate.snapshot.sessions, candidate.snapshot.plugins).Plugins; len(got) != 0 { - t.Fatalf("published plugins after the empty map = %v, want none", got) + testassert.NoError(t, err) + { + got := projectSettings(candidate.snapshot.sessions, candidate.snapshot.plugins).Plugins + testassert.Require(t, len(got) == 0, "published plugins after the empty map = %v, want none", got) } root = fileRoot(t, h.configPath) - if got := compactJSON(t, root["plugins"]); got != `{}` { - t.Fatalf("plugins member = %s, want the empty complete map", got) - } + testassert.Equal(t, compactJSON(t, root["plugins"]), `{}`) nextConfigurationEvent(t, sub, "3") } @@ -552,15 +509,14 @@ func TestConfigurationMutateSettingsOwnsOpaquePluginDocuments(t *testing.T) { // member. func TestConfigurationMutateEverySuccessfulEditWritesAndPublishes(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) probe := installOwningSyncProbe(t, h.configPath) defer probe.restore() @@ -569,16 +525,10 @@ func TestConfigurationMutateEverySuccessfulEditWritesAndPublishes(t *testing.T) return editMainConfig, nil }} candidate, err := svc.mutate(context.Background(), noChange) - if err != nil { - t.Fatalf("no-change mutate: %v", err) - } - if candidate.snapshot.generation != 2 || svc.current() != candidate.snapshot { - t.Fatalf("no-change mutate = generation %d, want 2 published", candidate.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Require(t, candidate.snapshot.generation == 2 && svc.current() == candidate.snapshot, "no-change mutate = generation %d, want 2 published", candidate.snapshot.generation) nextConfigurationEvent(t, sub, "2") - if syncs := probe.count(); syncs != 1 { - t.Fatalf("owning writes = %d, want the shared successful edit to write", syncs) - } + testassert.Equal(t, probe.count(), 1) } // --- owning file identity, mode, and no side effects --- @@ -595,43 +545,33 @@ func TestConfigurationMutateWritesOwningFileOnly0600(t *testing.T) { agentsPath := filepath.Join(h.dataDir, "agents.json") candidate, err := svc.mutate(context.Background(), svc.editAgentModel("primary", "prov/m")) - if err != nil { - t.Fatalf("agent mutate: %v", err) - } - if candidate.snapshot.generation != 1 { - t.Fatalf("mutation generation = %d, want 1 from the empty prior publication", candidate.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Equal(t, candidate.snapshot.generation, 1) info, err := os.Stat(agentsPath) - if err != nil { - t.Fatalf("owning agents file missing: %v", err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o600 { t.Fatalf("owning file mode = %o, want 600", info.Mode().Perm()) } - if _, err := os.Stat(h.configPath); !os.IsNotExist(err) { - t.Fatalf("the companion main configuration was created as a side effect: %v", err) + { + _, err := os.Stat(h.configPath) + testassert.Require(t, os.IsNotExist(err), "the companion main configuration was created as a side effect: %v", err) } // The settings PUT owns the main configuration only: agents.json keeps // its bytes and the main file is born at 0600 from the skeleton root. agentsBefore, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } - if _, err := svc.mutate(context.Background(), editSettings(mutationSettings())); err != nil { - t.Fatalf("settings mutate: %v", err) + testassert.NoError(t, err) + { + _, err := svc.mutate(context.Background(), editSettings(mutationSettings())) + testassert.NoError(t, err) } info, err = os.Stat(h.configPath) - if err != nil { - t.Fatalf("owning config file missing: %v", err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o600 { t.Fatalf("owning file mode = %o, want 600", info.Mode().Perm()) } agentsAfter, err := os.ReadFile(agentsPath) - if err != nil || string(agentsBefore) != string(agentsAfter) { - t.Fatalf("the companion agents file changed: %v", err) - } + testassert.Require(t, err == nil && string(agentsBefore) == string(agentsAfter), "the companion agents file changed: %v", err) } // --- agent model edit --- @@ -645,50 +585,47 @@ func TestConfigurationMutateWritesOwningFileOnly0600(t *testing.T) { // that writes the owning file and publishes the next generation. func TestConfigurationMutateAgentModelEditsLatestRoot(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) - writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"worker":{"system_prompt":"simple"}}`) + writeServiceFile(t, h.configPath, mutationConfigDocument()) + writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), workerAgentsDocument) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(16) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) agentsPath := filepath.Join(h.dataDir, "agents.json") // Set: the override lands on the existing entry and resolves. candidate, err := svc.mutate(context.Background(), svc.editAgentModel("worker", "prov/m")) - if err != nil { - t.Fatalf("set model: %v", err) - } - if found, model := hasDefinition(candidate.snapshot, "worker"); !found || model != "prov/m" { - t.Fatalf("worker definition = (%v, %q), want the new override", found, model) + testassert.NoError(t, err) + { + found, model := hasDefinition(candidate.snapshot, "worker") + testassert.Require(t, found && model == "prov/m", "worker definition = (%v, %q), want the new override", found, model) } nextConfigurationEvent(t, sub, "2") - if root := fileRoot(t, agentsPath); compactJSON(t, root["worker"]) != `{"model":"prov/m","system_prompt":"simple"}` { - t.Fatalf("worker entry = %s, want the model added beside the kept member", root["worker"]) + { + root := fileRoot(t, agentsPath) + testassert.Require(t, compactJSON(t, root["worker"]) == `{"model":"prov/m","system_prompt":"simple"}`, "worker entry = %s, want the model added beside the kept member", root["worker"]) } // Identical value: still a real edit and publication. candidate, err = svc.mutate(context.Background(), svc.editAgentModel("worker", "prov/m")) - if err != nil || candidate.snapshot.generation != 3 { - t.Fatalf("identical model edit = (%v, generation %d), want generation 3", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 3, "identical model edit = (%v, generation %d), want generation 3", err, candidate.snapshot.generation) nextConfigurationEvent(t, sub, "3") // Clear: the override is removed, the entry's other member kept. candidate, err = svc.mutate(context.Background(), svc.editAgentModel("worker", "")) - if err != nil { - t.Fatalf("clear model: %v", err) - } - if found, model := hasDefinition(candidate.snapshot, "worker"); !found || model != "" { - t.Fatalf("worker definition = (%v, %q), want the cleared override", found, model) + testassert.NoError(t, err) + { + found, model := hasDefinition(candidate.snapshot, "worker") + testassert.Require(t, found && model == "", "worker definition = (%v, %q), want the cleared override", found, model) } nextConfigurationEvent(t, sub, "4") - if root := fileRoot(t, agentsPath); compactJSON(t, root["worker"]) != `{"system_prompt":"simple"}` { - t.Fatalf("worker entry = %s, want the model member removed", root["worker"]) + { + root := fileRoot(t, agentsPath) + testassert.Require(t, compactJSON(t, root["worker"]) == `{"system_prompt":"simple"}`, "worker entry = %s, want the model member removed", root["worker"]) } // Clearing again with no user override present is still a successful @@ -699,30 +636,27 @@ func TestConfigurationMutateAgentModelEditsLatestRoot(t *testing.T) { // proven by the sync-seam probe. probe := installOwningSyncProbe(t, agentsPath) candidate, err = svc.mutate(context.Background(), svc.editAgentModel("worker", "")) - if err != nil || candidate.snapshot.generation != 5 { - t.Fatalf("absent-override clear = (%v, generation %d), want a successful edit at generation 5", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 5, "absent-override clear = (%v, generation %d), want a successful edit at generation 5", err, candidate.snapshot.generation) nextConfigurationEvent(t, sub, "5") - if syncs := probe.count(); syncs != 1 { - t.Fatalf("owning writes for the absent-override clear = %d, want a real write", syncs) - } + testassert.Equal(t, probe.count(), 1) probe.restore() - if found, model := hasDefinition(candidate.snapshot, "worker"); !found || model != "" { - t.Fatalf("worker definition = (%v, %q), want the resolved inherited state", found, model) + { + found, model := hasDefinition(candidate.snapshot, "worker") + testassert.Require(t, found && model == "", "worker definition = (%v, %q), want the resolved inherited state", found, model) } // A builtin without a user entry is addressable: the edit creates only // its model member. candidate, err = svc.mutate(context.Background(), svc.editAgentModel("primary", "prov/m")) - if err != nil { - t.Fatalf("builtin model edit: %v", err) - } - if found, model := hasDefinition(candidate.snapshot, "primary"); !found || model != "prov/m" { - t.Fatalf("primary definition = (%v, %q), want the new override", found, model) + testassert.NoError(t, err) + { + found, model := hasDefinition(candidate.snapshot, "primary") + testassert.Require(t, found && model == "prov/m", "primary definition = (%v, %q), want the new override", found, model) } nextConfigurationEvent(t, sub, "6") - if root := fileRoot(t, agentsPath); compactJSON(t, root["primary"]) != `{"model":"prov/m"}` { - t.Fatalf("primary entry = %s, want the bare model-only overlay", root["primary"]) + { + root := fileRoot(t, agentsPath) + testassert.Require(t, compactJSON(t, root["primary"]) == `{"model":"prov/m"}`, "primary entry = %s, want the bare model-only overlay", root["primary"]) } } @@ -734,16 +668,15 @@ func TestConfigurationMutateAgentModelEditsLatestRoot(t *testing.T) { // again instead of behaving as a reset. func TestConfigurationMutateAgentModelClearAlwaysWritesAndPublishes(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), "{}\n") svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(8) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) agentsPath := filepath.Join(h.dataDir, "agents.json") probe := installOwningSyncProbe(t, agentsPath) @@ -751,19 +684,14 @@ func TestConfigurationMutateAgentModelClearAlwaysWritesAndPublishes(t *testing.T for _, generation := range []string{"2", "3"} { candidate, err := svc.mutate(context.Background(), svc.editAgentModel("primary", "")) - if err != nil { - t.Fatalf("empty-ref clear: %v", err) - } - if got := fmt.Sprint(candidate.snapshot.generation); got != generation { - t.Fatalf("empty-ref clear generation = %s, want %s — the repeat must advance again", got, generation) - } + testassert.NoError(t, err) + testassert.Equal(t, fmt.Sprint(candidate.snapshot.generation), generation) nextConfigurationEvent(t, sub, generation) } - if syncs := probe.count(); syncs != 2 { - t.Fatalf("owning writes = %d, want a real write per request", syncs) - } - if root := fileRoot(t, agentsPath); compactJSON(t, root["primary"]) != `{}` { - t.Fatalf("primary entry = %s, want the retained WriteModel bare-entry semantics", root["primary"]) + testassert.Equal(t, probe.count(), 2) + { + root := fileRoot(t, agentsPath) + testassert.Require(t, compactJSON(t, root["primary"]) == `{}`, "primary entry = %s, want the retained WriteModel bare-entry semantics", root["primary"]) } } @@ -774,63 +702,49 @@ func TestConfigurationMutateAgentModelClearAlwaysWritesAndPublishes(t *testing.T // notified. func TestConfigurationMutateUnknownAgentTypeFailsWithoutBareEntry(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"broken":{"tools":["ghost_tool"]},"badmodel":{"model":"noslash"}}`) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + svc.attachWarnings(newWarningStore()) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) agentsPath := filepath.Join(h.dataDir, "agents.json") - before, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } + before, published, warnRev := metadataBaseline(t, svc, h.configPath) + agentsBefore := mustRead(t, agentsPath) for _, agentType := range []string{"ghost", "broken", "badmodel"} { - if _, err := svc.mutate(context.Background(), svc.editAgentModel(agentType, "prov/m")); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("agent type %q mutate = %v, want harness.ErrInvalid", agentType, err) - } - } - after, err := os.ReadFile(agentsPath) - if err != nil || string(before) != string(after) { - t.Fatalf("a failed agent mutate changed the owning file (%v)", err) + _, err := svc.mutate(context.Background(), svc.editAgentModel(agentType, "prov/m")) + assertMetadataRefused(t, svc, sub, h.configPath, before, published, warnRev, err, harness.ErrInvalid) } - if svc.current().generation != 1 { - t.Fatalf("generation = %d, want a failed edit to consume nothing", svc.current().generation) - } - assertNoEvent(t, sub) + testassert.Require(t, string(mustRead(t, agentsPath)) == string(agentsBefore), "a failed agent mutate changed the owning agents file") } // TestConfigurationMutateMalformedModelRefFailsWithoutWrite proves a // nonempty ref is validated by the retained model.Parse before any edit. func TestConfigurationMutateMalformedModelRefFailsWithoutWrite(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"worker":{"model":"prov/m"}}`) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } agentsPath := filepath.Join(h.dataDir, "agents.json") before, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } - if _, err := svc.mutate(context.Background(), svc.editAgentModel("worker", "noslash")); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("malformed ref mutate = %v, want harness.ErrInvalid", err) + testassert.NoError(t, err) + { + _, err := svc.mutate(context.Background(), svc.editAgentModel("worker", "noslash")) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "malformed ref mutate = %v, want harness.ErrInvalid", err) } after, err := os.ReadFile(agentsPath) - if err != nil || string(before) != string(after) { - t.Fatalf("a malformed ref changed the owning file (%v)", err) - } - if svc.current().generation != 1 { - t.Fatalf("generation = %d, want no consumed generation", svc.current().generation) - } + testassert.Require(t, err == nil && string(before) == string(after), "a malformed ref changed the owning file (%v)", err) + testassert.Equal(t, svc.current().generation, 1) } // --- invalid candidate: nothing changes --- @@ -861,7 +775,7 @@ func TestConfigurationMutateInvalidCandidateLeavesEverythingUnchanged(t *testing { name: "validator rejects the written section", validate: rejectWritten, - external: mutationConfigDocument, + external: mutationConfigDocument(), wantSource: "tools settings invalid", }, { @@ -873,55 +787,35 @@ func TestConfigurationMutateInvalidCandidateLeavesEverythingUnchanged(t *testing { name: "externally trailing document", validate: acceptValidator, - external: mutationConfigDocument + `{}`, + external: mutationConfigDocument() + `{}`, wantSource: "after top-level value", }, } for _, row := range rows { t.Run(row.name, func(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, row.validate), servicePlugin("alpha", &h.opens, acceptValidator)) - first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) - warnings := newWarningStore() - svc.attachWarnings(warnings) - revision, _ := warnings.snapshot() + svc.attachWarnings(newWarningStore()) // The external editor's whole-file last-writer-wins state is what - // the edit consumes as the latest raw layer. + // the edit consumes as the latest raw layer: the refusal baseline + // is the state that write left. writeServiceFile(t, h.configPath, row.external) - data, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + before, published, warnRev := metadataBaseline(t, svc, h.configPath) candidate, err := svc.mutate(context.Background(), editSettings(mutationSettings())) - if candidate.snapshot != nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("mutate = (%v, %v), want a rejected candidate", candidate, err) - } - if row.wantSource != "" && !strings.Contains(err.Error(), row.wantSource) { - t.Fatalf("error %v does not preserve %q", err, row.wantSource) - } - after, rerr := os.ReadFile(h.configPath) - if rerr != nil || string(data) != string(after) { - t.Fatalf("a failed candidate changed the owning file (%v)", rerr) - } - if gotRevision, _ := warnings.snapshot(); gotRevision != revision { - t.Fatalf("warning revision advanced on a failed candidate: %d → %d", revision, gotRevision) - } - assertNoEvent(t, sub) - if svc.current() != first.snapshot { - t.Fatal("a failed candidate replaced the prior publication") - } + testassert.Require(t, candidate.snapshot == nil, "mutate = %+v, want a rejected candidate", candidate) + testassert.Require(t, row.wantSource == "" || strings.Contains(err.Error(), row.wantSource), "error %v does not preserve %q", err, row.wantSource) + assertMetadataRefused(t, svc, sub, h.configPath, before, published, warnRev, err, ErrConfiguration) }) } } @@ -961,66 +855,37 @@ func TestConfigurationMutateAgentEditInvalidCandidateLeavesEverythingUnchanged(t for _, row := range rows { t.Run(row.name, func(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) - writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"worker":{"system_prompt":"simple"}}`) + writeServiceFile(t, h.configPath, mutationConfigDocument()) + writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), workerAgentsDocument) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator), servicePlugin("alpha", &h.opens, acceptValidator)) - first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) - warnings := newWarningStore() - svc.attachWarnings(warnings) - revision, _ := warnings.snapshot() + svc.attachWarnings(newWarningStore()) // The external bad state is the baseline: the oracles compare // against the latest bytes on disk, not the last valid file. writeServiceFile(t, h.configPath, row.config) - configBefore, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read companion file: %v", err) - } + before, published, warnRev := metadataBaseline(t, svc, h.configPath) agentsPath := filepath.Join(h.dataDir, "agents.json") - agentsBefore, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + agentsBefore := mustRead(t, agentsPath) candidate, err := svc.mutate(context.Background(), svc.editAgentModel("worker", "prov/m")) - if candidate.snapshot != nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("agent edit = (%v, %v), want a rejected candidate", candidate, err) - } - if !strings.Contains(err.Error(), row.wantSource) { - t.Fatalf("error %v does not preserve %q", err, row.wantSource) - } - configAfter, err := os.ReadFile(h.configPath) - if err != nil || string(configBefore) != string(configAfter) { - t.Fatalf("a failed candidate changed the companion file (%v)", err) - } - agentsAfter, err := os.ReadFile(agentsPath) - if err != nil || string(agentsBefore) != string(agentsAfter) { - t.Fatalf("a failed candidate changed the owning file (%v)", err) - } - if gotRevision, _ := warnings.snapshot(); gotRevision != revision { - t.Fatalf("warning revision advanced on a failed candidate: %d → %d", revision, gotRevision) - } - assertNoEvent(t, sub) - if svc.current() != first.snapshot || first.snapshot.generation != 1 { - t.Fatal("a failed candidate replaced the prior publication") - } + testassert.Require(t, candidate.snapshot == nil, "agent edit = %+v, want a rejected candidate", candidate) + testassert.Require(t, strings.Contains(err.Error(), row.wantSource), "error %v does not preserve %q", err, row.wantSource) + assertMetadataRefused(t, svc, sub, h.configPath, before, published, warnRev, err, ErrConfiguration) + testassert.Require(t, string(mustRead(t, agentsPath)) == string(agentsBefore), "a failed candidate changed the owning agents file") // The inverse probe: with the bad member gone the same edit // succeeds, proving the file/generation/event oracles flip. - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) candidate, err = svc.mutate(context.Background(), svc.editAgentModel("worker", "prov/m")) - if err != nil || candidate.snapshot.generation != 2 || svc.current() != candidate.snapshot { - t.Fatalf("inverse edit = (%v, generation %d), want a successful publication at 2", err, candidate.snapshot.generation) - } + testassert.Require(t, err == nil && candidate.snapshot.generation == 2 && svc.current() == candidate.snapshot, "inverse edit = (%v, generation %d), want a successful publication at 2", err, candidate.snapshot.generation) nextConfigurationEvent(t, sub, "2") }) } @@ -1031,24 +896,22 @@ func TestConfigurationMutateAgentEditInvalidCandidateLeavesEverythingUnchanged(t // to the built-in fallback without failing the mutation's publication. func TestConfigurationMutateMalformedPermissionsFallbackPublishes(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } dirID, err := config.WorkspacePermissionDirID("/ws") - if err != nil { - t.Fatalf("dir id: %v", err) - } + testassert.NoError(t, err) permissionPath := filepath.Join(h.home, ".lightcode", "projects", dirID, "permissions.json") writeServiceFile(t, permissionPath, `NOT JSON`) candidate, err := svc.mutate(context.Background(), editSettings(mutationSettings())) - if err != nil { - t.Fatalf("mutate with a malformed permission file: %v", err) - } + testassert.NoError(t, err) var builtin harness.PermissionPolicy - if got := candidate.snapshot.permissionPolicy("/ws"); fmt.Sprint(got) != fmt.Sprint(builtin) { - t.Fatalf("malformed permission policy = %#v, want the built-in fallback %#v", got, builtin) + { + got := candidate.snapshot.permissionPolicy("/ws") + testassert.Require(t, fmt.Sprint(got) == fmt.Sprint(builtin), "malformed permission policy = %#v, want the built-in fallback %#v", got, builtin) } } @@ -1061,67 +924,45 @@ func TestConfigurationMutateMalformedPermissionsFallbackPublishes(t *testing.T) func TestConfigurationMutateCancelsBeforeWrite(t *testing.T) { t.Run("caller canceled after validation", func(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) ctx, cancel := context.WithCancel(context.Background()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, func(json.RawMessage) error { cancel() return nil })) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) data, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + testassert.NoError(t, err) candidate, err := svc.mutate(ctx, editSettings(mutationSettings())) - if candidate.snapshot != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("mutate = (%v, %v), want the caller context error before the write", candidate, err) - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, context.Canceled), "mutate = (%v, %v), want the caller context error before the write", candidate, err) after, rerr := os.ReadFile(h.configPath) - if rerr != nil || string(data) != string(after) { - t.Fatalf("the canceled mutation changed the owning file (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(data) == string(after), "the canceled mutation changed the owning file (%v)", rerr) assertNoEvent(t, sub) }) t.Run("owner canceled before waiting", func(t *testing.T) { owner, cancelOwner := context.WithCancel(context.Background()) h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(owner, servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) - warnings := newWarningStore() - svc.attachWarnings(warnings) - revision, _ := warnings.snapshot() - data, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + svc.attachWarnings(newWarningStore()) + before, first, warnRev := metadataBaseline(t, svc, h.configPath) cancelOwner() candidate, err := svc.mutate(context.Background(), editSettings(mutationSettings())) - if candidate.snapshot != nil || !errors.Is(err, ErrClosed) { - t.Fatalf("mutate = (%v, %v), want ErrClosed before any write", candidate, err) - } - after, rerr := os.ReadFile(h.configPath) - if rerr != nil || string(data) != string(after) { - t.Fatalf("the owner-canceled mutation changed the owning file (%v)", rerr) - } - if gotRevision, _ := warnings.snapshot(); gotRevision != revision { - t.Fatalf("warning revision advanced on the owner-canceled mutation: %d → %d", revision, gotRevision) - } - assertNoEvent(t, sub) + testassert.Require(t, candidate.snapshot == nil, "mutate = %+v, want no candidate", candidate) + assertMetadataRefused(t, svc, sub, h.configPath, before, first, warnRev, err, ErrClosed) }) } @@ -1132,15 +973,14 @@ func TestConfigurationMutateCancelsBeforeWrite(t *testing.T) { // half-published generation. func TestConfigurationMutateCompletesPublicationAfterWriteStarts(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) probe := installOwningSyncProbe(t, h.configPath) probe.mu.Lock() @@ -1169,17 +1009,10 @@ func TestConfigurationMutateCompletesPublicationAfterWriteStarts(t *testing.T) { releasePark() got := <-done - if got.err != nil { - t.Fatalf("mutate after write start = %v, want the admitted call to complete publication", got.err) - } - if got.cfg.snapshot.generation != 2 || svc.current() != got.cfg.snapshot { - t.Fatalf("post-cancel publication = generation %d, want 2 published", got.cfg.snapshot.generation) - } + testassert.Require(t, got.err == nil && got.cfg.snapshot.generation == 2 && svc.current() == got.cfg.snapshot, "mutate after write start = %v, want the admitted call to complete publication; post-cancel publication = generation %d, want 2 published", got.err, got.cfg.snapshot.generation) nextConfigurationEvent(t, sub, "2") root := fileRoot(t, h.configPath) - if !strings.Contains(compactJSON(t, root["sessions"]), `"archive_after_days":5`) { - t.Fatalf("owning file = %s, want the written sessions member", root["sessions"]) - } + testassert.Require(t, strings.Contains(compactJSON(t, root["sessions"]), `"archive_after_days":5`), "owning file = %s, want the written sessions member", root["sessions"]) } // TestConfigurationMutateWriteFailureLeavesEverythingUnchanged proves a @@ -1187,15 +1020,14 @@ func TestConfigurationMutateCompletesPublicationAfterWriteStarts(t *testing.T) { // file is unchanged, and no generation, warning refresh, or event follows. func TestConfigurationMutateWriteFailureLeavesEverythingUnchanged(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) svc := h.service(context.Background(), servicePlugin("tools", &h.opens, acceptValidator)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) injected := errors.New("injected sync failure") probe := installOwningSyncProbe(t, h.configPath) @@ -1204,20 +1036,12 @@ func TestConfigurationMutateWriteFailureLeavesEverythingUnchanged(t *testing.T) probe.mu.Unlock() defer probe.restore() data, err := os.ReadFile(h.configPath) - if err != nil { - t.Fatalf("read owning file: %v", err) - } + testassert.NoError(t, err) candidate, err := svc.mutate(context.Background(), editSettings(mutationSettings())) - if candidate.snapshot != nil || !errors.Is(err, ErrConfiguration) || !errors.Is(err, injected) { - t.Fatalf("mutate = (%v, %v), want the injected write failure preserved", candidate, err) - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, ErrConfiguration) && errors.Is(err, injected), "mutate = (%v, %v), want the injected write failure preserved", candidate, err) after, rerr := os.ReadFile(h.configPath) - if rerr != nil || string(data) != string(after) { - t.Fatalf("a failed write changed the owning file (%v)", rerr) - } - if svc.current().generation != 1 { - t.Fatalf("generation = %d, want no consumed generation", svc.current().generation) - } + testassert.Require(t, rerr == nil && string(data) == string(after), "a failed write changed the owning file (%v)", rerr) + testassert.Equal(t, svc.current().generation, 1) assertNoEvent(t, sub) } @@ -1228,7 +1052,7 @@ func TestConfigurationMutateWriteFailureLeavesEverythingUnchanged(t *testing.T) // with the next generation, and the events keep publication order. func TestConfigurationMutateSerializesWithReload(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) var builds atomic.Int64 gate := make(chan struct{}) entered := make(chan struct{}, 1) @@ -1239,13 +1063,12 @@ func TestConfigurationMutateSerializesWithReload(t *testing.T) { } return nil })) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(8) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) type result struct { @@ -1278,11 +1101,13 @@ func TestConfigurationMutateSerializesWithReload(t *testing.T) { case <-time.After(100 * time.Millisecond): } close(gate) - if got := <-mutation; got.err != nil || got.revision != "2" { - t.Fatalf("mutation = %+v, want generation 2", got) + { + got := <-mutation + testassert.Require(t, got.err == nil && got.revision == "2", "mutation = %+v, want generation 2", got) } - if got := <-reload; got.err != nil || got.revision != "3" { - t.Fatalf("reload = %+v, want generation 3 after the serialized mutation", got) + { + got := <-reload + testassert.Require(t, got.err == nil && got.revision == "3", "reload = %+v, want generation 3 after the serialized mutation", got) } nextConfigurationEvent(t, sub, "2") nextConfigurationEvent(t, sub, "3") @@ -1301,46 +1126,28 @@ func TestRuntimeSettingsMutationLastWriterWins(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() view, err := r.getConfiguration(ctx) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } - if view.ConfigurationRevision.Generation != "1" { - t.Fatalf("initial revision = %+v, want generation 1", view.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, view.ConfigurationRevision.Generation == "1", "initial revision = %+v, want generation 1", view.ConfigurationRevision) stale := view.Settings // value copy: client 2's older whole-section document first := view.Settings first.Sessions.ArchiveAfterDays = 9 mutation, err := r.updateSettings(ctx, first) - if err != nil { - t.Fatalf("client 1 updateSettings: %v", err) - } - if mutation.ConfigurationRevision.Generation != "2" || mutation.Result.Sessions.ArchiveAfterDays != 9 { - t.Fatalf("client 1 mutation = %+v, want generation 2 with the written value", mutation) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Sessions.ArchiveAfterDays == 9, "client 1 mutation = %+v, want generation 2 with the written value", mutation) // Client 2 submits its older whole-section document: // last-writer-wins, not a conflict. mutation, err = r.updateSettings(ctx, stale) - if err != nil { - t.Fatalf("client 2 stale updateSettings: %v", err) - } - if mutation.ConfigurationRevision.Generation != "3" || mutation.Result.Sessions.ArchiveAfterDays != 3 { - t.Fatalf("client 2 mutation = %+v, want generation 3 with the stale value", mutation) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.ConfigurationRevision.Generation == "3" && mutation.Result.Sessions.ArchiveAfterDays == 3, "client 2 mutation = %+v, want generation 3 with the stale value", mutation) fresh, err := r.getConfiguration(ctx) - if err != nil { - t.Fatalf("fresh read: %v", err) - } - if fresh.ConfigurationRevision.Generation != "3" || fresh.Settings.Sessions.ArchiveAfterDays != 3 { - t.Fatalf("fresh settings = rev %+v sessions %+v, want the stale writer's value at generation 3", fresh.ConfigurationRevision, fresh.Settings.Sessions) - } + testassert.NoError(t, err) + testassert.Require(t, fresh.ConfigurationRevision.Generation == "3" && fresh.Settings.Sessions.ArchiveAfterDays == 3, "fresh settings = rev %+v sessions %+v, want the stale writer's value at generation 3", fresh.ConfigurationRevision, fresh.Settings.Sessions) for _, want := range []string{"2", "3"} { nextConfigurationEvent(t, sub, want) } @@ -1357,21 +1164,13 @@ func TestRuntimeSettingsMutationOwnershipAndResult(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() mutation, err := r.updateSettings(ctx, mutationSettings()) - if err != nil { - t.Fatalf("updateSettings: %v", err) - } - if mutation.ConfigurationRevision.Generation != "2" || mutation.ConfigurationRevision.InstanceId != "" { - t.Fatalf("mutation revision = %+v, want generation 2 with the empty pre-server instance", mutation.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.ConfigurationRevision.Generation == "2" && mutation.ConfigurationRevision.InstanceId == "", "mutation revision = %+v, want generation 2 with the empty pre-server instance", mutation.ConfigurationRevision) mutation.Result.Sessions.ArchiveAfterDays = 999 mutation.Result.Plugins["tools"] = `{"mutated":true}` fresh, err := r.getConfiguration(ctx) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } - if fresh.Settings.Sessions.ArchiveAfterDays != 5 || compactJSON(t, []byte(fresh.Settings.Plugins["tools"])) != compactJSON(t, []byte(mutationSettings().Plugins["tools"])) { - t.Fatalf("a mutation of the returned result reached the published settings: %+v", fresh.Settings) - } + testassert.NoError(t, err) + testassert.Require(t, fresh.Settings.Sessions.ArchiveAfterDays == 5 && compactJSON(t, []byte(fresh.Settings.Plugins["tools"])) == compactJSON(t, []byte(mutationSettings().Plugins["tools"])), "a mutation of the returned result reached the published settings: %+v", fresh.Settings) }) } @@ -1383,12 +1182,11 @@ func TestRuntimeSettingsMutationOwnershipAndResult(t *testing.T) { func TestRuntimeAgentModelMutationAffectsNextAdmissions(t *testing.T) { eachPrepStore(t, func(t *testing.T, store harness.Storage) { e := newOwnerEnv(t) - writeServiceFile(t, e.configPath, `{"providers":{"prov":{"transport":{"base_url":"`+e.server.URL+`","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":262144,"max_output_tokens":4096},"wide":{"name":"W","context_window":262144,"max_output_tokens":4096}}}}}`) + writeServiceFile(t, e.configPath, `{"providers":{"prov":`+nominalProvMember(e.server.URL, "", + `{"m":{"name":"M","context_window":262144,"max_output_tokens":4096},"wide":{"name":"W","context_window":262144,"max_output_tokens":4096}}`)+`}}`) writeServiceFile(t, agents.PathForConfig(e.configPath), `{"solo":{"model":"prov/m","system_prompt":"simple"},"worker":{"model":"prov/m","system_prompt":"simple"}}`) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) ctx := context.Background() @@ -1403,24 +1201,16 @@ func TestRuntimeAgentModelMutationAffectsNextAdmissions(t *testing.T) { awaitModelArrival(t, e) mutation, err := r.setAgentTypeModel(ctx, "worker", "prov/wide") - if err != nil { - t.Fatalf("setAgentTypeModel: %v", err) - } - if mutation.ConfigurationRevision.Generation != "2" || mutation.Result.Model != "prov/wide" || mutation.Result.Name != "worker" { - t.Fatalf("mutation = %+v, want the new model projected from the returned candidate at generation 2", mutation) - } + testassert.NoError(t, err) + testassert.Require(t, mutation.ConfigurationRevision.Generation == "2" && mutation.Result.Model == "prov/wide" && mutation.Result.Name == "worker", "mutation = %+v, want the new model projected from the returned candidate at generation 2", mutation) // Session B is idle: its next admission commits the new model at the // new generation, and the active Operation has not re-captured. The // held HTTP request parks every execution, so B's Operation runs up // to its model request and both settle once the gate opens. submitThroughRuntime(t, r, b.Identity.SessionID, "op-b1", "b turn") - if got := committedAdmission(t, r, b.Identity.SessionID, "op-b1"); got != "worker=prov/wide@2" { - t.Fatalf("B's committed capture = %q, want the new selection at generation 2", got) - } - if got := committedAdmission(t, r, a.Identity.SessionID, "op-active"); got != "worker=prov/m@1" { - t.Fatalf("A's committed capture = %q, want the original capture kept by the running Operation", got) - } + testassert.Equal(t, committedAdmission(t, r, b.Identity.SessionID, "op-b1"), "worker=prov/wide@2") + testassert.Equal(t, committedAdmission(t, r, a.Identity.SessionID, "op-active"), "worker=prov/m@1") // The active captures settle with their original selections; A's // next admission then commits the new model too. @@ -1428,27 +1218,23 @@ func TestRuntimeAgentModelMutationAffectsNextAdmissions(t *testing.T) { awaitIdleSession(t, r, a.Identity.SessionID) awaitIdleSession(t, r, b.Identity.SessionID) submitConvergedThroughRuntime(t, r, a.Identity.SessionID, "op-a2", "a turn", harness.OperationSuccess) - if got := committedAdmission(t, r, a.Identity.SessionID, "op-a2"); got != "worker=prov/wide@2" { - t.Fatalf("A's next committed capture = %q, want the selection under the mutated publication", got) - } + testassert.Equal(t, committedAdmission(t, r, a.Identity.SessionID, "op-a2"), "worker=prov/wide@2") // The forbidden siblings: an unknown type and a malformed ref fail // invalid and never touch the owning file. agentsPath := agents.PathForConfig(e.configPath) before, err := os.ReadFile(agentsPath) - if err != nil { - t.Fatalf("read agents file: %v", err) - } - if _, err := r.setAgentTypeModel(ctx, "ghost", "prov/m"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("unknown type = %v, want harness.ErrInvalid", err) + testassert.NoError(t, err) + { + _, err := r.setAgentTypeModel(ctx, "ghost", "prov/m") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "unknown type = %v, want harness.ErrInvalid", err) } - if _, err := r.setAgentTypeModel(ctx, "worker", "noslash"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("malformed ref = %v, want harness.ErrInvalid", err) + { + _, err := r.setAgentTypeModel(ctx, "worker", "noslash") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "malformed ref = %v, want harness.ErrInvalid", err) } after, err := os.ReadFile(agentsPath) - if err != nil || string(before) != string(after) { - t.Fatalf("a failed agent mutation changed the owning file (%v)", err) - } + testassert.Require(t, err == nil && string(before) == string(after), "a failed agent mutation changed the owning file (%v)", err) }) } @@ -1499,19 +1285,10 @@ func TestRuntimeOwnerCloseAfterWriteBeginsFinishesPublication(t *testing.T) { releasePark() got := <-done - if got.err != nil { - t.Fatalf("updateSettings after write start = %v, want the admitted call to complete", got.err) - } - if got.mutation.ConfigurationRevision.Generation != "2" { - t.Fatalf("mutation revision = %+v, want generation 2 published", got.mutation.ConfigurationRevision) - } - if err := <-closed; err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, got.err == nil && got.mutation.ConfigurationRevision.Generation == "2", "updateSettings after write start = %v, want the admitted call to complete; mutation revision = %+v, want generation 2 published", got.err, got.mutation.ConfigurationRevision) + testassert.NoError(t, <-closed) root := fileRoot(t, e.configPath) - if !strings.Contains(compactJSON(t, root["sessions"]), `"archive_after_days":5`) { - t.Fatalf("owning file = %s, want the written sessions member", root["sessions"]) - } + testassert.Require(t, strings.Contains(compactJSON(t, root["sessions"]), `"archive_after_days":5`), "owning file = %s, want the written sessions member", root["sessions"]) }) } @@ -1527,7 +1304,7 @@ func TestRuntimeOwnerCloseAfterWriteBeginsFinishesPublication(t *testing.T) { // no mtime checks, no second writer. func TestConfigurationMutateExternalEditorRaceLastWriterWins(t *testing.T) { h := newServiceHarness(t) - writeServiceFile(t, h.configPath, mutationConfigDocument) + writeServiceFile(t, h.configPath, mutationConfigDocument()) var parked int32 // the validator parks only the edit build arrive := make(chan struct{}, 1) gate := make(chan struct{}) @@ -1538,13 +1315,12 @@ func TestConfigurationMutateExternalEditorRaceLastWriterWins(t *testing.T) { } return nil })) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) atomic.StoreInt32(&parked, 1) @@ -1574,19 +1350,14 @@ func TestConfigurationMutateExternalEditorRaceLastWriterWins(t *testing.T) { // The real concurrent external editor: a valid whole-file OS write in // the open window, with a changed provider value and a new unowned // top-level member the captured root never saw. - external := strings.Replace(mutationConfigDocument, `"name":"One"`, `"name":"Two"`, 1) + external := strings.Replace(mutationConfigDocument(), `"name":"One"`, `"name":"Two"`, 1) external = strings.TrimSuffix(external, "}") + `,"external_note":"added mid-window"}` writeServiceFile(t, h.configPath, external) release() got := <-done received = true - if got.err != nil { - t.Fatalf("mutation after the concurrent external write: %v", got.err) - } - if got.cfg.snapshot.generation != 2 || svc.current() != got.cfg.snapshot { - t.Fatalf("race mutation = generation %d, want 2 published", got.cfg.snapshot.generation) - } + testassert.Require(t, got.err == nil && got.cfg.snapshot.generation == 2 && svc.current() == got.cfg.snapshot, "mutation after the concurrent external write: %v; race mutation = generation %d, want 2 published", got.err, got.cfg.snapshot.generation) nextConfigurationEvent(t, sub, "2") // The file: the complete earlier candidate bytes won — the captured old @@ -1594,8 +1365,9 @@ func TestConfigurationMutateExternalEditorRaceLastWriterWins(t *testing.T) { // requested settings are new — pinned against independent expected // values. root := fileRoot(t, h.configPath) - if _, ok := root["external_note"]; ok { - t.Fatalf("the external editor's mid-window member survived: %s", root["external_note"]) + { + _, ok := root["external_note"] + testassert.Require(t, !ok, "the external editor's mid-window member survived: %s", root["external_note"]) } var providers struct { Prov struct { @@ -1604,28 +1376,22 @@ func TestConfigurationMutateExternalEditorRaceLastWriterWins(t *testing.T) { } `json:"models"` } `json:"prov"` } - if err := json.Unmarshal(root["providers"], &providers); err != nil { - t.Fatalf("decode providers member: %v", err) - } - if got := providers.Prov.Models["m"].Name; got != "One" { - t.Fatalf("captured provider value = %q, want the captured \"One\" over the external \"Two\"", got) - } - if got, want := compactJSON(t, root["sessions"]), compactJSON(t, mustMarshal(t, mutationSettings().Sessions)); got != want { - t.Fatalf("sessions member = %s, want the requested %s", got, want) + testassert.NoError(t, json.Unmarshal(root["providers"], &providers)) + testassert.Equal(t, providers.Prov.Models["m"].Name, "One") + { + got, want := compactJSON(t, root["sessions"]), compactJSON(t, mustMarshal(t, mutationSettings().Sessions)) + testassert.Equal(t, got, want) } var writtenPlugins map[string]json.RawMessage - if err := json.Unmarshal(root["plugins"], &writtenPlugins); err != nil { - t.Fatalf("decode plugins member: %v", err) - } - if got, want := compactJSON(t, writtenPlugins["tools"]), compactJSON(t, []byte(mutationSettings().Plugins["tools"])); got != want { - t.Fatalf("plugins member = %s, want the requested %s", got, want) + testassert.NoError(t, json.Unmarshal(root["plugins"], &writtenPlugins)) + { + got, want := compactJSON(t, writtenPlugins["tools"]), compactJSON(t, []byte(mutationSettings().Plugins["tools"])) + testassert.Equal(t, got, want) } // The published projection: the same winning candidate's settings. view := projectSettings(svc.current().sessions, svc.current().plugins) - if view.Sessions != mutationSettings().Sessions || compactJSON(t, []byte(view.Plugins["tools"])) != compactJSON(t, []byte(mutationSettings().Plugins["tools"])) { - t.Fatalf("published settings = %+v, want the requested shape", view) - } + testassert.Require(t, view.Sessions == mutationSettings().Sessions && compactJSON(t, []byte(view.Plugins["tools"])) == compactJSON(t, []byte(mutationSettings().Plugins["tools"])), "published settings = %+v, want the requested shape", view) } // nextConfigurationEvent reads one event and proves it is the configuration @@ -1662,9 +1428,7 @@ func consumeOptionalWarningEvent(t *testing.T, sub *Subscription, store *warning t.Helper() select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed before the end of its batch") - } + testassert.Require(t, ok, "the subscription closed before the end of its batch") if eventKind(t, event) != "warning_changed" { t.Fatalf("batched publication event = %s, want the optional global warning event", eventJSON(t, event)) } @@ -1679,18 +1443,12 @@ func consumeOptionalWarningEvent(t *testing.T, sub *Subscription, store *warning func requireRealWarningRevision(t *testing.T, event Event, store *warningStore) { t.Helper() body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) != "runtime" { t.Fatalf("warning event scope kind = %q, want the runtime scope", scopeKind(t, body.Scope)) } revision, err := strconv.ParseUint(body.WarningsRevision.Revision, 10, 64) - if err != nil || revision == 0 { - t.Fatalf("warning event revision = %q, want a real advanced value", body.WarningsRevision.Revision) - } + testassert.Require(t, err == nil && revision != 0, "warning event revision = %q, want a real advanced value", body.WarningsRevision.Revision) current, _ := store.snapshot() - if revision > current { - t.Fatalf("warning event revision = %d, beyond the store's current value %d", revision, current) - } + testassert.Require(t, revision <= current, "warning event revision = %d, beyond the store's current value %d", revision, current) } diff --git a/runtime/configuration_service_test.go b/runtime/configuration_service_test.go index a67200f5..7b7cd66f 100644 --- a/runtime/configuration_service_test.go +++ b/runtime/configuration_service_test.go @@ -20,6 +20,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -65,9 +66,31 @@ func staticBundledFS() fstest.MapFS { }`)}} } +// nominalProvMember builds the header-free nominal prov provider object the +// configuration fixtures share: a transport carrying only the endpoint and +// the key-env name (both encoded as JSON strings), discovery disabled, and +// the authored raw models object spliced exactly as written — its numeric +// lexemes are never decoded and re-encoded. Callers keep their own document +// roots (permissions, plugins, sessions, custom members). +func nominalProvMember(endpoint, keyEnv, models string) string { + return `{"transport":{"base_url":` + fixtureString(endpoint) + `,"api_key_env":` + fixtureString(keyEnv) + + `},"discovery":false,"models":` + models + `}` +} + +// workerAgentsDocument is the shared one-worker agent document the +// configuration rows seed: a bare worker entry with no model selection. +const workerAgentsDocument = `{"worker":{"system_prompt":"simple"}}` + +// fixtureString renders one dynamic fixture value as its JSON string literal. +// A string value always encodes, so the marshal error is impossible. +func fixtureString(value string) string { + data, _ := json.Marshal(value) + return string(data) +} + func providerConfigFile(name string) string { - return `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"` + - name + `","context_window":9007199254740993}}}},"sessions":{"archive_after_days":3}}` + return `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "", + `{"m":{"name":`+fixtureString(name)+`,"context_window":9007199254740993}}`) + `},"sessions":{"archive_after_days":3}}` } func writeServiceFile(t *testing.T, path, content string) { @@ -75,8 +98,9 @@ func writeServiceFile(t *testing.T, path, content string) { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { t.Fatalf("MkdirAll(%s): %v", filepath.Dir(path), err) } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatalf("WriteFile(%s): %v", path, err) + { + err := os.WriteFile(path, []byte(content), 0o600) + testassert.Require(t, err == nil, "WriteFile(%s): %v", path, err) } } @@ -122,60 +146,38 @@ func TestConfigurationServicePublishesImmutableGenerations(t *testing.T) { svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("initial publish: %v", err) - } + testassert.NoError(t, err) if first.snapshot.generation != 1 || (Invocation{snapshot: first.snapshot}).Revision() != "1" { t.Fatalf("initial publication = generation %d revision %q, want 1", first.snapshot.generation, (Invocation{snapshot: first.snapshot}).Revision()) } - if model := first.snapshot.catalog.Providers["prov"].Models["m"]; model.ContextWindow != 9007199254740993 { - t.Fatalf("captured model = %+v, want the exact number preserved through the assembly", model) - } - if !first.snapshot.sessions.AutoArchive || first.snapshot.sessions.ArchiveAfterDays != 3 { - t.Fatalf("sessions = %+v, want the decoded policy over the defaults", first.snapshot.sessions) - } - if len(first.snapshot.catalogWarnings) != 0 { - t.Fatalf("catalog warnings = %#v, want none", first.snapshot.catalogWarnings) - } - if svc.current() != first.snapshot { - t.Fatal("current() does not return the published snapshot") + { + model := first.snapshot.catalog.Providers["prov"].Models["m"] + testassert.Require(t, model.ContextWindow == 9007199254740993, "captured model = %+v, want the exact number preserved through the assembly", model) } + testassert.Require(t, first.snapshot.sessions.AutoArchive && first.snapshot.sessions.ArchiveAfterDays == 3 && len(first.snapshot.catalogWarnings) == 0, "sessions = %+v, want the decoded policy over the defaults; catalog warnings = %#v, want none", first.snapshot.sessions, first.snapshot.catalogWarnings) + testassert.Require(t, svc.current() == first.snapshot, "current() does not return the published snapshot") writeServiceFile(t, h.configPath, providerConfigFile("Two")) second, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload: %v", err) - } + testassert.NoError(t, err) if second.snapshot.generation != 2 || snapshotModelName(second.snapshot, "prov", "m") != "Two" { t.Fatalf("reload = generation %d name %q, want 2/Two", second.snapshot.generation, snapshotModelName(second.snapshot, "prov", "m")) } - if svc.current() != second.snapshot { - t.Fatal("current() does not return the newest publication") - } + testassert.Require(t, svc.current() == second.snapshot, "current() does not return the newest publication") if first.snapshot.generation != 1 || snapshotModelName(first.snapshot, "prov", "m") != "One" { t.Fatalf("the old capture changed: generation %d name %q", first.snapshot.generation, snapshotModelName(first.snapshot, "prov", "m")) } writeServiceFile(t, h.configPath, `{not json`) failed, err := svc.publish(context.Background()) - if failed.snapshot != nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("failed publish = (%v, %v), want a nil candidate and ErrConfiguration", failed, err) - } - if !strings.Contains(err.Error(), "decode captured configuration") { - t.Fatalf("failed publish error = %v, want the source error preserved", err) - } - if svc.current() != second.snapshot { - t.Fatal("a failed build published something") - } + testassert.Require(t, failed.snapshot == nil && errors.Is(err, ErrConfiguration), "failed publish = (%v, %v), want a nil candidate and ErrConfiguration", failed, err) + testassert.Require(t, strings.Contains(err.Error(), "decode captured configuration"), "failed publish error = %v, want the source error preserved", err) + testassert.Require(t, svc.current() == second.snapshot, "a failed build published something") writeServiceFile(t, h.configPath, providerConfigFile("Three")) third, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish after failure: %v", err) - } - if third.snapshot.generation != 3 { - t.Fatalf("generation = %d after a failed build, want the failed build to consume nothing (3)", third.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Equal(t, third.snapshot.generation, 3) } // TestConfigurationServiceCapturesInputOnce proves one build consumes exactly @@ -195,28 +197,27 @@ func TestConfigurationServiceCapturesInputOnce(t *testing.T) { svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, rewrite)) first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("initial publish: %v", err) - } + testassert.NoError(t, err) if snapshotModelName(first.snapshot, "prov", "m") != "One" { t.Fatalf("the in-flight candidate reread the main configuration: name %q", snapshotModelName(first.snapshot, "prov", "m")) } - if early, _ := hasDefinition(first.snapshot, "early"); !early { - t.Fatal("the captured agent definitions were replaced mid-build") + { + early, _ := hasDefinition(first.snapshot, "early") + testassert.Require(t, early, "the captured agent definitions were replaced mid-build") } - if late, _ := hasDefinition(first.snapshot, "late"); late { - t.Fatal("the in-flight candidate reread the agent definitions") + { + late, _ := hasDefinition(first.snapshot, "late") + testassert.Require(t, !late, "the in-flight candidate reread the agent definitions") } second, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload: %v", err) - } + testassert.NoError(t, err) if snapshotModelName(second.snapshot, "prov", "m") != "Two" { t.Fatalf("the reload did not capture the current bytes: name %q", snapshotModelName(second.snapshot, "prov", "m")) } - if found, model := hasDefinition(second.snapshot, "late"); !found || model != "prov/m" { - t.Fatalf("late definition = (%v, %q), want the freshly captured bytes", found, model) + { + found, model := hasDefinition(second.snapshot, "late") + testassert.Require(t, found && model == "prov/m", "late definition = (%v, %q), want the freshly captured bytes", found, model) } } @@ -245,18 +246,9 @@ func TestConfigurationServicePluginValidationRejectsPublication(t *testing.T) { writeServiceFile(t, h.configPath, row.config) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, row.validate)) candidate, err := svc.publish(context.Background()) - if candidate.snapshot != nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("%s: publish = (%v, %v), want a rejected candidate", row.name, candidate, err) - } - if row.wantIs != nil && !errors.Is(err, row.wantIs) { - t.Fatalf("%s: error %v does not preserve the validator source error", row.name, err) - } - if row.wantSource != "" && !strings.Contains(err.Error(), row.wantSource) { - t.Fatalf("%s: error %v does not preserve %q", row.name, err, row.wantSource) - } - if svc.current() != nil { - t.Fatalf("%s: a rejected candidate published a snapshot", row.name) - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, ErrConfiguration), "%s: publish = (%v, %v), want a rejected candidate", row.name, candidate, err) + testassert.Require(t, (row.wantIs == nil || errors.Is(err, row.wantIs)) && (row.wantSource == "" || strings.Contains(err.Error(), row.wantSource)), "%s: error %v does not preserve the validator source error; %s: error %v does not preserve %q", row.name, err, row.name, err, row.wantSource) + testassert.Require(t, svc.current() == nil, "%s: a rejected candidate published a snapshot", row.name) if h.opens.Load() != 0 { t.Fatalf("%s: rejection ran %d factories", row.name, h.opens.Load()) } @@ -282,23 +274,22 @@ func TestConfigurationServicePluginValidationRejectsPublication(t *testing.T) { } } svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, validate)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("%s: publish: %v", row.name, err) + { + _, err := svc.publish(context.Background()) + testassert.Require(t, err == nil, "%s: publish: %v", row.name, err) } if row.name == "absent section passes nil" { if got := seen.Load(); got == nil || len(*got) != 0 { t.Fatalf("%s: the plugin received %q, want nil", row.name, string(*got)) } } - if h.opens.Load() != 0 { - t.Fatalf("%s: configuration publication ran a factory", row.name) - } + testassert.Require(t, h.opens.Load() == 0, "%s: configuration publication ran a factory", row.name) } } // pluginsConfigFile is providerConfigFile with one plugin section verbatim. func pluginsConfigFile(alpha string) string { - return `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"One","context_window":100}}}},"plugins":{"alpha":` + alpha + `}}` + return `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "", `{"m":{"name":"One","context_window":100}}`) + `},"plugins":{"alpha":` + alpha + `}}` } // TestConfigurationServiceValidatorScratchCannotChangePublication proves the @@ -318,14 +309,11 @@ func TestConfigurationServiceValidatorScratchCannotChangePublication(t *testing. return nil })) pub, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish: %v", err) - } - if string(pub.snapshot.plugins["alpha"]) != admitted { - t.Fatalf("published snapshot bytes = %s, want the retained candidate", pub.snapshot.plugins["alpha"]) - } - if got := (Invocation{snapshot: pub.snapshot}).Config("alpha"); string(got) != admitted { - t.Fatalf("Invocation.Config = %s, want the retained candidate", got) + testassert.NoError(t, err) + testassert.Require(t, string(pub.snapshot.plugins["alpha"]) == admitted, "published snapshot bytes = %s, want the retained candidate", pub.snapshot.plugins["alpha"]) + { + got := (Invocation{snapshot: pub.snapshot}).Config("alpha") + testassert.Require(t, string(got) == admitted, "Invocation.Config = %s, want the retained candidate", got) } }) t.Run("absent settings still pass nil through", func(t *testing.T) { @@ -336,12 +324,11 @@ func TestConfigurationServiceValidatorScratchCannotChangePublication(t *testing. sawNil = raw == nil return nil })) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("publish: %v", err) - } - if !sawNil { - t.Fatal("absent settings reached the validator as something other than nil") + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } + testassert.Require(t, sawNil, "absent settings reached the validator as something other than nil") }) t.Run("rejected tampering leaves the prior publication intact", func(t *testing.T) { h := newServiceHarness(t) @@ -355,34 +342,23 @@ func TestConfigurationServiceValidatorScratchCannotChangePublication(t *testing. return errValidator })) sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("initial publish: %v", err) - } - if event, ok := nextEvent(t, sub); !ok || eventKind(t, event) != "configuration_changed" || eventGeneration(t, event) != "1" { - t.Fatalf("initial publication event = %s (ok=%v), want the generation 1 configuration event", eventJSON(t, event), ok) - } - if _, err := svc.publish(context.Background()); !errors.Is(err, ErrConfiguration) || !errors.Is(err, errValidator) { - t.Fatalf("reload = %v, want a rejection preserving the validator source error", err) + testassert.NoError(t, err) + nextConfigurationEvent(t, sub, "1") + { + _, err := svc.publish(context.Background()) + testassert.Require(t, errors.Is(err, ErrConfiguration) && errors.Is(err, errValidator), "reload = %v, want a rejection preserving the validator source error", err) } select { case event, ok := <-sub.Events(): - if ok { - t.Fatalf("rejected build published event %+v, want silence", event) - } + testassert.Require(t, !ok, "rejected build published event %+v, want silence", event) t.Fatal("the subscription closed around the rejected build, want silence with the subscription open") default: } - if svc.current() != first.snapshot { - t.Fatal("a rejected candidate replaced the prior publication") - } - if first.snapshot.generation != 1 || string(first.snapshot.plugins["alpha"]) != admitted { - t.Fatalf("prior snapshot = generation %d bytes %s, want generation 1 with the retained bytes", first.snapshot.generation, first.snapshot.plugins["alpha"]) - } + testassert.Require(t, svc.current() == first.snapshot, "a rejected candidate replaced the prior publication") + testassert.Require(t, first.snapshot.generation == 1 && string(first.snapshot.plugins["alpha"]) == admitted, "prior snapshot = generation %d bytes %s, want generation 1 with the retained bytes", first.snapshot.generation, first.snapshot.plugins["alpha"]) }) } @@ -400,18 +376,10 @@ func TestConfigurationServiceCancellationBetweenValidatorsStopsTheRest(t *testin servicePlugin("third", &h.opens, func(json.RawMessage) error { lastRan.Store(true); return nil }), ) candidate, err := svc.publish(ctx) - if candidate.snapshot != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("publish = (%v, %v), want the caller context error", candidate, err) - } - if errors.Is(err, ErrConfiguration) || errors.Is(err, ErrClosed) { - t.Fatalf("cancellation was classified as %v, want unwrapped", err) - } - if lastRan.Load() { - t.Fatal("validation continued past the observed cancellation") - } - if svc.current() != nil { - t.Fatal("a canceled build published a snapshot") - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, context.Canceled), "publish = (%v, %v), want the caller context error", candidate, err) + testassert.Require(t, !errors.Is(err, ErrConfiguration) && !errors.Is(err, ErrClosed), "cancellation was classified as %v, want unwrapped", err) + testassert.Require(t, !lastRan.Load(), "validation continued past the observed cancellation") + testassert.Require(t, svc.current() == nil, "a canceled build published a snapshot") } // TestConfigurationServiceChecksCancellationBeforePublication proves the @@ -427,11 +395,13 @@ func TestConfigurationServiceChecksCancellationBeforePublication(t *testing.T) { svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) ctx, cancel := context.WithCancel(context.Background()) cancel() - if _, err := svc.publish(ctx); !errors.Is(err, context.Canceled) { - t.Fatalf("publish = %v, want the caller context error", err) + { + _, err := svc.publish(ctx) + testassert.Require(t, errors.Is(err, context.Canceled), "publish = %v, want the caller context error", err) } - if _, err := os.Stat(h.configPath); !os.IsNotExist(err) { - t.Fatalf("a pre-canceled publish started input reads: %v", err) + { + _, err := os.Stat(h.configPath) + testassert.Require(t, os.IsNotExist(err), "a pre-canceled publish started input reads: %v", err) } }) t.Run("owner canceled before waiting", func(t *testing.T) { @@ -439,11 +409,13 @@ func TestConfigurationServiceChecksCancellationBeforePublication(t *testing.T) { h := newServiceHarness(t) svc := h.service(owner, servicePlugin("alpha", &h.opens, nil)) cancelOwner() - if _, err := svc.publish(context.Background()); !errors.Is(err, ErrClosed) { - t.Fatalf("publish = %v, want ErrClosed for the canceled owner", err) + { + _, err := svc.publish(context.Background()) + testassert.Require(t, errors.Is(err, ErrClosed), "publish = %v, want ErrClosed for the canceled owner", err) } - if _, err := os.Stat(h.configPath); !os.IsNotExist(err) { - t.Fatalf("an owner-canceled publish started input reads: %v", err) + { + _, err := os.Stat(h.configPath) + testassert.Require(t, os.IsNotExist(err), "an owner-canceled publish started input reads: %v", err) } }) t.Run("caller canceled after the last validator", func(t *testing.T) { @@ -455,22 +427,14 @@ func TestConfigurationServiceChecksCancellationBeforePublication(t *testing.T) { return nil })) sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) candidate, err := svc.publish(ctx) - if candidate.snapshot != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("publish = (%v, %v), want the Store check to reject the completed candidate", candidate, err) - } - if svc.current() != nil { - t.Fatal("the Store check published anyway") - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, context.Canceled), "publish = (%v, %v), want the Store check to reject the completed candidate", candidate, err) + testassert.Require(t, svc.current() == nil, "the Store check published anyway") select { case event, ok := <-sub.Events(): - if ok { - t.Fatalf("the canceled publication emitted %+v, want event silence", event) - } + testassert.Require(t, !ok, "the canceled publication emitted %+v, want event silence", event) t.Fatal("the subscription closed around the canceled publication, want silence with the subscription open") default: } @@ -481,32 +445,23 @@ func TestConfigurationServiceChecksCancellationBeforePublication(t *testing.T) { h := newServiceHarness(t) svc := h.service(owner, servicePlugin("alpha", &h.opens, nil)) sub, err := svc.obs.subscribe(4) - if err != nil { - t.Fatalf("subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) cancelOwner() cancelCaller() candidate, err := svc.publish(caller) - if candidate.snapshot != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("both-done publish = (%v, %v), want the caller's own context error from the caller-first checkpoint", candidate, err) - } - if errors.Is(err, ErrClosed) { - t.Fatalf("both-done publish = %v, want the caller error, not the owner-lifecycle identity", err) - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, context.Canceled), "both-done publish = (%v, %v), want the caller's own context error from the caller-first checkpoint", candidate, err) + testassert.Require(t, !errors.Is(err, ErrClosed), "both-done publish = %v, want the caller error, not the owner-lifecycle identity", err) select { case event, ok := <-sub.Events(): - if ok { - t.Fatalf("the canceled publication emitted %+v, want event silence", event) - } + testassert.Require(t, !ok, "the canceled publication emitted %+v, want event silence", event) t.Fatal("the subscription closed around the canceled publication, want silence with the subscription open") default: } - if svc.current() != nil { - t.Fatal("a canceled publication stored a snapshot") - } - if _, err := os.Stat(h.configPath); !os.IsNotExist(err) { - t.Fatalf("a both-done publish started input reads: %v", err) + testassert.Require(t, svc.current() == nil, "a canceled publication stored a snapshot") + { + _, err := os.Stat(h.configPath) + testassert.Require(t, os.IsNotExist(err), "a both-done publish started input reads: %v", err) } }) t.Run("owner canceled after the last validator", func(t *testing.T) { @@ -518,12 +473,8 @@ func TestConfigurationServiceChecksCancellationBeforePublication(t *testing.T) { return nil })) candidate, err := svc.publish(context.Background()) - if candidate.snapshot != nil || !errors.Is(err, ErrClosed) { - t.Fatalf("publish = (%v, %v), want ErrClosed from the Store check", candidate, err) - } - if svc.current() != nil { - t.Fatal("an owner-canceled publication stored a snapshot") - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, ErrClosed), "publish = (%v, %v), want ErrClosed from the Store check", candidate, err) + testassert.Require(t, svc.current() == nil, "an owner-canceled publication stored a snapshot") }) } @@ -587,12 +538,8 @@ func TestConfigurationServiceSerializesBuildsAndRejectsCanceledWaiters(t *testin // check fail its input read instead of returning the canceled waiter's // own context error. dangling := h.configPath + ".stale" - if err := os.Rename(h.configPath, dangling); err != nil { - t.Fatalf("hide captured input: %v", err) - } - if err := os.Mkdir(h.configPath, 0o700); err != nil { - t.Fatalf("block the input path: %v", err) - } + testassert.NoError(t, os.Rename(h.configPath, dangling)) + testassert.NoError(t, os.Mkdir(h.configPath, 0o700)) readersStop := make(chan struct{}) var readers sync.WaitGroup @@ -620,9 +567,7 @@ func TestConfigurationServiceSerializesBuildsAndRejectsCanceledWaiters(t *testin go func() { immediate <- func() error { _, err := svc.publish(deadlineCtx); return err }() }() select { case err := <-immediate: - if !errors.Is(err, context.Canceled) { - t.Fatalf("pre-canceled publish = %v, want its context error", err) - } + testassert.Require(t, errors.Is(err, context.Canceled), "pre-canceled publish = %v, want its context error", err) case <-time.After(5 * time.Second): t.Fatal("a pre-canceled caller waited for the build mutex") } @@ -646,31 +591,17 @@ func TestConfigurationServiceSerializesBuildsAndRejectsCanceledWaiters(t *testin close(gate) first := <-buildA - if first.err != nil { - t.Fatalf("active build: %v", first.err) - } - if first.cfg.snapshot.generation != 1 { - t.Fatalf("active build generation = %d, want 1", first.cfg.snapshot.generation) - } - if waiter := <-waiting; waiter.cfg.snapshot != nil || !errors.Is(waiter.err, context.Canceled) { - t.Fatalf("canceled waiter = (%v, %v), want its own context error without starting another build", waiter.cfg, waiter.err) - } - if got := builds.Load(); got != 1 { - t.Fatalf("builds started = %d, want the canceled waiter to start no build", got) - } - if err := os.Remove(h.configPath); err != nil { - t.Fatalf("remove the input block: %v", err) - } - if err := os.Rename(dangling, h.configPath); err != nil { - t.Fatalf("restore the input: %v", err) - } + testassert.Require(t, first.err == nil && first.cfg.snapshot.generation == 1, "active build: %v; got %v, want %v", first.err, first.cfg.snapshot.generation, 1) + { + waiter := <-waiting + testassert.Require(t, waiter.cfg.snapshot == nil && errors.Is(waiter.err, context.Canceled), "canceled waiter = (%v, %v), want its own context error without starting another build", waiter.cfg, waiter.err) + } + testassert.Equal(t, builds.Load(), 1) + testassert.NoError(t, os.Remove(h.configPath)) + testassert.NoError(t, os.Rename(dangling, h.configPath)) second, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish after the serialized build: %v", err) - } - if second.snapshot.generation != 2 { - t.Fatalf("generation after serialization = %d, want 2", second.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Equal(t, second.snapshot.generation, 2) close(readersStop) readers.Wait() } @@ -685,12 +616,8 @@ func TestConfigurationServiceGenerationOverflow(t *testing.T) { svc.published.Store(exhausted) candidate, err := svc.publish(context.Background()) - if candidate.snapshot != nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("overflow publish = (%v, %v), want ErrConfiguration without publication", candidate, err) - } - if svc.current() != exhausted { - t.Fatal("the overflow failure replaced the published snapshot") - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, ErrConfiguration), "overflow publish = (%v, %v), want ErrConfiguration without publication", candidate, err) + testassert.Require(t, svc.current() == exhausted, "the overflow failure replaced the published snapshot") } // TestConfigurationServiceKeepsFirstRunSkeletons proves a fresh machine @@ -701,26 +628,21 @@ func TestConfigurationServiceKeepsFirstRunSkeletons(t *testing.T) { h := newServiceHarness(t) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("first-run publish: %v", err) - } + testassert.NoError(t, err) if configData, err := os.ReadFile(h.configPath); err != nil || string(configData) != mainConfigSkeleton { t.Fatalf("first-run config = %q (%v), want the retained skeleton", string(configData), err) } if agentsData, err := os.ReadFile(filepath.Join(h.dataDir, "agents.json")); err != nil || string(agentsData) != agentsSkeleton { t.Fatalf("first-run agents = %q (%v), want the retained skeleton", string(agentsData), err) } - if len(first.snapshot.definitions) != 5 { - t.Fatalf("definitions = %d, want the built-in roster", len(first.snapshot.definitions)) - } + testassert.Equal(t, len(first.snapshot.definitions), 5) writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"plain":{}}`) second, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish with a missing model selection: %v", err) - } - if found, model := hasDefinition(second.snapshot, "plain"); !found || model != "" { - t.Fatalf("plain definition = (%v, %q), want the roster with a wholly empty model", found, model) + testassert.NoError(t, err) + { + found, model := hasDefinition(second.snapshot, "plain") + testassert.Require(t, found && model == "", "plain definition = (%v, %q), want the roster with a wholly empty model", found, model) } } @@ -751,29 +673,23 @@ func TestConfigurationServiceKeepsHomeBasedCachePaths(t *testing.T) { writeServiceFile(t, configPath, `{}`) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) snapshot, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish: %v", err) - } - if snapshot.snapshot.catalog.Providers["remote"].Models["remote-model"] == nil { - t.Fatal("the discovery publication never reached the assembled catalog") + testassert.NoError(t, err) + testassert.Require(t, snapshot.snapshot.catalog.Providers["remote"].Models["remote-model"] != nil, "the discovery publication never reached the assembled catalog") + { + _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")) + testassert.NoError(t, err) } - if _, err := os.Stat(filepath.Join(home, ".lightcode", "cache", "discovery", "remote.json")); err != nil { - t.Fatalf("discovery cache moved away from the home root: %v", err) - } - if _, err := os.Stat(filepath.Join(home, ".lightcode", ".env")); !os.IsNotExist(err) { - t.Fatalf("the configuration build touched the dotenv source: %v", err) + { + _, err := os.Stat(filepath.Join(home, ".lightcode", ".env")) + testassert.Require(t, os.IsNotExist(err), "the configuration build touched the dotenv source: %v", err) } entries, err := os.ReadDir(dataDir) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) var names []string for _, entry := range entries { names = append(names, entry.Name()) } - if strings.Join(names, ",") != "agents.json,config.json" { - t.Fatalf("data directory entries = %q, want only the captured input files", names) - } + testassert.Require(t, strings.Join(names, ",") == "agents.json,config.json", "data directory entries = %q, want only the captured input files", names) } // TestConfigurationServiceRestartsGenerationWithFreshCaptures proves the @@ -784,30 +700,22 @@ func TestConfigurationServiceRestartsGenerationWithFreshCaptures(t *testing.T) { h := newServiceHarness(t) writeServiceFile(t, h.configPath, providerConfigFile("One")) before, err := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)).publish(context.Background()) - if err != nil { - t.Fatalf("publish before restart: %v", err) - } + testassert.NoError(t, err) writeServiceFile(t, h.configPath, providerConfigFile("Two")) after, err := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)).publish(context.Background()) - if err != nil { - t.Fatalf("publish after restart: %v", err) - } - if after.snapshot.generation != 1 { - t.Fatalf("after restart generation = %d, want a fresh lifetime starting again at 1", after.snapshot.generation) - } + testassert.NoError(t, err) + testassert.Equal(t, after.snapshot.generation, 1) if (Invocation{snapshot: after.snapshot}).Revision() != "1" || snapshotModelName(after.snapshot, "prov", "m") != "Two" { t.Fatalf("post-restart capture = %q/%q, want revision 1 with the fresh content", (Invocation{snapshot: after.snapshot}).Revision(), snapshotModelName(after.snapshot, "prov", "m")) } - if (Invocation{snapshot: before.snapshot}).Revision() != "1" || snapshotModelName(before.snapshot, "prov", "m") != "One" { - t.Fatal("the old-lifetime capture changed after the restart") - } + testassert.Require(t, (Invocation{snapshot: before.snapshot}).Revision() == "1" && snapshotModelName(before.snapshot, "prov", "m") == "One", "the old-lifetime capture changed after the restart") } const captureGlobalAllow = `{"rules":[{"permission":"file.write","target":"*","access":"allow"}]}` func captureConfigDoc() string { - return `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"One","context_window":100}}}},"permissions":` + captureGlobalAllow + `}` + return `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "", `{"m":{"name":"One","context_window":100}}`) + `},"permissions":` + captureGlobalAllow + `}` } // TestConfigurationServiceCapturesWorkspacePermissions proves the @@ -829,101 +737,88 @@ func TestConfigurationServiceCapturesWorkspacePermissions(t *testing.T) { // Startup on a fresh machine: no projects inventory, the Workspace level // absent and the captured global member deciding. first, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("fresh-install publish: %v", err) - } - if got := first.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("fresh-install policy = %#v, want the global-only capture", got) + testassert.NoError(t, err) + { + got := first.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "fresh-install policy = %#v, want the global-only capture", got) } // A later file requires Reload: the old revision is untouched and the // reload resolves the workspace rule over the captured global member. dirID, err := config.WorkspacePermissionDirID("/ws") - if err != nil { - t.Fatalf("WorkspacePermissionDirID: %v", err) - } + testassert.NoError(t, err) permissionPath := filepath.Join(h.home, ".lightcode", "projects", dirID, "permissions.json") writeServiceFile(t, permissionPath, `{"rules":[{"permission":"file.write","target":"*","access":"deny"}]}`) - if got := first.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("policy changed without Reload: %#v", got) + { + got := first.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "policy changed without Reload: %#v", got) } second, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload with the added file: %v", err) - } + testassert.NoError(t, err) want := harness.ResolvePermissionPolicy(json.RawMessage(captureGlobalAllow), json.RawMessage(`{"rules":[{"permission":"file.write","target":"*","access":"deny"}]}`)) - if got := second.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, want) { - t.Fatalf("reloaded policy = %#v, want the workspace deny over the global allow", got) + { + got := second.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, want), "reloaded policy = %#v, want the workspace deny over the global allow", got) } - if got := first.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("the old revision's capture changed after Reload: %#v", got) + { + got := first.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "the old revision's capture changed after Reload: %#v", got) } // An unreadable file is absent policy for that Workspace alone; the // global member still decides and publication succeeds. - if err := os.Chmod(permissionPath, 0o000); err != nil { - t.Fatalf("chmod: %v", err) - } + testassert.NoError(t, os.Chmod(permissionPath, 0o000)) t.Cleanup(func() { _ = os.Chmod(permissionPath, 0o600) }) third, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload with the unreadable file: %v", err) - } - if got := third.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("unreadable-file policy = %#v, want the global-only level", got) + testassert.NoError(t, err) + { + got := third.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "unreadable-file policy = %#v, want the global-only level", got) } - if got := second.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, want) { - t.Fatalf("the unreadable file changed the prior revision: %#v", got) + { + got := second.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, want), "the unreadable file changed the prior revision: %#v", got) } // A successfully read malformed file keeps publication and follows the // built-in fallback posture for that Workspace, discarding both user // levels there. - if err := os.Chmod(permissionPath, 0o600); err != nil { - t.Fatalf("restore: %v", err) - } + testassert.NoError(t, os.Chmod(permissionPath, 0o600)) writeServiceFile(t, permissionPath, `NOT JSON`) fourth, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload with the malformed file: %v", err) - } - if got := fourth.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, builtin) { - t.Fatalf("malformed-file policy = %#v, want the built-in fallback", got) + testassert.NoError(t, err) + { + got := fourth.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, builtin), "malformed-file policy = %#v, want the built-in fallback", got) } // A failed enumeration of the inventory directory leaves the whole // Workspace policy level absent for the revision, uniformly, without // failing publication. - if err := os.Chmod(filepath.Join(h.home, ".lightcode", "projects"), 0o000); err != nil { - t.Fatalf("chmod: %v", err) - } + testassert.NoError(t, os.Chmod(filepath.Join(h.home, ".lightcode", "projects"), 0o000)) t.Cleanup(func() { _ = os.Chmod(filepath.Join(h.home, ".lightcode", "projects"), 0o700) }) fifth, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload with the unreadable inventory: %v", err) - } - if got := fifth.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("failed-enumeration policy = %#v, want the uniform absent Workspace level", got) + testassert.NoError(t, err) + { + got := fifth.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "failed-enumeration policy = %#v, want the uniform absent Workspace level", got) } - if got := fifth.snapshot.permissionPolicy("/elsewhere"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("other-workspace policy under a failed enumeration = %#v, want the uniform absent level", got) + { + got := fifth.snapshot.permissionPolicy("/elsewhere") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "other-workspace policy under a failed enumeration = %#v, want the uniform absent level", got) } // A file removed again is absent policy at the reload that observes it. - if err := os.Chmod(filepath.Join(h.home, ".lightcode", "projects"), 0o700); err != nil { - t.Fatalf("restore inventory: %v", err) - } - if err := os.Remove(permissionPath); err != nil { - t.Fatalf("remove: %v", err) - } + testassert.NoError(t, os.Chmod(filepath.Join(h.home, ".lightcode", "projects"), 0o700)) + testassert.NoError(t, os.Remove(permissionPath)) sixth, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("reload with the removed file: %v", err) + testassert.NoError(t, err) + { + got := sixth.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "removed-file policy = %#v, want the absent Workspace level", got) } - if got := sixth.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("removed-file policy = %#v, want the absent Workspace level", got) - } - if got := fifth.snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("the removed file changed the prior revision: %#v", got) + { + got := fifth.snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "the removed file changed the prior revision: %#v", got) } } @@ -935,25 +830,18 @@ func TestConfigurationServiceUnreadableMainDocumentFailsPublication(t *testing.T h := newServiceHarness(t) writeServiceFile(t, h.configPath, providerConfigFile("One")) svc := h.service(context.Background(), servicePlugin("alpha", &h.opens, nil)) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("initial publish: %v", err) - } - if err := os.Remove(h.configPath); err != nil { - t.Fatalf("remove: %v", err) - } - if err := os.Mkdir(h.configPath, 0o700); err != nil { - t.Fatalf("block: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } + testassert.NoError(t, os.Remove(h.configPath)) + testassert.NoError(t, os.Mkdir(h.configPath, 0o700)) t.Cleanup(func() { _ = os.Remove(h.configPath) }) candidate, err := svc.publish(context.Background()) - if candidate.snapshot != nil || !errors.Is(err, ErrConfiguration) { - t.Fatalf("publish = (%v, %v), want the unreadable document to fail publication", candidate, err) - } - if !strings.Contains(err.Error(), "read main configuration") { - t.Fatalf("error = %v, want the retained source path", err) - } + testassert.Require(t, candidate.snapshot == nil && errors.Is(err, ErrConfiguration), "publish = (%v, %v), want the unreadable document to fail publication", candidate, err) + testassert.Require(t, strings.Contains(err.Error(), "read main configuration"), "error = %v, want the retained source path", err) } // TestConfigurationServiceToolDeclarationsStayDeclarative proves the @@ -979,30 +867,21 @@ func TestConfigurationServiceToolDeclarationsStayDeclarative(t *testing.T) { } svc := h.service(context.Background(), toolPlugin) snapshot, err := svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish: %v", err) - } + testassert.NoError(t, err) // The declared tool ID validated the configured reference and produced no // warning. found, _ := hasDefinition(snapshot.snapshot, "worker") - if !found || len(snapshot.snapshot.agentWarnings) != 0 { - t.Fatalf("worker retained = %v with warnings %+v, want the declared tool to validate cleanly", found, snapshot.snapshot.agentWarnings) - } + testassert.Require(t, found && len(snapshot.snapshot.agentWarnings) == 0, "worker retained = %v with warnings %+v, want the declared tool to validate cleanly", found, snapshot.snapshot.agentWarnings) // An undeclared reference drops the definition with the retained warning. writeServiceFile(t, filepath.Join(h.dataDir, "agents.json"), `{"worker":{"tools":["ghost_tool"]}}`) snapshot, err = svc.publish(context.Background()) - if err != nil { - t.Fatalf("publish with the unknown tool: %v", err) - } - if found, _ := hasDefinition(snapshot.snapshot, "worker"); found { - t.Fatal("the unknown tool name did not drop its definition") - } - if len(snapshot.snapshot.agentWarnings) != 1 || snapshot.snapshot.agentWarnings[0].Kind != "invalid_agent_type" || !strings.Contains(snapshot.snapshot.agentWarnings[0].Message, "ghost_tool") { - t.Fatalf("warnings = %+v, want the retained invalid-agent drop naming ghost_tool", snapshot.snapshot.agentWarnings) - } - if describeCalled { - t.Fatal("configuration publication ran the description function") + testassert.NoError(t, err) + { + found, _ := hasDefinition(snapshot.snapshot, "worker") + testassert.Require(t, !found, "the unknown tool name did not drop its definition") } + testassert.Require(t, len(snapshot.snapshot.agentWarnings) == 1 && snapshot.snapshot.agentWarnings[0].Kind == "invalid_agent_type" && strings.Contains(snapshot.snapshot.agentWarnings[0].Message, "ghost_tool"), "warnings = %+v, want the retained invalid-agent drop naming ghost_tool", snapshot.snapshot.agentWarnings) + testassert.Require(t, !describeCalled, "configuration publication ran the description function") if h.opens.Load() != 0 { t.Fatalf("%d factories ran during configuration publication", h.opens.Load()) } diff --git a/runtime/configuration_test.go b/runtime/configuration_test.go index f79c1600..1a49d689 100644 --- a/runtime/configuration_test.go +++ b/runtime/configuration_test.go @@ -11,6 +11,7 @@ import ( "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/config" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -62,9 +63,7 @@ func assembleConfiguration(generation uint64, configData, agentsData string, cap func testSnapshot(t *testing.T) *configuration { t.Helper() snapshot, err := assembleConfiguration(3, testConfigDocument, testAgentsDocument, testCapabilities()) - if err != nil { - t.Fatalf("newConfiguration: %v", err) - } + testassert.NoError(t, err) return snapshot } @@ -76,52 +75,38 @@ func testSnapshot(t *testing.T) *configuration { func TestNewConfigurationSnapshot(t *testing.T) { snapshot := testSnapshot(t) - if snapshot.generation != 3 { - t.Fatalf("generation = %d, want the supplied publication generation", snapshot.generation) - } - if snapshot.sessions.AutoArchive || snapshot.sessions.ArchiveAfterDays != 3 || snapshot.sessions.DeleteAfterArchiveDays != 7 { - t.Fatalf("sessions = %+v, want the decoded policy over the true/7/7 defaults", snapshot.sessions) - } + testassert.Require(t, snapshot.generation == 3 && !snapshot.sessions.AutoArchive && snapshot.sessions.ArchiveAfterDays == 3 && snapshot.sessions.DeleteAfterArchiveDays == 7, "got %v, want %v; sessions = %+v, want the decoded policy over the true/7/7 defaults", snapshot.generation, 3, snapshot.sessions) provider, found := snapshot.catalog.Providers["prov"] - if !found { - t.Fatalf("captured providers never reached the assembled catalog: %+v", snapshot.catalog) - } - if modelEntry := provider.Models["m"]; modelEntry == nil || modelEntry.ContextWindow != 9007199254740993 { - t.Fatalf("catalog model = %+v, want the exact integer preserved through the decode", modelEntry) + testassert.Require(t, found, "captured providers never reached the assembled catalog: %+v", snapshot.catalog) + { + modelEntry := provider.Models["m"] + testassert.Require(t, modelEntry != nil && modelEntry.ContextWindow == 9007199254740993, "catalog model = %+v, want the exact integer preserved through the decode", modelEntry) } plugin, ok := snapshot.plugins["alpha"] - if !ok { - t.Fatalf("plugins = %+v, want the alpha entry", snapshot.plugins) - } + testassert.Require(t, ok, "plugins = %+v, want the alpha entry", snapshot.plugins) var pluginFields map[string]any decoder := json.NewDecoder(strings.NewReader(string(plugin))) decoder.UseNumber() - if err := decoder.Decode(&pluginFields); err != nil { - t.Fatalf("plugin value must stay valid JSON: %v", err) - } - if number, isNumber := pluginFields["threshold"].(json.Number); !isNumber || number.String() != "9007199254740993" { - t.Fatalf("plugin threshold = %#v, want the exact json.Number preserved", pluginFields["threshold"]) + testassert.NoError(t, decoder.Decode(&pluginFields)) + { + number, isNumber := pluginFields["threshold"].(json.Number) + testassert.Require(t, isNumber && number.String() == "9007199254740993", "plugin threshold = %#v, want the exact json.Number preserved", pluginFields["threshold"]) } reference, err := agents.ParseWithCapabilities([]byte(testAgentsDocument), testCapabilities(), testToolUniverse(), nil) - if err != nil { - t.Fatalf("reference ParseWithCapabilities: %v", err) - } - if want := reference.All(); !reflect.DeepEqual(snapshot.definitions, want) { - t.Fatalf("definitions = %+v, want the retained roster with inherited fields, built-in overlays and prompt bytes", snapshot.definitions) + testassert.NoError(t, err) + { + want := reference.All() + testassert.Require(t, reflect.DeepEqual(snapshot.definitions, want), "definitions = %+v, want the retained roster with inherited fields, built-in overlays and prompt bytes", snapshot.definitions) } for _, def := range snapshot.definitions { // the complete private fields stay for their owning later phases if def.Name != "plan" { continue } - if def.LSP || !def.Readonly || def.WriteDir != "/tmp/plan" || def.Description != "planner" || !def.Subagent || def.Builtin { - t.Fatalf("resolved plan lost its retained fields: %+v", def) - } - } - if len(snapshot.catalogWarnings) != 0 || len(snapshot.agentWarnings) != 0 { - t.Fatalf("valid input produced warnings: catalog %+v agents %+v", snapshot.catalogWarnings, snapshot.agentWarnings) + testassert.Require(t, !def.LSP && def.Readonly && def.WriteDir == "/tmp/plan" && def.Description == "planner" && def.Subagent && !def.Builtin, "resolved plan lost its retained fields: %+v", def) } + testassert.Require(t, len(snapshot.catalogWarnings) == 0 && len(snapshot.agentWarnings) == 0, "valid input produced warnings: catalog %+v agents %+v", snapshot.catalogWarnings, snapshot.agentWarnings) } // TestNewConfigurationNullAndDefaults proves the target documents accept one @@ -132,23 +117,14 @@ func TestNewConfigurationSnapshot(t *testing.T) { func TestNewConfigurationNullAndDefaults(t *testing.T) { for _, doc := range []string{`{}`, `{"sessions":null,"plugins":null,"providers":null}`} { snapshot, err := assembleConfiguration(1, doc, `null`, nil) - if err != nil { - t.Fatalf("newConfiguration(%s): %v", doc, err) - } - if !snapshot.sessions.AutoArchive || snapshot.sessions.ArchiveAfterDays != 7 || snapshot.sessions.DeleteAfterArchiveDays != 7 { - t.Fatalf("sessions under %s = %+v, want true/7/7", doc, snapshot.sessions) - } - if snapshot.plugins != nil { - t.Fatalf("plugins under %s = %+v, want none", doc, snapshot.plugins) - } + testassert.Require(t, err == nil, "newConfiguration(%s): %v", doc, err) + testassert.Require(t, snapshot.sessions.AutoArchive && snapshot.sessions.ArchiveAfterDays == 7 && snapshot.sessions.DeleteAfterArchiveDays == 7 && snapshot.plugins == nil, "sessions under %s = %+v, want true/7/7; plugins under %s = %+v, want none", doc, snapshot.sessions, doc, snapshot.plugins) wantBuiltins := []string{"primary", "secondary", "explore", "review", "compact"} gotNames := make([]string, len(snapshot.definitions)) for i, def := range snapshot.definitions { gotNames[i] = def.Name } - if !reflect.DeepEqual(gotNames, wantBuiltins) { - t.Fatalf("definitions under %s = %q, want the five built-ins", doc, gotNames) - } + testassert.Require(t, reflect.DeepEqual(gotNames, wantBuiltins), "definitions under %s = %q, want the five built-ins", doc, gotNames) } } @@ -172,8 +148,9 @@ func TestNewConfigurationRejectsCandidates(t *testing.T) { {name: "plugins not an object", configData: `{"plugins":[]}`, agentsData: `{}`}, {name: "agents not an object", configData: `{}`, agentsData: `[]`}, } { - if snapshot, err := assembleConfiguration(1, row.configData, row.agentsData, nil); err == nil { - t.Fatalf("%s produced %+v, want rejection", row.name, snapshot) + { + snapshot, err := assembleConfiguration(1, row.configData, row.agentsData, nil) + testassert.Require(t, err != nil, "%s produced %+v, want rejection", row.name, snapshot) } } } @@ -186,15 +163,8 @@ func TestNewConfigurationKeepsExistingWarningTypes(t *testing.T) { `{"providers":{"broken":5}}`, `{"ghost":{"capabilities":["not-declared"]}}`, nil) - if err != nil { - t.Fatalf("newConfiguration: %v", err) - } - if len(snapshot.catalogWarnings) != 1 || snapshot.catalogWarnings[0].Kind != "user_config_skip" { - t.Fatalf("catalog warnings = %+v, want the retained catalog warning type", snapshot.catalogWarnings) - } - if len(snapshot.agentWarnings) != 1 || snapshot.agentWarnings[0].Kind != "invalid_agent_type" || snapshot.agentWarnings[0].Name != "ghost" { - t.Fatalf("agent warnings = %+v, want the retained invalid-agent drop", snapshot.agentWarnings) - } + testassert.NoError(t, err) + testassert.Require(t, len(snapshot.catalogWarnings) == 1 && snapshot.catalogWarnings[0].Kind == "user_config_skip" && len(snapshot.agentWarnings) == 1 && snapshot.agentWarnings[0].Kind == "invalid_agent_type" && snapshot.agentWarnings[0].Name == "ghost", "catalog warnings = %+v, want the retained catalog warning type; agent warnings = %+v, want the retained invalid-agent drop", snapshot.catalogWarnings, snapshot.agentWarnings) } // TestConfigurationAgentTypesProjection proves the per-Agent view over the @@ -210,79 +180,48 @@ func TestConfigurationAgentTypesProjection(t *testing.T) { for i, at := range types { got[i] = at.Name } - if !reflect.DeepEqual(got, want) { - t.Fatalf("projected roster = %q, want %q", got, want) - } + testassert.Require(t, reflect.DeepEqual(got, want), "projected roster = %q, want %q", got, want) plan, err := harness.ResolveAgentType("plan", types) - if err != nil { - t.Fatalf("ResolveAgentType(plan): %v", err) + testassert.NoError(t, err) + testassert.Require(t, plan.Model == (model.ModelRef{Provider: "prov", Model: "m"}) && plan.SystemPrompt == "simple" && plan.Prompt == "plan prompt", "plan view = %+v, want the public identity and prompt fields", plan) + { + wantTools := []string{"plan_tool", "plan_tool", "read_file"} + testassert.Require(t, reflect.DeepEqual(plan.Tools, wantTools), "plan tools = %q, want the retained configured names", plan.Tools) } - if plan.Model != (model.ModelRef{Provider: "prov", Model: "m"}) || plan.SystemPrompt != "simple" || plan.Prompt != "plan prompt" { - t.Fatalf("plan view = %+v, want the public identity and prompt fields", plan) - } - if wantTools := []string{"plan_tool", "plan_tool", "read_file"}; !reflect.DeepEqual(plan.Tools, wantTools) { - t.Fatalf("plan tools = %q, want the retained configured names", plan.Tools) - } - if wantCaps := []string{"cap-b", "cap-a"}; !reflect.DeepEqual(plan.Capabilities, wantCaps) { - t.Fatalf("plan capabilities = %q, want the selected order", plan.Capabilities) - } - if !plan.Readonly || plan.WriteDir != "/tmp/plan" { - t.Fatalf("plan permission constraints = %v %q, want the definition's readonly true and its write_dir", plan.Readonly, plan.WriteDir) - } - if !plan.Subagent || plan.Description != "planner" { - t.Fatalf("plan roster fields = subagent %v description %q, want the definition's subagent flag and description", plan.Subagent, plan.Description) + { + wantCaps := []string{"cap-b", "cap-a"} + testassert.Require(t, reflect.DeepEqual(plan.Capabilities, wantCaps), "plan capabilities = %q, want the selected order", plan.Capabilities) } + testassert.Require(t, plan.Readonly && plan.WriteDir == "/tmp/plan" && plan.Subagent && plan.Description == "planner", "plan permission constraints = %v %q, want the definition's readonly true and its write_dir; plan roster fields = subagent %v description %q, want the definition's subagent flag and description", plan.Readonly, plan.WriteDir, plan.Subagent, plan.Description) primaryView, err := harness.ResolveAgentType("primary", types) - if err != nil { - t.Fatalf("ResolveAgentType(primary): %v", err) - } - if primaryView.Readonly || primaryView.WriteDir != "" { - t.Fatalf("primary permission constraints = %v %q, want the unset defaults", primaryView.Readonly, primaryView.WriteDir) - } - if primaryView.Subagent || primaryView.Description != "" { - t.Fatalf("primary roster fields = subagent %v description %q, want the unset defaults", primaryView.Subagent, primaryView.Description) - } + testassert.NoError(t, err) + testassert.Require(t, !primaryView.Readonly && primaryView.WriteDir == "" && !primaryView.Subagent && primaryView.Description == "", "primary permission constraints = %v %q, want the unset defaults; primary roster fields = subagent %v description %q, want the unset defaults", primaryView.Readonly, primaryView.WriteDir, primaryView.Subagent, primaryView.Description) // WriteDir is trimmed exactly once at projection, preserving the legacy // whitespace-as-unset behavior: padded paths arrive clean and a // whitespace-only path arrives as unset. padded, err := assembleConfiguration(4, testConfigDocument, strings.Replace(testAgentsDocument, `"write_dir": "/tmp/plan"`, `"write_dir": " /tmp/plan "`, 1), testCapabilities()) - if err != nil { - t.Fatalf("padded configuration: %v", err) - } + testassert.NoError(t, err) paddedPlan, err := harness.ResolveAgentType("plan", padded.agentTypes()) - if err != nil { - t.Fatalf("ResolveAgentType(padded plan): %v", err) - } - if paddedPlan.WriteDir != "/tmp/plan" { - t.Fatalf("padded write_dir projected as %q, want the once-trimmed path", paddedPlan.WriteDir) - } + testassert.NoError(t, err) + testassert.Equal(t, paddedPlan.WriteDir, "/tmp/plan") blank, err := assembleConfiguration(5, testConfigDocument, strings.Replace(testAgentsDocument, `"write_dir": "/tmp/plan"`, `"write_dir": " "`, 1), testCapabilities()) - if err != nil { - t.Fatalf("blank write_dir configuration: %v", err) - } + testassert.NoError(t, err) blankPlan, err := harness.ResolveAgentType("plan", blank.agentTypes()) - if err != nil { - t.Fatalf("ResolveAgentType(blank plan): %v", err) - } - if blankPlan.WriteDir != "" { - t.Fatalf("whitespace-only write_dir projected as %q, want unset", blankPlan.WriteDir) - } + testassert.NoError(t, err) + testassert.Equal(t, blankPlan.WriteDir, "") // Two Agent types selected from one projection keep their own views: // explore inherits primary's tools with no capability selection of its // own, so declared-but-unselected IDs never appear in it. The inherited // default tools are intersected with the compiled tool universe. explore, err := harness.ResolveAgentType("explore", types) - if err != nil { - t.Fatalf("ResolveAgentType(explore): %v", err) - } - if explore.Model != plan.Model || explore.Capabilities != nil || !reflect.DeepEqual(explore.Tools, []string{"read_file"}) { - t.Fatalf("explore view = %+v, want the inherited model, an empty selection and the intersected default tools", explore) - } - if _, err := harness.ResolveAgentType("not-loaded", types); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("unknown selection = %v, want ErrInvalid", err) + testassert.NoError(t, err) + testassert.Require(t, explore.Model == plan.Model && explore.Capabilities == nil && reflect.DeepEqual(explore.Tools, []string{"read_file"}), "explore view = %+v, want the inherited model, an empty selection and the intersected default tools", explore) + { + _, err := harness.ResolveAgentType("not-loaded", types) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "unknown selection = %v, want ErrInvalid", err) } // Alias isolation: mutating a projection never feeds back into the @@ -290,12 +229,8 @@ func TestConfigurationAgentTypesProjection(t *testing.T) { plan.Tools[0] = "tampered" plan.Capabilities[0] = "tampered" again, err := harness.ResolveAgentType("plan", snapshot.agentTypes()) - if err != nil { - t.Fatalf("re-project: %v", err) - } - if again.Tools[0] != "plan_tool" || again.Capabilities[0] != "cap-b" { - t.Fatalf("projection aliases the snapshot storage: %+v", again) - } + testassert.NoError(t, err) + testassert.Require(t, again.Tools[0] == "plan_tool" && again.Capabilities[0] == "cap-b", "projection aliases the snapshot storage: %+v", again) } // TestInvocationAgentTypesReturnsOwnedRoster proves the public roster view: @@ -311,33 +246,28 @@ func TestInvocationAgentTypesReturnsOwnedRoster(t *testing.T) { for i, at := range roster { got[i] = at.Name } - if !reflect.DeepEqual(got, want) { - t.Fatalf("roster order = %q, want %q", got, want) - } - if plan := roster[len(roster)-1]; !plan.Subagent || plan.Description != "planner" { - t.Fatalf("plan roster entry = %+v, want the projected subagent fields", plan) + testassert.Require(t, reflect.DeepEqual(got, want), "roster order = %q, want %q", got, want) + { + plan := roster[len(roster)-1] + testassert.Require(t, plan.Subagent && plan.Description == "planner", "plan roster entry = %+v, want the projected subagent fields", plan) } // A caller's mutation — of an entry's own slices and of the returned // slice itself — never reaches the snapshot. - if len(roster[0].Tools) == 0 { - t.Fatalf("primary tools = %v, want a projected list to mutate", roster[0].Tools) - } + testassert.Require(t, len(roster[0].Tools) != 0, "primary tools = %v, want a projected list to mutate", roster[0].Tools) roster[0].Tools[0] = "tampered" clear(roster) again := inv.AgentTypes() - if len(again) != len(want) { - t.Fatalf("second roster length = %d, want %d", len(again), len(want)) - } - if again[0].Tools[0] != "read_file" { - t.Fatalf("second roster = %+v, want the pristine snapshot projection", again[0]) + testassert.Equal(t, len(again), len(want)) + testassert.Require(t, again[0].Tools[0] == "read_file", "second roster = %+v, want the pristine snapshot projection", again[0]) + { + plan := again[len(again)-1] + testassert.Require(t, plan.Subagent && plan.Description == "planner", "second plan entry = %+v, want the projected subagent fields", plan) } - if plan := again[len(again)-1]; !plan.Subagent || plan.Description != "planner" { - t.Fatalf("second plan entry = %+v, want the projected subagent fields", plan) - } - if empty := (Invocation{}).AgentTypes(); len(empty) != 0 { - t.Fatalf("zero Invocation roster = %v, want empty", empty) + { + empty := (Invocation{}).AgentTypes() + testassert.Require(t, len(empty) == 0, "zero Invocation roster = %v, want empty", empty) } } @@ -353,44 +283,43 @@ func TestConfigurationPermissionCapture(t *testing.T) { const globalDoc = `{"providers":{"prov":{"transport":{"base_url":"https://api.prov.test/v1","api_key_env":""},"models":{"m":{"name":"M","context_window":100}}}},"permissions":` + globalAllow + `}` snapshot, err := assembleConfiguration(2, globalDoc, `{"agent":{"model":"prov/m"}}`, nil) - if err != nil { - t.Fatalf("newConfiguration: %v", err) - } - if string(snapshot.permissions) != globalAllow { - t.Fatalf("captured global member = %q, want the raw bytes unchanged", snapshot.permissions) - } + testassert.NoError(t, err) + testassert.Require(t, string(snapshot.permissions) == globalAllow, "captured global member = %q, want the raw bytes unchanged", snapshot.permissions) wsID, err := config.WorkspacePermissionDirID("/ws") - if err != nil { - t.Fatalf("WorkspacePermissionDirID: %v", err) - } + testassert.NoError(t, err) snapshot.workspacePermissions = map[string]json.RawMessage{wsID: json.RawMessage(workspaceDeny)} // The Workspace rule decides over the global rule from the same capture. want := harness.ResolvePermissionPolicy(json.RawMessage(globalAllow), json.RawMessage(workspaceDeny)) - if got := snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, want) { - t.Fatalf("workspace policy = %#v, want the resolved workspace-over-global capture", got) + { + got := snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, want), "workspace policy = %#v, want the resolved workspace-over-global capture", got) } // An unknown Workspace resolves global-only. globalOnly := harness.ResolvePermissionPolicy(json.RawMessage(globalAllow), nil) - if got := snapshot.permissionPolicy("/other"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("unknown-workspace policy = %#v, want the global-only capture", got) + { + got := snapshot.permissionPolicy("/other") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "unknown-workspace policy = %#v, want the global-only capture", got) } // A malformed Workspace block discards both user levels for that // Workspace's resolutions: built-in fallback despite the valid global // member. snapshot.workspacePermissions = map[string]json.RawMessage{wsID: json.RawMessage(`{"rules":5}`)} var builtin harness.PermissionPolicy - if got := snapshot.permissionPolicy("/ws"); !reflect.DeepEqual(got, builtin) { - t.Fatalf("malformed-workspace policy = %#v, want the built-in fallback", got) + { + got := snapshot.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, builtin), "malformed-workspace policy = %#v, want the built-in fallback", got) } - if got := snapshot.permissionPolicy("/other"); !reflect.DeepEqual(got, globalOnly) { - t.Fatalf("other-workspace policy after the malformed sibling = %#v, want the untouched global capture", got) + { + got := snapshot.permissionPolicy("/other") + testassert.Require(t, reflect.DeepEqual(got, globalOnly), "other-workspace policy after the malformed sibling = %#v, want the untouched global capture", got) } // The retained legacy interactive permissions shape is not migrated: it // is malformed target policy, and the candidate still builds. legacy := testSnapshot(t) - if got := legacy.permissionPolicy("/ws"); !reflect.DeepEqual(got, builtin) { - t.Fatalf("legacy-member policy = %#v, want the built-in fallback", got) + { + got := legacy.permissionPolicy("/ws") + testassert.Require(t, reflect.DeepEqual(got, builtin), "legacy-member policy = %#v, want the built-in fallback", got) } } diff --git a/runtime/export_test.go b/runtime/export_test.go index 72d6d8ce..1d16b890 100644 --- a/runtime/export_test.go +++ b/runtime/export_test.go @@ -6,7 +6,6 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/catalog" - "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -130,14 +129,6 @@ func CreateSessionForTest(ctx context.Context, r *Runtime, workspace, agentType // SubmitForTest bridges one private regular user-message submission for the // external composition tests. It is absent from production builds. func SubmitForTest(ctx context.Context, r *Runtime, sessionID, operationID, text string) error { - return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: harness.MessageModeRegular, - }) - return err - }) + _, err := submitRawThroughRuntime(ctx, r, sessionID, operationID, text, harness.MessageModeRegular) + return err } diff --git a/runtime/integration_test.go b/runtime/integration_test.go index ca67c653..7d883c95 100644 --- a/runtime/integration_test.go +++ b/runtime/integration_test.go @@ -16,6 +16,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -28,16 +29,8 @@ import ( // parked execution, and restart repair on the same store. func assembledConfigDocument(endpoint, tag, label string) string { - return `{ - "providers": { - "prov": { - "transport": {"base_url": "` + endpoint + `", "api_key_env": "ASSEMBLED_TEST_KEY"}, - "discovery": false, - "models": {"m": {"name": "M", "context_window": 262144}} - } - }, - "plugins": {"hooks": {"tag": "` + tag + `"}, "depper": {"label": "` + label + `"}} -}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "ASSEMBLED_TEST_KEY", + `{"m":{"name":"M","context_window":262144}}`) + `},"plugins":{"hooks":{"tag":` + fixtureString(tag) + `},"depper":{"label":` + fixtureString(label) + `}}}` } // The "leaky" definition selects the Core storage export by ID: raw storage @@ -200,17 +193,13 @@ func writeSSE(w http.ResponseWriter, events ...string) { } func writeTextTurn(w http.ResponseWriter, text string) { - writeSSE(w, - `{"choices":[{"delta":{"role":"assistant","content":"`+text+`"},"finish_reason":null}]}`, - `{"choices":[{"delta":{},"finish_reason":"stop"}]}`, - ) + writeSSE(w, textTurnEvents(text)...) } -func writeToolCallTurn(w http.ResponseWriter, name, args string) { - writeSSE(w, - `{"choices":[{"delta":{"role":"assistant","tool_calls":[{"index":0,"id":"call-1","type":"function","function":{"name":"`+name+`","arguments":`+strconv.Quote(args)+`}}]},"finish_reason":null}]}`, - `{"choices":[{"delta":{},"finish_reason":"tool_calls"}]}`, - ) +// writeToolCallTurn writes one completed assistant turn publishing one tool +// call under the given call identity. +func writeToolCallTurn(w http.ResponseWriter, id, name, args string) { + writeSSE(w, toolCallTurnEvents(id, name, args)...) } func assembledPlugins(e *ownerEnv, store harness.Storage, hook *recordHook, ws *workspaceOpens) []Plugin { @@ -277,9 +266,7 @@ func operationRegisterStatus(t *testing.T, store harness.Storage, sessionID, ope func operationRegisterTerminal(t *testing.T, store harness.Storage, sessionID, operationID string) (harness.OperationState, string) { t.Helper() reg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: operationID}) - if err != nil { - t.Fatalf("ReadRegister(%s): %v", operationID, err) - } + testassert.Require(t, err == nil, "ReadRegister(%s): %v", operationID, err) var wire struct { State struct { Status harness.OperationState `json:"status"` @@ -288,9 +275,7 @@ func operationRegisterTerminal(t *testing.T, store harness.Storage, sessionID, o } `json:"terminal"` } `json:"state"` } - if err := json.Unmarshal(reg.Payload, &wire); err != nil { - t.Fatalf("decode operation register payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(reg.Payload, &wire)) var detail string if wire.State.Terminal != nil { detail = wire.State.Terminal.Detail @@ -342,57 +327,34 @@ func TestAssembledPhaseIntegration(t *testing.T) { // The combined scenario runs through the public production // construction: concrete preparation, custom plugin slice. openAssembled := func() (*Runtime, error) { - return Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: plugins}) + return e.open(ctx, plugins...) } // Raw storage is never exported into execution: the Core export ID is // outside the ordinary capability universe, so a definition selecting // it drops and the type is unknown at admission. leaky, err := openAssembled() - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) leakySession, err := leaky.createSession(ctx, e.dataDir, "leaky") - if err != nil { - t.Fatalf("createSession(leaky): %v", err) - } - if err := leaky.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: leakySession.Identity.SessionID, OperationID: "op-leaky", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "x"}}, Mode: harness.MessageModeRegular, - }) - return err - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit through a definition selecting the Core storage export = %v, want harness.ErrInvalid (raw storage must not be exported into execution)", err) - } - if err := leaky.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + _, err = submitRawThroughRuntime(ctx, leaky, leakySession.Identity.SessionID, "op-leaky", "x", harness.MessageModeRegular) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit through a definition selecting the Core storage export = %v, want harness.ErrInvalid (raw storage must not be exported into execution)", err) + testassert.NoError(t, leaky.Close(ctx)) r, err := openAssembled() - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) sat, err := r.Subscribe(1) - if err != nil { - t.Fatalf("Subscribe(saturated): %v", err) - } + testassert.NoError(t, err) healthy, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe(healthy): %v", err) - } + testassert.NoError(t, err) // Two Sessions in two distinct Workspaces run over their own scopes. wsA := filepath.Join(e.home, "int-wsa") wsB := filepath.Join(e.home, "int-wsb") sessionA, err := r.createSession(ctx, wsA, "integrated") - if err != nil { - t.Fatalf("createSession(A): %v", err) - } + testassert.NoError(t, err) sessionB, err := r.createSession(ctx, wsB, "integrated") - if err != nil { - t.Fatalf("createSession(B): %v", err) - } + testassert.NoError(t, err) // Baseline turn on B: the composed production turn calls the real // probe tool through the transport — one model request for the tool @@ -401,13 +363,11 @@ func TestAssembledPhaseIntegration(t *testing.T) { // settings. submitThroughRuntime(t, r, sessionB.Identity.SessionID, "op-b1", "hello") rec := awaitOperation(t, r, sessionB.Identity.SessionID, "op-b1", harness.OperationSuccess) - if got := ws.constructed(); !slices.Equal(got, []string{wsB}) { - t.Fatalf("Workspace scope constructions after the first turn = %v, want exactly the %q identity", got, wsB) - } - if rec.Admission.Execution.ConfigurationRevision != "1" || rec.Admission.Execution.SystemPrompt == "" || - !strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked") { - t.Fatalf("first turn record = %+v, want the hooked revision-1 success with the composed prompt", rec.Admission.Execution) + { + got := ws.constructed() + testassert.Require(t, slices.Equal(got, []string{wsB}), "Workspace scope constructions after the first turn = %v, want exactly the %q identity", got, wsB) } + testassert.Require(t, rec.Admission.Execution.ConfigurationRevision == "1" && rec.Admission.Execution.SystemPrompt != "" && strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked"), "first turn record = %+v, want the hooked revision-1 success with the composed prompt", rec.Admission.Execution) if !slices.Equal(toolNames(rec.Admission.Execution.Tools), []string{"probe"}) { t.Fatalf("first turn advertisement = %v, want the composed probe tool", toolNames(rec.Admission.Execution.Tools)) } @@ -417,9 +377,7 @@ func TestAssembledPhaseIntegration(t *testing.T) { if server.requests() != 2 { t.Fatalf("model requests = %d, want the tool-call turn and the follow-up", server.requests()) } - if auth := server.authAt(0); auth != "Bearer assembled-key-1" { - t.Fatalf("resolved credential on the wire = %q, want the resolved key's bearer form", auth) - } + testassert.Equal(t, server.authAt(0), "Bearer assembled-key-1") // The cleanup of the turn precedes the asynchronous Operation-close // publication: consume the healthy subscription until that exact @@ -428,9 +386,7 @@ func TestAssembledPhaseIntegration(t *testing.T) { var observed []Event for { event, ok := nextEvent(t, healthy) - if !ok { - t.Fatal("the healthy subscription closed before op-b1's Operation-close publication") - } + testassert.Require(t, ok, "the healthy subscription closed before op-b1's Operation-close publication") if body, closed := scopeClosedEvent(t, event); closed && operationScopeWorkspace(t, body.Scope) == wsB { recordCommittedEvent(t, &observed, event, sessionA.Identity.SessionID, sessionB.Identity.SessionID) break @@ -443,66 +399,52 @@ func TestAssembledPhaseIntegration(t *testing.T) { server.armGate() submitThroughRuntime(t, r, sessionA.Identity.SessionID, "op-a1", "parked") server.awaitRequests(3) - if got := ws.constructed(); !slices.Equal(got, []string{wsB, wsA}) { - t.Fatalf("Workspace scope constructions = %v, want one per distinct key in first-use order", got) + { + got := ws.constructed() + testassert.Require(t, slices.Equal(got, []string{wsB, wsA}), "Workspace scope constructions = %v, want one per distinct key in first-use order", got) } // The saturated subscriber filled on the first publication — a // passive fact now precedes every scope opening — and is removed at // the second, while the healthy one continues. first, ok := nextEvent(t, sat) - if !ok { - t.Fatal("saturated subscriber closed before its first buffered event") - } + testassert.Require(t, ok, "saturated subscriber closed before its first buffered event") switch eventKind(t, first) { case "configuration_changed", "scope_opened", "scope_closed", "session_changed", "text_delta", "tool_started", "tool_finished", "warning_changed": default: t.Fatalf("saturated subscriber first event = %s, outside the closed event set", eventJSON(t, first)) } - if _, ok := nextEvent(t, sat); ok { - t.Fatal("saturated subscriber still open at the second publication, want it removed and closed") + { + _, ok := nextEvent(t, sat) + testassert.Require(t, !ok, "saturated subscriber still open at the second publication, want it removed and closed") } sat.Close() // The queued next revision is buffered while the Operation is blocked. - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionA.Identity.SessionID, OperationID: "op-a2", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "queued"}}, Mode: harness.MessageModeQueued, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionQueued { - return fmt.Errorf("queued submit = %+v, want queued", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("queued submit: %v", err) - } + submitQueuedThroughRuntime(t, r, sessionA.Identity.SessionID, "op-a2", "queued") // Reload publishes the next revision over the blocked Operation. writeServiceFile(t, e.configPath, assembledConfigDocument(server.URL, "T2", "L2")) - if revision, err := r.Reload(ctx); err != nil || revision != "2" { - t.Fatalf("Reload over the blocked Operation = (%q, %v), want 2", revision, err) + { + revision, err := r.Reload(ctx) + testassert.Require(t, err == nil && revision == "2", "Reload over the blocked Operation = (%q, %v), want 2", revision, err) } // Release: the blocked Operation completes on its captured revision 1. server.releaseGate() awaitOperation(t, r, sessionA.Identity.SessionID, "op-a1", harness.OperationSuccess) - if rec := readOperation(t, r, sessionA.Identity.SessionID, "op-a1"); rec.Admission.Execution.ConfigurationRevision != "1" || - !strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked") { - t.Fatalf("blocked Operation record = %+v, want the revision-1 hooked success", rec.Admission.Execution) + { + rec := readOperation(t, r, sessionA.Identity.SessionID, "op-a1") + testassert.Require(t, rec.Admission.Execution.ConfigurationRevision == "1" && strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked"), "blocked Operation record = %+v, want the revision-1 hooked success", rec.Admission.Execution) } // The queued delivery captures the next revision: new hook settings // and the dependency's own settings from the same revision, while the // old capture keeps its content. recA2 := awaitOperation(t, r, sessionA.Identity.SessionID, "op-a2", harness.OperationSuccess) - if recA2.Admission.Execution.ConfigurationRevision != "2" { - t.Fatalf("queued Operation record = %+v, want the revision-2 success", recA2.Admission.Execution) - } - if got := hook.calls(); !slices.Equal(got, []hookCall{{revision: "1", tag: "T1"}, {revision: "1", tag: "T1"}, {revision: "2", tag: "T2"}}) { - t.Fatalf("hook calls = %v, want the configured tag of each call's own revision", got) + testassert.Require(t, recA2.Admission.Execution.ConfigurationRevision == "2", "queued Operation record = %+v, want the revision-2 success", recA2.Admission.Execution) + { + got := hook.calls() + testassert.Require(t, slices.Equal(got, []hookCall{{revision: "1", tag: "T1"}, {revision: "1", tag: "T1"}, {revision: "2", tag: "T2"}}), "hook calls = %v, want the configured tag of each call's own revision", got) } if n := countEvents(e.events.all(), "work:L2:base"); n != 1 { t.Fatalf("dependency invocations reading revision-2 settings = %d, want 1: %v", n, e.events.all()) @@ -512,9 +454,7 @@ func TestAssembledPhaseIntegration(t *testing.T) { closedOps := 0 for closedOps < 2 { event, ok := nextEvent(t, healthy) - if !ok { - t.Fatal("the healthy subscription closed before the queued delivery settled") - } + testassert.Require(t, ok, "the healthy subscription closed before the queued delivery settled") if body, closed := scopeClosedEvent(t, event); closed && operationScopeWorkspace(t, body.Scope) == wsA { closedOps++ } @@ -530,21 +470,9 @@ func TestAssembledPhaseIntegration(t *testing.T) { // A submit racing the predecessor's post-terminal drain is buffered // and delivered by that drain, so either disposition starts this // execution. - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionA.Identity.SessionID, OperationID: "op-a3", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "parked"}}, Mode: harness.MessageModeRegular, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionAdmitted && res.Disposition != harness.DispositionSteering { - return fmt.Errorf("submit op-a3 = %+v, want admitted or buffered steering", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("Submit(op-a3): %v", err) - } + parked, err := submitRawThroughRuntime(ctx, r, sessionA.Identity.SessionID, "op-a3", "parked", harness.MessageModeRegular) + testassert.NoError(t, err) + testassert.Require(t, parked.Disposition == harness.DispositionAdmitted || parked.Disposition == harness.DispositionSteering, "submit op-a3 = %+v, want admitted or buffered steering", parked.Disposition) server.awaitRequests(before + 1) closeDone := make(chan error, 1) go func() { closeDone <- r.Close(ctx) }() @@ -555,15 +483,11 @@ func TestAssembledPhaseIntegration(t *testing.T) { } select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("Close never converged after the parked execution settled") } - if status := operationRegisterStatus(t, store, sessionA.Identity.SessionID, "op-a3"); status != harness.OperationInterruption { - t.Fatalf("parked Operation terminal after Close = %q, want the committed interruption", status) - } + testassert.Equal(t, operationRegisterStatus(t, store, sessionA.Identity.SessionID, "op-a3"), harness.OperationInterruption) server.releaseGate() // The healthy observer saw every committed transition in publication @@ -608,22 +532,16 @@ func TestAssembledPhaseIntegration(t *testing.T) { // admission proceeds on the repaired state. seededSession, _ := seedRunningOperation(t, store, filepath.Join(e.home, "int-reseed")) r2, err := openAssembled() - if err != nil { - t.Fatalf("reopen: %v", err) - } - if rec := readOperation(t, r2, seededSession, "seed-op"); rec.State.Status != harness.OperationInterruption || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "Operation interrupted by Runtime loss." { - t.Fatalf("restarted Operation state = %+v, want the terminal interruption repair", rec.State) + testassert.NoError(t, err) + { + rec := readOperation(t, r2, seededSession, "seed-op") + testassert.Require(t, rec.State.Status == harness.OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "Operation interrupted by Runtime loss.", "restarted Operation state = %+v, want the terminal interruption repair", rec.State) } next, err := r2.createSession(ctx, wsB, "integrated") - if err != nil { - t.Fatalf("createSession after repair: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r2, next.Identity.SessionID, "op-after-repair", "continue") awaitOperation(t, r2, next.Identity.SessionID, "op-after-repair", harness.OperationSuccess) - if err := r2.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r2.Close(ctx)) }) } @@ -643,9 +561,7 @@ func recordCommittedEvent(t *testing.T, observed *[]Event, event Event, sessionI // carries the runtime scope, a session event the real Session's // scope. body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) == "runtime" { // the global reload groups } else if scopeNamesRealSession(t, body.Scope, sessionIDs...) { @@ -653,14 +569,13 @@ func recordCommittedEvent(t *testing.T, observed *[]Event, event Event, sessionI } else { t.Fatalf("warning event scope = %+v, want the runtime scope or a real session", body.Scope) } - if _, err := strconv.ParseUint(body.WarningsRevision.Revision, 10, 64); err != nil { - t.Fatalf("warning event revision %q is not a decimal integer", body.WarningsRevision.Revision) + { + _, err := strconv.ParseUint(body.WarningsRevision.Revision, 10, 64) + testassert.Require(t, err == nil, "warning event revision %q is not a decimal integer", body.WarningsRevision.Revision) } case "session_changed": body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) if !scopeNamesRealSession(t, body.Scope, sessionIDs...) { invalidation, _ := scopeSessionIdentity(t, body.Scope) t.Fatalf("passive invalidation for %v, want a real session", invalidation) @@ -684,9 +599,7 @@ func scopeClosedEvent(t *testing.T, event Event) (protocol.ScopeEvent, bool) { return protocol.ScopeEvent{}, false } body, err := event.AsScopeEvent() - if err != nil { - t.Fatalf("scope event body: %v", err) - } + testassert.NoError(t, err) return body, true } @@ -726,9 +639,7 @@ func operationScopeWorkspace(t *testing.T, scope protocol.Scope) string { return "" } body, err := scope.AsOperationScope() - if err != nil { - t.Fatalf("operation scope: %v", err) - } + testassert.NoError(t, err) return deref(body.Workspace) } diff --git a/runtime/jobs_plugin_config_test.go b/runtime/jobs_plugin_config_test.go index f62b80d6..54144898 100644 --- a/runtime/jobs_plugin_config_test.go +++ b/runtime/jobs_plugin_config_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/runtime" ) @@ -40,9 +41,7 @@ func TestComposedJobsPluginPublication(t *testing.T) { e := newComposeEnv(t) writeToolsConfig(t, e.configPath, accept) r, err := runtime.OpenForTest(ctx, e.dataDir, e.configPath, builtin.Plugins()) - if err != nil { - t.Fatalf("OpenForTest over the shipped five with a valid jobs section: %v", err) - } + testassert.NoError(t, err) if rev, err := r.Reload(ctx); err != nil || rev != "2" { _ = r.Close(ctx) t.Fatalf("first Reload = (%q, %v), want revision 2", rev, err) @@ -78,7 +77,5 @@ func TestComposedJobsPluginPublication(t *testing.T) { _ = r.Close(ctx) t.Fatalf("Reload after the failures = (%q, %v), want generation 3: failed publications published nothing", rev, err) } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) } diff --git a/runtime/maintenance_test.go b/runtime/maintenance_test.go index ed6590cb..7ca678e4 100644 --- a/runtime/maintenance_test.go +++ b/runtime/maintenance_test.go @@ -24,6 +24,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -32,7 +33,8 @@ import ( // stays: the sweep rows that admit no model work never reach it, and the two // startup-probe rows below build their own local-endpoint document. func ownerSweepDocument(sessions string) string { - return `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":4096}}}},"sessions":` + sessions + `}` + return `{"providers":{"prov":` + nominalProvMember("https://prov.test/v1", "", + `{"m":{"name":"M","context_window":4096}}`) + `},"sessions":` + sessions + `}` } // ownerSweepProbedDocument builds the sweep fixture's local-endpoint document @@ -40,7 +42,8 @@ func ownerSweepDocument(sessions string) string { // admission reaches the selected preparation hook and the local model server // instead of failing for absent metadata. func ownerSweepProbedDocument(endpoint, sessions string) string { - return `{"providers":{"prov":{"transport":{"base_url":"` + endpoint + `","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":262144,"max_output_tokens":4096}}}},"plugins":{"hooks":{"tag":"T1"}},"sessions":` + sessions + `}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"m":{"name":"M","context_window":262144,"max_output_tokens":4096}}`) + `},"plugins":{"hooks":{"tag":"T1"}},"sessions":` + sessions + `}` } // ownerSweepAgentsDocument selects the local model and the preparation hook @@ -59,9 +62,7 @@ func assertNoOperationRegisters(t *testing.T, store harness.Storage, sessionID s t.Fatalf("ReadRegisters(%s): %v", sessionID, err) } for _, reg := range regs { - if reg.Key.Kind == harness.RegisterOperation { - t.Fatalf("session %s carries the Operation register %q, want no admission", sessionID, reg.Key.OperationID) - } + testassert.Require(t, reg.Key.Kind != harness.RegisterOperation, "session %s carries the Operation register %q, want no admission", sessionID, reg.Key.OperationID) } } @@ -71,9 +72,7 @@ func captureSweepStderr(t *testing.T) func() string { t.Helper() old := os.Stderr f, err := os.CreateTemp(t.TempDir(), "sweep-stderr-*") - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) os.Stderr = f t.Cleanup(func() { os.Stderr = old @@ -319,23 +318,10 @@ func (c *deadlineContext) Err() error { func (c *deadlineContext) expire() { close(c.done) } -func readSweptSession(t *testing.T, r *Runtime, sessionID string) (harness.SessionHeader, error) { - t.Helper() - var rec harness.SessionHeader - err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - var err error - rec, err = h.ReadSessionHeader(ctx, sessionID) - return err - }) - return rec, err -} - func readSweepRegister(t *testing.T, store harness.Storage, sessionID string) harness.Register { t.Helper() reg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("read session register %q: %v", sessionID, err) - } + testassert.Require(t, err == nil, "read session register %q: %v", sessionID, err) return reg } @@ -346,38 +332,26 @@ func rewriteSessionRegister(t *testing.T, store harness.Storage, sessionID strin ctx := context.Background() key := harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession} reg, err := store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("read register to rewrite: %v", err) - } + testassert.NoError(t, err) var obj map[string]json.RawMessage - if err := json.Unmarshal(reg.Payload, &obj); err != nil { - t.Fatalf("unmarshal session payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(reg.Payload, &obj)) var state map[string]json.RawMessage - if err := json.Unmarshal(obj["state"], &state); err != nil { - t.Fatalf("unmarshal state section: %v", err) - } + testassert.NoError(t, json.Unmarshal(obj["state"], &state)) mutate(state) obj["state"] = mustSweepJSON(t, state) - if err := store.Transact(ctx, func(tx harness.Transaction) error { + testassert.NoError(t, store.Transact(ctx, func(tx harness.Transaction) error { _, err := tx.ReplaceRegister(key, reg.Revision, mustSweepJSON(t, obj)) return err - }); err != nil { - t.Fatalf("replace register: %v", err) - } + })) out, err := store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("reread rewritten register: %v", err) - } + testassert.NoError(t, err) return out } func mustSweepJSON(t *testing.T, value any) json.RawMessage { t.Helper() data, err := json.Marshal(value) - if err != nil { - t.Fatalf("marshal: %v", err) - } + testassert.NoError(t, err) return data } @@ -394,18 +368,12 @@ func seedStaleSession(t *testing.T, store harness.Storage, workspace string, sta return harness.PreparedExecution{}, errors.New("seed harness prepares nothing") }, }) - if err != nil { - t.Fatalf("seed harness.New: %v", err) - } + testassert.NoError(t, err) rec, err := h.CreateSession(ctx, harness.CreateSessionRequest{Workspace: workspace, AgentType: "seeded"}) - if err != nil { - t.Fatalf("seed CreateSession: %v", err) - } + testassert.NoError(t, err) rewriteSessionRegister(t, store, rec.Identity.SessionID, func(state map[string]json.RawMessage) { var last time.Time - if err := json.Unmarshal(state["last_activity"], &last); err != nil { - t.Fatalf("decode last_activity: %v", err) - } + testassert.NoError(t, json.Unmarshal(state["last_activity"], &last)) state["last_activity"] = mustSweepJSON(t, last.Add(-stale)) }) return rec.Identity.SessionID @@ -430,29 +398,17 @@ func TestMaintenanceInitialPassRunsAtStartup(t *testing.T) { hookPlugin(hook), shortScopeProbe("probe", ScopeOperation, &opOpens), shortScopeProbe("probe-agent", ScopeAgent, &agOpens)) - if err != nil { - t.Fatalf("open: %v", err) - } - rec, err := readSweptSession(t, r, seeded) - if err != nil { - t.Fatalf("ReadSession: %v", err) - } - if rec.Lifecycle != harness.LifecycleArchived || rec.ArchivedAt == nil { - t.Fatalf("seeded Session after startup = %+v, want the initial pass to have archived it", rec) - } - if got := e.server.requests(); got != 0 { - t.Fatalf("HTTP requests during startup = %d, want the sweep to admit no model work", got) - } - if calls := len(hook.calls()); calls != 0 { - t.Fatalf("preparation hook calls during startup = %d, want the sweep to admit no model work", calls) - } - if ops, ags := opOpens.Load(), agOpens.Load(); ops != 0 || ags != 0 { - t.Fatalf("short-scope factory opens during startup = %d/%d, want the sweep to admit no model work", ops, ags) + testassert.NoError(t, err) + rec := headerThroughRuntime(t, r, seeded) + testassert.Require(t, rec.Lifecycle == harness.LifecycleArchived && rec.ArchivedAt != nil, "seeded Session after startup = %+v, want the initial pass to have archived it", rec) + testassert.Equal(t, e.server.requests(), 0) + testassert.Equal(t, len(hook.calls()), 0) + { + ops, ags := opOpens.Load(), agOpens.Load() + testassert.Require(t, ops == 0 && ags == 0, "short-scope factory opens during startup = %d/%d, want the sweep to admit no model work", ops, ags) } assertNoOperationRegisters(t, store, seeded) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) }) } @@ -472,30 +428,22 @@ func TestMaintenanceControlledTicksSweepUnderTheCurrentPolicy(t *testing.T) { wrapped := newSweepStore(store) hook := &recordHook{name: "hook.first", events: &traceLog{}} var opOpens, agOpens atomic.Int64 - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped), hookPlugin(hook), + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped), hookPlugin(hook), shortScopeProbe("probe", ScopeOperation, &opOpens), shortScopeProbe("probe-agent", ScopeAgent, &agOpens)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } - if got := wrapped.listCount(); got != 1 { - t.Fatalf("storage lists after construction with auto_archive=false = %d, want recovery's single list only: false must disable the whole sweep", got) - } + testassert.NoError(t, err) + testassert.Equal(t, wrapped.listCount(), 1) created, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "sweep"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) id := created.Identity.SessionID stderr := captureSweepStderr(t) archiveBoundary := created.State.LastActivity.Add(24 * time.Hour) archivedAt := archiveBoundary.Add(time.Nanosecond) writeServiceFile(t, e.configPath, ownerSweepProbedDocument(e.server.URL, `{"archive_after_days":1,"delete_after_archive_days":1}`)) - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } // Each send's rendezvous proves the previous pass fully converged, so @@ -503,40 +451,27 @@ func TestMaintenanceControlledTicksSweepUnderTheCurrentPolicy(t *testing.T) { sendTick(t, ticks, archiveBoundary) // exact archive boundary: no transition sendTick(t, ticks, archivedAt) // one nanosecond past: archive, stamped with the tick time waitWritten(t, wrapped) // the archive committed; the boundary pass converged at the previous rendezvous - archived, err := readSweptSession(t, r, id) - if err != nil { - t.Fatalf("ReadSession after the archive ticks: %v", err) - } - if archived.Lifecycle != harness.LifecycleArchived || archived.Revision.DurableRevision != created.Revision+1 { - t.Fatalf("Session after the two archive-boundary ticks = %+v rev %d, want exactly one archive at revision %d", archived, archived.Revision.DurableRevision, created.Revision+1) - } - if archived.ArchivedAt == nil || !archived.ArchivedAt.Equal(archivedAt) { - t.Fatalf("sweep stamped %v, want the archive tick's explicit time %v", archived.ArchivedAt, archivedAt) - } + archived := headerThroughRuntime(t, r, id) + testassert.Require(t, archived.Lifecycle == harness.LifecycleArchived && archived.Revision.DurableRevision == created.Revision+1 && archived.ArchivedAt != nil && archived.ArchivedAt.Equal(archivedAt), "Session after the two archive-boundary ticks = %+v rev %d, want exactly one archive at revision %d; sweep stamped %v, want the archive tick's explicit time %v", archived, archived.Revision.DurableRevision, created.Revision+1, archived.ArchivedAt, archivedAt) sendTick(t, ticks, archivedAt.Add(24*time.Hour)) // exact delete boundary sendTick(t, ticks, archivedAt.Add(24*time.Hour+time.Nanosecond)) // one nanosecond past: delete waitWritten(t, wrapped) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } - if _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("Session register after the delete-boundary pair = err %v, want it past the boundary deleted", err) - } - if got := wrapped.listCount(); got != 5 { - t.Fatalf("storage lists = %d, want recovery plus exactly the four controlled passes", got) - } - if got := e.server.requests(); got != 0 { - t.Fatalf("HTTP requests = %d, want none: the sweep admits no model work", got) - } - if calls := len(hook.calls()); calls != 0 { - t.Fatalf("preparation hook calls = %d, want none: the sweep admits no model work", calls) - } - if ops, ags := opOpens.Load(), agOpens.Load(); ops != 0 || ags != 0 { - t.Fatalf("short-scope factory opens = %d/%d, want none: the sweep admits no model work", ops, ags) + testassert.NoError(t, r.Close(context.Background())) + { + _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "Session register after the delete-boundary pair = err %v, want it past the boundary deleted", err) + } + testassert.Equal(t, wrapped.listCount(), 5) + testassert.Equal(t, e.server.requests(), 0) + testassert.Equal(t, len(hook.calls()), 0) + { + ops, ags := opOpens.Load(), agOpens.Load() + testassert.Require(t, ops == 0 && ags == 0, "short-scope factory opens = %d/%d, want none: the sweep admits no model work", ops, ags) } assertNoOperationRegisters(t, wrapped, id) - if out := stderr(); strings.Contains(out, "lightcode: sweep:") { - t.Fatalf("boundary passes reported diagnostics: %q", out) + { + out := stderr() + testassert.Require(t, !strings.Contains(out, "lightcode: sweep:"), "boundary passes reported diagnostics: %q", out) } }) } @@ -558,36 +493,25 @@ func TestMaintenanceFailedPassReportsAndWaitsForTheNextTick(t *testing.T) { e := newOwnerEnv(t) writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped)) + testassert.NoError(t, err) created, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "sweep"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) stderr := captureSweepStderr(t) wrapped.armFailOnce(tc.failure) tick := created.State.LastActivity.Add(100 * time.Hour) sendTick(t, ticks, tick) // this pass lists, fails, and reports sendTick(t, ticks, tick) // the rendezvous proves the failed pass converged; this pass sweeps again waitWritten(t, wrapped) // the later tick committed the archive - if rec, err := readSweptSession(t, r, created.Identity.SessionID); err != nil || rec.Lifecycle != harness.LifecycleArchived { - t.Fatalf("Session after the later tick = %+v err %v, want the failed pass to leave no lasting damage", rec, err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + rec := headerThroughRuntime(t, r, created.Identity.SessionID) + testassert.Require(t, rec.Lifecycle == harness.LifecycleArchived, "Session after the later tick = %+v, want the failed pass to leave no lasting damage", rec) + testassert.NoError(t, r.Close(context.Background())) out := stderr() - if n := strings.Count(out, "lightcode: sweep: "+tc.failure.Error()); n != 1 { - t.Fatalf("stderr sweep diagnostics = %d in %q, want the failed pass reported exactly once", n, out) - } - if got := wrapped.listCount(); got != 4 { - t.Fatalf("storage lists = %d, want recovery, the initial pass, one failing pass, and one later pass: no immediate retry", got) + { + n := strings.Count(out, "lightcode: sweep: "+tc.failure.Error()) + testassert.Require(t, n == 1, "stderr sweep diagnostics = %d in %q, want the failed pass reported exactly once", n, out) } + testassert.Equal(t, wrapped.listCount(), 4) }) }) } @@ -605,17 +529,10 @@ func TestMaintenanceShutdownJoinsTheBlockedPassBeforeStorageTeardown(t *testing. e := newOwnerEnv(t) writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped)) + testassert.NoError(t, err) created, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "sweep"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) stderr := captureSweepStderr(t) wrapped.armBlock() sendTick(t, ticks, created.State.LastActivity.Add(100*time.Hour)) @@ -630,8 +547,9 @@ func TestMaintenanceShutdownJoinsTheBlockedPassBeforeStorageTeardown(t *testing. t.Fatal("shutdown converged while a sweep pass was blocked inside a storage transaction") case <-time.After(200 * time.Millisecond): } - if events := eventNames(e.events.all()); slices.Contains(events, "close:core") { - t.Fatal("the Core storage closed while a sweep pass was still admitted") + { + events := eventNames(e.events.all()) + testassert.Require(t, !slices.Contains(events, "close:core"), "the Core storage closed while a sweep pass was still admitted") } wrapped.releaseBlock() select { @@ -639,11 +557,10 @@ func TestMaintenanceShutdownJoinsTheBlockedPassBeforeStorageTeardown(t *testing. case <-time.After(10 * time.Second): t.Fatal("the shutdown never joined the released sweep pass") } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close = %v, want the joined shutdown to succeed with the pass error kept out of the owner result", err) - } - if out := stderr(); strings.Contains(out, "lightcode: sweep:") { - t.Fatalf("the canceled pass reported to stderr: %q, want cancellation joined silently", out) + testassert.NoError(t, r.Close(context.Background())) + { + out := stderr() + testassert.Require(t, !strings.Contains(out, "lightcode: sweep:"), "the canceled pass reported to stderr: %q, want cancellation joined silently", out) } e.assertLockReleased(t) } @@ -664,18 +581,11 @@ func TestMaintenanceShutdownJoinsTheBlockedPassBeforeStorageTeardown(t *testing. e := newOwnerEnv(t) writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks ctx := newDeadlineContext() - r, err := open(ctx, opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(ctx, e.storagePlugin(wrapped)) + testassert.NoError(t, err) created, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "sweep"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) stderr := captureSweepStderr(t) wrapped.armBlock() sendTick(t, ticks, created.State.LastActivity.Add(100*time.Hour)) @@ -698,11 +608,10 @@ func TestMaintenanceShutdownJoinsTheBlockedPassBeforeStorageTeardown(t *testing. case <-time.After(10 * time.Second): t.Fatal("the deadline-started shutdown never joined the released sweep pass") } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close = %v, want the joined deadline cancellation to succeed with the pass error kept out of the owner result", err) - } - if out := stderr(); strings.Contains(out, "lightcode: sweep:") { - t.Fatalf("the deadline-canceled pass reported to stderr: %q, want deadline expiry silent like cancellation", out) + testassert.NoError(t, r.Close(context.Background())) + { + out := stderr() + testassert.Require(t, !strings.Contains(out, "lightcode: sweep:"), "the deadline-canceled pass reported to stderr: %q, want deadline expiry silent like cancellation", out) } e.assertLockReleased(t) }) @@ -720,17 +629,10 @@ func TestMaintenanceUnrelatedFailureIsQuietOnceShutdownIsObserved(t *testing.T) e := newOwnerEnv(t) writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped)) + testassert.NoError(t, err) created, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "sweep"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) stderr := captureSweepStderr(t) unrelated := errors.New("test unrelated store failure") wrapped.armFailAfterRelease(unrelated) @@ -752,11 +654,10 @@ func TestMaintenanceUnrelatedFailureIsQuietOnceShutdownIsObserved(t *testing.T) case <-time.After(10 * time.Second): t.Fatal("the shutdown never joined the released sweep pass") } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close = %v, want the joined shutdown to succeed with the pass error kept out of the owner result", err) - } - if out := stderr(); strings.Contains(out, "lightcode: sweep:") { - t.Fatalf("the shutdown-observed unrelated failure reported to stderr: %q, want silence once shutdown is observed", out) + testassert.NoError(t, r.Close(context.Background())) + { + out := stderr() + testassert.Require(t, !strings.Contains(out, "lightcode: sweep:"), "the shutdown-observed unrelated failure reported to stderr: %q, want silence once shutdown is observed", out) } e.assertLockReleased(t) }) @@ -772,20 +673,15 @@ func TestMaintenanceGateRejectionIsQuiet(t *testing.T) { writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) r, err := e.open(context.Background(), e.storagePlugin(wrapped)) - if err != nil { - t.Fatalf("open: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(context.Background())) stderr := captureSweepStderr(t) before := wrapped.listCount() r.runSweepPass(context.Background(), time.Now()) - if got := wrapped.listCount(); got != before { - t.Fatalf("storage lists = %d, want the gate rejection to leave the store untouched (was %d)", got, before) - } - if out := stderr(); strings.Contains(out, "lightcode: sweep:") { - t.Fatalf("the gate-rejected pass reported to stderr: %q, want ErrClosed quiet", out) + testassert.Equal(t, wrapped.listCount(), before) + { + out := stderr() + testassert.Require(t, !strings.Contains(out, "lightcode: sweep:"), "the gate-rejected pass reported to stderr: %q, want ErrClosed quiet", out) } }) } @@ -799,21 +695,12 @@ func TestMaintenanceLeavesRunningSessionsAndAdmitsNoModel(t *testing.T) { e := newOwnerEnv(t) writeServiceFile(t, e.configPath, ownerSweepProbedDocument(e.server.URL, `{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped), hookPlugin(&recordHook{name: "hook.first", events: &traceLog{}})) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped), hookPlugin(&recordHook{name: "hook.first", events: &traceLog{}})) + testassert.NoError(t, err) busy, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "busy"), "solo") - if err != nil { - t.Fatalf("createSession busy: %v", err) - } + testassert.NoError(t, err) idle, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "idle"), "solo") - if err != nil { - t.Fatalf("createSession idle: %v", err) - } + testassert.NoError(t, err) gate := make(chan struct{}) e.server.setHold(gate) submitThroughRuntime(t, r, busy.Identity.SessionID, "op-busy", "work") @@ -827,22 +714,16 @@ func TestMaintenanceLeavesRunningSessionsAndAdmitsNoModel(t *testing.T) { sendTick(t, ticks, tick) sendTick(t, ticks, tick) // returns once the first pass fully converged after := readSweepRegister(t, wrapped, busy.Identity.SessionID) - if after.Revision != before.Revision || !bytes.Equal(after.Payload, before.Payload) { - t.Fatalf("the sweep changed the running Session's register (%d -> %d), want it left unchanged", before.Revision, after.Revision) - } - if rec, err := readSweptSession(t, r, idle.Identity.SessionID); err != nil || rec.Lifecycle != harness.LifecycleArchived { - t.Fatalf("idle sibling after the sweep = %+v err %v, want it archived", rec, err) - } - if got := e.server.requests(); got != 1 { - t.Fatalf("HTTP requests = %d, want only the explicit submit: the sweep admitted no model work", got) - } + testassert.Require(t, after.Revision == before.Revision && bytes.Equal(after.Payload, before.Payload), "the sweep changed the running Session's register (%d -> %d), want it left unchanged", before.Revision, after.Revision) + rec := headerThroughRuntime(t, r, idle.Identity.SessionID) + testassert.Require(t, rec.Lifecycle == harness.LifecycleArchived, "idle sibling after the sweep = %+v, want it archived", rec) + testassert.Equal(t, e.server.requests(), 1) close(gate) - if op := awaitOperation(t, r, busy.Identity.SessionID, "op-busy", harness.OperationSuccess); op.State.Status != harness.OperationSuccess { - t.Fatalf("Operation during the sweep = %+v, want the untouched success terminal", op.State) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + op := awaitOperation(t, r, busy.Identity.SessionID, "op-busy", harness.OperationSuccess) + testassert.Require(t, op.State.Status == harness.OperationSuccess, "Operation during the sweep = %+v, want the untouched success terminal", op.State) } + testassert.NoError(t, r.Close(context.Background())) }) } @@ -857,21 +738,12 @@ func TestMaintenancePassSweepsValidSiblingsAroundCorruption(t *testing.T) { e := newOwnerEnv(t) writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped)) + testassert.NoError(t, err) corrupted, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "bad"), "solo") - if err != nil { - t.Fatalf("createSession bad: %v", err) - } + testassert.NoError(t, err) valid, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "good"), "solo") - if err != nil { - t.Fatalf("createSession good: %v", err) - } + testassert.NoError(t, err) bad := rewriteSessionRegister(t, wrapped, corrupted.Identity.SessionID, func(state map[string]json.RawMessage) { state["lifecycle"] = json.RawMessage(`"bogus"`) }) @@ -884,47 +756,35 @@ func TestMaintenancePassSweepsValidSiblingsAroundCorruption(t *testing.T) { const malformed = "corrupt-id" // non-empty, not the durable 32-hex shape const preCorrupt = "fedcba9876543210fedcba9876543210" // valid hex, corrupt register for _, row := range []string{malformed, preCorrupt} { - if err := wrapped.Transact(context.Background(), func(tx harness.Transaction) error { - _, err := tx.InsertRegister(harness.RegisterDraft{ - Key: harness.RegisterKey{SessionID: row, Kind: harness.RegisterSession}, - Payload: json.RawMessage(`{}`), - }) - return err - }); err != nil { - t.Fatalf("plant row %q: %v", row, err) - } + lifecycleInsertRegister(t, wrapped, harness.RegisterKey{SessionID: row, Kind: harness.RegisterSession}, `{}`) } stderr := captureSweepStderr(t) tick := valid.State.LastActivity.Add(100 * time.Hour) sendTick(t, ticks, tick) sendTick(t, ticks, tick) - if rec, err := readSweptSession(t, r, valid.Identity.SessionID); err != nil || rec.Lifecycle != harness.LifecycleArchived { - t.Fatalf("valid sibling after the sweep = %+v err %v, want it archived", rec, err) - } + rec := headerThroughRuntime(t, r, valid.Identity.SessionID) + testassert.Require(t, rec.Lifecycle == harness.LifecycleArchived, "valid sibling after the sweep = %+v, want it archived", rec) // Shutdown still converges — Close returns, joining the latched // corruption of the planted corrupt Session as reporting, not as a // failure to converge. err = r.Close(context.Background()) - if err == nil { - t.Fatal("Close = nil, want the joined corruption error for the planted corrupt Session") - } - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("Close error = %v, want the corruption-class error", err) - } + testassert.Require(t, err != nil, "Close = nil, want the joined corruption error for the planted corrupt Session") + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "Close error = %v, want the corruption-class error", err) var corrupt *harness.CorruptionError - if !errors.As(err, &corrupt) || corrupt.SessionID != corrupted.Identity.SessionID { - t.Fatalf("Close error = %v, want CorruptionError for session %s", err, corrupted.Identity.SessionID) - } - if after := readSweepRegister(t, wrapped, corrupted.Identity.SessionID); after.Revision != bad.Revision || !bytes.Equal(after.Payload, bad.Payload) { - t.Fatalf("the sweep changed the corrupt register (%d -> %d), want it left in place", bad.Revision, after.Revision) + testassert.Require(t, errors.As(err, &corrupt) && corrupt.SessionID == corrupted.Identity.SessionID, "Close error = %v, want CorruptionError for session %s", err, corrupted.Identity.SessionID) + { + after := readSweepRegister(t, wrapped, corrupted.Identity.SessionID) + testassert.Require(t, after.Revision == bad.Revision && bytes.Equal(after.Payload, bad.Payload), "the sweep changed the corrupt register (%d -> %d), want it left in place", bad.Revision, after.Revision) } for _, id := range []string{malformed, preCorrupt} { - if _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); err != nil { - t.Fatalf("omitted row %s after the pass = err %v, want it left in place", id, err) + { + _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, err == nil, "omitted row %s after the pass = err %v, want it left in place", id, err) } } - if out := stderr(); strings.Contains(out, "lightcode: sweep:") { - t.Fatalf("the corrupt sibling was reported as a pass failure: %q", out) + { + out := stderr() + testassert.Require(t, !strings.Contains(out, "lightcode: sweep:"), "the corrupt sibling was reported as a pass failure: %q", out) } }) } @@ -939,30 +799,18 @@ func TestMaintenanceNonpositiveThresholdsDisableTheirTransitions(t *testing.T) { seeded := seedStaleSession(t, store, filepath.Join(e.dataDir, "seeded"), 100*time.Hour) wrapped := newSweepStore(store) r, err := e.open(context.Background(), e.storagePlugin(wrapped)) - if err != nil { - t.Fatalf("open: %v", err) - } - if got := wrapped.listCount(); got != 2 { - t.Fatalf("storage lists after startup = %d, want recovery plus exactly one initial pass: zero thresholds still schedule the sweep", got) - } + testassert.NoError(t, err) + testassert.Equal(t, wrapped.listCount(), 2) var payload struct { State struct { Lifecycle string `json:"lifecycle"` } `json:"state"` } reg := readSweepRegister(t, wrapped, seeded) - if err := json.Unmarshal(reg.Payload, &payload); err != nil { - t.Fatalf("unmarshal seeded payload: %v", err) - } - if payload.State.Lifecycle != string(harness.LifecycleOpen) { - t.Fatalf("stale Session lifecycle after a zero-threshold pass = %q, want it unchanged open", payload.State.Lifecycle) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } - if got := wrapped.listCount(); got != 2 { - t.Fatalf("storage lists after Close = %d, want no pass beyond the initial one", got) - } + testassert.NoError(t, json.Unmarshal(reg.Payload, &payload)) + testassert.Require(t, payload.State.Lifecycle == string(harness.LifecycleOpen), "stale Session lifecycle after a zero-threshold pass = %q, want it unchanged open", payload.State.Lifecycle) + testassert.NoError(t, r.Close(context.Background())) + testassert.Equal(t, wrapped.listCount(), 2) }) } @@ -1050,34 +898,28 @@ func seedStaleArchivedSession(t *testing.T, store harness.Storage, workspace str func plantSessionCode(t *testing.T, dataDir, sessionID string) string { t.Helper() root := filepath.Join(dataDir, "code", sessionID) - if err := os.MkdirAll(filepath.Join(root, "snapshots", "op-1"), 0o755); err != nil { - t.Fatalf("plant snapshot group: %v", err) - } - if err := os.WriteFile(filepath.Join(root, "snapshots", "op-1", "snapshot.txt"), []byte("snapshot"), 0o644); err != nil { - t.Fatalf("plant snapshot: %v", err) - } - if err := os.MkdirAll(filepath.Join(root, "output"), 0o755); err != nil { - t.Fatalf("plant output directory: %v", err) - } - if err := os.WriteFile(filepath.Join(root, "output", "spill.txt"), []byte("spill"), 0o644); err != nil { - t.Fatalf("plant spill: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(root, "snapshots", "op-1"), 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(root, "snapshots", "op-1", "snapshot.txt"), []byte("snapshot"), 0o644)) + testassert.NoError(t, os.MkdirAll(filepath.Join(root, "output"), 0o755)) + testassert.NoError(t, os.WriteFile(filepath.Join(root, "output", "spill.txt"), []byte("spill"), 0o644)) return root } // mustNotExist asserts one filesystem path is absent. func mustNotExist(t *testing.T, path, what string) { t.Helper() - if _, err := os.Stat(path); !os.IsNotExist(err) { - t.Fatalf("%s = err %v, want it removed", what, err) + { + _, err := os.Stat(path) + testassert.Require(t, os.IsNotExist(err), "%s = err %v, want it removed", what, err) } } // mustExist asserts one filesystem path is present. func mustExist(t *testing.T, path, what string) { t.Helper() - if _, err := os.Stat(path); err != nil { - t.Fatalf("%s = err %v, want it present", what, err) + { + _, err := os.Stat(path) + testassert.Require(t, err == nil, "%s = err %v, want it present", what, err) } } @@ -1091,91 +933,70 @@ func TestPrivateDeleteSessionCleansArtifacts(t *testing.T) { e := newOwnerEnv(t) ctx := context.Background() r, err := e.open(ctx, e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) archive := func(sessionID string) { t.Helper() - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.ArchiveSession(ctx, sessionID) - return err - }); err != nil { - t.Fatalf("archive %s: %v", sessionID, err) + { + err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + _, err := h.ArchiveSession(ctx, sessionID) + return err + }) + testassert.Require(t, err == nil, "archive %s: %v", sessionID, err) } } archived, err := r.createSession(ctx, filepath.Join(e.dataDir, "ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) archivedID := archived.Identity.SessionID archive(archivedID) archivedCode := plantSessionCode(t, e.dataDir, archivedID) sibling, err := r.createSession(ctx, filepath.Join(e.dataDir, "sibling"), "solo") - if err != nil { - t.Fatalf("create sibling: %v", err) - } + testassert.NoError(t, err) siblingCode := plantSessionCode(t, e.dataDir, sibling.Identity.SessionID) unrelated := filepath.Join(e.dataDir, "unrelated.txt") - if err := os.WriteFile(unrelated, []byte("keep"), 0o644); err != nil { - t.Fatalf("plant unrelated content: %v", err) - } + testassert.NoError(t, os.WriteFile(unrelated, []byte("keep"), 0o644)) - if err := r.deleteSession(ctx, archivedID); err != nil { - t.Fatalf("deleteSession of an archived session: %v", err) - } + testassert.NoError(t, r.deleteSession(ctx, archivedID)) mustNotExist(t, archivedCode, "the deleted session's artifact tree") mustExist(t, siblingCode, "the sibling session's artifact tree") mustExist(t, unrelated, "unrelated data-directory content") - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.ReadSessionHeader(ctx, archivedID) - return err - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read after delete = err %v, want ErrNotFound", err) + { + err := headerErrorThroughRuntime(ctx, r, archivedID) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read after delete = err %v, want ErrNotFound", err) } // the repeated delete is the same idempotent result with no artifacts left to clean - if err := r.deleteSession(ctx, archivedID); err != nil { - t.Fatalf("repeated deleteSession = err %v, want the idempotent cleanup success", err) - } + testassert.NoError(t, r.deleteSession(ctx, archivedID)) // canonical deletion with no artifact directory at all is already clean bare, err := r.createSession(ctx, filepath.Join(e.dataDir, "bare"), "solo") - if err != nil { - t.Fatalf("create for the missing-artifacts row: %v", err) - } + testassert.NoError(t, err) archive(bare.Identity.SessionID) - if err := r.deleteSession(ctx, bare.Identity.SessionID); err != nil { - t.Fatalf("deleteSession with no artifact directory = err %v, want nil", err) - } + testassert.NoError(t, r.deleteSession(ctx, bare.Identity.SessionID)) // an unknown valid identity with planted artifacts still cleans them const ghost = "0123456789abcdef0123456789abcdef" // valid hex identity, never registered ghostCode := plantSessionCode(t, e.dataDir, ghost) - if err := r.deleteSession(ctx, ghost); err != nil { - t.Fatalf("deleteSession of an unknown valid identity = err %v, want the cleanup success", err) - } + testassert.NoError(t, r.deleteSession(ctx, ghost)) mustNotExist(t, ghostCode, "the unknown identity's artifacts") // invalid and uncommitted deletions remove nothing open, err := r.createSession(ctx, filepath.Join(e.dataDir, "open"), "solo") - if err != nil { - t.Fatalf("create open: %v", err) - } + testassert.NoError(t, err) openCode := plantSessionCode(t, e.dataDir, open.Identity.SessionID) - if err := r.deleteSession(ctx, open.Identity.SessionID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("deleteSession of an open session = err %v, want ErrInvalid", err) + { + err := r.deleteSession(ctx, open.Identity.SessionID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "deleteSession of an open session = err %v, want ErrInvalid", err) } mustExist(t, openCode, "the open session's artifacts after the rejected delete") - if err := r.deleteSession(ctx, "not-a-session-id"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("deleteSession of a malformed identity = err %v, want ErrInvalid", err) + { + err := r.deleteSession(ctx, "not-a-session-id") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "deleteSession of a malformed identity = err %v, want ErrInvalid", err) } mustExist(t, siblingCode, "the sibling session's artifacts after the malformed delete") - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) }) } @@ -1190,20 +1011,14 @@ func TestPrivateDeleteSessionJoinsShutdown(t *testing.T) { ctx := context.Background() blocked := newTargetedBlockStore(store) r, err := open(ctx, e.options(e.storagePlugin(blocked))) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) archived, err := r.createSession(ctx, filepath.Join(e.dataDir, "ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) id := archived.Identity.SessionID - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { _, err := h.ArchiveSession(ctx, id) return err - }); err != nil { - t.Fatalf("archive: %v", err) - } + })) code := plantSessionCode(t, e.dataDir, id) blocked.block(id) @@ -1223,22 +1038,20 @@ func TestPrivateDeleteSessionJoinsShutdown(t *testing.T) { t.Fatal("shutdown converged while the direct deletion's admission was still held") case <-time.After(200 * time.Millisecond): } - if events := eventNames(e.events.all()); slices.Contains(events, "close:core") { - t.Fatal("the Core storage closed while the direct deletion was still admitted") + { + events := eventNames(e.events.all()) + testassert.Require(t, !slices.Contains(events, "close:core"), "the Core storage closed while the direct deletion was still admitted") } blocked.releaseBlock() - if err := <-done; err != nil { - t.Fatalf("deleteSession after the release: %v", err) - } + testassert.NoError(t, <-done) <-closeDone mustNotExist(t, code, "the artifact tree after the joined deletion") - if _, err := blocked.ReadRegister(ctx, harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("deleted session register = err %v, want ErrNotFound", err) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) + { + _, err := blocked.ReadRegister(ctx, harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "deleted session register = err %v, want ErrNotFound", err) } + testassert.NoError(t, r.Close(ctx)) }) } @@ -1251,41 +1064,31 @@ func TestPrivateDeleteSessionReturnsCleanupErrors(t *testing.T) { e := newOwnerEnv(t) ctx := context.Background() r, err := e.open(ctx, e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) archived, err := r.createSession(ctx, filepath.Join(e.dataDir, "ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) id := archived.Identity.SessionID - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { _, err := h.ArchiveSession(ctx, id) return err - }); err != nil { - t.Fatalf("archive: %v", err) - } + })) code := plantSessionCode(t, e.dataDir, id) codeRoot := filepath.Join(e.dataDir, "code") if os.Geteuid() == 0 { t.Skip("directory permissions do not block writes as root") } - if err := os.Chmod(codeRoot, 0o555); err != nil { // denies the removal of the Session's tree: the cleanup fails - t.Fatalf("chmod the code root: %v", err) - } + // denies the removal of the Session's tree: the cleanup fails + testassert.NoError(t, os.Chmod(codeRoot, 0o555)) t.Cleanup(func() { os.Chmod(codeRoot, 0o755) }) delErr := r.deleteSession(ctx, id) - if delErr == nil || !strings.Contains(delErr.Error(), code) { - t.Fatalf("deleteSession with a failing cleanup = err %v, want the removal error naming the blocked tree", delErr) - } - if _, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("register after the failed cleanup = err %v, want it gone: the deletion stays committed", err) + testassert.Require(t, delErr != nil && strings.Contains(delErr.Error(), code), "deleteSession with a failing cleanup = err %v, want the removal error naming the blocked tree", delErr) + { + _, err := store.ReadRegister(ctx, harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "register after the failed cleanup = err %v, want it gone: the deletion stays committed", err) } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) }) } @@ -1307,33 +1110,29 @@ func TestMaintenanceSweepCleansCommittedSessions(t *testing.T) { firstCode := plantSessionCode(t, e.dataDir, first) secondCode := plantSessionCode(t, e.dataDir, second) codeKeep := filepath.Join(e.dataDir, "code", "keep.txt") - if err := os.WriteFile(codeKeep, []byte("keep"), 0o644); err != nil { - t.Fatalf("plant code-root content: %v", err) - } + testassert.NoError(t, os.WriteFile(codeKeep, []byte("keep"), 0o644)) wrapped := newSweepStore(store) wrapped.armDeleteTarget(second) stderr := captureSweepStderr(t) r, err := open(context.Background(), e.options(e.storagePlugin(wrapped))) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) mustNotExist(t, firstCode, "the committed deletion's artifacts despite the stopping pass error") mustExist(t, secondCode, "the rolled-back deletion's artifacts") mustExist(t, codeKeep, "unrelated code-root content") - if _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: first, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("committed deletion's register = err %v, want ErrNotFound", err) + { + _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: first, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "committed deletion's register = err %v, want ErrNotFound", err) } readSweepRegister(t, wrapped, second) // the rolled-back deletion left the Session present out := stderr() - if n := strings.Count(out, "lightcode: sweep: "+errSweepRollback.Error()); n != 1 { - t.Fatalf("stderr sweep diagnostics = %d in %q, want one line reporting the stopping pass error", n, out) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + n := strings.Count(out, "lightcode: sweep: "+errSweepRollback.Error()) + testassert.Require(t, n == 1, "stderr sweep diagnostics = %d in %q, want one line reporting the stopping pass error", n, out) } + testassert.NoError(t, r.Close(context.Background())) }) } @@ -1357,29 +1156,23 @@ func TestMaintenanceSweepJoinsPassAndCleanupErrorsInOneDiagnostic(t *testing.T) t.Skip("directory permissions do not block writes as root") } codeRoot := filepath.Join(e.dataDir, "code") - if err := os.Chmod(codeRoot, 0o555); err != nil { // denies the committed deletion's cleanup - t.Fatalf("chmod the code root: %v", err) - } + // denies the committed deletion's cleanup + testassert.NoError(t, os.Chmod(codeRoot, 0o555)) t.Cleanup(func() { os.Chmod(codeRoot, 0o755) }) wrapped := newSweepStore(store) wrapped.armDeleteTarget(second) stderr := captureSweepStderr(t) r, err := open(context.Background(), e.options(e.storagePlugin(wrapped))) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) out := stderr() - if n := strings.Count(out, "lightcode: sweep:"); n != 1 { - t.Fatalf("stderr sweep diagnostics = %d in %q, want exactly one line for the whole pass", n, out) - } - if !strings.Contains(out, errSweepRollback.Error()) || !strings.Contains(out, firstCode) { - t.Fatalf("stderr sweep diagnostic %q, want it to carry both the stopping pass error and the failing cleanup", out) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + n := strings.Count(out, "lightcode: sweep:") + testassert.Require(t, n == 1, "stderr sweep diagnostics = %d in %q, want exactly one line for the whole pass", n, out) } + testassert.Require(t, strings.Contains(out, errSweepRollback.Error()) && strings.Contains(out, firstCode), "stderr sweep diagnostic %q, want it to carry both the stopping pass error and the failing cleanup", out) + testassert.NoError(t, r.Close(context.Background())) }) } @@ -1399,35 +1192,29 @@ func TestMaintenanceSweepReportsCleanupErrorsAndAttemptsEveryID(t *testing.T) { t.Skip("directory permissions do not block writes as root") } blocked := filepath.Join(aCode, "snapshots") // denies the removal of its contents: the cleanup of a fails - if err := os.Chmod(blocked, 0o555); err != nil { - t.Fatalf("chmod the blocked directory: %v", err) - } + testassert.NoError(t, os.Chmod(blocked, 0o555)) t.Cleanup(func() { os.Chmod(blocked, 0o755) }) stderr := captureSweepStderr(t) r, err := open(context.Background(), e.options(e.storagePlugin(store))) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) for _, id := range []string{a, b} { // committed deletions are never rolled back - if _, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("committed deletion of %s = err %v, want the register gone despite the cleanup failure", id, err) + { + _, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "committed deletion of %s = err %v, want the register gone despite the cleanup failure", id, err) } } mustExist(t, filepath.Join(aCode, "snapshots", "op-1"), "the failing cleanup's blocked directory") mustNotExist(t, bCode, "the succeeding cleanup's artifact tree") out := stderr() - if n := strings.Count(out, "lightcode: sweep:"); n != 1 { - t.Fatalf("stderr sweep diagnostics = %d in %q, want one line for the whole pass", n, out) - } - if !strings.Contains(out, filepath.Join(aCode, "snapshots")) { - t.Fatalf("stderr sweep diagnostic %q, want it to report the failing cleanup's path", out) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + n := strings.Count(out, "lightcode: sweep:") + testassert.Require(t, n == 1, "stderr sweep diagnostics = %d in %q, want one line for the whole pass", n, out) } + testassert.Require(t, strings.Contains(out, filepath.Join(aCode, "snapshots")), "stderr sweep diagnostic %q, want it to report the failing cleanup's path", out) + testassert.NoError(t, r.Close(context.Background())) }) } @@ -1451,16 +1238,12 @@ func TestMaintenanceShutdownJoinsSweepCleanup(t *testing.T) { plantSessionCode(t, e.dataDir, second) blocked := newTargetedBlockStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(blocked)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(blocked)) + testassert.NoError(t, err) writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } blocked.block(second) @@ -1479,8 +1262,9 @@ func TestMaintenanceShutdownJoinsSweepCleanup(t *testing.T) { t.Fatal("shutdown converged while the pass's cleanup admission was still held") case <-time.After(200 * time.Millisecond): } - if events := eventNames(e.events.all()); slices.Contains(events, "close:core") { - t.Fatal("the Core storage closed while the sweep cleanup was still admitted") + { + events := eventNames(e.events.all()) + testassert.Require(t, !slices.Contains(events, "close:core"), "the Core storage closed while the sweep cleanup was still admitted") } blocked.releaseBlock() @@ -1490,9 +1274,7 @@ func TestMaintenanceShutdownJoinsSweepCleanup(t *testing.T) { t.Fatal("the shutdown never joined the released pass") } mustNotExist(t, firstCode, "the committed deletion's artifacts after the joined pass") - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) }) } @@ -1514,23 +1296,17 @@ func TestMaintenanceSweepRemovesWarningsForEveryCommittedDeletion(t *testing.T) first, second, third := ids[0], ids[1], ids[2] wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped)) + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() keeper, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "keeper"), "solo") - if err != nil { - t.Fatalf("createSession(keeper): %v", err) - } + testassert.NoError(t, err) keeperID := keeper.Identity.SessionID seed := func(sessionID, kind string) { id := sessionID @@ -1542,15 +1318,14 @@ func TestMaintenanceSweepRemovesWarningsForEveryCommittedDeletion(t *testing.T) seed(keeperID, "keeper") sub, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) baseRevision, _ := r.warnings.snapshot() writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } wrapped.armDeleteTarget(third) stderr := captureSweepStderr(t) @@ -1563,40 +1338,36 @@ func TestMaintenanceSweepRemovesWarningsForEveryCommittedDeletion(t *testing.T) var hint *protocol.WarningChangedEvent for hint == nil { event, ok := nextEvent(t, sub) - if !ok { - t.Fatal("the subscription closed before the sweep's warning hint") - } + testassert.Require(t, ok, "the subscription closed before the sweep's warning hint") if eventKind(t, event) != "warning_changed" { continue } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } - if scopeKind(t, body.Scope) != "runtime" { - t.Fatalf("sweep warning hint scope = %+v, want one runtime-scoped hint for the batch", body.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, scopeKind(t, body.Scope) == "runtime", "sweep warning hint scope = %+v, want one runtime-scoped hint for the batch", body.Scope) hint = &body } - if hint.WarningsRevision.Revision != strconv.FormatUint(baseRevision+1, 10) { - t.Fatalf("sweep warning hint revision = %q, want one advance from %d", hint.WarningsRevision.Revision, baseRevision) - } + testassert.Require(t, hint.WarningsRevision.Revision == strconv.FormatUint(baseRevision+1, 10), "sweep warning hint revision = %q, want one advance from %d", hint.WarningsRevision.Revision, baseRevision) assertNoEvent(t, sub) for _, id := range []string{first, second} { - if _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("committed deletion of %s = err %v, want the register gone", id, err) + { + _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "committed deletion of %s = err %v, want the register gone", id, err) } - if _, warnings := r.warnings.snapshot(); sessionWarningPresent(warnings, id) { - t.Fatalf("committed deletion of %s kept its warning group: %+v", id, warnings) + { + _, warnings := r.warnings.snapshot() + testassert.Require(t, !sessionWarningPresent(warnings, id), "committed deletion of %s kept its warning group: %+v", id, warnings) } } readSweepRegister(t, wrapped, third) - if _, warnings := r.warnings.snapshot(); !sessionWarningPresent(warnings, third) { - t.Fatalf("the rolled-back deletion dropped its warning group") + { + _, warnings := r.warnings.snapshot() + testassert.Require(t, sessionWarningPresent(warnings, third), "the rolled-back deletion dropped its warning group") } - if _, warnings := r.warnings.snapshot(); !sessionWarningPresent(warnings, keeperID) { - t.Fatalf("the unrelated Session's warning group was removed") + { + _, warnings := r.warnings.snapshot() + testassert.Require(t, sessionWarningPresent(warnings, keeperID), "the unrelated Session's warning group was removed") } // The pass error's one retained diagnostic may land just after the @@ -1606,8 +1377,9 @@ func TestMaintenanceSweepRemovesWarningsForEveryCommittedDeletion(t *testing.T) time.Sleep(5 * time.Millisecond) } out := stderr() - if n := strings.Count(out, "lightcode: sweep:"); n != 1 { - t.Fatalf("stderr sweep diagnostics = %d in %q, want one line for the whole pass", n, out) + { + n := strings.Count(out, "lightcode: sweep:") + testassert.Require(t, n == 1, "stderr sweep diagnostics = %d in %q, want one line for the whole pass", n, out) } }) } @@ -1633,23 +1405,17 @@ func TestMaintenanceSweepStopsOnCandidateReadFailure(t *testing.T) { thirdCode := plantSessionCode(t, e.dataDir, third) wrapped := newSweepStore(store) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(wrapped)) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, ticks, err := e.openSwept(context.Background(), e.storagePlugin(wrapped)) + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() keeper, err := r.createSession(context.Background(), filepath.Join(e.dataDir, "keeper"), "solo") - if err != nil { - t.Fatalf("createSession(keeper): %v", err) - } + testassert.NoError(t, err) keeperID := keeper.Identity.SessionID seed := func(sessionID, kind string) { id := sessionID @@ -1661,15 +1427,14 @@ func TestMaintenanceSweepStopsOnCandidateReadFailure(t *testing.T) { seed(keeperID, "keeper") sub, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) baseRevision, _ := r.warnings.snapshot() writeServiceFile(t, e.configPath, ownerSweepDocument(`{"archive_after_days":1,"delete_after_archive_days":1}`)) - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } readFailure := errors.New("test sweep register read failure") wrapped.armReadFailure(third, readFailure) @@ -1682,48 +1447,43 @@ func TestMaintenanceSweepStopsOnCandidateReadFailure(t *testing.T) { var hint *protocol.WarningChangedEvent for hint == nil { event, ok := nextEvent(t, sub) - if !ok { - t.Fatal("the subscription closed before the sweep's warning hint") - } + testassert.Require(t, ok, "the subscription closed before the sweep's warning hint") if eventKind(t, event) != "warning_changed" { continue } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } - if scopeKind(t, body.Scope) != "runtime" { - t.Fatalf("sweep warning hint scope = %+v, want one runtime-scoped hint for the batch", body.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, scopeKind(t, body.Scope) == "runtime", "sweep warning hint scope = %+v, want one runtime-scoped hint for the batch", body.Scope) hint = &body } - if hint.WarningsRevision.Revision != strconv.FormatUint(baseRevision+1, 10) { - t.Fatalf("sweep warning hint revision = %q, want one advance from %d", hint.WarningsRevision.Revision, baseRevision) - } + testassert.Require(t, hint.WarningsRevision.Revision == strconv.FormatUint(baseRevision+1, 10), "sweep warning hint revision = %q, want one advance from %d", hint.WarningsRevision.Revision, baseRevision) assertNoEvent(t, sub) mustNotExist(t, firstCode, "the first committed deletion's artifacts despite the stopping read failure") mustNotExist(t, secondCode, "the second committed deletion's artifacts despite the stopping read failure") mustExist(t, thirdCode, "the unreadable candidate's artifacts") for _, id := range []string{first, second} { - if _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("committed deletion of %s = err %v, want the register gone", id, err) + { + _, err := wrapped.ReadRegister(context.Background(), harness.RegisterKey{SessionID: id, Kind: harness.RegisterSession}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "committed deletion of %s = err %v, want the register gone", id, err) } - if _, warnings := r.warnings.snapshot(); sessionWarningPresent(warnings, id) { - t.Fatalf("committed deletion of %s kept its warning group: %+v", id, warnings) + { + _, warnings := r.warnings.snapshot() + testassert.Require(t, !sessionWarningPresent(warnings, id), "committed deletion of %s kept its warning group: %+v", id, warnings) } } readSweepRegister(t, wrapped, third) // the unreadable candidate stays present for the next pass - if _, warnings := r.warnings.snapshot(); !sessionWarningPresent(warnings, third) { - t.Fatalf("the unreadable candidate dropped its warning group") + { + _, warnings := r.warnings.snapshot() + testassert.Require(t, sessionWarningPresent(warnings, third), "the unreadable candidate dropped its warning group") } - if _, warnings := r.warnings.snapshot(); !sessionWarningPresent(warnings, keeperID) { - t.Fatalf("the unrelated Session's warning group was removed") + { + _, warnings := r.warnings.snapshot() + testassert.Require(t, sessionWarningPresent(warnings, keeperID), "the unrelated Session's warning group was removed") } wrapped.disarmReadFailure(third) // the transient read failure is retryable, never latched - if rec, err := readSweptSession(t, r, third); err != nil || rec.Lifecycle != harness.LifecycleArchived { - t.Fatalf("the unreadable candidate after the pass = %+v err %v, want it left readable and archived", rec, err) - } + rec := headerThroughRuntime(t, r, third) + testassert.Require(t, rec.Lifecycle == harness.LifecycleArchived, "the unreadable candidate after the pass = %+v, want it left readable and archived", rec) // The pass error's one retained diagnostic may land just after the // hint; poll it under a bounded budget. @@ -1732,8 +1492,9 @@ func TestMaintenanceSweepStopsOnCandidateReadFailure(t *testing.T) { time.Sleep(5 * time.Millisecond) } out := stderr() - if n := strings.Count(out, "lightcode: sweep: "+readFailure.Error()); n != 1 { - t.Fatalf("stderr sweep diagnostics = %d in %q, want one line reporting the stopping read failure", n, out) + { + n := strings.Count(out, "lightcode: sweep: "+readFailure.Error()) + testassert.Require(t, n == 1, "stderr sweep diagnostics = %d in %q, want one line reporting the stopping read failure", n, out) } }) } @@ -1749,28 +1510,24 @@ func TestSweepSkipsHeldArtifactInterval(t *testing.T) { writeServiceFile(t, e.configPath, ownerSweepProbedDocument(e.server.URL, `{"archive_after_days":1,"delete_after_archive_days":1}`)) ctx := context.Background() r, err := e.open(ctx, e.storagePlugin(store), hookPlugin(&recordHook{name: "hook.first", events: &traceLog{}})) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) heldWorkspace := filepath.Join(e.dataDir, "held-ws") - if err := os.MkdirAll(heldWorkspace, 0o700); err != nil { - t.Fatalf("mkdir held workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(heldWorkspace, 0o700)) held := projectionSession(t, r, heldWorkspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, held, "op-1", "held") file := filepath.Join(heldWorkspace, "held.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, held, op.Admission.AdmittedEntry.EntryID), file, "v1") - if _, err := r.archiveSession(ctx, held); err != nil { - t.Fatalf("archive the held session: %v", err) + { + _, err := r.archiveSession(ctx, held) + testassert.NoError(t, err) } heldCode := filepath.Join(e.dataDir, "code", held) sibling := projectionSession(t, r, filepath.Join(e.dataDir, "swept-ws"), "solo").Identity.SessionID - if _, err := r.archiveSession(ctx, sibling); err != nil { - t.Fatalf("archive the sibling session: %v", err) + { + _, err := r.archiveSession(ctx, sibling) + testassert.NoError(t, err) } siblingCode := plantSessionCode(t, e.dataDir, sibling) @@ -1780,17 +1537,21 @@ func TestSweepSkipsHeldArtifactInterval(t *testing.T) { r.runSweepPass(ctx, time.Now().Add(200*time.Hour)) - if _, err := os.Stat(siblingCode); !os.IsNotExist(err) { - t.Fatalf("sibling artifacts = %v, want them swept by the pass", err) + { + _, err := os.Stat(siblingCode) + testassert.Require(t, os.IsNotExist(err), "sibling artifacts = %v, want them swept by the pass", err) } - if err := headerErrorThroughRuntime(ctx, r, sibling); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("sibling after the pass = %v, want the committed deletion", err) + { + err := headerErrorThroughRuntime(ctx, r, sibling) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "sibling after the pass = %v, want the committed deletion", err) } - if _, err := os.Stat(heldCode); err != nil { - t.Fatalf("held artifacts after the pass = %v, want them intact", err) + { + _, err := os.Stat(heldCode) + testassert.NoError(t, err) } - if header := headerThroughRuntime(t, r, held); header.Lifecycle != harness.LifecycleArchived { - t.Fatalf("held candidate lifecycle = %v, want it left archived", header.Lifecycle) + { + header := headerThroughRuntime(t, r, held) + testassert.Equal(t, header.Lifecycle, harness.LifecycleArchived) } park.release("restored") @@ -1800,23 +1561,18 @@ func TestSweepSkipsHeldArtifactInterval(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if parked.err != nil { - t.Fatalf("parked restore = %v", parked.err) - } - if len(parked.result.Restored) != 1 || parked.result.Restored[0] != file { - t.Fatalf("restored = %v, want the parked group's restore", parked.result.Restored) - } + testassert.Require(t, parked.err == nil && len(parked.result.Restored) == 1 && parked.result.Restored[0] == file, "parked restore = %v; restored = %v, want the parked group's restore", parked.err, parked.result.Restored) // A later pass owns the interval and completes the deletion. r.runSweepPass(ctx, time.Now().Add(200*time.Hour)) - if _, err := os.Stat(heldCode); !os.IsNotExist(err) { - t.Fatalf("held artifacts after the later pass = %v, want them removed", err) - } - if err := headerErrorThroughRuntime(ctx, r, held); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("held candidate after the later pass = %v, want the committed deletion", err) + { + _, err := os.Stat(heldCode) + testassert.Require(t, os.IsNotExist(err), "held artifacts after the later pass = %v, want them removed", err) } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) + { + err := headerErrorThroughRuntime(ctx, r, held) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "held candidate after the later pass = %v, want the committed deletion", err) } + testassert.NoError(t, r.Close(ctx)) }) } diff --git a/runtime/observation_bus_test.go b/runtime/observation_bus_test.go index dedcc404..56cddd9a 100644 --- a/runtime/observation_bus_test.go +++ b/runtime/observation_bus_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -28,22 +29,17 @@ func TestObservationPausedFactEmitsCurrentRevision(t *testing.T) { ctx := context.Background() e := newOwnerEnv(t) r, err := e.open(ctx, e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() sub, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(ctx, e.dataDir, "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID // The creation's own invalidation is consumed first: everything after @@ -67,9 +63,7 @@ func TestObservationPausedFactEmitsCurrentRevision(t *testing.T) { t.Helper() deadline := time.Now().Add(10 * time.Second) for sessionViewThroughRuntime(t, r, sessionID).Header.Lifecycle != want { - if time.Now().After(deadline) { - t.Fatalf("the session never reached lifecycle %q while its publication was paused", want) - } + testassert.Require(t, !time.Now().After(deadline), "the session never reached lifecycle %q while its publication was paused", want) time.Sleep(5 * time.Millisecond) } } @@ -83,12 +77,8 @@ func TestObservationPausedFactEmitsCurrentRevision(t *testing.T) { }() awaitLifecycle(harness.LifecycleOpen) release() - if err := <-archiveDone; err != nil { - t.Fatalf("archive: %v", err) - } - if err := <-reopenDone; err != nil { - t.Fatalf("reopen: %v", err) - } + testassert.NoError(t, <-archiveDone) + testassert.NoError(t, <-reopenDone) pausedEvent, ok := nextEvent(t, sub) if !ok || !equalEvent(pausedEvent, scopeEvent(protocol.ScopeOpened, ScopeInfo{Kind: ScopeWorkspace, Workspace: "/ws-paused-synthetic"})) { @@ -103,19 +93,11 @@ func TestObservationPausedFactEmitsCurrentRevision(t *testing.T) { t.Fatalf("queued invalidation = %s (ok=%v), want the session invalidation", eventJSON(t, event), ok) } body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } - if body.SessionRevision != final { - t.Fatalf("delayed invalidation revision = %+v, want the current final pair %+v (never the stale intermediate pair)", body.SessionRevision, final) - } + testassert.NoError(t, err) + testassert.Equal(t, body.SessionRevision, final) invalidation, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("invalidation scope: %v", err) - } - if invalidation.SessionId != sessionID { - t.Fatalf("delayed invalidation session = %q, want the real session", invalidation.SessionId) - } + testassert.NoError(t, err) + testassert.Equal(t, invalidation.SessionId, sessionID) } assertNoEvent(t, sub) }) @@ -133,14 +115,13 @@ func TestObservationRootChildScopeTagging(t *testing.T) { } }() sub, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) root := bg.session("root-ws") child := bg.launchChild(root, "child work", "child-op-1", 1) bg.submitMode(root, "root-op-1", "hello", harness.MessageModeRegular) - if rec := awaitOperation(t, bg.r, root, "root-op-1", harness.OperationSuccess); rec.State.Status != harness.OperationSuccess { - t.Fatalf("root op settled %q", rec.State.Status) + { + rec := awaitOperation(t, bg.r, root, "root-op-1", harness.OperationSuccess) + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) } awaitOperation(t, bg.r, child, "child-op-1", harness.OperationSuccess) awaitRestorableSession(t, bg.r, root) @@ -153,15 +134,11 @@ func TestObservationRootChildScopeTagging(t *testing.T) { for { select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed before the drain finished") - } + testassert.Require(t, ok, "the subscription closed before the drain finished") switch eventKind(t, event) { case "session_changed": body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) invalidationSession, _ := scopeSessionIdentity(t, body.Scope) switch invalidationSession { case root: @@ -171,29 +148,17 @@ func TestObservationRootChildScopeTagging(t *testing.T) { default: t.Fatalf("invalidation for a foreign session %+v", body.Scope) } - if scopeKind(t, body.Scope) != "session" { - t.Fatalf("invalidation scope = %+v, want the session granularity", body.Scope) - } + testassert.Require(t, scopeKind(t, body.Scope) == "session", "invalidation scope = %+v, want the session granularity", body.Scope) invalidation, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("invalidation scope: %v", err) - } - if deref(invalidation.Workspace) != workspace { - t.Fatalf("invalidation scope = %+v, want the shared workspace", body.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, deref(invalidation.Workspace) == workspace, "invalidation scope = %+v, want the shared workspace", body.Scope) case "text_delta": body, err := event.AsTextDeltaEvent() - if err != nil { - t.Fatalf("delta event body: %v", err) - } + testassert.NoError(t, err) progress, err := body.Scope.AsOperationScope() - if err != nil { - t.Fatalf("progress scope: %v", err) - } + testassert.NoError(t, err) if progress.SessionId == child { - if progress.OperationId != "child-op-1" { - t.Fatalf("child progress scope = %+v, want the child's real operation", body.Scope) - } + testassert.Require(t, progress.OperationId == "child-op-1", "child progress scope = %+v, want the child's real operation", body.Scope) childProgress++ continue } @@ -201,22 +166,16 @@ func TestObservationRootChildScopeTagging(t *testing.T) { // delivery's own follow-up turn: its operation is real // but caller-generated, so only the session identity is // pinned here. - if progress.SessionId != root || progress.OperationId == "" { - t.Fatalf("progress scope = %+v, want the root session's real operation", body.Scope) - } + testassert.Require(t, progress.SessionId == root && progress.OperationId != "", "progress scope = %+v, want the root session's real operation", body.Scope) rootProgress++ case "tool_started", "tool_finished": t.Fatalf("unexpected tool progress without a tool call: %s", eventJSON(t, event)) case "scope_opened", "scope_closed": body, err := event.AsScopeEvent() - if err != nil { - t.Fatalf("scope event body: %v", err) - } + testassert.NoError(t, err) if kind := scopeKind(t, body.Scope); kind == "operation" || kind == "agent" { shortSession, _ := scopeSessionIdentity(t, body.Scope) - if shortSession != root && shortSession != child { - t.Fatalf("short scope event for a foreign session: %+v", body.Scope) - } + testassert.Require(t, shortSession == root || shortSession == child, "short scope event for a foreign session: %+v", body.Scope) } default: // configuration and warning events carry no session attribution. @@ -226,12 +185,8 @@ func TestObservationRootChildScopeTagging(t *testing.T) { } break } - if rootInvalidations == 0 || childInvalidations == 0 { - t.Fatalf("root/child invalidations = %d/%d, want real committed hints for both sessions", rootInvalidations, childInvalidations) - } - if rootProgress == 0 || childProgress == 0 { - t.Fatalf("root/child progress = %d/%d, want the real progress truth for both sessions", rootProgress, childProgress) - } + testassert.Require(t, rootInvalidations != 0 && childInvalidations != 0, "root/child invalidations = %d/%d, want real committed hints for both sessions", rootInvalidations, childInvalidations) + testassert.Require(t, rootProgress != 0 && childProgress != 0, "root/child progress = %d/%d, want the real progress truth for both sessions", rootProgress, childProgress) }) } @@ -249,40 +204,35 @@ func TestObservationTwoHealthyOrderOneSaturatedNoReplay(t *testing.T) { } }() first, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe(first): %v", err) - } + testassert.NoError(t, err) second, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe(second): %v", err) - } + testassert.NoError(t, err) saturated, err := bg.r.Subscribe(1) - if err != nil { - t.Fatalf("Subscribe(saturated): %v", err) - } + testassert.NoError(t, err) session := bg.session("order-ws") bg.submitMode(session, "op-1", "hello", harness.MessageModeRegular) awaitOperation(t, bg.r, session, "op-1", harness.OperationSuccess) awaitRestorableSession(t, bg.r, session) - if _, err := bg.r.Reload(ctx); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := bg.r.Reload(ctx) + testassert.NoError(t, err) } // The saturated subscriber is removed on its second publication. - if _, ok := nextEvent(t, saturated); !ok { - t.Fatal("the saturated subscriber never received its first buffered event") + { + _, ok := nextEvent(t, saturated) + testassert.Require(t, ok, "the saturated subscriber never received its first buffered event") } - if _, ok := nextEvent(t, saturated); ok { - t.Fatal("the saturated subscriber stayed open past its capacity, want it removed and closed") + { + _, ok := nextEvent(t, saturated) + testassert.Require(t, !ok, "the saturated subscriber stayed open past its capacity, want it removed and closed") } saturated.Close() // A late subscriber receives no replay of the past work. late, err := bg.r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe(late): %v", err) - } + testassert.NoError(t, err) assertNoEvent(t, late) late.Close() @@ -309,9 +259,7 @@ func TestObservationTwoHealthyOrderOneSaturatedNoReplay(t *testing.T) { } firstSeq := drain(first) secondSeq := drain(second) - if len(firstSeq) == 0 || !slices.EqualFunc(firstSeq, secondSeq, equalEvent) { - t.Fatalf("the two healthy subscribers observed different orders: %d events vs %d", len(firstSeq), len(secondSeq)) - } + testassert.Require(t, len(firstSeq) != 0 && slices.EqualFunc(firstSeq, secondSeq, equalEvent), "the two healthy subscribers observed different orders: %d events vs %d", len(firstSeq), len(secondSeq)) }) } @@ -331,9 +279,7 @@ func TestObservationJobMemberCurrentPairAndInternalSilence(t *testing.T) { } }() sub, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) session := bg.session("job-ws") workspace := sessionViewThroughRuntime(t, bg.r, session).Header.Identity.Workspace @@ -390,19 +336,11 @@ func TestObservationJobMemberCurrentPairAndInternalSilence(t *testing.T) { t.Fatalf("creation event = %s (ok=%v), want the session's first invalidation", eventJSON(t, creation), ok) } creationBody, err := creation.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } - if scopeKind(t, creationBody.Scope) != "session" { - t.Fatalf("creation event scope = %+v, want the plain session granularity", creationBody.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, scopeKind(t, creationBody.Scope) == "session", "creation event scope = %+v, want the plain session granularity", creationBody.Scope) creationScope, err := creationBody.Scope.AsSessionScope() - if err != nil { - t.Fatalf("creation event scope: %v", err) - } - if creationScope.JobId != nil { - t.Fatalf("creation event scope = %+v, want the plain session granularity", creationBody.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, creationScope.JobId == nil, "creation event scope = %+v, want the plain session granularity", creationBody.Scope) // The admission's job-scoped hint with the CURRENT pair: after the // admission nothing else runs on the Session, so the pair equals the @@ -412,30 +350,20 @@ func TestObservationJobMemberCurrentPairAndInternalSilence(t *testing.T) { t.Fatalf("job admission event = %s (ok=%v), want the job-scoped invalidation", eventJSON(t, hint), ok) } body, err := hint.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } - if scopeKind(t, body.Scope) != "job" { - t.Fatalf("job admission scope = %+v, want the job scope with the owning session", body.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, scopeKind(t, body.Scope) == "job", "job admission scope = %+v, want the job scope with the owning session", body.Scope) jobScope, err := body.Scope.AsJobScope() - if err != nil { - t.Fatalf("job admission scope: %v", err) - } - if jobScope.JobId != "11111111" || - jobScope.SessionId != session || deref(jobScope.Workspace) != workspace { - t.Fatalf("job admission scope = %+v, want the job scope with the owning session", body.Scope) - } - if admitPair := snapshotPairOf(t, bg.r, session); body.SessionRevision != admitPair { - t.Fatalf("job admission pair = %+v, want the current pair %+v", body.SessionRevision, admitPair) + testassert.NoError(t, err) + testassert.Require(t, jobScope.JobId == "11111111" && jobScope.SessionId == session && deref(jobScope.Workspace) == workspace, "job admission scope = %+v, want the job scope with the owning session", body.Scope) + { + admitPair := snapshotPairOf(t, bg.r, session) + testassert.Equal(t, body.SessionRevision, admitPair) } // The parked internal start: membership unchanged, no duplicate // revisioned hint, no revision advance. assertNoEvent(t, sub) - if parkedPair := snapshotPairOf(t, bg.r, session); parkedPair != body.SessionRevision { - t.Fatalf("the parked internal start advanced the revision: %+v → %+v", body.SessionRevision, parkedPair) - } + testassert.Equal(t, snapshotPairOf(t, bg.r, session), body.SessionRevision) // Release the internal start and deliver the completion: the real // session-scoped hints of the delivery's commits arrive, then the @@ -444,53 +372,37 @@ func TestObservationJobMemberCurrentPairAndInternalSilence(t *testing.T) { completionMu.Lock() deliverID := completionID completionMu.Unlock() - if err := bg.r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, bg.r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { return h.DeliverBackgroundCompletion(ctx, session, deliverID, "job output") - }); err != nil { - t.Fatalf("DeliverBackgroundCompletion: %v", err) - } + })) deadline := time.Now().Add(10 * time.Second) finishPair := protocol.SessionRevision{} for { event, ok := nextEvent(t, sub) - if !ok { - t.Fatal("the subscription closed before the job finish hint") - } + testassert.Require(t, ok, "the subscription closed before the job finish hint") if eventKind(t, event) != "session_changed" { continue } body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) == "job" { finishHint, err := body.Scope.AsJobScope() - if err != nil { - t.Fatalf("job finish hint scope: %v", err) - } + testassert.NoError(t, err) if finishHint.JobId == "11111111" { finishPair = body.SessionRevision break } } - if scopeKind(t, body.Scope) != "session" { - t.Fatalf("completion-delivery hint scope = %+v, want the session granularity", body.Scope) - } - if time.Now().After(deadline) { - t.Fatal("the job finish hint never arrived") - } + testassert.Require(t, scopeKind(t, body.Scope) == "session", "completion-delivery hint scope = %+v, want the session granularity", body.Scope) + testassert.Require(t, !time.Now().After(deadline), "the job finish hint never arrived") } // The completion's own turn may still be settling when the hint is // observed, so the finish pair is pinned to the two invariants that // hold deterministically: it advanced past the admission pair, and // it never lies beyond the Session's current state. afterPair := snapshotPairOf(t, bg.r, session) - if !lessPair(t, finishPair, afterPair) && finishPair != afterPair { - t.Fatalf("job finish pair = %+v, beyond the Session's current state %+v", finishPair, afterPair) - } - if lessPair(t, finishPair, body.SessionRevision) { - t.Fatalf("job finish pair = %+v, not advanced past the admission pair %+v", finishPair, body.SessionRevision) - } + testassert.Require(t, lessPair(t, finishPair, afterPair) || finishPair == afterPair, "job finish pair = %+v, beyond the Session's current state %+v", finishPair, afterPair) + testassert.Require(t, !lessPair(t, finishPair, body.SessionRevision), "job finish pair = %+v, not advanced past the admission pair %+v", finishPair, body.SessionRevision) }) } @@ -519,29 +431,20 @@ func TestObservationPluginReportsLandWithRealAttribution(t *testing.T) { } } r, err := e.open(ctx, e.storagePlugin(store), reporterPlugin("alpha"), reporterPlugin("beta")) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() - if reports["alpha"] == nil || reports["beta"] == nil { - t.Fatal("the Runtime scope carried no per-plugin ReportWarning closure") - } + testassert.Require(t, reports["alpha"] != nil && reports["beta"] != nil, "the Runtime scope carried no per-plugin ReportWarning closure") session, err := r.createSession(ctx, e.dataDir, "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) other, err := r.createSession(ctx, e.dataDir, "solo") - if err != nil { - t.Fatalf("createSession(other): %v", err) - } + testassert.NoError(t, err) sub, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) // One report: the reporting plugin's own global group gains the entry // with the runtime-scoped warning event. The initial configuration @@ -554,15 +457,11 @@ func TestObservationPluginReportsLandWithRealAttribution(t *testing.T) { t.Fatalf("report event = %s (ok=%v), want the warning_changed hint", eventJSON(t, event), ok) } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) != "runtime" { t.Fatalf("report event scope kind = %q, want the runtime scope", scopeKind(t, body.Scope)) } - if body.WarningsRevision.Revision != strconv.FormatUint(baseRevision+1, 10) { - t.Fatalf("report event revision = %q, want the first advance", body.WarningsRevision.Revision) - } + testassert.Require(t, body.WarningsRevision.Revision == strconv.FormatUint(baseRevision+1, 10), "report event revision = %q, want the first advance", body.WarningsRevision.Revision) assertNoEvent(t, sub) // The second factory's report is attributed to its own registration @@ -573,12 +472,8 @@ func TestObservationPluginReportsLandWithRealAttribution(t *testing.T) { t.Fatalf("second report event = %s (ok=%v), want the warning_changed hint", eventJSON(t, event), ok) } body, err = event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } - if body.WarningsRevision.Revision != strconv.FormatUint(baseRevision+2, 10) { - t.Fatalf("second report event revision = %q, want the second advance", body.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Require(t, body.WarningsRevision.Revision == strconv.FormatUint(baseRevision+2, 10), "second report event revision = %q, want the second advance", body.WarningsRevision.Revision) // An identical report dedupes: no second entry, no second event. reports["alpha"]("install_failed", "alpha must be installed") @@ -586,37 +481,26 @@ func TestObservationPluginReportsLandWithRealAttribution(t *testing.T) { for _, sessionID := range []string{session.Identity.SessionID, other.Identity.SessionID} { hydration, err := r.buildHydration(ctx, sessionID) - if err != nil { - t.Fatalf("buildHydration(%s): %v", sessionID, err) - } - if warningOf(hydration.Warnings, "plugin:alpha", "install_failed", "") == nil || - warningOf(hydration.Warnings, "plugin:beta", "server_unavailable", "") == nil { - t.Fatalf("hydration of %s misses a plugin group: %+v", sessionID, hydration.Warnings) - } + testassert.Require(t, err == nil, "buildHydration(%s): %v", sessionID, err) + testassert.Require(t, warningOf(hydration.Warnings, "plugin:alpha", "install_failed", "") != nil && warningOf(hydration.Warnings, "plugin:beta", "server_unavailable", "") != nil, "hydration of %s misses a plugin group: %+v", sessionID, hydration.Warnings) } all, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } - if warningOf(all.Warnings, "plugin:alpha", "install_failed", "") == nil || - warningOf(all.Warnings, "plugin:beta", "server_unavailable", "") == nil { - t.Fatalf("the unfiltered read misses a plugin group: %+v", all.Warnings) - } + testassert.NoError(t, err) + testassert.Require(t, warningOf(all.Warnings, "plugin:alpha", "install_failed", "") != nil && warningOf(all.Warnings, "plugin:beta", "server_unavailable", "") != nil, "the unfiltered read misses a plugin group: %+v", all.Warnings) counts := map[string]int{} for _, warning := range all.Warnings { counts[string(warning.Source)]++ } - if counts["plugin:alpha"] != 1 || counts["plugin:beta"] != 1 { - t.Fatalf("plugin entries after the identical re-report = %v, want one deduped value per factory", counts) - } + testassert.Require(t, counts["plugin:alpha"] == 1 && counts["plugin:beta"] == 1, "plugin entries after the identical re-report = %v, want one deduped value per factory", counts) // Reports after the warning store's closure are ignored. revision, _ := r.warnings.snapshot() r.warnings.close() reports["alpha"]("install_failed", "a late report") assertNoEvent(t, sub) - if after, _ := r.warnings.snapshot(); after != revision { - t.Fatalf("a late report advanced the store: %d → %d", revision, after) + { + after, _ := r.warnings.snapshot() + testassert.Equal(t, after, revision) } }) } @@ -638,9 +522,7 @@ func TestObservationStopAllJobFinishHints(t *testing.T) { } }() sub, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) jobSession := bg.session("stopall-job") workspace := sessionViewThroughRuntime(t, bg.r, jobSession).Header.Identity.Workspace completionID := bg.startJob(jobSession, "0a1b2c3d") @@ -659,9 +541,7 @@ func TestObservationStopAllJobFinishHints(t *testing.T) { for { select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed before the drain finished") - } + testassert.Require(t, ok, "the subscription closed before the drain finished") out = append(out, event) continue default: @@ -698,28 +578,20 @@ func TestObservationStopAllJobFinishHints(t *testing.T) { continue } body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) sessionID, ok := scopeSessionIdentity(t, body.Scope) if !ok || sessionID != jobSession { continue } - if scopeKind(t, body.Scope) != "session" { - t.Fatalf("stopping invalidation scope = %+v, want the session granularity", body.Scope) - } + testassert.Require(t, scopeKind(t, body.Scope) == "session", "stopping invalidation scope = %+v, want the session granularity", body.Scope) stopping = body.SessionRevision } - if stopping.DurableRevision == "" { - t.Fatal("the owning Session's stopping invalidation was not queued when the stopper was entered") - } + testassert.Require(t, stopping.DurableRevision != "", "the owning Session's stopping invalidation was not queued when the stopper was entered") releaseGate() // the delivery runs: the member finishes and StopAll converges select { case err := <-stopDone: - if err != nil { - t.Fatalf("StopAll: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("StopAll never converged") } @@ -729,47 +601,31 @@ func TestObservationStopAllJobFinishHints(t *testing.T) { finishPair := protocol.SessionRevision{} for { event, ok := nextEvent(t, sub) - if !ok { - t.Fatal("the subscription closed before the job finish hint") - } + testassert.Require(t, ok, "the subscription closed before the job finish hint") if eventKind(t, event) != "session_changed" { continue } body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) != "job" { continue } jobScope, err := body.Scope.AsJobScope() - if err != nil { - t.Fatalf("job finish hint scope: %v", err) - } + testassert.NoError(t, err) if jobScope.JobId != "0a1b2c3d" { continue } - if jobScope.SessionId != jobSession || deref(jobScope.Workspace) != workspace { - t.Fatalf("job finish hint scope = %+v, want the owning session %q", body.Scope, jobSession) - } + testassert.Require(t, jobScope.SessionId == jobSession && deref(jobScope.Workspace) == workspace, "job finish hint scope = %+v, want the owning session %q", body.Scope, jobSession) finishPair = body.SessionRevision break } - if !lessPair(t, stopping, finishPair) { - t.Fatalf("job finish pair = %+v, want it advanced past the stopping pair %+v", finishPair, stopping) - } + testassert.Require(t, lessPair(t, stopping, finishPair), "job finish pair = %+v, want it advanced past the stopping pair %+v", finishPair, stopping) // Authoritative hydration agrees: no member and a revision not older // than the hint. hydration, err := bg.r.buildHydration(ctx, jobSession) - if err != nil { - t.Fatalf("buildHydration: %v", err) - } - if len(hydration.Background) != 0 { - t.Fatalf("hydration background = %+v, want no member after the stop", hydration.Background) - } - if lessPair(t, hydration.SessionRevision, finishPair) { - t.Fatalf("hydration revision %+v is older than the job finish hint %+v", hydration.SessionRevision, finishPair) - } + testassert.NoError(t, err) + testassert.Require(t, len(hydration.Background) == 0, "hydration background = %+v, want no member after the stop", hydration.Background) + testassert.Require(t, !lessPair(t, hydration.SessionRevision, finishPair), "hydration revision %+v is older than the job finish hint %+v", hydration.SessionRevision, finishPair) }) } diff --git a/runtime/observation_composed_lsp_test.go b/runtime/observation_composed_lsp_test.go index 2455006e..2625adb1 100644 --- a/runtime/observation_composed_lsp_test.go +++ b/runtime/observation_composed_lsp_test.go @@ -20,6 +20,7 @@ import ( "time" "github.com/MMinasyan/lightcode/internal/plugins/builtin" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" ) @@ -97,44 +98,31 @@ func TestComposedLSPWarningLandsInGlobalStoreAndEvents(t *testing.T) { e := newComposeEnv(t) t.Setenv("PATH", t.TempDir()) // no resolvable language server anywhere server := newLSPModelServer(t) - if err := os.WriteFile(e.configPath, []byte(lspConfigDocument(server.URL)), 0o600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(filepath.Dir(e.configPath), "agents.json"), []byte(lspAgentsDocument), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(e.configPath, []byte(lspConfigDocument(server.URL)), 0o600)) + testassert.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(e.configPath), "agents.json"), []byte(lspAgentsDocument), 0o600)) workspace := t.TempDir() - if err := os.WriteFile(filepath.Join(workspace, "CMakeLists.txt"), []byte("cmake_minimum_required(VERSION 3.10)\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "CMakeLists.txt"), []byte("cmake_minimum_required(VERSION 3.10)\n"), 0o600)) ctx := context.Background() r, err := runtime.Open(ctx, runtime.Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: builtin.Plugins()}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) // The owner closes on every exit: the deferred Close is idempotent and // joins the same shutdown, covering an assertion failure before the // server and data-root cleanups run. defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() sub, err := r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() session, err := runtime.CreateSessionForTest(ctx, r, workspace, "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } - if err := runtime.SubmitForTest(ctx, r, session.Identity.SessionID, "op-lsp", "find Foo"); err != nil { - t.Fatalf("Submit: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, runtime.SubmitForTest(ctx, r, session.Identity.SessionID, "op-lsp", "find Foo")) // The real warning arrives through the real manager's report: the // runtime-scoped warning_changed hint, then the global store read. The @@ -144,17 +132,13 @@ func TestComposedLSPWarningLandsInGlobalStoreAndEvents(t *testing.T) { for hint == nil { select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed before the LSP warning hint") - } + testassert.Require(t, ok, "the subscription closed before the LSP warning hint") kind, err := event.Discriminator() if err != nil || kind != "warning_changed" { continue } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if composedScopeKind(body.Scope) == "runtime" { hint = &body } @@ -162,24 +146,19 @@ func TestComposedLSPWarningLandsInGlobalStoreAndEvents(t *testing.T) { t.Fatal("the real LSP warning hint never arrived") } } - if _, err := strconv.ParseUint(hint.WarningsRevision.Revision, 10, 64); err != nil { - t.Fatalf("warning hint revision = %q, want a decimal value", hint.WarningsRevision.Revision) + { + _, err := strconv.ParseUint(hint.WarningsRevision.Revision, 10, 64) + testassert.Require(t, err == nil, "warning hint revision = %q, want a decimal value", hint.WarningsRevision.Revision) } warnings, err := runtime.GetWarningsForTest(r) - if err != nil { - t.Fatalf("GetWarningsForTest: %v", err) - } + testassert.NoError(t, err) found := false for _, warning := range warnings.Warnings { if warning.Source == "plugin:lsp" && strings.Contains(warning.Kind, "lsp_") { found = true } } - if !found { - t.Fatalf("the global warning read misses the real LSP report: %+v", warnings.Warnings) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, found, "the global warning read misses the real LSP report: %+v", warnings.Warnings) + testassert.NoError(t, r.Close(ctx)) } diff --git a/runtime/observation_test.go b/runtime/observation_test.go index 333c11ab..ef41b7ce 100644 --- a/runtime/observation_test.go +++ b/runtime/observation_test.go @@ -14,6 +14,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -36,9 +37,7 @@ func equalEvent(a, b Event) bool { func eventKind(t *testing.T, event Event) string { t.Helper() kind, err := event.Discriminator() - if err != nil { - t.Fatalf("event discriminator: %v", err) - } + testassert.NoError(t, err) return kind } @@ -46,9 +45,7 @@ func eventKind(t *testing.T, event Event) string { func scopeKind(t *testing.T, scope protocol.Scope) string { t.Helper() kind, err := scope.Discriminator() - if err != nil { - t.Fatalf("scope discriminator: %v", err) - } + testassert.NoError(t, err) return kind } @@ -60,27 +57,19 @@ func scopeSessionIdentity(t *testing.T, scope protocol.Scope) (string, bool) { switch scopeKind(t, scope) { case "session": body, err := scope.AsSessionScope() - if err != nil { - t.Fatalf("session scope: %v", err) - } + testassert.NoError(t, err) sessionID = body.SessionId case "operation": body, err := scope.AsOperationScope() - if err != nil { - t.Fatalf("operation scope: %v", err) - } + testassert.NoError(t, err) sessionID = body.SessionId case "agent": body, err := scope.AsAgentScope() - if err != nil { - t.Fatalf("agent scope: %v", err) - } + testassert.NoError(t, err) sessionID = body.SessionId case "job": body, err := scope.AsJobScope() - if err != nil { - t.Fatalf("job scope: %v", err) - } + testassert.NoError(t, err) sessionID = body.SessionId default: return "", false @@ -97,39 +86,27 @@ func scopeWorkspaceAttribution(t *testing.T, scope protocol.Scope) (string, bool switch scopeKind(t, scope) { case "runtime": body, err := scope.AsRuntimeScope() - if err != nil { - t.Fatalf("runtime scope: %v", err) - } + testassert.NoError(t, err) workspace = body.Workspace case "workspace": body, err := scope.AsWorkspaceScope() - if err != nil { - t.Fatalf("workspace scope: %v", err) - } + testassert.NoError(t, err) return body.Workspace, true case "session": body, err := scope.AsSessionScope() - if err != nil { - t.Fatalf("session scope: %v", err) - } + testassert.NoError(t, err) workspace = body.Workspace case "operation": body, err := scope.AsOperationScope() - if err != nil { - t.Fatalf("operation scope: %v", err) - } + testassert.NoError(t, err) workspace = body.Workspace case "agent": body, err := scope.AsAgentScope() - if err != nil { - t.Fatalf("agent scope: %v", err) - } + testassert.NoError(t, err) workspace = body.Workspace case "job": body, err := scope.AsJobScope() - if err != nil { - t.Fatalf("job scope: %v", err) - } + testassert.NoError(t, err) workspace = body.Workspace default: return "", false @@ -144,9 +121,7 @@ func scopeWorkspaceAttribution(t *testing.T, scope protocol.Scope) (string, bool func eventGeneration(t *testing.T, event Event) string { t.Helper() body, err := event.AsConfigurationChangedEvent() - if err != nil { - t.Fatalf("configuration event body: %v", err) - } + testassert.NoError(t, err) return body.ConfigurationRevision.Generation } @@ -179,33 +154,29 @@ func drainClosed(t *testing.T, sub *Subscription) []Event { func TestRuntimeSubscribeValidatesCapacityAndUsesTheClosedGate(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) + testassert.NoError(t, err) + { + _, err := r.Subscribe(0) + testassert.Require(t, err != nil, "Subscribe(0) succeeded, want capacity > 0 required") } - if _, err := r.Subscribe(0); err == nil { - t.Fatal("Subscribe(0) succeeded, want capacity > 0 required") - } - if _, err := r.Subscribe(-2); err == nil { - t.Fatal("Subscribe(-2) succeeded, want capacity > 0 required") + { + _, err := r.Subscribe(-2) + testassert.Require(t, err != nil, "Subscribe(-2) succeeded, want capacity > 0 required") } sub, err := r.Subscribe(4) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + testassert.NoError(t, err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } sub.Close() sub.Close() // idempotent got := drainClosed(t, sub) - if len(got) != 1 || eventKind(t, got[0]) != "configuration_changed" || eventGeneration(t, got[0]) != "2" { - t.Fatalf("events = %+v, want the buffered configuration event 2 then closure", got) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } - if _, err := r.Subscribe(1); !errors.Is(err, ErrClosed) { - t.Fatalf("Subscribe after Close = %v, want ErrClosed", err) + testassert.Require(t, len(got) == 1 && eventKind(t, got[0]) == "configuration_changed" && eventGeneration(t, got[0]) == "2", "events = %+v, want the buffered configuration event 2 then closure", got) + testassert.NoError(t, r.Close(context.Background())) + { + _, err := r.Subscribe(1) + testassert.Require(t, errors.Is(err, ErrClosed), "Subscribe after Close = %v, want ErrClosed", err) } } @@ -214,25 +185,22 @@ func TestRuntimeObservationPublishesCommittedTransitionsInOrder(t *testing.T) { r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory()), e.ordinaryPlugin("w", ScopeWorkspace, "w.cap")) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) sub, err := r.Subscribe(16) - if err != nil { - t.Fatalf("Subscribe: %v", err) + testassert.NoError(t, err) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == "2", "Reload = (%q, %v), want 2", revision, err) } - if revision, err := r.Reload(context.Background()); err != nil || revision != "2" { - t.Fatalf("Reload = (%q, %v), want 2", revision, err) + { + _, err := r.workspaces.get(context.Background(), ScopeInfo{Kind: ScopeWorkspace, DataDir: e.dataDir, Workspace: obsWorkspace}) + testassert.NoError(t, err) } - if _, err := r.workspaces.get(context.Background(), ScopeInfo{Kind: ScopeWorkspace, DataDir: e.dataDir, Workspace: obsWorkspace}); err != nil { - t.Fatalf("workspace get: %v", err) - } - if revision, err := r.Reload(context.Background()); err != nil || revision != "3" { - t.Fatalf("Reload = (%q, %v), want 3", revision, err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == "3", "Reload = (%q, %v), want 3", revision, err) } + testassert.NoError(t, r.Close(context.Background())) want := []Event{ configurationChangedEvent(2), scopeEvent(protocol.ScopeOpened, ScopeInfo{Kind: ScopeWorkspace, Workspace: obsWorkspace}), @@ -251,18 +219,15 @@ func TestObservationPausedPublisherOrdersTheNextPublication(t *testing.T) { r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory()), e.ordinaryPlugin("w", ScopeWorkspace, "w.cap")) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) pausedEvent := scopeEvent(protocol.ScopeOpened, ScopeInfo{Kind: ScopeWorkspace, Workspace: "/ws-paused-synthetic"}) pausedSecond := scopeEvent(protocol.ScopeClosed, ScopeInfo{Kind: ScopeWorkspace, Workspace: "/ws-paused-synthetic"}) @@ -281,13 +246,12 @@ func TestObservationPausedPublisherOrdersTheNextPublication(t *testing.T) { t.Fatalf("event %s enqueued before its publication committed (ok=%v)", eventJSON(t, event), ok) case <-time.After(200 * time.Millisecond): } - if snapshot := r.config.current(); snapshot == nil || snapshot.generation != 1 { - t.Fatalf("published generation during pause = %+v, want the paused publication's state commit to block the next one", snapshot) + { + snapshot := r.config.current() + testassert.Require(t, snapshot != nil && snapshot.generation == 1, "published generation during pause = %+v, want the paused publication's state commit to block the next one", snapshot) } release() - if err := <-reloadDone; err != nil { - t.Fatalf("Reload after release: %v", err) - } + testassert.NoError(t, <-reloadDone) first, ok := nextEvent(t, sub) if !ok || !equalEvent(first, pausedEvent) { t.Fatalf("first event = %s (ok=%v), want the paused publication's event before the next revision", eventJSON(t, first), ok) @@ -314,9 +278,7 @@ func TestObservationPausedPublisherOrdersTheNextPublication(t *testing.T) { case <-time.After(200 * time.Millisecond): } release() - if err := <-getDone; err != nil { - t.Fatalf("workspace get after release: %v", err) - } + testassert.NoError(t, <-getDone) first, ok := nextEvent(t, sub) if !ok || !equalEvent(first, pausedSecond) { t.Fatalf("first event = %s (ok=%v), want the paused publication's event first", eventJSON(t, first), ok) @@ -383,63 +345,52 @@ func pauseObservation(t *testing.T, o *observation, event Event) (<-chan struct{ func TestObservationSaturatedSubscriberIsRemovedAndHealthyOnesContinue(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) full, err := r.Subscribe(1) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) healthy, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) for _, want := range []string{"2", "3", "4"} { - if revision, err := r.Reload(context.Background()); err != nil || revision != want { - t.Fatalf("Reload = (%q, %v), want %s", revision, err, want) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == want, "Reload = (%q, %v), want %s", revision, err, want) } } first, ok := nextEvent(t, full) if !ok || eventGeneration(t, first) != "2" { t.Fatalf("saturated subscriber first event = %s (ok=%v), want revision 2", eventJSON(t, first), ok) } - if _, ok := nextEvent(t, full); ok { - t.Fatal("saturated subscriber still open, want it removed and closed") + { + _, ok := nextEvent(t, full) + testassert.Require(t, !ok, "saturated subscriber still open, want it removed and closed") } full.Close() full.Close() // idempotent after producer removal for _, want := range []string{"2", "3", "4"} { event, ok := nextEvent(t, healthy) - if !ok { - t.Fatalf("healthy subscriber closed early, want revision %s", want) - } + testassert.Require(t, ok, "healthy subscriber closed early, want revision %s", want) if eventKind(t, event) != "configuration_changed" || eventGeneration(t, event) != want { t.Fatalf("healthy subscriber event = %s, want configuration %s", eventJSON(t, event), want) } } - if revision, err := r.Reload(context.Background()); err != nil || revision != "5" { - t.Fatalf("Reload = (%q, %v), want 5", revision, err) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == "5", "Reload = (%q, %v), want 5", revision, err) } event, ok := nextEvent(t, healthy) if !ok || eventGeneration(t, event) != "5" { t.Fatalf("post-saturation event = %s (ok=%v), want revision 5", eventJSON(t, event), ok) } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) drainClosed(t, healthy) } func TestObservationSubscriptionCloseRacesPublication(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) healthy, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) var churn, reloads sync.WaitGroup for range 8 { churn.Add(1) @@ -467,16 +418,15 @@ func TestObservationSubscriptionCloseRacesPublication(t *testing.T) { reloads.Add(1) go func(i int) { defer reloads.Done() - if _, err := r.Reload(context.Background()); err != nil { - t.Errorf("Reload %d: %v", i, err) + { + _, err := r.Reload(context.Background()) + testassert.Check(t, err == nil, "Reload %d: %v", i, err) } }(i) } reloads.Wait() churn.Wait() - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) var revisions []string for _, event := range drainClosed(t, healthy) { switch eventKind(t, event) { @@ -484,16 +434,12 @@ func TestObservationSubscriptionCloseRacesPublication(t *testing.T) { revisions = append(revisions, eventGeneration(t, event)) case "scope_closed": body, err := event.AsScopeEvent() - if err != nil { - t.Fatalf("scope event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) != "runtime" { t.Fatalf("unexpected closure event %s, only the Runtime scope closes here", eventJSON(t, event)) } runtimeScope, err := body.Scope.AsRuntimeScope() - if err != nil { - t.Fatalf("runtime scope: %v", err) - } + testassert.NoError(t, err) if runtimeScope.Workspace != nil { t.Fatalf("unexpected closure event %s, only the Runtime scope closes here", eventJSON(t, event)) } @@ -505,9 +451,7 @@ func TestObservationSubscriptionCloseRacesPublication(t *testing.T) { for generation := uint64(2); generation <= 11; generation++ { want = append(want, strconv.FormatUint(generation, 10)) } - if !slices.Equal(revisions, want) { - t.Fatalf("raced publication revisions = %v, want every committed generation exactly once in order %v", revisions, want) - } + testassert.Require(t, slices.Equal(revisions, want), "raced publication revisions = %v, want every committed generation exactly once in order %v", revisions, want) } func TestRuntimeShutdownDoesNotWaitForUndrainedObservers(t *testing.T) { @@ -515,28 +459,24 @@ func TestRuntimeShutdownDoesNotWaitForUndrainedObservers(t *testing.T) { r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory()), e.ordinaryPlugin("w", ScopeWorkspace, "w.cap")) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) stalled, err := r.Subscribe(1) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) for _, want := range []string{"2", "3"} { - if revision, err := r.Reload(context.Background()); err != nil || revision != want { - t.Fatalf("Reload = (%q, %v), want %s", revision, err, want) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == want, "Reload = (%q, %v), want %s", revision, err, want) } } - if _, err := r.workspaces.get(context.Background(), ScopeInfo{Kind: ScopeWorkspace, DataDir: e.dataDir, Workspace: obsWorkspace}); err != nil { - t.Fatalf("workspace get: %v", err) + { + _, err := r.workspaces.get(context.Background(), ScopeInfo{Kind: ScopeWorkspace, DataDir: e.dataDir, Workspace: obsWorkspace}) + testassert.NoError(t, err) } closeDone := make(chan error, 1) go func() { closeDone <- r.Close(context.Background()) }() select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("shutdown waited for undrained observer output") } @@ -544,8 +484,9 @@ func TestRuntimeShutdownDoesNotWaitForUndrainedObservers(t *testing.T) { if !ok || eventGeneration(t, first) != "2" { t.Fatalf("stalled subscriber event = %s (ok=%v), want its one buffered event", eventJSON(t, first), ok) } - if _, ok := nextEvent(t, stalled); ok { - t.Fatal("stalled subscriber was never closed") + { + _, ok := nextEvent(t, stalled) + testassert.Require(t, !ok, "stalled subscriber was never closed") } e.assertLockReleased(t) } @@ -555,13 +496,9 @@ func TestObservationSubscriberLossDoesNotAffectExecution(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(store), e.ordinaryPlugin("w", ScopeWorkspace, "w.cap")) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(context.Background(), e.dataDir, "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID gate := make(chan struct{}) e.server.setHold(gate) @@ -572,31 +509,28 @@ func TestObservationSubscriberLossDoesNotAffectExecution(t *testing.T) { t.Fatal("the gated execution never reached its model request") } full, err := r.Subscribe(1) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) healthy, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) for _, want := range []string{"2", "3"} { - if revision, err := r.Reload(context.Background()); err != nil || revision != want { - t.Fatalf("Reload during execution = (%q, %v), want %s", revision, err, want) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == want, "Reload during execution = (%q, %v), want %s", revision, err, want) } } close(gate) - if rec := awaitOperation(t, r, sessionID, "op-observed", harness.OperationSuccess); rec.State.Status != harness.OperationSuccess { - t.Fatalf("Operation state = %+v, want success: subscriber saturation changed no execution outcome", rec.State) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + rec := awaitOperation(t, r, sessionID, "op-observed", harness.OperationSuccess) + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "Operation state = %+v, want success: subscriber saturation changed no execution outcome", rec.State) } + testassert.NoError(t, r.Close(context.Background())) first, ok := nextEvent(t, full) if !ok || eventGeneration(t, first) != "2" { t.Fatalf("saturated subscriber event = %s (ok=%v), want revision 2", eventJSON(t, first), ok) } - if _, ok := nextEvent(t, full); ok { - t.Fatal("saturated subscriber was never removed") + { + _, ok := nextEvent(t, full) + testassert.Require(t, !ok, "saturated subscriber was never removed") } want := []Event{ configurationChangedEvent(2), @@ -621,16 +555,10 @@ func TestObservationSubscriberLossDoesNotAffectExecution(t *testing.T) { continue case "session_changed": body, err := event.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session event body: %v", err) - } + testassert.NoError(t, err) invalidation, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("passive invalidation scope: %v", err) - } - if invalidation.SessionId != sessionID { - t.Fatalf("passive invalidation for %q, want the real session", invalidation.SessionId) - } + testassert.NoError(t, err) + testassert.Equal(t, invalidation.SessionId, sessionID) case "text_delta", "tool_started", "tool_finished": if err := assertOperationScoped(t, event, sessionID, "op-observed"); err != nil { t.Fatalf("passive progress %s: %v", eventJSON(t, event), err) @@ -652,27 +580,19 @@ func progressEventScope(t *testing.T, event Event) protocol.Scope { switch eventKind(t, event) { case "text_delta": body, err := event.AsTextDeltaEvent() - if err != nil { - t.Fatalf("delta event body: %v", err) - } + testassert.NoError(t, err) return body.Scope case "refusal_delta": body, err := event.AsRefusalDeltaEvent() - if err != nil { - t.Fatalf("refusal event body: %v", err) - } + testassert.NoError(t, err) return body.Scope case "tool_started": body, err := event.AsToolStartedEvent() - if err != nil { - t.Fatalf("tool event body: %v", err) - } + testassert.NoError(t, err) return body.Scope case "tool_finished": body, err := event.AsToolFinishedEvent() - if err != nil { - t.Fatalf("tool event body: %v", err) - } + testassert.NoError(t, err) return body.Scope } t.Fatalf("event %s is not a progress event", eventJSON(t, event)) @@ -688,9 +608,7 @@ func assertOperationScoped(t *testing.T, event Event, sessionID, operationID str return fmt.Errorf("progress carries the %s scope, want the operation scope", scopeKind(t, scope)) } body, err := scope.AsOperationScope() - if err != nil { - t.Fatalf("operation scope: %v", err) - } + testassert.NoError(t, err) if body.SessionId != sessionID || body.OperationId != operationID { return errors.New("progress carries a foreign session or operation identity") } @@ -718,9 +636,7 @@ func TestObservationRefusalDeltaProgress(t *testing.T) { } }() sub, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) session := bg.session("refusal") // The wire dispatch: the request whose history carries no refusal // receives the refusal-only turn; the later one — whose history @@ -754,30 +670,20 @@ func TestObservationRefusalDeltaProgress(t *testing.T) { for { select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed before the drain finished") - } + testassert.Require(t, ok, "the subscription closed before the drain finished") var entry progress switch eventKind(t, event) { case "text_delta": body, err := event.AsTextDeltaEvent() - if err != nil { - t.Fatalf("delta event body: %v", err) - } + testassert.NoError(t, err) scope, err := body.Scope.AsOperationScope() - if err != nil { - t.Fatalf("text progress scope: %v", err) - } + testassert.NoError(t, err) entry = progress{kind: "text_delta", content: body.Content, op: scope.OperationId} case "refusal_delta": body, err := event.AsRefusalDeltaEvent() - if err != nil { - t.Fatalf("refusal event body: %v", err) - } + testassert.NoError(t, err) scope, err := body.Scope.AsOperationScope() - if err != nil { - t.Fatalf("refusal progress scope: %v", err) - } + testassert.NoError(t, err) entry = progress{kind: "refusal_delta", content: body.Content, op: scope.OperationId} default: // committed and lifecycle publications interleave with progress @@ -803,9 +709,7 @@ func TestObservationRefusalDeltaProgress(t *testing.T) { {kind: "refusal_delta", content: " but no", op: "op-mixed"}, {kind: "refusal_delta", content: " can do.", op: "op-mixed"}, } - if !slices.Equal(got, want) { - t.Fatalf("progress = %+v, want the exact fragment order and operation identity %+v", got, want) - } + testassert.Require(t, slices.Equal(got, want), "progress = %+v, want the exact fragment order and operation identity %+v", got, want) }) } @@ -816,20 +720,12 @@ func TestObservationRefusalDeltaProgress(t *testing.T) { func lessPair(t *testing.T, a, b protocol.SessionRevision) bool { t.Helper() ad, err := strconv.ParseInt(a.DurableRevision, 10, 64) - if err != nil { - t.Fatalf("revision durable member %q is not a decimal integer", a.DurableRevision) - } + testassert.Require(t, err == nil, "revision durable member %q is not a decimal integer", a.DurableRevision) bd, err := strconv.ParseInt(b.DurableRevision, 10, 64) - if err != nil { - t.Fatalf("revision durable member %q is not a decimal integer", b.DurableRevision) - } + testassert.Require(t, err == nil, "revision durable member %q is not a decimal integer", b.DurableRevision) al, err := strconv.ParseUint(a.LocalRevision, 10, 64) - if err != nil { - t.Fatalf("revision local member %q is not a decimal integer", a.LocalRevision) - } + testassert.Require(t, err == nil, "revision local member %q is not a decimal integer", a.LocalRevision) bl, err := strconv.ParseUint(b.LocalRevision, 10, 64) - if err != nil { - t.Fatalf("revision local member %q is not a decimal integer", b.LocalRevision) - } + testassert.Require(t, err == nil, "revision local member %q is not a decimal integer", b.LocalRevision) return ad < bd || (ad == bd && al < bl) } diff --git a/runtime/preparation.go b/runtime/preparation.go index 92fba06e..cf1a5cc8 100644 --- a/runtime/preparation.go +++ b/runtime/preparation.go @@ -429,8 +429,6 @@ func (p *preparation) concretePrepare(_ context.Context, req harness.Preparation // transport are kept. The recorded compact configuration is always the // final effective model's. compactRef := sel.agent.Model - compactWindow := entry.ContextWindow - compactReserve := outputReserve(entry.MaxOutputTokens) compactTransport := transport if !compactType.Model.IsZero() && compactType.Model != sel.agent.Model { compactProvider, compactEntry, lookupErr := snapshot.catalog.Lookup(catalog.ModelRef{Provider: compactType.Model.Provider, Model: compactType.Model.Model}) @@ -445,8 +443,6 @@ func (p *preparation) concretePrepare(_ context.Context, req harness.Preparation built, buildErr := p.concreteTransport(compactProvider, compactEntry, compactType.Model, compactKey) if buildErr == nil { compactRef = compactType.Model - compactWindow = compactEntry.ContextWindow - compactReserve = outputReserve(compactEntry.MaxOutputTokens) compactTransport = built } } @@ -493,10 +489,8 @@ func (p *preparation) concretePrepare(_ context.Context, req harness.Preparation Readonly: sel.agent.Readonly, WriteDir: sel.agent.WriteDir, Compact: harness.CompactCapture{ - Model: compactRef, - ContextWindow: compactWindow, - OutputReserve: compactReserve, - SystemPrompt: compactType.Prompt, + Model: compactRef, + SystemPrompt: compactType.Prompt, }, } workspace := req.Session.Identity.Workspace diff --git a/runtime/preparation_test.go b/runtime/preparation_test.go index 602cf78e..f8d593ce 100644 --- a/runtime/preparation_test.go +++ b/runtime/preparation_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -49,20 +50,9 @@ func runtimeNormalize(call model.ToolCall) (json.RawMessage, error) { // rows never accidentally test compaction. The "zero" model keeps the // zero-window rejection row. func prepConfigDocument(endpoint, tag string) string { - return `{ - "providers": { - "prov": { - "transport": {"base_url": "` + endpoint + `", "api_key_env": ""}, - "discovery": false, - "models": { - "m": {"name": "M", "context_window": 262144, "max_output_tokens": 4096}, - "zero": {"name": "Z", "context_window": 0} - } - } - }, - "plugins": {"hooks": {"tag": "` + tag + `"} - } -}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"m":{"name":"M","context_window":262144,"max_output_tokens":4096},`+ + `"zero":{"name":"Z","context_window":0}}`) + `},"plugins":{"hooks":{"tag":` + fixtureString(tag) + `}}}` } // prepPolicyConfigDocument adds one global deny rule for the declared @@ -375,14 +365,8 @@ func newPrepEnv(t *testing.T, store harness.Storage) *prepEnv { for i, name := range []string{"arg.runtime", "arg.workspace", "arg.operation", "arg.agent"} { e.argHooks[i] = &recordArgumentHook{replace: json.RawMessage(`{"repaired":"` + name + `"}`)} } - r, err := Open(context.Background(), Options{ - DataDir: e.dataDir, - ConfigPath: e.configPath, - Plugins: append(e.plugins(), e.storagePlugin(store)), - }) - if err != nil { - t.Fatalf("Open: %v", err) - } + r, err := e.open(context.Background(), append(e.plugins(), e.storagePlugin(store))...) + testassert.NoError(t, err) e.r = r t.Cleanup(func() { _ = e.converge() }) return e @@ -482,19 +466,7 @@ func (e *prepEnv) session(agentType string) string { // submit runs one public Submit through the Runtime's actual Harness, // returning the reported result even on error exactly as the Harness does. func (e *prepEnv) submit(ctx context.Context, sessionID, operationID, text string, mode harness.MessageMode) (harness.SubmitResult, error) { - var res harness.SubmitResult - err := e.r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - var err error - res, err = h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: mode, - }) - return err - }) - return res, err + return submitRawThroughRuntime(ctx, e.r, sessionID, operationID, text, mode) } // readOperation, snapshotSession and fork run their public Harness @@ -637,9 +609,7 @@ func eachPrepStore(t *testing.T, run func(t *testing.T, store harness.Storage)) }) t.Run("sqlite", func(t *testing.T) { store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) defer store.Close() run(t, store) }) @@ -665,16 +635,12 @@ func TestPreparationHappyPathThroughHarness(t *testing.T) { res := e.admit(session, "op-1", "hello") got := res.Operation.Admission.Execution - if !slices.Equal(got.Capabilities, []string{"hook.first", "cap.shared", "hook.second"}) || - !slices.Equal(toolNames(got.Tools), []string{"echo", "read"}) || - got.Model != prepModelRef || got.ConfigurationRevision != "1" || - !strings.HasSuffix(got.SystemPrompt, "|first|second") { - t.Fatalf("committed capture = %+v, want the post-hook selection capture", got) - } + testassert.Require(t, slices.Equal(got.Capabilities, []string{"hook.first", "cap.shared", "hook.second"}) && slices.Equal(toolNames(got.Tools), []string{"echo", "read"}) && got.Model == prepModelRef && got.ConfigurationRevision == "1" && strings.HasSuffix(got.SystemPrompt, "|first|second"), "committed capture = %+v, want the post-hook selection capture", got) wantCalls := []hookCall{{revision: "1", tag: "A"}} for _, hook := range e.hooks { - if calls := hook.calls(); !slices.Equal(calls, wantCalls) { - t.Fatalf("hook %s calls = %v, want the captured revision %v", hook.name, calls, wantCalls) + { + calls := hook.calls() + testassert.Require(t, slices.Equal(calls, wantCalls), "hook %s calls = %v, want the captured revision %v", hook.name, calls, wantCalls) } } @@ -683,19 +649,16 @@ func TestPreparationHappyPathThroughHarness(t *testing.T) { // The wire request carried the committed capture: the composed system // prompt and the advertised tool set. - if n := e.server.requests(); n != 1 { - t.Fatalf("HTTP requests = %d, want the one admitted request", n) - } + testassert.Equal(t, e.server.requests(), 1) doc := decodeWireChatBody(t, e.server.bodyAt(0)) if doc.Model != "m" || !slices.Equal(wireToolNames(doc), []string{"echo", "read"}) { t.Fatalf("wire request = model %q tools %v, want the captured model and advertised tools", doc.Model, wireToolNames(doc)) } - if len(doc.Messages) == 0 || doc.Messages[0].Role != "system" { - t.Fatalf("wire messages = %+v, want the leading system prompt", doc.Messages) - } + testassert.Require(t, len(doc.Messages) != 0 && doc.Messages[0].Role == "system", "wire messages = %+v, want the leading system prompt", doc.Messages) var systemText string - if err := json.Unmarshal(doc.Messages[0].Content, &systemText); err != nil || systemText != got.SystemPrompt { - t.Fatalf("wire system prompt = %q (err %v), want the committed capture's %q", systemText, err, got.SystemPrompt) + { + err := json.Unmarshal(doc.Messages[0].Content, &systemText) + testassert.Require(t, err == nil && systemText == got.SystemPrompt, "wire system prompt = %q (err %v), want the committed capture's %q", systemText, err, got.SystemPrompt) } // Hooks ran in selected order inside the preparation guard; the owner's @@ -707,18 +670,13 @@ func TestPreparationHappyPathThroughHarness(t *testing.T) { "op-open:op-1", "ag-open", "ag-close", "op-close", } - if events := e.events.all(); !slices.Equal(events, wantEvents) { - t.Fatalf("events = %v, want %v", events, wantEvents) - } - if got := e.hookOpens.Load(); got != 1 { - t.Fatalf("hook factories ran %d times, want 1", got) - } - if got := e.opOpens.Load(); got != 1 { - t.Fatalf("Operation scope opened %d times, want 1", got) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + { + events := e.events.all() + testassert.Require(t, slices.Equal(events, wantEvents), "events = %v, want %v", events, wantEvents) } + testassert.Equal(t, e.hookOpens.Load(), 1) + testassert.Equal(t, e.opOpens.Load(), 1) + testassert.NoError(t, e.converge()) }) } @@ -775,8 +733,9 @@ func TestPreparationReloadDoesNotRecaptureInFlightPreparation(t *testing.T) { e.server.setHold(hold) e.admit(session, "op-1", "one") <-e.server.arrived - if res, err := e.submit(context.Background(), session, "op-2", "two", tc.mode); err != nil || res.Disposition != tc.want { - t.Fatalf("buffered submit = %+v err %v, want disposition %s", res, err, tc.want) + { + res, err := e.submit(context.Background(), session, "op-2", "two", tc.mode) + testassert.Require(t, err == nil && res.Disposition == tc.want, "buffered submit = %+v err %v, want disposition %s", res, err, tc.want) } e.hooks[0].parkNext(release, arrived) close(hold) // op-1 converges; the post-terminal drain reaches the parked hook @@ -822,8 +781,9 @@ func TestPreparationReloadDoesNotRecaptureInFlightPreparation(t *testing.T) { `"dual": {"model": "prov/m", "system_prompt": "simple", "tools": ["echo"], "capabilities": ["hook.first", "cap.shared"]}`, `"dual": {"model": "prov/m", "system_prompt": "simple", "tools": ["echo"], "capabilities": ["hook.first", "cap.shared"], "readonly": true, "write_dir": "/tmp/reload"}`, 1) writeServiceFile(t, agents.PathForConfig(e.configPath), reloadAgents) - if _, err := e.reload(); err != nil { - t.Fatalf("reload publish: %v", err) + { + _, err := e.reload() + testassert.NoError(t, err) } close(release) if tc.kind != "drained" { @@ -833,9 +793,7 @@ func TestPreparationReloadDoesNotRecaptureInFlightPreparation(t *testing.T) { mu.Lock() err := submitErr mu.Unlock() - if err != nil { - t.Fatalf("held admission failed: %v", err) - } + testassert.NoError(t, err) // The in-flight admission committed revision 1 and the // captured constraints. @@ -848,28 +806,14 @@ func TestPreparationReloadDoesNotRecaptureInFlightPreparation(t *testing.T) { } else { inFlight = e.awaitTerminal(session, "op-2", harness.OperationSuccess) } - if got := inFlight.Admission.Execution.ConfigurationRevision; got != "1" { - t.Fatalf("overlapping-reload admission committed revision %q, want the captured 1", got) - } - if inFlight.Admission.Execution.Readonly || inFlight.Admission.Execution.WriteDir != "" { - t.Fatalf("reloaded constraints leaked into the committed capture: readonly=%v write_dir=%q, want the captured false \"\"", - inFlight.Admission.Execution.Readonly, inFlight.Admission.Execution.WriteDir) - } + testassert.Require(t, inFlight.Admission.Execution.ConfigurationRevision == "1" && !inFlight.Admission.Execution.Readonly && inFlight.Admission.Execution.WriteDir == "", "got %v, want %v; reloaded constraints leaked into the committed capture: readonly=%v write_dir=%q, want the captured false \"\"", inFlight.Admission.Execution.ConfigurationRevision, "1", inFlight.Admission.Execution.Readonly, inFlight.Admission.Execution.WriteDir) // The next capture uses revision 2 and the reloaded // constraints. e.admitOrBuffer(session, "op-next", "next") next := e.awaitTerminal(session, "op-next", harness.OperationSuccess) - if got := next.Admission.Execution.ConfigurationRevision; got != "2" { - t.Fatalf("next admission revision = %q, want 2", got) - } - if !next.Admission.Execution.Readonly || next.Admission.Execution.WriteDir != "/tmp/reload" { - t.Fatalf("next admission constraints = readonly %v write_dir %q, want the reloaded true \"/tmp/reload\"", - next.Admission.Execution.Readonly, next.Admission.Execution.WriteDir) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.Require(t, next.Admission.Execution.ConfigurationRevision == "2" && next.Admission.Execution.Readonly && next.Admission.Execution.WriteDir == "/tmp/reload", "got %v, want %v; next admission constraints = readonly %v write_dir %q, want the reloaded true \"/tmp/reload\"", next.Admission.Execution.ConfigurationRevision, "2", next.Admission.Execution.Readonly, next.Admission.Execution.WriteDir) + testassert.NoError(t, e.converge()) if tc.kind == "drained" { // Buffered messages captured only on delivery, and the @@ -888,9 +832,7 @@ func TestPreparationReloadDoesNotRecaptureInFlightPreparation(t *testing.T) { } } } - if cleanup < 0 || secondHook < 0 || cleanup > secondHook { - t.Fatalf("events = %v, want the first scope close before the successor hook", events) - } + testassert.Require(t, cleanup >= 0 && secondHook >= 0 && cleanup <= secondHook, "events = %v, want the first scope close before the successor hook", events) } }) }) @@ -910,36 +852,24 @@ func TestPreparationDeliveryIdentity(t *testing.T) { e.admit(session, "op-1", "one") <-e.server.arrived - if res, err := e.submit(context.Background(), session, "op-1", "again", harness.MessageModeRegular); err != nil || res.Disposition != harness.DispositionExisting { - t.Fatalf("reused id = %+v err %v, want existing without preparation", res, err) - } - if res, err := e.submit(context.Background(), session, "op-2", "two", harness.MessageModeQueued); err != nil || res.Disposition != harness.DispositionQueued { - t.Fatalf("queued = %+v err %v, want queued", res, err) - } - if calls := len(e.hooks[0].calls()); calls != 1 { - t.Fatalf("preparation hook calls = %d, want only the admitted execution prepared so far", calls) - } - if got := e.server.requests(); got != 1 { - t.Fatalf("HTTP requests = %d, want only the admitted execution's request so far", got) + { + res, err := e.submit(context.Background(), session, "op-1", "again", harness.MessageModeRegular) + testassert.Require(t, err == nil && res.Disposition == harness.DispositionExisting, "reused id = %+v err %v, want existing without preparation", res, err) } - if got := e.opOpens.Load() + e.agOpens.Load(); got != 2 { - t.Fatalf("short-scope opens = %d, want the one admitted execution's pair", got) + { + res, err := e.submit(context.Background(), session, "op-2", "two", harness.MessageModeQueued) + testassert.Require(t, err == nil && res.Disposition == harness.DispositionQueued, "queued = %+v err %v, want queued", res, err) } + testassert.Equal(t, len(e.hooks[0].calls()), 1) + testassert.Equal(t, e.server.requests(), 1) + testassert.Equal(t, e.opOpens.Load()+e.agOpens.Load(), 2) close(hold) e.awaitTerminal(session, "op-1", harness.OperationSuccess) e.awaitTerminal(session, "op-2", harness.OperationSuccess) - if calls := len(e.hooks[0].calls()); calls != 2 { - t.Fatalf("preparation hook calls = %d, want the drained delivery to capture once", calls) - } - if got := e.server.requests(); got != 2 { - t.Fatalf("HTTP requests = %d, want the drained delivery to reach the model once", got) - } - if got := e.opOpens.Load() + e.agOpens.Load(); got != 4 { - t.Fatalf("short-scope opens = %d, want one pair per admitted execution", got) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.Equal(t, len(e.hooks[0].calls()), 2) + testassert.Equal(t, e.server.requests(), 2) + testassert.Equal(t, e.opOpens.Load()+e.agOpens.Load(), 4) + testassert.NoError(t, e.converge()) }) } @@ -962,27 +892,16 @@ func TestPreparationRejectsAdmissionAtTheBoundary(t *testing.T) { t.Cleanup(func() { e.converge() }) session := e.session(tc.agentType) res, err := e.submit(context.Background(), session, "op-x", "x", harness.MessageModeRegular) - if !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit error = %v, want harness.ErrInvalid", err) - } - if res.Disposition == harness.DispositionAdmitted { - t.Fatalf("rejected preparation admitted an Operation") - } - if got := e.opOpens.Load() + e.agOpens.Load(); got != 0 { - t.Fatalf("short scopes opened %d times without admission", got) - } - if _, err := e.readOperation(context.Background(), session, "op-x"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("rejected admission left an Operation: %v", err) - } - if calls := len(e.hooks[0].calls()) + len(e.hooks[1].calls()); calls != 0 { - t.Fatalf("rejected admission ran %d hooks", calls) - } - if got := e.server.requests(); got != 0 { - t.Fatalf("rejected admission made %d HTTP requests", got) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit error = %v, want harness.ErrInvalid", err) + testassert.Require(t, res.Disposition != harness.DispositionAdmitted, "rejected preparation admitted an Operation") + testassert.Equal(t, e.opOpens.Load()+e.agOpens.Load(), 0) + { + _, err := e.readOperation(context.Background(), session, "op-x") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "rejected admission left an Operation: %v", err) } + testassert.Equal(t, len(e.hooks[0].calls())+len(e.hooks[1].calls()), 0) + testassert.Equal(t, e.server.requests(), 0) + testassert.NoError(t, e.converge()) }) }) } @@ -1005,33 +924,28 @@ func TestPreparationPreparationHookErrorOnDrainedAndForkDelivery(t *testing.T) { e.admit(session, "op-1", "one") <-e.server.arrived e.hooks[0].failNext(errHook) - if res, err := e.submit(context.Background(), session, "op-2", "two", harness.MessageModeQueued); err != nil || res.Disposition != harness.DispositionQueued { - t.Fatalf("queued submit = %+v err %v, want queued", res, err) + { + res, err := e.submit(context.Background(), session, "op-2", "two", harness.MessageModeQueued) + testassert.Require(t, err == nil && res.Disposition == harness.DispositionQueued, "queued submit = %+v err %v, want queued", res, err) } - if res, err := e.submit(context.Background(), session, "op-3", "three", harness.MessageModeQueued); err != nil || res.Disposition != harness.DispositionQueued { - t.Fatalf("queued submit = %+v err %v, want queued", res, err) + { + res, err := e.submit(context.Background(), session, "op-3", "three", harness.MessageModeQueued) + testassert.Require(t, err == nil && res.Disposition == harness.DispositionQueued, "queued submit = %+v err %v, want queued", res, err) } close(hold) e.awaitTerminal(session, "op-1", harness.OperationSuccess) e.awaitTerminal(session, "op-3", harness.OperationSuccess) - if _, err := e.readOperation(context.Background(), session, "op-2"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("rejected delivery left an Operation: %v", err) + { + _, err := e.readOperation(context.Background(), session, "op-2") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "rejected delivery left an Operation: %v", err) } entries, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { - if entry.OperationID == "op-2" { - t.Fatalf("rejected delivery left entry %s (%s) behind", entry.ID, entry.Kind) - } - } - if opens := countEvents(e.events.all(), "op-open:op-2"); opens != 0 { - t.Fatalf("the rejected delivery opened its execution %d times", opens) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + testassert.Require(t, entry.OperationID != "op-2", "rejected delivery left entry %s (%s) behind", entry.ID, entry.Kind) } + testassert.Equal(t, countEvents(e.events.all(), "op-open:op-2"), 0) + testassert.NoError(t, e.converge()) }) }) t.Run("failed Fork publishes no destination and leaves the source unchanged", func(t *testing.T) { @@ -1043,17 +957,11 @@ func TestPreparationPreparationHookErrorOnDrainedAndForkDelivery(t *testing.T) { e.awaitTerminal(session, "op-1", harness.OperationSuccess) boundary := e.inputEntry(session, "op-1") sessionsBefore, err := store.ListSessionIDs(context.Background()) - if err != nil { - t.Fatalf("ListSessionIDs: %v", err) - } + testassert.NoError(t, err) entriesBefore, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) sourceBeforeView, err := e.sessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) e.hooks[0].failNext(errHook) res, err := e.fork(context.Background(), harness.ForkRequest{ SourceSessionID: session, @@ -1061,41 +969,23 @@ func TestPreparationPreparationHookErrorOnDrainedAndForkDelivery(t *testing.T) { OperationID: "fork-1", Content: []model.ContentPart{{Kind: model.PartText, Text: "fork input"}}, }) - if !errors.Is(err, errHook) { - t.Fatalf("fork with a failing hook = %+v err %v, want the hook's own error", res, err) - } + testassert.Require(t, errors.Is(err, errHook), "fork with a failing hook = %+v err %v, want the hook's own error", res, err) sessionsAfter, err := store.ListSessionIDs(context.Background()) - if err != nil { - t.Fatalf("ListSessionIDs: %v", err) - } - if !slices.Equal(sessionsAfter, sessionsBefore) { - t.Fatalf("session listing after the failed fork = %v, want unchanged %v", sessionsAfter, sessionsBefore) - } + testassert.NoError(t, err) + testassert.Require(t, slices.Equal(sessionsAfter, sessionsBefore), "session listing after the failed fork = %v, want unchanged %v", sessionsAfter, sessionsBefore) entriesAfter, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } - if len(entriesAfter) != len(entriesBefore) { - t.Fatalf("source entries after the failed fork = %d, want unchanged %d", len(entriesAfter), len(entriesBefore)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(entriesAfter), len(entriesBefore)) sourceAfterView, err := e.sessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) // The run's own retirement publications advance the local counter // between the two reads; the durable metadata and the register // totals must stay exactly as they were. afterHeader, beforeHeader := sourceAfterView.Header, sourceBeforeView.Header afterHeader.Revision.LocalRevision, beforeHeader.Revision.LocalRevision = 0, 0 - if !reflect.DeepEqual(afterHeader, beforeHeader) || !reflect.DeepEqual(sourceAfterView.TotalUsage, sourceBeforeView.TotalUsage) { - t.Fatalf("source session after the failed fork changed: %+v vs %+v", sourceAfterView, sourceBeforeView) - } - if calls := len(e.hooks[0].calls()); calls != 2 { - t.Fatalf("hook.first calls = %v, want the source admission plus the failed fork", calls) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.Require(t, reflect.DeepEqual(afterHeader, beforeHeader) && reflect.DeepEqual(sourceAfterView.TotalUsage, sourceBeforeView.TotalUsage), "source session after the failed fork changed: %+v vs %+v", sourceAfterView, sourceBeforeView) + testassert.Equal(t, len(e.hooks[0].calls()), 2) + testassert.NoError(t, e.converge()) }) }) } @@ -1138,19 +1028,16 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { e := newPrepEnv(t, store) e.hooks[0].set(nil, v.mutate) session := e.session("wide") - if _, err := e.submit(context.Background(), session, "op-1", "x", harness.MessageModeRegular); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit error = %v, want harness.ErrInvalid", err) + { + _, err := e.submit(context.Background(), session, "op-1", "x", harness.MessageModeRegular) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit error = %v, want harness.ErrInvalid", err) } events := strings.Join(e.events.all(), ",") if !strings.Contains(events, "hook:hook.first") || strings.Contains(events, "hook:hook.second") { t.Fatalf("events = %v, want the failing hook to stop the rest", e.events.all()) } - if got := e.opOpens.Load() + e.agOpens.Load(); got != 0 { - t.Fatalf("short scope factories ran after a rejected hook") - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.Equal(t, e.opOpens.Load()+e.agOpens.Load(), 0) + testassert.NoError(t, e.converge()) }) }) } @@ -1175,15 +1062,15 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { e := newPrepEnv(t, store) e.hooks[1].set(nil, v.mutate) session := e.session("wide") - if _, err := e.submit(context.Background(), session, "op-1", "x", harness.MessageModeRegular); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit error = %v, want harness.ErrInvalid", err) - } - if _, err := e.readOperation(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("rejected hook left an Operation: %v", err) + { + _, err := e.submit(context.Background(), session, "op-1", "x", harness.MessageModeRegular) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit error = %v, want harness.ErrInvalid", err) } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + { + _, err := e.readOperation(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "rejected hook left an Operation: %v", err) } + testassert.NoError(t, e.converge()) }) }) } @@ -1206,19 +1093,20 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { // chain), and mutating the second hook's own copy cannot // retroactively change the array the first hook received and // retained. - if firstTools, firstDesc := e.hooks[0].retainedInputs(); firstDesc != "" || len(firstTools) == 0 || firstTools[0].Description != "hook.first" { - t.Fatalf("hook.first input = %+v description %q, want the unaliased pre-hook capture it mutated", firstTools, firstDesc) + { + firstTools, firstDesc := e.hooks[0].retainedInputs() + testassert.Require(t, firstDesc == "" && len(firstTools) != 0 && firstTools[0].Description == "hook.first", "hook.first input = %+v description %q, want the unaliased pre-hook capture it mutated", firstTools, firstDesc) } - if _, secondDesc := e.hooks[1].retainedInputs(); secondDesc != "hook.first" { - t.Fatalf("hook.second input description at entry = %q, want the previous owned result", secondDesc) + { + _, secondDesc := e.hooks[1].retainedInputs() + testassert.Equal(t, secondDesc, "hook.first") } // The committed capture carries the final owned result. - if got := res.Operation.Admission.Execution.Tools; len(got) == 0 || got[0].Description != "hook.second" { - t.Fatalf("committed tools = %+v, want the final owned result", got) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + { + got := res.Operation.Admission.Execution.Tools + testassert.Require(t, len(got) != 0 && got[0].Description == "hook.second", "committed tools = %+v, want the final owned result", got) } + testassert.NoError(t, e.converge()) }) }) t.Run("hook error aborts admission", func(t *testing.T) { @@ -1226,15 +1114,15 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { e := newPrepEnv(t, store) e.hooks[0].set(errHook, nil) session := e.session("wide") - if _, err := e.submit(context.Background(), session, "op-1", "x", harness.MessageModeRegular); !errors.Is(err, errHook) { - t.Fatalf("submit error = %v, want %v", err, errHook) - } - if _, err := e.readOperation(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("failed hook left an Operation: %v", err) + { + _, err := e.submit(context.Background(), session, "op-1", "x", harness.MessageModeRegular) + testassert.Require(t, errors.Is(err, errHook), "submit error = %v, want %v", err, errHook) } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + { + _, err := e.readOperation(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "failed hook left an Operation: %v", err) } + testassert.NoError(t, e.converge()) }) }) t.Run("cancellation while a hook is parked aborts admission", func(t *testing.T) { @@ -1251,15 +1139,15 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { }() <-started cancel() - if err := <-done; !errors.Is(err, context.Canceled) { - t.Fatalf("canceled submit error = %v, want context.Canceled", err) - } - if _, err := e.readOperation(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("canceled preparation left an Operation: %v", err) + { + err := <-done + testassert.Require(t, errors.Is(err, context.Canceled), "canceled submit error = %v, want context.Canceled", err) } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + { + _, err := e.readOperation(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "canceled preparation left an Operation: %v", err) } + testassert.NoError(t, e.converge()) }) }) t.Run("cancellation between hooks aborts admission", func(t *testing.T) { @@ -1276,14 +1164,16 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { }() <-started cancel() - if err := <-done; !errors.Is(err, context.Canceled) { - t.Fatalf("canceled submit error = %v, want context.Canceled", err) + { + err := <-done + testassert.Require(t, errors.Is(err, context.Canceled), "canceled submit error = %v, want context.Canceled", err) } if slices.Contains(e.events.all(), "hook:hook.second") { t.Fatalf("later hook ran after cancellation: %v", e.events.all()) } - if _, err := e.readOperation(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("canceled hook run left an Operation: %v", err) + { + _, err := e.readOperation(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "canceled hook run left an Operation: %v", err) } _ = e.converge() }) @@ -1301,19 +1191,17 @@ func TestPreparationHooksAreOrderedAndValidated(t *testing.T) { done <- err }() <-arrived - if err := e.r.runtimeScope.close(); err != nil { - t.Fatalf("runtime scope close: %v", err) - } + testassert.NoError(t, e.r.runtimeScope.close()) close(release) - if err := <-done; !errors.Is(err, ErrClosed) { - t.Fatalf("submit after closed bindings error = %v, want ErrClosed", err) - } - if _, err := e.readOperation(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("rejected hook admission left an Operation: %v", err) + { + err := <-done + testassert.Require(t, errors.Is(err, ErrClosed), "submit after closed bindings error = %v, want ErrClosed", err) } - if got := e.opOpens.Load() + e.agOpens.Load(); got != 0 { - t.Fatalf("short scope factories ran %d times after a closed binding view", got) + { + _, err := e.readOperation(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "rejected hook admission left an Operation: %v", err) } + testassert.Equal(t, e.opOpens.Load()+e.agOpens.Load(), 0) _ = e.converge() }) }) @@ -1338,41 +1226,40 @@ func TestPreparationDistinctSelectionsOverReusedInstances(t *testing.T) { e.admitOrBuffer(solo, "op-3", "three") third := e.awaitTerminal(solo, "op-3", harness.OperationSuccess) - if first.Admission.AgentType != "dual" { - t.Fatalf("first committed agent = %q, want dual", first.Admission.AgentType) - } - if got := first.Admission.Execution.Capabilities; !slices.Equal(got, []string{"hook.first", "cap.shared"}) { - t.Fatalf("dual committed capabilities = %v", got) - } - if got := toolNames(first.Admission.Execution.Tools); !slices.Equal(got, []string{"echo"}) { - t.Fatalf("dual committed tools = %v", got) - } - if second.Admission.AgentType != "dup" { - t.Fatalf("second committed agent = %q, want dup", second.Admission.AgentType) + testassert.Equal(t, first.Admission.AgentType, "dual") + { + got := first.Admission.Execution.Capabilities + testassert.Require(t, slices.Equal(got, []string{"hook.first", "cap.shared"}), "dual committed capabilities = %v", got) } - if got := second.Admission.Execution.Capabilities; !slices.Equal(got, []string{"hook.second", "cap.other"}) { - t.Fatalf("dup committed capabilities = %v", got) + { + got := toolNames(first.Admission.Execution.Tools) + testassert.Require(t, slices.Equal(got, []string{"echo"}), "dual committed tools = %v", got) } - if got := toolNames(second.Admission.Execution.Tools); !slices.Equal(got, []string{"echo", "read"}) { - t.Fatalf("dup committed tools = %v, want the first-occurrence unique list", got) + testassert.Equal(t, second.Admission.AgentType, "dup") + { + got := second.Admission.Execution.Capabilities + testassert.Require(t, slices.Equal(got, []string{"hook.second", "cap.other"}), "dup committed capabilities = %v", got) } - if got := third.Admission.Execution.Capabilities; len(got) != 0 { - t.Fatalf("solo committed capabilities = %v, want none", got) + { + got := toolNames(second.Admission.Execution.Tools) + testassert.Require(t, slices.Equal(got, []string{"echo", "read"}), "dup committed tools = %v, want the first-occurrence unique list", got) } - if got := e.hookOpens.Load() + e.capOpens.Load(); got != 2 { - t.Fatalf("long-lived factories ran %d times, want exactly one per plugin", got) + { + got := third.Admission.Execution.Capabilities + testassert.Require(t, len(got) == 0, "solo committed capabilities = %v, want none", got) } + testassert.Equal(t, e.hookOpens.Load()+e.capOpens.Load(), 2) // Only the selected hook ran per admission: hook.first on dual, hook.second // on dup, neither on solo. - if calls := e.hooks[0].calls(); len(calls) != 1 { - t.Fatalf("hook.first calls = %v, want dual only", calls) - } - if calls := e.hooks[1].calls(); len(calls) != 1 { - t.Fatalf("hook.second calls = %v, want dup only", calls) + { + calls := e.hooks[0].calls() + testassert.Require(t, len(calls) == 1, "hook.first calls = %v, want dual only", calls) } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + { + calls := e.hooks[1].calls() + testassert.Require(t, len(calls) == 1, "hook.second calls = %v, want dup only", calls) } + testassert.NoError(t, e.converge()) }) } @@ -1393,24 +1280,15 @@ func TestPreparationSelectsShortScopeCapabilitiesAtOpen(t *testing.T) { session := e.session("deep") e.admit(session, "op-1", "one") rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess) - if got := hookSawOp.Load(); got != 0 { - t.Fatalf("Operation factories opened %d times during the preparation hook, want none", got) - } - if got := hookSawAg.Load(); got != 0 { - t.Fatalf("Agent factories opened %d times during the preparation hook, want none", got) - } - if got := rec.Admission.Execution.Capabilities; !slices.Equal(got, []string{"hook.first", "op.native", "ag.native"}) { - t.Fatalf("committed capabilities = %v, want the Agent's full selected names", got) - } - if got := e.opOpens.Load(); got != 1 { - t.Fatalf("Operation scope opened %d times after admission, want one", got) - } - if got := e.agOpens.Load(); got != 1 { - t.Fatalf("Agent scope opened %d times after admission, want one", got) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) + testassert.Equal(t, hookSawOp.Load(), 0) + testassert.Equal(t, hookSawAg.Load(), 0) + { + got := rec.Admission.Execution.Capabilities + testassert.Require(t, slices.Equal(got, []string{"hook.first", "op.native", "ag.native"}), "committed capabilities = %v, want the Agent's full selected names", got) } + testassert.Equal(t, e.opOpens.Load(), 1) + testassert.Equal(t, e.agOpens.Load(), 1) + testassert.NoError(t, e.converge()) }) } @@ -1428,19 +1306,17 @@ func TestPreparationExecutionLifetimeAndCleanup(t *testing.T) { e.awaitTerminal(session, "op-1", harness.OperationSuccess) e.awaitEvent("ag-close", "op-close") events := e.events.all() - if closes := countEvents(events, "ag-close"); closes != 1 { - t.Fatalf("Agent scope closed %d times, want once: %v", closes, events) + { + closes := countEvents(events, "ag-close") + testassert.Require(t, closes == 1, "Agent scope closed %d times, want once: %v", closes, events) } - if closes := countEvents(events, "op-close"); closes != 1 { - t.Fatalf("Operation scope closed %d times, want once: %v", closes, events) + { + closes := countEvents(events, "op-close") + testassert.Require(t, closes == 1, "Operation scope closed %d times, want once: %v", closes, events) } agIdx, opIdx := slices.Index(events, "ag-close"), slices.Index(events, "op-close") - if agIdx > opIdx { - t.Fatalf("events = %v, want the Agent scope closed before the Operation scope", events) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.Require(t, agIdx <= opIdx, "events = %v, want the Agent scope closed before the Operation scope", events) + testassert.NoError(t, e.converge()) }) }) t.Run("opening failure unwinds owned scopes", func(t *testing.T) { @@ -1450,25 +1326,11 @@ func TestPreparationExecutionLifetimeAndCleanup(t *testing.T) { session := e.session("dual") e.admit(session, "op-1", "one") rec := e.awaitTerminal(session, "op-1", harness.OperationFailure) - if rec.State.Terminal == nil || !strings.Contains(rec.State.Terminal.Detail, errOpen.Error()) { - t.Fatalf("operation state = %+v, want the failure carrying the opening error", rec.State) - } + testassert.Require(t, rec.State.Terminal != nil && strings.Contains(rec.State.Terminal.Detail, errOpen.Error()), "operation state = %+v, want the failure carrying the opening error", rec.State) events := e.events.all() - if !slices.Contains(events, "op-open:op-1") { - t.Fatalf("events = %v, want the Operation scope constructed before the failure", events) - } - if slices.Contains(events, "ag-open") || slices.Contains(events, "ag-close") { - t.Fatalf("events = %v, want no Agent scope constructed through the failed opening", events) - } - if !slices.Contains(events, "op-close") { - t.Fatalf("events = %v, want the owned Operation scope unwound", events) - } - if got := e.agOpens.Load(); got != 0 { - t.Fatalf("Agent factories opened %d times through the failed opening", got) - } - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.Require(t, slices.Contains(events, "op-open:op-1") && !slices.Contains(events, "ag-open") && !slices.Contains(events, "ag-close") && slices.Contains(events, "op-close"), "events = %v, want the Operation scope constructed before the failure; events = %v, want no Agent scope constructed through the failed opening; events = %v, want the owned Operation scope unwound", events, events, events) + testassert.Equal(t, e.agOpens.Load(), 0) + testassert.NoError(t, e.converge()) }) }) t.Run("cleanup error is retained without rewriting the settlement", func(t *testing.T) { @@ -1478,11 +1340,10 @@ func TestPreparationExecutionLifetimeAndCleanup(t *testing.T) { session := e.session("dual") e.admit(session, "op-1", "one") rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess) - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("cleanup failure rewrote the settlement: status %q", rec.State.Status) - } - if err := e.converge(); !errors.Is(err, errExecClose) { - t.Fatalf("Wait error = %v, want the retained cleanup failure %v", err, errExecClose) + testassert.Equal(t, rec.State.Status, harness.OperationSuccess) + { + err := e.converge() + testassert.Require(t, errors.Is(err, errExecClose), "Wait error = %v, want the retained cleanup failure %v", err, errExecClose) } }) }) @@ -1510,18 +1371,14 @@ func TestPreparationInterruptBeforeAgentScopeEntrySettlesInterruption(t *testing case <-time.After(10 * time.Second): t.Fatal("the Agent-scope opening never parked") } - if err := e.r.interruptSession(context.Background(), session); err != nil { - t.Fatalf("interruptSession: %v", err) - } + testassert.NoError(t, e.r.interruptSession(context.Background(), session)) releaseGate() rec := e.awaitTerminal(session, "op-int", harness.OperationInterruption) if rec.State.Terminal != nil && strings.Contains(rec.State.Terminal.Detail, ErrClosed.Error()) { t.Fatalf("terminal detail = %q, want the interruption identity, not the closed-scope failure", rec.State.Terminal.Detail) } e.awaitEvent("ag-close", "op-close") // the parked opening still disposed both scopes - if err := e.converge(); err != nil { - t.Fatalf("Wait: %v", err) - } + testassert.NoError(t, e.converge()) }) } @@ -1537,10 +1394,7 @@ func TestPreparationCapturesPermissionConstraints(t *testing.T) { session := e.session("locked") e.admit(session, "op-1", "one") rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess) - if !rec.Admission.Execution.Readonly || rec.Admission.Execution.WriteDir != "/tmp/pad" { - t.Fatalf("committed constraints = %v %q, want the definition's readonly true and once-trimmed %q", - rec.Admission.Execution.Readonly, rec.Admission.Execution.WriteDir, "/tmp/pad") - } + testassert.Require(t, rec.Admission.Execution.Readonly && rec.Admission.Execution.WriteDir == "/tmp/pad", "committed constraints = %v %q, want the definition's readonly true and once-trimmed %q", rec.Admission.Execution.Readonly, rec.Admission.Execution.WriteDir, "/tmp/pad") }) } @@ -1557,8 +1411,9 @@ func TestPreparationForwardsCapturedPolicyAndNormalizer(t *testing.T) { e := newPrepEnv(t, store) t.Cleanup(func() { e.converge() }) writeServiceFile(t, e.configPath, prepPolicyConfigDocument(e.server.URL, "A")) - if _, err := e.reload(); err != nil { - t.Fatalf("policy publish: %v", err) + { + _, err := e.reload() + testassert.NoError(t, err) } e.server.setScript(func(_ context.Context, body string) []string { if lastMessageRole(body) == "tool" { @@ -1568,20 +1423,15 @@ func TestPreparationForwardsCapturedPolicyAndNormalizer(t *testing.T) { }) session := e.session("solo") e.admit(session, "op-1", "one") - if rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess); rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation = %+v, want terminal success after the denial settled its call", rec.State) + { + rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess) + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "operation = %+v, want terminal success after the denial settled its call", rec.State) } normalized, executed, _ := e.echo.counters() - if executed != 0 { - t.Fatalf("concrete effects started = %d, want none for the denied call", executed) - } - if normalized != 1 { - t.Fatalf("normalizations = %d, want the committed value consumed without a second pass", normalized) - } + testassert.Equal(t, executed, 0) + testassert.Equal(t, normalized, 1) entries, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var denial bool published := 0 for _, entry := range entries { @@ -1589,8 +1439,9 @@ func TestPreparationForwardsCapturedPolicyAndNormalizer(t *testing.T) { continue } var obj map[string]json.RawMessage - if err := json.Unmarshal(entry.Payload, &obj); err != nil { - t.Fatalf("decode %s payload: %v", entry.ID, err) + { + err := json.Unmarshal(entry.Payload, &obj) + testassert.Require(t, err == nil, "decode %s payload: %v", entry.ID, err) } switch entry.Kind { case harness.EntryAssistant: @@ -1598,35 +1449,24 @@ func TestPreparationForwardsCapturedPolicyAndNormalizer(t *testing.T) { ID string `json:"id"` NormalizedArguments json.RawMessage `json:"normalized_arguments"` } - if err := json.Unmarshal(obj["tool_calls"], &calls); err != nil { - t.Fatalf("assistant tool calls: %v", err) - } + testassert.NoError(t, json.Unmarshal(obj["tool_calls"], &calls)) if len(calls) == 0 { continue // the continuation turn's text-only assistant } published++ - if len(calls) != 1 || calls[0].ID != "call-1" { - t.Fatalf("assistant tool calls = %s, want the one published call", obj["tool_calls"]) - } - if string(calls[0].NormalizedArguments) != `{"x":1}` { - t.Fatalf("committed normalized_arguments = %s, want the producer's compacted object", calls[0].NormalizedArguments) - } + testassert.Require(t, len(calls) == 1 && calls[0].ID == "call-1", "assistant tool calls = %s, want the one published call", obj["tool_calls"]) + testassert.Require(t, string(calls[0].NormalizedArguments) == `{"x":1}`, "committed normalized_arguments = %s, want the producer's compacted object", calls[0].NormalizedArguments) case harness.EntryToolResult: - if string(obj["status"]) != `"denied"` || string(obj["content"]) != `"Permission denied."` { - t.Fatalf("tool result = %s, want the fixed denial", entry.Payload) - } - if _, present := obj["metadata"]; present { - t.Fatalf("denied result carries a metadata member") + testassert.Require(t, string(obj["status"]) == `"denied"` && string(obj["content"]) == `"Permission denied."`, "tool result = %s, want the fixed denial", entry.Payload) + { + _, present := obj["metadata"] + testassert.Require(t, !present, "denied result carries a metadata member") } denial = true } } - if published != 1 || !denial { - t.Fatalf("committed entries: %d publishing assistants and denial=%v, want 1 and true", published, denial) - } - if got := e.server.requests(); got != 2 { - t.Fatalf("%d model requests, want the continuation after the settled call", got) - } + testassert.Require(t, published == 1 && denial, "committed entries: %d publishing assistants and denial=%v, want 1 and true", published, denial) + testassert.Equal(t, e.server.requests(), 2) var projected string doc := decodeWireChatBody(t, e.server.bodyAt(1)) for _, msg := range doc.Messages { @@ -1637,9 +1477,7 @@ func TestPreparationForwardsCapturedPolicyAndNormalizer(t *testing.T) { } } } - if projected != "Permission denied." { - t.Fatalf("second projection carried tool message %q, want the committed denial", projected) - } + testassert.Equal(t, projected, "Permission denied.") }) } @@ -1665,22 +1503,19 @@ func TestPreparationForwardsToolArgumentHooksAcrossScopes(t *testing.T) { }) session := e.session("hooky") e.admit(session, "op-1", "one") - if rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess); rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation = %+v, want terminal success after the hook chain", rec.State) + { + rec := e.awaitTerminal(session, "op-1", harness.OperationSuccess) + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "operation = %+v, want terminal success after the hook chain", rec.State) } _, executed, toolInput := e.echo.counters() - if executed != 1 || len(toolInput) != 1 || toolInput[0] != `{"repaired":"arg.agent"}` { - t.Fatalf("concrete tool ran %d times on %q, want once with the final committed replacement", executed, toolInput) - } + testassert.Require(t, executed == 1 && len(toolInput) == 1 && toolInput[0] == `{"repaired":"arg.agent"}`, "concrete tool ran %d times on %q, want once with the final committed replacement", executed, toolInput) // The committed hook evidence: one success hook_result per argument // hook in configured order, under the capability IDs, each repairing // from its received bytes. entries, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) type hookWire struct { HookID string `json:"hook_id"` ToolCallID string `json:"tool_call_id"` @@ -1693,53 +1528,38 @@ func TestPreparationForwardsToolArgumentHooksAcrossScopes(t *testing.T) { continue } var wire hookWire - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode hook result: %v", err) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) hookResults = append(hookResults, wire) } wantIDs := []string{"arg.runtime", "arg.workspace", "arg.operation", "arg.agent"} - if len(hookResults) != len(wantIDs) { - t.Fatalf("committed hook results = %+v, want one per argument hook", hookResults) - } + testassert.Require(t, len(hookResults) == len(wantIDs), "committed hook results = %+v, want one per argument hook", hookResults) for i, want := range wantIDs { got := hookResults[i] - if got.HookID != want || got.ToolCallID != "call-1" || got.Status != "success" { - t.Fatalf("hook result %d = %+v, want hook %q succeeding for call-1", i, got, want) - } - } - if string(hookResults[3].Arguments) != `{"repaired":"arg.agent"}` { - t.Fatalf("last committed arguments = %s, want the final hook's replacement", hookResults[3].Arguments) + testassert.Require(t, got.HookID == want && got.ToolCallID == "call-1" && got.Status == "success", "hook result %d = %+v, want hook %q succeeding for call-1", i, got, want) } + testassert.Require(t, string(hookResults[3].Arguments) == `{"repaired":"arg.agent"}`, "last committed arguments = %s, want the final hook's replacement", hookResults[3].Arguments) // The chain: the first hook received the invalid raw arguments, every // later one the preceding hook's committed replacement; the captured // Invocation revision reached every hook. for i, hook := range e.argHooks { seen, revs := hook.received() - if len(seen) != 1 { - t.Fatalf("hook %d ran %d times, want once", i, len(seen)) - } + testassert.Require(t, len(seen) == 1, "hook %d ran %d times, want once", i, len(seen)) if i > 0 && seen[0] != string(hookResults[i-1].Arguments) { t.Fatalf("hook %d received %q, want the preceding committed replacement %q", i, seen[0], hookResults[i-1].Arguments) } - if revs[0] == "" { - t.Fatalf("hook %d received an empty Invocation revision", i) - } + testassert.Require(t, revs[0] != "", "hook %d received an empty Invocation revision", i) } - if first, _ := e.argHooks[0].received(); first[0] != ` [not json ` { - t.Fatalf("first hook received %q, want the invalid raw arguments", first) + { + first, _ := e.argHooks[0].received() + testassert.Require(t, first[0] == ` [not json `, "first hook received %q, want the invalid raw arguments", first) } // The pure preparation hook in the same capability list ran at // preparation and never bound as an argument hook. - if len(e.hooks[0].calls()) == 0 { - t.Fatalf("preparation hook never ran") - } + testassert.Require(t, len(e.hooks[0].calls()) != 0, "preparation hook never ran") for _, result := range hookResults { - if result.HookID == "hook.first" { - t.Fatalf("preparation hook bound as an argument hook") - } + testassert.Require(t, result.HookID != "hook.first", "preparation hook bound as an argument hook") } }) } diff --git a/runtime/production_composition_test.go b/runtime/production_composition_test.go index 18da1035..a3c8b196 100644 --- a/runtime/production_composition_test.go +++ b/runtime/production_composition_test.go @@ -14,7 +14,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "io" "net/http" "net/http/httptest" @@ -22,7 +21,6 @@ import ( "os/exec" "path/filepath" "slices" - "strconv" "strings" "sync" "testing" @@ -33,6 +31,7 @@ import ( "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/pathutil" "github.com/MMinasyan/lightcode/internal/snapshot" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/tool" "github.com/MMinasyan/lightcode/model" ) @@ -51,64 +50,30 @@ func chainAgentsDocument(body string) string { // configured provider pointed at the scenario's server, the hook tag the // hookPlugin requires, and no sessions section. func chainPlainConfigDocument(endpoint string) string { - return `{ - "providers": { - "prov": { - "transport": {"base_url": "` + endpoint + `", "api_key_env": "PRODUCTION_TEST_KEY"}, - "discovery": false, - "models": {"gpt-5.5": {"name": "G", "context_window": 262144}} - } - }, - "plugins": {"hooks": {"tag": "T1"}} -}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "PRODUCTION_TEST_KEY", + `{"gpt-5.5":{"name":"G","context_window":262144}}`) + `},"plugins":{"hooks":{"tag":"T1"}}}` } // chainCommandConfigDocument is the readonly-git row's provider document: no // plugins section, since its composition declares no configured plugin. func chainCommandConfigDocument(endpoint string) string { - return `{ - "providers": { - "prov": { - "transport": {"base_url": "` + endpoint + `", "api_key_env": "PRODUCTION_TEST_KEY"}, - "discovery": false, - "models": {"m": {"name": "M", "context_window": 262144}} - } - } -}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "PRODUCTION_TEST_KEY", + `{"m":{"name":"M","context_window":262144}}`) + `}}` } // chainSweepConfigDocument adds the explicit sweep thresholds and the hook // tag to one provider document for the public-Open initial-sweep row. func chainSweepConfigDocument(endpoint string) string { - return `{ - "providers": { - "prov": { - "transport": {"base_url": "` + endpoint + `", "api_key_env": ""}, - "discovery": false, - "models": {"m": {"name": "M", "context_window": 4096}} - } - }, - "plugins": {"hooks": {"tag": "T1"}}, - "sessions": {"archive_after_days": 1, "delete_after_archive_days": 1} -}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"m":{"name":"M","context_window":4096}}`) + `},"plugins":{"hooks":{"tag":"T1"}},"sessions":{"archive_after_days":1,"delete_after_archive_days":1}}` } // chainModelsConfigDocument declares the two models the capability-ID row // composes: one matching the bundled GPT-family adaptation pattern, one // matching nothing. func chainModelsConfigDocument(endpoint string) string { - return `{ - "providers": { - "prov": { - "transport": {"base_url": "` + endpoint + `", "api_key_env": ""}, - "discovery": false, - "models": { - "gpt-5.5": {"name": "G", "context_window": 262144}, - "vanilla": {"name": "V", "context_window": 262144} - } - } - } -}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"gpt-5.5":{"name":"G","context_window":262144},"vanilla":{"name":"V","context_window":262144}}`) + `}}` } // rewriteWriteHook is the combined scenario's ToolArgumentsHook: it replaces @@ -602,9 +567,7 @@ func (s *chainServer) awaitRequests(t *testing.T, n int) { if got >= n { return } - if time.Now().After(deadline) { - t.Fatalf("chain server: request %d never arrived (received %d)", n, got) - } + testassert.Require(t, !time.Now().After(deadline), "chain server: request %d never arrived (received %d)", n, got) time.Sleep(5 * time.Millisecond) } } @@ -615,23 +578,12 @@ func (s *chainServer) bodyAt(i int) string { return s.bodies[i] } -// chainToolCallTurn writes one completed assistant turn publishing one tool -// call with an explicit call identity. -func chainToolCallTurn(w http.ResponseWriter, id, name, args string) { - writeSSE(w, - `{"choices":[{"delta":{"role":"assistant","tool_calls":[{"index":0,"id":"`+id+`","type":"function","function":{"name":"`+name+`","arguments":`+strconv.Quote(args)+`}}]},"finish_reason":null}]}`, - `{"choices":[{"delta":{},"finish_reason":"tool_calls"}]}`, - ) -} - // chainCodeGroupDirs lists one session's code-group directory names under the // owner data root. func chainCodeGroupDirs(t *testing.T, dataDir, sessionID string) []string { t.Helper() entries, err := os.ReadDir(filepath.Join(dataDir, "code", sessionID)) - if err != nil { - t.Fatalf("read code dir: %v", err) - } + testassert.NoError(t, err) var names []string for _, entry := range entries { if entry.IsDir() { @@ -650,9 +602,7 @@ func chainToolResults(t *testing.T, store harness.Storage, sessionID string) map } { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) results := make(map[string]struct { Status model.ToolResultStatus Content string @@ -668,9 +618,7 @@ func chainToolResults(t *testing.T, store harness.Storage, sessionID string) map Content string `json:"content"` Metadata json.RawMessage `json:"metadata"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode tool_result payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) results[wire.ToolCallID] = struct { Status model.ToolResultStatus Content string @@ -687,9 +635,7 @@ func chainSessionSignals(t *testing.T, store harness.Storage, sessionID string) } { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var signals []struct { Signal string Content string @@ -702,9 +648,7 @@ func chainSessionSignals(t *testing.T, store harness.Storage, sessionID string) Signal string `json:"signal"` Content string `json:"content"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode signal payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) signals = append(signals, struct { Signal string Content string @@ -735,25 +679,21 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { originalNotesTxt = "alpha\nbeta\ngamma\n" ) deniedPatch, err := json.Marshal(map[string]any{"input": "*** Begin Patch\n*** Update File: notes.txt\n*** Move to: .env\n@@\n-alpha\n+ALPHA\n*** End Patch"}) - if err != nil { - t.Fatalf("marshal denied patch: %v", err) - } + testassert.NoError(t, err) allowedPatch, err := json.Marshal(map[string]any{"input": "*** Begin Patch\n*** Update File: notes.txt\n*** Move to: renamed.txt\n@@\n-alpha\n+ALPHA\n*** End Patch"}) - if err != nil { - t.Fatalf("marshal allowed patch: %v", err) - } + testassert.NoError(t, err) writeTurn := func(id string) func(http.ResponseWriter) { - return func(w http.ResponseWriter) { chainToolCallTurn(w, id, "prod_write", modelWriteArgs) } + return func(w http.ResponseWriter) { writeToolCallTurn(w, id, "prod_write", modelWriteArgs) } } release2 := make(chan struct{}) release3 := make(chan struct{}) parked := make(chan struct{}) // the canceled operation parks until the owner cancels server := newChainServer(t, - chainTurn{respond: func(w http.ResponseWriter) { chainToolCallTurn(w, "call-1", "prod_write", modelWriteArgs) }}, + chainTurn{respond: func(w http.ResponseWriter) { writeToolCallTurn(w, "call-1", "prod_write", modelWriteArgs) }}, chainTurn{hold: release2, respond: writeTurn("call-2")}, chainTurn{hold: release3, respond: writeTurn("call-3")}, - chainTurn{respond: func(w http.ResponseWriter) { chainToolCallTurn(w, "call-4", "apply_patch", string(deniedPatch)) }}, - chainTurn{respond: func(w http.ResponseWriter) { chainToolCallTurn(w, "call-5", "apply_patch", string(allowedPatch)) }}, + chainTurn{respond: func(w http.ResponseWriter) { writeToolCallTurn(w, "call-4", "apply_patch", string(deniedPatch)) }}, + chainTurn{respond: func(w http.ResponseWriter) { writeToolCallTurn(w, "call-5", "apply_patch", string(allowedPatch)) }}, chainTurn{respond: func(w http.ResponseWriter) { writeTextTurn(w, "done") }}, chainTurn{respond: func(w http.ResponseWriter) { writeTextTurn(w, "done") }}, chainTurn{hold: parked, respond: func(w http.ResponseWriter) { writeTextTurn(w, "unreachable") }}, @@ -772,12 +712,8 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { writeServiceFile(t, agents.PathForConfig(configPath), chainAgentsDocument("first body")) workspace := filepath.Join(home, "chain-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } - if err := os.WriteFile(filepath.Join(workspace, "notes.txt"), []byte(originalNotesTxt), 0o600); err != nil { - t.Fatalf("write notes.txt: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "notes.txt"), []byte(originalNotesTxt), 0o600)) hook := &recordHook{name: "hook.first", events: &traceLog{}, mutate: func(c harness.ExecutionCapture) harness.ExecutionCapture { c.SystemPrompt += "|hooked" @@ -793,14 +729,10 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { owner, ownerCancel := context.WithCancel(ctx) defer ownerCancel() r, err := openChained(owner, hook, argHook) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(ctx, workspace, "chained") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID submitThroughRuntime(t, r, sessionID, "op-1", "start") server.awaitRequests(t, 2) // turn 1 answered, turn 2 parked at the server @@ -809,48 +741,36 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { // executed, the model's original arguments never reached disk, and // the hook observed exactly the original arguments. data, err := os.ReadFile(filepath.Join(workspace, "rewritten.txt")) - if err != nil || string(data) != "hook replacement" { - t.Fatalf("rewritten.txt = (%q, %v), want the hook's replacement content", data, err) - } - if _, err := os.Stat(filepath.Join(workspace, "hook.txt")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("hook.txt = err %v, want absent: the model's original arguments must never execute", err) + testassert.Require(t, err == nil && string(data) == "hook replacement", "rewritten.txt = (%q, %v), want the hook's replacement content", data, err) + { + _, err := os.Stat(filepath.Join(workspace, "hook.txt")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "hook.txt = err %v, want absent: the model's original arguments must never execute", err) } - if seen := argHook.received(); !slices.Equal(seen, []string{"prod_write " + modelWriteArgs}) { - t.Fatalf("argument hook observations = %v, want exactly the original call arguments", seen) + { + seen := argHook.received() + testassert.Require(t, slices.Equal(seen, []string{"prod_write " + modelWriteArgs}), "argument hook observations = %v, want exactly the original call arguments", seen) } admitted := readOperation(t, r, sessionID, "op-1").Admission.AdmittedEntry.EntryID // 2. The reload publishes a changed prompt while the operation runs. writeServiceFile(t, agents.PathForConfig(configPath), chainAgentsDocument("reloaded body")) - if revision, err := r.Reload(ctx); err != nil || revision != "2" { - t.Fatalf("Reload = (%q, %v), want 2", revision, err) + { + revision, err := r.Reload(ctx) + testassert.Require(t, err == nil && revision == "2", "Reload = (%q, %v), want 2", revision, err) } // 3. The steering submit buffers over the active operation: no // committed input and no new code-group directory while it sits // buffered. - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, OperationID: "op-1-steer", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: steeringText}}, Mode: harness.MessageModeRegular, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionSteering { - return fmt.Errorf("steering submit = %+v, want buffered steering", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("steering submit: %v", err) - } - if got := chainCodeGroupDirs(t, dataDir, sessionID); !slices.Equal(got, []string{admitted}) { - t.Fatalf("code groups while the steering input is buffered = %v, want only the admitted operation's group %q", got, admitted) + steering, err := submitRawThroughRuntime(ctx, r, sessionID, "op-1-steer", steeringText, harness.MessageModeRegular) + testassert.NoError(t, err) + testassert.Require(t, steering.Disposition == harness.DispositionSteering, "steering submit = %+v, want buffered steering", steering.Disposition) + { + got := chainCodeGroupDirs(t, dataDir, sessionID) + testassert.Require(t, slices.Equal(got, []string{admitted}), "code groups while the steering input is buffered = %v, want only the admitted operation's group %q", got, admitted) } for _, entry := range mustChainEntries(t, e.store, sessionID) { - if entry.Kind == harness.EntryInput && strings.Contains(string(entry.Payload), steeringText) { - t.Fatal("the buffered steering input committed a durable entry") - } + testassert.Require(t, entry.Kind != harness.EntryInput || !strings.Contains(string(entry.Payload), steeringText), "the buffered steering input committed a durable entry") } // The turn parked before the steering submit carries no steering @@ -866,23 +786,21 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { close(release3) server.awaitRequests(t, 4) steerAdmitted := readOperation(t, r, sessionID, "op-1-steer").Admission.AdmittedEntry.EntryID - if got := chainCodeGroupDirs(t, dataDir, sessionID); len(got) != 2 || !slices.Contains(got, admitted) || !slices.Contains(got, steerAdmitted) { - t.Fatalf("code groups after the delivered steering's mutating call = %v, want the predecessor's %q and the steering head's own %q", got, admitted, steerAdmitted) + { + got := chainCodeGroupDirs(t, dataDir, sessionID) + testassert.Require(t, len(got) == 2 && slices.Contains(got, admitted) && slices.Contains(got, steerAdmitted), "code groups after the delivered steering's mutating call = %v, want the predecessor's %q and the steering head's own %q", got, admitted, steerAdmitted) } // 4+5. The denied multi-target move settles only its call; the later // allowed call in the subsequent turn succeeds. The steering head's // Operation runs out on its scripted tail. rec := awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) - if rec.Admission.Execution.ConfigurationRevision != "1" { - t.Fatalf("capture revision = %q, want the admitted 1", rec.Admission.Execution.ConfigurationRevision) - } + testassert.Equal(t, rec.Admission.Execution.ConfigurationRevision, "1") steerRec := awaitOperation(t, r, sessionID, "op-1-steer", harness.OperationSuccess) - if steerRec.Admission.Execution.ConfigurationRevision != "2" { - t.Fatalf("steering head capture revision = %q, want the current reloaded 2", steerRec.Admission.Execution.ConfigurationRevision) - } - if _, err := os.Stat(filepath.Join(workspace, ".env")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf(".env = err %v, want absent: the denied move's destination must never be written", err) + testassert.Equal(t, steerRec.Admission.Execution.ConfigurationRevision, "2") + { + _, err := os.Stat(filepath.Join(workspace, ".env")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), ".env = err %v, want absent: the denied move's destination must never be written", err) } // The steering input's delivery is durable evidence now: a committed @@ -893,48 +811,35 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { steeringCommitted = true } } - if !steeringCommitted { - t.Fatal("the delivered steering input left no committed input entry") - } + testassert.Require(t, steeringCommitted, "the delivered steering input left no committed input entry") // 6. The allowed apply_patch turn's retained preview evidence: the // durable tool_result entry carries the edit_preview_files metadata // with the move's file/op shape. results := chainToolResults(t, e.store, sessionID) denied, ok := results["call-4"] - if !ok || denied.Status != model.ResultDenied || denied.Content != "Permission denied." { - t.Fatalf("denied move result = %+v (ok %v), want the fixed policy denial", denied, ok) - } - if denied.Metadata != nil { - t.Fatalf("denied move metadata = %s, want none", denied.Metadata) - } + testassert.Require(t, ok && denied.Status == model.ResultDenied && denied.Content == "Permission denied.", "denied move result = %+v (ok %v), want the fixed policy denial", denied, ok) + testassert.Require(t, denied.Metadata == nil, "denied move metadata = %s, want none", denied.Metadata) allowed, ok := results["call-5"] - if !ok || allowed.Status != model.ResultSuccess { - t.Fatalf("allowed patch result = %+v (ok %v), want success after the denial settled only its own call", allowed, ok) - } + testassert.Require(t, ok && allowed.Status == model.ResultSuccess, "allowed patch result = %+v (ok %v), want success after the denial settled only its own call", allowed, ok) var preview struct { EditPreviewFiles []struct { Path string `json:"path"` Op string `json:"op"` } `json:"edit_preview_files"` } - if err := json.Unmarshal(allowed.Metadata, &preview); err != nil { - t.Fatalf("decode allowed patch metadata %s: %v", allowed.Metadata, err) - } - if len(preview.EditPreviewFiles) != 2 || - preview.EditPreviewFiles[0].Path != "renamed.txt" || preview.EditPreviewFiles[0].Op != "M" || - preview.EditPreviewFiles[1].Path != "notes.txt" || preview.EditPreviewFiles[1].Op != "D" { - t.Fatalf("edit_preview_files = %s, want the move's two entries: the M destination then the D source", allowed.Metadata) + { + err := json.Unmarshal(allowed.Metadata, &preview) + testassert.Require(t, err == nil, "decode allowed patch metadata %s: %v", allowed.Metadata, err) } + testassert.Require(t, len(preview.EditPreviewFiles) == 2 && preview.EditPreviewFiles[0].Path == "renamed.txt" && preview.EditPreviewFiles[0].Op == "M" && preview.EditPreviewFiles[1].Path == "notes.txt" && preview.EditPreviewFiles[1].Op == "D", "edit_preview_files = %s, want the move's two entries: the M destination then the D source", allowed.Metadata) // 7. The retained snapshot evidence: the moved file's preimage in the // steering head's own code group carries the original bytes. found := false turnDir := filepath.Join(dataDir, "code", sessionID, steerAdmitted, "snapshots", "1") entries, err := os.ReadDir(turnDir) - if err != nil { - t.Fatalf("read group turn dir: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { metaData, err := os.ReadFile(filepath.Join(turnDir, entry.Name(), "meta.json")) if err != nil { @@ -948,21 +853,16 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { continue } original, err := os.ReadFile(filepath.Join(turnDir, entry.Name(), "original")) - if err != nil || string(original) != originalNotesTxt { - t.Fatalf("notes.txt preimage = (%q, %v), want the original bytes", original, err) - } + testassert.Require(t, err == nil && string(original) == originalNotesTxt, "notes.txt preimage = (%q, %v), want the original bytes", original, err) found = true } - if !found { - t.Fatalf("no notes.txt preimage entry in %s, want the retained snapshot evidence", turnDir) - } + testassert.Require(t, found, "no notes.txt preimage entry in %s, want the retained snapshot evidence", turnDir) // The hook observed every call's original arguments exactly once per // call: three rewritten writes and two pass-through patch calls. - if seen := argHook.received(); len(seen) != 5 || - seen[0] != "prod_write "+modelWriteArgs || seen[1] != "prod_write "+modelWriteArgs || seen[2] != "prod_write "+modelWriteArgs || - seen[3] != "apply_patch "+string(deniedPatch) || seen[4] != "apply_patch "+string(allowedPatch) { - t.Fatalf("argument hook observations = %v, want each call's original arguments exactly once", seen) + { + seen := argHook.received() + testassert.Require(t, len(seen) == 5 && seen[0] == "prod_write "+modelWriteArgs && seen[1] == "prod_write "+modelWriteArgs && seen[2] == "prod_write "+modelWriteArgs && seen[3] == "apply_patch "+string(deniedPatch) && seen[4] == "apply_patch "+string(allowedPatch), "argument hook observations = %v, want each call's original arguments exactly once", seen) } // The predecessor's own requests carry its immutable old capture, and @@ -970,18 +870,16 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { // capture with the delivered steering input in its history. oldPrompt := rec.Admission.Execution.SystemPrompt wire := decodeWireChatBody(t, server.bodyAt(1)) - if len(wire.Messages) == 0 || wire.Messages[0].Role != "system" { - t.Fatalf("pre-steering request messages = %+v, want the captured system prompt first", wire.Messages) - } - if want, _ := json.Marshal(oldPrompt); string(wire.Messages[0].Content) != string(want) { - t.Fatalf("pre-steering system message = %.120s, want the old captured prompt %.120s", wire.Messages[0].Content, want) + testassert.Require(t, len(wire.Messages) != 0 && wire.Messages[0].Role == "system", "pre-steering request messages = %+v, want the captured system prompt first", wire.Messages) + { + want, _ := json.Marshal(oldPrompt) + testassert.Require(t, string(wire.Messages[0].Content) == string(want), "pre-steering system message = %.120s, want the old captured prompt %.120s", wire.Messages[0].Content, want) } steerWire := decodeWireChatBody(t, server.bodyAt(2)) - if len(steerWire.Messages) == 0 || steerWire.Messages[0].Role != "system" { - t.Fatalf("steering-continued request messages = %+v, want the successor system prompt first", steerWire.Messages) - } - if want, _ := json.Marshal(steerRec.Admission.Execution.SystemPrompt); string(steerWire.Messages[0].Content) != string(want) { - t.Fatalf("steering-continued system message = %.120s, want the successor's current captured prompt %.120s", steerWire.Messages[0].Content, want) + testassert.Require(t, len(steerWire.Messages) != 0 && steerWire.Messages[0].Role == "system", "steering-continued request messages = %+v, want the successor system prompt first", steerWire.Messages) + { + want, _ := json.Marshal(steerRec.Admission.Execution.SystemPrompt) + testassert.Require(t, string(steerWire.Messages[0].Content) == string(want), "steering-continued system message = %.120s, want the successor's current captured prompt %.120s", steerWire.Messages[0].Content, want) } if !strings.Contains(server.bodyAt(2), steeringText) { t.Fatalf("steering-continued request = %s, want the delivered steering input in its history", server.bodyAt(2)) @@ -994,58 +892,37 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { // revision: a fresh Session rules out the post-terminal drain racing // the submit into a steering buffer. next, err := r.createSession(ctx, filepath.Join(home, "chain-ws-2"), "chained") - if err != nil { - t.Fatalf("createSession(next): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, next.Identity.SessionID, "op-2", "after reload") nextRec := awaitOperation(t, r, next.Identity.SessionID, "op-2", harness.OperationSuccess) - if nextRec.Admission.Execution.ConfigurationRevision != "2" || - nextRec.Admission.Execution.SystemPrompt == oldPrompt || - !strings.Contains(nextRec.Admission.Execution.SystemPrompt, "reloaded body") || - strings.Contains(nextRec.Admission.Execution.SystemPrompt, "first body") || - !strings.HasSuffix(nextRec.Admission.Execution.SystemPrompt, "|hooked") { - t.Fatalf("next capture = (%q, %.120s), want revision 2 with the reloaded hooked prompt", - nextRec.Admission.Execution.ConfigurationRevision, nextRec.Admission.Execution.SystemPrompt) - } + testassert.Require(t, nextRec.Admission.Execution.ConfigurationRevision == "2" && nextRec.Admission.Execution.SystemPrompt != oldPrompt && strings.Contains(nextRec.Admission.Execution.SystemPrompt, "reloaded body") && !strings.Contains(nextRec.Admission.Execution.SystemPrompt, "first body") && strings.HasSuffix(nextRec.Admission.Execution.SystemPrompt, "|hooked"), "next capture = (%q, %.120s), want revision 2 with the reloaded hooked prompt", nextRec.Admission.Execution.ConfigurationRevision, nextRec.Admission.Execution.SystemPrompt) // 8. The production-reachable cancellation act: the owner context // cancels while an operation is active, and the operation settles // interrupted with the standard model-visible signal. cxl, err := r.createSession(ctx, filepath.Join(home, "chain-ws-3"), "chained") - if err != nil { - t.Fatalf("createSession(cxl): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, cxl.Identity.SessionID, "op-3", "parked") server.awaitRequests(t, 8) ownerCancel() - if err := r.Close(ctx); err != nil { - t.Fatalf("Close after the owner cancellation: %v", err) - } + testassert.NoError(t, r.Close(ctx)) status, detail := operationRegisterTerminal(t, e.store, cxl.Identity.SessionID, "op-3") - if status != harness.OperationInterruption || detail != "agent interrupted" { - t.Fatalf("canceled operation = (%q, %q), want the committed interruption with the standard detail", status, detail) - } + testassert.Require(t, status == harness.OperationInterruption && detail == "agent interrupted", "canceled operation = (%q, %q), want the committed interruption with the standard detail", status, detail) signals := chainSessionSignals(t, e.store, cxl.Identity.SessionID) - if len(signals) != 1 || signals[0].Signal != "interruption" || signals[0].Content != "Operation interrupted." { - t.Fatalf("canceled operation signals = %+v, want exactly the standard interruption signal", signals) - } + testassert.Require(t, len(signals) == 1 && signals[0].Signal == "interruption" && signals[0].Content == "Operation interrupted.", "canceled operation signals = %+v, want exactly the standard interruption signal", signals) // 9. Restart: a fresh Open over the same data root runs recovery over // the committed interruption evidence and a post-repair admitted // operation works. r2, err := openChained(ctx, e.hookedHook(), &rewriteWriteHook{replace: hookRewriteArgs}) - if err != nil { - t.Fatalf("reopen: %v", err) - } + testassert.NoError(t, err) defer r2.Close(ctx) - if rec := readOperation(t, r2, cxl.Identity.SessionID, "op-3"); rec.State.Status != harness.OperationInterruption || - rec.State.Terminal == nil || rec.State.Terminal.Detail != "agent interrupted" { - t.Fatalf("restarted operation state = %+v, want the committed interruption intact", rec.State) + { + rec := readOperation(t, r2, cxl.Identity.SessionID, "op-3") + testassert.Require(t, rec.State.Status == harness.OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "agent interrupted", "restarted operation state = %+v, want the committed interruption intact", rec.State) } repaired, err := r2.createSession(ctx, filepath.Join(home, "chain-ws-4"), "chained") - if err != nil { - t.Fatalf("createSession after repair: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r2, repaired.Identity.SessionID, "op-4", "continue") awaitOperation(t, r2, repaired.Identity.SessionID, "op-4", harness.OperationSuccess) }) @@ -1055,9 +932,7 @@ func TestProductionCrossBoundaryComposition(t *testing.T) { func mustChainEntries(t *testing.T, store harness.Storage, sessionID string) []harness.Entry { t.Helper() entries, err := store.ReadEntries(context.Background(), sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) return entries } @@ -1073,18 +948,15 @@ func TestProductionReadonlyGitHookSeesOriginalArguments(t *testing.T) { // A real temporary git workspace with one untracked file. workspace := e.workspace("git-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } - if out, err := exec.Command("git", "init", workspace).CombinedOutput(); err != nil { - t.Fatalf("git init: %v (%s)", err, out) - } - if err := os.WriteFile(filepath.Join(workspace, "ro-note.txt"), []byte("untracked\n"), 0o600); err != nil { - t.Fatalf("write untracked file: %v", err) + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) + { + out, err := exec.Command("git", "init", workspace).CombinedOutput() + testassert.Require(t, err == nil, "git init: %v (%s)", err, out) } + testassert.NoError(t, os.WriteFile(filepath.Join(workspace, "ro-note.txt"), []byte("untracked\n"), 0o600)) server := newChainServer(t, - chainTurn{respond: func(w http.ResponseWriter) { chainToolCallTurn(w, "call-1", "run_command", `{"command":"git status"}`) }}, + chainTurn{respond: func(w http.ResponseWriter) { writeToolCallTurn(w, "call-1", "run_command", `{"command":"git status"}`) }}, chainTurn{respond: func(w http.ResponseWriter) { writeTextTurn(w, "done") }}, ) writeServiceFile(t, e.configPath, chainCommandConfigDocument(server.URL)) @@ -1092,27 +964,20 @@ func TestProductionReadonlyGitHookSeesOriginalArguments(t *testing.T) { `{"rogit": {"model": "prov/m", "system_prompt": "simple", "tools": ["run_command"], "readonly": true, "capabilities": ["arg.noop"]}}`) hook := &noopArgumentHook{} - r, err := Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: []Plugin{ - coreStoragePlugin(e.store), chainCommandPlugin(), argumentHookPlugin("arg.noop", hook), - }}) - if err != nil { - t.Fatalf("Open: %v", err) - } + r, err := e.openPlugins(ctx, coreStoragePlugin(e.store), chainCommandPlugin(), argumentHookPlugin("arg.noop", hook)) + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, workspace, "rogit") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "status") rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) - if !rec.Admission.Execution.Readonly { - t.Fatalf("capture readonly = %v, want the agent's constraint", rec.Admission.Execution.Readonly) - } + testassert.Require(t, rec.Admission.Execution.Readonly, "capture readonly = %v, want the agent's constraint", rec.Admission.Execution.Readonly) // The hook saw the call exactly once, with the original arguments. - if seen := hook.received(); !slices.Equal(seen, []string{`run_command {"command":"git status"}`}) { - t.Fatalf("argument hook observations = %v, want exactly one original-argument observation", seen) + { + seen := hook.received() + testassert.Require(t, slices.Equal(seen, []string{`run_command {"command":"git status"}`}), "argument hook observations = %v, want exactly one original-argument observation", seen) } // The command settled with real git status output in this workspace @@ -1123,15 +988,8 @@ func TestProductionReadonlyGitHookSeesOriginalArguments(t *testing.T) { // owns that row). results := chainToolResults(t, e.store, session.Identity.SessionID) settled, ok := results["call-1"] - if !ok || settled.Status != model.ResultSuccess { - t.Fatalf("run_command result = %+v (ok %v), want success", settled, ok) - } - if strings.TrimSpace(settled.Content) == "" { - t.Fatalf("settled run_command content = %q, want real git status output", settled.Content) - } - if !strings.Contains(settled.Content, "ro-note.txt") { - t.Fatalf("settled run_command content = %q, want the workspace's real untracked file listed", settled.Content) - } + testassert.Require(t, ok && settled.Status == model.ResultSuccess, "run_command result = %+v (ok %v), want success", settled, ok) + testassert.Require(t, strings.TrimSpace(settled.Content) != "" && strings.Contains(settled.Content, "ro-note.txt"), "settled run_command content = %q, want real git status output; settled run_command content = %q, want the workspace's real untracked file listed", settled.Content, settled.Content) }) } @@ -1153,58 +1011,36 @@ func TestProductionCapabilityIDsModelIndependent(t *testing.T) { "vanillaagent": {"model": "prov/vanilla", "system_prompt": "full", "tools": ["read_file", "write_file", "edit_file", "apply_patch"], "capabilities": ["model_adaptation"]} }`) - r, err := Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: []Plugin{ - coreStoragePlugin(e.store), inertToolsPlugin(), bundledAdaptationPlugin(), - }}) - if err != nil { - t.Fatalf("Open: %v", err) - } + r, err := e.openPlugins(ctx, coreStoragePlugin(e.store), inertToolsPlugin(), bundledAdaptationPlugin()) + testassert.NoError(t, err) defer r.Close(ctx) gpt, err := r.createSession(ctx, e.workspace("adapt-gpt"), "gptagent") - if err != nil { - t.Fatalf("createSession(gpt): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, gpt.Identity.SessionID, "op-gpt", "hello") gptRec := awaitOperation(t, r, gpt.Identity.SessionID, "op-gpt", harness.OperationSuccess) van, err := r.createSession(ctx, e.workspace("adapt-van"), "vanillaagent") - if err != nil { - t.Fatalf("createSession(van): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, van.Identity.SessionID, "op-van", "hello") vanRec := awaitOperation(t, r, van.Identity.SessionID, "op-van", harness.OperationSuccess) // Identical capability ID lists, from identical configured // capabilities. - if !slices.Equal(gptRec.Admission.Execution.Capabilities, vanRec.Admission.Execution.Capabilities) { - t.Fatalf("capability IDs = %q vs %q, want identical lists across the model switch", - gptRec.Admission.Execution.Capabilities, vanRec.Admission.Execution.Capabilities) - } - if !slices.Equal(gptRec.Admission.Execution.Capabilities, []string{"model_adaptation"}) { - t.Fatalf("capability IDs = %q, want exactly the agents' configured selection", gptRec.Admission.Execution.Capabilities) - } + testassert.Require(t, slices.Equal(gptRec.Admission.Execution.Capabilities, vanRec.Admission.Execution.Capabilities) && slices.Equal(gptRec.Admission.Execution.Capabilities, []string{"model_adaptation"}), "capability IDs = %q vs %q, want identical lists across the model switch; capability IDs = %q, want exactly the agents' configured selection", gptRec.Admission.Execution.Capabilities, vanRec.Admission.Execution.Capabilities, gptRec.Admission.Execution.Capabilities) // The advertisements differ exactly as the bundled adaptations // resolve: the GPT-family match swaps write/edit for apply_patch, // the unmatched model keeps the default-hidden patch tool out. gptTools := toolNames(gptRec.Admission.Execution.Tools) vanTools := toolNames(vanRec.Admission.Execution.Tools) - if !slices.Equal(gptTools, []string{"read_file", "apply_patch"}) { - t.Fatalf("adapted advertisement = %v, want write/edit excluded and apply_patch included", gptTools) - } - if !slices.Equal(vanTools, []string{"read_file", "write_file", "edit_file"}) { - t.Fatalf("baseline advertisement = %v, want the untouched list with the hidden patch tool out", vanTools) - } + testassert.Require(t, slices.Equal(gptTools, []string{"read_file", "apply_patch"}), "adapted advertisement = %v, want write/edit excluded and apply_patch included", gptTools) + testassert.Require(t, slices.Equal(vanTools, []string{"read_file", "write_file", "edit_file"}), "baseline advertisement = %v, want the untouched list with the hidden patch tool out", vanTools) // The prompts differ too: the adapted model's task_execution // addition renders in the full prompt. - if !strings.Contains(gptRec.Admission.Execution.SystemPrompt, "Use more tool calls when they can improve the response") { - t.Fatalf("adapted prompt = %.200s, want the adaptation's task_execution addition", gptRec.Admission.Execution.SystemPrompt) - } - if strings.Contains(vanRec.Admission.Execution.SystemPrompt, "Use more tool calls when they can improve the response") { - t.Fatalf("baseline prompt = %.200s, want no adaptation addition", vanRec.Admission.Execution.SystemPrompt) - } + testassert.Require(t, strings.Contains(gptRec.Admission.Execution.SystemPrompt, "Use more tool calls when they can improve the response"), "adapted prompt = %.200s, want the adaptation's task_execution addition", gptRec.Admission.Execution.SystemPrompt) + testassert.Require(t, !strings.Contains(vanRec.Admission.Execution.SystemPrompt, "Use more tool calls when they can improve the response"), "baseline prompt = %.200s, want no adaptation addition", vanRec.Admission.Execution.SystemPrompt) }) } @@ -1224,23 +1060,20 @@ func TestProductionOpenInitialSweepCleansArtifacts(t *testing.T) { youngCode := plantSessionCode(t, e.dataDir, young) r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) // The initial pass deleted the aged session and removed its artifact // tree before the owner was published. - if _, err := readSweptSession(t, r, deletable); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("aged session after the initial sweep = err %v, want it deleted", err) + { + err := headerErrorThroughRuntime(context.Background(), r, deletable) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "aged session after the initial sweep = err %v, want it deleted", err) } mustNotExist(t, deletableCode, "the deleted session's artifact tree") // The stale-but-not-yet-deletable session survives with its tree. - rec, err := readSweptSession(t, r, young) - if err != nil || rec.Lifecycle != harness.LifecycleArchived { - t.Fatalf("young session after the initial sweep = %+v err %v, want it archived and intact", rec, err) - } + rec := headerThroughRuntime(t, r, young) + testassert.Require(t, rec.Lifecycle == harness.LifecycleArchived, "young session after the initial sweep = %+v, want it archived and intact", rec) mustExist(t, youngCode, "the surviving session's artifact tree") }) } diff --git a/runtime/production_test.go b/runtime/production_test.go index e2810ee9..dfa7283e 100644 --- a/runtime/production_test.go +++ b/runtime/production_test.go @@ -24,6 +24,7 @@ import ( "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/config" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -335,12 +336,19 @@ func (e *productionEnv) open(ctx context.Context, hook *recordHook, park *parkin return e.openWithPlugins(ctx, hook, park) } +// openPlugins opens the fixture's exact plugin list over its data and config +// roots. +func (e *productionEnv) openPlugins(ctx context.Context, plugins ...Plugin) (*Runtime, error) { + e.t.Helper() + return Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: plugins}) +} + // openWithPlugins composes the suite's base plugin set plus extras — the // adaptation row adds its custom ModelAdaptation export this way. func (e *productionEnv) openWithPlugins(ctx context.Context, hook *recordHook, park *parkingHook, extra ...Plugin) (*Runtime, error) { e.t.Helper() plugins := []Plugin{coreStoragePlugin(e.store), productionToolsPlugin(), hookPlugin(hook), parkPlugin(park)} - return Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: append(plugins, extra...)}) + return e.openPlugins(ctx, append(plugins, extra...)...) } func (e *productionEnv) hookedHook() *recordHook { @@ -458,7 +466,7 @@ func (s *productionModelServer) serve(w http.ResponseWriter, r *http.Request) { return } } - writeToolCallTurn(w, toolName, toolArgs) + writeToolCallTurn(w, "call-1", toolName, toolArgs) } } @@ -564,9 +572,7 @@ type wireChatBody struct { func decodeWireChatBody(t *testing.T, body string) wireChatBody { t.Helper() var doc wireChatBody - if err := json.Unmarshal([]byte(body), &doc); err != nil { - t.Fatalf("decode wire body: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(body), &doc)) return doc } @@ -586,9 +592,7 @@ func awaitIdleSession(t *testing.T, r *Runtime, sessionID string) { if err == nil && idle { return } - if time.Now().After(deadline) { - t.Fatalf("session %q never became idle (read error %v)", sessionID, err) - } + testassert.Require(t, !time.Now().After(deadline), "session %q never became idle (read error %v)", sessionID, err) time.Sleep(10 * time.Millisecond) } } @@ -597,33 +601,23 @@ func TestProductionAdmittedOperation(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) workspace := e.workspace("prod-ws") session, err := r.createSession(ctx, workspace, "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "please write") rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) // The real file effect happened in the Workspace. data, err := os.ReadFile(filepath.Join(workspace, "out.txt")) - if err != nil || string(data) != "written by production" { - t.Fatalf("prod_write effect = (%q, %v), want the written content", data, err) - } - if rec.Admission.Execution.ConfigurationRevision != "1" { - t.Fatalf("capture revision = %q, want 1", rec.Admission.Execution.ConfigurationRevision) - } + testassert.Require(t, err == nil && string(data) == "written by production", "prod_write effect = (%q, %v), want the written content", data, err) + testassert.Equal(t, rec.Admission.Execution.ConfigurationRevision, "1") if !slices.Equal(toolNames(rec.Admission.Execution.Tools), []string{"prod_write", "prod_read"}) { t.Fatalf("advertisement = %v, want the agent's composed tools", toolNames(rec.Admission.Execution.Tools)) } - if !strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked") { - t.Fatalf("capture prompt = %q, want the hooked composed prompt", rec.Admission.Execution.SystemPrompt) - } + testassert.Require(t, strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked"), "capture prompt = %q, want the hooked composed prompt", rec.Admission.Execution.SystemPrompt) // The wire: one tool-call turn and one follow-up, both carrying the // resolved credential, the custom headers, the no-max_tokens body, @@ -632,48 +626,27 @@ func TestProductionAdmittedOperation(t *testing.T) { t.Fatalf("model requests = %d, want the tool-call turn and the follow-up", e.server.requests()) } first := decodeWireChatBody(t, e.server.bodyAt(0)) - if first.Model != "m" || !first.Stream || first.ToolChoice != "auto" { - t.Fatalf("wire body base = %+v, want the fixed streaming shape for model m", first) - } - if first.MaxTokens != nil || first.MaxCompletion != nil { - t.Fatalf("wire body carries an output-token field (%v, %v), want none", first.MaxTokens, first.MaxCompletion) - } - if first.StreamOptions == nil || !first.StreamOptions.IncludeUsage { - t.Fatalf("wire stream_options = %+v, want include_usage from the effective streamed-usage flag", first.StreamOptions) - } + testassert.Require(t, first.Model == "m" && first.Stream && first.ToolChoice == "auto" && first.MaxTokens == nil && first.MaxCompletion == nil && first.StreamOptions != nil && first.StreamOptions.IncludeUsage, "wire body base = %+v, want the fixed streaming shape for model m; wire body carries an output-token field (%v, %v), want none; wire stream_options = %+v, want include_usage from the effective streamed-usage flag", first, first.MaxTokens, first.MaxCompletion, first.StreamOptions) var wireTools []string for _, tool := range first.Tools { wireTools = append(wireTools, tool.Function.Name) } - if !slices.Equal(wireTools, []string{"prod_write", "prod_read"}) { - t.Fatalf("wire tools = %v, want the advertised order", wireTools) - } - if len(first.Messages) == 0 || first.Messages[0].Role != "system" { - t.Fatalf("wire messages = %+v, want the captured composed prompt first", first.Messages) - } - if want, err := json.Marshal(rec.Admission.Execution.SystemPrompt); err != nil || string(first.Messages[0].Content) != string(want) { - t.Fatalf("wire system message = %s, want the captured composed prompt %s", first.Messages[0].Content, want) - } - if auth := e.server.authAt(0); auth != "Bearer production-secret-1" { - t.Fatalf("wire authorization = %q, want the resolved key's bearer form", auth) - } - if trace := e.server.traceAt(0); trace != "trace-1" { - t.Fatalf("wire X-Provider-Trace = %q, want the captured transport headers", trace) + testassert.Require(t, slices.Equal(wireTools, []string{"prod_write", "prod_read"}), "wire tools = %v, want the advertised order", wireTools) + testassert.Require(t, len(first.Messages) != 0 && first.Messages[0].Role == "system", "wire messages = %+v, want the captured composed prompt first", first.Messages) + { + want, err := json.Marshal(rec.Admission.Execution.SystemPrompt) + testassert.Require(t, err == nil && string(first.Messages[0].Content) == string(want), "wire system message = %s, want the captured composed prompt %s", first.Messages[0].Content, want) } + testassert.Equal(t, e.server.authAt(0), "Bearer production-secret-1") + testassert.Equal(t, e.server.traceAt(0), "trace-1") // The merged sidecar layers reach the wire body as decoded members, // not whole-body substrings: the exact-number decode keeps the // provider's number lexeme and both sidecar string values comparable. decoder := json.NewDecoder(strings.NewReader(e.server.bodyAt(0))) decoder.UseNumber() var wire map[string]any - if err := decoder.Decode(&wire); err != nil { - t.Fatalf("decode wire body with exact numbers: %v", err) - } - if wire["provider_side"] != "provider-value" || wire["model_side"] != "model-value" || - wire["provider_number"] != json.Number("9007199254740993") { - t.Fatalf("wire body sidecar members = (%v, %v, %v), want the merged provider and model sidecar layers with the exact number lexeme", - wire["provider_side"], wire["model_side"], wire["provider_number"]) - } + testassert.NoError(t, decoder.Decode(&wire)) + testassert.Require(t, wire["provider_side"] == "provider-value" && wire["model_side"] == "model-value" && wire["provider_number"] == json.Number("9007199254740993"), "wire body sidecar members = (%v, %v, %v), want the merged provider and model sidecar layers with the exact number lexeme", wire["provider_side"], wire["model_side"], wire["provider_number"]) if lastMessageRole(e.server.bodyAt(1)) != "tool" { t.Fatalf("follow-up request = %s, want the tool result continuation", e.server.bodyAt(1)) } @@ -684,42 +657,19 @@ func TestProductionKeyResolution(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("key-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } - submit := func(opID string) error { - return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: opID, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "please write"}}, Mode: harness.MessageModeRegular, - }) - return err - }) - } - readOperationErr := func(opID string) error { - return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.ReadOperation(ctx, session.Identity.SessionID, opID) - return err - }) - } + testassert.NoError(t, err) // A required credential that resolves empty rejects admission with // the retained provider/env-name diagnostic; no Operation exists. e.t.Setenv("PRODUCTION_TEST_KEY", "") - err = submit("op-missing") - if !errors.Is(err, config.ErrMissingEnvVar) || !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("missing credential submit = %v, want the retained diagnostic wrapping both identities", err) - } - if !strings.Contains(err.Error(), "PRODUCTION_TEST_KEY") || !strings.Contains(err.Error(), `for provider "prov"`) { - t.Fatalf("missing credential diagnostic = %v, want the env name and provider identity", err) - } - if err := readOperationErr("op-missing"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("rejected admission left %v, want no Operation", err) + err = submitExpectFailure(r, session.Identity.SessionID, "op-missing", "please write") + testassert.Require(t, errors.Is(err, config.ErrMissingEnvVar) && errors.Is(err, harness.ErrInvalid) && strings.Contains(err.Error(), "PRODUCTION_TEST_KEY") && strings.Contains(err.Error(), `for provider "prov"`), "missing credential submit = %v, want the retained diagnostic wrapping both identities; missing credential diagnostic = %v, want the env name and provider identity", err, err) + { + err := readOperationError(ctx, r, session.Identity.SessionID, "op-missing") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "rejected admission left %v, want no Operation", err) } // Setting the variable lets the next admission succeed; unsetting it @@ -728,9 +678,7 @@ func TestProductionKeyResolution(t *testing.T) { // key, and the turn completes. e.t.Setenv("PRODUCTION_TEST_KEY", "production-secret-1") e.server.armGate() - if err := submit("op-1"); err != nil { - t.Fatalf("submit after the key is set: %v", err) - } + submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "please write") e.server.awaitRequests(1) e.t.Setenv("PRODUCTION_TEST_KEY", "") e.server.releaseGate() @@ -739,8 +687,9 @@ func TestProductionKeyResolution(t *testing.T) { t.Fatalf("model requests = %d, want both turns", e.server.requests()) } for i := 0; i < 2; i++ { - if got := e.server.authAt(i); got != "Bearer production-secret-1" { - t.Fatalf("request %d authorization = %q, want the captured key", i, got) + { + got := e.server.authAt(i) + testassert.Require(t, got == "Bearer production-secret-1", "request %d authorization = %q, want the captured key", i, got) } } }) @@ -760,26 +709,11 @@ func TestProductionAdmissionRacingParkedManagedWriteUsesPublishedPair(t *testing writeDotEnv(t, e.home, "PRODUCTION_TEST_KEY=\n") ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) - if r.managedEnv == nil || !r.managedEnv.IsManaged("PRODUCTION_TEST_KEY") { - t.Fatal("the fixture credential is not managed") - } + testassert.Require(t, r.managedEnv != nil && r.managedEnv.IsManaged("PRODUCTION_TEST_KEY"), "the fixture credential is not managed") session, err := r.createSession(ctx, e.workspace("race-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } - submit := func(opID string) error { - return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: opID, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "please write"}}, Mode: harness.MessageModeRegular, - }) - return err - }) - } + testassert.NoError(t, err) probe := installOwningSyncProbe(t, r.managedEnv.Path()) defer probe.restore() @@ -801,32 +735,24 @@ func TestProductionAdmissionRacingParkedManagedWriteUsesPublishedPair(t *testing // The admission must establish its wait at configuration capture // while the writer holds the publication/capture ownership. submitDone := make(chan error, 1) - go func() { submitDone <- submit("op-race") }() + go func() { submitDone <- submitExpectFailure(r, session.Identity.SessionID, "op-race", "please write") }() awaitConfigurationCaptureWait(t) release() connect := <-connectDone - if connect.err != nil { - t.Fatalf("connect: %v", connect.err) - } + testassert.Require(t, connect.err == nil, "connect: %v", connect.err) select { case err := <-submitDone: - if err != nil { - t.Fatalf("admitted submit: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the admission never resumed after the write published") } e.server.awaitRequests(1) - if got := e.server.authAt(0); got != "Bearer "+key { - t.Fatalf("model transport authorization = %q, want the published managed key", got) - } + testassert.Equal(t, e.server.authAt(0), "Bearer "+key) record := awaitOperation(t, r, session.Identity.SessionID, "op-race", harness.OperationSuccess) - if record.Admission.Execution.ConfigurationRevision != connect.mutation.ConfigurationRevision.Generation { - t.Fatalf("admitted revision = %q, writer's published generation = %s, want the same published pair", - record.Admission.Execution.ConfigurationRevision, connect.mutation.ConfigurationRevision.Generation) - } - if published := r.config.current(); published == nil || strconv.FormatUint(published.generation, 10) != connect.mutation.ConfigurationRevision.Generation { - t.Fatalf("live publication = %v, want the writer's published generation %s", published, connect.mutation.ConfigurationRevision.Generation) + testassert.Equal(t, record.Admission.Execution.ConfigurationRevision, connect.mutation.ConfigurationRevision.Generation) + { + published := r.config.current() + testassert.Require(t, published != nil && strconv.FormatUint(published.generation, 10) == connect.mutation.ConfigurationRevision.Generation, "live publication = %v, want the writer's published generation %s", published, connect.mutation.ConfigurationRevision.Generation) } }) } @@ -855,9 +781,7 @@ func TestProductionAdmissionUsesReadyRevisionDuringParkedBuild(t *testing.T) { }) ctx := context.Background() r, err := e.openWithPlugins(ctx, e.hookedHook(), &parkingHook{}, gate) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) // Teardown order: the ctx-blind validator gate is released first, so // the buffered Reload and Submit result sends complete before the // ordinary owner/store close joins both admission calls. A broken @@ -867,18 +791,7 @@ func TestProductionAdmissionUsesReadyRevisionDuringParkedBuild(t *testing.T) { defer releaseGate() // registered second: runs first session, err := r.createSession(ctx, e.workspace("ready-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } - submit := func(opID string) error { - return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: opID, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "please write"}}, Mode: harness.MessageModeRegular, - }) - return err - }) - } + testassert.NoError(t, err) armed.Store(true) reloadDone := make(chan error, 1) @@ -892,27 +805,22 @@ func TestProductionAdmissionUsesReadyRevisionDuringParkedBuild(t *testing.T) { t.Fatal("the reload never reached the parked validator") } submitDone := make(chan error, 1) - go func() { submitDone <- submit("op-ready") }() + go func() { + _, err := submitRawThroughRuntime(ctx, r, session.Identity.SessionID, "op-ready", "please write", harness.MessageModeRegular) + submitDone <- err + }() select { case err := <-submitDone: - if err != nil { - t.Fatalf("admitted submit during the parked build: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("admission did not use the ready revision while the build was parked") } e.server.awaitRequests(1) - if got := e.server.authAt(0); got != "Bearer production-secret-1" { - t.Fatalf("model transport authorization = %q, want the ready revision's captured key", got) - } + testassert.Equal(t, e.server.authAt(0), "Bearer production-secret-1") record := awaitOperation(t, r, session.Identity.SessionID, "op-ready", harness.OperationSuccess) - if record.Admission.Execution.ConfigurationRevision != "1" { - t.Fatalf("admitted revision = %q, want the ready generation 1", record.Admission.Execution.ConfigurationRevision) - } + testassert.Equal(t, record.Admission.Execution.ConfigurationRevision, "1") releaseGate() - if err := <-reloadDone; err != nil { - t.Fatalf("reload: %v", err) - } + testassert.NoError(t, <-reloadDone) }) } @@ -920,9 +828,7 @@ func TestProductionModelSelectionRejection(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) for _, row := range []struct { @@ -935,24 +841,12 @@ func TestProductionModelSelectionRejection(t *testing.T) { {agent: "absent", opID: "op-absent", wantText: "is not available in the catalog", wantClass: harness.ErrInvalid}, } { session, err := r.createSession(ctx, e.workspace("reject-"+row.agent), row.agent) - if err != nil { - t.Fatalf("createSession(%s): %v", row.agent, err) - } - err = r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: row.opID, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }) - return err - }) - if !errors.Is(err, row.wantClass) || !strings.Contains(err.Error(), row.wantText) { - t.Fatalf("%s submit = %v, want %v with %q", row.agent, err, row.wantClass, row.wantText) - } - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.ReadOperation(ctx, session.Identity.SessionID, row.opID) - return err - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("%s rejection left %v, want no Operation", row.agent, err) + testassert.Require(t, err == nil, "createSession(%s): %v", row.agent, err) + err = submitExpectFailure(r, session.Identity.SessionID, row.opID, "hello") + testassert.Require(t, errors.Is(err, row.wantClass) && strings.Contains(err.Error(), row.wantText), "%s submit = %v, want %v with %q", row.agent, err, row.wantClass, row.wantText) + { + err := readOperationError(ctx, r, session.Identity.SessionID, row.opID) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "%s rejection left %v, want no Operation", row.agent, err) } } @@ -960,9 +854,7 @@ func TestProductionModelSelectionRejection(t *testing.T) { r.workspaces.mu.Lock() live := len(r.workspaces.live) r.workspaces.mu.Unlock() - if live != 0 { - t.Fatalf("live Workspace scopes after the rejections = %d, want none", live) - } + testassert.Equal(t, live, 0) }) } @@ -971,40 +863,38 @@ func TestProductionReloadCapture(t *testing.T) { ctx := context.Background() hook := e.hookedHook() r, err := e.open(ctx, hook, &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("reload-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) // The in-flight admission stays on its captured revision. e.server.armGate() submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "parked") e.server.awaitRequests(1) writeServiceFile(t, e.configPath, productionConfigDocument(e.server.URL, "T2")) - if revision, err := r.Reload(ctx); err != nil || revision != "2" { - t.Fatalf("Reload = (%q, %v), want 2", revision, err) + { + revision, err := r.Reload(ctx) + testassert.Require(t, err == nil && revision == "2", "Reload = (%q, %v), want 2", revision, err) } e.server.releaseGate() - if rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess); rec.Admission.Execution.ConfigurationRevision != "1" { - t.Fatalf("in-flight capture revision = %q, want the captured 1", rec.Admission.Execution.ConfigurationRevision) + { + rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) + testassert.Equal(t, rec.Admission.Execution.ConfigurationRevision, "1") } // The next admission prepares on the new revision and its hook reads // the new tag; the fresh Session rules out any post-terminal drain // racing the submit into a steering buffer. next, err := r.createSession(ctx, e.workspace("reload-ws-2"), "worker") - if err != nil { - t.Fatalf("createSession(next): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, next.Identity.SessionID, "op-2", "hello") - if rec := awaitOperation(t, r, next.Identity.SessionID, "op-2", harness.OperationSuccess); rec.Admission.Execution.ConfigurationRevision != "2" { - t.Fatalf("next capture revision = %q, want 2", rec.Admission.Execution.ConfigurationRevision) + { + rec := awaitOperation(t, r, next.Identity.SessionID, "op-2", harness.OperationSuccess) + testassert.Equal(t, rec.Admission.Execution.ConfigurationRevision, "2") } - if got := hook.calls(); !slices.Equal(got, []hookCall{{revision: "1", tag: "T1"}, {revision: "2", tag: "T2"}}) { - t.Fatalf("hook calls = %v, want each admission's own revision tag", got) + { + got := hook.calls() + testassert.Require(t, slices.Equal(got, []hookCall{{revision: "1", tag: "T1"}, {revision: "2", tag: "T2"}}), "hook calls = %v, want each admission's own revision tag", got) } }) } @@ -1013,35 +903,17 @@ func TestProductionQueuedAndFork(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("queue-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) // The queued message buffers over the blocked Operation and delivers // a working execution when the drain reaches it. e.server.armGate() submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "parked") e.server.awaitRequests(1) - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: "op-2", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "please write"}}, Mode: harness.MessageModeQueued, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionQueued { - return fmt.Errorf("submit = %v, want queued", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("queued submit: %v", err) - } + submitQueuedThroughRuntime(t, r, session.Identity.SessionID, "op-2", "please write") e.server.releaseGate() awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) awaitOperation(t, r, session.Identity.SessionID, "op-2", harness.OperationSuccess) @@ -1052,20 +924,16 @@ func TestProductionQueuedAndFork(t *testing.T) { // compose time. boundary := "" entries, err := e.store.ReadEntries(ctx, session.Identity.SessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.Kind == harness.EntryInput && entry.OperationID == "op-2" { boundary = entry.ID break } } - if boundary == "" { - t.Fatal("no admitted input entry was found for the fork boundary") - } + testassert.Require(t, boundary != "", "no admitted input entry was found for the fork boundary") var forked harness.SessionRecord - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { res, err := h.Fork(ctx, harness.ForkRequest{ SourceSessionID: session.Identity.SessionID, BoundaryEntryID: boundary, @@ -1077,13 +945,9 @@ func TestProductionQueuedAndFork(t *testing.T) { } forked = res.Session return nil - }); err != nil { - t.Fatalf("Fork: %v", err) - } + })) rec := awaitOperation(t, r, forked.Identity.SessionID, "op-fork", harness.OperationSuccess) - if rec.Admission.Execution.ConfigurationRevision != "1" || !strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked") { - t.Fatalf("forked Operation capture = %+v, want the hooked production capture", rec.Admission.Execution) - } + testassert.Require(t, rec.Admission.Execution.ConfigurationRevision == "1" && strings.HasSuffix(rec.Admission.Execution.SystemPrompt, "|hooked"), "forked Operation capture = %+v, want the hooked production capture", rec.Admission.Execution) }) } @@ -1092,36 +956,19 @@ func TestProductionHookCapture(t *testing.T) { ctx := context.Background() hook := e.hookedHook() r, err := e.open(ctx, hook, &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("hook-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } - submit := func(opID string) error { - return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: opID, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }) - return err - }) - } + testassert.NoError(t, err) // The hook's narrowed description survives into the committed // capture. hook.set(nil, func(c harness.ExecutionCapture) harness.ExecutionCapture { c.Tools[0].Description = "narrowed by the hook" return c }) - if err := submit("op-narrow"); err != nil { - t.Fatalf("submit: %v", err) - } + submitThroughRuntime(t, r, session.Identity.SessionID, "op-narrow", "hello") rec := awaitOperation(t, r, session.Identity.SessionID, "op-narrow", harness.OperationSuccess) - if got := rec.Admission.Execution.Tools[0].Description; got != "narrowed by the hook" { - t.Fatalf("committed description = %q, want the hook's narrowed form", got) - } + testassert.Equal(t, rec.Admission.Execution.Tools[0].Description, "narrowed by the hook") // Capability expansion and constraint changes still reject with no // Operation published. Every rejection runs on its own fresh Session, @@ -1146,24 +993,12 @@ func TestProductionHookCapture(t *testing.T) { } { hook.set(nil, row.mutate) rowSession, err := r.createSession(ctx, e.workspace("hook-"+row.name+"-ws"), "worker") - if err != nil { - t.Fatalf("createSession(%s): %v", row.name, err) - } - err = r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: rowSession.Identity.SessionID, OperationID: "op-" + row.name, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }) - return err - }) - if !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s submit = %v, want the ErrInvalid rejection", row.name, err) - } - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.ReadOperation(ctx, rowSession.Identity.SessionID, "op-"+row.name) - return err - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("%s rejection left %v, want no Operation", row.name, err) + testassert.Require(t, err == nil, "createSession(%s): %v", row.name, err) + err = submitExpectFailure(r, rowSession.Identity.SessionID, "op-"+row.name, "hello") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s submit = %v, want the ErrInvalid rejection", row.name, err) + { + err := readOperationError(ctx, r, rowSession.Identity.SessionID, "op-"+row.name) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "%s rejection left %v, want no Operation", row.name, err) } } hook.set(nil, nil) @@ -1184,12 +1019,8 @@ func TestProductionFailedOpening(t *testing.T) { // same root opens again once the document is valid. writeServiceFile(t, e.configPath, productionConfigDocument(e.server.URL, "T1")) r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("reopen after failed construction: %v", err) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(ctx)) }) } @@ -1198,13 +1029,9 @@ func TestProductionBlockedShutdown(t *testing.T) { ctx := context.Background() park := &parkingHook{} r, err := e.open(ctx, e.hookedHook(), park) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(ctx, e.workspace("shutdown-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) // The armed hook parks the admission: an in-flight admitted call // blocks Close until released, and the joined shutdown completes. @@ -1213,13 +1040,7 @@ func TestProductionBlockedShutdown(t *testing.T) { park.arm(release, used) done := make(chan error, 1) go func() { - done <- r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: "op-parked", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }) - return err - }) + done <- submitExpectFailure(r, session.Identity.SessionID, "op-parked", "hello") }() select { case <-used: @@ -1234,11 +1055,10 @@ func TestProductionBlockedShutdown(t *testing.T) { case <-time.After(200 * time.Millisecond): } close(release) - if err := <-closeDone; err != nil { - t.Fatalf("Close: %v", err) - } - if err := <-done; err == nil { - t.Fatal("the parked admission succeeded after the owner canceled; want the cancellation failure") + testassert.NoError(t, <-closeDone) + { + err := <-done + testassert.Require(t, err != nil, "the parked admission succeeded after the owner canceled; want the cancellation failure") } }) @@ -1247,39 +1067,22 @@ func TestProductionBlockedShutdown(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) session, err := r.createSession(ctx, e.workspace("shutdown-successor-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) e.server.armGate() // the run parks at its first model request submitThroughRuntime(t, r, session.Identity.SessionID, "op-run", "hello") e.server.awaitRequests(1) - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session.Identity.SessionID, OperationID: "op-steer", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "steer"}}, Mode: harness.MessageModeRegular, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionSteering { - t.Errorf("steering submit disposition = %q, want steering", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("steering submit: %v", err) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + steering, err := submitRawThroughRuntime(ctx, r, session.Identity.SessionID, "op-steer", "steer", harness.MessageModeRegular) + testassert.NoError(t, err) + testassert.Check(t, steering.Disposition == harness.DispositionSteering, "steering submit disposition = %q, want steering", steering.Disposition) + testassert.NoError(t, r.Close(ctx)) e.server.releaseGate() - if _, err := e.store.ReadRegister(ctx, harness.RegisterKey{ - SessionID: session.Identity.SessionID, Kind: harness.RegisterOperation, OperationID: "op-steer", - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("shutdown started a successor: %v", err) + { + _, err := e.store.ReadRegister(ctx, harness.RegisterKey{ + SessionID: session.Identity.SessionID, Kind: harness.RegisterOperation, OperationID: "op-steer", + }) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "shutdown started a successor: %v", err) } }) } @@ -1288,18 +1091,15 @@ func TestProductionDuplicateTools(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("dup-ws"), "duper") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "hello") rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) - if got := toolNames(rec.Admission.Execution.Tools); !slices.Equal(got, []string{"prod_read", "prod_write"}) { - t.Fatalf("advertisement = %v, want duplicates deduped by first occurrence", got) + { + got := toolNames(rec.Admission.Execution.Tools) + testassert.Require(t, slices.Equal(got, []string{"prod_read", "prod_write"}), "advertisement = %v, want duplicates deduped by first occurrence", got) } }) } @@ -1308,21 +1108,16 @@ func TestProductionReadonlyAdvertisement(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("ro-ws"), "ro") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "hello") rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) - if !rec.Admission.Execution.Readonly { - t.Fatalf("capture readonly = %v, want the agent's constraint", rec.Admission.Execution.Readonly) - } - if got := toolNames(rec.Admission.Execution.Tools); !slices.Equal(got, []string{"prod_read"}) { - t.Fatalf("advertisement = %v, want the mutation tool dropped by the hard constraint", got) + testassert.Require(t, rec.Admission.Execution.Readonly, "capture readonly = %v, want the agent's constraint", rec.Admission.Execution.Readonly) + { + got := toolNames(rec.Admission.Execution.Tools) + testassert.Require(t, slices.Equal(got, []string{"prod_read"}), "advertisement = %v, want the mutation tool dropped by the hard constraint", got) } }) } @@ -1342,53 +1137,26 @@ func TestProductionTransportConversion(t *testing.T) { obs := newObservation() svc := newConfigurationService(owner, c, sh.loader, sh.configPath, obs) snapshot, err := svc.publish(owner) - if err != nil { - t.Fatalf("publish: %v", err) - } + testassert.NoError(t, err) p := newPreparation(svc, c, runtimeScope, newWorkspaceScopes(owner, c, []*scope{runtimeScope}, obs), sh.home, nil, nil, nil, nil) ref := model.ModelRef{Provider: "prov", Model: "m"} provider, entry, err := snapshot.snapshot.catalog.Lookup(catalog.ModelRef{Provider: ref.Provider, Model: ref.Model}) - if err != nil { - t.Fatalf("catalog lookup: %v", err) - } + testassert.NoError(t, err) resolved, err := p.resolveTransport(provider, entry, ref, "resolved-key") - if err != nil { - t.Fatalf("resolveTransport: %v", err) - } - if resolved.Model != ref || resolved.BaseURL != "https://prov.test/v1" || resolved.APIKey != "resolved-key" { - t.Fatalf("resolved identity = %+v, want the captured endpoint, key, and model", resolved) - } - if resolved.WireSystemRole != "user" { - t.Fatalf("wire system role = %q, want the model-level override", resolved.WireSystemRole) - } - if !resolved.StreamedUsage { - t.Fatal("streamed usage = false, want the model-level true override of the provider-level false") - } - if resolved.Headers["X-Trace"] != "t1" { - t.Fatalf("resolved headers = %v, want the captured transport headers", resolved.Headers) - } - if got := string(resolved.ProviderExtraBody["provider_side"]); got != `"p"` { - t.Fatalf("provider sidecar = %s, want the verbatim re-encoded value", got) - } - if got := string(resolved.ProviderExtraBody["big"]); got != "9007199254740993" { - t.Fatalf("provider sidecar number = %s, want the raw lexeme with no float coercion", got) - } - if got := string(resolved.ModelExtraBody["precise"]); got != "1.25" { - t.Fatalf("model sidecar number = %s, want the raw lexeme", got) - } - if resolved.ProtocolFamily != "fam" || !slices.Equal(resolved.MustPreserve, []string{"proto_keep"}) || - !resolved.Drop["drop_me"] || len(resolved.Drop) != 1 { - t.Fatalf("resolved protocol metadata = family %q must-preserve %v drop %v, want the model-level effective values", resolved.ProtocolFamily, resolved.MustPreserve, resolved.Drop) - } + testassert.NoError(t, err) + testassert.Require(t, resolved.Model == ref && resolved.BaseURL == "https://prov.test/v1" && resolved.APIKey == "resolved-key" && resolved.WireSystemRole == "user", "resolved identity = %+v, want the captured endpoint, key, and model; got %v, want %v", resolved, resolved.WireSystemRole, "user") + testassert.Require(t, resolved.StreamedUsage, "streamed usage = false, want the model-level true override of the provider-level false") + testassert.Require(t, resolved.Headers["X-Trace"] == "t1", "resolved headers = %v, want the captured transport headers", resolved.Headers) + testassert.Equal(t, string(resolved.ProviderExtraBody["provider_side"]), `"p"`) + testassert.Equal(t, string(resolved.ProviderExtraBody["big"]), "9007199254740993") + testassert.Require(t, string(resolved.ModelExtraBody["precise"]) == "1.25" && resolved.ProtocolFamily == "fam" && slices.Equal(resolved.MustPreserve, []string{"proto_keep"}) && resolved.Drop["drop_me"] && len(resolved.Drop) == 1, "got %v, want %v; resolved protocol metadata = family %q must-preserve %v drop %v, want the model-level effective values", string(resolved.ModelExtraBody["precise"]), "1.25", resolved.ProtocolFamily, resolved.MustPreserve, resolved.Drop) wantFamilies := map[model.ModelRef]string{ {Provider: "prov", Model: "m"}: "fam", {Provider: "prov", Model: "sib"}: "fam", {Provider: "prov", Model: "other"}: "else", } - if len(resolved.SourceFamilies) != len(wantFamilies) { - t.Fatalf("source families = %v, want exactly the same-provider map %v", resolved.SourceFamilies, wantFamilies) - } + testassert.Require(t, len(resolved.SourceFamilies) == len(wantFamilies), "source families = %v, want exactly the same-provider map %v", resolved.SourceFamilies, wantFamilies) for k, v := range wantFamilies { if resolved.SourceFamilies[k] != v { t.Fatalf("source family of %s = %q, want %q", k.String(), resolved.SourceFamilies[k], v) @@ -1397,43 +1165,28 @@ func TestProductionTransportConversion(t *testing.T) { // The system message encodes with the effective wire role. body, _, err := model.Encode(resolved, model.Request{Messages: []model.Message{{Role: model.RoleSystem, Content: []model.ContentPart{{Kind: model.PartText, Text: "sys"}}}}}, nil) - if err != nil { - t.Fatalf("encode system: %v", err) - } - if !strings.Contains(string(body), `"role":"user"`) { - t.Fatalf("system message wire role = %s, want the effective user role", body) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(body), `"role":"user"`), "system message wire role = %s, want the effective user role", body) // The wire-debug opt-in: off by default; enabled it creates the // home-based private directory and supplies it; a creation failure // disables diagnostics, never execution. - if got := resolved.WireDebugDir; got != "" { - t.Fatalf("wire debug dir with the opt-in off = %q, want empty", got) - } + testassert.Equal(t, resolved.WireDebugDir, "") t.Setenv(debugWireEnv, "1") enabled, err := p.resolveTransport(provider, entry, ref, "resolved-key") - if err != nil { - t.Fatalf("resolveTransport with the opt-in on: %v", err) - } + testassert.NoError(t, err) want := filepath.Join(sh.home, ".lightcode", "debug", "wire") - if enabled.WireDebugDir != want { - t.Fatalf("wire debug dir = %q, want the created home-based %q", enabled.WireDebugDir, want) - } - if info, err := os.Stat(want); err != nil || !info.IsDir() { - t.Fatalf("wire debug directory = (%v, %v), want the created private directory", info, err) + testassert.Equal(t, enabled.WireDebugDir, want) + { + info, err := os.Stat(want) + testassert.Require(t, err == nil && info.IsDir(), "wire debug directory = (%v, %v), want the created private directory", info, err) } blocked := filepath.Join(sh.home, "file.txt") - if err := os.WriteFile(blocked, []byte("a file, not a directory"), 0o600); err != nil { - t.Fatalf("write the blocking file: %v", err) - } + testassert.NoError(t, os.WriteFile(blocked, []byte("a file, not a directory"), 0o600)) broken := &preparation{home: filepath.Join(blocked, "sub")} disabled, err := broken.resolveTransport(provider, entry, ref, "resolved-key") - if err != nil { - t.Fatalf("resolveTransport over an unusable home: %v", err) - } - if disabled.WireDebugDir != "" { - t.Fatalf("wire debug dir over an unusable home = %q, want the empty disabled form", disabled.WireDebugDir) - } + testassert.NoError(t, err) + testassert.Equal(t, disabled.WireDebugDir, "") } func conversionConfigDocument() string { @@ -1465,14 +1218,10 @@ func TestProductionCommandRow(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("run-ws"), "runner") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "please run") rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) if !slices.Equal(toolNames(rec.Admission.Execution.Tools), []string{"run_command"}) { @@ -1481,9 +1230,7 @@ func TestProductionCommandRow(t *testing.T) { // The settled result content proves the command ran through the // concrete opener's dispatch. entries, err := e.store.ReadEntries(ctx, session.Identity.SessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var content string for _, entry := range entries { if entry.Kind != harness.EntryToolResult { @@ -1492,14 +1239,10 @@ func TestProductionCommandRow(t *testing.T) { var wire struct { Content string `json:"content"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode tool result: %v", err) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) content = wire.Content } - if content != "production-command-row" { - t.Fatalf("settled run_command content = %q, want the echoed token", content) - } + testassert.Equal(t, content, "production-command-row") }) } @@ -1512,34 +1255,23 @@ func TestProductionAdaptationComposes(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.openWithPlugins(ctx, e.hookedHook(), &parkingHook{}, prodAdaptationPlugin()) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("adapt-ws"), "adapted") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "hello") rec := awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) prompt := rec.Admission.Execution.SystemPrompt - if !strings.Contains(prompt, "PROD-ADAPTATION-BLOCK") { - t.Fatalf("captured prompt misses the adaptation's coaching block: %.200s", prompt) - } - if !strings.Contains(prompt, "ADDITION-SAFETY-TEXT") { - t.Fatalf("captured prompt misses the adaptation's section addition: %.200s", prompt) - } - if got := toolNames(rec.Admission.Execution.Tools); !slices.Equal(got, []string{"prod_write"}) { - t.Fatalf("advertisement = %v, want the adaptation's excluded tool absent", got) + testassert.Require(t, strings.Contains(prompt, "PROD-ADAPTATION-BLOCK") && strings.Contains(prompt, "ADDITION-SAFETY-TEXT"), "captured prompt misses the adaptation's coaching block: %.200s; captured prompt misses the adaptation's section addition: %.200s", prompt, prompt) + { + got := toolNames(rec.Admission.Execution.Tools) + testassert.Require(t, slices.Equal(got, []string{"prod_write"}), "advertisement = %v, want the adaptation's excluded tool absent", got) } // The exclusion is the adaptation's work, not the hard constraints: // the capture's readonly/write-dir fields stay the definition's. - if rec.Admission.Execution.Readonly || rec.Admission.Execution.WriteDir != "" { - t.Fatalf("capture constraints = (%v, %q), want the definition's untouched values", - rec.Admission.Execution.Readonly, rec.Admission.Execution.WriteDir) - } + testassert.Require(t, !rec.Admission.Execution.Readonly && rec.Admission.Execution.WriteDir == "", "capture constraints = (%v, %q), want the definition's untouched values", rec.Admission.Execution.Readonly, rec.Admission.Execution.WriteDir) }) } @@ -1551,26 +1283,18 @@ func TestProductionKeylessProvider(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) session, err := r.createSession(ctx, e.workspace("keyless-ws"), "keyless") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, session.Identity.SessionID, "op-1", "please write") awaitOperation(t, r, session.Identity.SessionID, "op-1", harness.OperationSuccess) if e.server.requests() != 2 { t.Fatalf("model requests = %d, want the tool-call turn and the follow-up", e.server.requests()) } - if got := e.server.authAt(0); got != "" { - t.Fatalf("keyless request authorization = %q, want the header omitted entirely", got) - } + testassert.Equal(t, e.server.authAt(0), "") data, err := os.ReadFile(filepath.Join(e.workspace("keyless-ws"), "out.txt")) - if err != nil || string(data) != "written by production" { - t.Fatalf("prod_write effect = (%q, %v), want the written content", data, err) - } + testassert.Require(t, err == nil && string(data) == "written by production", "prod_write effect = (%q, %v), want the written content", data, err) }) } @@ -1586,15 +1310,11 @@ func TestExecutionOpenerToolWriteWaitsForRestoreInterval(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer r.Close(ctx) workspace := e.workspace("interval-ws") session, err := r.createSession(ctx, workspace, "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID op := submitCodeOperation(t, r, sessionID, "op-1", "first") awaitRestorableSession(t, r, sessionID) @@ -1607,28 +1327,20 @@ func TestExecutionOpenerToolWriteWaitsForRestoreInterval(t *testing.T) { admitted := make(chan error, 1) go func() { - admitted <- r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: "op-write", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "write it"}}, - Mode: harness.MessageModeRegular, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionAdmitted { - return fmt.Errorf("disposition %q, want admitted", res.Disposition) - } - return nil - }) + res, err := submitRawThroughRuntime(ctx, r, sessionID, "op-write", "write it", harness.MessageModeRegular) + if err != nil { + admitted <- err + return + } + if res.Disposition != harness.DispositionAdmitted { + admitted <- fmt.Errorf("disposition %q, want admitted", res.Disposition) + return + } + admitted <- nil }() select { case err := <-admitted: - if err != nil { - t.Fatalf("admission during a parked restore: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("admission blocked during a parked restore: admission stays Harness-owned") } @@ -1640,8 +1352,9 @@ func TestExecutionOpenerToolWriteWaitsForRestoreInterval(t *testing.T) { awaitOpenerRegistered(t, r, sessionID) // The real write has not begun: the opener cannot have won the token. - if data, err := os.ReadFile(file); err != nil || len(data) != 0 { - t.Fatalf("the tool write began inside the restore interval: (%q, %v)", data, err) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && len(data) == 0, "the tool write began inside the restore interval: (%q, %v)", data, err) } park.release("restored") @@ -1651,18 +1364,14 @@ func TestExecutionOpenerToolWriteWaitsForRestoreInterval(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if parkedRestore.err != nil { - t.Fatalf("parked restore = %v", parkedRestore.err) - } - if len(parkedRestore.result.Restored) != 1 || parkedRestore.result.Restored[0] != file { - t.Fatalf("restored = %v, want the parked group's restore", parkedRestore.result.Restored) - } + testassert.Require(t, parkedRestore.err == nil && len(parkedRestore.result.Restored) == 1 && parkedRestore.result.Restored[0] == file, "parked restore = %v; restored = %v, want the parked group's restore", parkedRestore.err, parkedRestore.result.Restored) // The opener won the released interval: the real tool effect ran and // the durable Operation settled. awaitOperation(t, r, sessionID, "op-write", harness.OperationSuccess) - if data, err := os.ReadFile(file); err != nil || string(data) != "written by production" { - t.Fatalf("file after the tool write = (%q, %v), want the tool's content", data, err) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "written by production", "file after the tool write = (%q, %v), want the tool's content", data, err) } }) } diff --git a/runtime/protocol_code_test.go b/runtime/protocol_code_test.go index 29d5b65f..03e2b2b3 100644 --- a/runtime/protocol_code_test.go +++ b/runtime/protocol_code_test.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "encoding/json" "errors" - "fmt" "net/http" "os" "path/filepath" @@ -21,7 +20,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/snapshot" - "github.com/MMinasyan/lightcode/model" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" "golang.org/x/sys/unix" ) @@ -39,18 +38,16 @@ func codeGroupRoot(r *Runtime, sessionID, entryID string) string { func captureCodeMutation(t *testing.T, groupRoot, displayPath, target string, content []byte) { t.Helper() group, err := snapshot.OpenCodeStore(groupRoot) - if err != nil { - t.Fatalf("OpenCodeStore(%s): %v", groupRoot, err) - } + testassert.Require(t, err == nil, "OpenCodeStore(%s): %v", groupRoot, err) entryID, _, err := group.SnapshotResolvedEntry(1, displayPath, target) - if err != nil { - t.Fatalf("SnapshotResolvedEntry(%s): %v", target, err) - } - if err := os.WriteFile(target, content, 0o600); err != nil { - t.Fatalf("WriteFile(%s): %v", target, err) + testassert.Require(t, err == nil, "SnapshotResolvedEntry(%s): %v", target, err) + { + err := os.WriteFile(target, content, 0o600) + testassert.Require(t, err == nil, "WriteFile(%s): %v", target, err) } - if err := group.RecordSnapshotContent(1, entryID, content); err != nil { - t.Fatalf("RecordSnapshotContent(%s): %v", target, err) + { + err := group.RecordSnapshotContent(1, entryID, content) + testassert.Require(t, err == nil, "RecordSnapshotContent(%s): %v", target, err) } group.RetainSnapshotEntry(1, entryID) } @@ -60,19 +57,8 @@ func captureCodeMutation(t *testing.T, groupRoot, displayPath, target string, co // buffered submission drains under the same caller ID. func submitCodeOperation(t *testing.T, r *Runtime, sessionID, operationID, text string) harness.OperationRecord { t.Helper() - err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: harness.MessageModeRegular, - }) - return err - }) - if err != nil { - t.Fatalf("Submit(%q): %v", operationID, err) - } + _, err := submitRawThroughRuntime(context.Background(), r, sessionID, operationID, text, harness.MessageModeRegular) + testassert.Require(t, err == nil, "Submit(%q): %v", operationID, err) rec := awaitOperation(t, r, sessionID, operationID, harness.OperationSuccess) awaitIdleSession(t, r, sessionID) return rec @@ -107,17 +93,13 @@ func TestCodeSnapshotsTargetGroups(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "code-groups") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID // A valid Session with no recorded groups lists a non-nil empty array // before any capture exists. initial, err := r.listSessionCodeSnapshots(context.Background(), session) - if err != nil || initial == nil || len(initial) != 0 { - t.Fatalf("initial group list = (%+v, %v), want a non-nil empty list", initial, err) - } + testassert.Require(t, err == nil && initial != nil && len(initial) == 0, "initial group list = (%+v, %v), want a non-nil empty list", initial, err) // Admission order is op-z, then op-a: OperationID order is reversed. first := submitCodeOperation(t, r, session, "op-z", "first") @@ -126,49 +108,29 @@ func TestCodeSnapshotsTargetGroups(t *testing.T) { firstEntry := first.Admission.AdmittedEntry.EntryID secondEntry := second.Admission.AdmittedEntry.EntryID emptyEntry := empty.Admission.AdmittedEntry.EntryID - if firstEntry == "" || secondEntry == "" || emptyEntry == "" { - t.Fatalf("admitted entries = (%q, %q, %q), want all present", firstEntry, secondEntry, emptyEntry) - } + testassert.Require(t, firstEntry != "" && secondEntry != "" && emptyEntry != "", "admitted entries = (%q, %q, %q), want all present", firstEntry, secondEntry, emptyEntry) edited := filepath.Join(workspace, "notes.txt") - if err := os.WriteFile(edited, []byte("v0"), 0o600); err != nil { - t.Fatalf("write preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(edited, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, session, firstEntry), edited, edited, []byte("v1")) created := filepath.Join(workspace, "created.txt") captureCodeMutation(t, codeGroupRoot(r, session, secondEntry), created, created, []byte("new")) - if err := os.MkdirAll(codeGroupRoot(r, session, emptyEntry), 0o700); err != nil { - t.Fatalf("mkdir empty group: %v", err) - } + testassert.NoError(t, os.MkdirAll(codeGroupRoot(r, session, emptyEntry), 0o700)) groups, err := r.listSessionCodeSnapshots(context.Background(), session) - if err != nil { - t.Fatalf("listSessionCodeSnapshots: %v", err) - } - if len(groups) != 2 { - t.Fatalf("groups = %+v, want exactly the two recorded groups", groups) - } - if groups[0].OperationId != "op-z" || groups[1].OperationId != "op-a" { - t.Fatalf("group order = (%q, %q), want admission order (op-z, op-a)", groups[0].OperationId, groups[1].OperationId) - } - if groups[0].GroupItemId != projectItemID(session, firstEntry) || groups[1].GroupItemId != projectItemID(session, secondEntry) { - t.Fatalf("group item ids = (%q, %q), want the admitted-entry projections", groups[0].GroupItemId, groups[1].GroupItemId) - } - if len(groups[0].Files) != 1 || groups[0].Files[0] != (protocol.SnapshotFile{Path: edited, Existed: true}) { - t.Fatalf("first group files = %+v, want the edited display path with an existing preimage", groups[0].Files) - } - if len(groups[1].Files) != 1 || groups[1].Files[0] != (protocol.SnapshotFile{Path: created, Existed: false}) { - t.Fatalf("second group files = %+v, want the created display path with an absent preimage", groups[1].Files) - } + testassert.NoError(t, err) + testassert.Require(t, len(groups) == 2, "groups = %+v, want exactly the two recorded groups", groups) + testassert.Require(t, groups[0].OperationId == "op-z" && groups[1].OperationId == "op-a", "group order = (%q, %q), want admission order (op-z, op-a)", groups[0].OperationId, groups[1].OperationId) + testassert.Require(t, groups[0].GroupItemId == projectItemID(session, firstEntry) && groups[1].GroupItemId == projectItemID(session, secondEntry), "group item ids = (%q, %q), want the admitted-entry projections", groups[0].GroupItemId, groups[1].GroupItemId) + testassert.Require(t, len(groups[0].Files) == 1 && groups[0].Files[0] == (protocol.SnapshotFile{Path: edited, Existed: true}), "first group files = %+v, want the edited display path with an existing preimage", groups[0].Files) + testassert.Require(t, len(groups[1].Files) == 1 && groups[1].Files[0] == (protocol.SnapshotFile{Path: created, Existed: false}), "second group files = %+v, want the created display path with an absent preimage", groups[1].Files) // A returned group list is a caller copy: mutation cannot affect // the next read. groups[0].OperationId = "mutated" groups[0].Files[0].Path = "mutated" reread, err := r.listSessionCodeSnapshots(context.Background(), session) - if err != nil || len(reread) != 2 || reread[0].OperationId != "op-z" || reread[0].Files[0] != (protocol.SnapshotFile{Path: edited, Existed: true}) { - t.Fatalf("re-read groups = (%+v, %v), want the immutable first projection", reread, err) - } + testassert.Require(t, err == nil && len(reread) == 2 && reread[0].OperationId == "op-z" && reread[0].Files[0] == (protocol.SnapshotFile{Path: edited, Existed: true}), "re-read groups = (%+v, %v), want the immutable first projection", reread, err) }) } @@ -189,13 +151,8 @@ func openCodeRuntime(t *testing.T, store harness.Storage) *codeRuntime { // text turns and manual compaction. e := newOwnerEnv(t) writeServiceFile(t, agents.PathForConfig(e.configPath), projectionAgentsDocument) - ticks := make(chan time.Time) - opts := e.options(e.storagePlugin(store), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, newLifecycleStopper())) - opts.sweepTicks = ticks - r, err := open(context.Background(), opts) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _, err := e.openSwept(context.Background(), e.storagePlugin(store), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, newLifecycleStopper())) + testassert.NoError(t, err) return &codeRuntime{t: t, store: store, r: r, e: e} } @@ -213,17 +170,7 @@ func (f *codeRuntime) session(name string) string { // is the rendezvous, not the terminal state. func (f *codeRuntime) submit(sessionID, operationID, text string) { f.t.Helper() - err := f.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: harness.MessageModeRegular, - }) - return err - }) - if err != nil { + if _, err := submitRawThroughRuntime(context.Background(), f.r, sessionID, operationID, text, harness.MessageModeRegular); err != nil { f.t.Fatalf("Submit(%s): %v", operationID, err) } } @@ -256,24 +203,11 @@ func (f *codeRuntime) compactIdle(sessionID, operationID string) harness.Operati // disposition. func (f *codeRuntime) submitMode(sessionID, operationID, text string, mode harness.MessageMode) harness.SubmitDisposition { f.t.Helper() - var disposition harness.SubmitDisposition - err := f.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: mode, - }) - if err == nil { - disposition = res.Disposition - } - return err - }) + res, err := submitRawThroughRuntime(context.Background(), f.r, sessionID, operationID, text, mode) if err != nil { f.t.Fatalf("Submit(%s): %v", operationID, err) } - return disposition + return res.Disposition } // TestCodeSnapshotsSteeringDerivesOwnGroup proves a delivered steering input @@ -294,11 +228,10 @@ func TestCodeSnapshotsSteeringDerivesOwnGroup(t *testing.T) { f.e.server.setHold(gate) f.submit(session, "op-1", "start work") awaitModelArrival(t, f.e) - if got := f.submitMode(session, "op-steer", "steering text", harness.MessageModeRegular); got != harness.DispositionSteering { - t.Fatalf("steering submit = %q, want steering", got) - } - if got := sessionViewThroughRuntime(t, f.r, session); len(got.Operations) != 1 { - t.Fatalf("operations while the steering input is buffered = %d, want only the predecessor", len(got.Operations)) + testassert.Equal(t, f.submitMode(session, "op-steer", "steering text", harness.MessageModeRegular), harness.DispositionSteering) + { + got := sessionViewThroughRuntime(t, f.r, session) + testassert.Equal(t, len(got.Operations), 1) } close(gate) awaitOperation(t, f.r, session, "op-1", harness.OperationSuccess) @@ -307,66 +240,47 @@ func TestCodeSnapshotsSteeringDerivesOwnGroup(t *testing.T) { steered := readOperation(t, f.r, session, "op-steer").Admission.AdmittedEntry.EntryID admitted := readOperation(t, f.r, session, "op-1").Admission.AdmittedEntry.EntryID - if admitted == "" || steered == "" || steered == admitted { - t.Fatalf("admitted/steering entries = (%q, %q), want two distinct admitted inputs", admitted, steered) - } + testassert.Require(t, admitted != "" && steered != "" && steered != admitted, "admitted/steering entries = (%q, %q), want two distinct admitted inputs", admitted, steered) workspace := filepath.Join(f.r.dataDir, "steering-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) admittedFile := filepath.Join(workspace, "admitted.txt") - if err := os.WriteFile(admittedFile, []byte("v0"), 0o600); err != nil { - t.Fatalf("write admitted preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(admittedFile, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(f.r, session, admitted), admittedFile, admittedFile, []byte("v1")) steeringFile := filepath.Join(workspace, "steering.txt") - if err := os.WriteFile(steeringFile, []byte("s0"), 0o600); err != nil { - t.Fatalf("write steering preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(steeringFile, []byte("s0"), 0o600)) captureCodeMutation(t, codeGroupRoot(f.r, session, steered), steeringFile, steeringFile, []byte("s1")) awaitRestorableSession(t, f.r, session) groups, err := f.r.listSessionCodeSnapshots(context.Background(), session) - if err != nil { - t.Fatalf("listSessionCodeSnapshots: %v", err) - } - if len(groups) != 2 { - t.Fatalf("groups = %+v, want the predecessor's and the steering head's own groups", groups) - } + testassert.NoError(t, err) + testassert.Require(t, len(groups) == 2, "groups = %+v, want the predecessor's and the steering head's own groups", groups) byOperation := map[string]protocol.CodeSnapshotGroup{} for _, group := range groups { byOperation[group.OperationId] = group } - if _, ok := byOperation["op-1"]; !ok { - t.Fatalf("groups = %+v, want the predecessor's admitted-entry group", groups) + { + _, ok := byOperation["op-1"] + testassert.Require(t, ok, "groups = %+v, want the predecessor's admitted-entry group", groups) } steerGroup, ok := byOperation["op-steer"] - if !ok || steerGroup.GroupItemId != projectItemID(session, steered) { - t.Fatalf("steering group = %+v, want the steering head's own admitted-entry group %q", steerGroup, steered) - } + testassert.Require(t, ok && steerGroup.GroupItemId == projectItemID(session, steered), "steering group = %+v, want the steering head's own admitted-entry group %q", steerGroup, steered) // Restoring to the steering head's boundary leaves the predecessor's // group untouched; restoring to the predecessor covers both. result, err := f.r.revertSessionCode(context.Background(), session, "op-steer") - if err != nil { - t.Fatalf("revertSessionCode(op-steer): %v", err) - } - if len(result.Restored) != 1 || result.Restored[0] != steeringFile { - t.Fatalf("restored = %v, want only the steering head's file", result.Restored) - } - if data, err := os.ReadFile(admittedFile); err != nil || string(data) != "v1" { - t.Fatalf("admitted-entry file = (%q, %v), want it untouched by the steering head's restore", data, err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == steeringFile, "restored = %v, want only the steering head's file", result.Restored) + { + data, err := os.ReadFile(admittedFile) + testassert.Require(t, err == nil && string(data) == "v1", "admitted-entry file = (%q, %v), want it untouched by the steering head's restore", data, err) } result, err = f.r.revertSessionCode(context.Background(), session, "op-1") - if err != nil { - t.Fatalf("revertSessionCode(op-1): %v", err) - } - if len(result.Restored) != 1 || result.Restored[0] != admittedFile { - t.Fatalf("restored to the predecessor = %v, want the predecessor's file (the consumed steering group already restored)", result.Restored) - } - if data, err := os.ReadFile(steeringFile); err != nil || string(data) != "s0" { - t.Fatalf("steering-entry file = (%q, %v), want the earlier restore's preimage retained", data, err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == admittedFile, "restored to the predecessor = %v, want the predecessor's file (the consumed steering group already restored)", result.Restored) + { + data, err := os.ReadFile(steeringFile) + testassert.Require(t, err == nil && string(data) == "s0", "steering-entry file = (%q, %v), want the earlier restore's preimage retained", data, err) } }) } @@ -381,9 +295,7 @@ func TestCodeSnapshotsBoundaryAndRevertOrder(t *testing.T) { defer func() { _ = f.r.Close(context.Background()) }() session := f.session("revert-order") workspace := filepath.Join(f.r.dataDir, "revert-order-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) f.submit(session, "op-z", "first") awaitOperation(t, f.r, session, "op-z", harness.OperationSuccess) @@ -401,9 +313,7 @@ func TestCodeSnapshotsBoundaryAndRevertOrder(t *testing.T) { // The preimage chain: op-z writes v1, op-a writes v2. edited := filepath.Join(workspace, "chain.txt") - if err := os.WriteFile(edited, []byte("v0"), 0o600); err != nil { - t.Fatalf("write chain preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(edited, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(f.r, session, firstEntry), edited, edited, []byte("v1")) created := filepath.Join(workspace, "created.txt") captureCodeMutation(t, codeGroupRoot(f.r, session, lastEntry), edited, edited, []byte("v2")) @@ -412,17 +322,15 @@ func TestCodeSnapshotsBoundaryAndRevertOrder(t *testing.T) { // The middle Operation owns no recorded snapshots but is a valid // boundary: op-a still rewinds. result, err := f.r.revertSessionCode(context.Background(), session, "op-m") - if err != nil { - t.Fatalf("revert boundary op-m: %v", err) - } - if len(result.Restored) != 2 || !slices.Contains(result.Restored, edited) || !slices.Contains(result.Restored, created) { - t.Fatalf("restored = %v, want the later group's edited and created files", result.Restored) - } - if data, err := os.ReadFile(edited); err != nil || string(data) != "v1" { - t.Fatalf("chain after boundary op-m = (%q, %v), want op-a's restored preimage", data, err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 2 && slices.Contains(result.Restored, edited) && slices.Contains(result.Restored, created), "restored = %v, want the later group's edited and created files", result.Restored) + { + data, err := os.ReadFile(edited) + testassert.Require(t, err == nil && string(data) == "v1", "chain after boundary op-m = (%q, %v), want op-a's restored preimage", data, err) } - if _, err := os.Stat(created); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("created file stat = %v, want restored absence", err) + { + _, err := os.Stat(created) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "created file stat = %v, want restored absence", err) } // Re-record op-a, then the earliest admitted boundary op-z rewinds the @@ -431,11 +339,10 @@ func TestCodeSnapshotsBoundaryAndRevertOrder(t *testing.T) { // addresses only op-z and skips on the v1 identity mismatch. captureCodeMutation(t, codeGroupRoot(f.r, session, lastEntry), edited, edited, []byte("v2")) result, err = f.r.revertSessionCode(context.Background(), session, "op-z") - if err != nil { - t.Fatalf("revert boundary op-z: %v", err) - } - if data, err := os.ReadFile(edited); err != nil || string(data) != "v0" { - t.Fatalf("chain after boundary op-z = (%q, %v, restored %v), want the earliest group's preimage", data, err, result.Restored) + testassert.NoError(t, err) + { + data, err := os.ReadFile(edited) + testassert.Require(t, err == nil && string(data) == "v0", "chain after boundary op-z = (%q, %v, restored %v), want the earliest group's preimage", data, err, result.Restored) } // A manual-compaction Operation has the zero admitted entry: it is not @@ -443,21 +350,19 @@ func TestCodeSnapshotsBoundaryAndRevertOrder(t *testing.T) { f.compactIdle(session, "compact-op") awaitRestorableSession(t, f.r, session) groups, err := f.r.listSessionCodeSnapshots(context.Background(), session) - if err != nil { - t.Fatalf("list after compact: %v", err) - } + testassert.NoError(t, err) for _, group := range groups { - if group.OperationId == "compact-op" { - t.Fatalf("compact Operation listed a code group: %+v", group) - } + testassert.Require(t, group.OperationId != "compact-op", "compact Operation listed a code group: %+v", group) } for _, boundary := range []string{"compact-op", "", "op-missing"} { - if _, err := f.r.revertSessionCode(context.Background(), session, boundary); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("revert boundary %q = %v, want harness.ErrInvalid", boundary, err) + { + _, err := f.r.revertSessionCode(context.Background(), session, boundary) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "revert boundary %q = %v, want harness.ErrInvalid", boundary, err) } } - if data, err := os.ReadFile(edited); err != nil || string(data) != "v0" { - t.Fatalf("chain after refused boundaries = (%q, %v), want no mutation", data, err) + { + data, err := os.ReadFile(edited) + testassert.Require(t, err == nil && string(data) == "v0", "chain after refused boundaries = (%q, %v), want no mutation", data, err) } }) } @@ -470,46 +375,28 @@ func TestCodeSnapshotsSkipOnIdentityChange(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "skip-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "one") entry := op.Admission.AdmittedEntry.EntryID changed := filepath.Join(workspace, "changed.txt") - if err := os.WriteFile(changed, []byte("v0"), 0o600); err != nil { - t.Fatalf("write changed preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(changed, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, session, entry), changed, changed, []byte("v1")) - if err := os.WriteFile(changed, []byte("external"), 0o600); err != nil { - t.Fatalf("external edit: %v", err) - } + testassert.NoError(t, os.WriteFile(changed, []byte("external"), 0o600)) moved := filepath.Join(workspace, "moved.txt") - if err := os.WriteFile(moved, []byte("m0"), 0o600); err != nil { - t.Fatalf("write moved preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(moved, []byte("m0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, session, entry), moved, moved, []byte("m1")) - if err := os.Remove(moved); err != nil { - t.Fatalf("remove moved: %v", err) - } + testassert.NoError(t, os.Remove(moved)) elsewhere := filepath.Join(workspace, "elsewhere.txt") - if err := os.WriteFile(elsewhere, []byte("elsewhere"), 0o600); err != nil { - t.Fatalf("write elsewhere: %v", err) - } - if err := os.Symlink(elsewhere, moved); err != nil { - t.Fatalf("symlink moved: %v", err) - } + testassert.NoError(t, os.WriteFile(elsewhere, []byte("elsewhere"), 0o600)) + testassert.NoError(t, os.Symlink(elsewhere, moved)) awaitRestorableSession(t, r, session) result, err := r.revertSessionCode(context.Background(), session, "op-1") - if err != nil { - t.Fatalf("revertSessionCode: %v", err) - } - if len(result.Restored) != 0 || len(result.Skipped) != 2 { - t.Fatalf("result = %+v, want both files skipped", result) - } + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 0 && len(result.Skipped) == 2, "result = %+v, want both files skipped", result) reasons := map[string]string{} for _, skip := range result.Skipped { reasons[skip.Path] = skip.Reason @@ -520,11 +407,13 @@ func TestCodeSnapshotsSkipOnIdentityChange(t *testing.T) { if !strings.Contains(reasons[moved], "canonical path changed") { t.Fatalf("moved skip = %q, want the canonical-identity reason", reasons[moved]) } - if data, err := os.ReadFile(changed); err != nil || string(data) != "external" { - t.Fatalf("changed file = (%q, %v), want the external content untouched", data, err) + { + data, err := os.ReadFile(changed) + testassert.Require(t, err == nil && string(data) == "external", "changed file = (%q, %v), want the external content untouched", data, err) } - if link, err := os.Readlink(moved); err != nil || link != elsewhere { - t.Fatalf("moved file = (%q, %v), want the symlink untouched", link, err) + { + link, err := os.Readlink(moved) + testassert.Require(t, err == nil && link == elsewhere, "moved file = (%q, %v), want the symlink untouched", link, err) } }) } @@ -537,16 +426,12 @@ func TestCodeSnapshotsForkAndChildIndependence(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "lineage-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) root := projectionSession(t, r, workspace, "solo").Identity.SessionID rootOp := submitCodeOperation(t, r, root, "op-root", "root") rootEntry := rootOp.Admission.AdmittedEntry.EntryID rootFile := filepath.Join(workspace, "root.txt") - if err := os.WriteFile(rootFile, []byte("r0"), 0o600); err != nil { - t.Fatalf("write root preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(rootFile, []byte("r0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, root, rootEntry), rootFile, rootFile, []byte("r1")) awaitOperation(t, r, root, "op-root", harness.OperationSuccess) // the settled root execution retires before Fork requires its idle source @@ -555,9 +440,7 @@ func TestCodeSnapshotsForkAndChildIndependence(t *testing.T) { forkOp := readOperation(t, r, forked, "op-fork") forkEntry := forkOp.Admission.AdmittedEntry.EntryID forkFile := filepath.Join(workspace, "fork.txt") - if err := os.WriteFile(forkFile, []byte("f0"), 0o600); err != nil { - t.Fatalf("write fork preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(forkFile, []byte("f0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, forked, forkEntry), forkFile, forkFile, []byte("f1")) child := launchChildThroughRuntime(t, r, root, "op-child") @@ -565,39 +448,32 @@ func TestCodeSnapshotsForkAndChildIndependence(t *testing.T) { childOp := readOperation(t, r, child, "op-child") childEntry := childOp.Admission.AdmittedEntry.EntryID childFile := filepath.Join(workspace, "child.txt") - if err := os.WriteFile(childFile, []byte("c0"), 0o600); err != nil { - t.Fatalf("write child preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(childFile, []byte("c0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, child, childEntry), childFile, childFile, []byte("c1")) awaitRestorableSession(t, r, root) result, err := r.revertSessionCode(context.Background(), root, "op-root") - if err != nil { - t.Fatalf("revert root: %v", err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == rootFile, "root restored = %v, want only the root file", result.Restored) + { + data, err := os.ReadFile(rootFile) + testassert.Require(t, err == nil && string(data) == "r0", "root file = (%q, %v), want the restored preimage", data, err) } - if len(result.Restored) != 1 || result.Restored[0] != rootFile { - t.Fatalf("root restored = %v, want only the root file", result.Restored) + { + data, err := os.ReadFile(forkFile) + testassert.Require(t, err == nil && string(data) == "f1", "fork file = (%q, %v), want it untouched", data, err) } - if data, err := os.ReadFile(rootFile); err != nil || string(data) != "r0" { - t.Fatalf("root file = (%q, %v), want the restored preimage", data, err) - } - if data, err := os.ReadFile(forkFile); err != nil || string(data) != "f1" { - t.Fatalf("fork file = (%q, %v), want it untouched", data, err) - } - if data, err := os.ReadFile(childFile); err != nil || string(data) != "c1" { - t.Fatalf("child file = (%q, %v), want it untouched", data, err) + { + data, err := os.ReadFile(childFile) + testassert.Require(t, err == nil && string(data) == "c1", "child file = (%q, %v), want it untouched", data, err) } for _, tc := range []struct{ session, entry, operation string }{ {forked, forkEntry, "op-fork"}, {child, childEntry, "op-child"}, } { groups, err := r.listSessionCodeSnapshots(context.Background(), tc.session) - if err != nil { - t.Fatalf("list %s: %v", tc.operation, err) - } - if len(groups) != 1 || groups[0].OperationId != tc.operation || groups[0].GroupItemId != projectItemID(tc.session, tc.entry) { - t.Fatalf("%s groups = %+v, want the Session's own group", tc.operation, groups) - } + testassert.Require(t, err == nil, "list %s: %v", tc.operation, err) + testassert.Require(t, len(groups) == 1 && groups[0].OperationId == tc.operation && groups[0].GroupItemId == projectItemID(tc.session, tc.entry), "%s groups = %+v, want the Session's own group", tc.operation, groups) } }) } @@ -631,14 +507,10 @@ func TestCodeRevertGateAxes(t *testing.T) { } err := codeRestoreRefusal(sessionID, snap) if tc.refused { - if !errors.Is(err, harness.ErrConflict) { - t.Fatalf("%s gate = %v, want harness.ErrConflict", tc.name, err) - } + testassert.Require(t, errors.Is(err, harness.ErrConflict), "%s gate = %v, want harness.ErrConflict", tc.name, err) continue } - if err != nil { - t.Fatalf("%s gate = %v, want nil", tc.name, err) - } + testassert.Require(t, err == nil, "%s gate = %v, want nil", tc.name, err) } } @@ -651,9 +523,7 @@ func TestCodeSnapshotsStateGates(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "gates-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID gate := make(chan struct{}) @@ -662,24 +532,22 @@ func TestCodeSnapshotsStateGates(t *testing.T) { awaitModelArrival(t, e) runningEntry := readOperation(t, r, session, "op-1").Admission.AdmittedEntry.EntryID file := filepath.Join(workspace, "gated.txt") - if err := os.WriteFile(file, []byte("v0"), 0o600); err != nil { - t.Fatalf("write preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, session, runningEntry), file, file, []byte("v1")) - if _, err := r.revertSessionCode(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("running restore = %v, want harness.ErrConflict", err) + { + _, err := r.revertSessionCode(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrConflict), "running restore = %v, want harness.ErrConflict", err) } - if data, err := os.ReadFile(file); err != nil || string(data) != "v1" { - t.Fatalf("running refusal mutated the file: (%q, %v)", data, err) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "v1", "running refusal mutated the file: (%q, %v)", data, err) } close(gate) awaitOperation(t, r, session, "op-1", harness.OperationSuccess) awaitRestorableSession(t, r, session) result, err := r.revertSessionCode(context.Background(), session, "op-1") - if err != nil || len(result.Restored) != 1 { - t.Fatalf("idle restore = (%+v, %v), want the restored file", result, err) - } + testassert.Require(t, err == nil && len(result.Restored) == 1, "idle restore = (%+v, %v), want the restored file", result, err) // A live background child refuses the parent's restore even while the // parent's own Operation state is idle. @@ -687,23 +555,24 @@ func TestCodeSnapshotsStateGates(t *testing.T) { parentOp := submitCodeOperation(t, r, parent, "op-parent", "parent") parentEntry := parentOp.Admission.AdmittedEntry.EntryID parentFile := filepath.Join(workspace, "parent.txt") - if err := os.WriteFile(parentFile, []byte("p0"), 0o600); err != nil { - t.Fatalf("write parent preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(parentFile, []byte("p0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, parent, parentEntry), parentFile, parentFile, []byte("p1")) awaitOperation(t, r, parent, "op-parent", harness.OperationSuccess) // the parent's own execution retires before the child launch childGate := make(chan struct{}) e.server.setHold(childGate) child := launchChildThroughRuntime(t, r, parent, "op-live-child") awaitModelArrival(t, e) - if children := sessionViewThroughRuntime(t, r, parent).Background; len(children) == 0 { - t.Fatal("parent background membership is empty after a live child launch") + { + children := sessionViewThroughRuntime(t, r, parent).Background + testassert.Require(t, len(children) != 0, "parent background membership is empty after a live child launch") } - if _, err := r.revertSessionCode(context.Background(), parent, "op-parent"); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("live-child restore = %v, want harness.ErrConflict", err) + { + _, err := r.revertSessionCode(context.Background(), parent, "op-parent") + testassert.Require(t, errors.Is(err, harness.ErrConflict), "live-child restore = %v, want harness.ErrConflict", err) } - if data, err := os.ReadFile(parentFile); err != nil || string(data) != "p1" { - t.Fatalf("live-child refusal mutated the file: (%q, %v)", data, err) + { + data, err := os.ReadFile(parentFile) + testassert.Require(t, err == nil && string(data) == "p1", "live-child refusal mutated the file: (%q, %v)", data, err) } close(childGate) awaitOperation(t, r, child, "op-live-child", harness.OperationSuccess) @@ -723,9 +592,7 @@ func TestCodeRevertPartialTraversalParkedGroup(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "fifo-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID first := submitCodeOperation(t, r, session, "op-first", "first") second := submitCodeOperation(t, r, session, "op-second", "second") @@ -734,17 +601,11 @@ func TestCodeRevertPartialTraversalParkedGroup(t *testing.T) { // The later-visited (earlier-admitted) group fails on a corrupt meta // after the earlier-visited (later-admitted) group restores. badEntryDir := filepath.Join(codeGroupRoot(r, session, first.Admission.AdmittedEntry.EntryID), "snapshots", "1", "aaaaaaaaaaaaaaaa") - if err := os.MkdirAll(badEntryDir, 0o700); err != nil { - t.Fatalf("mkdir bad entry: %v", err) - } - if err := os.WriteFile(filepath.Join(badEntryDir, "meta.json"), []byte("{"), 0o600); err != nil { - t.Fatalf("write bad meta: %v", err) - } + testassert.NoError(t, os.MkdirAll(badEntryDir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(badEntryDir, "meta.json"), []byte("{"), 0o600)) target := filepath.Join(workspace, "parked.txt") - if err := os.WriteFile(target, []byte("current"), 0o600); err != nil { - t.Fatalf("write parked target: %v", err) - } + testassert.NoError(t, os.WriteFile(target, []byte("current"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, session, second.Admission.AdmittedEntry.EntryID), target, "current") park, outcomes := parkRestore(t, r, session, "op-first", fifo) @@ -758,17 +619,10 @@ func TestCodeRevertPartialTraversalParkedGroup(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the restore never returned after its parked group completed") } - if out.err == nil { - t.Fatalf("restore = %+v, want the later bad group's traversal error", out.result) - } - if len(out.result.Restored) != 1 || out.result.Restored[0] != target || out.result.Skipped == nil { - t.Fatalf("partial result = %+v, want the parked group's accumulated restore and a non-nil skip list", out.result) - } - if out.result.Error == nil || out.result.Error.Code != protocol.Internal || out.result.Error.Message == "" || out.result.Error.SessionId != nil { - t.Fatalf("partial error = %+v, want the internal class with no target Session id", out.result.Error) - } - if data, err := os.ReadFile(target); err != nil || string(data) != "restored" { - t.Fatalf("parked target = (%q, %v), want the FIFO restore to complete", data, err) + testassert.Require(t, out.err != nil && len(out.result.Restored) == 1 && out.result.Restored[0] == target && out.result.Skipped != nil && out.result.Error != nil && out.result.Error.Code == protocol.Internal && out.result.Error.Message != "" && out.result.Error.SessionId == nil, "restore = %+v, want the later bad group's traversal error; partial result = %+v, want the parked group's accumulated restore and a non-nil skip list; partial error = %+v, want the internal class with no target Session id", out.result, out.result, out.result.Error) + { + data, err := os.ReadFile(target) + testassert.Require(t, err == nil && string(data) == "restored", "parked target = (%q, %v), want the FIFO restore to complete", data, err) } }) } @@ -780,16 +634,13 @@ func TestCodeRevertPartialTraversalParkedGroup(t *testing.T) { func seedFIFOCodeEntry(t *testing.T, groupRoot, target, content string) string { t.Helper() canonical, err := filepath.EvalSymlinks(target) - if err != nil { - t.Fatalf("EvalSymlinks(%s): %v", target, err) - } + testassert.Require(t, err == nil, "EvalSymlinks(%s): %v", target, err) entryDir := filepath.Join(groupRoot, "snapshots", "1", sha256Hex16(canonical)) - if err := os.MkdirAll(entryDir, 0o700); err != nil { - t.Fatalf("mkdir FIFO entry: %v", err) - } + testassert.NoError(t, os.MkdirAll(entryDir, 0o700)) fifo := filepath.Join(entryDir, "original") - if err := unix.Mkfifo(fifo, 0o600); err != nil { - t.Fatalf("Mkfifo(%s): %v", fifo, err) + { + err := unix.Mkfifo(fifo, 0o600) + testassert.Require(t, err == nil, "Mkfifo(%s): %v", fifo, err) } meta := map[string]any{ "original_path": target, @@ -829,12 +680,8 @@ func parkFIFO(t *testing.T, fifo string) *fifoPark { if err == nil { return &fifoPark{writerFD: fd, fifo: fifo} } - if !errors.Is(err, unix.ENXIO) { - t.Fatalf("FIFO writer open: %v", err) - } - if time.Now().After(deadline) { - t.Fatal("the restore never parked on the FIFO saved original") - } + testassert.Require(t, errors.Is(err, unix.ENXIO), "FIFO writer open: %v", err) + testassert.Require(t, !time.Now().After(deadline), "the restore never parked on the FIFO saved original") time.Sleep(5 * time.Millisecond) } } @@ -881,46 +728,29 @@ func TestCodeRevertCrossGroupSkipLedger(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "ledger-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID first := submitCodeOperation(t, r, session, "op-first", "first") second := submitCodeOperation(t, r, session, "op-second", "second") awaitRestorableSession(t, r, session) file := filepath.Join(workspace, "chain.txt") - if err := os.WriteFile(file, []byte("v0"), 0o600); err != nil { - t.Fatalf("write v0: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v0"), 0o600)) // The older group records the file through a display alias: an // absolute symlink resolving to the same canonical identity. alias := filepath.Join(workspace, "alias.txt") - if err := os.Symlink(file, alias); err != nil { - t.Fatalf("symlink alias: %v", err) - } + testassert.NoError(t, os.Symlink(file, alias)) captureCodeMutation(t, codeGroupRoot(r, session, first.Admission.AdmittedEntry.EntryID), alias, file, []byte("v1")) captureCodeMutation(t, codeGroupRoot(r, session, second.Admission.AdmittedEntry.EntryID), file, file, []byte("v2")) // The external edit matches the older group's last-write identity, so // only a shared ledger keeps the older group from restoring. - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write external v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) result, err := r.revertSessionCode(context.Background(), session, "op-first") - if err != nil { - t.Fatalf("cross-group restore = %v, want the skip-ledger success", err) - } - if len(result.Restored) != 0 { - t.Fatalf("restored = %v, want no restore after the newer group's skip", result.Restored) - } - if len(result.Skipped) != 1 || result.Skipped[0].Path != file { - t.Fatalf("skipped = %+v, want exactly one reported skip for %s", result.Skipped, file) - } + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 0 && len(result.Skipped) == 1 && result.Skipped[0].Path == file, "restored = %v, want no restore after the newer group's skip; skipped = %+v, want exactly one reported skip for %s", result.Restored, result.Skipped, file) data, err := os.ReadFile(file) - if err != nil || string(data) != "v1" { - t.Fatalf("file = (%q, %v), want the external v1 preserved", data, err) - } + testassert.Require(t, err == nil && string(data) == "v1", "file = (%q, %v), want the external v1 preserved", data, err) }) } @@ -932,23 +762,17 @@ func TestCodeRewindConflictWhileParked(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "rewind-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID first := submitCodeOperation(t, r, session, "op-first", "first") second := submitCodeOperation(t, r, session, "op-second", "second") awaitRestorableSession(t, r, session) keep := filepath.Join(workspace, "keep.txt") - if err := os.WriteFile(keep, []byte("v0"), 0o600); err != nil { - t.Fatalf("write keep v0: %v", err) - } + testassert.NoError(t, os.WriteFile(keep, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, session, first.Admission.AdmittedEntry.EntryID), keep, keep, []byte("v1")) rewind := filepath.Join(workspace, "rewind.txt") - if err := os.WriteFile(rewind, []byte("v2"), 0o600); err != nil { - t.Fatalf("write rewind v2: %v", err) - } + testassert.NoError(t, os.WriteFile(rewind, []byte("v2"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, session, second.Admission.AdmittedEntry.EntryID), rewind, "v2") park, outcomes := parkRestore(t, r, session, "op-first", fifo) @@ -966,11 +790,10 @@ func TestCodeRewindConflictWhileParked(t *testing.T) { case <-time.After(5 * time.Second): t.Fatal("the second rewind waited behind the parked restore instead of conflicting") } - if !errors.Is(retry.err, harness.ErrConflict) { - t.Fatalf("second rewind = (%+v, %v), want the conflict class with zero mutation", retry.result, retry.err) - } - if data, err := os.ReadFile(keep); err != nil || string(data) != "v1" { - t.Fatalf("keep = (%q, %v), want it untouched by the refused rewind", data, err) + testassert.Require(t, errors.Is(retry.err, harness.ErrConflict), "second rewind = (%+v, %v), want the conflict class with zero mutation", retry.result, retry.err) + { + data, err := os.ReadFile(keep) + testassert.Require(t, err == nil && string(data) == "v1", "keep = (%q, %v), want it untouched by the refused rewind", data, err) } park.release("restored") @@ -980,14 +803,10 @@ func TestCodeRewindConflictWhileParked(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if parked.err != nil { - t.Fatalf("parked restore = %v", parked.err) - } - if len(parked.result.Restored) != 2 { - t.Fatalf("restored = %v, want both groups' preimages", parked.result.Restored) - } - if data, err := os.ReadFile(keep); err != nil || string(data) != "v0" { - t.Fatalf("keep = (%q, %v), want the restored preimage v0", data, err) + testassert.Require(t, parked.err == nil && len(parked.result.Restored) == 2, "parked restore = %v; restored = %v, want both groups' preimages", parked.err, parked.result.Restored) + { + data, err := os.ReadFile(keep) + testassert.Require(t, err == nil && string(data) == "v0", "keep = (%q, %v), want the restored preimage v0", data, err) } }) } @@ -1001,33 +820,33 @@ func TestDeleteSessionConflictsWithRestoreInterval(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "delete-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "only") awaitRestorableSession(t, r, session) file := filepath.Join(workspace, "deleted.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) groupRoot := codeGroupRoot(r, session, op.Admission.AdmittedEntry.EntryID) fifo := seedFIFOCodeEntry(t, groupRoot, file, "v1") - if _, err := r.archiveSession(context.Background(), session); err != nil { - t.Fatalf("archiveSession: %v", err) + { + _, err := r.archiveSession(context.Background(), session) + testassert.NoError(t, err) } park, outcomes := parkRestore(t, r, session, "op-1", fifo) defer park.flush() - if err := r.deleteSession(context.Background(), session); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("delete during a parked restore = %v, want the conflict class", err) + { + err := r.deleteSession(context.Background(), session) + testassert.Require(t, errors.Is(err, harness.ErrConflict), "delete during a parked restore = %v, want the conflict class", err) } - if header := headerThroughRuntime(t, r, session); header.Lifecycle != harness.LifecycleArchived { - t.Fatalf("refused delete changed the Session lifecycle to %v", header.Lifecycle) + { + header := headerThroughRuntime(t, r, session) + testassert.Equal(t, header.Lifecycle, harness.LifecycleArchived) } - if _, err := os.Stat(groupRoot); err != nil { - t.Fatalf("refused delete touched the artifacts: %v", err) + { + _, err := os.Stat(groupRoot) + testassert.NoError(t, err) } park.release("restored") @@ -1037,15 +856,12 @@ func TestDeleteSessionConflictsWithRestoreInterval(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if parked.err != nil { - t.Fatalf("parked restore = %v", parked.err) - } + testassert.Require(t, parked.err == nil, "parked restore = %v", parked.err) // The interval, not the Session, blocked the deletion. - if err := r.deleteSession(context.Background(), session); err != nil { - t.Fatalf("delete after the restore completed = %v", err) - } - if _, err := os.Stat(groupRoot); !os.IsNotExist(err) { - t.Fatalf("group root after the deletion = %v, want it removed", err) + testassert.NoError(t, r.deleteSession(context.Background(), session)) + { + _, err := os.Stat(groupRoot) + testassert.Require(t, os.IsNotExist(err), "group root after the deletion = %v, want it removed", err) } }) } @@ -1060,23 +876,17 @@ func TestCodeRestoreIntervalKeepsMetadataTransitionsAvailable(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "meta-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "only") awaitRestorableSession(t, r, session) keep := filepath.Join(workspace, "keep.txt") - if err := os.WriteFile(keep, []byte("v0"), 0o600); err != nil { - t.Fatalf("write keep v0: %v", err) - } + testassert.NoError(t, os.WriteFile(keep, []byte("v0"), 0o600)) group := codeGroupRoot(r, session, op.Admission.AdmittedEntry.EntryID) captureCodeMutation(t, group, keep, keep, []byte("v1")) parked := filepath.Join(workspace, "parked.txt") - if err := os.WriteFile(parked, []byte("v1"), 0o600); err != nil { - t.Fatalf("write parked v1: %v", err) - } + testassert.NoError(t, os.WriteFile(parked, []byte("v1"), 0o600)) fifo := seedFIFOCodeEntry(t, group, parked, "v1") park, outcomes := parkRestore(t, r, session, "op-1", fifo) @@ -1084,26 +894,14 @@ func TestCodeRestoreIntervalKeepsMetadataTransitionsAvailable(t *testing.T) { defer park.flush() changed, err := r.setSessionAgentType(context.Background(), session, protocol.SetSessionAgentTypeRequest{AgentType: "worker"}) - if err != nil { - t.Fatalf("agent-type change during a parked restore: %v", err) - } - if changed.AgentType != "worker" { - t.Fatalf("changed header agent type = %q, want worker", changed.AgentType) - } + testassert.NoError(t, err) + testassert.Equal(t, changed.AgentType, "worker") archived, err := r.archiveSession(context.Background(), session) - if err != nil { - t.Fatalf("archive during a parked restore: %v", err) - } - if archived.Lifecycle != protocol.Archived { - t.Fatalf("archived header lifecycle = %v, want archived", archived.Lifecycle) - } + testassert.NoError(t, err) + testassert.Equal(t, archived.Lifecycle, protocol.Archived) reopened, err := r.reopenSession(context.Background(), session) - if err != nil { - t.Fatalf("reopen during a parked restore: %v", err) - } - if reopened.Lifecycle != protocol.Open || reopened.AgentType != "worker" { - t.Fatalf("reopened header = %+v, want open with the changed selection", reopened) - } + testassert.NoError(t, err) + testassert.Require(t, reopened.Lifecycle == protocol.Open && reopened.AgentType == "worker", "reopened header = %+v, want open with the changed selection", reopened) park.release("restored") var out restoreOutcome @@ -1112,17 +910,14 @@ func TestCodeRestoreIntervalKeepsMetadataTransitionsAvailable(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if out.err != nil { - t.Fatalf("parked restore = %v", out.err) - } - if len(out.result.Restored) != 2 { - t.Fatalf("restored = %v, want both group entries", out.result.Restored) + testassert.Require(t, out.err == nil && len(out.result.Restored) == 2, "parked restore = %v; restored = %v, want both group entries", out.err, out.result.Restored) + { + data, err := os.ReadFile(keep) + testassert.Require(t, err == nil && string(data) == "v0", "keep = (%q, %v), want the restored preimage v0", data, err) } - if data, err := os.ReadFile(keep); err != nil || string(data) != "v0" { - t.Fatalf("keep = (%q, %v), want the restored preimage v0", data, err) - } - if data, err := os.ReadFile(parked); err != nil || string(data) != "restored" { - t.Fatalf("parked = (%q, %v), want the FIFO restore to complete", data, err) + { + data, err := os.ReadFile(parked) + testassert.Require(t, err == nil && string(data) == "restored", "parked = (%q, %v), want the FIFO restore to complete", data, err) } }) } @@ -1159,9 +954,7 @@ func seedRetainedSession(t *testing.T, home, workspace, legacyID string) string func writeRetainedJSON(t *testing.T, path string, value any) { t.Helper() data, err := json.Marshal(value) - if err != nil { - t.Fatalf("marshal %s: %v", path, err) - } + testassert.Require(t, err == nil, "marshal %s: %v", path, err) writeServiceFile(t, path, string(data)) } @@ -1169,13 +962,9 @@ func rewriteRetainedMeta(t *testing.T, sessionDir string, edit func(map[string]a t.Helper() path := filepath.Join(sessionDir, "meta.json") data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read meta: %v", err) - } + testassert.NoError(t, err) var meta map[string]any - if err := json.Unmarshal(data, &meta); err != nil { - t.Fatalf("decode meta: %v", err) - } + testassert.NoError(t, json.Unmarshal(data, &meta)) edit(meta) writeRetainedJSON(t, path, meta) } @@ -1185,18 +974,16 @@ func rewriteRetainedMeta(t *testing.T, sessionDir string, edit func(map[string]a func recordRetainedMutation(t *testing.T, sessionDir string, turn int, display, target string, content []byte) { t.Helper() store, err := snapshot.OpenCodeStore(sessionDir) - if err != nil { - t.Fatalf("OpenCodeStore(%s): %v", sessionDir, err) - } + testassert.Require(t, err == nil, "OpenCodeStore(%s): %v", sessionDir, err) entryID, _, err := store.SnapshotResolvedEntry(turn, display, target) - if err != nil { - t.Fatalf("SnapshotResolvedEntry turn %d: %v", turn, err) - } - if err := os.WriteFile(target, content, 0o600); err != nil { - t.Fatalf("WriteFile(%s): %v", target, err) + testassert.Require(t, err == nil, "SnapshotResolvedEntry turn %d: %v", turn, err) + { + err := os.WriteFile(target, content, 0o600) + testassert.Require(t, err == nil, "WriteFile(%s): %v", target, err) } - if err := store.RecordSnapshotContent(turn, entryID, content); err != nil { - t.Fatalf("RecordSnapshotContent turn %d: %v", turn, err) + { + err := store.RecordSnapshotContent(turn, entryID, content) + testassert.Require(t, err == nil, "RecordSnapshotContent turn %d: %v", turn, err) } store.RetainSnapshotEntry(turn, entryID) } @@ -1210,9 +997,7 @@ func assertCanariesUntouched(t *testing.T, sessionDir string) { filepath.Join(sessionDir, "claims", "claim.json"): retainedClaimCanary, } { data, err := os.ReadFile(path) - if err != nil || string(data) != want { - t.Fatalf("canary %s = (%q, %v), want untouched %q", path, data, err, want) - } + testassert.Require(t, err == nil && string(data) == want, "canary %s = (%q, %v), want untouched %q", path, data, err, want) } } @@ -1226,52 +1011,51 @@ func TestRetainedCodeSnapshotsProof(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "legacy-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) legacyID := "deadbeef" home := r.config.loader.Home() sessionDir := seedRetainedSession(t, home, workspace, legacyID) turns, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID) - if err != nil || turns.Turns == nil || len(turns.Turns) != 0 { - t.Fatalf("empty retained list = (%+v, %v), want a non-nil empty list", turns, err) - } + testassert.Require(t, err == nil && turns.Turns != nil && len(turns.Turns) == 0, "empty retained list = (%+v, %v), want a non-nil empty list", turns, err) result, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID, AfterTurn: 3}) - if err != nil || result.Restored == nil || result.Skipped == nil || len(result.Restored) != 0 || len(result.Skipped) != 0 { - t.Fatalf("empty retained revert = (%+v, %v), want non-nil empty success", result, err) - } + testassert.Require(t, err == nil && result.Restored != nil && result.Skipped != nil && len(result.Restored) == 0 && len(result.Skipped) == 0, "empty retained revert = (%+v, %v), want non-nil empty success", result, err) // The workspace proof is lexical: a non-normalized spelling of the // same root normalizes to the same storage key and meta proof, while // a symlinked spelling is a different key and is never resolved. - if turns, err := r.listRetainedCodeSnapshots(context.Background(), workspace+string(filepath.Separator)+".", legacyID); err != nil || len(turns.Turns) != 0 { - t.Fatalf("non-normalized workspace spelling = (%+v, %v), want the proven empty success", turns, err) + { + turns, err := r.listRetainedCodeSnapshots(context.Background(), workspace+string(filepath.Separator)+".", legacyID) + testassert.Require(t, err == nil && len(turns.Turns) == 0, "non-normalized workspace spelling = (%+v, %v), want the proven empty success", turns, err) } symlink := filepath.Join(e.home, "legacy-ws-link") - if err := os.Symlink(workspace, symlink); err != nil { - t.Fatalf("symlink workspace: %v", err) - } - if _, err := r.listRetainedCodeSnapshots(context.Background(), symlink, legacyID); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("symlinked workspace spelling = %v, want harness.ErrNotFound (lexical proof only)", err) + testassert.NoError(t, os.Symlink(workspace, symlink)) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), symlink, legacyID) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "symlinked workspace spelling = %v, want harness.ErrNotFound (lexical proof only)", err) } - if _, err := r.listRetainedCodeSnapshots(context.Background(), "", legacyID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty workspace = %v, want harness.ErrInvalid", err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), "", legacyID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty workspace = %v, want harness.ErrInvalid", err) } for _, id := range []string{"deadbee", "deadbeef0", "DEADBEEF", "deadbeez"} { - if _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, id); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("legacy id %q = %v, want harness.ErrInvalid", id, err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, id) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "legacy id %q = %v, want harness.ErrInvalid", id, err) } } - if _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, "0123456789abcdef0123456789abcdef"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("32-hex target id into retained route = %v, want harness.ErrInvalid", err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, "0123456789abcdef0123456789abcdef") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "32-hex target id into retained route = %v, want harness.ErrInvalid", err) } - if _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, "abcdef01"); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("missing legacy session = %v, want harness.ErrNotFound", err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, "abcdef01") + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "missing legacy session = %v, want harness.ErrNotFound", err) } - if _, err := r.listSessionCodeSnapshots(context.Background(), legacyID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("8-hex id into target route = %v, want harness.ErrInvalid", err) + { + _, err := r.listSessionCodeSnapshots(context.Background(), legacyID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "8-hex id into target route = %v, want harness.ErrInvalid", err) } for _, tc := range []struct { @@ -1284,20 +1068,21 @@ func TestRetainedCodeSnapshotsProof(t *testing.T) { } { metaPath := filepath.Join(sessionDir, "meta.json") original, err := os.ReadFile(metaPath) - if err != nil { - t.Fatalf("read original meta: %v", err) - } + testassert.NoError(t, err) rewriteRetainedMeta(t, sessionDir, tc.edit) - if _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s = %v, want harness.ErrInvalid", tc.name, err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s = %v, want harness.ErrInvalid", tc.name, err) } - if _, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s revert = %v, want harness.ErrInvalid", tc.name, err) + { + _, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s revert = %v, want harness.ErrInvalid", tc.name, err) } writeServiceFile(t, metaPath, string(original)) } - if _, err := os.Stat(sessionDir); err != nil { - t.Fatalf("retained session dir vanished: %v", err) + { + _, err := os.Stat(sessionDir) + testassert.NoError(t, err) } assertCanariesUntouched(t, sessionDir) @@ -1307,9 +1092,7 @@ func TestRetainedCodeSnapshotsProof(t *testing.T) { // malformed sibling stays refused. metaPath := filepath.Join(sessionDir, "meta.json") original, err := os.ReadFile(metaPath) - if err != nil { - t.Fatalf("read original meta: %v", err) - } + testassert.NoError(t, err) normalized := filepath.Clean(workspace) sum := sha256.Sum256([]byte(normalized)) hash := hex.EncodeToString(sum[:]) @@ -1321,30 +1104,33 @@ func TestRetainedCodeSnapshotsProof(t *testing.T) { "state": "active", "legacy_ignored_field": strings.Repeat("x", (1<<20)+1), }) - if err != nil { - t.Fatalf("marshal large meta: %v", err) - } + testassert.NoError(t, err) writeServiceFile(t, metaPath, string(large)) - if turns, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID); err != nil || len(turns.Turns) != 0 { - t.Fatalf("large ignored meta field list = (%+v, %v), want the proven empty success", turns, err) + { + turns, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID) + testassert.Require(t, err == nil && len(turns.Turns) == 0, "large ignored meta field list = (%+v, %v), want the proven empty success", turns, err) } - if result, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}); err != nil || len(result.Restored) != 0 { - t.Fatalf("large ignored meta field revert = (%+v, %v), want the proven empty success", result, err) + { + result, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}) + testassert.Require(t, err == nil && len(result.Restored) == 0, "large ignored meta field revert = (%+v, %v), want the proven empty success", result, err) } writeServiceFile(t, metaPath, "{ not a json document") - if _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("malformed meta list = %v, want harness.ErrInvalid", err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "malformed meta list = %v, want harness.ErrInvalid", err) } - if _, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("malformed meta revert = %v, want harness.ErrInvalid", err) + { + _, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "malformed meta revert = %v, want harness.ErrInvalid", err) } writeServiceFile(t, metaPath, string(original)) // The full-hash directory is exact: a same-ID session under a wrong // project directory is not found. wrongRoot := filepath.Join(e.home, "other-ws") - if _, err := r.listRetainedCodeSnapshots(context.Background(), wrongRoot, legacyID); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("wrong workspace root = %v, want harness.ErrNotFound", err) + { + _, err := r.listRetainedCodeSnapshots(context.Background(), wrongRoot, legacyID) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "wrong workspace root = %v, want harness.ErrNotFound", err) } }) } @@ -1358,78 +1144,56 @@ func TestRetainedCodeSnapshotsListAndRevert(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "legacy-list-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) legacyID := "cafebabe" home := r.config.loader.Home() sessionDir := seedRetainedSession(t, home, workspace, legacyID) file := filepath.Join(workspace, "legacy.txt") - if err := os.WriteFile(file, []byte("a0"), 0o600); err != nil { - t.Fatalf("write turn1 preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("a0"), 0o600)) recordRetainedMutation(t, sessionDir, 1, file, file, []byte("a1")) created := filepath.Join(workspace, "legacy-created.txt") recordRetainedMutation(t, sessionDir, 2, file, file, []byte("a2")) recordRetainedMutation(t, sessionDir, 2, created, created, []byte("c")) turns, err := r.listRetainedCodeSnapshots(context.Background(), workspace, legacyID) - if err != nil { - t.Fatalf("listRetainedCodeSnapshots: %v", err) - } - if len(turns.Turns) != 2 || turns.Turns[0].Turn != 1 || turns.Turns[1].Turn != 2 { - t.Fatalf("turns = %+v, want the retained order 1, 2", turns.Turns) - } - if len(turns.Turns[0].Files) != 1 || turns.Turns[0].Files[0] != (protocol.SnapshotFile{Path: file, Existed: true}) { - t.Fatalf("turn 1 files = %+v, want the edited display path", turns.Turns[0].Files) - } - if len(turns.Turns[1].Files) != 2 { - t.Fatalf("turn 2 files = %+v, want the edited and created entries", turns.Turns[1].Files) - } + testassert.NoError(t, err) + testassert.Require(t, len(turns.Turns) == 2 && turns.Turns[0].Turn == 1 && turns.Turns[1].Turn == 2, "turns = %+v, want the retained order 1, 2", turns.Turns) + testassert.Require(t, len(turns.Turns[0].Files) == 1 && turns.Turns[0].Files[0] == (protocol.SnapshotFile{Path: file, Existed: true}), "turn 1 files = %+v, want the edited display path", turns.Turns[0].Files) + testassert.Require(t, len(turns.Turns[1].Files) == 2, "turn 2 files = %+v, want the edited and created entries", turns.Turns[1].Files) assertCanariesUntouched(t, sessionDir) result, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID, AfterTurn: 1}) - if err != nil { - t.Fatalf("revert after_turn 1: %v", err) - } - if len(result.Restored) != 2 || result.Skipped == nil || len(result.Skipped) != 0 { - t.Fatalf("turn 2 result = %+v, want both files restored without skips", result) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 2 && result.Skipped != nil && len(result.Skipped) == 0, "turn 2 result = %+v, want both files restored without skips", result) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "a1", "legacy file after turn 2 revert = (%q, %v), want the turn 1 post-write", data, err) } - if data, err := os.ReadFile(file); err != nil || string(data) != "a1" { - t.Fatalf("legacy file after turn 2 revert = (%q, %v), want the turn 1 post-write", data, err) - } - if _, err := os.Stat(created); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("created file stat = %v, want restored absence", err) + { + _, err := os.Stat(created) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "created file stat = %v, want restored absence", err) } result, err = r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}) - if err != nil { - t.Fatalf("revert after_turn 0: %v", err) - } - if len(result.Restored) != 1 || result.Restored[0] != file { - t.Fatalf("turn 1 result = %+v, want the edited file restored", result) - } - if data, err := os.ReadFile(file); err != nil || string(data) != "a0" { - t.Fatalf("legacy file after turn 1 revert = (%q, %v), want the turn 1 preimage", data, err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == file, "turn 1 result = %+v, want the edited file restored", result) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "a0", "legacy file after turn 1 revert = (%q, %v), want the turn 1 preimage", data, err) } assertCanariesUntouched(t, sessionDir) // A re-recorded turn 1 with an external edit skips instead of // overwriting, still reporting the retained display path. recordRetainedMutation(t, sessionDir, 1, file, file, []byte("a1")) - if err := os.WriteFile(file, []byte("external"), 0o600); err != nil { - t.Fatalf("external edit: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("external"), 0o600)) result, err = r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}) - if err != nil { - t.Fatalf("skip revert: %v", err) - } - if len(result.Restored) != 0 || len(result.Skipped) != 1 || result.Skipped[0].Path != file || !strings.Contains(result.Skipped[0].Reason, "content changed") { - t.Fatalf("skip result = %+v, want the changed display path skipped", result) - } - if data, err := os.ReadFile(file); err != nil || string(data) != "external" { - t.Fatalf("legacy file after skip = (%q, %v), want the external content", data, err) + testassert.NoError(t, err) + testassert.Require(t, len(result.Restored) == 0 && len(result.Skipped) == 1 && result.Skipped[0].Path == file && strings.Contains(result.Skipped[0].Reason, "content changed"), "skip result = %+v, want the changed display path skipped", result) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "external", "legacy file after skip = (%q, %v), want the external content", data, err) } }) } @@ -1442,17 +1206,13 @@ func TestRetainedCodeSnapshotsPartialTraversal(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "legacy-partial-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) legacyID := "feedface" home := r.config.loader.Home() sessionDir := seedRetainedSession(t, home, workspace, legacyID) file := filepath.Join(workspace, "partial.txt") - if err := os.WriteFile(file, []byte("p0"), 0o600); err != nil { - t.Fatalf("write preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("p0"), 0o600)) recordRetainedMutation(t, sessionDir, 1, file, file, []byte("p1")) recordRetainedMutation(t, sessionDir, 2, file, file, []byte("p2")) @@ -1460,25 +1220,15 @@ func TestRetainedCodeSnapshotsPartialTraversal(t *testing.T) { // turn 1 errors. turnDir := filepath.Join(sessionDir, "snapshots", "1") entries, err := os.ReadDir(turnDir) - if err != nil || len(entries) != 1 { - t.Fatalf("turn 1 dirs = (%d, %v), want one entry", len(entries), err) - } - if err := os.WriteFile(filepath.Join(turnDir, entries[0].Name(), "meta.json"), []byte("{"), 0o600); err != nil { - t.Fatalf("corrupt meta: %v", err) - } + testassert.Require(t, err == nil && len(entries) == 1, "turn 1 dirs = (%d, %v), want one entry", len(entries), err) + testassert.NoError(t, os.WriteFile(filepath.Join(turnDir, entries[0].Name(), "meta.json"), []byte("{"), 0o600)) result, err := r.revertRetainedCode(context.Background(), protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID}) - if err == nil { - t.Fatalf("partial revert = %+v, want the traversal error", result) - } - if len(result.Restored) != 1 || result.Restored[0] != file || result.Skipped == nil { - t.Fatalf("partial result = %+v, want the later turn's accumulated restore", result) - } - if result.Error == nil || result.Error.Code != protocol.Internal || result.Error.Message == "" || result.Error.SessionId != nil { - t.Fatalf("partial error = %+v, want the internal class with no target Session id", result.Error) - } - if data, err := os.ReadFile(file); err != nil || string(data) != "p1" { - t.Fatalf("partial file = (%q, %v), want the completed later-turn restore", data, err) + testassert.Require(t, err != nil, "partial revert = %+v, want the traversal error", result) + testassert.Require(t, len(result.Restored) == 1 && result.Restored[0] == file && result.Skipped != nil && result.Error != nil && result.Error.Code == protocol.Internal && result.Error.Message != "" && result.Error.SessionId == nil, "partial result = %+v, want the later turn's accumulated restore; partial error = %+v, want the internal class with no target Session id", result, result.Error) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "p1", "partial file = (%q, %v), want the completed later-turn restore", data, err) } assertCanariesUntouched(t, sessionDir) }) @@ -1498,15 +1248,11 @@ func TestRetainedMountedRestoreConflict(t *testing.T) { ctx := context.Background() workspace := filepath.Join(e.home, "retained-mounted-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) legacyID := "c0ffee12" sessionDir := seedRetainedSession(t, r.config.loader.Home(), workspace, legacyID) file := filepath.Join(workspace, "retained-mounted.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) // The turn-1 entry's saved original is a FIFO whose last-write hash // matches the file's current content, so the retained restore // reaches restoreFile's plain os.Open and parks on the FIFO. @@ -1543,20 +1289,14 @@ func TestRetainedMountedRestoreConflict(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the second mounted retained restore never answered, want the typed conflict") } - if second.err != nil { - t.Fatalf("second mounted retained restore: %v, want the typed conflict 409", second.err) - } - if second.response == nil { - t.Fatal("second mounted retained restore = , want the typed conflict 409") - } + testassert.Require(t, second.err == nil, "second mounted retained restore: %v, want the typed conflict 409", second.err) + testassert.Require(t, second.response != nil, "second mounted retained restore = , want the typed conflict 409") if second.response.JSONDefault == nil || second.response.HTTPResponse.StatusCode != http.StatusConflict { body, _ := json.Marshal(second.response) t.Fatalf("second mounted retained restore = (%d, %s), want the typed conflict 409", second.response.HTTPResponse.StatusCode, body) } conflictError, err := second.response.JSONDefault.AsError() - if err != nil || conflictError.Code != protocol.Conflict { - t.Fatalf("second retained restore error = %+v (%v), want the typed conflict", second.response.JSONDefault, err) - } + testassert.Require(t, err == nil && conflictError.Code == protocol.Conflict, "second retained restore error = %+v (%v), want the typed conflict", second.response.JSONDefault, err) // The parked mounted retained restore completes with its own result. park.release("mounted-restored") @@ -1566,17 +1306,11 @@ func TestRetainedMountedRestoreConflict(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked mounted retained restore never returned after its FIFO completed") } - if out.err != nil { - t.Fatalf("mounted retained restore: %v, want the restored result", out.err) - } - if out.response == nil || out.response.JSON200 == nil { - t.Fatalf("mounted retained restore = %+v, want the restored result", out.response) - } - if len(out.response.JSON200.Restored) != 1 || out.response.JSON200.Restored[0] != file || len(out.response.JSON200.Skipped) != 0 { - t.Fatalf("restored = %+v, want exactly the retained display path", out.response.JSON200) - } - if data, err := os.ReadFile(file); err != nil || string(data) != "mounted-restored" { - t.Fatalf("file = (%q, %v), want the FIFO content", data, err) + testassert.Require(t, out.err == nil && out.response != nil && out.response.JSON200 != nil, "mounted retained restore: %v, want the restored result; mounted retained restore = %+v, want the restored result", out.err, out.response) + testassert.Require(t, len(out.response.JSON200.Restored) == 1 && out.response.JSON200.Restored[0] == file && len(out.response.JSON200.Skipped) == 0, "restored = %+v, want exactly the retained display path", out.response.JSON200) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "mounted-restored", "file = (%q, %v), want the FIFO content", data, err) } assertCanariesUntouched(t, sessionDir) }) @@ -1593,59 +1327,43 @@ func TestWorkspaceFileReadContainment(t *testing.T) { defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "viewer-ws") target := filepath.Join(workspace, "docs", "note.txt") - if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil { - t.Fatalf("mkdir docs: %v", err) - } - if err := os.WriteFile(target, []byte("hello"), 0o600); err != nil { - t.Fatalf("write target: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Dir(target), 0o700)) + testassert.NoError(t, os.WriteFile(target, []byte("hello"), 0o600)) canonical, err := filepath.EvalSymlinks(target) - if err != nil { - t.Fatalf("EvalSymlinks: %v", err) - } + testassert.NoError(t, err) relative, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: filepath.Join("docs", "note.txt")}) - if err != nil || relative.Content != "hello" || relative.CanonicalPath != canonical || relative.Truncated { - t.Fatalf("relative read = (%+v, %v), want the contained canonical view", relative, err) - } + testassert.Require(t, err == nil && relative.Content == "hello" && relative.CanonicalPath == canonical && !relative.Truncated, "relative read = (%+v, %v), want the contained canonical view", relative, err) absolute, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: target}) - if err != nil || absolute.CanonicalPath != canonical { - t.Fatalf("absolute read = (%+v, %v), want the same canonical target", absolute, err) - } + testassert.Require(t, err == nil && absolute.CanonicalPath == canonical, "absolute read = (%+v, %v), want the same canonical target", absolute, err) cleaned, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: filepath.Join("docs", "..", "docs", "note.txt")}) - if err != nil || cleaned.Content != "hello" { - t.Fatalf("cleaned read = (%+v, %v), want the contained target", cleaned, err) - } + testassert.Require(t, err == nil && cleaned.Content == "hello", "cleaned read = (%+v, %v), want the contained target", cleaned, err) outside := filepath.Join(e.home, "outside.txt") - if err := os.WriteFile(outside, []byte("outside"), 0o600); err != nil { - t.Fatalf("write outside: %v", err) - } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: filepath.Join("..", "outside.txt")}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("dotdot read = %v, want harness.ErrInvalid", err) + testassert.NoError(t, os.WriteFile(outside, []byte("outside"), 0o600)) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: filepath.Join("..", "outside.txt")}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "dotdot read = %v, want harness.ErrInvalid", err) } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: outside}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("absolute outside read = %v, want harness.ErrInvalid", err) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: outside}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "absolute outside read = %v, want harness.ErrInvalid", err) } - if err := os.Symlink(outside, filepath.Join(workspace, "escape")); err != nil { - t.Fatalf("symlink escape: %v", err) - } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "escape"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("outside symlink read = %v, want harness.ErrInvalid", err) - } - if err := os.Symlink(target, filepath.Join(workspace, "inside-link")); err != nil { - t.Fatalf("symlink inside: %v", err) + testassert.NoError(t, os.Symlink(outside, filepath.Join(workspace, "escape"))) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "escape"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "outside symlink read = %v, want harness.ErrInvalid", err) } + testassert.NoError(t, os.Symlink(target, filepath.Join(workspace, "inside-link"))) linked, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "inside-link"}) - if err != nil || linked.Content != "hello" || linked.CanonicalPath != canonical { - t.Fatalf("inside symlink read = (%+v, %v), want the resolved contained target", linked, err) - } + testassert.Require(t, err == nil && linked.Content == "hello" && linked.CanonicalPath == canonical, "inside symlink read = (%+v, %v), want the resolved contained target", linked, err) for _, req := range []protocol.ReadFileRequest{ {Path: "note.txt"}, {Workspace: workspace}, } { - if _, err := r.readWorkspaceFile(context.Background(), req); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty member %+v = %v, want harness.ErrInvalid", req, err) + { + _, err := r.readWorkspaceFile(context.Background(), req) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty member %+v = %v, want harness.ErrInvalid", req, err) } } }) @@ -1658,42 +1376,40 @@ func TestWorkspaceFileReadRefusals(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "viewer-refusals") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) for _, sensitive := range []string{".env", "id_rsa", "server.pem"} { - if err := os.WriteFile(filepath.Join(workspace, sensitive), []byte("secret"), 0o600); err != nil { - t.Fatalf("write %s: %v", sensitive, err) + { + err := os.WriteFile(filepath.Join(workspace, sensitive), []byte("secret"), 0o600) + testassert.Require(t, err == nil, "write %s: %v", sensitive, err) } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: sensitive}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("sensitive %q = %v, want harness.ErrInvalid", sensitive, err) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: sensitive}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "sensitive %q = %v, want harness.ErrInvalid", sensitive, err) } } original := filepath.Join(workspace, "original.txt") - if err := os.WriteFile(original, []byte("linked"), 0o600); err != nil { - t.Fatalf("write original: %v", err) - } + testassert.NoError(t, os.WriteFile(original, []byte("linked"), 0o600)) hardlink := filepath.Join(workspace, "hardlink.txt") - if err := os.Link(original, hardlink); err != nil { - t.Fatalf("hardlink: %v", err) - } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "hardlink.txt"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("hardlink read = %v, want harness.ErrInvalid", err) + testassert.NoError(t, os.Link(original, hardlink)) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "hardlink.txt"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "hardlink read = %v, want harness.ErrInvalid", err) } fifo := filepath.Join(workspace, "pipe") - if err := unix.Mkfifo(fifo, 0o600); err != nil { - t.Fatalf("mkfifo: %v", err) - } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "pipe"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("fifo read = %v, want harness.ErrInvalid", err) + testassert.NoError(t, unix.Mkfifo(fifo, 0o600)) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "pipe"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "fifo read = %v, want harness.ErrInvalid", err) } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "."}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("directory read = %v, want harness.ErrInvalid", err) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "."}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "directory read = %v, want harness.ErrInvalid", err) } - if _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "missing.txt"}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("missing read = %v, want harness.ErrNotFound", err) + { + _, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "missing.txt"}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "missing read = %v, want harness.ErrNotFound", err) } }) } @@ -1711,9 +1427,7 @@ func TestViewerSensitivePath(t *testing.T) { "/home/user/credentials.yaml", } for _, p := range sensitive { - if !isSensitivePath(p) { - t.Fatalf("isSensitivePath(%q) = false, want true", p) - } + testassert.Require(t, isSensitivePath(p), "isSensitivePath(%q) = false, want true", p) } normal := []string{ @@ -1722,9 +1436,7 @@ func TestViewerSensitivePath(t *testing.T) { "/home/user/.gitignore", } for _, p := range normal { - if isSensitivePath(p) { - t.Fatalf("isSensitivePath(%q) = true, want false", p) - } + testassert.Require(t, !isSensitivePath(p), "isSensitivePath(%q) = true, want false", p) } } @@ -1737,46 +1449,37 @@ func TestWorkspaceFileReadPrefixRule(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "viewer-prefix") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) write := func(name string, data []byte) string { t.Helper() path := filepath.Join(workspace, name) - if err := os.WriteFile(path, data, 0o600); err != nil { - t.Fatalf("write %s: %v", name, err) + { + err := os.WriteFile(path, data, 0o600) + testassert.Require(t, err == nil, "write %s: %v", name, err) } return path } write("binary.bin", []byte{'A', 0xff, 0xfe, 'B', 0x80, 0x80, 'C'}) binary, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "binary.bin"}) - if err != nil || binary.Content != "A\uFFFDB\uFFFDC" || binary.Truncated { - t.Fatalf("binary view = (%+v, %v), want the maximal invalid-run replacements", binary, err) - } + testassert.Require(t, err == nil && binary.Content == "A\uFFFDB\uFFFDC" && !binary.Truncated, "binary view = (%+v, %v), want the maximal invalid-run replacements", binary, err) write("exact.txt", bytes.Repeat([]byte("a"), maxViewPrefixBytes)) exact, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "exact.txt"}) - if err != nil || exact.Truncated || len(exact.Content) != maxViewPrefixBytes { - t.Fatalf("exact view = (%d bytes, truncated %v, err %v), want the untruncated 1 MiB", len(exact.Content), exact.Truncated, err) - } + testassert.Require(t, err == nil && !exact.Truncated && len(exact.Content) == maxViewPrefixBytes, "exact view = (%d bytes, truncated %v, err %v), want the untruncated 1 MiB", len(exact.Content), exact.Truncated, err) large := append(bytes.Repeat([]byte("a"), maxViewPrefixBytes), []byte("tail")...) write("large.txt", large) view, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "large.txt"}) - if err != nil || !view.Truncated || len(view.Content) != maxViewPrefixBytes || strings.ContainsRune(view.Content, '\uFFFD') { - t.Fatalf("large view = (%d bytes, truncated %v, err %v), want exactly the first raw MiB", len(view.Content), view.Truncated, err) - } + testassert.Require(t, err == nil && view.Truncated && len(view.Content) == maxViewPrefixBytes && !strings.ContainsRune(view.Content, '\uFFFD'), "large view = (%d bytes, truncated %v, err %v), want exactly the first raw MiB", len(view.Content), view.Truncated, err) // Truncation precedes conversion: the cap cuts the three-byte rune // after its first byte, so the dangling run becomes one replacement. midRune := append(bytes.Repeat([]byte("a"), maxViewPrefixBytes-1), []byte("\u20AC extra")...) write("mid-rune.txt", midRune) cut, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "mid-rune.txt"}) - if err != nil || !cut.Truncated || cut.Content != strings.Repeat("a", maxViewPrefixBytes-1)+"\uFFFD" { - t.Fatalf("mid-rune view = (%q..., truncated %v, err %v), want the raw cap converted after truncation", cut.Content, cut.Truncated, err) - } + testassert.Require(t, err == nil && cut.Truncated && cut.Content == strings.Repeat("a", maxViewPrefixBytes-1)+"\uFFFD", "mid-rune view = (%q..., truncated %v, err %v), want the raw cap converted after truncation", cut.Content, cut.Truncated, err) // No second converted-length cap: alternating invalid runs convert to // four bytes per two raw bytes, so the returned content is 2 MiB for @@ -1784,9 +1487,7 @@ func TestWorkspaceFileReadPrefixRule(t *testing.T) { pattern := bytes.Repeat([]byte{0xff, 'A'}, maxViewPrefixBytes/2) write("invalid.bin", append(pattern, 0x00)) expanded, err := r.readWorkspaceFile(context.Background(), protocol.ReadFileRequest{Workspace: workspace, Path: "invalid.bin"}) - if err != nil || !expanded.Truncated || expanded.Content != strings.Repeat("\uFFFDA", maxViewPrefixBytes/2) { - t.Fatalf("expanded view = (%d bytes, truncated %v, err %v), want 2 MiB of converted raw prefix without a wire cap", len(expanded.Content), expanded.Truncated, err) - } + testassert.Require(t, err == nil && expanded.Truncated && expanded.Content == strings.Repeat("\uFFFDA", maxViewPrefixBytes/2), "expanded view = (%d bytes, truncated %v, err %v), want 2 MiB of converted raw prefix without a wire cap", len(expanded.Content), expanded.Truncated, err) }) } @@ -1798,26 +1499,29 @@ func TestWorkspaceFileReadPrefixRule(t *testing.T) { func TestCodeProducersClosedOwner(t *testing.T) { eachPrepStore(t, func(t *testing.T, store harness.Storage) { r, e := openProjectionRuntime(t, store) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) ctx := context.Background() sessionID := "0123456789abcdef0123456789abcdef" workspace := filepath.Join(e.home, "closed-ws") - if _, err := r.listSessionCodeSnapshots(ctx, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed list = %v, want ErrClosed", err) + { + _, err := r.listSessionCodeSnapshots(ctx, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed list = %v, want ErrClosed", err) } - if _, err := r.revertSessionCode(ctx, sessionID, "op"); !errors.Is(err, ErrClosed) { - t.Fatalf("closed revert = %v, want ErrClosed", err) + { + _, err := r.revertSessionCode(ctx, sessionID, "op") + testassert.Require(t, errors.Is(err, ErrClosed), "closed revert = %v, want ErrClosed", err) } - if _, err := r.listRetainedCodeSnapshots(ctx, workspace, "deadbeef"); !errors.Is(err, ErrClosed) { - t.Fatalf("closed retained list = %v, want ErrClosed", err) + { + _, err := r.listRetainedCodeSnapshots(ctx, workspace, "deadbeef") + testassert.Require(t, errors.Is(err, ErrClosed), "closed retained list = %v, want ErrClosed", err) } - if _, err := r.revertRetainedCode(ctx, protocol.RetainedRevertRequest{Workspace: workspace, SessionId: "deadbeef"}); !errors.Is(err, ErrClosed) { - t.Fatalf("closed retained revert = %v, want ErrClosed", err) + { + _, err := r.revertRetainedCode(ctx, protocol.RetainedRevertRequest{Workspace: workspace, SessionId: "deadbeef"}) + testassert.Require(t, errors.Is(err, ErrClosed), "closed retained revert = %v, want ErrClosed", err) } - if _, err := r.readWorkspaceFile(ctx, protocol.ReadFileRequest{Workspace: workspace, Path: "x"}); !errors.Is(err, ErrClosed) { - t.Fatalf("closed viewer = %v, want ErrClosed", err) + { + _, err := r.readWorkspaceFile(ctx, protocol.ReadFileRequest{Workspace: workspace, Path: "x"}) + testassert.Require(t, errors.Is(err, ErrClosed), "closed viewer = %v, want ErrClosed", err) } }) } @@ -1854,9 +1558,7 @@ func awaitNoRegistryEntries(t *testing.T, r *Runtime, what string) { if entries == 0 { return } - if time.Now().After(deadline) { - t.Fatalf("%s left %d registry entries, want the reclaimed empty registry", what, entries) - } + testassert.Require(t, !time.Now().After(deadline), "%s left %d registry entries, want the reclaimed empty registry", what, entries) time.Sleep(5 * time.Millisecond) } } @@ -1873,16 +1575,12 @@ func TestQueuedDrainOpenerWaitsForRestoreInterval(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "drain-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "only") awaitRestorableSession(t, r, session) file := filepath.Join(workspace, "queued.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, session, op.Admission.AdmittedEntry.EntryID), file, "v1") park, outcomes := parkRestore(t, r, session, "op-1", fifo) @@ -1895,42 +1593,10 @@ func TestQueuedDrainOpenerWaitsForRestoreInterval(t *testing.T) { defer releaseGate() e.server.setHold(gate) - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, - OperationID: "op-run", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "run first"}}, - Mode: harness.MessageModeRegular, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionAdmitted { - return fmt.Errorf("disposition %q, want admitted", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("direct submit during a parked restore: %v", err) - } - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, - OperationID: "op-queued", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "queued behind"}}, - Mode: harness.MessageModeQueued, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionQueued { - return fmt.Errorf("disposition %q, want queued", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("queued submit during a parked restore: %v", err) - } + run, err := submitRawThroughRuntime(context.Background(), r, session, "op-run", "run first", harness.MessageModeRegular) + testassert.Require(t, err == nil, "Submit(op-run): %v", err) + testassert.Require(t, run.Disposition == harness.DispositionAdmitted, "disposition %q, want admitted", run.Disposition) + submitQueuedThroughRuntime(t, r, session, "op-queued", "queued behind") awaitOpenerRegistered(t, r, session) select { // neither execution's model request began inside the interval @@ -1946,9 +1612,7 @@ func TestQueuedDrainOpenerWaitsForRestoreInterval(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if parkedRestore.err != nil || len(parkedRestore.result.Restored) != 1 { - t.Fatalf("parked restore = (%+v, %v)", parkedRestore.result, parkedRestore.err) - } + testassert.Require(t, parkedRestore.err == nil && len(parkedRestore.result.Restored) == 1, "parked restore = (%+v, %v)", parkedRestore.result, parkedRestore.err) awaitModelArrival(t, e) // the direct submit's opener won the released interval releaseGate() @@ -1970,21 +1634,15 @@ func TestBackgroundWakeOpenerWaitsForRestoreInterval(t *testing.T) { writeServiceFile(t, agents.PathForConfig(e.configPath), projectionAgentsDocument) stopper := &stubStopper{events: e.events} r, err := e.open(context.Background(), e.storagePlugin(store), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "wake-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "only") awaitRestorableSession(t, r, session) file := filepath.Join(workspace, "wake.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, session, op.Admission.AdmittedEntry.EntryID), file, "v1") park, outcomes := parkRestore(t, r, session, "op-1", fifo) @@ -1998,19 +1656,15 @@ func TestBackgroundWakeOpenerWaitsForRestoreInterval(t *testing.T) { e.server.setHold(gate) var completionID string - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { return h.StartJob(ctx, session, "0a1b2c3d", func(_ context.Context, id string) error { completionID = id return nil }) - }); err != nil { - t.Fatalf("StartJob during a parked restore: %v", err) - } - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { + })) + testassert.NoError(t, r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { return h.DeliverBackgroundCompletion(ctx, session, completionID, "job report") - }); err != nil { - t.Fatalf("DeliverBackgroundCompletion during a parked restore: %v", err) - } + })) awaitOpenerRegistered(t, r, session) // the wake's opener is the registered waiter select { @@ -2026,9 +1680,7 @@ func TestBackgroundWakeOpenerWaitsForRestoreInterval(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked restore never returned after its FIFO completed") } - if parkedRestore.err != nil || len(parkedRestore.result.Restored) != 1 { - t.Fatalf("parked restore = (%+v, %v)", parkedRestore.result, parkedRestore.err) - } + testassert.Require(t, parkedRestore.err == nil && len(parkedRestore.result.Restored) == 1, "parked restore = (%+v, %v)", parkedRestore.result, parkedRestore.err) awaitModelArrival(t, e) // the wake's opener won the released interval releaseGate() @@ -2062,30 +1714,17 @@ func TestExecutionOpenerFailureReleasesInterval(t *testing.T) { var fail atomic.Bool fail.Store(true) r, err := e.open(context.Background(), e.storagePlugin(store), failingAgentFactoryPlugin(&fail)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "openerr-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, - OperationID: "op-fail", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "fail"}}, - Mode: harness.MessageModeRegular, - }) - return err - }); err != nil { - t.Fatalf("submit: %v", err) - } - if rec := awaitOperation(t, r, session, "op-fail", harness.OperationFailure); rec.State.Status != harness.OperationFailure { - t.Fatalf("failed opening settled as %+v, want failure", rec.State) + _, err = submitRawThroughRuntime(context.Background(), r, session, "op-fail", "fail", harness.MessageModeRegular) + testassert.NoError(t, err) + { + rec := awaitOperation(t, r, session, "op-fail", harness.OperationFailure) + testassert.Require(t, rec.State.Status == harness.OperationFailure, "failed opening settled as %+v, want failure", rec.State) } awaitNoRegistryEntries(t, r, "the failed opening") // the unwind released the token awaitRestorableSession(t, r, session) // the failed execution's slot retired @@ -2128,14 +1767,10 @@ func TestExecutionCleanupReleasesIntervalLast(t *testing.T) { closeGate := make(chan struct{}) closeStarted := make(chan struct{}, 1) r, err := e.open(context.Background(), e.storagePlugin(store), parkingAgentClosePlugin(closeStarted, closeGate)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) workspace := filepath.Join(e.home, "cleanup-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID var gateOnce sync.Once @@ -2150,8 +1785,9 @@ func TestExecutionCleanupReleasesIntervalLast(t *testing.T) { // The token is still held through the blocked cleanup and the not-yet- // closed scopes: a restore conflicts before any directory work. - if _, err := r.revertSessionCode(context.Background(), session, "op-1"); !errors.Is(err, harness.ErrConflict) { - t.Fatalf("restore during the blocked cleanup = %v, want the conflict class", err) + { + _, err := r.revertSessionCode(context.Background(), session, "op-1") + testassert.Require(t, errors.Is(err, harness.ErrConflict), "restore during the blocked cleanup = %v, want the conflict class", err) } assertRegistryState(t, r.artifacts, sessionArtifactDir(r.dataDir, session), 1, 1, true) @@ -2159,9 +1795,7 @@ func TestExecutionCleanupReleasesIntervalLast(t *testing.T) { awaitRestorableSession(t, r, session) awaitNoRegistryEntries(t, r, "the settled cleanup") result, err := r.revertSessionCode(context.Background(), session, "op-1") - if err != nil || result.Restored == nil { - t.Fatalf("restore after the cleanup = (%+v, %v), want the admitted traversal", result, err) - } + testassert.Require(t, err == nil && result.Restored != nil, "restore after the cleanup = (%+v, %v), want the admitted traversal", result, err) }) } @@ -2175,23 +1809,20 @@ func TestRestoreIntervalCancellationAndShutdownOutcomes(t *testing.T) { r, e := openProjectionRuntime(t, store) defer closeProjectionRuntime(r) // every early failure joins the ordinary Runtime cleanup workspace := filepath.Join(e.home, "cancel-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "only") awaitRestorableSession(t, r, session) file := filepath.Join(workspace, "cancel.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, session, op.Admission.AdmittedEntry.EntryID), file, "v1") // A canceled caller before ownership: no token, no file effect. canceledCtx, cancelCaller := context.WithCancel(context.Background()) cancelCaller() - if _, err := r.revertSessionCode(canceledCtx, session, "op-1"); !errors.Is(err, context.Canceled) { - t.Fatalf("canceled restore = %v, want the caller's cancellation", err) + { + _, err := r.revertSessionCode(canceledCtx, session, "op-1") + testassert.Require(t, errors.Is(err, context.Canceled), "canceled restore = %v, want the caller's cancellation", err) } assertRegistryState(t, r.artifacts, sessionArtifactDir(r.dataDir, session), 0, 0, false) @@ -2199,18 +1830,8 @@ func TestRestoreIntervalCancellationAndShutdownOutcomes(t *testing.T) { defer park.flush() drainCommandModelArrivals(e) - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, - OperationID: "op-parked", - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "parked"}}, - Mode: harness.MessageModeRegular, - }) - return err - }); err != nil { - t.Fatalf("submit during a parked restore: %v", err) - } + _, err := submitRawThroughRuntime(context.Background(), r, session, "op-parked", "parked", harness.MessageModeRegular) + testassert.NoError(t, err) awaitOpenerRegistered(t, r, session) // The managed shutdown begins while the opener is parked: its @@ -2227,14 +1848,11 @@ func TestRestoreIntervalCancellationAndShutdownOutcomes(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the begun restore never finished under the shutdown") } - if parkedRestore.err != nil || len(parkedRestore.result.Restored) != 1 || parkedRestore.result.Restored[0] != file { - t.Fatalf("begun restore = (%+v, %v), want its admitted result", parkedRestore.result, parkedRestore.err) - } - if data, err := os.ReadFile(file); err != nil || string(data) != "restored" { - t.Fatalf("file = (%q, %v), want the restore's FIFO content", data, err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + testassert.Require(t, parkedRestore.err == nil && len(parkedRestore.result.Restored) == 1 && parkedRestore.result.Restored[0] == file, "begun restore = (%+v, %v), want its admitted result", parkedRestore.result, parkedRestore.err) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "restored", "file = (%q, %v), want the restore's FIFO content", data, err) } + testassert.NoError(t, r.Close(context.Background())) }) } diff --git a/runtime/protocol_commands_test.go b/runtime/protocol_commands_test.go index de5e5229..c01fe611 100644 --- a/runtime/protocol_commands_test.go +++ b/runtime/protocol_commands_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -87,9 +88,7 @@ func TestSessionCommandCreateHeader(t *testing.T) { workspace := filepath.Join(e.home, "command-ws") header, err := r.createSessionHeader(ctx, workspace, "solo") - if err != nil { - t.Fatalf("createSessionHeader: %v", err) - } + testassert.NoError(t, err) snap := sessionViewThroughRuntime(t, r, header.SessionId) if !reflect.DeepEqual(header, projectSession(snap.Header)) { t.Fatalf("create header = %+v, want the real post-transition read projection %+v", header, projectSession(snap.Header)) @@ -97,40 +96,31 @@ func TestSessionCommandCreateHeader(t *testing.T) { if header.SessionRevision != wireRevision(snap.Header.Revision) { t.Fatalf("create header revision = %+v, want the read pair %+v", header.SessionRevision, wireRevision(snap.Header.Revision)) } - if header.Workspace != workspace || header.AgentType != "solo" || header.Lifecycle != protocol.Open { - t.Fatalf("create header = %+v, want workspace %q solo open", header, workspace) - } + testassert.Require(t, header.Workspace == workspace && header.AgentType == "solo" && header.Lifecycle == protocol.Open, "create header = %+v, want workspace %q solo open", header, workspace) unclean := filepath.Join(e.home, "command-ws", "..", "command-ws") uncleanHeader, err := r.createSessionHeader(ctx, unclean, "solo") - if err != nil { - t.Fatalf("createSessionHeader(unclean): %v", err) - } - if uncleanHeader.Workspace != workspace { - t.Fatalf("unclean workspace = %q, want the lexical %q", uncleanHeader.Workspace, workspace) - } + testassert.NoError(t, err) + testassert.Equal(t, uncleanHeader.Workspace, workspace) - if _, err := r.createSessionHeader(ctx, "", "solo"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty workspace header = %v, want harness.ErrInvalid", err) + { + _, err := r.createSessionHeader(ctx, "", "solo") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty workspace header = %v, want harness.ErrInvalid", err) } - if _, err := r.createSession(ctx, "", "solo"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty workspace record = %v, want harness.ErrInvalid", err) + { + _, err := r.createSession(ctx, "", "solo") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty workspace record = %v, want harness.ErrInvalid", err) } ghost, err := r.createSessionHeader(ctx, filepath.Join(e.home, "ghost-ws"), "ghost") - if err != nil { - t.Fatalf("createSessionHeader(ghost) = %v, want the nonempty-only selection rule", err) - } - if ghost.AgentType != "ghost" { - t.Fatalf("ghost header agent type = %q, want ghost", ghost.AgentType) - } - if _, err := r.submitSession(ctx, ghost.SessionId, commandSubmitRequest(t, "op-ghost", "hi")); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit on unknown agent type = %v, want the preparation-time harness.ErrInvalid", err) + testassert.NoError(t, err) + testassert.Equal(t, ghost.AgentType, "ghost") + { + _, err := r.submitSession(ctx, ghost.SessionId, commandSubmitRequest(t, "op-ghost", "hi")) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit on unknown agent type = %v, want the preparation-time harness.ErrInvalid", err) } ghostSnap := sessionViewThroughRuntime(t, r, ghost.SessionId) - if len(ghostSnap.Operations) != 0 || ghostSnap.Header.CurrentOperationID != "" { - t.Fatalf("unknown-type preparation failure committed state: %+v", ghostSnap.Header) - } + testassert.Require(t, len(ghostSnap.Operations) == 0 && ghostSnap.Header.CurrentOperationID == "", "unknown-type preparation failure committed state: %+v", ghostSnap.Header) }) } @@ -144,45 +134,35 @@ func TestSessionCommandAgentType(t *testing.T) { defer closeProjectionRuntime(r) rec, err := r.createSession(ctx, filepath.Join(e.home, "type-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := rec.Identity.SessionID header, err := r.setSessionAgentType(ctx, sessionID, protocol.SetSessionAgentTypeRequest{AgentType: "ghost"}) - if err != nil { - t.Fatalf("setSessionAgentType(ghost) = %v, want the nonempty-only selection rule", err) - } - if header.AgentType != "ghost" { - t.Fatalf("ghost header agent type = %q, want ghost", header.AgentType) - } + testassert.NoError(t, err) + testassert.Equal(t, header.AgentType, "ghost") snap := sessionViewThroughRuntime(t, r, sessionID) if !reflect.DeepEqual(header, projectSession(snap.Header)) || header.SessionRevision != wireRevision(snap.Header.Revision) { t.Fatalf("ghost header = %+v, want the real read projection %+v", header, projectSession(snap.Header)) } worker, err := r.setSessionAgentType(ctx, sessionID, protocol.SetSessionAgentTypeRequest{AgentType: "worker"}) - if err != nil { - t.Fatalf("setSessionAgentType(worker): %v", err) - } + testassert.NoError(t, err) workerSnap := sessionViewThroughRuntime(t, r, sessionID) if !reflect.DeepEqual(worker, projectSession(workerSnap.Header)) || worker.SessionRevision != wireRevision(workerSnap.Header.Revision) { t.Fatalf("worker header = %+v, want the real read projection %+v", worker, projectSession(workerSnap.Header)) } - if worker.SessionRevision.DurableRevision == header.SessionRevision.DurableRevision { - t.Fatalf("durable revision did not advance across the committed change: %+v", worker.SessionRevision) - } + testassert.Require(t, worker.SessionRevision.DurableRevision != header.SessionRevision.DurableRevision, "durable revision did not advance across the committed change: %+v", worker.SessionRevision) - if _, err := r.setSessionAgentType(ctx, sessionID, protocol.SetSessionAgentTypeRequest{}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty agent type = %v, want harness.ErrInvalid", err) + { + _, err := r.setSessionAgentType(ctx, sessionID, protocol.SetSessionAgentTypeRequest{}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty agent type = %v, want harness.ErrInvalid", err) } after := sessionViewThroughRuntime(t, r, sessionID) - if after.Header.CurrentAgentType != "worker" { - t.Fatalf("agent type after empty refusal = %q, want worker", after.Header.CurrentAgentType) - } + testassert.Equal(t, after.Header.CurrentAgentType, "worker") - if _, err := r.setSessionAgentType(ctx, "ffffffffffffffffffffffffffffffff", protocol.SetSessionAgentTypeRequest{AgentType: "worker"}); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("set on absent session = %v, want harness.ErrNotFound", err) + { + _, err := r.setSessionAgentType(ctx, "ffffffffffffffffffffffffffffffff", protocol.SetSessionAgentTypeRequest{AgentType: "worker"}) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "set on absent session = %v, want harness.ErrNotFound", err) } }) } @@ -200,46 +180,28 @@ func TestSessionCommandSubmitModes(t *testing.T) { defer closeProjectionRuntime(r) first, err := r.createSession(ctx, filepath.Join(e.home, "modes-a"), "solo") - if err != nil { - t.Fatalf("createSession(a): %v", err) - } + testassert.NoError(t, err) sessionID := first.Identity.SessionID admitted, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-1", "run")) - if err != nil { - t.Fatalf("idle regular submit: %v", err) - } - if admitted.Disposition != protocol.SubmitResultDispositionAdmitted || admitted.Operation == nil { - t.Fatalf("idle regular result = %+v, want admitted with an Operation", admitted) - } - if admitted.Operation.OperationId != "op-1" || admitted.Operation.Status != protocol.OperationStatusRunning { - t.Fatalf("idle regular operation = %+v, want op-1 running", admitted.Operation) - } + testassert.NoError(t, err) + testassert.Require(t, admitted.Disposition == protocol.SubmitResultDispositionAdmitted && admitted.Operation != nil, "idle regular result = %+v, want admitted with an Operation", admitted) + testassert.Require(t, admitted.Operation.OperationId == "op-1" && admitted.Operation.Status == protocol.OperationStatusRunning, "idle regular operation = %+v, want op-1 running", admitted.Operation) awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) awaitRestorableSession(t, r, sessionID) retry, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-1", "run")) - if err != nil { - t.Fatalf("same-ID retry: %v", err) - } - if retry.Disposition != protocol.SubmitResultDispositionExisting || retry.Operation == nil { - t.Fatalf("same-ID retry = %+v, want existing with the first Operation", retry) - } - if retry.Operation.OperationId != "op-1" || !retry.Operation.AdmittedAt.Equal(admitted.Operation.AdmittedAt) { - t.Fatalf("same-ID retry operation = %+v, want the first-writer admission", retry.Operation) - } + testassert.NoError(t, err) + testassert.Require(t, retry.Disposition == protocol.SubmitResultDispositionExisting && retry.Operation != nil, "same-ID retry = %+v, want existing with the first Operation", retry) + testassert.Require(t, retry.Operation.OperationId == "op-1" && retry.Operation.AdmittedAt.Equal(admitted.Operation.AdmittedAt), "same-ID retry operation = %+v, want the first-writer admission", retry.Operation) queuedIdle, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ OperationId: "op-q-idle", Mode: protocol.SubmitRequestModeQueued, Content: []protocol.ContentPart{textContentPart(t, "queued while idle", nil)}, }) - if err != nil { - t.Fatalf("idle queued submit: %v", err) - } - if queuedIdle.Disposition != protocol.SubmitResultDispositionAdmitted || queuedIdle.Operation == nil { - t.Fatalf("idle queued result = %+v, want ordinary admission", queuedIdle) - } + testassert.NoError(t, err) + testassert.Require(t, queuedIdle.Disposition == protocol.SubmitResultDispositionAdmitted && queuedIdle.Operation != nil, "idle queued result = %+v, want ordinary admission", queuedIdle) awaitOperation(t, r, sessionID, "op-q-idle", harness.OperationSuccess) awaitRestorableSession(t, r, sessionID) @@ -248,43 +210,24 @@ func TestSessionCommandSubmitModes(t *testing.T) { defer releaseCommandGate(gate) e.server.setHold(gate) active, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-active", "active run")) - if err != nil { - t.Fatalf("active regular submit: %v", err) - } - if active.Disposition != protocol.SubmitResultDispositionAdmitted || active.Operation == nil { - t.Fatalf("active admission = %+v, want admitted", active) - } + testassert.NoError(t, err) + testassert.Require(t, active.Disposition == protocol.SubmitResultDispositionAdmitted && active.Operation != nil, "active admission = %+v, want admitted", active) <-e.server.arrived // the active model call is parked before the buffers are filled steering, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-steer", "steer")) - if err != nil { - t.Fatalf("active steering submit: %v", err) - } - if steering.Disposition != protocol.SubmitResultDispositionSteering || steering.Operation != nil { - t.Fatalf("steering result = %+v, want steering with nil Operation", steering) - } + testassert.NoError(t, err) + testassert.Require(t, steering.Disposition == protocol.SubmitResultDispositionSteering && steering.Operation == nil, "steering result = %+v, want steering with nil Operation", steering) queued, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ OperationId: "op-queued", Mode: protocol.SubmitRequestModeQueued, Content: []protocol.ContentPart{textContentPart(t, "queued", nil)}, }) - if err != nil { - t.Fatalf("active queued submit: %v", err) - } - if queued.Disposition != protocol.SubmitResultDispositionQueued || queued.Operation != nil { - t.Fatalf("queued result = %+v, want queued with nil Operation", queued) - } + testassert.NoError(t, err) + testassert.Require(t, queued.Disposition == protocol.SubmitResultDispositionQueued && queued.Operation == nil, "queued result = %+v, want queued with nil Operation", queued) buffered := sessionViewThroughRuntime(t, r, sessionID) - if len(buffered.Steering) != 1 || buffered.Steering[0].OperationID != "op-steer" || buffered.Steering[0].Origin != harness.InputOriginUser { - t.Fatalf("steering buffer = %+v, want the caller-minted op-steer user item", buffered.Steering) - } - if len(buffered.Queued) != 1 || buffered.Queued[0].OperationID != "op-queued" || buffered.Queued[0].Origin != harness.InputOriginUser { - t.Fatalf("queued buffer = %+v, want the caller-minted op-queued user item", buffered.Queued) - } + testassert.Require(t, len(buffered.Steering) == 1 && buffered.Steering[0].OperationID == "op-steer" && buffered.Steering[0].Origin == harness.InputOriginUser && len(buffered.Queued) == 1 && buffered.Queued[0].OperationID == "op-queued" && buffered.Queued[0].Origin == harness.InputOriginUser, "steering buffer = %+v, want the caller-minted op-steer user item; queued buffer = %+v, want the caller-minted op-queued user item", buffered.Steering, buffered.Queued) for _, op := range buffered.Operations { - if op.OperationID == "op-steer" || op.OperationID == "op-queued" { - t.Fatalf("buffered input %q was durably admitted before the drain", op.OperationID) - } + testassert.Require(t, op.OperationID != "op-steer" && op.OperationID != "op-queued", "buffered input %q was durably admitted before the drain", op.OperationID) } steeringCommitted := false @@ -298,33 +241,21 @@ func TestSessionCommandSubmitModes(t *testing.T) { if fact.Kind != harness.EntryInput || fact.OperationID != "op-steer" { continue } - if fact.Input.Origin != harness.InputOriginUser { - t.Fatalf("committed input origin = %q, want user (the wire carries none)", fact.Input.Origin) - } + testassert.Equal(t, fact.Input.Origin, harness.InputOriginUser) if fact.Input.Content[0].Text == "steer" { steeringCommitted = true } } - if !steeringCommitted { - t.Fatal("the steering input never committed as its own Operation's admitted entry") - } + testassert.Require(t, steeringCommitted, "the steering input never committed as its own Operation's admitted entry") activeRec := readOperation(t, r, sessionID, "op-active") - if activeRec.State.Status != harness.OperationSuccess || activeRec.State.Terminal == nil || activeRec.State.Terminal.Detail != "" { - t.Fatalf("active operation = %+v, want the handoff's quiet success", activeRec) - } + testassert.Require(t, activeRec.State.Status == harness.OperationSuccess && activeRec.State.Terminal != nil && activeRec.State.Terminal.Detail == "", "active operation = %+v, want the handoff's quiet success", activeRec) second, err := r.createSession(ctx, filepath.Join(e.home, "modes-b"), "solo") - if err != nil { - t.Fatalf("createSession(b): %v", err) - } + testassert.NoError(t, err) _, err = r.submitSession(ctx, second.Identity.SessionID, commandSubmitRequest(t, "op-1", "reuse")) - if !errors.Is(err, harness.ErrInvalid) || !strings.Contains(err.Error(), "another session") { - t.Fatalf("cross-Session Operation ID reuse = %v, want the landed invalid rule", err) - } + testassert.Require(t, errors.Is(err, harness.ErrInvalid) && strings.Contains(err.Error(), "another session"), "cross-Session Operation ID reuse = %v, want the landed invalid rule", err) secondSnap := sessionViewThroughRuntime(t, r, second.Identity.SessionID) - if len(secondSnap.Operations) != 0 { - t.Fatalf("cross-Session reuse committed an Operation: %+v", secondSnap.Operations) - } + testassert.Require(t, len(secondSnap.Operations) == 0, "cross-Session reuse committed an Operation: %+v", secondSnap.Operations) }) } @@ -338,9 +269,7 @@ func TestSessionCommandSubmitContent(t *testing.T) { defer closeProjectionRuntime(r) rec, err := r.createSession(ctx, filepath.Join(e.home, "content-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := rec.Identity.SessionID textExtra := protocol.JSONObject{"n": json.RawMessage("42")} @@ -351,27 +280,17 @@ func TestSessionCommandSubmitContent(t *testing.T) { "s": json.RawMessage(`"opaque-tag"`), } var text, image, opaque protocol.ContentPart - if err := text.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: "hello", Extra: &textExtra}); err != nil { - t.Fatalf("text part: %v", err) - } - if err := image.FromImageURLPart(protocol.ImageURLPart{Kind: protocol.ImageUrl, Url: "https://example.test/x.png", Extra: &imageExtra}); err != nil { - t.Fatalf("image part: %v", err) - } - if err := opaque.FromOpaquePart(protocol.OpaquePart{Kind: protocol.Opaque, OpaqueWireType: "audio", Extra: &opaqueExtra}); err != nil { - t.Fatalf("opaque part: %v", err) - } + testassert.NoError(t, text.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: "hello", Extra: &textExtra})) + testassert.NoError(t, image.FromImageURLPart(protocol.ImageURLPart{Kind: protocol.ImageUrl, Url: "https://example.test/x.png", Extra: &imageExtra})) + testassert.NoError(t, opaque.FromOpaquePart(protocol.OpaquePart{Kind: protocol.Opaque, OpaqueWireType: "audio", Extra: &opaqueExtra})) res, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ OperationId: "op-content", Mode: protocol.SubmitRequestModeRegular, Content: []protocol.ContentPart{text, image, opaque}, }) - if err != nil { - t.Fatalf("content submit: %v", err) - } - if res.Disposition != protocol.SubmitResultDispositionAdmitted { - t.Fatalf("content submit = %+v, want admitted", res) - } + testassert.NoError(t, err) + testassert.Require(t, res.Disposition == protocol.SubmitResultDispositionAdmitted, "content submit = %+v, want admitted", res) awaitOperation(t, r, sessionID, "op-content", harness.OperationSuccess) snap := sessionViewThroughRuntime(t, r, sessionID) @@ -385,30 +304,17 @@ func TestSessionCommandSubmitContent(t *testing.T) { "s": json.RawMessage(`"opaque-tag"`), }}, } - if !reflect.DeepEqual(fact.Input.Content, want) { - t.Fatalf("committed content = %#v, want raw-preserving %#v", fact.Input.Content, want) - } - if fact.Input.Origin != harness.InputOriginUser { - t.Fatalf("committed content origin = %q, want user", fact.Input.Origin) - } + testassert.Require(t, reflect.DeepEqual(fact.Input.Content, want) && fact.Input.Origin == harness.InputOriginUser, "committed content = %#v, want raw-preserving %#v; got %v, want %v", fact.Input.Content, want, fact.Input.Origin, harness.InputOriginUser) unknown := json.RawMessage(`{"kind":"audio","url":"x"}`) var unknownPart protocol.ContentPart - if err := json.Unmarshal(unknown, &unknownPart); err != nil { - t.Fatalf("build unknown union: %v", err) - } + testassert.NoError(t, json.Unmarshal(unknown, &unknownPart)) var missingKind protocol.ContentPart - if err := json.Unmarshal([]byte(`{"text":"x"}`), &missingKind); err != nil { - t.Fatalf("build kind-less union: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(`{"text":"x"}`), &missingKind)) var badDecode protocol.ContentPart - if err := json.Unmarshal([]byte(`{"kind":"text","text":12}`), &badDecode); err != nil { - t.Fatalf("build decode-failing union: %v", err) - } + testassert.NoError(t, json.Unmarshal([]byte(`{"kind":"text","text":12}`), &badDecode)) var missingWireType protocol.ContentPart - if err := missingWireType.FromOpaquePart(protocol.OpaquePart{Kind: protocol.Opaque}); err != nil { - t.Fatalf("build wire-type-less opaque: %v", err) - } + testassert.NoError(t, missingWireType.FromOpaquePart(protocol.OpaquePart{Kind: protocol.Opaque})) for name, part := range map[string]protocol.ContentPart{ "unknown kind": unknownPart, @@ -421,28 +327,26 @@ func TestSessionCommandSubmitContent(t *testing.T) { Mode: protocol.SubmitRequestModeRegular, Content: []protocol.ContentPart{part}, }) - if !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s = %v, want harness.ErrInvalid", name, err) - } + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s = %v, want harness.ErrInvalid", name, err) } - if _, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ - OperationId: "op-bad-mode", - Mode: protocol.SubmitRequestMode("weird"), - Content: []protocol.ContentPart{textContentPart(t, "x", nil)}, - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("invalid mode = %v, want the Harness mode validator", err) + { + _, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ + OperationId: "op-bad-mode", + Mode: protocol.SubmitRequestMode("weird"), + Content: []protocol.ContentPart{textContentPart(t, "x", nil)}, + }) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "invalid mode = %v, want the Harness mode validator", err) } - if _, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ - Mode: protocol.SubmitRequestModeRegular, - Content: []protocol.ContentPart{textContentPart(t, "x", nil)}, - }); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("empty operation id = %v, want harness.ErrInvalid", err) + { + _, err := r.submitSession(ctx, sessionID, protocol.SubmitRequest{ + Mode: protocol.SubmitRequestModeRegular, + Content: []protocol.ContentPart{textContentPart(t, "x", nil)}, + }) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "empty operation id = %v, want harness.ErrInvalid", err) } after := sessionViewThroughRuntime(t, r, sessionID) - if len(after.Operations) != 1 || after.Operations[0].OperationID != "op-content" { - t.Fatalf("malformed submits committed state: %+v", after.Operations) - } + testassert.Require(t, len(after.Operations) == 1 && after.Operations[0].OperationID == "op-content", "malformed submits committed state: %+v", after.Operations) }) } @@ -456,41 +360,34 @@ func TestSessionCommandCompact(t *testing.T) { defer closeProjectionRuntime(r) rec, err := r.createSession(ctx, filepath.Join(e.home, "compact-cmd-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := rec.Identity.SessionID - if _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-seed", "seed")); err != nil { - t.Fatalf("seed submit: %v", err) + { + _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-seed", "seed")) + testassert.NoError(t, err) } awaitOperation(t, r, sessionID, "op-seed", harness.OperationSuccess) awaitRestorableSession(t, r, sessionID) op, err := r.compactSession(ctx, sessionID, protocol.CompactRequest{OperationId: "op-compact"}) - if err != nil { - t.Fatalf("compactSession: %v", err) - } - if op.OperationId != "op-compact" || op.RequestKind != protocol.Compact { - t.Fatalf("compact operation = %+v, want op-compact compact", op) - } + testassert.NoError(t, err) + testassert.Require(t, op.OperationId == "op-compact" && op.RequestKind == protocol.Compact, "compact operation = %+v, want op-compact compact", op) retry, err := r.compactSession(ctx, sessionID, protocol.CompactRequest{OperationId: "op-compact"}) - if err != nil { - t.Fatalf("compact retry: %v", err) - } - if retry.OperationId != op.OperationId || !retry.AdmittedAt.Equal(op.AdmittedAt) { - t.Fatalf("compact retry = %+v, want the first-writer Operation", retry) - } + testassert.NoError(t, err) + testassert.Require(t, retry.OperationId == op.OperationId && retry.AdmittedAt.Equal(op.AdmittedAt), "compact retry = %+v, want the first-writer Operation", retry) awaitOperation(t, r, sessionID, "op-compact", harness.OperationSuccess) awaitIdleSession(t, r, sessionID) gate := make(chan struct{}) defer releaseCommandGate(gate) e.server.setHold(gate) - if _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-run", "run")); err != nil { - t.Fatalf("active submit: %v", err) + { + _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-run", "run")) + testassert.NoError(t, err) } - if _, err := r.compactSession(ctx, sessionID, protocol.CompactRequest{OperationId: "op-compact-busy"}); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("non-idle compact = %v, want the Harness idle guard", err) + { + _, err := r.compactSession(ctx, sessionID, protocol.CompactRequest{OperationId: "op-compact-busy"}) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "non-idle compact = %v, want the Harness idle guard", err) } close(gate) awaitOperation(t, r, sessionID, "op-run", harness.OperationSuccess) @@ -513,12 +410,11 @@ func TestSessionCommandFork(t *testing.T) { defer closeProjectionRuntime(r) source, err := r.createSession(ctx, filepath.Join(e.home, "fork-src"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sourceID := source.Identity.SessionID - if _, err := r.submitSession(ctx, sourceID, commandSubmitRequest(t, "op-src", "boundary")); err != nil { - t.Fatalf("source submit: %v", err) + { + _, err := r.submitSession(ctx, sourceID, commandSubmitRequest(t, "op-src", "boundary")) + testassert.NoError(t, err) } awaitOperation(t, r, sourceID, "op-src", harness.OperationSuccess) awaitIdleSession(t, r, sourceID) @@ -532,9 +428,7 @@ func TestSessionCommandFork(t *testing.T) { assistantEntry = fact.EntryID } } - if assistantEntry == "" { - t.Fatal("fixture committed no assistant entry") - } + testassert.Require(t, assistantEntry != "", "fixture committed no assistant entry") // A valid source resolves its own boundary: neither invalid row may // enumerate destinations as a fallback. @@ -543,11 +437,13 @@ func TestSessionCommandFork(t *testing.T) { "non-user item": {BoundaryItemId: projectItemID(sourceID, assistantEntry), OperationId: "op-bad-b", Content: []protocol.ContentPart{textContentPart(t, "fork", nil)}}, } { before := counting.listCount() - if _, err := r.forkSession(ctx, sourceID, req); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s fork = %v, want harness.ErrInvalid", name, err) + { + _, err := r.forkSession(ctx, sourceID, req) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s fork = %v, want harness.ErrInvalid", name, err) } - if got := counting.listCount() - before; got != 0 { - t.Fatalf("%s fork issued %d session enumerations, want none from a valid source", name, got) + { + got := counting.listCount() - before + testassert.Require(t, got == 0, "%s fork issued %d session enumerations, want none from a valid source", name, got) } } @@ -567,22 +463,15 @@ func TestSessionCommandFork(t *testing.T) { } before := counting.listCount() result, err := r.forkSession(ctx, sourceID, req) - if err != nil { - t.Fatalf("forkSession: %v", err) - } - if got := counting.listCount() - before; got != 1 { - t.Fatalf("live-source fork issued %d session enumerations, want exactly the Harness first-writer lookup", got) - } + testassert.NoError(t, err) + testassert.Equal(t, counting.listCount()-before, 1) destID := result.Session.SessionId - if destID == sourceID || result.Session.SourceSessionId == nil || *result.Session.SourceSessionId != sourceID { - t.Fatalf("fork destination = %+v, want independent lineage from %q", result.Session, sourceID) - } - if result.Operation.OperationId != "op-fork" || result.Operation.Status != protocol.OperationStatusRunning { - t.Fatalf("fork operation = %+v, want running op-fork", result.Operation) - } + testassert.Require(t, destID != sourceID && result.Session.SourceSessionId != nil && *result.Session.SourceSessionId == sourceID, "fork destination = %+v, want independent lineage from %q", result.Session, sourceID) + testassert.Require(t, result.Operation.OperationId == "op-fork" && result.Operation.Status == protocol.OperationStatusRunning, "fork operation = %+v, want running op-fork", result.Operation) <-e.server.arrived // rendezvous: the destination's first model effect is parked - if status, err := operationStatusOf(store, destID, "op-fork"); err != nil || status != harness.OperationRunning { - t.Fatalf("fork header rendezvous = (%q, %v), want the destination still running", status, err) + { + status, err := operationStatusOf(store, destID, "op-fork") + testassert.Require(t, err == nil && status == harness.OperationRunning, "fork header rendezvous = (%q, %v), want the destination still running", status, err) } destSnap := sessionViewThroughRuntime(t, r, destID) if !reflect.DeepEqual(result.Session, projectSession(destSnap.Header)) || result.Session.SessionRevision != wireRevision(destSnap.Header.Revision) { @@ -590,45 +479,39 @@ func TestSessionCommandFork(t *testing.T) { } retry, err := r.forkSession(ctx, sourceID, req) - if err != nil { - t.Fatalf("fork same-ID retry: %v", err) - } - if !reflect.DeepEqual(retry.Session, result.Session) || retry.Operation.OperationId != "op-fork" { - t.Fatalf("fork retry = (%+v,%+v), want the identical first destination %+v", retry.Session, retry.Operation, result) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(retry.Session, result.Session) && retry.Operation.OperationId == "op-fork", "fork retry = (%+v,%+v), want the identical first destination %+v", retry.Session, retry.Operation, result) - if header, err := r.archiveSession(ctx, sourceID); err != nil || header.Lifecycle != protocol.Archived { - t.Fatalf("archive source = (%+v, %v), want archived", header, err) - } - if err := r.deleteSession(ctx, sourceID); err != nil { - t.Fatalf("delete source: %v", err) + { + header, err := r.archiveSession(ctx, sourceID) + testassert.Require(t, err == nil && header.Lifecycle == protocol.Archived, "archive source = (%+v, %v), want archived", header, err) } + testassert.NoError(t, r.deleteSession(ctx, sourceID)) afterDelete, err := r.forkSession(ctx, sourceID, req) - if err != nil { - t.Fatalf("fork retry after source deletion: %v", err) - } - if !reflect.DeepEqual(afterDelete.Session, result.Session) || afterDelete.Operation.OperationId != "op-fork" { - t.Fatalf("post-deletion retry = (%+v,%+v), want the identical destination %+v", afterDelete.Session, afterDelete.Operation, result) - } - if _, err := r.forkSession(ctx, sourceID, protocol.ForkRequest{ - BoundaryItemId: projectItemID(sourceID, assistantEntry), - OperationId: "op-fork", - Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("mismatched boundary after source deletion = %v, want typed source unavailability", err) + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(afterDelete.Session, result.Session) && afterDelete.Operation.OperationId == "op-fork", "post-deletion retry = (%+v,%+v), want the identical destination %+v", afterDelete.Session, afterDelete.Operation, result) + { + _, err := r.forkSession(ctx, sourceID, protocol.ForkRequest{ + BoundaryItemId: projectItemID(sourceID, assistantEntry), + OperationId: "op-fork", + Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, + }) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "mismatched boundary after source deletion = %v, want typed source unavailability", err) } - if _, err := r.forkSession(ctx, sourceID, protocol.ForkRequest{ - BoundaryItemId: boundaryItem, - OperationId: "op-other", - Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, - }); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("unknown operation after source deletion = %v, want typed source unavailability", err) + { + _, err := r.forkSession(ctx, sourceID, protocol.ForkRequest{ + BoundaryItemId: boundaryItem, + OperationId: "op-other", + Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, + }) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "unknown operation after source deletion = %v, want typed source unavailability", err) } // The gate held the destination through every comparison: it must // still be running at the release point. - if status, err := operationStatusOf(store, destID, "op-fork"); err != nil || status != harness.OperationRunning { - t.Fatalf("destination settled before release: (%q, %v), want still running", status, err) + { + status, err := operationStatusOf(store, destID, "op-fork") + testassert.Require(t, err == nil && status == harness.OperationRunning, "destination settled before release: (%q, %v), want still running", status, err) } releaseCommandGate(gate) @@ -636,12 +519,8 @@ func TestSessionCommandFork(t *testing.T) { awaitRestorableSession(t, r, destID) sessions, err := r.listSessions(ctx, protocol.ListSessionsParams{Workspace: filepath.Join(e.home, "fork-src"), Lifecycle: protocol.Open}) - if err != nil { - t.Fatalf("listSessions: %v", err) - } - if len(sessions) != 1 || sessions[0].SessionId != destID { - t.Fatalf("workspace sessions = %+v, want exactly the destination %q", sessions, destID) - } + testassert.NoError(t, err) + testassert.Require(t, len(sessions) == 1 && sessions[0].SessionId == destID, "workspace sessions = %+v, want exactly the destination %q", sessions, destID) }) } @@ -654,12 +533,11 @@ func TestSessionCommandForkCorruptSource(t *testing.T) { ctx := context.Background() first, e := openProjectionRuntime(t, store) source, err := first.createSession(ctx, filepath.Join(e.home, "corrupt-fork-src"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sourceID := source.Identity.SessionID - if _, err := first.submitSession(ctx, sourceID, commandSubmitRequest(t, "op-src", "boundary")); err != nil { - t.Fatalf("source submit: %v", err) + { + _, err := first.submitSession(ctx, sourceID, commandSubmitRequest(t, "op-src", "boundary")) + testassert.NoError(t, err) } awaitOperation(t, first, sourceID, "op-src", harness.OperationSuccess) awaitIdleSession(t, first, sourceID) @@ -672,9 +550,7 @@ func TestSessionCommandForkCorruptSource(t *testing.T) { Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, } result, err := first.forkSession(ctx, sourceID, req) - if err != nil { - t.Fatalf("forkSession: %v", err) - } + testassert.NoError(t, err) destID := result.Session.SessionId awaitOperation(t, first, destID, "op-fork", harness.OperationSuccess) awaitRestorableSession(t, first, destID) @@ -687,12 +563,8 @@ func TestSessionCommandForkCorruptSource(t *testing.T) { second, _ := openProjectionRuntime(t, store) defer closeProjectionRuntime(second) retry, err := second.forkSession(ctx, sourceID, req) - if err != nil { - t.Fatalf("corrupt-source retry: %v", err) - } - if retry.Session.SessionId != destID || retry.Operation.OperationId != "op-fork" { - t.Fatalf("corrupt-source retry = (%q,%q), want the committed destination %q", retry.Session.SessionId, retry.Operation.OperationId, destID) - } + testassert.NoError(t, err) + testassert.Require(t, retry.Session.SessionId == destID && retry.Operation.OperationId == "op-fork", "corrupt-source retry = (%q,%q), want the committed destination %q", retry.Session.SessionId, retry.Operation.OperationId, destID) // Nearest forbidden sibling: with no matching destination lineage the // original source corruption returns, never nil and never a fabricated @@ -701,8 +573,9 @@ func TestSessionCommandForkCorruptSource(t *testing.T) { "unknown operation": {BoundaryItemId: boundaryItem, OperationId: "op-absent", Content: req.Content}, "mismatched boundary": {BoundaryItemId: projectItemID(sourceID, "ffffffffffffffffffffffffffffffff"), OperationId: "op-fork", Content: req.Content}, } { - if _, err := second.forkSession(ctx, sourceID, bad); !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("%s corrupt-source fork = %v, want the original source corruption", name, err) + { + _, err := second.forkSession(ctx, sourceID, bad) + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "%s corrupt-source fork = %v, want the original source corruption", name, err) } } }) @@ -719,9 +592,7 @@ func TestSessionCommandLifecycle(t *testing.T) { defer closeProjectionRuntime(r) rec, err := r.createSession(ctx, filepath.Join(e.home, "life-ws"), "solo") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := rec.Identity.SessionID // Publish a local revision: a steering enqueue and its handoff delivery @@ -730,61 +601,51 @@ func TestSessionCommandLifecycle(t *testing.T) { gate := make(chan struct{}) defer releaseCommandGate(gate) e.server.setHold(gate) - if _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-1", "run")); err != nil { - t.Fatalf("active submit: %v", err) + { + _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-1", "run")) + testassert.NoError(t, err) } <-e.server.arrived // the active model call is parked before the steering enqueue - if _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-steer", "steer")); err != nil { - t.Fatalf("steering submit: %v", err) + { + _, err := r.submitSession(ctx, sessionID, commandSubmitRequest(t, "op-steer", "steer")) + testassert.NoError(t, err) } close(gate) awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) awaitRestorableSession(t, r, sessionID) before := sessionViewThroughRuntime(t, r, sessionID) - if before.Header.Revision.LocalRevision == 0 { - t.Fatal("fixture published no coordinator-local revision") - } + testassert.Require(t, before.Header.Revision.LocalRevision != 0, "fixture published no coordinator-local revision") archived, err := r.archiveSession(ctx, sessionID) - if err != nil { - t.Fatalf("archiveSession: %v", err) - } - if archived.Lifecycle != protocol.Archived || archived.ArchivedAt == nil { - t.Fatalf("archive header = %+v, want archived with a timestamp", archived) - } + testassert.NoError(t, err) + testassert.Require(t, archived.Lifecycle == protocol.Archived && archived.ArchivedAt != nil, "archive header = %+v, want archived with a timestamp", archived) archivedSnap := sessionViewThroughRuntime(t, r, sessionID) if !reflect.DeepEqual(archived, projectSession(archivedSnap.Header)) || archived.SessionRevision != wireRevision(archivedSnap.Header.Revision) { t.Fatalf("archive header = %+v, want the real read projection %+v", archived, projectSession(archivedSnap.Header)) } - if archived.SessionRevision.LocalRevision != wireRevision(before.Header.Revision).LocalRevision { - t.Fatalf("archive header local revision = %q, want the real %q", archived.SessionRevision.LocalRevision, wireRevision(before.Header.Revision).LocalRevision) - } + testassert.Equal(t, archived.SessionRevision.LocalRevision, wireRevision(before.Header.Revision).LocalRevision) reopened, err := r.reopenSession(ctx, sessionID) - if err != nil { - t.Fatalf("reopenSession: %v", err) - } - if reopened.Lifecycle != protocol.Open || reopened.ArchivedAt != nil { - t.Fatalf("reopen header = %+v, want open without a timestamp", reopened) - } + testassert.NoError(t, err) + testassert.Require(t, reopened.Lifecycle == protocol.Open && reopened.ArchivedAt == nil, "reopen header = %+v, want open without a timestamp", reopened) reopenedSnap := sessionViewThroughRuntime(t, r, sessionID) if !reflect.DeepEqual(reopened, projectSession(reopenedSnap.Header)) || reopened.SessionRevision != wireRevision(reopenedSnap.Header.Revision) { t.Fatalf("reopen header = %+v, want the real read projection %+v", reopened, projectSession(reopenedSnap.Header)) } running, err := r.createSession(ctx, filepath.Join(e.home, "life-running"), "solo") - if err != nil { - t.Fatalf("createSession(running): %v", err) - } + testassert.NoError(t, err) runningGate := make(chan struct{}) defer releaseCommandGate(runningGate) e.server.setHold(runningGate) - if _, err := r.submitSession(ctx, running.Identity.SessionID, commandSubmitRequest(t, "op-running", "run")); err != nil { - t.Fatalf("running submit: %v", err) + { + _, err := r.submitSession(ctx, running.Identity.SessionID, commandSubmitRequest(t, "op-running", "run")) + testassert.NoError(t, err) } - if _, err := r.archiveSession(ctx, running.Identity.SessionID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("archive running = %v, want the Harness running gate", err) + { + _, err := r.archiveSession(ctx, running.Identity.SessionID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "archive running = %v, want the Harness running gate", err) } close(runningGate) awaitOperation(t, r, running.Identity.SessionID, "op-running", harness.OperationSuccess) @@ -793,42 +654,44 @@ func TestSessionCommandLifecycle(t *testing.T) { ownTree := filepath.Join(r.dataDir, "code", sessionID) siblingTree := filepath.Join(r.dataDir, "code", "ffffffffffffffffffffffffffffffff") for _, tree := range []string{ownTree, siblingTree} { - if err := os.MkdirAll(filepath.Join(tree, "snapshots", "1"), 0o700); err != nil { - t.Fatalf("seed artifact tree %s: %v", tree, err) + { + err := os.MkdirAll(filepath.Join(tree, "snapshots", "1"), 0o700) + testassert.Require(t, err == nil, "seed artifact tree %s: %v", tree, err) } - if err := os.WriteFile(filepath.Join(tree, "snapshots", "1", "f"), []byte("x"), 0o600); err != nil { - t.Fatalf("seed artifact file %s: %v", tree, err) + { + err := os.WriteFile(filepath.Join(tree, "snapshots", "1", "f"), []byte("x"), 0o600) + testassert.Require(t, err == nil, "seed artifact file %s: %v", tree, err) } } openSession, err := r.createSession(ctx, filepath.Join(e.home, "life-open"), "solo") - if err != nil { - t.Fatalf("createSession(open): %v", err) + testassert.NoError(t, err) + { + err := r.deleteSession(ctx, openSession.Identity.SessionID) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "delete open Session = %v, want the archived-only rule", err) } - if err := r.deleteSession(ctx, openSession.Identity.SessionID); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("delete open Session = %v, want the archived-only rule", err) - } - if _, err := os.Stat(filepath.Join(r.dataDir, "code", openSession.Identity.SessionID)); !os.IsNotExist(err) { - t.Fatalf("open delete created or removed artifacts: %v", err) + { + _, err := os.Stat(filepath.Join(r.dataDir, "code", openSession.Identity.SessionID)) + testassert.Require(t, os.IsNotExist(err), "open delete created or removed artifacts: %v", err) } - if _, err := r.archiveSession(ctx, sessionID); err != nil { - t.Fatalf("re-archive: %v", err) - } - if err := r.deleteSession(ctx, sessionID); err != nil { - t.Fatalf("deleteSession: %v", err) + { + _, err := r.archiveSession(ctx, sessionID) + testassert.NoError(t, err) } - if _, err := os.Stat(ownTree); !os.IsNotExist(err) { - t.Fatalf("own artifact tree survived deletion: %v", err) + testassert.NoError(t, r.deleteSession(ctx, sessionID)) + { + _, err := os.Stat(ownTree) + testassert.Require(t, os.IsNotExist(err), "own artifact tree survived deletion: %v", err) } - if _, err := os.Stat(filepath.Join(siblingTree, "snapshots", "1", "f")); err != nil { - t.Fatalf("sibling artifact tree was touched: %v", err) + { + _, err := os.Stat(filepath.Join(siblingTree, "snapshots", "1", "f")) + testassert.NoError(t, err) } - if err := headerErrorThroughRuntime(ctx, r, sessionID); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("deleted by-ID read = %v, want harness.ErrNotFound", err) - } - if err := r.deleteSession(ctx, sessionID); err != nil { - t.Fatalf("repeat delete = %v, want idempotent success", err) + { + err := headerErrorThroughRuntime(ctx, r, sessionID) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "deleted by-ID read = %v, want harness.ErrNotFound", err) } + testassert.NoError(t, r.deleteSession(ctx, sessionID)) }) } @@ -840,8 +703,9 @@ func TestSessionCommandArchiveLiveBackground(t *testing.T) { ctx := context.Background() bg := openBackgroundRuntime(t, store, newLifecycleStopper()) defer func() { - if err := bg.r.Close(ctx); err != nil { - t.Errorf("Close: %v", err) + { + err := bg.r.Close(ctx) + testassert.Check(t, err == nil, "Close: %v", err) } }() @@ -860,23 +724,18 @@ func TestSessionCommandArchiveLiveBackground(t *testing.T) { }) child := bg.launchChild(root, "child work", "op-child", 5) - if _, err := bg.r.archiveSession(ctx, root); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("archive with live background = %v, want the Harness live-member gate", err) + { + _, err := bg.r.archiveSession(ctx, root) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "archive with live background = %v, want the Harness live-member gate", err) } <-childStarted // the child's execution is open before the stop races it - if err := bg.r.stopSession(ctx, root); err != nil { - t.Fatalf("stopSession: %v", err) - } + testassert.NoError(t, bg.r.stopSession(ctx, root)) awaitOperation(t, bg.r, child, "op-child", harness.OperationInterruption) awaitIdleSession(t, bg.r, root) header, err := bg.r.archiveSession(ctx, root) - if err != nil { - t.Fatalf("archive after stop: %v", err) - } - if header.Lifecycle != protocol.Archived { - t.Fatalf("archive after stop = %+v, want archived", header) - } + testassert.NoError(t, err) + testassert.Require(t, header.Lifecycle == protocol.Archived, "archive after stop = %+v, want archived", header) }) } @@ -910,21 +769,19 @@ func TestSessionCommandInterruptStop(t *testing.T) { return nil } }) - if _, err := bg.r.submitSession(ctx, root, commandSubmitRequest(t, "op-int", "run")); err != nil { - t.Fatalf("submit: %v", err) + { + _, err := bg.r.submitSession(ctx, root, commandSubmitRequest(t, "op-int", "run")) + testassert.NoError(t, err) } <-started - if err := bg.r.interruptSession(ctx, root); err != nil { - t.Fatalf("interruptSession: %v", err) - } - if status, err := operationStatusOf(store, root, "op-int"); err != nil || status != harness.OperationRunning { - t.Fatalf("interrupt waited for the terminal: status = (%q, %v), want still running", status, err) + testassert.NoError(t, bg.r.interruptSession(ctx, root)) + { + status, err := operationStatusOf(store, root, "op-int") + testassert.Require(t, err == nil && status == harness.OperationRunning, "interrupt waited for the terminal: status = (%q, %v), want still running", status, err) } close(release) awaitOperation(t, bg.r, root, "op-int", harness.OperationInterruption) - if err := bg.r.interruptSession(ctx, root); err != nil { - t.Fatalf("idle interrupt = %v, want nil", err) - } + testassert.NoError(t, bg.r.interruptSession(ctx, root)) }) t.Run("child stop converges and permanently closes admission", func(t *testing.T) { @@ -945,12 +802,11 @@ func TestSessionCommandInterruptStop(t *testing.T) { }) child := bg.launchChild(root, "child work", "op-child", 5) <-childStarted // the child's execution is open before the stop races it - if err := bg.r.stopSession(ctx, child); err != nil { - t.Fatalf("stopSession(child): %v", err) - } + testassert.NoError(t, bg.r.stopSession(ctx, child)) awaitOperation(t, bg.r, child, "op-child", harness.OperationInterruption) - if _, err := bg.r.submitSession(ctx, child, commandSubmitRequest(t, "op-after", "late")); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("submit to stopped child = %v, want the permanent closure", err) + { + _, err := bg.r.submitSession(ctx, child, commandSubmitRequest(t, "op-after", "late")) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "submit to stopped child = %v, want the permanent closure", err) } }) @@ -985,31 +841,30 @@ func TestSessionCommandInterruptStop(t *testing.T) { } return nil }) - if _, err := bg.r.submitSession(ctx, root, commandSubmitRequest(t, "op-root", "run")); err != nil { - t.Fatalf("root submit: %v", err) + { + _, err := bg.r.submitSession(ctx, root, commandSubmitRequest(t, "op-root", "run")) + testassert.NoError(t, err) } <-started - if res, err := bg.r.submitSession(ctx, root, protocol.SubmitRequest{ - OperationId: "op-wait", - Mode: protocol.SubmitRequestModeQueued, - Content: []protocol.ContentPart{textContentPart(t, "queued", nil)}, - }); err != nil || res.Disposition != protocol.SubmitResultDispositionQueued { - t.Fatalf("queued submit = (%+v, %v), want queued", res, err) + { + res, err := bg.r.submitSession(ctx, root, protocol.SubmitRequest{ + OperationId: "op-wait", + Mode: protocol.SubmitRequestModeQueued, + Content: []protocol.ContentPart{textContentPart(t, "queued", nil)}, + }) + testassert.Require(t, err == nil && res.Disposition == protocol.SubmitResultDispositionQueued, "queued submit = (%+v, %v), want queued", res, err) } child := bg.launchChild(root, "child work", "op-member-child", 5) <-childStarted - if err := bg.r.stopSession(ctx, root); err != nil { - t.Fatalf("stopSession(root): %v", err) - } + testassert.NoError(t, bg.r.stopSession(ctx, root)) awaitOperation(t, bg.r, child, "op-member-child", harness.OperationInterruption) - if status, err := operationStatusOf(store, root, "op-root"); err != nil || status != harness.OperationRunning { - t.Fatalf("root stop touched the root Operation: status = (%q, %v), want still running", status, err) + { + status, err := operationStatusOf(store, root, "op-root") + testassert.Require(t, err == nil && status == harness.OperationRunning, "root stop touched the root Operation: status = (%q, %v), want still running", status, err) } snap := sessionViewThroughRuntime(t, bg.r, root) - if len(snap.Queued) != 1 || snap.Queued[0].OperationID != "op-wait" { - t.Fatalf("root stop touched the pending buffer: %+v", snap.Queued) - } + testassert.Require(t, len(snap.Queued) == 1 && snap.Queued[0].OperationID == "op-wait", "root stop touched the pending buffer: %+v", snap.Queued) close(release) awaitOperation(t, bg.r, root, "op-root", harness.OperationSuccess) @@ -1032,24 +887,14 @@ func TestSessionCommandPreparationError(t *testing.T) { bg.hook.failNext(failure) res, err := bg.r.submitSession(ctx, root, commandSubmitRequest(t, "op-fail", "run")) - if !errors.Is(err, failure) { - t.Fatalf("failing preparation = %v, want the injected error", err) - } - if res.Disposition != "" || res.Operation != nil { - t.Fatalf("failing preparation result = %+v, want the zero result", res) - } + testassert.Require(t, errors.Is(err, failure), "failing preparation = %v, want the injected error", err) + testassert.Require(t, res.Disposition == "" && res.Operation == nil, "failing preparation result = %+v, want the zero result", res) snap := sessionViewThroughRuntime(t, bg.r, root) - if len(snap.Operations) != 0 || snap.Header.CurrentOperationID != "" || len(snap.History.HistoryHeaders()) != 0 { - t.Fatalf("failing preparation committed state: %+v", snap) - } + testassert.Require(t, len(snap.Operations) == 0 && snap.Header.CurrentOperationID == "" && len(snap.History.HistoryHeaders()) == 0, "failing preparation committed state: %+v", snap) ok, err := bg.r.submitSession(ctx, root, commandSubmitRequest(t, "op-ok", "run")) - if err != nil { - t.Fatalf("submit after failure: %v", err) - } - if ok.Disposition != protocol.SubmitResultDispositionAdmitted || ok.Operation == nil { - t.Fatalf("submit after failure = %+v, want admitted", ok) - } + testassert.NoError(t, err) + testassert.Require(t, ok.Disposition == protocol.SubmitResultDispositionAdmitted && ok.Operation != nil, "submit after failure = %+v, want admitted", ok) awaitOperation(t, bg.r, root, "op-ok", harness.OperationSuccess) }) } @@ -1059,52 +904,63 @@ func TestSessionCommandPreparationError(t *testing.T) { func TestSessionCommandClosedOwner(t *testing.T) { eachPrepStore(t, func(t *testing.T, store harness.Storage) { r, e := openProjectionRuntime(t, store) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) ctx := context.Background() sessionID := "0123456789abcdef0123456789abcdef" workspace := filepath.Join(e.home, "closed-ws") req := commandSubmitRequest(t, "op-closed", "x") - if _, err := r.createSessionHeader(ctx, workspace, "solo"); !errors.Is(err, ErrClosed) { - t.Fatalf("closed create header = %v, want ErrClosed", err) + { + _, err := r.createSessionHeader(ctx, workspace, "solo") + testassert.Require(t, errors.Is(err, ErrClosed), "closed create header = %v, want ErrClosed", err) } - if _, err := r.createSession(ctx, workspace, "solo"); !errors.Is(err, ErrClosed) { - t.Fatalf("closed create = %v, want ErrClosed", err) + { + _, err := r.createSession(ctx, workspace, "solo") + testassert.Require(t, errors.Is(err, ErrClosed), "closed create = %v, want ErrClosed", err) } - if _, err := r.listSessions(ctx, protocol.ListSessionsParams{Workspace: workspace, Lifecycle: protocol.Open}); !errors.Is(err, ErrClosed) { - t.Fatalf("closed list = %v, want ErrClosed", err) + { + _, err := r.listSessions(ctx, protocol.ListSessionsParams{Workspace: workspace, Lifecycle: protocol.Open}) + testassert.Require(t, errors.Is(err, ErrClosed), "closed list = %v, want ErrClosed", err) } - if err := headerErrorThroughRuntime(ctx, r, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed read = %v, want ErrClosed", err) + { + err := headerErrorThroughRuntime(ctx, r, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed read = %v, want ErrClosed", err) } - if _, err := r.submitSession(ctx, sessionID, req); !errors.Is(err, ErrClosed) { - t.Fatalf("closed submit = %v, want ErrClosed", err) + { + _, err := r.submitSession(ctx, sessionID, req) + testassert.Require(t, errors.Is(err, ErrClosed), "closed submit = %v, want ErrClosed", err) } - if _, err := r.compactSession(ctx, sessionID, protocol.CompactRequest{OperationId: "op-closed"}); !errors.Is(err, ErrClosed) { - t.Fatalf("closed compact = %v, want ErrClosed", err) + { + _, err := r.compactSession(ctx, sessionID, protocol.CompactRequest{OperationId: "op-closed"}) + testassert.Require(t, errors.Is(err, ErrClosed), "closed compact = %v, want ErrClosed", err) } - if _, err := r.forkSession(ctx, sessionID, protocol.ForkRequest{BoundaryItemId: "x", OperationId: "op-closed", Content: req.Content}); !errors.Is(err, ErrClosed) { - t.Fatalf("closed fork = %v, want ErrClosed", err) + { + _, err := r.forkSession(ctx, sessionID, protocol.ForkRequest{BoundaryItemId: "x", OperationId: "op-closed", Content: req.Content}) + testassert.Require(t, errors.Is(err, ErrClosed), "closed fork = %v, want ErrClosed", err) } - if _, err := r.setSessionAgentType(ctx, sessionID, protocol.SetSessionAgentTypeRequest{AgentType: "solo"}); !errors.Is(err, ErrClosed) { - t.Fatalf("closed agent type = %v, want ErrClosed", err) + { + _, err := r.setSessionAgentType(ctx, sessionID, protocol.SetSessionAgentTypeRequest{AgentType: "solo"}) + testassert.Require(t, errors.Is(err, ErrClosed), "closed agent type = %v, want ErrClosed", err) } - if _, err := r.archiveSession(ctx, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed archive = %v, want ErrClosed", err) + { + _, err := r.archiveSession(ctx, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed archive = %v, want ErrClosed", err) } - if _, err := r.reopenSession(ctx, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed reopen = %v, want ErrClosed", err) + { + _, err := r.reopenSession(ctx, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed reopen = %v, want ErrClosed", err) } - if err := r.interruptSession(ctx, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed interrupt = %v, want ErrClosed", err) + { + err := r.interruptSession(ctx, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed interrupt = %v, want ErrClosed", err) } - if err := r.stopSession(ctx, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed stop = %v, want ErrClosed", err) + { + err := r.stopSession(ctx, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed stop = %v, want ErrClosed", err) } - if err := r.deleteSession(ctx, sessionID); !errors.Is(err, ErrClosed) { - t.Fatalf("closed delete = %v, want ErrClosed", err) + { + err := r.deleteSession(ctx, sessionID) + testassert.Require(t, errors.Is(err, ErrClosed), "closed delete = %v, want ErrClosed", err) } }) } @@ -1146,9 +1002,7 @@ func TestSessionCommandCommitJoinsClose(t *testing.T) { break } release() - if time.Now().After(deadline) { - t.Fatal("Close never closed admission") - } + testassert.Require(t, !time.Now().After(deadline), "Close never closed admission") time.Sleep(time.Millisecond) } select { @@ -1164,14 +1018,7 @@ func TestSessionCommandCommitJoinsClose(t *testing.T) { gated.releaseGate() got := <-done - if got.err != nil { - t.Fatalf("admitted submit after gate release = %v, want the committed response", got.err) - } - if got.res.Operation == nil || got.res.Operation.OperationId != "op-commit" { - t.Fatalf("admitted submit result = %+v, want the committed Operation", got.res) - } - if err := <-closeDone; err != nil { - t.Fatalf("Close after the admitted response: %v", err) - } + testassert.Require(t, got.err == nil && got.res.Operation != nil && got.res.Operation.OperationId == "op-commit", "admitted submit after gate release = %v, want the committed response; admitted submit result = %+v, want the committed Operation", got.err, got.res) + testassert.NoError(t, <-closeDone) }) } diff --git a/runtime/protocol_composed_loss_test.go b/runtime/protocol_composed_loss_test.go index 28360135..74bbc29e 100644 --- a/runtime/protocol_composed_loss_test.go +++ b/runtime/protocol_composed_loss_test.go @@ -23,6 +23,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -45,10 +46,8 @@ func TestProtocolComposedRuntimeLoss(t *testing.T) { return c }} ws := &workspaceOpens{} - r, err := Open(ctx, Options{DataDir: e.dataDir, ConfigPath: e.configPath, Plugins: assembledPlugins(e, store, hook, ws)}) - if err != nil { - t.Fatalf("successor Open: %v", err) - } + r, err := e.open(ctx, assembledPlugins(e, store, hook, ws)...) + testassert.NoError(t, err) ps := openProtocolServer(t, r) client := protocolClient(t, ps) // The stream opens before the recovered Sessions are ever touched, so @@ -66,39 +65,27 @@ func TestProtocolComposedRuntimeLoss(t *testing.T) { // exact-match on the recorded trace, so the fixture's probe tool's // real trace is spelled out: one invocation during recovery fails this // oracle even while the model counter still reads zero. - if got := server.requests(); got != 0 { - t.Fatalf("model requests during recovery = %d, want none", got) - } - if n := countEvents(e.events.all(), "work:L1:base"); n != 0 { - t.Fatalf("probe-tool invocations during recovery = %d, want none", n) - } + testassert.Equal(t, server.requests(), 0) + testassert.Equal(t, countEvents(e.events.all(), "work:L1:base"), 0) // Durable interruption through the mounted read: the seeded running // root settled as the runtime-loss interruption with no live member. rootHydration := composedLossHydration(t, client, runningRoot) - if op := composedLossOperation(t, rootHydration, runningOp); op.Status != protocol.OperationStatusInterruption { - t.Fatalf("seeded running root operation = %q, want the interruption repair", op.Status) - } - if rootHydration.ActiveOperation != nil || len(rootHydration.Background) != 0 { - t.Fatalf("seeded root hydration = active %+v background %+v, want no active work and no member", rootHydration.ActiveOperation, rootHydration.Background) - } - if rootHydration.SessionRevision.InstanceId != ps.instance { - t.Fatalf("seeded root revision instance = %q, want the successor's %q", rootHydration.SessionRevision.InstanceId, ps.instance) + { + op := composedLossOperation(t, rootHydration, runningOp) + testassert.Equal(t, op.Status, protocol.OperationStatusInterruption) } + testassert.Require(t, rootHydration.ActiveOperation == nil && len(rootHydration.Background) == 0 && rootHydration.SessionRevision.InstanceId == ps.instance, "seeded root hydration = active %+v background %+v, want no active work and no member; got %v, want %v", rootHydration.ActiveOperation, rootHydration.Background, rootHydration.SessionRevision.InstanceId, ps.instance) // The child hydrates as one ordinary Session with its own interrupted // Operation and its committed input visible. childHydration := composedLossHydration(t, client, child) - if op := composedLossOperation(t, childHydration, childOp); op.Status != protocol.OperationStatusInterruption { - t.Fatalf("seeded child operation = %q, want the interruption repair", op.Status) - } - if childHydration.Session.SessionId != child || childHydration.Session.Lifecycle != protocol.Open || - childHydration.ActiveOperation != nil || len(childHydration.Background) != 0 { - t.Fatalf("seeded child hydration = %+v, want one ordinary open session", childHydration.Session) - } - if len(childHydration.Conversation.Items) == 0 { - t.Fatal("the seeded child's committed input vanished from its hydration") + { + op := composedLossOperation(t, childHydration, childOp) + testassert.Equal(t, op.Status, protocol.OperationStatusInterruption) } + testassert.Require(t, childHydration.Session.SessionId == child && childHydration.Session.Lifecycle == protocol.Open && childHydration.ActiveOperation == nil && len(childHydration.Background) == 0, "seeded child hydration = %+v, want one ordinary open session", childHydration.Session) + testassert.Require(t, len(childHydration.Conversation.Items) != 0, "the seeded child's committed input vanished from its hydration") // No replayed facts reach the fresh stream: fresh work — created and // submitted through the generated operations — is read until its own @@ -106,56 +93,41 @@ func TestProtocolComposedRuntimeLoss(t *testing.T) { // witness carries only the fresh Session and Workspace identities. freshWorkspace := filepath.Join(e.home, "loss-next") created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: freshWorkspace, AgentType: "integrated"}) - if err != nil { - t.Fatalf("CreateSession(fresh): %v", err) - } - if created.JSON201 == nil { - t.Fatalf("CreateSession(fresh) = status %d: %s", created.HTTPResponse.StatusCode, created.Body) - } + testassert.NoError(t, err) + testassert.Require(t, created.JSON201 != nil, "CreateSession(fresh) = status %d: %s", created.HTTPResponse.StatusCode, created.Body) fresh := created.JSON201.SessionId var freshPart protocol.ContentPart - if err := freshPart.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: "continue"}); err != nil { - t.Fatalf("build text part: %v", err) - } + testassert.NoError(t, freshPart.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: "continue"})) submitted, err := client.SubmitSessionWithResponse(ctx, fresh, protocol.SubmitRequest{ OperationId: "op-after-loss", Mode: protocol.SubmitRequestModeRegular, Content: []protocol.ContentPart{freshPart}, }) - if err != nil { - t.Fatalf("SubmitSession(fresh): %v", err) - } - if submitted.JSON200 == nil { - t.Fatalf("SubmitSession(fresh) = status %d: %s", submitted.HTTPResponse.StatusCode, submitted.Body) - } + testassert.NoError(t, err) + testassert.Require(t, submitted.JSON200 != nil, "SubmitSession(fresh) = status %d: %s", submitted.HTTPResponse.StatusCode, submitted.Body) for { frame, ok, err := stream.readSSEFrame() - if err != nil { - t.Fatalf("fresh stream read: %v", err) - } - if !ok { - t.Fatal("the fresh stream closed before the fresh work's witness") - } + testassert.NoError(t, err) + testassert.Require(t, ok, "the fresh stream closed before the fresh work's witness") var event protocol.Event - if err := json.Unmarshal(frame, &event); err != nil { - t.Fatalf("decode fresh stream frame %s: %v", frame, err) + { + err := json.Unmarshal(frame, &event) + testassert.Require(t, err == nil, "decode fresh stream frame %s: %v", frame, err) } scope, err := composedLossEventScope(t, event) - if err != nil { - t.Fatalf("fresh stream frame scope: %v", err) + testassert.NoError(t, err) + { + stale, ok := scopeSessionIdentity(t, scope) + testassert.Require(t, !ok || stale == fresh, "replayed frame for session %s reached the fresh stream: %s", stale, frame) } - if stale, ok := scopeSessionIdentity(t, scope); ok && stale != fresh { - t.Fatalf("replayed frame for session %s reached the fresh stream: %s", stale, frame) - } - if stale, ok := scopeWorkspaceAttribution(t, scope); ok && stale != freshWorkspace { - t.Fatalf("replayed frame for workspace %s reached the fresh stream: %s", stale, frame) + { + stale, ok := scopeWorkspaceAttribution(t, scope) + testassert.Require(t, !ok || stale == freshWorkspace, "replayed frame for workspace %s reached the fresh stream: %s", stale, frame) } if body, err := event.AsSessionChangedEvent(); err == nil && scopeKind(t, body.Scope) == "session" { freshScope, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("fresh invalidation scope: %v", err) - } + testassert.NoError(t, err) if freshScope.SessionId == fresh { break // the fresh work's own invalidation is the witness } @@ -165,21 +137,22 @@ func TestProtocolComposedRuntimeLoss(t *testing.T) { // Ordinary work proceeds on the repaired state through the mounted // route: the successor's first model requests belong to fresh work. composedLossAwaitSettled(t, client, fresh, "op-after-loss") - if got := server.requests(); got < 1 { - t.Fatalf("the fresh turn's model requests = %d, want the real execution", got) + { + got := server.requests() + testassert.Require(t, got >= 1, "the fresh turn's model requests = %d, want the real execution", got) } settled := composedLossHydration(t, client, fresh) - if op := composedLossOperation(t, settled, "op-after-loss"); op.Status != protocol.OperationStatusSuccess { - t.Fatalf("the fresh mounted operation = %q, want success", op.Status) + { + op := composedLossOperation(t, settled, "op-after-loss") + testassert.Equal(t, op.Status, protocol.OperationStatusSuccess) } // The closed owner: the listener refuses connections. stream.close() - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - if _, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")); err == nil { - t.Fatal("the protocol listener still accepts after the successor closed") + testassert.NoError(t, r.Close(ctx)) + { + _, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")) + testassert.Require(t, err != nil, "the protocol listener still accepts after the successor closed") } }) } @@ -187,12 +160,8 @@ func TestProtocolComposedRuntimeLoss(t *testing.T) { func composedLossHydration(t *testing.T, client *protocol.ClientWithResponses, sessionID string) *protocol.Hydration { t.Helper() resp, err := client.GetSessionHydrationWithResponse(context.Background(), sessionID) - if err != nil { - t.Fatalf("GetSessionHydration(%s): %v", sessionID, err) - } - if resp.JSON200 == nil { - t.Fatalf("GetSessionHydration(%s) = status %d", sessionID, resp.HTTPResponse.StatusCode) - } + testassert.Require(t, err == nil, "GetSessionHydration(%s): %v", sessionID, err) + testassert.Require(t, resp.JSON200 != nil, "GetSessionHydration(%s) = status %d", sessionID, resp.HTTPResponse.StatusCode) return resp.JSON200 } @@ -208,9 +177,7 @@ func composedLossAwaitSettled(t *testing.T, client *protocol.ClientWithResponses return } } - if time.Now().After(deadline) { - t.Fatalf("operation %q never settled to success (operations %+v)", operationID, h.Operations) - } + testassert.Require(t, !time.Now().After(deadline), "operation %q never settled to success (operations %+v)", operationID, h.Operations) time.Sleep(10 * time.Millisecond) } } diff --git a/runtime/protocol_composed_test.go b/runtime/protocol_composed_test.go index e40f63c2..6938f251 100644 --- a/runtime/protocol_composed_test.go +++ b/runtime/protocol_composed_test.go @@ -35,6 +35,7 @@ import ( "github.com/MMinasyan/lightcode/internal/plugins/tasks" "github.com/MMinasyan/lightcode/internal/plugins/tools" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/plugins/jobs" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" @@ -308,11 +309,10 @@ func (s *composedStream) count(match func(protocol.Event) bool) int { // composedWriteFile writes one configuration file with its owning directory. func composedWriteFile(t *testing.T, path, content string) { t.Helper() - if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { - t.Fatalf("create config directory: %v", err) - } - if err := os.WriteFile(path, []byte(content), 0o600); err != nil { - t.Fatalf("write %s: %v", path, err) + testassert.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + { + err := os.WriteFile(path, []byte(content), 0o600) + testassert.Require(t, err == nil, "write %s: %v", path, err) } } @@ -320,9 +320,7 @@ func composedWriteFile(t *testing.T, path, content string) { func composedDiscriminator(t *testing.T, event protocol.Event) string { t.Helper() kind, err := event.Discriminator() - if err != nil { - t.Fatalf("event discriminator: %v", err) - } + testassert.NoError(t, err) return kind } @@ -355,12 +353,8 @@ func composedIsJobHintFor(t *testing.T, jobID string) func(protocol.Event) bool func composedHydrate(t *testing.T, c *client.Client, sessionID string) *protocol.Hydration { t.Helper() resp, err := c.GetSessionHydrationWithResponse(context.Background(), sessionID) - if err != nil { - t.Fatalf("GetSessionHydration(%s): %v", sessionID, err) - } - if resp.JSON200 == nil { - t.Fatalf("GetSessionHydration(%s) = status %d: %s", sessionID, resp.HTTPResponse.StatusCode, resp.Body) - } + testassert.Require(t, err == nil, "GetSessionHydration(%s): %v", sessionID, err) + testassert.Require(t, resp.JSON200 != nil, "GetSessionHydration(%s) = status %d: %s", sessionID, resp.HTTPResponse.StatusCode, resp.Body) return resp.JSON200 } @@ -390,9 +384,7 @@ func composedAwaitOp(t *testing.T, c *client.Client, sessionID, operationID stri return h } } - if time.Now().After(deadline) { - t.Fatalf("operation %q never settled to %q (operations %+v)", operationID, want, h.Operations) - } + testassert.Require(t, !time.Now().After(deadline), "operation %q never settled to %q (operations %+v)", operationID, want, h.Operations) time.Sleep(10 * time.Millisecond) } } @@ -414,9 +406,7 @@ func composedAwaitQuiet(t *testing.T, c *client.Client, sessionID string, backgr if quiet { return h } - if time.Now().After(deadline) { - t.Fatalf("session %q never went quiet (active %+v, background %+v, operations %+v)", sessionID, h.ActiveOperation, h.Background, h.Operations) - } + testassert.Require(t, !time.Now().After(deadline), "session %q never went quiet (active %+v, background %+v, operations %+v)", sessionID, h.ActiveOperation, h.Background, h.Operations) time.Sleep(10 * time.Millisecond) } } @@ -424,13 +414,9 @@ func composedAwaitQuiet(t *testing.T, c *client.Client, sessionID string, backgr // composedItemText returns an input item's first text part. func composedItemText(t *testing.T, item protocol.InputItem) string { t.Helper() - if len(item.Content) == 0 { - t.Fatalf("input item %s carries no content", item.ItemId) - } + testassert.Require(t, len(item.Content) != 0, "input item %s carries no content", item.ItemId) part, err := item.Content[0].AsTextPart() - if err != nil { - t.Fatalf("input item text part: %v", err) - } + testassert.NoError(t, err) return part.Text } @@ -442,9 +428,7 @@ func composedAssistantOf(t *testing.T, items []protocol.ConversationItem, operat continue } assistant, err := item.AsAssistantItem() - if err != nil { - t.Fatalf("assistant item: %v", err) - } + testassert.NoError(t, err) if assistant.OperationId != nil && *assistant.OperationId == operationID { return assistant } @@ -456,9 +440,7 @@ func composedAssistantOf(t *testing.T, items []protocol.ConversationItem, operat func composedItemKind(t *testing.T, item protocol.ConversationItem) string { t.Helper() kind, err := item.Discriminator() - if err != nil { - t.Fatalf("item discriminator: %v", err) - } + testassert.NoError(t, err) return kind } @@ -467,20 +449,14 @@ func composedItemKind(t *testing.T, item protocol.ConversationItem) string { func composedSubmit(t *testing.T, c *client.Client, sessionID, operationID, text string) { t.Helper() var part protocol.ContentPart - if err := part.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: text}); err != nil { - t.Fatalf("build text part: %v", err) - } + testassert.NoError(t, part.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: text})) resp, err := c.SubmitSessionWithResponse(context.Background(), sessionID, protocol.SubmitRequest{ OperationId: operationID, Mode: protocol.SubmitRequestModeRegular, Content: []protocol.ContentPart{part}, }) - if err != nil { - t.Fatalf("SubmitSession(%s): %v", operationID, err) - } - if resp.JSON200 == nil { - t.Fatalf("SubmitSession(%s) = status %d: %s", operationID, resp.HTTPResponse.StatusCode, resp.Body) - } + testassert.Require(t, err == nil, "SubmitSession(%s): %v", operationID, err) + testassert.Require(t, resp.JSON200 != nil, "SubmitSession(%s) = status %d: %s", operationID, resp.HTTPResponse.StatusCode, resp.Body) } // composedReserveEnv starts one environment name unset and restores its @@ -518,21 +494,13 @@ func TestProtocolComposedFlow(t *testing.T) { composedWriteFile(t, agents.PathForConfig(configPath), composedFlowAgentsDocument) r, err := runtime.Open(ctx, runtime.Options{DataDir: dataDir, ConfigPath: configPath, Plugins: plugins}) - if err != nil { - t.Fatalf("runtime.Open: %v", err) - } + testassert.NoError(t, err) ps, err := r.OpenProtocol(ctx) - if err != nil { - t.Fatalf("OpenProtocol: %v", err) - } + testassert.NoError(t, err) record := filepath.Join(t.TempDir(), "discovery.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(record)) c, err := client.Connect(ctx, record) - if err != nil { - t.Fatalf("client.Connect: %v", err) - } + testassert.NoError(t, err) defer func() { _ = r.Close(ctx) }() // The unauthorized-transport sibling: a strict record carrying a @@ -541,8 +509,9 @@ func TestProtocolComposedFlow(t *testing.T) { foreign := filepath.Join(t.TempDir(), "foreign.json") wrongCredential := strings.Repeat("0", 64) composedWriteFile(t, foreign, fmt.Sprintf(`{"endpoint":%q,"instance_id":%q,"protocol_version":%q,"credential":%q}`, ps.Endpoint(), c.InstanceID(), protocol.N2, wrongCredential)) - if _, err := client.Connect(ctx, foreign); !errors.Is(err, client.ErrNoRuntime) { - t.Fatalf("wrong-credential connect = %v, want ErrNoRuntime", err) + { + _, err := client.Connect(ctx, foreign) + testassert.Require(t, errors.Is(err, client.ErrNoRuntime), "wrong-credential connect = %v, want ErrNoRuntime", err) } streamCtx, cancelStream := context.WithCancel(ctx) @@ -552,12 +521,8 @@ func TestProtocolComposedFlow(t *testing.T) { // 1. Create and the real model/tool turn with a captured code group. wsMain := filepath.Join(home, "main-ws") created, err := c.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: wsMain, AgentType: "main"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if created.JSON201 == nil { - t.Fatalf("CreateSession = status %d: %s", created.HTTPResponse.StatusCode, created.Body) - } + testassert.NoError(t, err) + testassert.Require(t, created.JSON201 != nil, "CreateSession = status %d: %s", created.HTTPResponse.StatusCode, created.Body) main := created.JSON201.SessionId if created.JSON201.SessionRevision.InstanceId != c.InstanceID() { t.Fatalf("created revision instance = %q, want %q", created.JSON201.SessionRevision.InstanceId, c.InstanceID()) @@ -566,18 +531,12 @@ func TestProtocolComposedFlow(t *testing.T) { composedSubmit(t, c, main, "op-write-1", "write v1") h := composedAwaitOp(t, c, main, "op-write-1", protocol.OperationStatusSuccess) data, err := os.ReadFile(filepath.Join(wsMain, "composed.txt")) - if err != nil || string(data) != "v1" { - t.Fatalf("composed.txt = (%q, %v), want the real tool effect v1", data, err) - } + testassert.Require(t, err == nil && string(data) == "v1", "composed.txt = (%q, %v), want the real tool effect v1", data, err) assistant := composedAssistantOf(t, h.Conversation.Items, "op-write-1") // The assistant is indivisible with its published call: a terminal // success status and present nonempty content, without pinning the // exact result wording. - if len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].Name != "write_file" || - assistant.ToolCalls[0].Status == nil || *assistant.ToolCalls[0].Status != protocol.ToolCallStatusSuccess || - assistant.ToolCalls[0].Content == nil || *assistant.ToolCalls[0].Content == "" { - t.Fatalf("op-write-1 assistant tool calls = %+v, want one successful write_file call with nonempty terminal content", assistant.ToolCalls) - } + testassert.Require(t, len(assistant.ToolCalls) == 1 && assistant.ToolCalls[0].Name == "write_file" && assistant.ToolCalls[0].Status != nil && *assistant.ToolCalls[0].Status == protocol.ToolCallStatusSuccess && assistant.ToolCalls[0].Content != nil && *assistant.ToolCalls[0].Content != "", "op-write-1 assistant tool calls = %+v, want one successful write_file call with nonempty terminal content", assistant.ToolCalls) opWriteInput := protocol.InputItem{} found := false for _, item := range h.Conversation.Items { @@ -585,21 +544,15 @@ func TestProtocolComposedFlow(t *testing.T) { continue } input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } + testassert.NoError(t, err) if input.OperationId != nil && *input.OperationId == "op-write-1" { - if text := composedItemText(t, input); text != "write v1" { - t.Fatalf("op-write-1 input text = %q, want the exact submitted text", text) - } + testassert.Equal(t, composedItemText(t, input), "write v1") opWriteInput = input found = true break } } - if !found { - t.Fatal("no input item owned by op-write-1") - } + testassert.Require(t, found, "no input item owned by op-write-1") // The SSE stream observed the real progress and invalidation of the // same operation. @@ -611,16 +564,10 @@ func TestProtocolComposedFlow(t *testing.T) { return err == nil && body.Name == "write_file" }, "write_file tool_started") startedBody, err := started.AsToolStartedEvent() - if err != nil { - t.Fatalf("tool_started body: %v", err) - } - if composedScopeKind(startedBody.Scope) != "operation" { - t.Fatalf("write_file tool_started scope = %+v, want the operation scope of the real session", startedBody.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, composedScopeKind(startedBody.Scope) == "operation", "write_file tool_started scope = %+v, want the operation scope of the real session", startedBody.Scope) startedScope, err := startedBody.Scope.AsOperationScope() - if err != nil || startedScope.SessionId != main { - t.Fatalf("write_file tool_started scope = %+v, want the operation scope of the real session", startedBody.Scope) - } + testassert.Require(t, err == nil && startedScope.SessionId == main, "write_file tool_started scope = %+v, want the operation scope of the real session", startedBody.Scope) stream.wait(func(event protocol.Event) bool { if composedDiscriminator(t, event) != "tool_finished" { return false @@ -640,21 +587,15 @@ func TestProtocolComposedFlow(t *testing.T) { return err == nil && session.SessionId == main }, "the settled operation's session invalidation") sessionHint, err := invalidation.AsSessionChangedEvent() - if err != nil { - t.Fatalf("session_changed body: %v", err) - } + testassert.NoError(t, err) if sessionHint.SessionRevision.InstanceId != c.InstanceID() { t.Fatalf("session invalidation revision instance = %q, want %q", sessionHint.SessionRevision.InstanceId, c.InstanceID()) } // The recorded code group is listed with the projected input item. groups, err := c.GetSessionCodeSnapshotsWithResponse(ctx, main) - if err != nil { - t.Fatalf("GetSessionCodeSnapshots: %v", err) - } - if groups.JSON200 == nil { - t.Fatalf("GetSessionCodeSnapshots = status %d: %s", groups.HTTPResponse.StatusCode, groups.Body) - } + testassert.NoError(t, err) + testassert.Require(t, groups.JSON200 != nil, "GetSessionCodeSnapshots = status %d: %s", groups.HTTPResponse.StatusCode, groups.Body) var writeGroup *protocol.CodeSnapshotGroup for i, group := range *groups.JSON200 { if group.OperationId == "op-write-1" { @@ -662,52 +603,43 @@ func TestProtocolComposedFlow(t *testing.T) { } } composedDisplay := filepath.Join(wsMain, "composed.txt") - if writeGroup == nil || len(writeGroup.Files) != 1 || writeGroup.Files[0].Path != composedDisplay || writeGroup.Files[0].Existed { - t.Fatalf("op-write-1 code group = %+v, want the created %s file", writeGroup, composedDisplay) - } + testassert.Require(t, writeGroup != nil && len(writeGroup.Files) == 1 && writeGroup.Files[0].Path == composedDisplay && !writeGroup.Files[0].Existed, "op-write-1 code group = %+v, want the created %s file", writeGroup, composedDisplay) // The group's boundary is the projected admitted input item, never a // raw entry identity. - if writeGroup.GroupItemId != opWriteInput.ItemId { - t.Fatalf("group item id = %q, want the projected admitted input item %q", writeGroup.GroupItemId, opWriteInput.ItemId) - } + testassert.Equal(t, writeGroup.GroupItemId, opWriteInput.ItemId) composedSubmit(t, c, main, "op-write-2", "write v2") composedAwaitOp(t, c, main, "op-write-2", protocol.OperationStatusSuccess) - if data, _ := os.ReadFile(filepath.Join(wsMain, "composed.txt")); string(data) != "v2" { - t.Fatalf("composed.txt after the second write = %q, want v2", data) + { + data, _ := os.ReadFile(filepath.Join(wsMain, "composed.txt")) + testassert.Require(t, string(data) == "v2", "composed.txt after the second write = %q, want v2", data) } // 2. Identity-safe rewind: the external change is skipped with its // reason and never overwritten; the recorded state restores. edited := composedDisplay - if err := os.WriteFile(edited, []byte("external"), 0o600); err != nil { - t.Fatalf("external edit: %v", err) - } + testassert.NoError(t, os.WriteFile(edited, []byte("external"), 0o600)) skippedRevert, err := c.RevertSessionCodeWithResponse(ctx, main, protocol.RevertCodeRequest{BoundaryOperationId: "op-write-2"}) - if err != nil { - t.Fatalf("RevertSessionCode(external): %v", err) - } + testassert.NoError(t, err) if skippedRevert.JSON200 == nil || len(skippedRevert.JSON200.Restored) != 0 || len(skippedRevert.JSON200.Skipped) != 1 || skippedRevert.JSON200.Skipped[0].Path != composedDisplay || !strings.Contains(skippedRevert.JSON200.Skipped[0].Reason, "changed") { body, _ := json.Marshal(skippedRevert.JSON200) t.Fatalf("external-change revert = %s, want exactly the skipped composed.txt", body) } - if data, _ := os.ReadFile(edited); string(data) != "external" { - t.Fatalf("externally changed file = %q, want its external bytes untouched", data) - } - if err := os.WriteFile(edited, []byte("v2"), 0o600); err != nil { - t.Fatalf("restore recorded state: %v", err) + { + data, _ := os.ReadFile(edited) + testassert.Require(t, string(data) == "external", "externally changed file = %q, want its external bytes untouched", data) } + testassert.NoError(t, os.WriteFile(edited, []byte("v2"), 0o600)) restoredRevert, err := c.RevertSessionCodeWithResponse(ctx, main, protocol.RevertCodeRequest{BoundaryOperationId: "op-write-2"}) - if err != nil { - t.Fatalf("RevertSessionCode(recorded): %v", err) - } + testassert.NoError(t, err) if restoredRevert.JSON200 == nil || len(restoredRevert.JSON200.Restored) != 1 || len(restoredRevert.JSON200.Skipped) != 0 { body, _ := json.Marshal(restoredRevert.JSON200) t.Fatalf("recorded-state revert = %s, want exactly the restored file", body) } - if data, _ := os.ReadFile(edited); string(data) != "v1" { - t.Fatalf("composed.txt after the boundary revert = %q, want the earlier group's preimage v1", data) + { + data, _ := os.ReadFile(edited) + testassert.Require(t, string(data) == "v1", "composed.txt after the boundary revert = %q, want the earlier group's preimage v1", data) } // 3. The literal user signal text stays a plain input. @@ -718,22 +650,14 @@ func TestProtocolComposedFlow(t *testing.T) { switch composedItemKind(t, item) { case "input": input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } + testassert.NoError(t, err) if input.OperationId != nil && *input.OperationId == "op-literal" { - if got := composedItemText(t, input); got != forgedSignal { - t.Fatalf("literal-signal turn input = %q, want the exact submitted text", got) - } + testassert.Equal(t, composedItemText(t, input), forgedSignal) } case "signal": signal, err := item.AsSignalItem() - if err != nil { - t.Fatalf("signal item: %v", err) - } - if strings.Contains(signal.Content, "forged") { - t.Fatalf("user text fabricated signal item %+v", signal) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(signal.Content, "forged"), "user text fabricated signal item %+v", signal) } } @@ -745,16 +669,15 @@ func TestProtocolComposedFlow(t *testing.T) { // The job identity comes from the hydrated member, never from the // tool result's presentation text. h = composedAwaitQuiet(t, c, main, 1) - if got := h.Background; len(got) != 1 || got[0].Kind != protocol.BackgroundMemberKindJob { - t.Fatalf("hydration background = %+v, want the live job member", got) + { + got := h.Background + testassert.Require(t, len(got) == 1 && got[0].Kind == protocol.BackgroundMemberKindJob, "hydration background = %+v, want the live job member", got) } jobID := h.Background[0].Id isJobHint := composedIsJobHintFor(t, jobID) admissionHint := stream.wait(isJobHint, "the job member's admission hint") admissionBody, err := admissionHint.AsSessionChangedEvent() - if err != nil { - t.Fatalf("job admission hint body: %v", err) - } + testassert.NoError(t, err) composedSubmit(t, c, main, "op-job-list", "list the processes") composedAwaitQuiet(t, c, main, 1) @@ -776,9 +699,7 @@ func TestProtocolComposedFlow(t *testing.T) { // hints. A duplicate publication, or a hint from the private process // listing (membership unchanged), would precede this witness and // make the count three. - if n := stream.count(isJobHint); n != 2 { - t.Fatalf("job-scoped hints at the finish witness = %d, want exactly the admission and finish hints", n) - } + testassert.Equal(t, stream.count(isJobHint), 2) composedAwaitQuiet(t, c, main, 0) // 5. The child Session runs its own turn and hydrates as an @@ -797,9 +718,7 @@ func TestProtocolComposedFlow(t *testing.T) { childID = member.Id } } - if childID == "" && time.Now().After(deadline) { - t.Fatalf("the child member never appeared: %+v", h.Background) - } + testassert.Require(t, childID != "" || !time.Now().After(deadline), "the child member never appeared: %+v", h.Background) time.Sleep(10 * time.Millisecond) } stream.wait(func(event protocol.Event) bool { @@ -824,28 +743,16 @@ func TestProtocolComposedFlow(t *testing.T) { continue } input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } + testassert.NoError(t, err) if input.Origin == protocol.InputOriginRuntime && composedItemText(t, input) == "child finished" { completionInput = true } } - if !completionInput { - t.Fatal("no runtime-origin completion input carrying the child's final text") - } + testassert.Require(t, completionInput, "no runtime-origin completion input carrying the child's final text") childHydration := composedHydrate(t, c, childID) - if childHydration.Session.SessionId != childID || childHydration.Session.Lifecycle != protocol.Open || - childHydration.ActiveOperation != nil || len(childHydration.Background) != 0 || len(childHydration.Operations) == 0 { - t.Fatalf("child hydration = %+v, want one ordinary open session with its own settled operations", childHydration.Session) - } - if len(childHydration.Conversation.Items) < 2 { - t.Fatalf("child conversation items = %+v, want its own input and assistant items", childHydration.Conversation.Items) - } + testassert.Require(t, childHydration.Session.SessionId == childID && childHydration.Session.Lifecycle == protocol.Open && childHydration.ActiveOperation == nil && len(childHydration.Background) == 0 && len(childHydration.Operations) != 0 && len(childHydration.Conversation.Items) >= 2, "child hydration = %+v, want one ordinary open session with its own settled operations; child conversation items = %+v, want its own input and assistant items", childHydration.Session, childHydration.Conversation.Items) for _, operation := range childHydration.Operations { - if operation.Status != protocol.OperationStatusSuccess { - t.Fatalf("child operation %s = %q, want success", operation.OperationId, operation.Status) - } + testassert.Require(t, operation.Status == protocol.OperationStatusSuccess, "child operation %s = %q, want success", operation.OperationId, operation.Status) } // The child's GET/history matches its hydration: both mounted reads // answer from the child's own one snapshot, with the same item @@ -853,19 +760,13 @@ func TestProtocolComposedFlow(t *testing.T) { childHistory := composedHistory(t, c, childID, nil) historyIDs := composedPageIDs(t, childHistory.Items) hydrationIDs := composedPageIDs(t, childHydration.Conversation.Items) - if strings.Join(historyIDs, ",") != strings.Join(hydrationIDs, ",") { - t.Fatalf("child history items %v, want its hydration's conversation %v", historyIDs, hydrationIDs) - } - if childHistory.SessionRevision != childHydration.SessionRevision { - t.Fatalf("child history revision = %+v, want the hydration's %+v", childHistory.SessionRevision, childHydration.SessionRevision) - } + testassert.Require(t, strings.Join(historyIDs, ",") == strings.Join(hydrationIDs, ","), "child history items %v, want its hydration's conversation %v", historyIDs, hydrationIDs) + testassert.Equal(t, childHistory.SessionRevision, childHydration.SessionRevision) // 6. Older pages: fill past the 50-item boundary, then the anchored // mechanics and the cross-Session cursor refusal. for i := 0; ; i++ { - if i > 40 { - t.Fatal("the history never crossed the 50-item page boundary") - } + testassert.Require(t, i <= 40, "the history never crossed the 50-item page boundary") page := composedHistory(t, c, main, nil) if len(page.Items) == 50 && page.OlderCursor != nil { break @@ -876,42 +777,29 @@ func TestProtocolComposedFlow(t *testing.T) { composedAwaitQuiet(t, c, main, 0) } first := composedHistory(t, c, main, nil) - if len(first.Items) != 50 || first.OlderCursor == nil { - t.Fatalf("initial page = %d items (cursor %v), want exactly 50 with an older cursor", len(first.Items), first.OlderCursor) - } + testassert.Require(t, len(first.Items) == 50 && first.OlderCursor != nil, "initial page = %d items (cursor %v), want exactly 50 with an older cursor", len(first.Items), first.OlderCursor) older := composedHistory(t, c, main, first.OlderCursor) - if len(older.Items) == 0 || older.OlderCursor != nil { - t.Fatalf("older page = %d items (cursor %v), want the remaining prefix", len(older.Items), older.OlderCursor) - } + testassert.Require(t, len(older.Items) != 0 && older.OlderCursor == nil, "older page = %d items (cursor %v), want the remaining prefix", len(older.Items), older.OlderCursor) olderIDs := composedPageIDs(t, older.Items) composedSubmit(t, c, main, "op-after-anchor", "after the anchor") composedAwaitOp(t, c, main, "op-after-anchor", protocol.OperationStatusSuccess) newest := composedHistory(t, c, main, nil) - if newest.SessionRevision == first.SessionRevision { - t.Fatalf("newest page revision = %+v, want the advanced current revision", newest.SessionRevision) - } + testassert.Require(t, newest.SessionRevision != first.SessionRevision, "newest page revision = %+v, want the advanced current revision", newest.SessionRevision) olderAfter := composedHistory(t, c, main, first.OlderCursor) - if got := composedPageIDs(t, olderAfter.Items); strings.Join(got, ",") != strings.Join(olderIDs, ",") { - t.Fatalf("older page after the append = %v, want the anchored %v", got, olderIDs) + { + got := composedPageIDs(t, olderAfter.Items) + testassert.Require(t, strings.Join(got, ",") == strings.Join(olderIDs, ","), "older page after the append = %v, want the anchored %v", got, olderIDs) } wsB := filepath.Join(home, "b-ws") createdB, err := c.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: wsB, AgentType: "main"}) - if err != nil { - t.Fatalf("CreateSession(B): %v", err) - } - if createdB.JSON201 == nil { - t.Fatalf("CreateSession(B) = status %d: %s", createdB.HTTPResponse.StatusCode, createdB.Body) - } + testassert.NoError(t, err) + testassert.Require(t, createdB.JSON201 != nil, "CreateSession(B) = status %d: %s", createdB.HTTPResponse.StatusCode, createdB.Body) sessionB := createdB.JSON201.SessionId composedSubmit(t, c, sessionB, "op-b", "hello b") composedAwaitOp(t, c, sessionB, "op-b", protocol.OperationStatusSuccess) foreignCursor, err := c.GetSessionHistoryWithResponse(ctx, sessionB, &protocol.GetSessionHistoryParams{Cursor: first.OlderCursor}) - if err != nil { - t.Fatalf("cross-session history: %v", err) - } - if foreignCursor.JSONDefault == nil || foreignCursor.JSONDefault.Code != protocol.Invalid { - t.Fatalf("cross-session cursor response = %+v (status %d), want the typed invalid refusal", foreignCursor.JSONDefault, foreignCursor.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, foreignCursor.JSONDefault != nil && foreignCursor.JSONDefault.Code == protocol.Invalid, "cross-session cursor response = %+v (status %d), want the typed invalid refusal", foreignCursor.JSONDefault, foreignCursor.HTTPResponse.StatusCode) // 7. Settings: the stale whole-section save succeeds under // last-writer-wins; each compiled plugin's own document string @@ -928,70 +816,37 @@ func TestProtocolComposedFlow(t *testing.T) { put := func(settings protocol.Settings) *protocol.UpdateConfigurationSettingsResponse { t.Helper() resp, err := c.UpdateConfigurationSettingsWithResponse(ctx, protocol.UpdateSettingsRequest{Settings: settings}) - if err != nil { - t.Fatalf("UpdateConfigurationSettings: %v", err) - } + testassert.NoError(t, err) return resp } firstPut := put(settingsA) - if firstPut.JSON200 == nil || !firstPut.JSON200.Result.Sessions.AutoArchive || firstPut.JSON200.Result.Sessions.ArchiveAfterDays != 3 || - compactComposed(t, []byte(firstPut.JSON200.Result.Plugins["tools"])) != compactComposed(t, []byte(settingsADocument)) || - compactComposed(t, []byte(firstPut.JSON200.Result.Plugins[composedCustomPluginID])) != compactComposed(t, []byte(composedCustomSettingsDocument)) { - t.Fatalf("settings write A = %+v, want the published A section with every plugin's exact document", firstPut.JSON200) - } + testassert.Require(t, firstPut.JSON200 != nil && firstPut.JSON200.Result.Sessions.AutoArchive && firstPut.JSON200.Result.Sessions.ArchiveAfterDays == 3 && compactComposed(t, []byte(firstPut.JSON200.Result.Plugins["tools"])) == compactComposed(t, []byte(settingsADocument)) && compactComposed(t, []byte(firstPut.JSON200.Result.Plugins[composedCustomPluginID])) == compactComposed(t, []byte(composedCustomSettingsDocument)), "settings write A = %+v, want the published A section with every plugin's exact document", firstPut.JSON200) secondPut := put(settingsB) - if secondPut.JSON200 == nil || secondPut.JSON200.Result.Sessions.AutoArchive || len(secondPut.JSON200.Result.Plugins) != 0 { - t.Fatalf("settings write B = %+v, want the published B section with every plugin section removed", secondPut.JSON200) - } + testassert.Require(t, secondPut.JSON200 != nil && !secondPut.JSON200.Result.Sessions.AutoArchive && len(secondPut.JSON200.Result.Plugins) == 0, "settings write B = %+v, want the published B section with every plugin section removed", secondPut.JSON200) stalePut := put(settingsA) - if stalePut.JSON200 == nil || !stalePut.JSON200.Result.Sessions.AutoArchive || stalePut.JSON200.Result.Sessions.ArchiveAfterDays != 3 || - stalePut.JSON200.Result.Sessions.DeleteAfterArchiveDays != 0 || - compactComposed(t, []byte(stalePut.JSON200.Result.Plugins["tools"])) != compactComposed(t, []byte(settingsADocument)) || - compactComposed(t, []byte(stalePut.JSON200.Result.Plugins[composedCustomPluginID])) != compactComposed(t, []byte(composedCustomSettingsDocument)) { - t.Fatalf("stale whole-section save = %+v, want the wholesale replacement back to A", stalePut.JSON200) - } + testassert.Require(t, stalePut.JSON200 != nil && stalePut.JSON200.Result.Sessions.AutoArchive && stalePut.JSON200.Result.Sessions.ArchiveAfterDays == 3 && stalePut.JSON200.Result.Sessions.DeleteAfterArchiveDays == 0 && compactComposed(t, []byte(stalePut.JSON200.Result.Plugins["tools"])) == compactComposed(t, []byte(settingsADocument)) && compactComposed(t, []byte(stalePut.JSON200.Result.Plugins[composedCustomPluginID])) == compactComposed(t, []byte(composedCustomSettingsDocument)), "stale whole-section save = %+v, want the wholesale replacement back to A", stalePut.JSON200) publishedGeneration := stalePut.JSON200.ConfigurationRevision.Generation invalid := protocol.Settings{Sessions: settingsA.Sessions, Plugins: protocol.PluginsSettings{"tasks": `{"max_concurrent":99,"max_output_bytes":4096}`}} refused, err := c.UpdateConfigurationSettingsWithResponse(ctx, protocol.UpdateSettingsRequest{Settings: invalid}) - if err != nil { - t.Fatalf("invalid settings write: %v", err) - } - if refused.JSONDefault == nil || refused.JSONDefault.Code != protocol.Configuration { - t.Fatalf("invalid settings response = %+v (status %d), want the typed configuration refusal", refused.JSONDefault, refused.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, refused.JSONDefault != nil && refused.JSONDefault.Code == protocol.Configuration, "invalid settings response = %+v (status %d), want the typed configuration refusal", refused.JSONDefault, refused.HTTPResponse.StatusCode) // The selected custom plugin's own validator refuses its invalid // document with the same configuration class. customInvalid := protocol.Settings{Sessions: settingsA.Sessions, Plugins: protocol.PluginsSettings{composedCustomPluginID: `{"threshold":0}`}} customRefused, err := c.UpdateConfigurationSettingsWithResponse(ctx, protocol.UpdateSettingsRequest{Settings: customInvalid}) - if err != nil { - t.Fatalf("invalid custom settings write: %v", err) - } - if customRefused.JSONDefault == nil || customRefused.JSONDefault.Code != protocol.Configuration { - t.Fatalf("invalid custom settings response = %+v (status %d), want the typed configuration refusal", customRefused.JSONDefault, customRefused.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, customRefused.JSONDefault != nil && customRefused.JSONDefault.Code == protocol.Configuration, "invalid custom settings response = %+v (status %d), want the typed configuration refusal", customRefused.JSONDefault, customRefused.HTTPResponse.StatusCode) view, err := c.GetConfigurationWithResponse(ctx) - if err != nil { - t.Fatalf("GetConfiguration: %v", err) - } - if view.JSON200 == nil { - t.Fatalf("GetConfiguration = status %d: %s", view.HTTPResponse.StatusCode, view.Body) - } - if view.JSON200.ConfigurationRevision.Generation != publishedGeneration || !view.JSON200.Settings.Sessions.AutoArchive || view.JSON200.Settings.Sessions.ArchiveAfterDays != 3 || - compactComposed(t, []byte(view.JSON200.Settings.Plugins["tools"])) != compactComposed(t, []byte(settingsADocument)) || - compactComposed(t, []byte(view.JSON200.Settings.Plugins[composedCustomPluginID])) != compactComposed(t, []byte(composedCustomSettingsDocument)) { - t.Fatalf("configuration after the refused write = %+v, want the unchanged last publication", view.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, view.JSON200 != nil, "GetConfiguration = status %d: %s", view.HTTPResponse.StatusCode, view.Body) + testassert.Require(t, view.JSON200.ConfigurationRevision.Generation == publishedGeneration && view.JSON200.Settings.Sessions.AutoArchive && view.JSON200.Settings.Sessions.ArchiveAfterDays == 3 && compactComposed(t, []byte(view.JSON200.Settings.Plugins["tools"])) == compactComposed(t, []byte(settingsADocument)) && compactComposed(t, []byte(view.JSON200.Settings.Plugins[composedCustomPluginID])) == compactComposed(t, []byte(composedCustomSettingsDocument)), "configuration after the refused write = %+v, want the unchanged last publication", view.JSON200) // The custom plugin's neutral warning: the Runtime-scoped factory's // report carries its own registered source through the mounted // unfiltered read and every Session's hydration. allWarnings, err := c.GetWarningsWithResponse(ctx) - if err != nil { - t.Fatalf("GetWarnings: %v", err) - } - if allWarnings.JSON200 == nil { - t.Fatalf("GetWarnings = status %d: %s", allWarnings.HTTPResponse.StatusCode, allWarnings.Body) - } + testassert.NoError(t, err) + testassert.Require(t, allWarnings.JSON200 != nil, "GetWarnings = status %d: %s", allWarnings.HTTPResponse.StatusCode, allWarnings.Body) composedAssertPluginWarning(t, allWarnings.JSON200.Warnings) hydratedWarnings := composedHydrate(t, c, main) composedAssertPluginWarning(t, hydratedWarnings.Warnings) @@ -1002,31 +857,19 @@ func TestProtocolComposedFlow(t *testing.T) { connectGeneration := view.JSON200.ConfigurationRevision.Generation connectKey := composedFlowConnectKey connected, err := c.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: "connprov"}, protocol.ConnectRequest{ApiKey: &connectKey}) - if err != nil { - t.Fatalf("ConnectProvider: %v", err) - } - if connected.JSON200 == nil || !connected.JSON200.Result.Connected || connected.JSON200.Result.KeySource != protocol.Managed { - t.Fatalf("connected provider view = %+v, want connected with the managed key source", connected.JSON200) - } - if !composedGenerationAfter(t, connected.JSON200.ConfigurationRevision.Generation, connectGeneration) { - t.Fatalf("connect generation = %s, want an advance over %s", connected.JSON200.ConfigurationRevision.Generation, connectGeneration) - } - if bytes, _ := json.Marshal(connected.JSON200); strings.Contains(string(bytes), composedFlowConnectKey) { - t.Fatal("the connect response body carries the credential value") + testassert.NoError(t, err) + testassert.Require(t, connected.JSON200 != nil && connected.JSON200.Result.Connected && connected.JSON200.Result.KeySource == protocol.Managed, "connected provider view = %+v, want connected with the managed key source", connected.JSON200) + testassert.Require(t, composedGenerationAfter(t, connected.JSON200.ConfigurationRevision.Generation, connectGeneration), "connect generation = %s, want an advance over %s", connected.JSON200.ConfigurationRevision.Generation, connectGeneration) + { + bytes, _ := json.Marshal(connected.JSON200) + testassert.Require(t, !strings.Contains(string(bytes), composedFlowConnectKey), "the connect response body carries the credential value") } composedSubmit(t, c, main, "op-env", "print the environment") h = composedAwaitOp(t, c, main, "op-env", protocol.OperationStatusSuccess) envAssistant := composedAssistantOf(t, h.Conversation.Items, "op-env") - if len(envAssistant.ToolCalls) != 1 || envAssistant.ToolCalls[0].Content == nil { - t.Fatalf("environment turn tool calls = %+v, want the command result", envAssistant.ToolCalls) - } + testassert.Require(t, len(envAssistant.ToolCalls) == 1 && envAssistant.ToolCalls[0].Content != nil, "environment turn tool calls = %+v, want the command result", envAssistant.ToolCalls) envOutput := *envAssistant.ToolCalls[0].Content - if !strings.Contains(envOutput, "composed-shell-value|") { - t.Fatalf("environment command output = %q, want the shell-exported sibling visible", envOutput) - } - if strings.Contains(envOutput, composedFlowConnectKey) { - t.Fatalf("environment command output = %q, want the late-managed connect key scrubbed", envOutput) - } + testassert.Require(t, strings.Contains(envOutput, "composed-shell-value|") && !strings.Contains(envOutput, composedFlowConnectKey), "environment command output = %q, want the shell-exported sibling visible; environment command output = %q, want the late-managed connect key scrubbed", envOutput, envOutput) // The disconnect refusal: the credential state the disconnect // published governs the next admission. The selected model is @@ -1034,135 +877,82 @@ func TestProtocolComposedFlow(t *testing.T) { // the managed key, and the following admission refuses with no // published Operation. selected, err := c.SetAgentTypeModelWithResponse(ctx, &protocol.SetAgentTypeModelParams{AgentType: "main"}, protocol.SetAgentTypeModelRequest{Model: "connprov/cm"}) - if err != nil { - t.Fatalf("SetAgentTypeModel(connprov/cm): %v", err) - } - if selected.JSON200 == nil || selected.JSON200.Result.Model != "connprov/cm" { - t.Fatalf("selected model edit = %+v, want the main roster entry's connected-provider model", selected.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, selected.JSON200 != nil && selected.JSON200.Result.Model == "connprov/cm", "selected model edit = %+v, want the main roster entry's connected-provider model", selected.JSON200) disconnected, err := c.DisconnectProviderWithResponse(ctx, &protocol.DisconnectProviderParams{ProviderId: "connprov"}) - if err != nil { - t.Fatalf("DisconnectProvider: %v", err) - } - if disconnected.JSON200 == nil || disconnected.JSON200.Result == nil || disconnected.JSON200.Result.Connected { - t.Fatalf("disconnect response = %+v, want the disconnected provider view", disconnected.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, disconnected.JSON200 != nil && disconnected.JSON200.Result != nil && !disconnected.JSON200.Result.Connected, "disconnect response = %+v, want the disconnected provider view", disconnected.JSON200) var refusedPart protocol.ContentPart - if err := refusedPart.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: "refused turn"}); err != nil { - t.Fatalf("build refused part: %v", err) - } + testassert.NoError(t, refusedPart.FromTextPart(protocol.TextPart{Kind: protocol.TextPartKindText, Text: "refused turn"})) refusedSubmit, err := c.SubmitSessionWithResponse(ctx, main, protocol.SubmitRequest{ OperationId: "op-refused", Mode: protocol.SubmitRequestModeRegular, Content: []protocol.ContentPart{refusedPart}, }) - if err != nil { - t.Fatalf("refused submit: %v", err) - } - if refusedSubmit.JSONDefault == nil || refusedSubmit.JSONDefault.Code != protocol.Invalid || - refusedSubmit.HTTPResponse.StatusCode != http.StatusBadRequest { - t.Fatalf("refused submit response = %+v (status %d), want the typed invalid refusal over HTTP 400", refusedSubmit.JSONDefault, refusedSubmit.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, refusedSubmit.JSONDefault != nil && refusedSubmit.JSONDefault.Code == protocol.Invalid && refusedSubmit.HTTPResponse.StatusCode == http.StatusBadRequest, "refused submit response = %+v (status %d), want the typed invalid refusal over HTTP 400", refusedSubmit.JSONDefault, refusedSubmit.HTTPResponse.StatusCode) afterRefusal := composedHydrate(t, c, main) for _, operation := range afterRefusal.Operations { - if operation.OperationId == "op-refused" { - t.Fatalf("the refused admission published operation %+v, want no Operation", operation) - } + testassert.Require(t, operation.OperationId != "op-refused", "the refused admission published operation %+v, want no Operation", operation) } for _, item := range afterRefusal.Conversation.Items { if composedItemKind(t, item) != "input" { continue } input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } - if input.OperationId != nil && *input.OperationId == "op-refused" { - t.Fatalf("the refused admission published input %+v, want no published input", input) - } + testassert.NoError(t, err) + testassert.Require(t, input.OperationId == nil || *input.OperationId != "op-refused", "the refused admission published input %+v, want no published input", input) } // 9. Archive keeps history readable; reopen; delete removes every // read; the surviving workspace list is intact. archivedChild, err := c.ArchiveSessionWithResponse(ctx, childID) - if err != nil { - t.Fatalf("archive child: %v", err) - } - if archivedChild.JSON200 == nil { - t.Fatalf("archive child = status %d: %s", archivedChild.HTTPResponse.StatusCode, archivedChild.Body) - } + testassert.NoError(t, err) + testassert.Require(t, archivedChild.JSON200 != nil, "archive child = status %d: %s", archivedChild.HTTPResponse.StatusCode, archivedChild.Body) deletedChild, err := c.DeleteSessionWithResponse(ctx, childID) - if err != nil { - t.Fatalf("delete child: %v", err) - } - if deletedChild.HTTPResponse.StatusCode != http.StatusNoContent { - t.Fatalf("delete child = status %d: %s", deletedChild.HTTPResponse.StatusCode, deletedChild.Body) - } + testassert.NoError(t, err) + testassert.Require(t, deletedChild.HTTPResponse.StatusCode == http.StatusNoContent, "delete child = status %d: %s", deletedChild.HTTPResponse.StatusCode, deletedChild.Body) archived, err := c.ArchiveSessionWithResponse(ctx, main) - if err != nil { - t.Fatalf("archive main: %v", err) - } - if archived.JSON200 == nil || archived.JSON200.Lifecycle != protocol.Archived { - t.Fatalf("archive main = %+v (status %d)", archived.JSON200, archived.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, archived.JSON200 != nil && archived.JSON200.Lifecycle == protocol.Archived, "archive main = %+v (status %d)", archived.JSON200, archived.HTTPResponse.StatusCode) archivedHistory := composedHistory(t, c, main, nil) - if len(archivedHistory.Items) == 0 { - t.Fatal("the archived session's history is hidden, want the fully readable history") - } + testassert.Require(t, len(archivedHistory.Items) != 0, "the archived session's history is hidden, want the fully readable history") reopened, err := c.ReopenSessionWithResponse(ctx, main) - if err != nil { - t.Fatalf("reopen main: %v", err) - } - if reopened.JSON200 == nil || reopened.JSON200.Lifecycle != protocol.Open { - t.Fatalf("reopen main = %+v (status %d)", reopened.JSON200, reopened.HTTPResponse.StatusCode) - } - if _, err := c.ArchiveSessionWithResponse(ctx, main); err != nil { - t.Fatalf("re-archive main: %v", err) + testassert.NoError(t, err) + testassert.Require(t, reopened.JSON200 != nil && reopened.JSON200.Lifecycle == protocol.Open, "reopen main = %+v (status %d)", reopened.JSON200, reopened.HTTPResponse.StatusCode) + { + _, err := c.ArchiveSessionWithResponse(ctx, main) + testassert.NoError(t, err) } deleted, err := c.DeleteSessionWithResponse(ctx, main) - if err != nil { - t.Fatalf("delete main: %v", err) - } - if deleted.HTTPResponse.StatusCode != http.StatusNoContent { - t.Fatalf("delete main = status %d: %s", deleted.HTTPResponse.StatusCode, deleted.Body) - } + testassert.NoError(t, err) + testassert.Require(t, deleted.HTTPResponse.StatusCode == http.StatusNoContent, "delete main = status %d: %s", deleted.HTTPResponse.StatusCode, deleted.Body) missing, err := c.GetSessionHydrationWithResponse(ctx, main) - if err != nil { - t.Fatalf("deleted session read: %v", err) - } - if missing.JSON200 != nil || missing.JSONDefault == nil || missing.JSONDefault.Code != protocol.NotFound { - t.Fatalf("deleted session read = (%+v, %+v), want the typed not-found", missing.JSON200, missing.JSONDefault) - } + testassert.NoError(t, err) + testassert.Require(t, missing.JSON200 == nil && missing.JSONDefault != nil && missing.JSONDefault.Code == protocol.NotFound, "deleted session read = (%+v, %+v), want the typed not-found", missing.JSON200, missing.JSONDefault) mainList, err := c.ListSessionsWithResponse(ctx, &protocol.ListSessionsParams{Workspace: wsMain, Lifecycle: protocol.Open}) - if err != nil { - t.Fatalf("deleted workspace list: %v", err) - } - if mainList.JSON200 == nil || len(*mainList.JSON200) != 0 { - t.Fatalf("deleted workspace list = %+v (status %d), want the empty array", mainList.JSON200, mainList.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, mainList.JSON200 != nil && len(*mainList.JSON200) == 0, "deleted workspace list = %+v (status %d), want the empty array", mainList.JSON200, mainList.HTTPResponse.StatusCode) bList, err := c.ListSessionsWithResponse(ctx, &protocol.ListSessionsParams{Workspace: wsB, Lifecycle: protocol.Open}) - if err != nil { - t.Fatalf("surviving workspace list: %v", err) - } - if bList.JSON200 == nil || len(*bList.JSON200) != 1 || (*bList.JSON200)[0].SessionId != sessionB { - t.Fatalf("surviving workspace list = %+v (status %d), want exactly session B", bList.JSON200, bList.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, bList.JSON200 != nil && len(*bList.JSON200) == 1 && (*bList.JSON200)[0].SessionId == sessionB, "surviving workspace list = %+v (status %d), want exactly session B", bList.JSON200, bList.HTTPResponse.StatusCode) // 10. The closed owner: the stream closes by caller cancellation, // the managed shutdown withdraws the record, refuses the listener, // and the withdrawn record is no usable owner. cancelStream() - if err := <-errs; !errors.Is(err, context.Canceled) { - t.Fatalf("canceled stream error = %v, want context.Canceled", err) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) + { + err := <-errs + testassert.Require(t, errors.Is(err, context.Canceled), "canceled stream error = %v, want context.Canceled", err) } - if _, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")); err == nil { - t.Fatal("the protocol listener still accepts after shutdown") + testassert.NoError(t, r.Close(ctx)) + { + _, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")) + testassert.Require(t, err != nil, "the protocol listener still accepts after shutdown") } - if _, err := client.Connect(ctx, record); !errors.Is(err, client.ErrNoRuntime) { - t.Fatalf("connect after the managed close = %v, want ErrNoRuntime over the withdrawn record", err) + { + _, err := client.Connect(ctx, record) + testassert.Require(t, errors.Is(err, client.ErrNoRuntime), "connect after the managed close = %v, want ErrNoRuntime over the withdrawn record", err) } } @@ -1181,12 +971,8 @@ func TestProtocolComposedFlow(t *testing.T) { func composedHistory(t *testing.T, c *client.Client, sessionID string, cursor *string) *protocol.HistoryPage { t.Helper() resp, err := c.GetSessionHistoryWithResponse(context.Background(), sessionID, &protocol.GetSessionHistoryParams{Cursor: cursor}) - if err != nil { - t.Fatalf("GetSessionHistory: %v", err) - } - if resp.JSON200 == nil { - t.Fatalf("GetSessionHistory = status %d: %s", resp.HTTPResponse.StatusCode, resp.Body) - } + testassert.NoError(t, err) + testassert.Require(t, resp.JSON200 != nil, "GetSessionHistory = status %d: %s", resp.HTTPResponse.StatusCode, resp.Body) return resp.JSON200 } @@ -1195,39 +981,27 @@ func composedPageIDs(t *testing.T, items []protocol.ConversationItem) []string { ids := make([]string, 0, len(items)) for _, item := range items { kind, err := item.Discriminator() - if err != nil { - t.Fatalf("item discriminator: %v", err) - } + testassert.NoError(t, err) switch kind { case "input": input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } + testassert.NoError(t, err) ids = append(ids, input.ItemId) case "assistant": assistant, err := item.AsAssistantItem() - if err != nil { - t.Fatalf("assistant item: %v", err) - } + testassert.NoError(t, err) ids = append(ids, assistant.ItemId) case "signal": signal, err := item.AsSignalItem() - if err != nil { - t.Fatalf("signal item: %v", err) - } + testassert.NoError(t, err) ids = append(ids, signal.ItemId) case "compaction": compaction, err := item.AsCompactionItem() - if err != nil { - t.Fatalf("compaction item: %v", err) - } + testassert.NoError(t, err) ids = append(ids, compaction.ItemId) case "operation_end": end, err := item.AsOperationEndItem() - if err != nil { - t.Fatalf("operation_end item: %v", err) - } + testassert.NoError(t, err) ids = append(ids, end.ItemId) } } @@ -1249,13 +1023,9 @@ func composedParseRevision(value string) uint64 { func composedGenerationAfter(t *testing.T, got, before string) bool { t.Helper() g, err := strconv.ParseUint(got, 10, 64) - if err != nil { - t.Fatalf("generation %q: %v", got, err) - } + testassert.Require(t, err == nil, "generation %q: %v", got, err) b, err := strconv.ParseUint(before, 10, 64) - if err != nil { - t.Fatalf("generation %q: %v", before, err) - } + testassert.Require(t, err == nil, "generation %q: %v", before, err) return g > b } @@ -1427,20 +1197,14 @@ func heldOperationExecution(t *testing.T, store harness.Storage, sessionID, oper reg, err := store.ReadRegister(context.Background(), harness.RegisterKey{ SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: operationID, }) - if err != nil { - t.Fatalf("ReadRegister(%s, %s): %v", sessionID, operationID, err) - } + testassert.Require(t, err == nil, "ReadRegister(%s, %s): %v", sessionID, operationID, err) var payload struct { Admission struct { Execution json.RawMessage `json:"execution"` } `json:"admission"` } - if err := json.Unmarshal(reg.Payload, &payload); err != nil { - t.Fatalf("decode operation register: %v", err) - } - if len(payload.Admission.Execution) == 0 { - t.Fatalf("operation register %s/%s carries no captured execution", sessionID, operationID) - } + testassert.NoError(t, json.Unmarshal(reg.Payload, &payload)) + testassert.Require(t, len(payload.Admission.Execution) != 0, "operation register %s/%s carries no captured execution", sessionID, operationID) return payload.Admission.Execution } @@ -1475,35 +1239,21 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { r, err := runtime.Open(ctx, runtime.Options{DataDir: dataDir, ConfigPath: configPath, Plugins: []runtime.Plugin{ composedMemoryStorage(store), tools.Plugin(), jobs.Plugin(), }}) - if err != nil { - t.Fatalf("runtime.Open: %v", err) - } + testassert.NoError(t, err) defer func() { _ = r.Close(ctx) }() ps, err := r.OpenProtocol(ctx) - if err != nil { - t.Fatalf("OpenProtocol: %v", err) - } + testassert.NoError(t, err) record := filepath.Join(t.TempDir(), "discovery.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(record)) c, err := client.Connect(ctx, record) - if err != nil { - t.Fatalf("client.Connect: %v", err) - } + testassert.NoError(t, err) ws := filepath.Join(home, "held-ws") created, err := c.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: ws, AgentType: "main"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if created.JSON201 == nil { - t.Fatalf("CreateSession = status %d: %s", created.HTTPResponse.StatusCode, created.Body) - } + testassert.NoError(t, err) + testassert.Require(t, created.JSON201 != nil, "CreateSession = status %d: %s", created.HTTPResponse.StatusCode, created.Body) sessionID := created.JSON201.SessionId - if err := os.MkdirAll(ws, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(ws, 0o700)) // The held turn admits, its opener captures, and only then does the // model request park at the gate — before any command environment. @@ -1514,9 +1264,7 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { t.Fatal("the held turn's model request never arrived") } preReload, err := c.GetConfigurationWithResponse(ctx) - if err != nil || preReload.JSON200 == nil { - t.Fatalf("GetConfiguration before the hold = (%v, status %d)", err, preReload.HTTPResponse.StatusCode) - } + testassert.Require(t, err == nil && preReload.JSON200 != nil, "GetConfiguration before the hold = (%v, status %d)", err, preReload.HTTPResponse.StatusCode) heldBefore := heldOperationExecution(t, store, sessionID, "op-held") // While the request is parked: rename the provider binding from the @@ -1528,27 +1276,17 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { composedWriteFile(t, agentsPath, heldAgentsRetargeted) connectKey := heldManagedValue connected, err := c.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: "connprov"}, protocol.ConnectRequest{ApiKey: &connectKey}) - if err != nil { - t.Fatalf("ConnectProvider: %v", err) - } - if connected.JSON200 == nil || connected.JSON200.Result == nil || connected.JSON200.Result.KeySource != protocol.Managed { - t.Fatalf("connect response = %+v, want the separately managed key", connected.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, connected.JSON200 != nil && connected.JSON200.Result != nil && connected.JSON200.Result.KeySource == protocol.Managed, "connect response = %+v, want the separately managed key", connected.JSON200) reloaded, err := c.ReloadConfigurationWithResponse(ctx) - if err != nil { - t.Fatalf("ReloadConfiguration: %v", err) - } - if reloaded.JSON200 == nil { - t.Fatalf("ReloadConfiguration = status %d: %s", reloaded.HTTPResponse.StatusCode, reloaded.Body) - } + testassert.NoError(t, err) + testassert.Require(t, reloaded.JSON200 != nil, "ReloadConfiguration = status %d: %s", reloaded.HTTPResponse.StatusCode, reloaded.Body) // The admitted capture is still the original one: nothing // recaptured the running Operation from the new configuration, and // its revision stays the pre-reload generation. heldAfter := heldOperationExecution(t, store, sessionID, "op-held") - if string(heldAfter) != string(heldBefore) { - t.Fatal("the captured execution changed across the configuration reload") - } + testassert.Require(t, string(heldAfter) == string(heldBefore), "the captured execution changed across the configuration reload") var capture struct { ConfigurationRevision string `json:"configuration_revision"` Model struct { @@ -1556,23 +1294,14 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { Model string `json:"model"` } `json:"model"` } - if err := json.Unmarshal(heldBefore, &capture); err != nil { - t.Fatalf("decode captured execution: %v", err) - } - if capture.Model.Provider != "prov" || capture.Model.Model != "m" { - t.Fatalf("captured model = %q/%q, want the preparation-time selection", capture.Model.Provider, capture.Model.Model) - } - if capture.ConfigurationRevision != preReload.JSON200.ConfigurationRevision.Generation { - t.Fatalf("captured revision = %q, want the pre-reload generation %q", capture.ConfigurationRevision, preReload.JSON200.ConfigurationRevision.Generation) - } + testassert.NoError(t, json.Unmarshal(heldBefore, &capture)) + testassert.Require(t, capture.Model.Provider == "prov" && capture.Model.Model == "m" && capture.ConfigurationRevision == preReload.JSON200.ConfigurationRevision.Generation, "captured model = %q/%q, want the preparation-time selection; got %v, want %v", capture.Model.Provider, capture.Model.Model, capture.ConfigurationRevision, preReload.JSON200.ConfigurationRevision.Generation) // Release the parked request: the admitted command runs and builds // its child environment from the live exclusion service. model.release() h := composedAwaitOp(t, c, sessionID, "op-held", protocol.OperationStatusSuccess) - if string(heldOperationExecution(t, store, sessionID, "op-held")) != string(heldBefore) { - t.Fatal("the captured execution changed across the held Operation's run") - } + testassert.Require(t, string(heldOperationExecution(t, store, sessionID, "op-held")) == string(heldBefore), "the captured execution changed across the held Operation's run") // Every request the held input issued followed the preparation-time // model and credential: boolean comparisons only, never a credential @@ -1588,16 +1317,10 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { continue } heldRequests++ - if !rec.oldModel || !rec.oldAuth { - t.Fatalf("held-input request %d selection = (model %t, auth %t), want the captured model and the preparation-time credential", i, rec.oldModel, rec.oldAuth) - } - } - if heldRequests == 0 { - t.Fatal("no recorded model request carried the held input") - } - if model.wasCanceled() { - t.Fatal("the parked model request was canceled during the hold") + testassert.Require(t, rec.oldModel && rec.oldAuth, "held-input request %d selection = (model %t, auth %t), want the captured model and the preparation-time credential", i, rec.oldModel, rec.oldAuth) } + testassert.Require(t, heldRequests != 0, "no recorded model request carried the held input") + testassert.Require(t, !model.wasCanceled(), "the parked model request was canceled during the hold") // The child saw neither the preparation-time external binding, nor // the renamed current one, nor the separately managed key, and still @@ -1610,25 +1333,17 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { continue } input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } + testassert.NoError(t, err) text := composedItemText(t, input) if input.Origin == protocol.InputOriginRuntime && strings.Contains(text, "old=") { flags = text } } - if flags == "" { - t.Fatal("no background completion carried the child's presence flags") - } - if !strings.Contains(flags, heldEnvWant) { - t.Fatalf("background child presence flags = %q, want %q", flags, heldEnvWant) - } + testassert.Require(t, flags != "", "no background completion carried the child's presence flags") + testassert.Require(t, strings.Contains(flags, heldEnvWant), "background child presence flags = %q, want %q", flags, heldEnvWant) } else { assistant := composedAssistantOf(t, h.Conversation.Items, "op-held") - if len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].Content == nil { - t.Fatalf("held command tool calls = %+v, want one result", assistant.ToolCalls) - } + testassert.Require(t, len(assistant.ToolCalls) == 1 && assistant.ToolCalls[0].Content != nil, "held command tool calls = %+v, want one result", assistant.ToolCalls) if *assistant.ToolCalls[0].Content != heldEnvWant { t.Fatalf("foreground child presence flags = %q, want %q", *assistant.ToolCalls[0].Content, heldEnvWant) } @@ -1639,11 +1354,10 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { composedSubmit(t, c, sessionID, "op-deny", "probe the denial") h = composedAwaitOp(t, c, sessionID, "op-deny", protocol.OperationStatusSuccess) probe := composedAssistantOf(t, h.Conversation.Items, "op-deny") - if len(probe.ToolCalls) != 1 || probe.ToolCalls[0].Status == nil || *probe.ToolCalls[0].Status != protocol.ToolCallStatusDenied { - t.Fatalf("probe tool calls = %+v, want the denied command under the new permissions", probe.ToolCalls) - } - if _, err := os.Stat(filepath.Join(ws, "held-deny-marker")); !os.IsNotExist(err) { - t.Fatalf("the denied probe started its command (%v)", err) + testassert.Require(t, len(probe.ToolCalls) == 1 && probe.ToolCalls[0].Status != nil && *probe.ToolCalls[0].Status == protocol.ToolCallStatusDenied, "probe tool calls = %+v, want the denied command under the new permissions", probe.ToolCalls) + { + _, err := os.Stat(filepath.Join(ws, "held-deny-marker")) + testassert.Require(t, os.IsNotExist(err), "the denied probe started its command (%v)", err) } // The parent shell keeps every external value untouched and @@ -1654,43 +1368,30 @@ func TestProtocolComposedHeldCommandEnvironment(t *testing.T) { {heldNewKeyEnv, heldNewKeyValue}, {heldSiblingEnv, heldSiblingValue}, } { - if got := os.Getenv(row.name); got != row.want { - t.Fatalf("parent %s preserved = %t, want the external value untouched", row.name, got == row.want) + { + got := os.Getenv(row.name) + testassert.Require(t, got == row.want, "parent %s preserved = %t, want the external value untouched", row.name, got == row.want) } } supplied := "sk-held-supplied-1" refused, err := c.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: "prov"}, protocol.ConnectRequest{ApiKey: &supplied}) - if err != nil { - t.Fatalf("ConnectProvider(prov): %v", err) - } - if refused.JSONDefault == nil { - t.Fatalf("supplied-key connect on the external binding = status %d, want the typed refusal", refused.HTTPResponse.StatusCode) - } - if os.Getenv(heldNewKeyEnv) != heldNewKeyValue { - t.Fatal("the refused supplied-key write changed the parent external value") - } + testassert.NoError(t, err) + testassert.Require(t, refused.JSONDefault != nil, "supplied-key connect on the external binding = status %d, want the typed refusal", refused.HTTPResponse.StatusCode) + testassert.Require(t, os.Getenv(heldNewKeyEnv) == heldNewKeyValue, "the refused supplied-key write changed the parent external value") providers, err := c.ListProvidersWithResponse(ctx) - if err != nil { - t.Fatalf("ListProviders: %v", err) - } - if providers.JSON200 == nil { - t.Fatalf("ListProviders = status %d: %s", providers.HTTPResponse.StatusCode, providers.Body) - } + testassert.NoError(t, err) + testassert.Require(t, providers.JSON200 != nil, "ListProviders = status %d: %s", providers.HTTPResponse.StatusCode, providers.Body) unmanaged := false for _, provider := range providers.JSON200.Providers { if provider.Id == "prov" && provider.KeySource == protocol.External { unmanaged = true } } - if !unmanaged { - t.Fatal("the renamed binding left the external classification after the refused write") - } + testassert.Require(t, unmanaged, "the renamed binding left the external classification after the refused write") // Every background member already settled above; the managed // shutdown joins the rest through the existing cleanup path. - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) } t.Run("foreground", func(t *testing.T) { run(t, false) }) diff --git a/runtime/protocol_configuration_test.go b/runtime/protocol_configuration_test.go index dd6afaae..2d12a374 100644 --- a/runtime/protocol_configuration_test.go +++ b/runtime/protocol_configuration_test.go @@ -19,6 +19,7 @@ import ( "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/config" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -99,12 +100,8 @@ func openConfigurationRuntime(t *testing.T, store harness.Storage, configDoc, ag func writeDotEnv(t *testing.T, home, content string) { t.Helper() dir := filepath.Join(home, ".lightcode") - if err := os.MkdirAll(dir, 0o700); err != nil { - t.Fatalf("MkdirAll: %v", err) - } - if err := os.WriteFile(filepath.Join(dir, ".env"), []byte(content), 0o600); err != nil { - t.Fatalf("WriteFile(.env): %v", err) - } + testassert.NoError(t, os.MkdirAll(dir, 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(dir, ".env"), []byte(content), 0o600)) } // unsetenv cleans one process-environment key before and after the test: @@ -153,26 +150,16 @@ func TestConfigurationViewProjectsProvidersPinsSourcesKeySourcesAndHeaders(t *te defer closeProjectionRuntime(r) view, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } - if view.ConfigurationRevision.Generation != "1" || view.ConfigurationRevision.InstanceId != "" { - t.Fatalf("configuration revision = %+v, want generation 1 with the empty pre-server instance", view.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, view.ConfigurationRevision.Generation == "1" && view.ConfigurationRevision.InstanceId == "", "configuration revision = %+v, want generation 1 with the empty pre-server instance", view.ConfigurationRevision) // Source labels through the projection: the custom provider's models // are user, and every bundled provider's shipped models are bundled. prov := findProvider(view.Providers, "prov") - if prov.Id != "prov" { - t.Fatalf("prov view missing: %+v", prov) - } + testassert.Require(t, prov.Id == "prov", "prov view missing: %+v", prov) openrouter := findProvider(view.Providers, "openrouter") - if !openrouter.Builtin { - t.Fatalf("openrouter builtin = false, want the bundled identity") - } - if len(openrouter.Models) == 0 || openrouter.Models[0].Source != protocol.ModelSource("bundled") { - t.Fatalf("openrouter model sources = %+v, want bundled labels", openrouter.Models) - } + testassert.Require(t, openrouter.Builtin, "openrouter builtin = false, want the bundled identity") + testassert.Require(t, len(openrouter.Models) != 0 && openrouter.Models[0].Source == protocol.ModelSource("bundled"), "openrouter model sources = %+v, want bundled labels", openrouter.Models) // Key-source labels over the capture's own credential sample; the // wire view carries no mutability flags or suggested names — source @@ -188,47 +175,36 @@ func TestConfigurationViewProjectsProvidersPinsSourcesKeySourcesAndHeaders(t *te {"keyless", "keyless", true}, } { view := findProvider(view.Providers, row.id) - if view.KeySource != protocol.ProviderKeySource(row.keySource) || view.Connected != row.connected { - t.Fatalf("%s labels = key_source %q connected %v, want %q/%v", - row.id, view.KeySource, view.Connected, row.keySource, row.connected) - } + testassert.Require(t, view.KeySource == protocol.ProviderKeySource(row.keySource) && view.Connected == row.connected, "%s labels = key_source %q connected %v, want %q/%v", row.id, view.KeySource, view.Connected, row.keySource, row.connected) } // The no-authorization response contract: no casing or padding of the // credential header names survives into either map. - if _, present := prov.Headers["Authorization"]; present { - t.Fatal("effective headers returned Authorization") + { + _, present := prov.Headers["Authorization"] + testassert.Require(t, !present, "effective headers returned Authorization") } for name := range prov.Headers { trimmed := strings.TrimSpace(name) - if strings.EqualFold(trimmed, "Authorization") || strings.EqualFold(trimmed, "Proxy-Authorization") { - t.Fatalf("effective headers returned credential header %q", name) - } - } - if prov.Headers["X-Trace"] != "t1" { - t.Fatalf("effective headers = %v, want the retained transport header", prov.Headers) + testassert.Require(t, !strings.EqualFold(trimmed, "Authorization") && !strings.EqualFold(trimmed, "Proxy-Authorization"), "effective headers returned credential header %q", name) } + testassert.Require(t, prov.Headers["X-Trace"] == "t1", "effective headers = %v, want the retained transport header", prov.Headers) // The user headers projection carries the user layer's own headers // exactly — no source-based stripping — so a user-layer collision // with a bundled header name is visible and editable; the user file // is never edited. - if got := openrouter.UserHeaders; len(got) != 2 || got["x-title"] != "user-title" || got["X-Custom"] != "keep" { - t.Fatalf("openrouter user_headers = %v, want the user layer's own headers", got) - } - if len(openrouter.Headers["X-Title"]) == 0 || openrouter.Headers["HTTP-Referer"] == "" { - t.Fatalf("openrouter effective headers = %v, want the bundled headers present", openrouter.Headers) + { + got := openrouter.UserHeaders + testassert.Require(t, len(got) == 2 && got["x-title"] == "user-title" && got["X-Custom"] == "keep", "openrouter user_headers = %v, want the user layer's own headers", got) } + testassert.Require(t, len(openrouter.Headers["X-Title"]) != 0 && openrouter.Headers["HTTP-Referer"] != "", "openrouter effective headers = %v, want the bundled headers present", openrouter.Headers) // No secret value leaves through any response: labels only. data, err := json.Marshal(view) - if err != nil { - t.Fatalf("marshal view: %v", err) - } + testassert.NoError(t, err) for _, secret := range []string{"managed-secret-value", "shell-secret-value", "Bearer forged", "forged2", "forged3"} { - if strings.Contains(string(data), secret) { - t.Fatalf("configuration view carries secret bytes %q: %s", secret, data) - } + testassert.Require(t, !strings.Contains(string(data), secret), "configuration view carries secret bytes %q: %s", secret, data) } }) } @@ -253,27 +229,25 @@ func TestRuntimeSharedCredentialReference(t *testing.T) { defer closeProjectionRuntime(r) ctx := context.Background() sub, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() // Every sibling resolves the one shared credential. for _, id := range []string{"shalpha", "shbeta", "shgamma"} { detail, err := r.getProvider(ctx, id) - if err != nil || !detail.Provider.Connected || detail.Provider.KeySource != protocol.ProviderKeySource(config.KeySourceManaged) { - t.Fatalf("%s = (%v, %+v), want the shared managed connection", id, err, detail.Provider) - } + testassert.Require(t, err == nil && detail.Provider.Connected && detail.Provider.KeySource == protocol.ProviderKeySource(config.KeySourceManaged), "%s = (%v, %+v), want the shared managed connection", id, err, detail.Provider) } // The sibling's update keeps the shared reference and the variable. name := "Renamed Alpha" - if _, err := r.updateProvider(ctx, "shalpha", protocol.ProviderEdit{Name: &name}); err != nil { - t.Fatalf("sibling update: %v", err) + { + _, err := r.updateProvider(ctx, "shalpha", protocol.ProviderEdit{Name: &name}) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "2") - if detail, err := r.getProvider(ctx, "shbeta"); err != nil || !detail.Provider.Connected || detail.Provider.ApiKeyEnv != "SHARED_CRED_TEST_KEY" { - t.Fatalf("shbeta after the sibling update = (%v, %+v), want the shared reference untouched", err, detail.Provider) + { + detail, err := r.getProvider(ctx, "shbeta") + testassert.Require(t, err == nil && detail.Provider.Connected && detail.Provider.ApiKeyEnv == "SHARED_CRED_TEST_KEY", "shbeta after the sibling update = (%v, %+v), want the shared reference untouched", err, detail.Provider) } if os.Getenv("SHARED_CRED_TEST_KEY") != "shared-secret-value" || !r.managedEnv.IsManaged("SHARED_CRED_TEST_KEY") { t.Fatalf("shared variable after the sibling update = (%q, %v), want the exact value and ownership", os.Getenv("SHARED_CRED_TEST_KEY"), r.managedEnv.IsManaged("SHARED_CRED_TEST_KEY")) @@ -283,15 +257,17 @@ func TestRuntimeSharedCredentialReference(t *testing.T) { // the credential: the variable keeps its value and the sibling keeps // resolving it. fresh := "RESHARED_TEST_KEY" - if _, err := r.updateProvider(ctx, "shalpha", protocol.ProviderEdit{ApiKeyEnv: &fresh}); err != nil { - t.Fatalf("connected rebinding: %v", err) + { + _, err := r.updateProvider(ctx, "shalpha", protocol.ProviderEdit{ApiKeyEnv: &fresh}) + testassert.NoError(t, err) } drainConnectionEvent(t, r.warnings, sub, "3") if os.Getenv("SHARED_CRED_TEST_KEY") != "shared-secret-value" { t.Fatalf("the rebinding unset the shared credential: %q", os.Getenv("SHARED_CRED_TEST_KEY")) } - if detail, err := r.getProvider(ctx, "shbeta"); err != nil || !detail.Provider.Connected { - t.Fatalf("shbeta after the rebinding = (%v, %+v), want the connection through the shared variable", err, detail.Provider) + { + detail, err := r.getProvider(ctx, "shbeta") + testassert.Require(t, err == nil && detail.Provider.Connected, "shbeta after the rebinding = (%v, %+v), want the connection through the shared variable", err, detail.Provider) } // The deletion is the strongest form: removing a provider that @@ -299,15 +275,14 @@ func TestRuntimeSharedCredentialReference(t *testing.T) { // surviving sibling's connection intact — the variable is not a // per-provider lease. mutation, err := r.deleteProvider(ctx, "shbeta") - if err != nil || mutation.Result != nil { - t.Fatalf("shared-reference delete = (%v, %+v), want the null post-state", err, mutation) - } + testassert.Require(t, err == nil && mutation.Result == nil, "shared-reference delete = (%v, %+v), want the null post-state", err, mutation) drainConnectionEvent(t, r.warnings, sub, "4") if os.Getenv("SHARED_CRED_TEST_KEY") != "shared-secret-value" || !r.managedEnv.IsManaged("SHARED_CRED_TEST_KEY") { t.Fatalf("the deletion removed the shared variable = (%q, %v)", os.Getenv("SHARED_CRED_TEST_KEY"), r.managedEnv.IsManaged("SHARED_CRED_TEST_KEY")) } - if detail, err := r.getProvider(ctx, "shgamma"); err != nil || !detail.Provider.Connected || detail.Provider.KeySource != protocol.ProviderKeySource(config.KeySourceManaged) { - t.Fatalf("shgamma after the sibling deletion = (%v, %+v), want the surviving shared connection", err, detail.Provider) + { + detail, err := r.getProvider(ctx, "shgamma") + testassert.Require(t, err == nil && detail.Provider.Connected && detail.Provider.KeySource == protocol.ProviderKeySource(config.KeySourceManaged), "shgamma after the sibling deletion = (%v, %+v), want the surviving shared connection", err, detail.Provider) } } @@ -318,9 +293,7 @@ func openConfigurationRuntimeWithEnv(t *testing.T, store harness.Storage, e *own writeServiceFile(t, e.configPath, configDoc) writeServiceFile(t, agents.PathForConfig(e.configPath), agentsDoc) r, err := e.open(context.Background(), append([]Plugin{e.storagePlugin(store)}, extra...)...) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) return r, e } @@ -335,28 +308,21 @@ func TestConfigurationViewProjectsSettingsAndRoster(t *testing.T) { defer closeProjectionRuntime(r) view, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) sessions := view.Settings.Sessions - if sessions.AutoArchive || sessions.ArchiveAfterDays != 3 || sessions.DeleteAfterArchiveDays != 7 { - t.Fatalf("sessions settings = %+v, want the supplied values with the 7-day delete default", sessions) - } + testassert.Require(t, !sessions.AutoArchive && sessions.ArchiveAfterDays == 3 && sessions.DeleteAfterArchiveDays == 7, "sessions settings = %+v, want the supplied values with the 7-day delete default", sessions) wantDocs := map[string]string{ "tools": `{"max_output_bytes":42}`, "jobs": `{"max_background_processes":20}`, "tasks": `{}`, } - if len(view.Settings.Plugins) != len(wantDocs) { - t.Fatalf("plugins = %v, want exactly the owned sections %v", view.Settings.Plugins, wantDocs) - } + testassert.Require(t, len(view.Settings.Plugins) == len(wantDocs), "plugins = %v, want exactly the owned sections %v", view.Settings.Plugins, wantDocs) for id, wantDoc := range wantDocs { got, ok := view.Settings.Plugins[id] - if !ok { - t.Fatalf("plugins.%s missing, want the owned document %s", id, wantDoc) - } - if got := compactJSON(t, []byte(got)); got != wantDoc { - t.Fatalf("plugins.%s = %s, want the exact owned document %s (no decoder defaults)", id, got, wantDoc) + testassert.Require(t, ok, "plugins.%s missing, want the owned document %s", id, wantDoc) + { + got := compactJSON(t, []byte(got)) + testassert.Require(t, got == wantDoc, "plugins.%s = %s, want the exact owned document %s (no decoder defaults)", id, got, wantDoc) } } @@ -365,12 +331,8 @@ func TestConfigurationViewProjectsSettingsAndRoster(t *testing.T) { absent, _ := openConfigurationRuntime(t, store, `{"providers":{}}`, configurationAgentsDocument, settingsPlugins()...) defer closeProjectionRuntime(absent) absentView, err := absent.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration absent: %v", err) - } - if len(absentView.Settings.Plugins) != 0 { - t.Fatalf("absent plugin sections projected = %+v, want none", absentView.Settings.Plugins) - } + testassert.NoError(t, err) + testassert.Require(t, len(absentView.Settings.Plugins) == 0, "absent plugin sections projected = %+v, want none", absentView.Settings.Plugins) // The Agent roster: the primary model selection and the worker's // public fields with the trimmed write dir. @@ -383,12 +345,8 @@ func TestConfigurationViewProjectsSettingsAndRoster(t *testing.T) { worker = &view.Agents[i] } } - if primary == nil || primary.Model != "prov/m" { - t.Fatalf("primary roster entry = %+v, want the configured model", primary) - } - if worker == nil || !worker.Subagent || !worker.Readonly || worker.Description != "w" || worker.WriteDir != "/ws" { - t.Fatalf("worker roster entry = %+v, want the public definition fields with the trimmed write dir", worker) - } + testassert.Require(t, primary != nil && primary.Model == "prov/m", "primary roster entry = %+v, want the configured model", primary) + testassert.Require(t, worker != nil && worker.Subagent && worker.Readonly && worker.Description == "w" && worker.WriteDir == "/ws", "worker roster entry = %+v, want the public definition fields with the trimmed write dir", worker) }) } @@ -434,20 +392,12 @@ func TestConfigurationReadsRemainAvailableDuringParkedBuild(t *testing.T) { } view, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("read during a parked build: %v", err) - } - if view.ConfigurationRevision.Generation != "1" { - t.Fatalf("read during a parked build = generation %s, want the ready generation 1", view.ConfigurationRevision.Generation) - } + testassert.NoError(t, err) + testassert.Equal(t, view.ConfigurationRevision.Generation, "1") releaseGate() - if err := <-reloadDone; err != nil { - t.Fatalf("reload: %v", err) - } + testassert.NoError(t, <-reloadDone) after, err := r.getConfiguration(context.Background()) - if err != nil || after.ConfigurationRevision.Generation != "2" { - t.Fatalf("read after the reload = (%v, generation %s), want generation 2", err, after.ConfigurationRevision.Generation) - } + testassert.Require(t, err == nil && after.ConfigurationRevision.Generation == "2", "read after the reload = (%v, generation %s), want generation 2", err, after.ConfigurationRevision.Generation) } // definedEmptyProvidersDocument is the classifier fixture: a defined-but-empty @@ -477,9 +427,7 @@ func TestConfigurationDefinedEmptyExternalKeySource(t *testing.T) { ctx := context.Background() view, err := r.getConfiguration(ctx) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) for _, row := range []struct { id string source string @@ -491,29 +439,16 @@ func TestConfigurationDefinedEmptyExternalKeySource(t *testing.T) { {"live", "keyless", true}, } { prov := findProvider(view.Providers, row.id) - if prov.Id != row.id { - t.Fatalf("%s view missing: %+v", row.id, prov) - } - if string(prov.KeySource) != row.source || prov.Connected != row.connected { - t.Fatalf("%s = key_source %q connected %v, want %q/%v", - row.id, prov.KeySource, prov.Connected, row.source, row.connected) - } + testassert.Require(t, prov.Id == row.id && string(prov.KeySource) == row.source && prov.Connected == row.connected, "%s view missing: %+v; %s = key_source %q connected %v, want %q/%v", row.id, prov, row.id, prov.KeySource, prov.Connected, row.source, row.connected) } models, err := r.listModels(ctx, protocol.ListModelsParams{All: true}) - if err != nil { - t.Fatalf("listModels: %v", err) - } + testassert.NoError(t, err) included := map[string]bool{} for _, entry := range models.Models { included[entry.Provider] = true } - if included["emptyext"] || included["absent"] || included["managedempty"] { - t.Fatalf("the picker includes disconnected providers: %v", included) - } - if !included["live"] { - t.Fatalf("the picker excludes the connected keyless provider: %v", included) - } + testassert.Require(t, !included["emptyext"] && !included["absent"] && !included["managedempty"] && included["live"], "the picker includes disconnected providers: %v; the picker excludes the connected keyless provider: %v", included, included) } // TestConfigurationModelPickerConnectedRules pins the flat picker: both the @@ -528,13 +463,9 @@ func TestConfigurationModelPickerConnectedRules(t *testing.T) { defer closeProjectionRuntime(r) visible, err := r.listModels(context.Background(), protocol.ListModelsParams{All: false}) - if err != nil { - t.Fatalf("listModels(visible): %v", err) - } + testassert.NoError(t, err) full, err := r.listModels(context.Background(), protocol.ListModelsParams{All: true}) - if err != nil { - t.Fatalf("listModels(full): %v", err) - } + testassert.NoError(t, err) // Connected providers only: unsetp's model is in neither list; // openrouter's isolated key keeps it disconnected in both. @@ -543,24 +474,11 @@ func TestConfigurationModelPickerConnectedRules(t *testing.T) { entries []protocol.ModelListEntry }{{"visible", visible.Models}, {"full", full.Models}} { for _, entry := range list.entries { - if entry.Provider == "unsetp" || entry.Provider == "openrouter" { - t.Fatalf("%s list contains entry from a disconnected provider: %+v", list.name, entry) - } - if entry.Ref != entry.Provider+"/"+entry.Model { - t.Fatalf("%s list ref = %q, want the provider/model spelling", list.name, entry.Ref) - } - if entry.Source == "" { - t.Fatalf("%s list entry %s carries no source label", list.name, entry.Ref) - } + testassert.Require(t, entry.Provider != "unsetp" && entry.Provider != "openrouter" && entry.Ref == entry.Provider+"/"+entry.Model && entry.Source != "", "%s list contains entry from a disconnected provider: %+v; %s list ref = %q, want the provider/model spelling; %s list entry %s carries no source label", list.name, entry, list.name, entry.Ref, list.name, entry.Ref) } } for _, entry := range visible.Models { - if entry.Hidden || entry.ProviderHidden { - t.Fatalf("visible list contains hidden entry %+v", entry) - } - if entry.Provider == "prov" && entry.Model == "m" && (entry.DisplayName != "M" || entry.ProviderName != "prov") { - t.Fatalf("prov/m entry display names = (%q, %q), want the effective model and provider names", entry.DisplayName, entry.ProviderName) - } + testassert.Require(t, !entry.Hidden && !entry.ProviderHidden && (entry.Provider != "prov" || entry.Model != "m" || entry.DisplayName == "M" && entry.ProviderName == "prov"), "visible list contains hidden entry %+v; prov/m entry display names = (%q, %q), want the effective model and provider names", entry, entry.DisplayName, entry.ProviderName) } var hiddenOne, incompleteOne bool for _, entry := range full.Models { @@ -571,46 +489,34 @@ func TestConfigurationModelPickerConnectedRules(t *testing.T) { incompleteOne = entry.Provider == "prov" && entry.Incomplete } } - if !hiddenOne || !incompleteOne { - t.Fatalf("full list misses the hidden (%v) or incomplete (%v) entry with its flag", hiddenOne, incompleteOne) - } + testassert.Require(t, hiddenOne && incompleteOne, "full list misses the hidden (%v) or incomplete (%v) entry with its flag", hiddenOne, incompleteOne) // The per-provider model read: every model view of one provider, // including the incomplete and hidden entries with usable flags. models, err := r.listProviderModels(context.Background(), "prov") - if err != nil { - t.Fatalf("listProviderModels: %v", err) - } - if len(models.Models) != 3 { - t.Fatalf("provider models = %d, want all three effective entries", len(models.Models)) - } - if models.ConfigurationRevision.Generation != "1" { - t.Fatalf("provider models revision = %+v, want generation 1", models.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, len(models.Models) == 3 && models.ConfigurationRevision.Generation == "1", "got %v, want %v; provider models revision = %+v, want generation 1", len(models.Models), 3, models.ConfigurationRevision) for _, model := range models.Models { - if model.Usable != (model.ContextWindow > 0) { - t.Fatalf("model %s usable = %v with window %d", model.Id, model.Usable, model.ContextWindow) - } + testassert.Require(t, model.Usable == (model.ContextWindow > 0), "model %s usable = %v with window %d", model.Id, model.Usable, model.ContextWindow) } // Identity rules: an unknown valid provider is the typed unknown // error; an empty identity wraps the shared invalid sentinel. - if _, err := r.getProvider(context.Background(), "absent"); err == nil || !strings.Contains(err.Error(), "unknown provider") { - t.Fatalf("getProvider(absent) = %v, want the typed unknown-provider error", err) + { + _, err := r.getProvider(context.Background(), "absent") + testassert.Require(t, err != nil && strings.Contains(err.Error(), "unknown provider"), "getProvider(absent) = %v, want the typed unknown-provider error", err) } - if _, err := r.getProvider(context.Background(), ""); !strings.Contains(err.Error(), "provider id must be non-empty") { - t.Fatalf("getProvider(empty) = %v, want the shared invalid sentinel", err) + { + _, err := r.getProvider(context.Background(), "") + testassert.Require(t, strings.Contains(err.Error(), "provider id must be non-empty"), "getProvider(empty) = %v, want the shared invalid sentinel", err) } - if _, err := r.listProviderModels(context.Background(), ""); !strings.Contains(err.Error(), "provider id must be non-empty") { - t.Fatalf("listProviderModels(empty) = %v, want the shared invalid sentinel", err) + { + _, err := r.listProviderModels(context.Background(), "") + testassert.Require(t, strings.Contains(err.Error(), "provider id must be non-empty"), "listProviderModels(empty) = %v, want the shared invalid sentinel", err) } detail, err := r.getProvider(context.Background(), "prov") - if err != nil { - t.Fatalf("getProvider(prov): %v", err) - } - if detail.Provider.Id != "prov" || detail.ConfigurationRevision.Generation != "1" { - t.Fatalf("provider detail = %+v, want the projected view at generation 1", detail.Provider) - } + testassert.NoError(t, err) + testassert.Require(t, detail.Provider.Id == "prov" && detail.ConfigurationRevision.Generation == "1", "provider detail = %+v, want the projected view at generation 1", detail.Provider) }) } @@ -625,13 +531,9 @@ func TestConfigurationReadsOwnReturnedValues(t *testing.T) { defer closeProjectionRuntime(r) baseline, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) expected, err := json.Marshal(baseline) - if err != nil { - t.Fatalf("marshal baseline: %v", err) - } + testassert.NoError(t, err) // Mutate everything reachable through the returned view. prov := findProvider(baseline.Providers, "prov") @@ -643,9 +545,7 @@ func TestConfigurationReadsOwnReturnedValues(t *testing.T) { model = &prov.Models[i] } } - if model == nil || model.Cost == nil || model.Cost.Input == nil || model.ExtraBody == nil || len(model.InputModalities) == 0 { - t.Fatalf("prov/m view missing expected references: %+v", model) - } + testassert.Require(t, model != nil && model.Cost != nil && model.Cost.Input != nil && model.ExtraBody != nil && len(model.InputModalities) != 0, "prov/m view missing expected references: %+v", model) *model.Cost.Input = 999 (*model.ExtraBody)["side"] = "mutated" model.InputModalities[0] = "mutated" @@ -658,63 +558,37 @@ func TestConfigurationReadsOwnReturnedValues(t *testing.T) { } fresh, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration again: %v", err) - } + testassert.NoError(t, err) got, err := json.Marshal(fresh) - if err != nil { - t.Fatalf("marshal fresh: %v", err) - } - if string(got) != string(expected) { - t.Fatalf("a mutation of the returned view reached the captured configuration:\n%s\nwant\n%s", got, expected) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == string(expected), "a mutation of the returned view reached the captured configuration:\n%s\nwant\n%s", got, expected) // The model list and provider detail own their values the same way. list, err := r.listModels(context.Background(), protocol.ListModelsParams{All: true}) - if err != nil { - t.Fatalf("listModels: %v", err) - } + testassert.NoError(t, err) listExpected, err := json.Marshal(list) - if err != nil { - t.Fatalf("marshal list: %v", err) - } + testassert.NoError(t, err) if len(list.Models) > 0 { list.Models[0].DisplayName = "mutated" list.Models = nil } detail, err := r.getProvider(context.Background(), "prov") - if err != nil { - t.Fatalf("getProvider: %v", err) - } + testassert.NoError(t, err) detailExpected, err := json.Marshal(detail) - if err != nil { - t.Fatalf("marshal detail: %v", err) - } + testassert.NoError(t, err) detail.Provider.Models[0].Name = "mutated" detail.Provider.Headers["X-Trace"] = "mutated" freshList, err := r.listModels(context.Background(), protocol.ListModelsParams{All: true}) - if err != nil { - t.Fatalf("listModels again: %v", err) - } + testassert.NoError(t, err) gotList, err := json.Marshal(freshList) - if err != nil { - t.Fatalf("marshal fresh list: %v", err) - } - if string(gotList) != string(listExpected) { - t.Fatalf("a mutation of the returned list reached the captured configuration") - } + testassert.NoError(t, err) + testassert.Require(t, string(gotList) == string(listExpected), "a mutation of the returned list reached the captured configuration") freshDetail, err := r.getProvider(context.Background(), "prov") - if err != nil { - t.Fatalf("getProvider again: %v", err) - } + testassert.NoError(t, err) gotDetail, err := json.Marshal(freshDetail) - if err != nil { - t.Fatalf("marshal fresh detail: %v", err) - } - if string(gotDetail) != string(detailExpected) { - t.Fatalf("a mutation of the returned detail reached the captured configuration") - } + testassert.NoError(t, err) + testassert.Require(t, string(gotDetail) == string(detailExpected), "a mutation of the returned detail reached the captured configuration") }) } @@ -728,33 +602,22 @@ func TestConfigurationFailedReloadLeavesClocksUnchanged(t *testing.T) { defer closeProjectionRuntime(r) before, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) warningsBefore, err := r.getWarnings(context.Background()) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } + testassert.NoError(t, err) writeServiceFile(t, e.configPath, "{not json") - if _, err := r.Reload(context.Background()); err == nil { - t.Fatal("Reload over a malformed configuration succeeded, want failure") + { + _, err := r.Reload(context.Background()) + testassert.Require(t, err != nil, "Reload over a malformed configuration succeeded, want failure") } after, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration after failed reload: %v", err) - } - if after.ConfigurationRevision.Generation != before.ConfigurationRevision.Generation { - t.Fatalf("configuration generation advanced on a failed reload: %s → %s", before.ConfigurationRevision.Generation, after.ConfigurationRevision.Generation) - } + testassert.NoError(t, err) + testassert.Equal(t, after.ConfigurationRevision.Generation, before.ConfigurationRevision.Generation) warningsAfter, err := r.getWarnings(context.Background()) - if err != nil { - t.Fatalf("getWarnings after failed reload: %v", err) - } - if warningsAfter.WarningsRevision.Revision != warningsBefore.WarningsRevision.Revision { - t.Fatalf("warning revision advanced on a failed reload: %s → %s", warningsBefore.WarningsRevision.Revision, warningsAfter.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, warningsAfter.WarningsRevision.Revision, warningsBefore.WarningsRevision.Revision) }) } @@ -771,35 +634,28 @@ func TestConfigurationSetupWarningsLifecycle(t *testing.T) { r, _ := openConfigurationRuntimeWithEnv(t, store, e, disconnectedDoc, configurationAgentsDocument) defer closeProjectionRuntime(r) setupSub, err := r.Subscribe(64) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(setupSub.Close) // Disconnected: no_provider plus the primary model's unavailability. snap, err := r.getWarnings(context.Background()) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } + testassert.NoError(t, err) var setupKinds []string for _, warning := range snap.Warnings { if warning.Source == "runtime:setup" { setupKinds = append(setupKinds, warning.Kind) } } - if len(setupKinds) != 2 || setupKinds[0] != "setup_no_provider" || setupKinds[1] != "setup_model_unavailable" { - t.Fatalf("setup warnings = %v, want no_provider then model_unavailable", setupKinds) - } + testassert.Require(t, len(setupKinds) == 2 && setupKinds[0] == "setup_no_provider" && setupKinds[1] == "setup_model_unavailable", "setup warnings = %v, want no_provider then model_unavailable", setupKinds) // Connect (the managed key writes through the retained path) and // reload: the setup warnings clear and the publication carries its // warning hint — one configuration event then one runtime-scoped // warning event with the advanced revision. - if err := r.managedEnv.Set("UNSET_SETUP_KEY", "setup-secret"); err != nil { - t.Fatalf("managed set: %v", err) - } - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + testassert.NoError(t, r.managedEnv.Set("UNSET_SETUP_KEY", "setup-secret")) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } clearingConfig, ok := nextEvent(t, setupSub) if !ok || eventKind(t, clearingConfig) != "configuration_changed" { @@ -810,34 +666,23 @@ func TestConfigurationSetupWarningsLifecycle(t *testing.T) { t.Fatalf("clearing reload's warning event = %s (ok=%v), want the required warning hint after the configuration event", eventJSON(t, clearingWarning), ok) } clearingBody, err := clearingWarning.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } - if scopeKind(t, clearingBody.Scope) != "runtime" { - t.Fatalf("clearing reload's warning scope = %+v, want the runtime scope", clearingBody.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, scopeKind(t, clearingBody.Scope) == "runtime", "clearing reload's warning scope = %+v, want the runtime scope", clearingBody.Scope) clear, err := r.getWarnings(context.Background()) - if err != nil { - t.Fatalf("getWarnings after connect: %v", err) - } + testassert.NoError(t, err) for _, warning := range clear.Warnings { - if warning.Source == "runtime:setup" { - t.Fatalf("setup warning survived the connected reload: %+v", warning) - } + testassert.Require(t, warning.Source != "runtime:setup", "setup warning survived the connected reload: %+v", warning) } revision, err := strconv.ParseUint(clear.WarningsRevision.Revision, 10, 64) - if err != nil || revision == 0 { - t.Fatalf("warning revision after the clearing reload = %v, want an advanced nonzero counter", clear.WarningsRevision.Revision) - } + testassert.Require(t, err == nil && revision != 0, "warning revision after the clearing reload = %v, want an advanced nonzero counter", clear.WarningsRevision.Revision) - if clearingBody.WarningsRevision.Revision != clear.WarningsRevision.Revision { - t.Fatalf("clearing reload's warning hint revision = %q, want the store's current value %q", clearingBody.WarningsRevision.Revision, clear.WarningsRevision.Revision) - } + testassert.Equal(t, clearingBody.WarningsRevision.Revision, clear.WarningsRevision.Revision) // Idempotent reports advance nothing: reload again with no change — // exactly one configuration event and NO warning hint. - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload again: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } idempotentConfig, ok := nextEvent(t, setupSub) if !ok || eventKind(t, idempotentConfig) != "configuration_changed" { @@ -845,12 +690,8 @@ func TestConfigurationSetupWarningsLifecycle(t *testing.T) { } assertNoEvent(t, setupSub) idempotent, err := r.getWarnings(context.Background()) - if err != nil { - t.Fatalf("getWarnings after idempotent reload: %v", err) - } - if idempotent.WarningsRevision.Revision != clear.WarningsRevision.Revision { - t.Fatalf("warning revision advanced on an unchanged publication: %s → %s", clear.WarningsRevision.Revision, idempotent.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, idempotent.WarningsRevision.Revision, clear.WarningsRevision.Revision) // A catalog warning rides the published candidate; reads enumerate // the actual stored groups deterministically — global groups ordered @@ -858,13 +699,12 @@ func TestConfigurationSetupWarningsLifecycle(t *testing.T) { // provider also restores the setup group. brokenDoc := strings.Replace(disconnectedDoc, `"base_url":"https://prov.test/v1"`, `"base_url":""`, 1) writeServiceFile(t, e.configPath, brokenDoc) - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload broken: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } broken, err := r.getWarnings(context.Background()) - if err != nil { - t.Fatalf("getWarnings broken: %v", err) - } + testassert.NoError(t, err) var sources []string var catalogSeen, setupSeen bool for _, warning := range broken.Warnings { @@ -876,12 +716,8 @@ func TestConfigurationSetupWarningsLifecycle(t *testing.T) { setupSeen = true } } - if !slices.IsSorted(sources) || len(sources) < 2 || sources[0] != "runtime:agents" { - t.Fatalf("broken-reload warning order = %v, want the lexical global groups with the agents group first", sources) - } - if !catalogSeen || !setupSeen { - t.Fatalf("broken-reload warnings = %+v, want both the catalog and setup groups", broken.Warnings) - } + testassert.Require(t, slices.IsSorted(sources) && len(sources) >= 2 && sources[0] == "runtime:agents", "broken-reload warning order = %v, want the lexical global groups with the agents group first", sources) + testassert.Require(t, catalogSeen && setupSeen, "broken-reload warnings = %+v, want both the catalog and setup groups", broken.Warnings) }) } @@ -898,9 +734,7 @@ func TestConfigurationRosterRequiredArrays(t *testing.T) { defer closeProjectionRuntime(r) view, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) type rosterProbe struct { Name string `json:"name"` Model string `json:"model"` @@ -912,22 +746,16 @@ func TestConfigurationRosterRequiredArrays(t *testing.T) { Readonly bool `json:"readonly"` } data, err := json.Marshal(view.Agents) - if err != nil { - t.Fatalf("marshal roster: %v", err) - } + testassert.NoError(t, err) var probes []rosterProbe - if err := json.Unmarshal(data, &probes); err != nil { - t.Fatalf("unmarshal roster: %v", err) - } + testassert.NoError(t, json.Unmarshal(data, &probes)) byName := make(map[string]rosterProbe, len(probes)) for _, probe := range probes { for _, field := range []struct { name string value json.RawMessage }{{"tools", probe.Tools}, {"capabilities", probe.Capabilities}} { - if field.value == nil || string(field.value) == "null" { - t.Fatalf("agent %q's required %s member = %q, want a present array", probe.Name, field.name, field.value) - } + testassert.Require(t, field.value != nil && string(field.value) != "null", "agent %q's required %s member = %q, want a present array", probe.Name, field.name, field.value) } byName[probe.Name] = probe } @@ -935,27 +763,16 @@ func TestConfigurationRosterRequiredArrays(t *testing.T) { if !ok { t.Fatalf("roster misses the compact builtin: %v", probeNames(probes, func(p rosterProbe) string { return p.Name })) } - if string(compact.Tools) != "[]" || string(compact.Capabilities) != "[]" { - t.Fatalf("compact arrays = tools %s capabilities %s, want the required empty []", compact.Tools, compact.Capabilities) - } + testassert.Require(t, string(compact.Tools) == "[]" && string(compact.Capabilities) == "[]", "compact arrays = tools %s capabilities %s, want the required empty []", compact.Tools, compact.Capabilities) // The retained resolution chain: compact inherits the primary's model // through secondary; the projection shows the resolved selection. - if compact.Model != "prov/m" { - t.Fatalf("compact model = %q, want the retained inherited selection", compact.Model) - } + testassert.Equal(t, compact.Model, "prov/m") worker, ok := byName["worker"] if !ok { t.Fatalf("roster misses the custom sibling: %v", probeNames(probes, func(p rosterProbe) string { return p.Name })) } - if string(worker.Tools) != `["prod_read"]` || string(worker.Capabilities) != "[]" { - t.Fatalf("worker arrays = tools %s capabilities %s, want the nonempty tools and empty [] capabilities", worker.Tools, worker.Capabilities) - } - if worker.WriteDir != "/ws" || !worker.Subagent || !worker.Readonly || worker.Description != "w" { - t.Fatalf("worker public fields = %+v, want the normalized definition values", worker) - } - if strings.Contains(string(data), "system_prompt") || strings.Contains(string(data), "lsp") || strings.Contains(string(data), "prompt\":") { - t.Fatalf("roster exposes private definition fields: %s", data) - } + testassert.Require(t, string(worker.Tools) == `["prod_read"]` && string(worker.Capabilities) == "[]" && worker.WriteDir == "/ws" && worker.Subagent && worker.Readonly && worker.Description == "w", "worker arrays = tools %s capabilities %s, want the nonempty tools and empty [] capabilities; worker public fields = %+v, want the normalized definition values", worker.Tools, worker.Capabilities, worker) + testassert.Require(t, !strings.Contains(string(data), "system_prompt") && !strings.Contains(string(data), "lsp") && !strings.Contains(string(data), "prompt\":"), "roster exposes private definition fields: %s", data) }) } @@ -985,88 +802,54 @@ func TestConfigurationProviderExtrasExactAndOwned(t *testing.T) { exactJSONMember := func(where, field string, value any, want string) { t.Helper() raw, err := json.Marshal(value) - if err != nil { - t.Fatalf("%s: marshal %s: %v", where, field, err) - } - if string(raw) != want { - t.Fatalf("%s: %s = %s, want the exact lexeme %s", where, field, raw, want) - } + testassert.Require(t, err == nil, "%s: marshal %s: %v", where, field, err) + testassert.Require(t, string(raw) == want, "%s: %s = %s, want the exact lexeme %s", where, field, raw, want) } checkExtras := func(where string, view protocol.Provider) { t.Helper() - if view.ExtraBody == nil { - t.Fatalf("%s: prov extra_body missing, want the captured sidecar layer", where) - } - if got := (*view.ExtraBody)["side"]; got != "value" { - t.Fatalf("%s: prov extra_body side = %v, want the captured value", where, got) + testassert.Require(t, view.ExtraBody != nil, "%s: prov extra_body missing, want the captured sidecar layer", where) + { + got := (*view.ExtraBody)["side"] + testassert.Require(t, got == "value", "%s: prov extra_body side = %v, want the captured value", where, got) } exactJSONMember(where, "extra_body.big", (*view.ExtraBody)["big"], "9007199254740993") - if view.ProtocolMetadata == nil || view.ProtocolMetadata.Family == nil || *view.ProtocolMetadata.Family != "testfam" { - t.Fatalf("%s: prov protocol metadata family = %+v, want testfam", where, view.ProtocolMetadata) - } - if view.ProtocolMetadata.MustPreserve == nil || !slices.Equal(*view.ProtocolMetadata.MustPreserve, []string{"x_keep"}) { - t.Fatalf("%s: prov must_preserve = %v, want [x_keep]", where, view.ProtocolMetadata.MustPreserve) - } - if view.ProtocolMetadata.Drop == nil || !slices.Equal(*view.ProtocolMetadata.Drop, []string{"x_drop"}) { - t.Fatalf("%s: prov drop = %v, want [x_drop]", where, view.ProtocolMetadata.Drop) - } + testassert.Require(t, view.ProtocolMetadata != nil && view.ProtocolMetadata.Family != nil && *view.ProtocolMetadata.Family == "testfam", "%s: prov protocol metadata family = %+v, want testfam", where, view.ProtocolMetadata) + testassert.Require(t, view.ProtocolMetadata.MustPreserve != nil && slices.Equal(*view.ProtocolMetadata.MustPreserve, []string{"x_keep"}) && view.ProtocolMetadata.Drop != nil && slices.Equal(*view.ProtocolMetadata.Drop, []string{"x_drop"}), "%s: prov must_preserve = %v, want [x_keep]; %s: prov drop = %v, want [x_drop]", where, view.ProtocolMetadata.MustPreserve, where, view.ProtocolMetadata.Drop) } configView, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) prov := findProvider(configView.Providers, "prov") checkExtras("getConfiguration", prov) // The remaining provider/model fields the sidecar fixture carries: // the effective system role and the streamed-usage flag. - if prov.SystemRole != protocol.SystemRole("system") || !prov.UsageInStream { - t.Fatalf("prov system_role = %q usage_in_stream = %v, want the effective defaults", prov.SystemRole, prov.UsageInStream) - } + testassert.Require(t, prov.SystemRole == protocol.SystemRole("system") && prov.UsageInStream, "prov system_role = %q usage_in_stream = %v, want the effective defaults", prov.SystemRole, prov.UsageInStream) for _, model := range prov.Models { - if model.SystemRole != protocol.SystemRole("system") || !model.UsageInStream { - t.Fatalf("prov/%s system_role = %q usage_in_stream = %v, want the effective defaults", model.Id, model.SystemRole, model.UsageInStream) - } + testassert.Require(t, model.SystemRole == protocol.SystemRole("system") && model.UsageInStream, "prov/%s system_role = %q usage_in_stream = %v, want the effective defaults", model.Id, model.SystemRole, model.UsageInStream) } list, err := r.listProviders(context.Background()) - if err != nil { - t.Fatalf("listProviders: %v", err) - } + testassert.NoError(t, err) checkExtras("listProviders", findProvider(list.Providers, "prov")) detail, err := r.getProvider(context.Background(), "prov") - if err != nil { - t.Fatalf("getProvider: %v", err) - } + testassert.NoError(t, err) checkExtras("getProvider", detail.Provider) - if configView.ConfigurationRevision.Generation != list.ConfigurationRevision.Generation || - list.ConfigurationRevision.Generation != detail.ConfigurationRevision.Generation { - t.Fatalf("the three read paths disagree on the generation: %q/%q/%q", - configView.ConfigurationRevision.Generation, list.ConfigurationRevision.Generation, detail.ConfigurationRevision.Generation) - } + testassert.Require(t, configView.ConfigurationRevision.Generation == list.ConfigurationRevision.Generation && list.ConfigurationRevision.Generation == detail.ConfigurationRevision.Generation, "the three read paths disagree on the generation: %q/%q/%q", configView.ConfigurationRevision.Generation, list.ConfigurationRevision.Generation, detail.ConfigurationRevision.Generation) // Ownership: the sidecar members are owned against an independent // serialized baseline — nested member mutations never reach the // captured configuration. baseline, err := json.Marshal(detail.Provider) - if err != nil { - t.Fatalf("marshal baseline: %v", err) - } + testassert.NoError(t, err) (*detail.Provider.ExtraBody)["side"] = "mutated" (*detail.Provider.ExtraBody)["big"] = nil *detail.Provider.ProtocolMetadata.Family = "mutated" (*detail.Provider.ProtocolMetadata.MustPreserve)[0] = "mutated" (*detail.Provider.ProtocolMetadata.Drop)[0] = "mutated" fresh, err := r.getProvider(context.Background(), "prov") - if err != nil { - t.Fatalf("getProvider again: %v", err) - } + testassert.NoError(t, err) got, err := json.Marshal(fresh.Provider) - if err != nil { - t.Fatalf("marshal fresh: %v", err) - } - if string(got) != string(baseline) { - t.Fatalf("a mutation of the returned provider's sidecar members reached the captured configuration:\n%s\nwant\n%s", got, baseline) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == string(baseline), "a mutation of the returned provider's sidecar members reached the captured configuration:\n%s\nwant\n%s", got, baseline) // The model rows keep their own owned cost/extra/modality values with // raw number lexemes. @@ -1076,18 +859,11 @@ func TestConfigurationProviderExtrasExactAndOwned(t *testing.T) { model = &fresh.Provider.Models[i] } } - if model == nil || model.Cost == nil || model.Cost.Input == nil || model.ExtraBody == nil { - t.Fatalf("prov/m view missing expected sidecar references: %+v", model) - } + testassert.Require(t, model != nil && model.Cost != nil && model.Cost.Input != nil && model.ExtraBody != nil, "prov/m view missing expected sidecar references: %+v", model) if *model.Cost.Input != 1.5 { t.Fatalf("prov/m cost input = %v, want the captured price", *model.Cost.Input) } - if model.MaxOutputTokens != 0 { - t.Fatalf("prov/m max_output_tokens = %d, want the unset default", model.MaxOutputTokens) - } - if model.ProtocolMetadata == nil || model.ProtocolMetadata.Family == nil || *model.ProtocolMetadata.Family != "testfam" { - t.Fatalf("prov/m protocol metadata = %+v, want the effective model-over-provider merge", model.ProtocolMetadata) - } + testassert.Require(t, model.MaxOutputTokens == 0 && model.ProtocolMetadata != nil && model.ProtocolMetadata.Family != nil && *model.ProtocolMetadata.Family == "testfam", "got %v, want %v; prov/m protocol metadata = %+v, want the effective model-over-provider merge", model.MaxOutputTokens, 0, model.ProtocolMetadata) exactJSONMember("getProvider model", "extra_body.mside", (*model.ExtraBody)["mside"], "1") }) } @@ -1109,53 +885,40 @@ func TestConfigurationModelSourceExactThroughCapturedCatalog(t *testing.T) { // Before any cache record: the bundled provider's shipped models are // bundled and the user-layer model is user; nothing is discovered. before, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration: %v", err) - } + testassert.NoError(t, err) openrouter := findProvider(before.Providers, "openrouter") - if !openrouter.Builtin { - t.Fatal("openrouter builtin = false, want the bundled identity") - } + testassert.Require(t, openrouter.Builtin, "openrouter builtin = false, want the bundled identity") wantBefore := map[string]string{"m": "user"} for _, model := range openrouter.Models { want := wantBefore[model.Id] if want == "" { want = "bundled" // every shipped catalog model } - if model.Source != protocol.ModelSource(want) { - t.Fatalf("openrouter/%s source = %q, want %q", model.Id, model.Source, want) - } + testassert.Require(t, model.Source == protocol.ModelSource(want), "openrouter/%s source = %q, want %q", model.Id, model.Source, want) } // Seed a bound cache record from the effective merged transport the // first build produced, then reload: the rebuild adopts the discovered // model and labels it from the same accepted record. transport := catalog.Transport{BaseURL: openrouter.BaseUrl, APIKeyEnv: openrouter.ApiKeyEnv, Headers: openrouter.Headers} - if err := catalog.WriteDiscoveryCache(e.home, "openrouter", transport, + testassert.NoError(t, catalog.WriteDiscoveryCache(e.home, "openrouter", transport, catalog.DiscoveredProvider{Models: map[string]catalog.DiscoveredModel{"found-via-cache": {Name: "Found Via Cache", ContextWindow: 4096}}}, - time.Now().UTC()); err != nil { - t.Fatalf("WriteDiscoveryCache: %v", err) - } - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + time.Now().UTC())) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } after, err := r.getConfiguration(context.Background()) - if err != nil { - t.Fatalf("getConfiguration after reload: %v", err) - } + testassert.NoError(t, err) reloaded := findProvider(after.Providers, "openrouter") - if reloaded.Id != "openrouter" { - t.Fatal("reloaded openrouter view missing") - } + testassert.Require(t, reloaded.Id == "openrouter", "reloaded openrouter view missing") wantAfter := map[string]string{"m": "user", "found-via-cache": "discovered"} for _, model := range reloaded.Models { want := wantAfter[model.Id] if want == "" { want = "bundled" } - if model.Source != protocol.ModelSource(want) { - t.Fatalf("openrouter/%s source = %q, want %q", model.Id, model.Source, want) - } + testassert.Require(t, model.Source == protocol.ModelSource(want), "openrouter/%s source = %q, want %q", model.Id, model.Source, want) } discovered := reloaded.Models[0] for i := range reloaded.Models { @@ -1163,26 +926,18 @@ func TestConfigurationModelSourceExactThroughCapturedCatalog(t *testing.T) { discovered = reloaded.Models[i] } } - if discovered.Name != "Found Via Cache" || discovered.ContextWindow != 4096 || !discovered.Usable { - t.Fatalf("discovered model view = %+v, want the record's adopted identity", discovered) - } + testassert.Require(t, discovered.Name == "Found Via Cache" && discovered.ContextWindow == 4096 && discovered.Usable, "discovered model view = %+v, want the record's adopted identity", discovered) // The picker carries the same exact labels from the captured catalog, // still connected-only: the disconnected openrouter appears in neither // list, and the connected custom provider's model is user. list, err := r.listModels(context.Background(), protocol.ListModelsParams{All: true}) - if err != nil { - t.Fatalf("listModels: %v", err) - } + testassert.NoError(t, err) pickerLabels := map[string]string{} for _, entry := range list.Models { - if entry.Provider == "openrouter" { - t.Fatalf("the picker listed a disconnected provider's model: %+v", entry) - } + testassert.Require(t, entry.Provider != "openrouter", "the picker listed a disconnected provider's model: %+v", entry) pickerLabels[entry.Provider+"/"+entry.Model] = string(entry.Source) } - if pickerLabels["prov/m"] != "user" { - t.Fatalf("picker labels = %v, want the exact user label for the connected provider's model", pickerLabels) - } + testassert.Require(t, pickerLabels["prov/m"] == "user", "picker labels = %v, want the exact user label for the connected provider's model", pickerLabels) }) } diff --git a/runtime/protocol_conversation_test.go b/runtime/protocol_conversation_test.go index 3a55881d..66862bf4 100644 --- a/runtime/protocol_conversation_test.go +++ b/runtime/protocol_conversation_test.go @@ -15,6 +15,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -80,14 +81,10 @@ func convResultFact(entryID, assistantID, callID, status, content, metadata stri // Session/entry binding, stable per binding and distinct per binding. func TestConversationItemIDKnownVector(t *testing.T) { const want = "4EngWdrqzp5Q-GPdY7_DFw" - if got := projectItemID(convSessionID, convEntryID); got != want { - t.Fatalf("projectItemID = %q, want the pinned vector %q", got, want) - } + testassert.Equal(t, projectItemID(convSessionID, convEntryID), want) otherSession := projectItemID("ffffffffffffffffffffffffffffffff", convEntryID) otherEntry := projectItemID(convSessionID, "ffffffffffffffffffffffffffffffff") - if otherSession == want || otherEntry == want || otherSession == otherEntry { - t.Fatalf("derived identities collide: %q %q %q", want, otherSession, otherEntry) - } + testassert.Require(t, otherSession != want && otherEntry != want && otherSession != otherEntry, "derived identities collide: %q %q %q", want, otherSession, otherEntry) } // TestConversationProjectionDeterministicItemKinds proves the exact item @@ -151,152 +148,68 @@ func TestConversationProjectionDeterministicItemKinds(t *testing.T) { }, } items, err := projectConversation(convSessionID, facts) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } - if len(items) != 7 { // the tool result stays absorbed - t.Fatalf("items = %d, want exactly the 7 projected facts", len(items)) - } + testassert.NoError(t, err) + // the tool result stays absorbed + testassert.Equal(t, len(items), 7) // The input item keeps its literal text and per-part extras. input, err := items[0].AsInputItem() - if err != nil { - t.Fatalf("AsInputItem: %v", err) - } - if input.ItemId != projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01") || - input.Origin != protocol.InputOriginUser || - !input.CommittedAt.Equal(convTime) || input.OperationId == nil || *input.OperationId != "op-1" { - t.Fatalf("input item = %+v, want the op-1 user input at the pinned identity", input) - } - if len(input.Content) != 3 { - t.Fatalf("input content = %d parts, want 3", len(input.Content)) - } + testassert.NoError(t, err) + testassert.Require(t, input.ItemId == projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01") && input.Origin == protocol.InputOriginUser && input.CommittedAt.Equal(convTime) && input.OperationId != nil && *input.OperationId == "op-1" && len(input.Content) == 3, "input item = %+v, want the op-1 user input at the pinned identity; got %v, want %v", input, len(input.Content), 3) textPart, err := input.Content[0].AsTextPart() - if err != nil || textPart.Text != "Operation interrupted." { - t.Fatalf("first part = %+v err %v, want the literal user text", textPart, err) - } + testassert.Require(t, err == nil && textPart.Text == "Operation interrupted.", "first part = %+v err %v, want the literal user text", textPart, err) imagePart, err := input.Content[1].AsImageURLPart() - if err != nil || imagePart.Url != "https://img.test/p.png" { - t.Fatalf("second part = %+v err %v, want the image part", imagePart, err) - } + testassert.Require(t, err == nil && imagePart.Url == "https://img.test/p.png", "second part = %+v err %v, want the image part", imagePart, err) opaquePart, err := input.Content[2].AsOpaquePart() - if err != nil || opaquePart.OpaqueWireType != "thinking" || opaquePart.Extra == nil || string((*opaquePart.Extra)["depth"]) != `1e1000` { - t.Fatalf("third part = %+v err %v, want the opaque part with its raw extra", opaquePart, err) - } + testassert.Require(t, err == nil && opaquePart.OpaqueWireType == "thinking" && opaquePart.Extra != nil && string((*opaquePart.Extra)["depth"]) == `1e1000`, "third part = %+v err %v, want the opaque part with its raw extra", opaquePart, err) // The assistant item is indivisible: both calls in published order, the // first carrying its terminal outcome, the second pending. assistant, err := items[1].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } - if assistant.Status != protocol.AssistantItemStatusCompleted || assistant.Source != "prov/m" || - assistant.ItemId != projectItemID(convSessionID, assistantID) || - !assistant.CommittedAt.Equal(convTime) { - t.Fatalf("assistant item = %+v, want the completed prov/m item at the pinned identity and commit time", assistant) - } - if assistant.Extra == nil || string((*assistant.Extra)["model_extra"]) != `{"k":1}` { - t.Fatalf("assistant extra = %v, want the validated message-level extra", assistant.Extra) - } - if assistant.Refusal == nil || *assistant.Refusal != "no" { - t.Fatalf("assistant refusal = %v, want the projected refusal", assistant.Refusal) - } + testassert.NoError(t, err) + testassert.Require(t, assistant.Status == protocol.AssistantItemStatusCompleted && assistant.Source == "prov/m" && assistant.ItemId == projectItemID(convSessionID, assistantID) && assistant.CommittedAt.Equal(convTime) && assistant.Extra != nil && string((*assistant.Extra)["model_extra"]) == `{"k":1}` && assistant.Refusal != nil && *assistant.Refusal == "no", "assistant item = %+v, want the completed prov/m item at the pinned identity and commit time; assistant extra = %v, want the validated message-level extra; assistant refusal = %v, want the projected refusal", assistant, assistant.Extra, assistant.Refusal) textPart, err = assistant.Content[0].AsTextPart() - if err != nil || textPart.Extra == nil || string((*textPart.Extra)["grade"]) != `9007199254740993` { - t.Fatalf("assistant part extra = %+v err %v, want the raw number preserved", textPart, err) - } - if len(assistant.ToolCalls) != 2 || - assistant.ToolCalls[0].Id != "call-1" || assistant.ToolCalls[1].Id != "call-2" { - t.Fatalf("tool calls = %+v, want call-1 then call-2 in published order", assistant.ToolCalls) - } + testassert.Require(t, err == nil && textPart.Extra != nil && string((*textPart.Extra)["grade"]) == `9007199254740993`, "assistant part extra = %+v err %v, want the raw number preserved", textPart, err) + testassert.Require(t, len(assistant.ToolCalls) == 2 && assistant.ToolCalls[0].Id == "call-1" && assistant.ToolCalls[1].Id == "call-2", "tool calls = %+v, want call-1 then call-2 in published order", assistant.ToolCalls) call1 := assistant.ToolCalls[0] - if call1.Arguments != `{"n":9007199254740993}` { - t.Fatalf("call-1 arguments = %q, want the decoded raw text", call1.Arguments) - } + testassert.Equal(t, call1.Arguments, `{"n":9007199254740993}`) // The call-level extra is its own level, independent of the message and // content-part extras, with the same raw-number fidelity. - if call1.Extra == nil || string((*call1.Extra)["tool_extra"]) != `{"tool_n":9007199254740993}` { - t.Fatalf("call-1 extra = %v, want the call-level extra with its raw number", call1.Extra) - } - if call1.NormalizedArguments == nil || string((*call1.NormalizedArguments)["x"]) != `1e1000` { - t.Fatalf("call-1 normalized arguments = %v, want the raw object", call1.NormalizedArguments) - } - if call1.Status == nil || *call1.Status != protocol.ToolCallStatusSuccess || - call1.Content == nil || *call1.Content != "" { - t.Fatalf("call-1 terminal outcome = (%v, %v), want success with a present empty content", call1.Status, call1.Content) - } - if call1.Metadata == nil || string(*call1.Metadata) != `{"rows":1e1000,"zero":0}` { - t.Fatalf("call-1 metadata = %v, want the raw value preserved", call1.Metadata) - } + testassert.Require(t, call1.Extra != nil && string((*call1.Extra)["tool_extra"]) == `{"tool_n":9007199254740993}` && call1.NormalizedArguments != nil && string((*call1.NormalizedArguments)["x"]) == `1e1000` && call1.Status != nil && *call1.Status == protocol.ToolCallStatusSuccess && call1.Content != nil && *call1.Content == "" && call1.Metadata != nil && string(*call1.Metadata) == `{"rows":1e1000,"zero":0}`, "call-1 extra = %v, want the call-level extra with its raw number; call-1 normalized arguments = %v, want the raw object; call-1 terminal outcome = (%v, %v), want success with a present empty content; call-1 metadata = %v, want the raw value preserved", call1.Extra, call1.NormalizedArguments, call1.Status, call1.Content, call1.Metadata) call2 := assistant.ToolCalls[1] - if call2.Status != nil || call2.Content != nil || call2.Metadata != nil || call2.NormalizedArguments != nil { - t.Fatalf("pending call-2 = %+v, want every terminal member omitted", call2) - } - if call2.Arguments != `[]` { - t.Fatalf("call-2 arguments = %q, want the decoded raw text", call2.Arguments) - } + testassert.Require(t, call2.Status == nil && call2.Content == nil && call2.Metadata == nil && call2.NormalizedArguments == nil && call2.Arguments == `[]`, "pending call-2 = %+v, want every terminal member omitted; got %v, want %v", call2, call2.Arguments, `[]`) // The signals carry their typed subtypes; the background completion names // its related member and no operation. Every kind's item identity is the // fixture entry bound to this fixture Session's namespace. interruption, err := items[2].AsSignalItem() - if err != nil || interruption.Subtype != protocol.SignalItemSubtypeInterruption || - interruption.Content != "Operation interrupted." || interruption.OperationId == nil { - t.Fatalf("interruption item = %+v err %v, want the typed interruption signal", interruption, err) - } - if interruption.ItemId != projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa05") { - t.Fatalf("interruption item_id = %q, want the fixture entry's namespace identity", interruption.ItemId) - } + testassert.Require(t, err == nil && interruption.Subtype == protocol.SignalItemSubtypeInterruption && interruption.Content == "Operation interrupted." && interruption.OperationId != nil, "interruption item = %+v err %v, want the typed interruption signal", interruption, err) + testassert.Require(t, interruption.ItemId == projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa05"), "interruption item_id = %q, want the fixture entry's namespace identity", interruption.ItemId) completion, err := items[3].AsSignalItem() - if err != nil || completion.Subtype != protocol.SignalItemSubtypeBackgroundCompletion || - completion.Content != "the child finished" || completion.OperationId != nil || - completion.RelatedMember == nil || completion.RelatedMember.Kind != protocol.BackgroundMemberKindChild || completion.RelatedMember.Id != "dddddddddddddddddddddddddddddd07" { - t.Fatalf("background completion item = %+v err %v, want the operationless typed signal with its member", completion, err) - } - if completion.ItemId != projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa06") { - t.Fatalf("completion item_id = %q, want the fixture entry's namespace identity", completion.ItemId) - } + testassert.Require(t, err == nil && completion.Subtype == protocol.SignalItemSubtypeBackgroundCompletion && completion.Content == "the child finished" && completion.OperationId == nil && completion.RelatedMember != nil && completion.RelatedMember.Kind == protocol.BackgroundMemberKindChild && completion.RelatedMember.Id == "dddddddddddddddddddddddddddddd07", "background completion item = %+v err %v, want the operationless typed signal with its member", completion, err) + testassert.Require(t, completion.ItemId == projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa06"), "completion item_id = %q, want the fixture entry's namespace identity", completion.ItemId) // The compaction and terminal outcomes, each at its own entry's identity. compaction, err := items[4].AsCompactionItem() - if err != nil || compaction.Summary != "summary two" || compaction.Model != "prov/m2" || - compaction.OperationId == nil || *compaction.OperationId != "op-2" { - t.Fatalf("compaction item = %+v err %v, want the op-2 compaction", compaction, err) - } - if compaction.ItemId != projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa08") { - t.Fatalf("compaction item_id = %q, want the fixture entry's namespace identity", compaction.ItemId) - } + testassert.Require(t, err == nil && compaction.Summary == "summary two" && compaction.Model == "prov/m2" && compaction.OperationId != nil && *compaction.OperationId == "op-2", "compaction item = %+v err %v, want the op-2 compaction", compaction, err) + testassert.Require(t, compaction.ItemId == projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa08"), "compaction item_id = %q, want the fixture entry's namespace identity", compaction.ItemId) failureEnd, err := items[5].AsOperationEndItem() - if err != nil || failureEnd.Status != protocol.OperationEndItemStatusFailure || failureEnd.Detail != "boom" { - t.Fatalf("failure end item = %+v err %v, want the failure detail", failureEnd, err) - } - if failureEnd.ItemId != projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa09") { - t.Fatalf("failure end item_id = %q, want the fixture entry's namespace identity", failureEnd.ItemId) - } + testassert.Require(t, err == nil && failureEnd.Status == protocol.OperationEndItemStatusFailure && failureEnd.Detail == "boom", "failure end item = %+v err %v, want the failure detail", failureEnd, err) + testassert.Require(t, failureEnd.ItemId == projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa09"), "failure end item_id = %q, want the fixture entry's namespace identity", failureEnd.ItemId) successEnd, err := items[6].AsOperationEndItem() - if err != nil || successEnd.Status != protocol.OperationEndItemStatusSuccess || successEnd.Detail != "" { - t.Fatalf("success end item = %+v err %v, want the required present empty detail", successEnd, err) - } - if successEnd.ItemId != projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10") { - t.Fatalf("success end item_id = %q, want the fixture entry's namespace identity", successEnd.ItemId) - } + testassert.Require(t, err == nil && successEnd.Status == protocol.OperationEndItemStatusSuccess && successEnd.Detail == "", "success end item = %+v err %v, want the required present empty detail", successEnd, err) + testassert.Require(t, successEnd.ItemId == projectItemID(convSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10"), "success end item_id = %q, want the fixture entry's namespace identity", successEnd.ItemId) // Every item's wire shape is exactly its kind's schema surface, the raw // entry identities never leave the facts, and the required arrays stay // present. for i, item := range items { data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal item %d: %v", i, err) - } + testassert.Require(t, err == nil, "marshal item %d: %v", i, err) for _, raw := range []string{assistantID, resultID, "cccccccccccccccccccccccccccccc04"} { - if strings.Contains(string(data), raw) { - t.Fatalf("item %d wire = %s, want no raw entry identity %q", i, data, raw) - } - } - if strings.Contains(string(data), "arguments_base64") || strings.Contains(string(data), "result_entry_id") { - t.Fatalf("item %d wire = %s, want no internal payload keys", i, data) + testassert.Require(t, !strings.Contains(string(data), raw), "item %d wire = %s, want no raw entry identity %q", i, data, raw) } + testassert.Require(t, !strings.Contains(string(data), "arguments_base64") && !strings.Contains(string(data), "result_entry_id"), "item %d wire = %s, want no internal payload keys", i, data) } assertExactJSONKeys(t, items[0], []string{ "committed_at", "content", "item_id", "kind", "operation_id", "origin", @@ -306,29 +219,17 @@ func TestConversationProjectionDeterministicItemKinds(t *testing.T) { "refusal", "source", "status", "tool_calls", }) empty, err := json.Marshal(items[0]) - if err != nil { - t.Fatalf("marshal input item: %v", err) - } - if !strings.Contains(string(empty), `"content":[`) { - t.Fatalf("input item wire = %s, want a present content array", empty) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(empty), `"content":[`), "input item wire = %s, want a present content array", empty) // Determinism: a second projection of the same snapshot is identical. again, err := projectConversation(convSessionID, facts) - if err != nil { - t.Fatalf("second projectConversation: %v", err) - } + testassert.NoError(t, err) first, err := json.Marshal(items) - if err != nil { - t.Fatalf("marshal first projection: %v", err) - } + testassert.NoError(t, err) second, err := json.Marshal(again) - if err != nil { - t.Fatalf("marshal second projection: %v", err) - } - if string(first) != string(second) { - t.Fatalf("second projection = %s, want the deterministic %s", second, first) - } + testassert.NoError(t, err) + testassert.Require(t, string(first) == string(second), "second projection = %s, want the deterministic %s", second, first) } // TestConversationProjectionSameCallIDAcrossOperations proves the tool-result @@ -370,36 +271,19 @@ func TestConversationProjectionSameCallIDAcrossOperations(t *testing.T) { convResultFact(resultB, assistantB, "call-1", "success", "answer-b", `{"which":"b"}`), } items, err := projectConversation(convSessionID, facts) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } - if len(items) != 2 { // the results stay absorbed - t.Fatalf("items = %d, want exactly the two assistants", len(items)) - } + testassert.NoError(t, err) + // the results stay absorbed + testassert.Equal(t, len(items), 2) first, err := items[0].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } + testassert.NoError(t, err) second, err := items[1].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } + testassert.NoError(t, err) // Independent item identities in the shared Session namespace. - if first.ItemId != projectItemID(convSessionID, assistantA) || - second.ItemId != projectItemID(convSessionID, assistantB) || - first.ItemId == second.ItemId { - t.Fatalf("item identities = (%q, %q), want each assistant's own namespace identity", first.ItemId, second.ItemId) - } + testassert.Require(t, first.ItemId == projectItemID(convSessionID, assistantA) && second.ItemId == projectItemID(convSessionID, assistantB) && first.ItemId != second.ItemId, "item identities = (%q, %q), want each assistant's own namespace identity", first.ItemId, second.ItemId) // Each projected call carries its own assistant's result, never the // same-ID sibling's. - if first.ToolCalls[0].Content == nil || *first.ToolCalls[0].Content != "answer-a" || - first.ToolCalls[0].Metadata == nil || string(*first.ToolCalls[0].Metadata) != `{"which":"a"}` { - t.Fatalf("first call = %+v, want its own answer-a result", first.ToolCalls[0]) - } - if second.ToolCalls[0].Content == nil || *second.ToolCalls[0].Content != "answer-b" || - second.ToolCalls[0].Metadata == nil || string(*second.ToolCalls[0].Metadata) != `{"which":"b"}` { - t.Fatalf("second call = %+v, want its own answer-b result", second.ToolCalls[0]) - } + testassert.Require(t, first.ToolCalls[0].Content != nil && *first.ToolCalls[0].Content == "answer-a" && first.ToolCalls[0].Metadata != nil && string(*first.ToolCalls[0].Metadata) == `{"which":"a"}`, "first call = %+v, want its own answer-a result", first.ToolCalls[0]) + testassert.Require(t, second.ToolCalls[0].Content != nil && *second.ToolCalls[0].Content == "answer-b" && second.ToolCalls[0].Metadata != nil && string(*second.ToolCalls[0].Metadata) == `{"which":"b"}`, "second call = %+v, want its own answer-b result", second.ToolCalls[0]) } // TestConversationProjectionMetadataShapeFidelity pins the tool-owned @@ -430,27 +314,22 @@ func TestConversationProjectionMetadataShapeFidelity(t *testing.T) { convResultFact(resultID, assistantID, "call-1", "success", "out", raw), } items, err := projectConversation(convSessionID, facts) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } + testassert.NoError(t, err) // The generated union's own encoding compacts raw values, so the // fidelity contract is the compacted byte form: every value stays // byte-exact after compaction, never re-decoded to a Go float. var compacted bytes.Buffer - if err := json.Compact(&compacted, []byte(raw)); err != nil { - t.Fatalf("compacting %s: %v", raw, err) + { + err := json.Compact(&compacted, []byte(raw)) + testassert.Require(t, err == nil, "compacting %s: %v", raw, err) } assistant, err := items[0].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } + testassert.NoError(t, err) if assistant.ToolCalls[0].Metadata == nil || string(*assistant.ToolCalls[0].Metadata) != compacted.String() { t.Fatalf("projected metadata = %v, want %s preserved", assistant.ToolCalls[0].Metadata, compacted.String()) } data, err := json.Marshal(items[0]) - if err != nil { - t.Fatalf("marshal item: %v", err) - } + testassert.NoError(t, err) if !strings.Contains(string(data), compacted.String()) { t.Fatalf("item wire = %s, want %s preserved", data, compacted.String()) } @@ -470,17 +349,10 @@ func TestConversationProjectionMetadataShapeFidelity(t *testing.T) { convResultFact(resultID, assistantID, "call-1", "denied", "Permission denied.", ""), } items, err := projectConversation(convSessionID, facts) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } + testassert.NoError(t, err) assistant, err := items[0].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } - if assistant.ToolCalls[0].Metadata != nil || - assistant.ToolCalls[0].Status == nil || *assistant.ToolCalls[0].Status != protocol.ToolCallStatusDenied { - t.Fatalf("denied call = %+v, want the denied status and omitted metadata", assistant.ToolCalls[0]) - } + testassert.NoError(t, err) + testassert.Require(t, assistant.ToolCalls[0].Metadata == nil && assistant.ToolCalls[0].Status != nil && *assistant.ToolCalls[0].Status == protocol.ToolCallStatusDenied, "denied call = %+v, want the denied status and omitted metadata", assistant.ToolCalls[0]) } // TestConversationProjectionRawArgumentText proves the decoded argument text @@ -501,26 +373,14 @@ func TestConversationProjectionRawArgumentText(t *testing.T) { }), } items, err := projectConversation(convSessionID, facts) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } + testassert.NoError(t, err) assistant, err := items[0].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } - if assistant.ToolCalls[0].Arguments != `[1,2,` { - t.Fatalf("malformed arguments = %q, want the raw text ungated", assistant.ToolCalls[0].Arguments) - } - if !strings.ContainsRune(assistant.ToolCalls[1].Arguments, '\uFFFD') { - t.Fatalf("non-UTF-8 arguments = %q, want the replacement text", assistant.ToolCalls[1].Arguments) - } + testassert.NoError(t, err) + testassert.Equal(t, assistant.ToolCalls[0].Arguments, `[1,2,`) + testassert.Require(t, strings.ContainsRune(assistant.ToolCalls[1].Arguments, '\uFFFD'), "non-UTF-8 arguments = %q, want the replacement text", assistant.ToolCalls[1].Arguments) data, err := json.Marshal(items[0]) - if err != nil { - t.Fatalf("marshal item: %v", err) - } - if strings.Contains(string(data), "arguments_base64") { - t.Fatalf("item wire = %s, want no base64 arguments", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), "arguments_base64"), "item wire = %s, want no base64 arguments", data) } // TestConversationProjectionHookEvidenceExcluded proves, over real validated @@ -545,55 +405,37 @@ func TestConversationProjectionHookEvidenceExcluded(t *testing.T) { e.awaitTerminal(session, "op-1", harness.OperationSuccess) runtimeSeen, _ := e.argHooks[0].received() - if len(runtimeSeen) != 1 { - t.Fatalf("argument hook received = %v, want the one real call", runtimeSeen) - } + testassert.Require(t, len(runtimeSeen) == 1, "argument hook received = %v, want the one real call", runtimeSeen) entries, err := store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var hookEntries int for _, entry := range entries { if entry.Kind == harness.EntryHookResult { hookEntries++ } } - if hookEntries != 4 { // the hooky type's four argument hooks, one entry each - t.Fatalf("hook evidence entries = %d, want the four committed hook_result entries", hookEntries) - } + // the hooky type's four argument hooks, one entry each + testassert.Equal(t, hookEntries, 4) view, err := e.sessionView(context.Background(), session) - if err != nil { - t.Fatalf("ReadSessionView: %v", err) - } + testassert.NoError(t, err) hookFacts := historyFactsOf(view.History) for _, fact := range hookFacts { - if fact.Kind == harness.EntryHookResult { - t.Fatal("hook evidence became a client fact") - } + testassert.Require(t, fact.Kind != harness.EntryHookResult, "hook evidence became a client fact") } items, err := projectConversation(view.Header.Identity.SessionID, hookFacts) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } + testassert.NoError(t, err) if len(items) != 4 { // input, publishing assistant, continuation assistant, end t.Fatalf("items = %d (%s), want exactly the conversation items", len(items), itemKinds(t, items)) } for _, item := range items { data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal item: %v", err) - } - if strings.Contains(string(data), "hook") { - t.Fatalf("item wire = %s, want no hook evidence", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), "hook"), "item wire = %s, want no hook evidence", data) } // The publishing assistant keeps the call and its normalized arguments. assistant, err := items[1].AsAssistantItem() - if err != nil || len(assistant.ToolCalls) != 1 || assistant.ToolCalls[0].Id != "call-1" || - assistant.ToolCalls[0].Status == nil || *assistant.ToolCalls[0].Status != protocol.ToolCallStatusError { - t.Fatalf("publishing assistant = %+v err %v, want the settled call-1", assistant, err) - } + testassert.Require(t, err == nil && len(assistant.ToolCalls) == 1 && assistant.ToolCalls[0].Id == "call-1" && assistant.ToolCalls[0].Status != nil && *assistant.ToolCalls[0].Status == protocol.ToolCallStatusError, "publishing assistant = %+v err %v, want the settled call-1", assistant, err) }) } @@ -670,20 +512,12 @@ func TestConversationProjectionToolCallLifecycle(t *testing.T) { bg.e.server.awaitRequests(2) // the parked second call's turn arrived <-toolStarted pending := projectSessionItems(t, bg.r, session) - if len(pending) != 3 { // input, publishing assistant, pending assistant - t.Fatalf("pending items = %d, want the input and both assistants", len(pending)) - } + // input, publishing assistant, pending assistant + testassert.Equal(t, len(pending), 3) first, err := pending[1].AsAssistantItem() - if err != nil || len(first.ToolCalls) != 1 || first.ToolCalls[0].Status == nil || - first.ToolCalls[0].Content == nil || *first.ToolCalls[0].Content != "first" || - first.ToolCalls[0].Metadata == nil || string(*first.ToolCalls[0].Metadata) != `{"n":9007199254740993}` { - t.Fatalf("settled assistant = %+v err %v, want call-1 attached with its metadata", first, err) - } + testassert.Require(t, err == nil && len(first.ToolCalls) == 1 && first.ToolCalls[0].Status != nil && first.ToolCalls[0].Content != nil && *first.ToolCalls[0].Content == "first" && first.ToolCalls[0].Metadata != nil && string(*first.ToolCalls[0].Metadata) == `{"n":9007199254740993}`, "settled assistant = %+v err %v, want call-1 attached with its metadata", first, err) second, err := pending[2].AsAssistantItem() - if err != nil || len(second.ToolCalls) != 1 || second.ToolCalls[0].Id != "call-2" || - second.ToolCalls[0].Status != nil || second.ToolCalls[0].Content != nil || second.ToolCalls[0].Metadata != nil { - t.Fatalf("pending assistant = %+v err %v, want call-2 with every terminal member omitted", second, err) - } + testassert.Require(t, err == nil && len(second.ToolCalls) == 1 && second.ToolCalls[0].Id == "call-2" && second.ToolCalls[0].Status == nil && second.ToolCalls[0].Content == nil && second.ToolCalls[0].Metadata == nil, "pending assistant = %+v err %v, want call-2 with every terminal member omitted", second, err) pendingIDs := itemIDs(t, pending) releaseToolOnce() @@ -692,29 +526,18 @@ func TestConversationProjectionToolCallLifecycle(t *testing.T) { attached := projectSessionItems(t, bg.r, session) attachedIDs := itemIDs(t, attached) - if len(attached) != 6 { // + the two-call assistant, the closing assistant - t.Fatalf("attached items = %d, want the complete conversation", len(attached)) - } + // + the two-call assistant, the closing assistant + testassert.Equal(t, len(attached), 6) for i, id := range pendingIDs { if attachedIDs[i] != id { t.Fatalf("item %d identity drifted from %q to %q", i, id, attachedIDs[i]) } } second, err = attached[2].AsAssistantItem() - if err != nil || second.ToolCalls[0].Status == nil || *second.ToolCalls[0].Status != protocol.ToolCallStatusSuccess || - second.ToolCalls[0].Content == nil || *second.ToolCalls[0].Content != "second" || - second.ToolCalls[0].Metadata == nil || string(*second.ToolCalls[0].Metadata) != `{"late":true}` { - t.Fatalf("late-attached call = %+v err %v, want the second outcome under the same item", second, err) - } + testassert.Require(t, err == nil && second.ToolCalls[0].Status != nil && *second.ToolCalls[0].Status == protocol.ToolCallStatusSuccess && second.ToolCalls[0].Content != nil && *second.ToolCalls[0].Content == "second" && second.ToolCalls[0].Metadata != nil && string(*second.ToolCalls[0].Metadata) == `{"late":true}`, "late-attached call = %+v err %v, want the second outcome under the same item", second, err) pair, err := attached[3].AsAssistantItem() - if err != nil || len(pair.ToolCalls) != 2 || - pair.ToolCalls[0].Id != "call-3" || pair.ToolCalls[1].Id != "call-4" || - pair.ToolCalls[0].Status == nil || pair.ToolCalls[1].Status == nil { - t.Fatalf("multi-call assistant = %+v err %v, want both calls attached in published order", pair, err) - } - if err := bg.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, err == nil && len(pair.ToolCalls) == 2 && pair.ToolCalls[0].Id == "call-3" && pair.ToolCalls[1].Id == "call-4" && pair.ToolCalls[0].Status != nil && pair.ToolCalls[1].Status != nil, "multi-call assistant = %+v err %v, want both calls attached in published order", pair, err) + testassert.NoError(t, bg.r.Close(context.Background())) }) } @@ -724,9 +547,7 @@ func projectSessionItems(t *testing.T, r *Runtime, sessionID string) []protocol. t.Helper() snap := sessionViewThroughRuntime(t, r, sessionID) items, err := projectConversation(sessionID, historyFactsOf(snap.History)) - if err != nil { - t.Fatalf("projectConversation: %v", err) - } + testassert.NoError(t, err) return items } @@ -738,27 +559,12 @@ func projectSessionItems(t *testing.T, r *Runtime, sessionID string) []protocol. // so the one-rule check repeats here; awaitOperation owns the convergence. func submitConvergedThroughRuntime(t *testing.T, r *Runtime, sessionID, operationID, text string, want harness.OperationState) { t.Helper() - var disposition harness.SubmitDisposition - err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, submitErr := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: harness.MessageModeRegular, - }) - if submitErr == nil { - disposition = res.Disposition - } - return submitErr - }) - if err != nil { - t.Fatalf("Submit(%q): %v", operationID, err) - } - switch disposition { + res, err := submitRawThroughRuntime(context.Background(), r, sessionID, operationID, text, harness.MessageModeRegular) + testassert.Require(t, err == nil, "Submit(%q): %v", operationID, err) + switch res.Disposition { case harness.DispositionAdmitted, harness.DispositionSteering, harness.DispositionQueued: default: - t.Fatalf("Submit(%q) = %+v, want an admitted or buffered disposition", operationID, disposition) + t.Fatalf("Submit(%q) = %+v, want an admitted or buffered disposition", operationID, res.Disposition) } awaitOperation(t, r, sessionID, operationID, want) } @@ -775,27 +581,14 @@ func itemIDs(t *testing.T, items []protocol.ConversationItem) []string { // itemIdentity reads one item's kind and identity member. func itemIdentity(t *testing.T, item protocol.ConversationItem) string { t.Helper() - data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal item: %v", err) - } - var probe struct { - Kind string `json:"kind"` - ItemID string `json:"item_id"` - } - if err := json.Unmarshal(data, &probe); err != nil { - t.Fatalf("decode item identity: %v", err) - } - return probe.Kind + ":" + probe.ItemID + return itemKind(t, item) + ":" + wireItemID(t, item) } // itemKind reports one projected item's discriminator kind. func itemKind(t *testing.T, item protocol.ConversationItem) string { t.Helper() kind, err := item.Discriminator() - if err != nil { - t.Fatalf("item discriminator: %v", err) - } + testassert.NoError(t, err) return kind } @@ -824,19 +617,11 @@ func TestConversationProjectionSyntheticToolResult(t *testing.T) { awaitOperation(t, bg.r, session, "op-1", harness.OperationSuccess) items := projectSessionItems(t, bg.r, session) - if len(items) != 4 { // input, assistant with the synthetic result, closing assistant, end - t.Fatalf("items = %d, want the complete conversation", len(items)) - } + // input, assistant with the synthetic result, closing assistant, end + testassert.Equal(t, len(items), 4) assistant, err := items[1].AsAssistantItem() - if err != nil || len(assistant.ToolCalls) != 1 || - assistant.ToolCalls[0].Status == nil || *assistant.ToolCalls[0].Status != protocol.ToolCallStatusError || - assistant.ToolCalls[0].Content == nil || *assistant.ToolCalls[0].Content != "no concrete tools yet" || - assistant.ToolCalls[0].Metadata != nil { - t.Fatalf("synthetic result call = %+v err %v, want the error outcome and no metadata", assistant.ToolCalls, err) - } - if err := bg.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, err == nil && len(assistant.ToolCalls) == 1 && assistant.ToolCalls[0].Status != nil && *assistant.ToolCalls[0].Status == protocol.ToolCallStatusError && assistant.ToolCalls[0].Content != nil && *assistant.ToolCalls[0].Content == "no concrete tools yet" && assistant.ToolCalls[0].Metadata == nil, "synthetic result call = %+v err %v, want the error outcome and no metadata", assistant.ToolCalls, err) + testassert.NoError(t, bg.r.Close(context.Background())) }) } @@ -913,11 +698,7 @@ func TestConversationProjectionSyntheticInterruptedResult(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the first call never started") } - if err := bg.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - return h.Interrupt(ctx, session) - }); err != nil { - t.Fatalf("Interrupt: %v", err) - } + testassert.NoError(t, bg.r.interruptSession(context.Background(), session)) awaitOperation(t, bg.r, session, "op-1", harness.OperationInterruption) items := projectSessionItems(t, bg.r, session) @@ -926,27 +707,15 @@ func TestConversationProjectionSyntheticInterruptedResult(t *testing.T) { t.Fatalf("items = %d (%s), want the complete interrupted conversation", len(items), itemKinds(t, items)) } assistant, err := items[1].AsAssistantItem() - if err != nil || len(assistant.ToolCalls) != 2 { - t.Fatalf("assistant = %+v err %v, want both published calls indivisible", assistant, err) - } + testassert.Require(t, err == nil && len(assistant.ToolCalls) == 2, "assistant = %+v err %v, want both published calls indivisible", assistant, err) started := assistant.ToolCalls[0] - if started.Id != "call-1" || started.Status == nil || *started.Status != protocol.ToolCallStatusSuccess || - started.Content == nil || *started.Content != "first ran" || started.Metadata != nil { - t.Fatalf("started call = %+v, want the real returned outcome", started) - } + testassert.Require(t, started.Id == "call-1" && started.Status != nil && *started.Status == protocol.ToolCallStatusSuccess && started.Content != nil && *started.Content == "first ran" && started.Metadata == nil, "started call = %+v, want the real returned outcome", started) unstarted := assistant.ToolCalls[1] - if unstarted.Id != "call-2" || unstarted.Status == nil || *unstarted.Status != protocol.ToolCallStatusInterrupted || - unstarted.Content == nil || *unstarted.Content != "Tool call interrupted." || unstarted.Metadata != nil { - t.Fatalf("unstarted call = %+v, want the synthetic interrupted result and no metadata", unstarted) - } + testassert.Require(t, unstarted.Id == "call-2" && unstarted.Status != nil && *unstarted.Status == protocol.ToolCallStatusInterrupted && unstarted.Content != nil && *unstarted.Content == "Tool call interrupted." && unstarted.Metadata == nil, "unstarted call = %+v, want the synthetic interrupted result and no metadata", unstarted) signal, err := items[2].AsSignalItem() - if err != nil || signal.Subtype != protocol.SignalItemSubtypeInterruption { - t.Fatalf("signal = %+v err %v, want the typed interruption", signal, err) - } + testassert.Require(t, err == nil && signal.Subtype == protocol.SignalItemSubtypeInterruption, "signal = %+v err %v, want the typed interruption", signal, err) end, err := items[3].AsOperationEndItem() - if err != nil || end.Status != protocol.OperationEndItemStatusInterruption || end.Detail == "" { - t.Fatalf("end = %+v err %v, want the interruption detail", end, err) - } + testassert.Require(t, err == nil && end.Status == protocol.OperationEndItemStatusInterruption && end.Detail != "", "end = %+v err %v, want the interruption detail", end, err) }) } @@ -987,22 +756,13 @@ func TestConversationProjectionErroredPartialAssistant(t *testing.T) { t.Fatalf("items = %d (%s), want the committed order input, assistant, signal, end", len(items), itemKinds(t, items)) } partial, err := items[1].AsAssistantItem() - if err != nil || partial.Status != protocol.AssistantItemStatusErrored { - t.Fatalf("partial assistant = %+v err %v, want the errored status", partial, err) - } + testassert.Require(t, err == nil && partial.Status == protocol.AssistantItemStatusErrored, "partial assistant = %+v err %v, want the errored status", partial, err) textPart, err := partial.Content[0].AsTextPart() - if err != nil || textPart.Text != "partial answer" { - t.Fatalf("partial content = %+v, want the retained fragment", partial.Content) - } + testassert.Require(t, err == nil && textPart.Text == "partial answer", "partial content = %+v, want the retained fragment", partial.Content) continuation, err := items[2].AsSignalItem() - if err != nil || continuation.Subtype != protocol.SignalItemSubtypeModelFailureContinuation || - continuation.Content != "The previous model response failed after partial output. Continue from the retained response." { - t.Fatalf("continuation signal = %+v err %v, want the typed signal with its required content", continuation, err) - } + testassert.Require(t, err == nil && continuation.Subtype == protocol.SignalItemSubtypeModelFailureContinuation && continuation.Content == "The previous model response failed after partial output. Continue from the retained response.", "continuation signal = %+v err %v, want the typed signal with its required content", continuation, err) end, err := items[3].AsOperationEndItem() - if err != nil || end.Status != protocol.OperationEndItemStatusFailure || !strings.Contains(end.Detail, "model down") { - t.Fatalf("end = %+v err %v, want the failure detail", end, err) - } + testassert.Require(t, err == nil && end.Status == protocol.OperationEndItemStatusFailure && strings.Contains(end.Detail, "model down"), "end = %+v err %v, want the failure detail", end, err) }) } @@ -1028,13 +788,12 @@ func TestConversationProjectionInterruptedPartialAssistant(t *testing.T) { // The real provider serves this row through the owning configuration: // the endpoint change publishes before any admission runs. writeServiceFile(t, bg.e.configPath, backgroundLifecycleConfigDocument(stream.URL)) - if _, err := bg.r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := bg.r.Reload(context.Background()) + testassert.NoError(t, err) } sub, err := bg.r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) defer sub.Close() session := bg.session("interrupted") submitThroughRuntime(t, bg.r, session, "op-1", "work") @@ -1046,36 +805,22 @@ func TestConversationProjectionInterruptedPartialAssistant(t *testing.T) { for !sawFragment { select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed before the fragment's progress arrived") - } + testassert.Require(t, ok, "the subscription closed before the fragment's progress arrived") if eventKind(t, event) != "text_delta" { continue } delta, err := event.AsTextDeltaEvent() - if err != nil { - t.Fatalf("delta event body: %v", err) - } + testassert.NoError(t, err) scope, err := delta.Scope.AsOperationScope() - if err != nil { - t.Fatalf("progress scope: %v", err) - } - if scope.SessionId != session || scope.OperationId != "op-1" { - t.Fatalf("progress scope = %+v, want this session's operation", delta.Scope) - } - if delta.Content != "partial answer" { - t.Fatalf("progress content = %q, want the flushed fragment", delta.Content) - } + testassert.NoError(t, err) + testassert.Require(t, scope.SessionId == session && scope.OperationId == "op-1", "progress scope = %+v, want this session's operation", delta.Scope) + testassert.Equal(t, delta.Content, "partial answer") sawFragment = true case <-deadline: t.Fatal("the partial fragment never became published progress") } } - if err := bg.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - return h.Interrupt(ctx, session) - }); err != nil { - t.Fatalf("Interrupt: %v", err) - } + testassert.NoError(t, bg.r.interruptSession(context.Background(), session)) awaitOperation(t, bg.r, session, "op-1", harness.OperationInterruption) items := projectSessionItems(t, bg.r, session) @@ -1084,43 +829,25 @@ func TestConversationProjectionInterruptedPartialAssistant(t *testing.T) { switch itemKind(t, item) { case "assistant": assistant, err := item.AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } - if assistant.Status != protocol.AssistantItemStatusInterrupted { - t.Fatalf("assistant status = %q, want the interrupted partial", assistant.Status) - } + testassert.NoError(t, err) + testassert.Equal(t, assistant.Status, protocol.AssistantItemStatusInterrupted) textPart, err := assistant.Content[0].AsTextPart() - if err != nil || textPart.Text != "partial answer" { - t.Fatalf("partial content = %+v, want the retained fragment", assistant.Content) - } + testassert.Require(t, err == nil && textPart.Text == "partial answer", "partial content = %+v, want the retained fragment", assistant.Content) sawAssistant = true case "signal": signal, err := item.AsSignalItem() - if err != nil { - t.Fatalf("AsSignalItem: %v", err) - } - if signal.Subtype != protocol.SignalItemSubtypeInterruption || signal.Content != "Operation interrupted." { - t.Fatalf("signal item = %+v, want the typed interruption", signal) - } + testassert.NoError(t, err) + testassert.Require(t, signal.Subtype == protocol.SignalItemSubtypeInterruption && signal.Content == "Operation interrupted.", "signal item = %+v, want the typed interruption", signal) sawSignal = true case "operation_end": end, err := item.AsOperationEndItem() - if err != nil { - t.Fatalf("AsOperationEndItem: %v", err) - } - if end.Status != protocol.OperationEndItemStatusInterruption || end.Detail == "" { - t.Fatalf("end item = %+v, want the interruption detail", end) - } + testassert.NoError(t, err) + testassert.Require(t, end.Status == protocol.OperationEndItemStatusInterruption && end.Detail != "", "end item = %+v, want the interruption detail", end) sawEnd = true } } - if !sawAssistant || !sawSignal || !sawEnd { - t.Fatalf("interrupted conversation = %d items (assistant %v signal %v end %v), want all three", len(items), sawAssistant, sawSignal, sawEnd) - } - if err := bg.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, sawAssistant && sawSignal && sawEnd, "interrupted conversation = %d items (assistant %v signal %v end %v), want all three", len(items), sawAssistant, sawSignal, sawEnd) + testassert.NoError(t, bg.r.Close(context.Background())) }) } @@ -1179,27 +906,17 @@ func TestConversationProjectionForkPrefixIsIndependent(t *testing.T) { for _, item := range sourceItems { sourceIDs[itemIdentity(t, item)] = true data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal source item: %v", err) - } - if strings.Contains(string(data), forked) { - t.Fatalf("source item wire = %s, want no fork identity", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), forked), "source item wire = %s, want no fork identity", data) } for i, item := range forkItems[:3] { data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal fork item: %v", err) - } - if strings.Contains(string(data), root) { - t.Fatalf("fork prefix item wire = %s, want no source identity", data) - } + testassert.NoError(t, err) + testassert.Require(t, !strings.Contains(string(data), root), "fork prefix item wire = %s, want no source identity", data) if sourceIDs[itemIdentity(t, item)] { t.Fatalf("fork prefix item %d shares the source identity %q", i, itemIdentity(t, item)) } - if strings.Contains(string(data), `"operation_id"`) { - t.Fatalf("fork prefix item wire = %s, want no Operation ownership", data) - } + testassert.Require(t, !strings.Contains(string(data), `"operation_id"`), "fork prefix item wire = %s, want no Operation ownership", data) } // The copied assistant's call is a real completed tool turn: the @@ -1208,9 +925,7 @@ func TestConversationProjectionForkPrefixIsIndependent(t *testing.T) { // arguments, and terminal outcome inside the assistant — under a // destination-namespace identity. copiedAssistant, err := forkItems[1].AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } + testassert.NoError(t, err) copiedAssistantEntry := "" forkSnap := sessionViewThroughRuntime(t, bg.r, forked) for _, fact := range historyFactsOf(forkSnap.History) { @@ -1222,32 +937,15 @@ func TestConversationProjectionForkPrefixIsIndependent(t *testing.T) { break } } - if copiedAssistantEntry == "" { - t.Fatal("the fork copied no assistant entry") - } - if copiedAssistant.ItemId != projectItemID(forked, copiedAssistantEntry) { - t.Fatalf("copied assistant item_id = %q, want the destination namespace identity", copiedAssistant.ItemId) - } - if copiedAssistant.OperationId != nil { - t.Fatalf("copied assistant operation_id = %v, want the cleared source attribution", copiedAssistant.OperationId) - } + testassert.Require(t, copiedAssistantEntry != "", "the fork copied no assistant entry") + testassert.Require(t, copiedAssistant.ItemId == projectItemID(forked, copiedAssistantEntry) && copiedAssistant.OperationId == nil, "copied assistant item_id = %q, want the destination namespace identity; copied assistant operation_id = %v, want the cleared source attribution", copiedAssistant.ItemId, copiedAssistant.OperationId) call := copiedAssistant.ToolCalls[0] - if call.Id != "call-1" || call.Arguments != `{"n":9007199254740993}` || - call.NormalizedArguments == nil || string((*call.NormalizedArguments)["n"]) != `9007199254740993` || - call.Status == nil || *call.Status != protocol.ToolCallStatusSuccess || - call.Content == nil || *call.Content != "first" || - call.Metadata == nil || string(*call.Metadata) != `{"n":9007199254740993}` { - t.Fatalf("copied tool call = %+v, want the attached copied result with its fidelity", call) - } + testassert.Require(t, call.Id == "call-1" && call.Arguments == `{"n":9007199254740993}` && call.NormalizedArguments != nil && string((*call.NormalizedArguments)["n"]) == `9007199254740993` && call.Status != nil && *call.Status == protocol.ToolCallStatusSuccess && call.Content != nil && *call.Content == "first" && call.Metadata != nil && string(*call.Metadata) == `{"n":9007199254740993}`, "copied tool call = %+v, want the attached copied result with its fidelity", call) // The fork's own admission keeps its Operation attribution. forkInput, err := forkItems[3].AsInputItem() - if err != nil || forkInput.Origin != protocol.InputOriginUser || forkInput.OperationId == nil || *forkInput.OperationId != "fork-op-1" { - t.Fatalf("fork input = %+v err %v, want the fork operation's own input", forkInput, err) - } - if err := bg.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, err == nil && forkInput.Origin == protocol.InputOriginUser && forkInput.OperationId != nil && *forkInput.OperationId == "fork-op-1", "fork input = %+v err %v, want the fork operation's own input", forkInput, err) + testassert.NoError(t, bg.r.Close(context.Background())) }) } @@ -1273,9 +971,7 @@ func TestConversationProjectionChildAndBackgroundCompletion(t *testing.T) { completionID := bg.startJob(child, "cc000003") stopper.addDelivery(child, completionID, "job report") - if err := bg.stop(child); err != nil { - t.Fatalf("Stop(child): %v", err) - } + testassert.NoError(t, bg.r.stopSession(context.Background(), child)) // The child's conversation: its own turn plus the typed completion // signal naming the job member. @@ -1286,13 +982,8 @@ func TestConversationProjectionChildAndBackgroundCompletion(t *testing.T) { continue } signal, err := item.AsSignalItem() - if err != nil { - t.Fatalf("AsSignalItem: %v", err) - } - if signal.Subtype != protocol.SignalItemSubtypeBackgroundCompletion || signal.Content != "job report" || - signal.RelatedMember == nil || signal.RelatedMember.Kind != protocol.BackgroundMemberKindJob || signal.RelatedMember.Id != "cc000003" { - t.Fatalf("completion signal = %+v, want the job member's typed record", signal) - } + testassert.NoError(t, err) + testassert.Require(t, signal.Subtype == protocol.SignalItemSubtypeBackgroundCompletion && signal.Content == "job report" && signal.RelatedMember != nil && signal.RelatedMember.Kind == protocol.BackgroundMemberKindJob && signal.RelatedMember.Id == "cc000003", "completion signal = %+v, want the job member's typed record", signal) sawSignal = true } if !sawSignal { @@ -1314,9 +1005,7 @@ func TestConversationProjectionChildAndBackgroundCompletion(t *testing.T) { } time.Sleep(5 * time.Millisecond) } - if err := bg.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, bg.r.Close(context.Background())) }) } @@ -1329,9 +1018,7 @@ func hasRuntimeOriginInput(t *testing.T, items []protocol.ConversationItem, want continue } input, err := item.AsInputItem() - if err != nil { - t.Fatalf("AsInputItem: %v", err) - } + testassert.NoError(t, err) if input.Origin != protocol.InputOriginRuntime || len(input.Content) == 0 { continue } @@ -1379,22 +1066,14 @@ func TestConversationProjectionCompactionTwiceRetainsEarlierItems(t *testing.T) switch itemKind(t, item) { case "compaction": compaction, err := item.AsCompactionItem() - if err != nil { - t.Fatalf("AsCompactionItem: %v", err) - } + testassert.NoError(t, err) compactions++ - if compaction.Summary == "" || compaction.Model != "prov/m" { - t.Fatalf("compaction item = %+v, want the summary and model", compaction) - } + testassert.Require(t, compaction.Summary != "" && compaction.Model == "prov/m", "compaction item = %+v, want the summary and model", compaction) case "input": input, err := item.AsInputItem() - if err != nil { - t.Fatalf("AsInputItem: %v", err) - } + testassert.NoError(t, err) textPart, err := input.Content[0].AsTextPart() - if err != nil { - t.Fatalf("input part: %v", err) - } + testassert.NoError(t, err) switch textPart.Text { case "hello first": sawFirstInput = true @@ -1403,13 +1082,9 @@ func TestConversationProjectionCompactionTwiceRetainsEarlierItems(t *testing.T) } case "assistant": assistant, err := item.AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } + testassert.NoError(t, err) textPart, err := assistant.Content[0].AsTextPart() - if err != nil { - t.Fatalf("assistant part: %v", err) - } + testassert.NoError(t, err) if textPart.Text == "done" { if !sawFirstAssistant && !sawSecondAssistant { sawFirstAssistant = true @@ -1419,25 +1094,14 @@ func TestConversationProjectionCompactionTwiceRetainsEarlierItems(t *testing.T) } case "operation_end": end, err := item.AsOperationEndItem() - if err != nil { - t.Fatalf("AsOperationEndItem: %v", err) - } - if end.Status != protocol.OperationEndItemStatusSuccess { - t.Fatalf("end item = %+v, want the settled success", end) - } + testassert.NoError(t, err) + testassert.Require(t, end.Status == protocol.OperationEndItemStatusSuccess, "end item = %+v, want the settled success", end) sawEnds++ } } - if compactions != 2 { - t.Fatalf("compaction items = %d, want both compactions visible", compactions) - } - if !sawFirstInput || !sawFirstAssistant || !sawSecondInput || !sawSecondAssistant || sawEnds != 4 { - t.Fatalf("retained items = (input %v/%v assistant %v/%v ends %d), want every earlier item and all four ends", - sawFirstInput, sawSecondInput, sawFirstAssistant, sawSecondAssistant, sawEnds) - } - if err := f.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Equal(t, compactions, 2) + testassert.Require(t, sawFirstInput && sawFirstAssistant && sawSecondInput && sawSecondAssistant && sawEnds == 4, "retained items = (input %v/%v assistant %v/%v ends %d), want every earlier item and all four ends", sawFirstInput, sawSecondInput, sawFirstAssistant, sawSecondAssistant, sawEnds) + testassert.NoError(t, f.r.Close(context.Background())) }) } @@ -1476,13 +1140,9 @@ func TestConversationProjectionTerminalFailureWithoutAssistant(t *testing.T) { t.Fatalf("first turn items = %d (%s), want input, assistant, end", len(items), itemKinds(t, items)) } literal, err := items[0].AsInputItem() - if err != nil { - t.Fatalf("first item: %v", err) - } + testassert.NoError(t, err) textPart, err := literal.Content[0].AsTextPart() - if err != nil || textPart.Text != "Operation interrupted." { - t.Fatalf("first item = %+v, want the literal input", literal) - } + testassert.Require(t, err == nil && textPart.Text == "Operation interrupted.", "first item = %+v, want the literal input", literal) submitConvergedThroughRuntime(t, bg.r, session, "op-2", "again", harness.OperationFailure) @@ -1491,17 +1151,10 @@ func TestConversationProjectionTerminalFailureWithoutAssistant(t *testing.T) { t.Fatalf("items = %d (%s), want the prefix plus input and failure end", len(items), itemKinds(t, items)) } failureEnd, err := items[4].AsOperationEndItem() - if err != nil { - t.Fatalf("last item: %v", err) - } + testassert.NoError(t, err) failingInput, err := items[3].AsInputItem() - if err != nil { - t.Fatalf("fourth item: %v", err) - } - if failureEnd.Status != protocol.OperationEndItemStatusFailure || - !strings.Contains(failureEnd.Detail, "model down") || failingInput.OperationId == nil || *failingInput.OperationId != "op-2" { - t.Fatalf("failure projection = (end %+v, input %+v), want the failed op-2 end with its detail", failureEnd, failingInput) - } + testassert.NoError(t, err) + testassert.Require(t, failureEnd.Status == protocol.OperationEndItemStatusFailure && strings.Contains(failureEnd.Detail, "model down") && failingInput.OperationId != nil && *failingInput.OperationId == "op-2", "failure projection = (end %+v, input %+v), want the failed op-2 end with its detail", failureEnd, failingInput) // The no-output interruption on a fresh Session: the model effect // arrives and cooperatively parks without yielding any content, the @@ -1516,11 +1169,7 @@ func TestConversationProjectionTerminalFailureWithoutAssistant(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the model effect never arrived") } - if err := bg.r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - return h.Interrupt(ctx, interrupted) - }); err != nil { - t.Fatalf("Interrupt: %v", err) - } + testassert.NoError(t, bg.r.interruptSession(context.Background(), interrupted)) awaitOperation(t, bg.r, interrupted, "op-int", harness.OperationInterruption) items = projectSessionItems(t, bg.r, interrupted) @@ -1538,35 +1187,19 @@ func TestConversationProjectionTerminalFailureWithoutAssistant(t *testing.T) { wantEntries[fact.Kind] = fact.EntryID } } - if len(wantEntries) != 3 { - t.Fatalf("committed facts = %v, want exactly input, signal, settlement", wantEntries) - } + testassert.Require(t, len(wantEntries) == 3, "committed facts = %v, want exactly input, signal, settlement", wantEntries) interruptionInput, err := items[0].AsInputItem() - if err != nil || interruptionInput.Origin != protocol.InputOriginUser || - interruptionInput.OperationId == nil || *interruptionInput.OperationId != "op-int" || - interruptionInput.ItemId != projectItemID(interrupted, wantEntries[harness.EntryInput]) { - t.Fatalf("interruption input = %+v err %v, want the op-int input at its fact identity", interruptionInput, err) - } + testassert.Require(t, err == nil && interruptionInput.Origin == protocol.InputOriginUser && interruptionInput.OperationId != nil && *interruptionInput.OperationId == "op-int" && interruptionInput.ItemId == projectItemID(interrupted, wantEntries[harness.EntryInput]), "interruption input = %+v err %v, want the op-int input at its fact identity", interruptionInput, err) interruptionSignal, err := items[1].AsSignalItem() - if err != nil || interruptionSignal.Subtype != protocol.SignalItemSubtypeInterruption || - interruptionSignal.Content != "Operation interrupted." || - interruptionSignal.ItemId != projectItemID(interrupted, wantEntries[harness.EntrySignal]) { - t.Fatalf("interruption signal = %+v err %v, want the typed signal at its fact identity", interruptionSignal, err) - } + testassert.Require(t, err == nil && interruptionSignal.Subtype == protocol.SignalItemSubtypeInterruption && interruptionSignal.Content == "Operation interrupted." && interruptionSignal.ItemId == projectItemID(interrupted, wantEntries[harness.EntrySignal]), "interruption signal = %+v err %v, want the typed signal at its fact identity", interruptionSignal, err) interruptedEnd, err := items[2].AsOperationEndItem() - if err != nil || interruptedEnd.Status != protocol.OperationEndItemStatusInterruption || - interruptedEnd.Detail == "" || - interruptedEnd.ItemId != projectItemID(interrupted, wantEntries[harness.EntryOperationSettlement]) { - t.Fatalf("interruption end = %+v err %v, want the interruption detail at its fact identity", interruptedEnd, err) - } + testassert.Require(t, err == nil && interruptedEnd.Status == protocol.OperationEndItemStatusInterruption && interruptedEnd.Detail != "" && interruptedEnd.ItemId == projectItemID(interrupted, wantEntries[harness.EntryOperationSettlement]), "interruption end = %+v err %v, want the interruption detail at its fact identity", interruptedEnd, err) // The identities are stable across an immediate second read. if again := itemIDs(t, projectSessionItems(t, bg.r, interrupted)); !reflect.DeepEqual(again, itemIDs(t, items)) { t.Fatalf("re-read identities = %v, want the stable %v", again, itemIDs(t, items)) } - if err := bg.r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, bg.r.Close(context.Background())) }) } @@ -1577,7 +1210,5 @@ func TestConversationProjectionOverEmptySession(t *testing.T) { defer closeProjectionRuntime(r) session := projectionSession(t, r, "/tmp/conv-empty", "solo").Identity.SessionID items := projectSessionItems(t, r, session) - if items == nil || len(items) != 0 { - t.Fatalf("items = %v, want the present empty list", items) - } + testassert.Require(t, items != nil && len(items) == 0, "items = %v, want the present empty list", items) } diff --git a/runtime/protocol_discovery_process_test.go b/runtime/protocol_discovery_process_test.go index 7d1bef78..ca9e0811 100644 --- a/runtime/protocol_discovery_process_test.go +++ b/runtime/protocol_discovery_process_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -72,9 +73,7 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { firstToken := first.await("acquired", "owned") secondToken := second.await("acquired", "owned") - if (firstToken == "owned") == (secondToken == "owned") { - t.Fatalf("contender dispositions = (%q, %q), want exactly one owner and one ErrOwned loser", firstToken, secondToken) - } + testassert.Require(t, (firstToken == "owned") != (secondToken == "owned"), "contender dispositions = (%q, %q), want exactly one owner and one ErrOwned loser", firstToken, secondToken) winner, loser := first, second winnerMarker, loserMarker := firstMarker, secondMarker if firstToken == "owned" { @@ -85,25 +84,27 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { if err := loser.awaitExit(); err != nil { t.Fatalf("losing contender exit: %v output=%q", err, loser.log.text()) } - if _, err := os.Stat(winnerMarker); err != nil { - t.Fatalf("the winner's Runtime factory marker: %v", err) + { + _, err := os.Stat(winnerMarker) + testassert.NoError(t, err) } - if _, err := os.Stat(loserMarker); !os.IsNotExist(err) { - t.Fatalf("the losing contender invoked a factory: marker stat error = %v", err) + { + _, err := os.Stat(loserMarker) + testassert.Require(t, os.IsNotExist(err), "the losing contender invoked a factory: marker stat error = %v", err) } - if _, err := os.Stat(filepath.Join(dataDir, "lightcode.db")); err != nil { - t.Fatalf("the winner's SQLite derivation under the data root: %v", err) + { + _, err := os.Stat(filepath.Join(dataDir, "lightcode.db")) + testassert.NoError(t, err) } record := readDiscoveryRecord(t, recordPath) health := healthSnapshot(t, record) - if health.InstanceId != record.InstanceId || health.ProtocolVersion != protocol.N2 { - t.Fatalf("authenticated health = %s/%s, want the record's %s/%s", health.InstanceId, health.ProtocolVersion, record.InstanceId, record.ProtocolVersion) - } + testassert.Require(t, health.InstanceId == record.InstanceId && health.ProtocolVersion == protocol.N2, "authenticated health = %s/%s, want the record's %s/%s", health.InstanceId, health.ProtocolVersion, record.InstanceId, record.ProtocolVersion) winner.shutdown() - if after := readDiscoveryBytes(t, recordPath); after != nil { - t.Fatalf("the record survived managed shutdown (%d bytes)", len(after)) + { + after := readDiscoveryBytes(t, recordPath) + testassert.Require(t, after == nil, "the record survived managed shutdown (%d bytes)", len(after)) } e.assertLockReleased(t) }) @@ -116,17 +117,18 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { dead := readDiscoveryRecord(t, recordPath) before := readDiscoveryBytes(t, recordPath) - if err := first.cmd.Process.Kill(); err != nil { - t.Fatalf("kill the first owner: %v", err) + testassert.NoError(t, first.cmd.Process.Kill()) + { + err := first.awaitExit() + testassert.Require(t, err != nil, "the hard-killed owner reported success") } - if err := first.awaitExit(); err == nil { - t.Fatal("the hard-killed owner reported success") + { + _, err := os.Stat(recordPath) + testassert.NoError(t, err) } - if _, err := os.Stat(recordPath); err != nil { - t.Fatalf("the hard-killed owner's record disappeared: %v", err) - } - if err := probeHealth(dead.Endpoint, dead.Credential); err == nil { - t.Fatal("the health probe to the dead owner's endpoint succeeded") + { + err := probeHealth(dead.Endpoint, dead.Credential) + testassert.Require(t, err != nil, "the health probe to the dead owner's endpoint succeeded") } incumbent := startDiscoveryChild(t, e.home, dataDir, recordPath, filepath.Join(t.TempDir(), "marker"), discoveryModeHold) @@ -137,19 +139,20 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { if err := contender.awaitExit(); err != nil { t.Fatalf("contender exit: %v output=%q", err, contender.log.text()) } - if _, err := os.Stat(contenderMarker); !os.IsNotExist(err) { - t.Fatalf("the contender invoked a factory over a dead record: marker stat error = %v", err) - } - if err := incumbent.alive(); err != nil { - t.Fatalf("the incumbent died after the contender attempt: %v", err) + { + _, err := os.Stat(contenderMarker) + testassert.Require(t, os.IsNotExist(err), "the contender invoked a factory over a dead record: marker stat error = %v", err) } - if after := readDiscoveryBytes(t, recordPath); !bytes.Equal(after, before) { - t.Fatalf("the dead record changed across the contender attempt") + testassert.NoError(t, incumbent.alive()) + { + after := readDiscoveryBytes(t, recordPath) + testassert.Require(t, bytes.Equal(after, before), "the dead record changed across the contender attempt") } incumbent.shutdown() - if after := readDiscoveryBytes(t, recordPath); !bytes.Equal(after, before) { - t.Fatalf("the incumbent shutdown changed a record it never published") + { + after := readDiscoveryBytes(t, recordPath) + testassert.Require(t, bytes.Equal(after, before), "the incumbent shutdown changed a record it never published") } }) @@ -163,9 +166,7 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { record := readDiscoveryRecord(t, recordPath) before := readDiscoveryBytes(t, recordPath) first := healthSnapshot(t, record) - if first.InstanceId != record.InstanceId || first.ProtocolVersion == "" || first.ProtocolVersion == protocol.N2 { - t.Fatalf("incompatible health = %s/%q, want the record's instance and a different nonempty version", first.InstanceId, first.ProtocolVersion) - } + testassert.Require(t, first.InstanceId == record.InstanceId && first.ProtocolVersion != "" && first.ProtocolVersion != protocol.N2, "incompatible health = %s/%q, want the record's instance and a different nonempty version", first.InstanceId, first.ProtocolVersion) contenderMarker := filepath.Join(t.TempDir(), "marker") contender := startDiscoveryChild(t, e.home, dataDir, recordPath, contenderMarker, discoveryModeOwner) @@ -173,23 +174,22 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { if err := contender.awaitExit(); err != nil { t.Fatalf("contender exit: %v output=%q", err, contender.log.text()) } - if _, err := os.Stat(contenderMarker); !os.IsNotExist(err) { - t.Fatalf("the contender invoked a factory over an incompatible live record: marker stat error = %v", err) - } - if after := readDiscoveryBytes(t, recordPath); !bytes.Equal(after, before) { - t.Fatalf("the incompatible record changed across the contender attempt") + { + _, err := os.Stat(contenderMarker) + testassert.Require(t, os.IsNotExist(err), "the contender invoked a factory over an incompatible live record: marker stat error = %v", err) } - if err := owner.alive(); err != nil { - t.Fatalf("the incumbent died after the contender attempt: %v", err) + { + after := readDiscoveryBytes(t, recordPath) + testassert.Require(t, bytes.Equal(after, before), "the incompatible record changed across the contender attempt") } + testassert.NoError(t, owner.alive()) second := healthSnapshot(t, record) - if second.InstanceId != first.InstanceId || second.ProtocolVersion != first.ProtocolVersion { - t.Fatalf("the incumbent health changed after the contender attempt: %s/%s -> %s/%s", first.InstanceId, first.ProtocolVersion, second.InstanceId, second.ProtocolVersion) - } + testassert.Require(t, second.InstanceId == first.InstanceId && second.ProtocolVersion == first.ProtocolVersion, "the incumbent health changed after the contender attempt: %s/%s -> %s/%s", first.InstanceId, first.ProtocolVersion, second.InstanceId, second.ProtocolVersion) owner.shutdown() - if after := readDiscoveryBytes(t, recordPath); after != nil { - t.Fatalf("the record survived managed shutdown (%d bytes)", len(after)) + { + after := readDiscoveryBytes(t, recordPath) + testassert.Require(t, after == nil, "the record survived managed shutdown (%d bytes)", len(after)) } }) @@ -198,9 +198,7 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { e, dataDir := newDiscoveryRoot(t) recordPath := filepath.Join(t.TempDir(), "discovery.json") child := startDiscoveryChild(t, e.home, dataDir, recordPath, filepath.Join(t.TempDir(), "marker"), discoveryModeUnready) - if token := child.await("refused", "published"); token != "refused" { - t.Fatalf("publication while readiness reported an incompatible version = %q, want the refusal", token) - } + testassert.Equal(t, child.await("refused", "published"), "refused") refuseDiscovery(t, recordPath) child.shutdown() refuseDiscovery(t, recordPath) @@ -209,9 +207,7 @@ func TestRuntimeDiscoveryOwnershipAcrossProcesses(t *testing.T) { e, dataDir := newDiscoveryRoot(t) recordPath := filepath.Join(t.TempDir(), "discovery.json") child := startDiscoveryChild(t, e.home, dataDir, recordPath, filepath.Join(t.TempDir(), "marker"), discoveryModeCancel) - if token := child.await("refused", "published"); token != "refused" { - t.Fatalf("publication after cancellation during readiness = %q, want the refusal", token) - } + testassert.Equal(t, child.await("refused", "published"), "refused") refuseDiscovery(t, recordPath) child.shutdown() refuseDiscovery(t, recordPath) @@ -280,19 +276,13 @@ func runDiscoveryChild(t *testing.T, dataDir string) { fmt.Println("owned") return } - if err != nil { - t.Fatalf("discovery child open: %v", err) - } + testassert.NoError(t, err) ps, err := r.OpenProtocol(context.Background()) - if err != nil { - t.Fatalf("discovery child OpenProtocol: %v", err) - } + testassert.NoError(t, err) fmt.Println("acquired") switch mode { case discoveryModeOwner, discoveryModeIncompatible: - if err := ps.PublishDiscovery(recordPath); err != nil { - t.Fatalf("discovery child PublishDiscovery: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(recordPath)) fmt.Println("published") if mode == discoveryModeIncompatible { ps.server.Handler = incompatibleHealthHandler(ps, ps.server.Handler, "1") @@ -303,8 +293,9 @@ func runDiscoveryChild(t *testing.T, dataDir string) { // publishes nothing, leaving any existing record untouched. case discoveryModeUnready: ps.server.Handler = incompatibleHealthHandler(ps, ps.server.Handler, "1") - if err := ps.PublishDiscovery(recordPath); err == nil { - t.Fatal("publication succeeded while authenticated health reported an incompatible version") + { + err := ps.PublishDiscovery(recordPath) + testassert.Require(t, err != nil, "publication succeeded while authenticated health reported an incompatible version") } refuseDiscovery(t, recordPath) fmt.Println("refused") @@ -316,9 +307,7 @@ func runDiscoveryChild(t *testing.T, dataDir string) { t.Fatalf("unknown discovery child mode %q", mode) } _, _ = io.Copy(io.Discard, os.Stdin) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("discovery child Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) fmt.Println("closed") } @@ -353,15 +342,14 @@ func cancelDuringReadiness(t *testing.T, ps *ProtocolServer, r *Runtime) { } r.cancelWork() close(gate) - if err := <-publishDone; err == nil { - t.Fatal("publication succeeded after cancellation during readiness") + { + err := <-publishDone + testassert.Require(t, err != nil, "publication succeeded after cancellation during readiness") } ps.pubMu.Lock() remembered := ps.discoveryPath ps.pubMu.Unlock() - if remembered != "" { - t.Fatalf("canceled publication remembered path %q", remembered) - } + testassert.Equal(t, remembered, "") } // incompatibleHealthHandler is a test-only native handler wrapper: an @@ -391,8 +379,9 @@ func incompatibleHealthHandler(ps *ProtocolServer, original http.Handler, versio // refuseDiscovery proves no usable discovery record exists at the path. func refuseDiscovery(t *testing.T, path string) { t.Helper() - if _, err := os.Stat(path); !os.IsNotExist(err) { - t.Fatalf("refused publication left a record at %s: %v", path, err) + { + _, err := os.Stat(path) + testassert.Require(t, os.IsNotExist(err), "refused publication left a record at %s: %v", path, err) } } @@ -402,30 +391,20 @@ func refuseDiscovery(t *testing.T, path string) { func readDiscoveryRecord(t *testing.T, path string) discoveryRecord { t.Helper() info, err := os.Stat(path) - if err != nil { - t.Fatalf("stat discovery record: %v", err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o600 { t.Fatalf("discovery record mode = %o, want 0600", info.Mode().Perm()) } data := readDiscoveryBytes(t, path) var record discoveryRecord - if err := decodeOneDocument(data, &record); err != nil { - t.Fatalf("decode discovery record at %s: %v", path, err) - } - if record.ProtocolVersion != string(protocol.N2) { - t.Fatalf("discovery record protocol version = %q, want %q", record.ProtocolVersion, protocol.N2) - } - if len(record.InstanceId) != 32 || strings.Trim(record.InstanceId, "0123456789abcdef") != "" { - t.Fatalf("discovery instance %q is not 32 lowercase hex", record.InstanceId) - } - if len(record.Credential) != 64 || strings.Trim(record.Credential, "0123456789abcdef") != "" { - t.Fatal("discovery credential is not 64 lowercase hex") + { + err := decodeOneDocument(data, &record) + testassert.Require(t, err == nil, "decode discovery record at %s: %v", path, err) } + testassert.Require(t, record.ProtocolVersion == string(protocol.N2) && len(record.InstanceId) == 32 && strings.Trim(record.InstanceId, "0123456789abcdef") == "", "discovery record protocol version = %q, want %q; discovery instance %q is not 32 lowercase hex", record.ProtocolVersion, protocol.N2, record.InstanceId) + testassert.Require(t, len(record.Credential) == 64 && strings.Trim(record.Credential, "0123456789abcdef") == "", "discovery credential is not 64 lowercase hex") endpoint, err := url.Parse(record.Endpoint) - if err != nil || endpoint.Scheme != "http" || endpoint.Hostname() != "127.0.0.1" || endpoint.Port() == "" { - t.Fatalf("discovery endpoint %q is not an http loopback host:port", record.Endpoint) - } + testassert.Require(t, err == nil && endpoint.Scheme == "http" && endpoint.Hostname() == "127.0.0.1" && endpoint.Port() != "", "discovery endpoint %q is not an http loopback host:port", record.Endpoint) return record } @@ -450,15 +429,12 @@ func healthSnapshot(t *testing.T, record discoveryRecord) protocol.Health { resp := rawProtocol(t, http.MethodGet, record.Endpoint+"/v1/health", record.Credential, "") defer resp.Body.Close() body, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("read health body: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("health status = %d body=%q, want 200", resp.StatusCode, body) - } + testassert.NoError(t, err) + testassert.Require(t, resp.StatusCode == http.StatusOK, "health status = %d body=%q, want 200", resp.StatusCode, body) var health protocol.Health - if err := decodeOneDocument(body, &health); err != nil { - t.Fatalf("decode health body %q: %v", body, err) + { + err := decodeOneDocument(body, &health) + testassert.Require(t, err == nil, "decode health body %q: %v", body, err) } return health } @@ -509,18 +485,12 @@ func startDiscoveryChild(t *testing.T, home, dataDir, record, marker, mode strin discoveryChildModeEnv+"="+mode, ) stdin, err := cmd.StdinPipe() - if err != nil { - t.Fatalf("discovery child StdinPipe: %v", err) - } + testassert.NoError(t, err) stdout, err := cmd.StdoutPipe() - if err != nil { - t.Fatalf("discovery child StdoutPipe: %v", err) - } + testassert.NoError(t, err) log := &discoveryLog{} cmd.Stderr = log - if err := cmd.Start(); err != nil { - t.Fatalf("discovery child Start: %v", err) - } + testassert.NoError(t, cmd.Start()) c := &discoveryChild{ t: t, cmd: cmd, stdin: stdin, tokens: make(chan string, 64), diff --git a/runtime/protocol_hydration_pending_test.go b/runtime/protocol_hydration_pending_test.go index 6fa21da6..80a74a6d 100644 --- a/runtime/protocol_hydration_pending_test.go +++ b/runtime/protocol_hydration_pending_test.go @@ -16,6 +16,7 @@ import ( "time" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -103,34 +104,30 @@ func TestProtocolHydrationPendingDuringParkedQueuedDelivery(t *testing.T) { defer release() // LIFO: the gate releases before the owner close joins created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: filepath.Join(e.home, "pending-delivery"), AgentType: "solo"}) - if err != nil { - t.Fatalf("create: %v", err) - } - if created.JSON201 == nil { - t.Fatalf("create = status %d: %s", created.HTTPResponse.StatusCode, created.Body) - } + testassert.NoError(t, err) + testassert.Require(t, created.JSON201 != nil, "create = status %d: %s", created.HTTPResponse.StatusCode, created.Body) session := created.JSON201.SessionId // op-1 parks at its model boundary; the queued message buffers behind // the active turn. - if _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ - OperationId: "op-1", Mode: protocol.SubmitRequestModeRegular, - Content: []protocol.ContentPart{textContentPart(t, "running", nil)}, - }); err != nil { - t.Fatalf("op-1 submit: %v", err) + { + _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ + OperationId: "op-1", Mode: protocol.SubmitRequestModeRegular, + Content: []protocol.ContentPart{textContentPart(t, "running", nil)}, + }) + testassert.NoError(t, err) } awaitModelArrival(t, e) - if _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ - OperationId: "op-2", Mode: protocol.SubmitRequestModeQueued, - Content: []protocol.ContentPart{textContentPart(t, "queued-1", nil)}, - }); err != nil { - t.Fatalf("queued submit: %v", err) + { + _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ + OperationId: "op-2", Mode: protocol.SubmitRequestModeQueued, + Content: []protocol.ContentPart{textContentPart(t, "queued-1", nil)}, + }) + testassert.NoError(t, err) } buffered := mountedHydration(t, client, session) - if len(buffered.Pending.Queued) != 1 || buffered.Pending.Queued[0].OperationId != "op-2" { - t.Fatalf("buffered pending = %+v, want the queued op-2", buffered.Pending) - } + testassert.Require(t, len(buffered.Pending.Queued) == 1 && buffered.Pending.Queued[0].OperationId == "op-2", "buffered pending = %+v, want the queued op-2", buffered.Pending) // The queued delivery parks inside its admission's storage lifetime: // the coordinator mutex is free there, so the mounted hydration reads @@ -145,38 +142,28 @@ func TestProtocolHydrationPendingDuringParkedQueuedDelivery(t *testing.T) { } unresolved := mountedHydration(t, client, session) - if len(unresolved.Pending.Queued) != 1 || unresolved.Pending.Queued[0].OperationId != "op-2" { - t.Fatalf("pending during the parked delivery = %+v, want the selected head still pending", unresolved.Pending) - } + testassert.Require(t, len(unresolved.Pending.Queued) == 1 && unresolved.Pending.Queued[0].OperationId == "op-2", "pending during the parked delivery = %+v, want the selected head still pending", unresolved.Pending) for _, op := range unresolved.Operations { - if op.OperationId == "op-2" { - t.Fatalf("the pending item %s committed before its delivery completed", op.OperationId) - } + testassert.Require(t, op.OperationId != "op-2", "the pending item %s committed before its delivery completed", op.OperationId) } parked.releasePark() // the adoption removes the queued head in its own critical section awaitOperation(t, r, session, "op-2", harness.OperationSuccess) settled := mountedHydration(t, client, session) - if len(settled.Pending.Steering) != 0 || len(settled.Pending.Queued) != 0 { - t.Fatalf("pending after the adoption = %+v, want both FIFOs empty", settled.Pending) - } + testassert.Require(t, len(settled.Pending.Steering) == 0 && len(settled.Pending.Queued) == 0, "pending after the adoption = %+v, want both FIFOs empty", settled.Pending) found := false for _, item := range settled.Conversation.Items { if itemKind(t, item) != "input" { continue } input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } + testassert.NoError(t, err) if input.OperationId != nil && *input.OperationId == "op-2" { found = true } } - if !found { - t.Fatal("the adopted queued input is missing from the conversation page") - } + testassert.Require(t, found, "the adopted queued input is missing from the conversation page") }) } @@ -186,11 +173,7 @@ func mountedHydration(t *testing.T, client *protocol.ClientWithResponses, sessio ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() resp, err := client.GetSessionHydrationWithResponse(ctx, sessionID) - if err != nil { - t.Fatalf("GetSessionHydration(%s): %v", sessionID, err) - } - if resp.JSON200 == nil { - t.Fatalf("GetSessionHydration(%s) = status %d: %s", sessionID, resp.HTTPResponse.StatusCode, resp.Body) - } + testassert.Require(t, err == nil, "GetSessionHydration(%s): %v", sessionID, err) + testassert.Require(t, resp.JSON200 != nil, "GetSessionHydration(%s) = status %d: %s", sessionID, resp.HTTPResponse.StatusCode, resp.Body) return resp.JSON200 } diff --git a/runtime/protocol_lifecycle_test.go b/runtime/protocol_lifecycle_test.go index 2fa80194..89b5faba 100644 --- a/runtime/protocol_lifecycle_test.go +++ b/runtime/protocol_lifecycle_test.go @@ -22,6 +22,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/atomicfs" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -46,9 +47,7 @@ func dialSmallBuffer(t *testing.T, ps *ProtocolServer) net.Conn { }) }} conn, err := dialer.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")) - if err != nil { - t.Fatalf("dial small-buffer connection: %v", err) - } + testassert.NoError(t, err) return conn } @@ -58,19 +57,16 @@ func dialSmallBuffer(t *testing.T, ps *ProtocolServer) net.Conn { func openSSEOverConn(t *testing.T, conn net.Conn, ps *ProtocolServer) { t.Helper() request := "GET /v1/events HTTP/1.1\r\nHost: events\r\nAuthorization: Bearer " + ps.credential + "\r\n\r\n" - if _, err := conn.Write([]byte(request)); err != nil { - t.Fatalf("write events request: %v", err) + { + _, err := conn.Write([]byte(request)) + testassert.NoError(t, err) } reader := bufio.NewReader(conn) status, err := reader.ReadString('\n') - if err != nil || !strings.Contains(status, "200") { - t.Fatalf("events handshake = (%q, %v), want the 200 status line", status, err) - } + testassert.Require(t, err == nil && strings.Contains(status, "200"), "events handshake = (%q, %v), want the 200 status line", status, err) for { line, err := reader.ReadString('\n') - if err != nil { - t.Fatalf("read events headers: %v", err) - } + testassert.NoError(t, err) if strings.TrimSpace(line) == "" { break } @@ -120,11 +116,12 @@ func TestProtocolServerStreamIsolation(t *testing.T) { e.server.setHold(parked) gate := sync.OnceFunc(func() { close(parked) }) defer gate() - if _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ - OperationId: "op-1", Mode: "regular", - Content: []protocol.ContentPart{textContentPart(t, "running", nil)}, - }); err != nil { - t.Fatalf("gated submit: %v", err) + { + _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ + OperationId: "op-1", Mode: "regular", + Content: []protocol.ContentPart{textContentPart(t, "running", nil)}, + }) + testassert.NoError(t, err) } awaitModelArrival(t, e) @@ -148,21 +145,18 @@ func TestProtocolServerStreamIsolation(t *testing.T) { overflowed = subscriberCount(r) == 1 time.Sleep(time.Millisecond) } - if !overflowed { - t.Fatalf("the overflowing stream survived %d published frames, want its subscription removed alone", flooded) - } + testassert.Require(t, overflowed, "the overflowing stream survived %d published frames, want its subscription removed alone", flooded) // The removed subscription's watcher closed exactly this connection // while its socket write was blocked: draining the buffered window now // ends in EOF. A live connection would answer a timeout, which is the // forbidden sibling. - if err := victimConn.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { - t.Fatalf("set victim read deadline: %v", err) - } + testassert.NoError(t, victimConn.SetReadDeadline(time.Now().Add(10*time.Second))) // io.Copy reports nil only when the peer's EOF ends the read; a live // connection would answer the read deadline instead. - if _, err := io.Copy(io.Discard, victimConn); err != nil { - t.Fatalf("draining the removed stream's connection ended with %v, want the watcher's EOF", err) + { + _, err := io.Copy(io.Discard, victimConn) + testassert.NoError(t, err) } // The healthy sibling observed a substantial share of the flood, keeps @@ -180,14 +174,16 @@ func TestProtocolServerStreamIsolation(t *testing.T) { } awaitHealthy(100) before := healthyCount.Load() - if _, err := r.submitSession(ctx, session, protocol.SubmitRequest{ - OperationId: "op-final", Mode: "queued", - Content: []protocol.ContentPart{textContentPart(t, "final", nil)}, - }); err != nil { - t.Fatalf("post-flood submit: %v", err) + { + _, err := r.submitSession(ctx, session, protocol.SubmitRequest{ + OperationId: "op-final", Mode: "queued", + Content: []protocol.ContentPart{textContentPart(t, "final", nil)}, + }) + testassert.NoError(t, err) } - if _, err := client.GetSessionHydrationWithResponse(ctx, session); err != nil { - t.Fatalf("post-flood session read: %v", err) + { + _, err := client.GetSessionHydrationWithResponse(ctx, session) + testassert.NoError(t, err) } awaitHealthy(before + 1) @@ -195,9 +191,7 @@ func TestProtocolServerStreamIsolation(t *testing.T) { // it settle success. gate() record := awaitOperation(t, r, session, "op-1", harness.OperationSuccess) - if record.State.Status != harness.OperationSuccess { - t.Fatalf("the gated operation settled as %q, want success after the stream loss", record.State.Status) - } + testassert.Equal(t, record.State.Status, harness.OperationSuccess) } // TestProtocolServerHeartbeat pins the 15-second keepalive comment on one @@ -229,9 +223,7 @@ func TestProtocolServerHeartbeat(t *testing.T) { }() select { case line := <-keepalive: - if line != ": keepalive" { - t.Fatalf("keepalive comment = %q, want exactly %q", line, ": keepalive") - } + testassert.Equal(t, line, ": keepalive") case <-time.After(sseHeartbeatInterval + 5*time.Second): t.Fatalf("no keepalive comment within one heartbeat interval (%s)", sseHeartbeatInterval) } @@ -248,20 +240,14 @@ func TestProtocolServerEventQualification(t *testing.T) { warning := warningChangedEvent(runtimeEventScope(), 7) qualified := qualifyEvent(warning, instance) value, err := qualified.AsWarningChangedEvent() - if err != nil || value.WarningsRevision.InstanceId != instance || value.WarningsRevision.Revision != "7" { - t.Fatalf("qualified warning event = (%+v, %v), want the instance-qualified revision", value, err) - } + testassert.Require(t, err == nil && value.WarningsRevision.InstanceId == instance && value.WarningsRevision.Revision == "7", "qualified warning event = (%+v, %v), want the instance-qualified revision", value, err) delta := textDeltaEvent(operationEventScope("ws", "session", "op"), 3, "chunk") unmodified := qualifyEvent(delta, instance) frame, err := json.Marshal(unmodified) - if err != nil || strings.Contains(string(frame), "instance_id") { - t.Fatalf("qualified transient event = %s (%v), want no instance member on the wire", frame, err) - } + testassert.Require(t, err == nil && !strings.Contains(string(frame), "instance_id"), "qualified transient event = %s (%v), want no instance member on the wire", frame, err) transient, err := unmodified.AsTextDeltaEvent() - if err != nil || transient.Content != "chunk" || transient.Position != 3 { - t.Fatalf("qualified transient event = (%+v, %v), want the unchanged payload", transient, err) - } + testassert.Require(t, err == nil && transient.Content == "chunk" && transient.Position == 3, "qualified transient event = (%+v, %v), want the unchanged payload", transient, err) store := storage.NewMemory() r, _ := openProjectionRuntime(t, store) @@ -272,13 +258,12 @@ func TestProtocolServerEventQualification(t *testing.T) { stream := openSSEStream(t, ps) defer stream.close() - if _, err := client.ReloadConfigurationWithResponse(ctx); err != nil { - t.Fatalf("reload: %v", err) + { + _, err := client.ReloadConfigurationWithResponse(ctx) + testassert.NoError(t, err) } frameEvent := readUntilEvent(t, stream, "configuration_changed", 16) - if got := eventRevisionInstance(t, frameEvent["configuration_revision"], "configuration_changed"); got != ps.instance { - t.Fatalf("stream configuration revision instance = %q, want the minted %q", got, ps.instance) - } + testassert.Equal(t, eventRevisionInstance(t, frameEvent["configuration_revision"], "configuration_changed"), ps.instance) } // TestProtocolServerShutdownTopology pins the shutdown ordering: the listener @@ -289,11 +274,7 @@ func TestProtocolServerEventQualification(t *testing.T) { func TestProtocolServerShutdownTopology(t *testing.T) { store := storage.NewMemory() blocked := newTargetedBlockStore(store) - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(blocked)) - if err != nil { - t.Fatalf("open: %v", err) - } + r, e := openProtocolOwner(t, blocked) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) client := protocolClient(t, ps) @@ -337,18 +318,15 @@ func TestProtocolServerShutdownTopology(t *testing.T) { // One published discovery record the shutdown must withdraw. record := filepath.Join(t.TempDir(), "discovery.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(record)) workspace := filepath.Join(e.home, "shutdown") created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: workspace, AgentType: "solo"}) - if err != nil || created.JSON201 == nil { - t.Fatalf("create: %v", err) - } + testassert.Require(t, err == nil && created.JSON201 != nil, "create: %v", err) session := created.JSON201.SessionId - if response, err := client.ArchiveSessionWithResponse(ctx, session); err != nil || response.JSON200 == nil { - t.Fatalf("archive: %v", err) + { + response, err := client.ArchiveSessionWithResponse(ctx, session) + testassert.Require(t, err == nil && response.JSON200 != nil, "archive: %v", err) } // One active stream closes when the server's connections close. @@ -403,13 +381,15 @@ func TestProtocolServerShutdownTopology(t *testing.T) { } // The record is still in place while the owner is joined on the command. - if _, err := os.Stat(record); err != nil { - t.Fatalf("the discovery record was withdrawn before the joined command settled: %v", err) + { + _, err := os.Stat(record) + testassert.NoError(t, err) } // A racing attachment after shutdown began cannot attach. - if second, err := r.OpenProtocol(context.Background()); err == nil || !errors.Is(err, ErrClosed) { - t.Fatalf("post-shutdown attachment = (%v, %v), want the closed refusal", second, err) + { + second, err := r.OpenProtocol(context.Background()) + testassert.Require(t, err != nil && errors.Is(err, ErrClosed), "post-shutdown attachment = (%v, %v), want the closed refusal", second, err) } // Releasing the parked transaction lets the deletion settle, the close @@ -427,17 +407,17 @@ func TestProtocolServerShutdownTopology(t *testing.T) { select { case err := <-closeDone: closeJoined = true - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the owner shutdown never converged after the command settled") } - if _, err := os.Stat(record); !os.IsNotExist(err) { - t.Fatalf("the discovery record survived shutdown: %v", err) + { + _, err := os.Stat(record) + testassert.Require(t, os.IsNotExist(err), "the discovery record survived shutdown: %v", err) } - if _, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")); err == nil { - t.Fatal("the protocol listener still accepts after shutdown") + { + _, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")) + testassert.Require(t, err != nil, "the protocol listener still accepts after shutdown") } e.assertLockReleased(t) } @@ -447,30 +427,20 @@ func TestProtocolServerShutdownTopology(t *testing.T) { // claim after a failed pre-write attempt, the required existing parent, and // the post-shutdown refusal. func TestProtocolServerDiscoveryPublication(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) dir := t.TempDir() record := filepath.Join(dir, "discovery.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(record)) info, err := os.Stat(record) - if err != nil { - t.Fatalf("published record: %v", err) - } + testassert.NoError(t, err) if info.Mode().Perm() != 0o600 { t.Fatalf("published record mode = %o, want 0600", info.Mode().Perm()) } data, err := os.ReadFile(record) - if err != nil { - t.Fatalf("read published record: %v", err) - } + testassert.NoError(t, err) decoder := json.NewDecoder(strings.NewReader(string(data))) decoder.DisallowUnknownFields() var published struct { @@ -482,34 +452,34 @@ func TestProtocolServerDiscoveryPublication(t *testing.T) { if err := decoder.Decode(&published); err != nil { t.Fatalf("decode published record %s: %v", string(data), err) } - if published.Endpoint != ps.Endpoint() || published.InstanceId != ps.instance || - published.ProtocolVersion != string(protocol.N2) || published.Credential != ps.credential { - t.Fatalf("published record = %+v, want the endpoint, identity, wire version, and credential", published) - } + testassert.Require(t, published.Endpoint == ps.Endpoint() && published.InstanceId == ps.instance && published.ProtocolVersion == string(protocol.N2) && published.Credential == ps.credential, "published record = %+v, want the endpoint, identity, wire version, and credential", published) // A second publication is refused — successful publication cannot repeat. second := filepath.Join(dir, "second.json") - if err := ps.PublishDiscovery(second); err == nil { - t.Fatal("repeat publication was accepted") + { + err := ps.PublishDiscovery(second) + testassert.Require(t, err != nil, "repeat publication was accepted") } - if _, err := os.Stat(second); !os.IsNotExist(err) { - t.Fatal("the refused repeat published a second record") + { + _, err := os.Stat(second) + testassert.Require(t, os.IsNotExist(err), "the refused repeat published a second record") } // After the owner's shutdown, publication is refused and leaves no // record: the owner never answers health again. - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) late := filepath.Join(dir, "late.json") - if err := ps.PublishDiscovery(late); err == nil || !errors.Is(err, ErrClosed) { - t.Fatalf("post-shutdown publication = %v, want the closed refusal", err) + { + err := ps.PublishDiscovery(late) + testassert.Require(t, err != nil && errors.Is(err, ErrClosed), "post-shutdown publication = %v, want the closed refusal", err) } - if _, err := os.Stat(late); !os.IsNotExist(err) { - t.Fatal("the refused post-shutdown publication wrote a record") + { + _, err := os.Stat(late) + testassert.Require(t, os.IsNotExist(err), "the refused post-shutdown publication wrote a record") } - if _, err := os.Stat(record); !os.IsNotExist(err) { - t.Fatalf("the discovery record %s survived shutdown", record) + { + _, err := os.Stat(record) + testassert.Require(t, os.IsNotExist(err), "the discovery record %s survived shutdown", record) } } @@ -517,31 +487,29 @@ func TestProtocolServerDiscoveryPublication(t *testing.T) { // claim: a pre-write failure — the parent directory must already exist — // leaves the server free to publish at the next valid path. func TestProtocolServerDiscoveryClaimRelease(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) dir := t.TempDir() missing := filepath.Join(dir, "absent-parent", "discovery.json") - if err := ps.PublishDiscovery(missing); err == nil { - t.Fatal("publication into a missing parent was accepted") + { + err := ps.PublishDiscovery(missing) + testassert.Require(t, err != nil, "publication into a missing parent was accepted") } - if _, err := os.Stat(missing); !os.IsNotExist(err) { - t.Fatal("the failed attempt published a record") + { + _, err := os.Stat(missing) + testassert.Require(t, os.IsNotExist(err), "the failed attempt published a record") } - if _, err := os.Stat(filepath.Join(dir, "absent-parent")); !os.IsNotExist(err) { - t.Fatal("the failed attempt resurrected the parent directory") + { + _, err := os.Stat(filepath.Join(dir, "absent-parent")) + testassert.Require(t, os.IsNotExist(err), "the failed attempt resurrected the parent directory") } retry := filepath.Join(dir, "retry.json") - if err := ps.PublishDiscovery(retry); err != nil { - t.Fatalf("publication after the failed attempt: %v", err) - } - if _, err := os.Stat(retry); err != nil { - t.Fatalf("the retried publication's record: %v", err) + testassert.NoError(t, ps.PublishDiscovery(retry)) + { + _, err := os.Stat(retry) + testassert.NoError(t, err) } } @@ -549,28 +517,18 @@ func TestProtocolServerDiscoveryClaimRelease(t *testing.T) { // second attachment while one exists fails invalid and the first server keeps // serving. func TestProtocolServerAttachmentRaces(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) first := openProtocolServer(t, r) second, err := r.OpenProtocol(context.Background()) - if err == nil || !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("second attachment = (%v, %v), want the invalid refusal", second, err) - } - if second != nil { - t.Fatal("the refused attachment returned a server") - } + testassert.Require(t, err != nil && errors.Is(err, harness.ErrInvalid), "second attachment = (%v, %v), want the invalid refusal", second, err) + testassert.Require(t, second == nil, "the refused attachment returned a server") // The first server is untouched: health still answers. resp := rawProtocol(t, http.MethodGet, protocolTarget(first, "/v1/health"), first.credential, "") defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("health after the refused attachment = %d, want 200", resp.StatusCode) - } + testassert.Equal(t, resp.StatusCode, http.StatusOK) } // TestProtocolServerDiscoveryPublicationRacingClose pins the publication @@ -579,11 +537,7 @@ func TestProtocolServerAttachmentRaces(t *testing.T) { // written record is withdrawn before the ownership lock releases, and a // publication attempted after closure is the refused sibling. func TestProtocolServerDiscoveryPublicationRacingClose(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, e := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) @@ -623,8 +577,9 @@ func TestProtocolServerDiscoveryPublicationRacingClose(t *testing.T) { t.Fatalf("the parked publication returned before its write completed: %v", err) default: } - if _, err := os.Stat(record); !os.IsNotExist(err) { - t.Fatalf("the parked write exposed the record before publication completed: %v", err) + { + _, err := os.Stat(record) + testassert.Require(t, os.IsNotExist(err), "the parked write exposed the record before publication completed: %v", err) } closeDone := make(chan error, 1) @@ -641,15 +596,11 @@ func TestProtocolServerDiscoveryPublicationRacingClose(t *testing.T) { releaseWrite() select { case err := <-publishDone: - if err != nil { - t.Fatalf("the joined publication failed: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the released publication never returned") } - if got := probe.count(); got != 1 { - t.Fatalf("the owning probe matched %d syncs, want exactly the discovery write", got) - } + testassert.Equal(t, probe.count(), 1) select { case <-releaseArrived: @@ -657,15 +608,14 @@ func TestProtocolServerDiscoveryPublicationRacingClose(t *testing.T) { t.Fatal("the owner lock release was never reached") } // The record is already withdrawn while the lock release is parked. - if _, err := os.Stat(record); !os.IsNotExist(err) { - t.Fatalf("the discovery record survived into the lock release: %v", err) + { + _, err := os.Stat(record) + testassert.Require(t, os.IsNotExist(err), "the discovery record survived into the lock release: %v", err) } releaseLock() select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("Close never converged after the lock release") } @@ -673,19 +623,19 @@ func TestProtocolServerDiscoveryPublicationRacingClose(t *testing.T) { // The hooked release reported success without dropping the flock; drop // it for real and prove the ownership lock is free. atomicfs.ReleaseFunc = previousRelease - if err := r.lock.Release(); err != nil { - t.Fatalf("real ownership lock release: %v", err) - } + testassert.NoError(t, r.lock.Release()) e.assertLockReleased(t) // The nearest sibling: a publication attempted after closure is refused // and leaves no record. late := filepath.Join(dir, "late.json") - if err := ps.PublishDiscovery(late); err == nil || !errors.Is(err, ErrClosed) { - t.Fatalf("post-shutdown publication = %v, want the closed refusal", err) + { + err := ps.PublishDiscovery(late) + testassert.Require(t, err != nil && errors.Is(err, ErrClosed), "post-shutdown publication = %v, want the closed refusal", err) } - if _, err := os.Stat(late); !os.IsNotExist(err) { - t.Fatal("the refused post-shutdown publication wrote a record") + { + _, err := os.Stat(late) + testassert.Require(t, os.IsNotExist(err), "the refused post-shutdown publication wrote a record") } } @@ -704,38 +654,22 @@ func TestProtocolServerStreamDisconnectPreservesLiveJob(t *testing.T) { stream.close() hydration, err := client.GetSessionHydrationWithResponse(ctx, session) - if err != nil || hydration.JSON200 == nil { - t.Fatalf("hydration after disconnect: %v", err) - } + testassert.Require(t, err == nil && hydration.JSON200 != nil, "hydration after disconnect: %v", err) live := false for _, member := range hydration.JSON200.Background { if member.Kind == protocol.BackgroundMemberKindJob && member.Id == "0a1b2c3d" { live = true } } - if !live { - t.Fatalf("hydration background = %+v, want the live job member after the stream disconnect", hydration.JSON200.Background) - } - if stops := countEvents(e.events.all(), "stopjob"); stops != 0 { - t.Fatalf("the stream disconnect invoked the job stopper %d times", stops) - } + testassert.Require(t, live, "hydration background = %+v, want the live job member after the stream disconnect", hydration.JSON200.Background) + testassert.Equal(t, countEvents(e.events.all(), "stopjob"), 0) // The existing stop path completes the job through the fixture seam. - if err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - return h.Stop(ctx, session) - }); err != nil { - t.Fatalf("Stop: %v", err) - } - if stops := countEvents(e.events.all(), "stopjob"); stops != 1 { - t.Fatalf("stop invoked the job stopper %d times, want exactly one", stops) - } + testassert.NoError(t, r.stopSession(ctx, session)) + testassert.Equal(t, countEvents(e.events.all(), "stopjob"), 1) settled, err := client.GetSessionHydrationWithResponse(ctx, session) - if err != nil || settled.JSON200 == nil { - t.Fatalf("hydration after stop: %v", err) - } - if len(settled.JSON200.Background) != 0 { - t.Fatalf("hydration background = %+v, want the stopped job removed", settled.JSON200.Background) - } + testassert.Require(t, err == nil && settled.JSON200 != nil, "hydration after stop: %v", err) + testassert.Require(t, len(settled.JSON200.Background) == 0, "hydration background = %+v, want the stopped job removed", settled.JSON200.Background) } // firstNonLoopbackAddress returns the machine's first usable non-loopback @@ -743,9 +677,7 @@ func TestProtocolServerStreamDisconnectPreservesLiveJob(t *testing.T) { func firstNonLoopbackAddress(t *testing.T) string { t.Helper() interfaces, err := net.Interfaces() - if err != nil { - t.Fatalf("list network interfaces: %v", err) - } + testassert.NoError(t, err) for _, iface := range interfaces { if iface.Flags&net.FlagUp == 0 { continue @@ -770,11 +702,7 @@ func firstNonLoopbackAddress(t *testing.T) string { // endpoint answers only on the loopback interface: dialing the bound port // through a non-loopback local address fails. func TestProtocolServerListenerIsLoopbackOnly(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) _, port, err := net.SplitHostPort(strings.TrimPrefix(ps.Endpoint(), "http://")) @@ -851,11 +779,7 @@ func processListenPorts(t *testing.T) map[int]bool { // attachment returns the closed refusal with no attachment while the // process-table-captured listener port stops answering. func TestProtocolServerPostBindCloseRace(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) before := processListenPorts(t) @@ -909,14 +833,10 @@ func TestProtocolServerPostBindCloseRace(t *testing.T) { if before[port] { continue } - if boundPort != 0 { - t.Fatalf("more than one new listening socket appeared: %d and %d", boundPort, port) - } + testassert.Require(t, boundPort == 0, "more than one new listening socket appeared: %d and %d", boundPort, port) boundPort = port } - if boundPort == 0 { - t.Fatal("no newly bound listening socket appeared while OpenProtocol was parked") - } + testassert.Require(t, boundPort != 0, "no newly bound listening socket appeared while OpenProtocol was parked") boundAddress := net.JoinHostPort("127.0.0.1", strconv.Itoa(boundPort)) closeStarted = true @@ -929,9 +849,7 @@ func TestProtocolServerPostBindCloseRace(t *testing.T) { if closed { break } - if time.Now().After(deadline) { - t.Fatal("Close never marked admission closed") - } + testassert.Require(t, !time.Now().After(deadline), "Close never marked admission closed") time.Sleep(time.Millisecond) } @@ -951,16 +869,12 @@ func TestProtocolServerPostBindCloseRace(t *testing.T) { r.mu.Lock() attached := r.protocol != nil r.mu.Unlock() - if attached { - t.Fatal("the rejected OpenProtocol left an attachment") - } + testassert.Require(t, !attached, "the rejected OpenProtocol left an attachment") select { case err := <-closeDone: closeConsumed = true - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("Close never converged after the rejected attachment") } @@ -975,11 +889,7 @@ func TestProtocolServerPostBindCloseRace(t *testing.T) { // verification completes, Close cancels the owner work context, and the // admitted publisher errors without remembering a path or writing a record. func TestProtocolServerPublicationReadinessVsClose(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) @@ -1016,26 +926,21 @@ func TestProtocolServerPublicationReadinessVsClose(t *testing.T) { go func() { closeDone <- r.Close(context.Background()) }() select { case err := <-publishDone: - if err == nil { - t.Fatal("the readiness-gated publication succeeded") - } + testassert.Require(t, err != nil, "the readiness-gated publication succeeded") case <-time.After(10 * time.Second): t.Fatal("the readiness-gated publication never returned") } ps.pubMu.Lock() remembered := ps.discoveryPath ps.pubMu.Unlock() - if remembered != "" { - t.Fatalf("the refused publication remembered path %q", remembered) - } - if _, err := os.Stat(record); !os.IsNotExist(err) { - t.Fatalf("the refused publication wrote a record: %v", err) + testassert.Equal(t, remembered, "") + { + _, err := os.Stat(record) + testassert.Require(t, os.IsNotExist(err), "the refused publication wrote a record: %v", err) } select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("Close never converged while readiness was gated") } @@ -1048,29 +953,25 @@ func TestProtocolServerPublicationReadinessVsClose(t *testing.T) { func TestProtocolServerDiscoveryConcurrentPublishers(t *testing.T) { assertOnePublication := func(t *testing.T, remembered string, firstErr, secondErr error, first, second string) { t.Helper() - if (firstErr == nil) == (secondErr == nil) { - t.Fatalf("concurrent publications = (%v, %v), want exactly one success", firstErr, secondErr) - } + testassert.Require(t, (firstErr == nil) != (secondErr == nil), "concurrent publications = (%v, %v), want exactly one success", firstErr, secondErr) winner, loser := first, second if remembered == second { winner, loser = second, first } else if remembered != first { t.Fatalf("remembered path %q is neither published path", remembered) } - if _, err := os.Stat(winner); err != nil { - t.Fatalf("the successful publication's record: %v", err) + { + _, err := os.Stat(winner) + testassert.NoError(t, err) } - if _, err := os.Stat(loser); !os.IsNotExist(err) { - t.Fatalf("the refused publication left a record at %s: %v", loser, err) + { + _, err := os.Stat(loser) + testassert.Require(t, os.IsNotExist(err), "the refused publication left a record at %s: %v", loser, err) } } t.Run("parked first write leaves one remembered path", func(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) dir := t.TempDir() @@ -1098,9 +999,7 @@ func TestProtocolServerDiscoveryConcurrentPublishers(t *testing.T) { releaseWrite() firstErr := <-firstResult secondErr := <-secondResult - if got := probe.count(); got != 1 { - t.Fatalf("the owning probe matched %d syncs, want only the first path's write", got) - } + testassert.Equal(t, probe.count(), 1) ps.pubMu.Lock() remembered := ps.discoveryPath ps.pubMu.Unlock() @@ -1108,11 +1007,7 @@ func TestProtocolServerDiscoveryConcurrentPublishers(t *testing.T) { }) t.Run("injected first-write failure lets the waiter succeed", func(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) dir := t.TempDir() @@ -1135,26 +1030,20 @@ func TestProtocolServerDiscoveryConcurrentPublishers(t *testing.T) { go func() { secondResult <- ps.PublishDiscovery(second) }() firstErr := <-firstResult secondErr := <-secondResult - if !errors.Is(firstErr, injected) { - t.Fatalf("the injected first write = %v, want the injected failure", firstErr) - } - if secondErr != nil { - t.Fatalf("the waiting second publication = %v, want success after the released claim", secondErr) - } - if got := probe.count(); got != 1 { - t.Fatalf("the owning probe matched %d syncs, want only the first path's write", got) - } + testassert.Require(t, errors.Is(firstErr, injected), "the injected first write = %v, want the injected failure", firstErr) + testassert.NoError(t, secondErr) + testassert.Equal(t, probe.count(), 1) ps.pubMu.Lock() remembered := ps.discoveryPath ps.pubMu.Unlock() - if remembered != second { - t.Fatalf("remembered path = %q, want the second publisher's %q", remembered, second) - } - if _, err := os.Stat(second); err != nil { - t.Fatalf("the second publication's record: %v", err) + testassert.Equal(t, remembered, second) + { + _, err := os.Stat(second) + testassert.NoError(t, err) } - if _, err := os.Stat(first); !os.IsNotExist(err) { - t.Fatalf("the failed publication left a record at %s: %v", first, err) + { + _, err := os.Stat(first) + testassert.Require(t, os.IsNotExist(err), "the failed publication left a record at %s: %v", first, err) } }) } @@ -1165,57 +1054,35 @@ func TestProtocolServerDiscoveryConcurrentPublishers(t *testing.T) { // releases and the directory contents survive. func TestProtocolServerDiscoveryWithdrawalStates(t *testing.T) { t.Run("already removed", func(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, e := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) record := filepath.Join(t.TempDir(), "discovery.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } - if err := os.Remove(record); err != nil { - t.Fatalf("remove the record before shutdown: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close with an already-removed record: %v", err) - } - if _, err := os.Stat(record); !os.IsNotExist(err) { - t.Fatalf("the removed record reappeared: %v", err) + testassert.NoError(t, ps.PublishDiscovery(record)) + testassert.NoError(t, os.Remove(record)) + testassert.NoError(t, r.Close(context.Background())) + { + _, err := os.Stat(record) + testassert.Require(t, os.IsNotExist(err), "the removed record reappeared: %v", err) } e.assertLockReleased(t) }) t.Run("replaced by a nonempty directory", func(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, e := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) record := filepath.Join(t.TempDir(), "discovery.json") - if err := ps.PublishDiscovery(record); err != nil { - t.Fatalf("PublishDiscovery: %v", err) - } - if err := os.Remove(record); err != nil { - t.Fatalf("remove the record: %v", err) - } - if err := os.Mkdir(record, 0o700); err != nil { - t.Fatalf("replace the record with a directory: %v", err) - } + testassert.NoError(t, ps.PublishDiscovery(record)) + testassert.NoError(t, os.Remove(record)) + testassert.NoError(t, os.Mkdir(record, 0o700)) inner := filepath.Join(record, "inner.txt") - if err := os.WriteFile(inner, []byte("kept"), 0o600); err != nil { - t.Fatalf("write the directory content: %v", err) - } - err = r.Close(context.Background()) - if err == nil || !strings.Contains(err.Error(), "withdraw discovery record") { - t.Fatalf("Close with a replaced record = %v, want the removal failure", err) - } - if data, err := os.ReadFile(inner); err != nil || string(data) != "kept" { - t.Fatalf("the directory content = (%q, %v), want it preserved", data, err) + testassert.NoError(t, os.WriteFile(inner, []byte("kept"), 0o600)) + err := r.Close(context.Background()) + testassert.Require(t, err != nil && strings.Contains(err.Error(), "withdraw discovery record"), "Close with a replaced record = %v, want the removal failure", err) + { + data, err := os.ReadFile(inner) + testassert.Require(t, err == nil && string(data) == "kept", "the directory content = (%q, %v), want it preserved", data, err) } e.assertLockReleased(t) }) diff --git a/runtime/protocol_paging_test.go b/runtime/protocol_paging_test.go index b368730d..32d5fa4b 100644 --- a/runtime/protocol_paging_test.go +++ b/runtime/protocol_paging_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -54,39 +55,27 @@ func pageHeaders(n int) []harness.HistoryEntryHeader { func wireItemID(t *testing.T, item protocol.ConversationItem) string { t.Helper() discriminator, err := item.Discriminator() - if err != nil { - t.Fatalf("conversation item discriminator: %v", err) - } + testassert.NoError(t, err) switch discriminator { case "input": v, err := item.AsInputItem() - if err != nil { - t.Fatalf("AsInputItem: %v", err) - } + testassert.NoError(t, err) return v.ItemId case "assistant": v, err := item.AsAssistantItem() - if err != nil { - t.Fatalf("AsAssistantItem: %v", err) - } + testassert.NoError(t, err) return v.ItemId case "signal": v, err := item.AsSignalItem() - if err != nil { - t.Fatalf("AsSignalItem: %v", err) - } + testassert.NoError(t, err) return v.ItemId case "compaction": v, err := item.AsCompactionItem() - if err != nil { - t.Fatalf("AsCompactionItem: %v", err) - } + testassert.NoError(t, err) return v.ItemId case "operation_end": v, err := item.AsOperationEndItem() - if err != nil { - t.Fatalf("AsOperationEndItem: %v", err) - } + testassert.NoError(t, err) return v.ItemId default: t.Fatalf("conversation item kind %q is outside the closed set", discriminator) @@ -117,71 +106,46 @@ func TestHistoryPagePureThreePages(t *testing.T) { headers := pageHeaders(120) page1, older1, err := selectHistoryPage(convSessionID, headers, nil) - if err != nil { - t.Fatalf("initial page: %v", err) - } - if len(page1) != 50 { - t.Fatalf("initial page = %d items, want exactly 50", len(page1)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(page1), 50) for i, entryID := range page1 { - if id := projectItemID(convSessionID, entryID); id != wantPageItemID(70+i) { - t.Fatalf("initial item %d = %q, want the ascending item %d", i, id, 70+i) + { + id := projectItemID(convSessionID, entryID) + testassert.Require(t, id == wantPageItemID(70+i), "initial item %d = %q, want the ascending item %d", i, id, 70+i) } } - if older1 == nil { - t.Fatal("initial page produced no older cursor with 70 older items") - } + testassert.Require(t, older1 != nil, "initial page produced no older cursor with 70 older items") cursor1, err := decodeHistoryCursor(*older1, convSessionID) - if err != nil { - t.Fatalf("decode older cursor: %v", err) - } - if cursor1.SessionID != convSessionID || cursor1.AnchorItemID != wantPageItemID(70) { - t.Fatalf("older cursor = %+v, want this session anchored at item 70", cursor1) - } + testassert.NoError(t, err) + testassert.Require(t, cursor1.SessionID == convSessionID && cursor1.AnchorItemID == wantPageItemID(70), "older cursor = %+v, want this session anchored at item 70", cursor1) page2, older2, err := selectHistoryPage(convSessionID, headers, older1) - if err != nil { - t.Fatalf("second page: %v", err) - } - if len(page2) != 50 { - t.Fatalf("second page = %d items, want exactly 50", len(page2)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(page2), 50) for i, entryID := range page2 { - if id := projectItemID(convSessionID, entryID); id != wantPageItemID(20+i) { - t.Fatalf("second page item %d = %q, want item %d", i, id, 20+i) + { + id := projectItemID(convSessionID, entryID) + testassert.Require(t, id == wantPageItemID(20+i), "second page item %d = %q, want item %d", i, id, 20+i) } } for _, entryID := range page2 { // strictly before the anchor - if projectItemID(convSessionID, entryID) == cursor1.AnchorItemID { - t.Fatalf("second page contains its own anchor %q", entryID) - } - } - if older2 == nil { - t.Fatal("second page produced no older cursor with 20 older items") + testassert.Require(t, projectItemID(convSessionID, entryID) != cursor1.AnchorItemID, "second page contains its own anchor %q", entryID) } + testassert.Require(t, older2 != nil, "second page produced no older cursor with 20 older items") cursor2, err := decodeHistoryCursor(*older2, convSessionID) - if err != nil { - t.Fatalf("decode second cursor: %v", err) - } - if cursor2.AnchorItemID != wantPageItemID(20) { - t.Fatalf("second cursor anchor = %q, want item 20", cursor2.AnchorItemID) - } + testassert.NoError(t, err) + testassert.Require(t, cursor2.AnchorItemID == wantPageItemID(20), "second cursor anchor = %q, want item 20", cursor2.AnchorItemID) page3, older3, err := selectHistoryPage(convSessionID, headers, older2) - if err != nil { - t.Fatalf("third page: %v", err) - } - if len(page3) != 20 { - t.Fatalf("third page = %d items, want the remaining 20", len(page3)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(page3), 20) for i, entryID := range page3 { - if id := projectItemID(convSessionID, entryID); id != wantPageItemID(i) { - t.Fatalf("third page item %d = %q, want item %d", i, id, i) + { + id := projectItemID(convSessionID, entryID) + testassert.Require(t, id == wantPageItemID(i), "third page item %d = %q, want item %d", i, id, i) } } - if older3 != nil { - t.Fatalf("third page produced an older cursor %v with nothing older", older3) - } + testassert.Require(t, older3 == nil, "third page produced an older cursor %v with nothing older", older3) } // TestHistoryPagePureIndivisibleBoundary proves the page boundary can never @@ -199,41 +163,23 @@ func TestHistoryPagePureIndivisibleBoundary(t *testing.T) { ) page1, older1, err := selectHistoryPage(convSessionID, headers, nil) - if err != nil { - t.Fatalf("initial page: %v", err) - } - if len(page1) != 50 { - t.Fatalf("initial page = %d items, want exactly 50", len(page1)) - } - if page1[49] != assistantID { - t.Fatalf("page edge item = %q, want the assistant %q at the boundary", page1[49], assistantID) - } - if older1 == nil { - t.Fatal("initial page produced no older cursor with one older item") - } + testassert.NoError(t, err) + testassert.Equal(t, len(page1), 50) + testassert.Equal(t, page1[49], assistantID) + testassert.Require(t, older1 != nil, "initial page produced no older cursor with one older item") cursor1, err := decodeHistoryCursor(*older1, convSessionID) - if err != nil { - t.Fatalf("decode older cursor: %v", err) - } + testassert.NoError(t, err) if cursor1.AnchorItemID != wantPageItemID(1) { t.Fatalf("older cursor anchor = %q, want the oldest returned input %q", cursor1.AnchorItemID, wantPageItemID(1)) } page2, older2, err := selectHistoryPage(convSessionID, headers, older1) - if err != nil { - t.Fatalf("older page: %v", err) - } - if len(page2) != 1 || page2[0] != pageEntryID(0) { - t.Fatalf("older page = %v, want the single oldest input", page2) - } - if older2 != nil { - t.Fatalf("older page produced a next cursor %v with nothing older", older2) - } + testassert.NoError(t, err) + testassert.Require(t, len(page2) == 1 && page2[0] == pageEntryID(0), "older page = %v, want the single oldest input", page2) + testassert.Require(t, older2 == nil, "older page produced a next cursor %v with nothing older", older2) // The result entry never became an item of either page. for _, page := range [][]string{page1, page2} { for _, entryID := range page { - if entryID == resultID { - t.Fatalf("the terminal result entry %q became a page item", resultID) - } + testassert.Require(t, entryID != resultID, "the terminal result entry %q became a page item", resultID) } } } @@ -265,8 +211,9 @@ func cursorJSON(sessionID, anchor string) string { func TestHistoryCursorStrictValidation(t *testing.T) { headers := pageHeaders(3) valid := cursorBody(t, cursorJSON(convSessionID, wantPageItemID(1))) - if _, _, err := selectHistoryPage(convSessionID, headers, valid); err != nil { - t.Fatalf("valid cursor rejected: %v", err) + { + _, _, err := selectHistoryPage(convSessionID, headers, valid) + testassert.NoError(t, err) } rejections := map[string]*string{ @@ -284,20 +231,17 @@ func TestHistoryCursorStrictValidation(t *testing.T) { "nonexistent anchor": cursorBody(t, cursorJSON(convSessionID, "missing")), } for name, cursor := range rejections { - if _, _, err := selectHistoryPage(convSessionID, headers, cursor); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s: selectHistoryPage = %v, want harness.ErrInvalid", name, err) + { + _, _, err := selectHistoryPage(convSessionID, headers, cursor) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s: selectHistoryPage = %v, want harness.ErrInvalid", name, err) } } // The one uniform slicing rule for a resolvable anchor with nothing // older: a present empty page with no next cursor. page, older, err := selectHistoryPage(convSessionID, headers, cursorBody(t, cursorJSON(convSessionID, wantPageItemID(0)))) - if err != nil { - t.Fatalf("oldest-anchor page: %v", err) - } - if page == nil || len(page) != 0 || older != nil { - t.Fatalf("oldest-anchor page = %+v / %+v, want the present empty page with no cursor", page, older) - } + testassert.NoError(t, err) + testassert.Require(t, page != nil && len(page) == 0 && older == nil, "oldest-anchor page = %+v / %+v, want the present empty page with no cursor", page, older) } // --- the pure usage clocks --- @@ -323,8 +267,9 @@ func TestUsedTokensPureClocks(t *testing.T) { {"positive extreme beyond MaxInt64", &harness.UsageCount{InputTokens: math.MaxInt64, CachedInputTokens: math.MaxInt64, OutputTokens: 0}, "18446744073709551614"}, } for _, tc := range cases { - if got := projectUsedTokens(tc.measured); got != tc.want { - t.Fatalf("%s: used tokens = %q, want %q", tc.name, got, tc.want) + { + got := projectUsedTokens(tc.measured) + testassert.Require(t, got == tc.want, "%s: used tokens = %q, want %q", tc.name, got, tc.want) } } } @@ -345,9 +290,7 @@ const ( // from a durably current Operation is the composed view's own observation. func TestSessionContextWindowPureClocks(t *testing.T) { captured, err := assembleConfiguration(2, usageWindowConfig, usageWindowAgents, nil) - if err != nil { - t.Fatalf("assembleConfiguration: %v", err) - } + testassert.NoError(t, err) window := func(view harness.SessionReadView) int { return sessionContextWindow(view, resolveSessionSelection(view.Header.CurrentAgentType, captured)) } @@ -357,9 +300,7 @@ func TestSessionContextWindowPureClocks(t *testing.T) { Header: harness.SessionHeader{CurrentOperationID: "op-1", CurrentAgentType: "solo"}, ActiveContextWindow: 4096, } - if got := window(active); got != 4096 { - t.Fatalf("active window = %d, want the captured 4096", got) - } + testassert.Equal(t, window(active), 4096) // The busy fact and a retiring run alone are not an active Operation: // with no durably running current Operation the idle clock applies. @@ -367,20 +308,12 @@ func TestSessionContextWindowPureClocks(t *testing.T) { Header: harness.SessionHeader{CurrentAgentType: "solo"}, History: harness.HistoryReadView{ExecutionBusy: true}, } - if got := window(retiring); got != 8192 { - t.Fatalf("retiring-run window = %d, want the captured catalog 8192", got) - } + testassert.Equal(t, window(retiring), 8192) idle := harness.SessionReadView{Header: harness.SessionHeader{CurrentAgentType: "solo"}} - if got := window(idle); got != 8192 { - t.Fatalf("idle window = %d, want the catalog 8192", got) - } - if got := window(harness.SessionReadView{Header: harness.SessionHeader{CurrentAgentType: "modelless"}}); got != 0 { - t.Fatalf("modelless window = %d, want 0", got) - } - if got := window(harness.SessionReadView{Header: harness.SessionHeader{CurrentAgentType: "unknown"}}); got != 0 { - t.Fatalf("unknown type window = %d, want 0", got) - } + testassert.Equal(t, window(idle), 8192) + testassert.Equal(t, window(harness.SessionReadView{Header: harness.SessionHeader{CurrentAgentType: "modelless"}}), 0) + testassert.Equal(t, window(harness.SessionReadView{Header: harness.SessionHeader{CurrentAgentType: "unknown"}}), 0) } // --- the live composed-Runtime rows --- @@ -457,9 +390,7 @@ func usageSeedSession(t *testing.T, store harness.Storage, sessionID, currentOp, LastActivity: now, }, }) - if err != nil { - t.Fatalf("marshal session register: %v", err) - } + testassert.NoError(t, err) lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession}, string(payload)) } @@ -472,7 +403,7 @@ func usageSeedMessageOperation(t *testing.T, store harness.Storage, sessionID, o now := time.Now().UTC() inputID, assistantID, settlementID := newLifecycleID(t), newLifecycleID(t), newLifecycleID(t) lifecycleInsertEntry(t, store, sessionID, inputID, operationID, harness.EntryInput, - compactRawInputEntry(sessionID, inputID, operationID, "question "+operationID)) + compactRawInputEntry(sessionID, inputID, operationID, "user", "question "+operationID)) assistant, err := json.Marshal(struct { SessionID string `json:"session_id"` EntryID string `json:"entry_id"` @@ -489,54 +420,29 @@ func usageSeedMessageOperation(t *testing.T, store harness.Storage, sessionID, o ToolCalls: []harness.ToolCallRecord{}, Usage: assistantUsage, }) - if err != nil { - t.Fatalf("marshal assistant: %v", err) - } - settlementWire := struct { - SessionID string `json:"session_id"` - EntryID string `json:"entry_id"` - OperationID string `json:"operation_id"` - Status string `json:"status"` - Model *usageWireRef `json:"model,omitempty"` - Usage *harness.UsageCount `json:"usage,omitempty"` - }{ - SessionID: sessionID, EntryID: settlementID, OperationID: operationID, Status: "success", - } + testassert.NoError(t, err) + settlementExtra := "" if settlementUsage != nil { - settlementWire.Model = &usageWireRef{Provider: "prov", Model: "m"} - settlementWire.Usage = settlementUsage - } - settlementData, err := json.Marshal(settlementWire) - if err != nil { - t.Fatalf("marshal settlement: %v", err) + model, err := json.Marshal(usageWireRef{Provider: "prov", Model: "m"}) + testassert.NoError(t, err) + usage, err := json.Marshal(settlementUsage) + testassert.NoError(t, err) + settlementExtra = `,"model":` + string(model) + `,"usage":` + string(usage) } lifecycleInsertEntry(t, store, sessionID, assistantID, operationID, harness.EntryAssistant, string(assistant)) - lifecycleInsertEntry(t, store, sessionID, settlementID, operationID, harness.EntryOperationSettlement, string(settlementData)) + lifecycleInsertEntry(t, store, sessionID, settlementID, operationID, harness.EntryOperationSettlement, + compactRawSettlementEntry(sessionID, settlementID, operationID, settlementExtra)) stateUsage := emptyUsageWireTotals() if assistantUsage != nil || settlementUsage != nil { stateUsage = usageWireTotalsFor(derefUsage(assistantUsage), derefUsage(settlementUsage)) } - state, err := json.Marshal(struct { - Status harness.OperationState `json:"status"` - StartedAt time.Time `json:"started_at"` - SettledAt time.Time `json:"settled_at"` - PendingToolCalls []harness.PendingToolCall `json:"pending_tool_calls"` - Usage usageWireTotals `json:"usage"` - Terminal harness.OperationTerminal `json:"terminal"` - }{ - Status: harness.OperationSuccess, - StartedAt: now, - SettledAt: now, - PendingToolCalls: []harness.PendingToolCall{}, - Usage: stateUsage, - Terminal: harness.OperationTerminal{SettlementEntry: harness.EntryRef{SessionID: sessionID, EntryID: settlementID}}, - }) - if err != nil { - t.Fatalf("marshal operation state: %v", err) - } + usageJSON, err := json.Marshal(stateUsage) + testassert.NoError(t, err) lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: operationID}, - fmt.Sprintf(`{"admission":%s,"state":%s}`, compactRawMessageAdmission(sessionID, operationID, inputID), state)) + compactRawOperationRegister( + compactRawMessageAdmission(sessionID, operationID, inputID), + compactRawSuccessState(now.Format(time.RFC3339Nano), settlementID, sessionID, string(usageJSON)))) return assistantID } @@ -568,20 +474,12 @@ func TestHistoryPagingAppendStableAnchors(t *testing.T) { } page1, err := r.getHistory(context.Background(), session, nil) - if err != nil { - t.Fatalf("initial history: %v", err) - } - if len(page1.Items) != 50 || page1.OlderCursor == nil { - t.Fatalf("initial page = %d items (cursor %v), want exactly 50 with an older cursor", len(page1.Items), page1.OlderCursor) - } + testassert.NoError(t, err) + testassert.Require(t, len(page1.Items) == 50 && page1.OlderCursor != nil, "initial page = %d items (cursor %v), want exactly 50 with an older cursor", len(page1.Items), page1.OlderCursor) wantPage1 := strings.Join(pageItemIDs(t, page1), ",") page2, err := r.getHistory(context.Background(), session, page1.OlderCursor) - if err != nil { - t.Fatalf("older history: %v", err) - } - if len(page2.Items) != 10 || page2.OlderCursor != nil { - t.Fatalf("older page = %d items (cursor %v), want the remaining 10 with no cursor", len(page2.Items), page2.OlderCursor) - } + testassert.NoError(t, err) + testassert.Require(t, len(page2.Items) == 10 && page2.OlderCursor == nil, "older page = %d items (cursor %v), want the remaining 10 with no cursor", len(page2.Items), page2.OlderCursor) wantPage2 := strings.Join(pageItemIDs(t, page2), ",") firstRevision := page1.SessionRevision @@ -592,38 +490,24 @@ func TestHistoryPagingAppendStableAnchors(t *testing.T) { awaitIdleSession(t, r, session) page1After, err := r.getHistory(context.Background(), session, nil) - if err != nil { - t.Fatalf("initial history after append: %v", err) - } - if len(page1After.Items) != 50 || page1After.OlderCursor == nil { - t.Fatalf("post-append initial page = %d items, want exactly 50", len(page1After.Items)) - } + testassert.NoError(t, err) + testassert.Require(t, len(page1After.Items) == 50 && page1After.OlderCursor != nil, "post-append initial page = %d items, want exactly 50", len(page1After.Items)) afterIDs := pageItemIDs(t, page1After) oldIDs := strings.Split(wantPage1, ",") for i, id := range oldIDs { // Exactly the three appended items aged out of the newest-50 // window; every other stable identity remains. - if agedOut := i < 3; slices.Contains(afterIDs, id) == agedOut { - t.Fatalf("post-append page item %d (%q) presence = %v, want the stable window shift by 3", i, id, !agedOut) + { + agedOut := i < 3 + testassert.Require(t, slices.Contains(afterIDs, id) != agedOut, "post-append page item %d (%q) presence = %v, want the stable window shift by 3", i, id, !agedOut) } } - if page1After.SessionRevision.DurableRevision == firstRevision.DurableRevision { - t.Fatalf("post-append revision = %+v, want the advanced current revision (was %+v)", page1After.SessionRevision, firstRevision) - } + testassert.Require(t, page1After.SessionRevision.DurableRevision != firstRevision.DurableRevision, "post-append revision = %+v, want the advanced current revision (was %+v)", page1After.SessionRevision, firstRevision) page2After, err := r.getHistory(context.Background(), session, page1.OlderCursor) - if err != nil { - t.Fatalf("older history after append: %v", err) - } - if got := strings.Join(pageItemIDs(t, page2After), ","); got != wantPage2 { - t.Fatalf("post-append older page = %s, want the unchanged %s", got, wantPage2) - } - if page2After.SessionRevision.DurableRevision == firstRevision.DurableRevision { - t.Fatalf("older page returned the stale client revision %+v, want the current one", page2After.SessionRevision) - } - if page2After.SessionRevision.DurableRevision != page1After.SessionRevision.DurableRevision { - t.Fatalf("older page revision = %+v, want the same current revision as the fresh initial page %+v", page2After.SessionRevision, page1After.SessionRevision) - } + testassert.NoError(t, err) + testassert.Equal(t, strings.Join(pageItemIDs(t, page2After), ","), wantPage2) + testassert.Require(t, page2After.SessionRevision.DurableRevision != firstRevision.DurableRevision && page2After.SessionRevision.DurableRevision == page1After.SessionRevision.DurableRevision, "older page returned the stale client revision %+v, want the current one; older page revision = %+v, want the same current revision as the fresh initial page %+v", page2After.SessionRevision, page2After.SessionRevision, page1After.SessionRevision) }) } @@ -649,32 +533,28 @@ func TestHistoryReadsArchivedDeletedSessions(t *testing.T) { defer closeProjectionRuntime(r) archived := projectionSession(t, r, "/tmp/paging-archived", "solo").Identity.SessionID deleted := projectionSession(t, r, "/tmp/paging-deleted", "solo").Identity.SessionID - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { _, err := h.ArchiveSession(ctx, archived) return err - }); err != nil { - t.Fatalf("ArchiveSession: %v", err) - } - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { + })) + testassert.NoError(t, r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { _, err := h.ArchiveSession(ctx, deleted) return err - }); err != nil { - t.Fatalf("ArchiveSession(deleted): %v", err) - } - if err := r.deleteSession(context.Background(), deleted); err != nil { - t.Fatalf("DeleteSession: %v", err) - } + })) + testassert.NoError(t, r.deleteSession(context.Background(), deleted)) archivedReads := sessionReadCalls(context.Background(), r, archived) for i, read := range archivedReads { - if err := read(); err != nil { - t.Fatalf("archived read %d: %v", i, err) + { + err := read() + testassert.Require(t, err == nil, "archived read %d: %v", i, err) } } deletedReads := sessionReadCalls(context.Background(), r, deleted) for i, read := range deletedReads { - if err := read(); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("deleted read %d = %v, want harness.ErrNotFound", i, err) + { + err := read() + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "deleted read %d = %v, want harness.ErrNotFound", i, err) } } }) @@ -700,8 +580,9 @@ func TestHistoryCursorRejectionsThroughRuntime(t *testing.T) { cursorBody(t, cursorJSON(session, "missing")), } for i, cursor := range rejected { - if _, err := r.getHistory(context.Background(), session, cursor); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("cursor rejection %d = %v, want harness.ErrInvalid", i, err) + { + _, err := r.getHistory(context.Background(), session, cursor) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "cursor rejection %d = %v, want harness.ErrInvalid", i, err) } } }) @@ -730,34 +611,26 @@ func TestHistoryGeneratedClientCompactionRetainsOldCursor(t *testing.T) { // The pre-compaction paging: the newest 50 and the older 4, with the // older cursor anchored at the fourth item. page1, err := client.GetSessionHistoryWithResponse(ctx, session, &protocol.GetSessionHistoryParams{}) - if err != nil || page1.JSON200 == nil { - t.Fatalf("initial history: %v", err) - } + testassert.Require(t, err == nil && page1.JSON200 != nil, "initial history: %v", err) if len(page1.JSON200.Items) != 50 || page1.JSON200.OlderCursor == nil { body, _ := json.Marshal(page1.JSON200) t.Fatalf("initial history = %s, want exactly 50 items with an older cursor", body) } assertQualifiedInstance(t, "initial history", page1.JSON200.SessionRevision, ps.instance) page2, err := client.GetSessionHistoryWithResponse(ctx, session, &protocol.GetSessionHistoryParams{Cursor: page1.JSON200.OlderCursor}) - if err != nil || page2.JSON200 == nil { - t.Fatalf("older history: %v", err) - } + testassert.Require(t, err == nil && page2.JSON200 != nil, "older history: %v", err) if len(page2.JSON200.Items) != 4 || page2.JSON200.OlderCursor != nil { body, _ := json.Marshal(page2.JSON200) t.Fatalf("older history = %s, want the remaining 4 items with no cursor", body) } preItems := append(append([]protocol.ConversationItem{}, page1.JSON200.Items...), page2.JSON200.Items...) - if len(preItems) != 54 { - t.Fatalf("pre-compaction walk = %d items, want all 54", len(preItems)) - } + testassert.Equal(t, len(preItems), 54) preRevision := page1.JSON200.SessionRevision.DurableRevision oldCursor := *page1.JSON200.OlderCursor // The real manual compaction through the mounted command. compacted, err := client.CompactSessionWithResponse(ctx, session, protocol.CompactRequest{OperationId: "compact-1"}) - if err != nil { - t.Fatalf("CompactSession: %v", err) - } + testassert.NoError(t, err) if compacted.JSON200 == nil || compacted.JSON200.OperationId != "compact-1" { body, _ := json.Marshal(compacted.JSON200) t.Fatalf("compact = %s, want the admitted compact Operation", body) @@ -767,18 +640,11 @@ func TestHistoryGeneratedClientCompactionRetainsOldCursor(t *testing.T) { // The old cursor stays valid: the same four items, no compaction // item, nothing older, and the current revision. page2After, err := client.GetSessionHistoryWithResponse(ctx, session, &protocol.GetSessionHistoryParams{Cursor: &oldCursor}) - if err != nil || page2After.JSON200 == nil { - t.Fatalf("older history after compaction: %v", err) - } + testassert.Require(t, err == nil && page2After.JSON200 != nil, "older history after compaction: %v", err) if got := strings.Join(pageItemIDs(t, *page2After.JSON200), ","); got != strings.Join(pageItemIDs(t, *page2.JSON200), ",") { t.Fatalf("old-cursor page after compaction = %s, want the unchanged %s", got, strings.Join(pageItemIDs(t, *page2.JSON200), ",")) } - if page2After.JSON200.OlderCursor != nil { - t.Fatalf("old-cursor page after compaction produced an older cursor %v with nothing older", page2After.JSON200.OlderCursor) - } - if page2After.JSON200.SessionRevision.DurableRevision == preRevision { - t.Fatalf("old-cursor page revision = %s, want the advanced current revision", page2After.JSON200.SessionRevision.DurableRevision) - } + testassert.Require(t, page2After.JSON200.OlderCursor == nil && page2After.JSON200.SessionRevision.DurableRevision != preRevision, "old-cursor page after compaction produced an older cursor %v with nothing older; old-cursor page revision = %s, want the advanced current revision", page2After.JSON200.OlderCursor, page2After.JSON200.SessionRevision.DurableRevision) assertQualifiedInstance(t, "old-cursor page", page2After.JSON200.SessionRevision, ps.instance) // The complete history plus the compaction item stays pageable: walk @@ -799,25 +665,19 @@ func TestHistoryGeneratedClientCompactionRetainsOldCursor(t *testing.T) { firstPageLen := 0 for cursor := (*string)(nil); ; { page, next, err := walk(cursor) - if err != nil { - t.Fatalf("%v", err) - } + testassert.NoError(t, err) if cursor == nil { firstPageLen = len(page.Items) } walked = append(walked, page.Items...) currentRevision = page.SessionRevision.DurableRevision - if currentRevision == preRevision { - t.Fatalf("walk page revision = %s, want the advanced current revision", currentRevision) - } + testassert.Require(t, currentRevision != preRevision, "walk page revision = %s, want the advanced current revision", currentRevision) if next == nil { break } cursor = next } - if len(walked) != 56 { - t.Fatalf("post-compaction walk = %d items, want all 54 original items plus the compaction and settlement items", len(walked)) - } + testassert.Equal(t, len(walked), 56) // Exactly one compaction item, carrying the real scripted model's // summary, and the compact Operation's settlement beside it — the // first page's two newest items. @@ -827,57 +687,40 @@ func TestHistoryGeneratedClientCompactionRetainsOldCursor(t *testing.T) { if itemKind(t, item) != "compaction" { continue } - if compactionAt >= 0 { - t.Fatalf("second compaction item at walk position %d", i) - } + testassert.Require(t, compactionAt < 0, "second compaction item at walk position %d", i) value, err := item.AsCompactionItem() - if err != nil { - t.Fatalf("compaction item %d: %v", i, err) - } + testassert.Require(t, err == nil, "compaction item %d: %v", i, err) compactions = append(compactions, &value) compactionAt = i } - if compactionAt != firstPageLen-2 { - t.Fatalf("compaction item at walk position %d, want the first page's second-newest item", compactionAt) - } + testassert.Equal(t, compactionAt, firstPageLen-2) if len(compactions) != 1 || compactions[0].Summary != "done" || compactions[0].Model != "prov/m" { body, _ := json.Marshal(compactions) t.Fatalf("compaction items = %s, want exactly the real model summary", body) } end, err := walked[firstPageLen-1].AsOperationEndItem() - if err != nil || end.Status != protocol.OperationEndItemStatusSuccess || - end.OperationId == nil || *end.OperationId != "compact-1" { - t.Fatalf("compact settlement item = %+v err %v, want compact-1's settled success", end, err) - } + testassert.Require(t, err == nil && end.Status == protocol.OperationEndItemStatusSuccess && end.OperationId != nil && *end.OperationId == "compact-1", "compact settlement item = %+v err %v, want compact-1's settled success", end, err) // Removing the compaction and settlement items leaves exactly the // original items: every pre-compaction identity appears once with // byte-identical wire content. without := append(append([]protocol.ConversationItem{}, walked[:compactionAt]...), walked[compactionAt+2:]...) - if len(without) != len(preItems) { - t.Fatalf("post-compaction original items = %d, want %d", len(without), len(preItems)) - } + testassert.Equal(t, len(without), len(preItems)) preByItem := make(map[string]string, len(preItems)) for _, item := range preItems { data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal original item: %v", err) - } + testassert.NoError(t, err) preByItem[wireItemID(t, item)] = string(data) } for _, item := range without { id := wireItemID(t, item) data, err := json.Marshal(item) - if err != nil { - t.Fatalf("marshal post-compaction item: %v", err) - } + testassert.NoError(t, err) if preByItem[id] != string(data) { t.Fatalf("item %q = %s, want the original %s", id, data, preByItem[id]) } delete(preByItem, id) } - if len(preByItem) != 0 { - t.Fatalf("post-compaction history lost %d original items", len(preByItem)) - } + testassert.Equal(t, len(preByItem), 0) }) } @@ -901,17 +744,14 @@ func TestForkBoundaryResolution(t *testing.T) { headers := view.HistoryHeaders() entryID := inputEntryID(t, r, session, "op-1") boundary, err := resolveBoundaryEntry(session, headers, projectItemID(session, entryID)) - if err != nil { - t.Fatalf("resolveBoundaryEntry: %v", err) - } - if boundary != entryID { - t.Fatalf("resolved boundary = %q, want the private entry identity %q", boundary, entryID) - } + testassert.NoError(t, err) + testassert.Equal(t, boundary, entryID) // The user input of a different Session's namespace never resolves. foreign := projectItemID("ffffffffffffffffffffffffffffffff", entryID) - if _, err := resolveBoundaryEntry(session, headers, foreign); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("foreign boundary = %v, want harness.ErrInvalid", err) + { + _, err := resolveBoundaryEntry(session, headers, foreign) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "foreign boundary = %v, want harness.ErrInvalid", err) } // A committed non-user item never resolves: find the first turn's // assistant item identity. @@ -922,11 +762,13 @@ func TestForkBoundaryResolution(t *testing.T) { break } } - if _, err := resolveBoundaryEntry(session, headers, assistantItem); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("assistant boundary = %v, want harness.ErrInvalid", err) + { + _, err := resolveBoundaryEntry(session, headers, assistantItem) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "assistant boundary = %v, want harness.ErrInvalid", err) } - if _, err := resolveBoundaryEntry(session, headers, "no-such-item"); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("nonexistent boundary = %v, want harness.ErrInvalid", err) + { + _, err := resolveBoundaryEntry(session, headers, "no-such-item") + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "nonexistent boundary = %v, want harness.ErrInvalid", err) } }) } @@ -960,19 +802,13 @@ func TestHistoryPageAttachesResultsBeyondTheBaseRange(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the first call never started") } - if err := bg.r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { - return h.Interrupt(ctx, session) - }); err != nil { - t.Fatalf("Interrupt: %v", err) - } + testassert.NoError(t, bg.r.interruptSession(ctx, session)) awaitOperation(t, bg.r, session, "op-1", harness.OperationInterruption) awaitIdleSession(t, bg.r, session) // The interruption signal's item identity anchors the older page. newest, err := bg.r.getHistory(ctx, session, nil) - if err != nil { - t.Fatalf("getHistory: %v", err) - } + testassert.NoError(t, err) signalAnchor := "" for _, item := range newest.Items { if kind, _ := item.Discriminator(); kind == "signal" { @@ -984,25 +820,16 @@ func TestHistoryPageAttachesResultsBeyondTheBaseRange(t *testing.T) { } page, err := bg.r.getHistory(ctx, session, cursorBody(t, cursorJSON(session, signalAnchor))) - if err != nil { - t.Fatalf("anchored getHistory: %v", err) - } + testassert.NoError(t, err) if len(page.Items) != 2 { // the input and the interrupted assistant t.Fatalf("anchored page = %s, want the input and the assistant before the signal", itemKinds(t, page.Items)) } assistant, err := page.Items[1].AsAssistantItem() - if err != nil || len(assistant.ToolCalls) != 2 { - t.Fatalf("anchored assistant = %+v err %v, want both published calls", assistant, err) - } + testassert.Require(t, err == nil && len(assistant.ToolCalls) == 2, "anchored assistant = %+v err %v, want both published calls", assistant, err) started := assistant.ToolCalls[0] - if started.Id != "call-1" || started.Status == nil || *started.Status != protocol.ToolCallStatusSuccess || - started.Content == nil || *started.Content != "first ran" { - t.Fatalf("started call = %+v, want the real returned outcome attached beyond the page range", started) - } + testassert.Require(t, started.Id == "call-1" && started.Status != nil && *started.Status == protocol.ToolCallStatusSuccess && started.Content != nil && *started.Content == "first ran", "started call = %+v, want the real returned outcome attached beyond the page range", started) unstarted := assistant.ToolCalls[1] - if unstarted.Id != "call-2" || unstarted.Status == nil || *unstarted.Status != protocol.ToolCallStatusInterrupted { - t.Fatalf("unstarted call = %+v, want the synthetic interrupted outcome attached beyond the page range", unstarted) - } + testassert.Require(t, unstarted.Id == "call-2" && unstarted.Status != nil && *unstarted.Status == protocol.ToolCallStatusInterrupted, "unstarted call = %+v, want the synthetic interrupted outcome attached beyond the page range", unstarted) }) } @@ -1026,57 +853,25 @@ func TestHydrationPendingFIFOsAndActiveOperation(t *testing.T) { awaitModelArrival(t, e) // A regular submit while running rides the steering buffer; queued // submissions fill the queued FIFO in order. - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: session, OperationID: "op-s1", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "steer me"}}, Mode: harness.MessageModeRegular, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionSteering { - return fmt.Errorf("steering submit = %s, want steering", res.Disposition) - } - return nil - }); err != nil { - t.Fatalf("steering Submit: %v", err) - } + steering, err := submitRawThroughRuntime(context.Background(), r, session, "op-s1", "steer me", harness.MessageModeRegular) + testassert.Require(t, err == nil, "Submit(op-s1): %v", err) + testassert.Require(t, steering.Disposition == harness.DispositionSteering, "steering submit = %s, want steering", steering.Disposition) submitQueuedThroughRuntime(t, r, session, "op-q1", "queued one") submitQueuedThroughRuntime(t, r, session, "op-q2", "queued two") hydration, err := r.buildHydration(context.Background(), session) - if err != nil { - t.Fatalf("buildHydration: %v", err) - } + testassert.NoError(t, err) pending := hydration.Pending - if len(pending.Steering) != 1 || len(pending.Queued) != 2 { - t.Fatalf("pending = %+v, want one steering and two queued members", pending) - } + testassert.Require(t, len(pending.Steering) == 1 && len(pending.Queued) == 2, "pending = %+v, want one steering and two queued members", pending) steerText := pendingText(t, pending.Steering[0]) - if pending.Steering[0].OperationId != "op-s1" || steerText != "steer me" || - pending.Steering[0].Origin != protocol.InputOriginUser { - t.Fatalf("steering member = %+v (%q), want op-s1's user text", pending.Steering[0], steerText) - } - if pending.Queued[0].OperationId != "op-q1" || pendingText(t, pending.Queued[0]) != "queued one" || - pending.Queued[1].OperationId != "op-q2" || pendingText(t, pending.Queued[1]) != "queued two" { - t.Fatalf("queued members = %+v, want the FIFO order q1 then q2", pending.Queued) - } - if hydration.ActiveOperation == nil || hydration.ActiveOperation.OperationId != "op-1" || - hydration.ActiveOperation.Status != protocol.OperationStatusRunning { - t.Fatalf("active operation = %+v, want the durably running op-1", hydration.ActiveOperation) - } + testassert.Require(t, pending.Steering[0].OperationId == "op-s1" && steerText == "steer me" && pending.Steering[0].Origin == protocol.InputOriginUser && pending.Queued[0].OperationId == "op-q1" && pendingText(t, pending.Queued[0]) == "queued one" && pending.Queued[1].OperationId == "op-q2" && pendingText(t, pending.Queued[1]) == "queued two", "steering member = %+v (%q), want op-s1's user text; queued members = %+v, want the FIFO order q1 then q2", pending.Steering[0], steerText, pending.Queued) + testassert.Require(t, hydration.ActiveOperation != nil && hydration.ActiveOperation.OperationId == "op-1" && hydration.ActiveOperation.Status == protocol.OperationStatusRunning, "active operation = %+v, want the durably running op-1", hydration.ActiveOperation) header := projectSession(headerThroughRuntime(t, r, session)) - if !reflect.DeepEqual(hydration.Session, header) { - t.Fatalf("hydration session = %+v, want the header read %+v", hydration.Session, header) - } + testassert.Require(t, reflect.DeepEqual(hydration.Session, header), "hydration session = %+v, want the header read %+v", hydration.Session, header) data, err := json.Marshal(hydration) - if err != nil { - t.Fatalf("marshal hydration: %v", err) - } + testassert.NoError(t, err) for _, member := range []string{"prompt-solo", "system_prompt", "result_entry_id"} { - if strings.Contains(string(data), member) { - t.Fatalf("hydration wire = %s, want no internal %q representation", data, member) - } + testassert.Require(t, !strings.Contains(string(data), member), "hydration wire = %s, want no internal %q representation", data, member) } }) } @@ -1088,9 +883,7 @@ func pendingText(t *testing.T, member protocol.PendingInput) string { return "" } part, err := member.Content[0].AsTextPart() - if err != nil { - t.Fatalf("pending part: %v", err) - } + testassert.NoError(t, err) return part.Text } @@ -1140,29 +933,13 @@ func TestHydrationRootChildCoherence(t *testing.T) { // its active Operation, its conversation of one input, and its live // job member. childHydration, err := bg.r.buildHydration(context.Background(), child) - if err != nil { - t.Fatalf("buildHydration(child): %v", err) - } - if childHydration.Session.SessionId != child || childHydration.ActiveOperation == nil || - childHydration.ActiveOperation.OperationId != "child-op-1" || - childHydration.ActiveOperation.Status != protocol.OperationStatusRunning { - t.Fatalf("child hydration = %+v active %+v, want the ordinary running child Session", childHydration.Session, childHydration.ActiveOperation) - } - if len(childHydration.Conversation.Items) != 1 || len(childHydration.Operations) != 1 { - t.Fatalf("child hydration = %d items %d operations, want one item and one Operation", len(childHydration.Conversation.Items), len(childHydration.Operations)) - } - if len(childHydration.Background) != 1 || childHydration.Background[0].Id != "cc000003" { - t.Fatalf("child background = %+v, want the live job member", childHydration.Background) - } + testassert.NoError(t, err) + testassert.Require(t, childHydration.Session.SessionId == child && childHydration.ActiveOperation != nil && childHydration.ActiveOperation.OperationId == "child-op-1" && childHydration.ActiveOperation.Status == protocol.OperationStatusRunning && len(childHydration.Conversation.Items) == 1 && len(childHydration.Operations) == 1 && len(childHydration.Background) == 1 && childHydration.Background[0].Id == "cc000003", "child hydration = %+v active %+v, want the ordinary running child Session; child hydration = %d items %d operations, want one item and one Operation; child background = %+v, want the live job member", childHydration.Session, childHydration.ActiveOperation, len(childHydration.Conversation.Items), len(childHydration.Operations), childHydration.Background) // The parent carries the live child member. rootHydration, err := bg.r.buildHydration(context.Background(), root) - if err != nil { - t.Fatalf("buildHydration(root): %v", err) - } - if len(rootHydration.Background) != 1 || rootHydration.Background[0].Id != child { - t.Fatalf("root background = %+v, want the live child member", rootHydration.Background) - } + testassert.NoError(t, err) + testassert.Require(t, len(rootHydration.Background) == 1 && rootHydration.Background[0].Id == child, "root background = %+v, want the live child member", rootHydration.Background) // The child settles; its parent member finishes and the completion // input is delivered to the parent's conversation. @@ -1170,15 +947,11 @@ func TestHydrationRootChildCoherence(t *testing.T) { awaitOperation(t, bg.r, child, "child-op-1", harness.OperationSuccess) awaitIdleSession(t, bg.r, child) stopper.addDelivery(child, completionID, "job report") - if err := bg.stop(child); err != nil { - t.Fatalf("Stop(child): %v", err) - } + testassert.NoError(t, bg.r.stopSession(context.Background(), child)) deadline := time.Now().Add(10 * time.Second) for { rootHydration, err = bg.r.buildHydration(context.Background(), root) - if err != nil { - t.Fatalf("buildHydration(root) after completion: %v", err) - } + testassert.NoError(t, err) if hasRuntimeOriginInput(t, rootHydration.Conversation.Items, "child finished") { break } @@ -1187,17 +960,16 @@ func TestHydrationRootChildCoherence(t *testing.T) { } time.Sleep(5 * time.Millisecond) } - if len(rootHydration.Background) != 0 { - t.Fatalf("root background after completion = %+v, want the finished member gone", rootHydration.Background) - } + testassert.Require(t, len(rootHydration.Background) == 0, "root background after completion = %+v, want the finished member gone", rootHydration.Background) // The delivered runtime-origin input is committed non-user history: // its item identity never resolves as a fork boundary. facts, _ := historyThroughRuntime(t, bg.r, root) for _, fact := range facts { if fact.Kind == harness.EntryInput && fact.Input.Origin == harness.InputOriginRuntime { - if _, err := resolveBoundaryEntry(root, historyViewThroughRuntime(t, bg.r, root).HistoryHeaders(), projectItemID(root, fact.EntryID)); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("runtime-origin boundary = %v, want harness.ErrInvalid", err) + { + _, err := resolveBoundaryEntry(root, historyViewThroughRuntime(t, bg.r, root).HistoryHeaders(), projectItemID(root, fact.EntryID)) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "runtime-origin boundary = %v, want harness.ErrInvalid", err) } } } @@ -1206,21 +978,10 @@ func TestHydrationRootChildCoherence(t *testing.T) { // member equals its own narrow producer's read of the same state. childHeader := projectSession(headerThroughRuntime(t, bg.r, child)) childPage, err := bg.r.getHistory(context.Background(), child, nil) - if err != nil { - t.Fatalf("getHistory(child): %v", err) - } + testassert.NoError(t, err) childHydration, err = bg.r.buildHydration(context.Background(), child) - if err != nil { - t.Fatalf("buildHydration(child) settled: %v", err) - } - if !reflect.DeepEqual(childHydration.Session, childHeader) || - childHydration.SessionRevision != childPage.SessionRevision || - !reflect.DeepEqual(childHydration.Conversation.Items, childPage.Items) || - !reflect.DeepEqual(childHydration.Conversation.OlderCursor, childPage.OlderCursor) || - len(childHydration.Pending.Steering) != 0 || len(childHydration.Pending.Queued) != 0 || - childHydration.ActiveOperation != nil { - t.Fatalf("settled child hydration disagrees with the narrow producers: %+v", childHydration) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(childHydration.Session, childHeader) && childHydration.SessionRevision == childPage.SessionRevision && reflect.DeepEqual(childHydration.Conversation.Items, childPage.Items) && reflect.DeepEqual(childHydration.Conversation.OlderCursor, childPage.OlderCursor) && len(childHydration.Pending.Steering) == 0 && len(childHydration.Pending.Queued) == 0 && childHydration.ActiveOperation == nil, "settled child hydration disagrees with the narrow producers: %+v", childHydration) }) } @@ -1236,12 +997,10 @@ func TestUsageReadsDistinctWindowsAndClocks(t *testing.T) { // distinct from the fixtures' shared generous default. The row admits // no work, so the placeholder endpoint is never contacted. e := newOwnerEnv(t) - writeServiceFile(t, e.configPath, `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":4096}}}}}`) + writeServiceFile(t, e.configPath, `{"providers":{"prov":`+nominalProvMember("https://prov.test/v1", "", `{"m":{"name":"M","context_window":4096}}`)+`}}`) writeServiceFile(t, agents.PathForConfig(e.configPath), usageWindowAgents) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) ctx := context.Background() @@ -1275,7 +1034,7 @@ func TestUsageReadsDistinctWindowsAndClocks(t *testing.T) { busyInput := newLifecycleID(t) usageSeedSession(t, store, busy, "b-op-1", "", "solo", emptyUsageWireTotals()) lifecycleInsertEntry(t, store, busy, busyInput, "b-op-1", harness.EntryInput, - compactRawInputEntry(busy, busyInput, "b-op-1", "running")) + compactRawInputEntry(busy, busyInput, "b-op-1", "user", "running")) lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: busy, Kind: harness.RegisterOperation, OperationID: "b-op-1"}, compactRawOperationRegister(compactRawMessageAdmission(busy, "b-op-1", busyInput), compactRawRunningModelState(time.Now().UTC().Format(time.RFC3339Nano), newLifecycleID(t)))) @@ -1286,81 +1045,57 @@ func TestUsageReadsDistinctWindowsAndClocks(t *testing.T) { usageRow := func(t *testing.T, sessionID string) protocol.Hydration { t.Helper() hydration, err := r.buildHydration(ctx, sessionID) - if err != nil { - t.Fatalf("buildHydration(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "buildHydration(%s): %v", sessionID, err) return hydration } // Generation 1: the fixture catalog's 4096 window for idle reads, // the captured window for the busy read. got := usageRow(t, measured) - if !reflect.DeepEqual(got.Usage.Totals, wantUsageRow("450", "35", "15")) { - t.Fatalf("measured totals = %+v, want the register totals 450/35/15", got.Usage.Totals) - } - if got.Usage.Context.UsedTokens != "55" || got.Usage.Context.ContextWindow != 4096 { - t.Fatalf("measured context = %+v, want the estimate 55 under the 4096 window", got.Usage.Context) - } - if got.ConfigurationRevision.Generation != "1" { - t.Fatalf("configuration revision = %+v, want generation 1", got.ConfigurationRevision) - } + testassert.Require(t, reflect.DeepEqual(got.Usage.Totals, wantUsageRow("450", "35", "15")) && got.Usage.Context.UsedTokens == "55" && got.Usage.Context.ContextWindow == 4096 && got.ConfigurationRevision.Generation == "1", "measured totals = %+v, want the register totals 450/35/15; measured context = %+v, want the estimate 55 under the 4096 window; configuration revision = %+v, want generation 1", got.Usage.Totals, got.Usage.Context, got.ConfigurationRevision) resetRow := usageRow(t, reset) - if resetRow.Usage.Context.UsedTokens != "0" || resetRow.Usage.Context.ContextWindow != 4096 || - !reflect.DeepEqual(resetRow.Usage.Totals, wantUsageRow("70", "7", "2")) { - t.Fatalf("reset usage = %+v, want estimate 0 with the independent 70/7/2 totals", resetRow.Usage) - } + testassert.Require(t, resetRow.Usage.Context.UsedTokens == "0" && resetRow.Usage.Context.ContextWindow == 4096 && reflect.DeepEqual(resetRow.Usage.Totals, wantUsageRow("70", "7", "2")), "reset usage = %+v, want estimate 0 with the independent 70/7/2 totals", resetRow.Usage) busyRow := usageRow(t, busy) - if busyRow.Usage.Context.ContextWindow != 4096 { - t.Fatalf("busy window = %d, want the captured 4096", busyRow.Usage.Context.ContextWindow) - } - if modellessRow := usageRow(t, modelless); modellessRow.Usage.Context.ContextWindow != 0 { - t.Fatalf("modelless window = %d, want 0", modellessRow.Usage.Context.ContextWindow) + testassert.Equal(t, busyRow.Usage.Context.ContextWindow, 4096) + { + modellessRow := usageRow(t, modelless) + testassert.Equal(t, modellessRow.Usage.Context.ContextWindow, 0) } // Reload to a distinct positive catalog window plus the modelless // Agent type: the idle reads report the new selection while the // busy read keeps its capture, and every read names the captured // generation. - writeServiceFile(t, e.configPath, `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":8192}}}}}`) + writeServiceFile(t, e.configPath, `{"providers":{"prov":`+nominalProvMember("https://prov.test/v1", "", `{"m":{"name":"M","context_window":8192}}`)+`}}`) writeServiceFile(t, agents.PathForConfig(e.configPath), usageWindowAgents) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } got = usageRow(t, measured) - if got.Usage.Context.ContextWindow != 8192 || got.Usage.Context.UsedTokens != "55" || got.ConfigurationRevision.Generation != "2" { - t.Fatalf("reloaded idle usage = %+v rev %+v, want estimate 55 under 8192 at generation 2", got.Usage, got.ConfigurationRevision) - } + testassert.Require(t, got.Usage.Context.ContextWindow == 8192 && got.Usage.Context.UsedTokens == "55" && got.ConfigurationRevision.Generation == "2", "reloaded idle usage = %+v rev %+v, want estimate 55 under 8192 at generation 2", got.Usage, got.ConfigurationRevision) busyRow = usageRow(t, busy) - if busyRow.Usage.Context.ContextWindow != 4096 || busyRow.ConfigurationRevision.Generation != "2" { - t.Fatalf("reloaded busy usage = %+v rev %+v, want the active captured 4096 at generation 2", busyRow.Usage, busyRow.ConfigurationRevision) - } + testassert.Require(t, busyRow.Usage.Context.ContextWindow == 4096 && busyRow.ConfigurationRevision.Generation == "2", "reloaded busy usage = %+v rev %+v, want the active captured 4096 at generation 2", busyRow.Usage, busyRow.ConfigurationRevision) // The same running Session's hydration: the separately captured // configuration revision and the retained captured window ride the // one snapshot producer. busyHydration, err := r.buildHydration(ctx, busy) - if err != nil { - t.Fatalf("buildHydration(busy): %v", err) - } - if busyHydration.ConfigurationRevision.Generation != "2" || busyHydration.Usage.Context.ContextWindow != 4096 || - busyHydration.ActiveOperation == nil || busyHydration.ActiveOperation.OperationId != "b-op-1" { - t.Fatalf("reloaded busy hydration = rev %+v context %+v active %+v, want generation 2, the captured 4096 window, and the running b-op-1", - busyHydration.ConfigurationRevision, busyHydration.Usage.Context, busyHydration.ActiveOperation) - } - if row := usageRow(t, modelless); row.Usage.Context.ContextWindow != 0 || row.ConfigurationRevision.Generation != "2" { - t.Fatalf("modelless usage = %+v, want window 0 at generation 2", row.Usage) + testassert.NoError(t, err) + testassert.Require(t, busyHydration.ConfigurationRevision.Generation == "2" && busyHydration.Usage.Context.ContextWindow == 4096 && busyHydration.ActiveOperation != nil && busyHydration.ActiveOperation.OperationId == "b-op-1", "reloaded busy hydration = rev %+v context %+v active %+v, want generation 2, the captured 4096 window, and the running b-op-1", busyHydration.ConfigurationRevision, busyHydration.Usage.Context, busyHydration.ActiveOperation) + { + row := usageRow(t, modelless) + testassert.Require(t, row.Usage.Context.ContextWindow == 0 && row.ConfigurationRevision.Generation == "2", "modelless usage = %+v, want window 0 at generation 2", row.Usage) } // Deleting the model from the catalog: the idle window is 0 while // the estimate and totals stay independent. writeServiceFile(t, e.configPath, `{"providers":{}}`) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload after deletion: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } got = usageRow(t, measured) - if got.Usage.Context.ContextWindow != 0 || got.Usage.Context.UsedTokens != "55" || - !reflect.DeepEqual(got.Usage.Totals, wantUsageRow("450", "35", "15")) || got.ConfigurationRevision.Generation != "3" { - t.Fatalf("deleted-model usage = %+v rev %+v, want window 0 with the retained estimate and totals at generation 3", got.Usage, got.ConfigurationRevision) - } + testassert.Require(t, got.Usage.Context.ContextWindow == 0 && got.Usage.Context.UsedTokens == "55" && reflect.DeepEqual(got.Usage.Totals, wantUsageRow("450", "35", "15")) && got.ConfigurationRevision.Generation == "3", "deleted-model usage = %+v rev %+v, want window 0 with the retained estimate and totals at generation 3", got.Usage, got.ConfigurationRevision) }) } @@ -1373,51 +1108,34 @@ func TestUsageReadsDistinctWindowsAndClocks(t *testing.T) { func TestHydrationSelectedModelFollowsNextAdmission(t *testing.T) { eachPrepStore(t, func(t *testing.T, store harness.Storage) { e := newOwnerEnv(t) - writeServiceFile(t, e.configPath, `{"providers":{"prov":{"transport":{"base_url":"`+e.server.URL+`","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":4096,"max_output_tokens":1024},"m2":{"name":"M2","context_window":8192,"max_output_tokens":1024}}}}}`) + writeServiceFile(t, e.configPath, `{"providers":{"prov":`+nominalProvMember(e.server.URL, "", `{"m":{"name":"M","context_window":4096,"max_output_tokens":1024},"m2":{"name":"M2","context_window":8192,"max_output_tokens":1024}}`)+`}}`) writeServiceFile(t, agents.PathForConfig(e.configPath), `{"solo":{"model":"prov/m","system_prompt":"simple"},"alternate":{"model":"prov/m2","system_prompt":"simple"},"modelless":{"system_prompt":"simple"},"gone":{"model":"prov/gone","system_prompt":"simple"}}`) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) // An idle Session resolves the selected definition in the captured // catalog: one shared selection supplies the model and the window. solo := projectionSession(t, r, "/tmp/selection-solo", "solo").Identity.SessionID idle, err := r.buildHydration(context.Background(), solo) - if err != nil { - t.Fatalf("buildHydration(solo): %v", err) - } - if idle.SelectedModel == nil || *idle.SelectedModel != "prov/m" || idle.Usage.Context.ContextWindow != 4096 { - t.Fatalf("idle selection = model %v window %d, want prov/m under 4096", idle.SelectedModel, idle.Usage.Context.ContextWindow) - } - if idle.Session.AgentType != "solo" { - t.Fatalf("idle session agent type = %q, want solo", idle.Session.AgentType) - } + testassert.NoError(t, err) + testassert.Require(t, idle.SelectedModel != nil && *idle.SelectedModel == "prov/m" && idle.Usage.Context.ContextWindow == 4096 && idle.Session.AgentType == "solo", "idle selection = model %v window %d, want prov/m under 4096; got %v, want %v", idle.SelectedModel, idle.Usage.Context.ContextWindow, idle.Session.AgentType, "solo") // No configured ref and an unresolvable type both yield null rather // than a fallback model. for _, tc := range []struct{ name, agentType string }{{"modelless", "modelless"}, {"unknown", "ghost"}} { session := projectionSession(t, r, "/tmp/selection-"+tc.name, tc.agentType).Identity.SessionID hydration, err := r.buildHydration(context.Background(), session) - if err != nil { - t.Fatalf("buildHydration(%s): %v", tc.name, err) - } - if hydration.SelectedModel != nil || hydration.Usage.Context.ContextWindow != 0 { - t.Fatalf("%s selection = model %v window %d, want null and 0", tc.name, hydration.SelectedModel, hydration.Usage.Context.ContextWindow) - } + testassert.Require(t, err == nil, "buildHydration(%s): %v", tc.name, err) + testassert.Require(t, hydration.SelectedModel == nil && hydration.Usage.Context.ContextWindow == 0, "%s selection = model %v window %d, want null and 0", tc.name, hydration.SelectedModel, hydration.Usage.Context.ContextWindow) } // A configured but unavailable ref stays identifiable while admission // still refuses it: the window is 0, the model is not invented away. gone := projectionSession(t, r, "/tmp/selection-gone", "gone").Identity.SessionID goneHydration, err := r.buildHydration(context.Background(), gone) - if err != nil { - t.Fatalf("buildHydration(gone): %v", err) - } - if goneHydration.SelectedModel == nil || *goneHydration.SelectedModel != "prov/gone" || goneHydration.Usage.Context.ContextWindow != 0 { - t.Fatalf("gone selection = model %v window %d, want the configured prov/gone identifiable at 0", goneHydration.SelectedModel, goneHydration.Usage.Context.ContextWindow) - } + testassert.NoError(t, err) + testassert.Require(t, goneHydration.SelectedModel != nil && *goneHydration.SelectedModel == "prov/gone" && goneHydration.Usage.Context.ContextWindow == 0, "gone selection = model %v window %d, want the configured prov/gone identifiable at 0", goneHydration.SelectedModel, goneHydration.Usage.Context.ContextWindow) // A running Operation keeps its capture: an Agent-type change while // it runs names the next admission's selection, while the active @@ -1430,41 +1148,20 @@ func TestHydrationSelectedModelFollowsNextAdmission(t *testing.T) { active := projectionSession(t, r, "/tmp/selection-active", "solo").Identity.SessionID submitThroughRuntime(t, r, active, "op-1", "running") awaitModelArrival(t, e) - if err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { + testassert.NoError(t, r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { _, err := h.ChangeAgentType(ctx, active, "alternate") return err - }); err != nil { - t.Fatalf("ChangeAgentType: %v", err) - } + })) activeHydration, err := r.buildHydration(context.Background(), active) - if err != nil { - t.Fatalf("buildHydration(active): %v", err) - } - if activeHydration.ActiveOperation == nil || activeHydration.ActiveOperation.Model != "prov/m" || - activeHydration.Usage.Context.ContextWindow != 4096 { - t.Fatalf("active hydration = active %+v window %d, want the captured prov/m under 4096", - activeHydration.ActiveOperation, activeHydration.Usage.Context.ContextWindow) - } - if activeHydration.SelectedModel == nil || *activeHydration.SelectedModel != "prov/m2" { - t.Fatalf("active next selection = %v, want the changed prov/m2", activeHydration.SelectedModel) - } - if activeHydration.Session.AgentType != "alternate" { - t.Fatalf("active session agent type = %q, want alternate", activeHydration.Session.AgentType) - } - if activeHydration.SessionRevision != activeHydration.Session.SessionRevision { - t.Fatalf("hydration revision = %+v, want the same captured session revision %+v", activeHydration.SessionRevision, activeHydration.Session.SessionRevision) - } + testassert.NoError(t, err) + testassert.Require(t, activeHydration.ActiveOperation != nil && activeHydration.ActiveOperation.Model == "prov/m" && activeHydration.Usage.Context.ContextWindow == 4096 && activeHydration.SelectedModel != nil && *activeHydration.SelectedModel == "prov/m2" && activeHydration.Session.AgentType == "alternate" && activeHydration.SessionRevision == activeHydration.Session.SessionRevision, "active hydration = active %+v window %d, want the captured prov/m under 4096; active next selection = %v, want the changed prov/m2; got %v, want %v; got %v, want %v", activeHydration.ActiveOperation, activeHydration.Usage.Context.ContextWindow, activeHydration.SelectedModel, activeHydration.Session.AgentType, "alternate", activeHydration.SessionRevision, activeHydration.Session.SessionRevision) // Once idle, the shared resolution supplies the new model's window. release() awaitIdleSession(t, r, active) settled, err := r.buildHydration(context.Background(), active) - if err != nil { - t.Fatalf("buildHydration(settled): %v", err) - } - if settled.SelectedModel == nil || *settled.SelectedModel != "prov/m2" || settled.Usage.Context.ContextWindow != 8192 { - t.Fatalf("settled selection = model %v window %d, want prov/m2 under 8192", settled.SelectedModel, settled.Usage.Context.ContextWindow) - } + testassert.NoError(t, err) + testassert.Require(t, settled.SelectedModel != nil && *settled.SelectedModel == "prov/m2" && settled.Usage.Context.ContextWindow == 8192, "settled selection = model %v window %d, want prov/m2 under 8192", settled.SelectedModel, settled.Usage.Context.ContextWindow) // A reload re-points the selected Agent type to another model while a // second Operation is blocked: selected_model follows the reloaded @@ -1478,34 +1175,19 @@ func TestHydrationSelectedModelFollowsNextAdmission(t *testing.T) { submitThroughRuntime(t, r, active, "op-2", "running again") awaitModelArrival(t, e) writeServiceFile(t, agents.PathForConfig(e.configPath), `{"solo":{"model":"prov/m","system_prompt":"simple"},"alternate":{"model":"prov/m","system_prompt":"simple"},"modelless":{"system_prompt":"simple"},"gone":{"model":"prov/gone","system_prompt":"simple"}}`) - if _, err := r.Reload(context.Background()); err != nil { - t.Fatalf("Reload: %v", err) + { + _, err := r.Reload(context.Background()) + testassert.NoError(t, err) } reloaded, err := r.buildHydration(context.Background(), active) - if err != nil { - t.Fatalf("buildHydration(reloaded): %v", err) - } - if reloaded.ActiveOperation == nil || reloaded.ActiveOperation.Model != "prov/m2" || - reloaded.Usage.Context.ContextWindow != 8192 { - t.Fatalf("reloaded active capture = active %+v window %d, want the captured prov/m2 under its captured 8192", - reloaded.ActiveOperation, reloaded.Usage.Context.ContextWindow) - } - if reloaded.SelectedModel == nil || *reloaded.SelectedModel != "prov/m" { - t.Fatalf("reloaded selected model = %v, want the re-pointed prov/m", reloaded.SelectedModel) - } - if reloaded.ConfigurationRevision.Generation != "2" { - t.Fatalf("reloaded configuration revision = %+v, want generation 2", reloaded.ConfigurationRevision) - } + testassert.NoError(t, err) + testassert.Require(t, reloaded.ActiveOperation != nil && reloaded.ActiveOperation.Model == "prov/m2" && reloaded.Usage.Context.ContextWindow == 8192 && reloaded.SelectedModel != nil && *reloaded.SelectedModel == "prov/m" && reloaded.ConfigurationRevision.Generation == "2", "reloaded active capture = active %+v window %d, want the captured prov/m2 under its captured 8192; reloaded selected model = %v, want the re-pointed prov/m; reloaded configuration revision = %+v, want generation 2", reloaded.ActiveOperation, reloaded.Usage.Context.ContextWindow, reloaded.SelectedModel, reloaded.ConfigurationRevision) releaseSecond() awaitIdleSession(t, r, active) afterReload, err := r.buildHydration(context.Background(), active) - if err != nil { - t.Fatalf("buildHydration(after reload): %v", err) - } - if afterReload.SelectedModel == nil || *afterReload.SelectedModel != "prov/m" || afterReload.Usage.Context.ContextWindow != 4096 { - t.Fatalf("after-reload selection = model %v window %d, want prov/m under 4096", afterReload.SelectedModel, afterReload.Usage.Context.ContextWindow) - } + testassert.NoError(t, err) + testassert.Require(t, afterReload.SelectedModel != nil && *afterReload.SelectedModel == "prov/m" && afterReload.Usage.Context.ContextWindow == 4096, "after-reload selection = model %v window %d, want prov/m under 4096", afterReload.SelectedModel, afterReload.Usage.Context.ContextWindow) }) } @@ -1516,13 +1198,12 @@ func TestProjectionReadsClosedAndCanceledCallers(t *testing.T) { r, _ := openProjectionRuntime(t, storage.NewMemory()) defer closeProjectionRuntime(r) const sessionID = "0123456789abcdef0123456789abcdef" - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) closed := sessionReadCalls(context.Background(), r, sessionID) for i, call := range closed { - if err := call(); !errors.Is(err, ErrClosed) { - t.Fatalf("closed caller %d = %v, want ErrClosed", i, err) + { + err := call() + testassert.Require(t, errors.Is(err, ErrClosed), "closed caller %d = %v, want ErrClosed", i, err) } } @@ -1532,8 +1213,9 @@ func TestProjectionReadsClosedAndCanceledCallers(t *testing.T) { cancel() canceledCalls := sessionReadCalls(canceled, r2, sessionID) for i, call := range canceledCalls { - if err := call(); !errors.Is(err, context.Canceled) { - t.Fatalf("canceled caller %d = %v, want context.Canceled", i, err) + { + err := call() + testassert.Require(t, errors.Is(err, context.Canceled), "canceled caller %d = %v, want context.Canceled", i, err) } } } @@ -1553,50 +1235,30 @@ func TestHydrationAndPageReturnedValueOwnership(t *testing.T) { awaitSessionNotBusy(t, r, session) page, err := r.getHistory(context.Background(), session, nil) - if err != nil { - t.Fatalf("getHistory: %v", err) - } + testassert.NoError(t, err) wantPage, err := json.Marshal(page) - if err != nil { - t.Fatalf("marshal page: %v", err) - } + testassert.NoError(t, err) page.Items = append(page.Items, protocol.ConversationItem{}) page.Items[0] = protocol.ConversationItem{} reread, err := r.getHistory(context.Background(), session, nil) - if err != nil { - t.Fatalf("getHistory re-read: %v", err) - } + testassert.NoError(t, err) gotPage, err := json.Marshal(reread) - if err != nil { - t.Fatalf("marshal re-read page: %v", err) - } - if string(gotPage) != string(wantPage) { - t.Fatalf("re-read page = %s, want the immutable %s", gotPage, wantPage) - } + testassert.NoError(t, err) + testassert.Require(t, string(gotPage) == string(wantPage), "re-read page = %s, want the immutable %s", gotPage, wantPage) hydration, err := r.buildHydration(context.Background(), session) - if err != nil { - t.Fatalf("buildHydration: %v", err) - } + testassert.NoError(t, err) wantHydration, err := json.Marshal(hydration) - if err != nil { - t.Fatalf("marshal hydration: %v", err) - } + testassert.NoError(t, err) hydration.Session.AgentType = "mutated" hydration.Conversation.Items = nil hydration.Pending.Steering = append(hydration.Pending.Steering, protocol.PendingInput{OperationId: "mutated"}) hydration.Operations[0].OperationId = "mutated" hydration.Background = append(hydration.Background, protocol.BackgroundMember{Id: "mutated"}) rereadHydration, err := r.buildHydration(context.Background(), session) - if err != nil { - t.Fatalf("buildHydration re-read: %v", err) - } + testassert.NoError(t, err) gotHydration, err := json.Marshal(rereadHydration) - if err != nil { - t.Fatalf("marshal re-read hydration: %v", err) - } - if string(gotHydration) != string(wantHydration) { - t.Fatalf("re-read hydration = %s, want the immutable %s", gotHydration, wantHydration) - } + testassert.NoError(t, err) + testassert.Require(t, string(gotHydration) == string(wantHydration), "re-read hydration = %s, want the immutable %s", gotHydration, wantHydration) }) } diff --git a/runtime/protocol_projection_test.go b/runtime/protocol_projection_test.go index b6c24ac7..60accf5a 100644 --- a/runtime/protocol_projection_test.go +++ b/runtime/protocol_projection_test.go @@ -18,6 +18,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -34,9 +35,7 @@ func openProjectionRuntime(t *testing.T, store harness.Storage) (*Runtime, *owne e := newOwnerEnv(t) writeServiceFile(t, agents.PathForConfig(e.configPath), projectionAgentsDocument) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) return r, e } @@ -50,9 +49,7 @@ func closeProjectionRuntime(r *Runtime) { func projectionSession(t *testing.T, r *Runtime, workspace, agentType string) harness.SessionRecord { t.Helper() rec, err := r.createSession(context.Background(), workspace, agentType) - if err != nil { - t.Fatalf("createSession(%s): %v", workspace, err) - } + testassert.Require(t, err == nil, "createSession(%s): %v", workspace, err) return rec } @@ -64,9 +61,7 @@ func sessionViewThroughRuntime(t *testing.T, r *Runtime, sessionID string) harne view, err = h.ReadSessionView(ctx, sessionID) return err }) - if err != nil { - t.Fatalf("ReadSessionView(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadSessionView(%s): %v", sessionID, err) return view } @@ -81,9 +76,7 @@ func headerThroughRuntime(t *testing.T, r *Runtime, sessionID string) harness.Se header, err = h.ReadSessionHeader(ctx, sessionID) return err }) - if err != nil { - t.Fatalf("ReadSessionHeader(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadSessionHeader(%s): %v", sessionID, err) return header } @@ -105,9 +98,7 @@ func historyThroughRuntime(t *testing.T, r *Runtime, sessionID string) ([]harnes revision = view.Revision return nil }) - if err != nil { - t.Fatalf("ReadHistoryView(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadHistoryView(%s): %v", sessionID, err) return facts, revision } @@ -145,25 +136,9 @@ func historyFactsOf(view harness.HistoryReadView) []harness.HistoryFact { // submitQueuedThroughRuntime submits one buffered queued message. func submitQueuedThroughRuntime(t *testing.T, r *Runtime, sessionID, operationID, text string) { t.Helper() - err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, - OperationID: operationID, - Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: harness.MessageModeQueued, - }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionQueued { - return errors.New("submit " + operationID + " = " + string(res.Disposition) + ", want queued") - } - return nil - }) - if err != nil { - t.Fatalf("queued Submit(%q): %v", operationID, err) - } + res, err := submitRawThroughRuntime(context.Background(), r, sessionID, operationID, text, harness.MessageModeQueued) + testassert.Require(t, err == nil, "queued Submit(%q): %v", operationID, err) + testassert.Require(t, res.Disposition == harness.DispositionQueued, "queued submit %q = %+v, want queued", operationID, res.Disposition) } // forkThroughRuntime forks one source Session at the input entry of the named @@ -184,9 +159,7 @@ func forkThroughRuntime(t *testing.T, r *Runtime, sourceID, boundaryEntryID, ope dest = res.Session.Identity.SessionID return nil }) - if err != nil { - t.Fatalf("Fork: %v", err) - } + testassert.NoError(t, err) return dest } @@ -209,9 +182,7 @@ func launchChildThroughRuntime(t *testing.T, r *Runtime, parent, operationID str childID = res.ChildSessionID return nil }) - if err != nil { - t.Fatalf("LaunchChildSession: %v", err) - } + testassert.NoError(t, err) return childID } @@ -251,9 +222,7 @@ func historyViewThroughRuntime(t *testing.T, r *Runtime, sessionID string) harne view, err = h.ReadHistoryView(ctx, sessionID) return err }) - if err != nil { - t.Fatalf("ReadHistoryView(%s): %v", sessionID, err) - } + testassert.Require(t, err == nil, "ReadHistoryView(%s): %v", sessionID, err) return view } @@ -276,20 +245,8 @@ func assertStableHeader(t *testing.T, header protocol.Session, sessionHeader har t.Helper() currentOK := (wantCurrent == "") == (header.CurrentOperationId == nil) && (wantCurrent == "" || *header.CurrentOperationId == wantCurrent) - if header.SessionId != sessionID || - header.Workspace != workspace || - header.Lifecycle != protocol.Open || - header.AgentType != agent || - header.ArchivedAt != nil || - !currentOK { - t.Fatalf("header = %+v, want the open %s header at %s with current operation %q", header, sessionID, workspace, wantCurrent) - } - if !header.CreatedAt.Equal(sessionHeader.Identity.CreatedAt) || !header.LastActivity.Equal(sessionHeader.LastActivity) { - t.Fatalf("header times = (%v, %v), want the read times (%v, %v)", header.CreatedAt, header.LastActivity, sessionHeader.Identity.CreatedAt, sessionHeader.LastActivity) - } - if loc := header.CreatedAt.Location(); loc != time.UTC { - t.Fatalf("created_at zone = %v, want UTC", loc) - } + testassert.Require(t, header.SessionId == sessionID && header.Workspace == workspace && header.Lifecycle == protocol.Open && header.AgentType == agent && header.ArchivedAt == nil && currentOK && header.CreatedAt.Equal(sessionHeader.Identity.CreatedAt) && header.LastActivity.Equal(sessionHeader.LastActivity), "header = %+v, want the open %s header at %s with current operation %q; header times = (%v, %v), want the read times (%v, %v)", header, sessionID, workspace, wantCurrent, header.CreatedAt, header.LastActivity, sessionHeader.Identity.CreatedAt, sessionHeader.LastActivity) + testassert.Equal(t, header.CreatedAt.Location(), time.UTC) if header.SessionRevision != wantSessionRevision(t, sessionHeader) { t.Fatalf("session_revision = %+v, want the read pair %+v", header.SessionRevision, wantSessionRevision(t, sessionHeader)) } @@ -311,9 +268,7 @@ func TestProjectionSessionHeaderRootForkChild(t *testing.T) { rootHeader := projectSession(headerThroughRuntime(t, r, root.Identity.SessionID)) assertStableHeader(t, rootHeader, headerThroughRuntime(t, r, root.Identity.SessionID), root.Identity.SessionID, workspace, "solo", "") - if rootHeader.ParentSessionId != nil || rootHeader.SourceSessionId != nil { - t.Fatalf("root lineage pointers = (%v, %v), want none", rootHeader.ParentSessionId, rootHeader.SourceSessionId) - } + testassert.Require(t, rootHeader.ParentSessionId == nil && rootHeader.SourceSessionId == nil, "root lineage pointers = (%v, %v), want none", rootHeader.ParentSessionId, rootHeader.SourceSessionId) // One settled admission gives the fork its committed input boundary; // its terminal cleanup proves the settled idle source Fork requires. @@ -333,24 +288,14 @@ func TestProjectionSessionHeaderRootForkChild(t *testing.T) { forkHeader := projectSession(headerThroughRuntime(t, r, forked)) assertStableHeader(t, forkHeader, headerThroughRuntime(t, r, forked), forked, workspace, "solo", "fork-op-1") - if forkHeader.SourceSessionId == nil || *forkHeader.SourceSessionId != root.Identity.SessionID { - t.Fatalf("fork source = %v, want %s", forkHeader.SourceSessionId, root.Identity.SessionID) - } - if forkHeader.ParentSessionId != nil { - t.Fatalf("fork parent pointer = %v, want none", forkHeader.ParentSessionId) - } + testassert.Require(t, forkHeader.SourceSessionId != nil && *forkHeader.SourceSessionId == root.Identity.SessionID && forkHeader.ParentSessionId == nil, "fork source = %v, want %s; fork parent pointer = %v, want none", forkHeader.SourceSessionId, root.Identity.SessionID, forkHeader.ParentSessionId) child := launchChildThroughRuntime(t, r, root.Identity.SessionID, "child-op-1") awaitModelArrival(t, e) childHeader := projectSession(headerThroughRuntime(t, r, child)) assertStableHeader(t, childHeader, headerThroughRuntime(t, r, child), child, workspace, "worker", "child-op-1") - if childHeader.ParentSessionId == nil || *childHeader.ParentSessionId != root.Identity.SessionID { - t.Fatalf("child parent = %v, want %s", childHeader.ParentSessionId, root.Identity.SessionID) - } - if childHeader.SourceSessionId != nil { - t.Fatalf("child source pointer = %v, want none", childHeader.SourceSessionId) - } + testassert.Require(t, childHeader.ParentSessionId != nil && *childHeader.ParentSessionId == root.Identity.SessionID && childHeader.SourceSessionId == nil, "child parent = %v, want %s; child source pointer = %v, want none", childHeader.ParentSessionId, root.Identity.SessionID, childHeader.SourceSessionId) release() awaitOperation(t, r, forked, "fork-op-1", harness.OperationSuccess) @@ -373,21 +318,13 @@ func TestProjectionOperationActiveAndTerminal(t *testing.T) { awaitModelArrival(t, e) active := projectOperation(readOperation(t, r, session, "op-1")) - if active.OperationId != "op-1" || active.RequestKind != protocol.Message || - active.Status != protocol.OperationStatusRunning || active.SettledAt != nil || active.Detail != nil || - active.AgentType != "solo" || active.Model != "prov/m" || active.AdmittedAt.IsZero() { - t.Fatalf("active projection = %+v, want the running message op-1 with no terminal members", active) - } + testassert.Require(t, active.OperationId == "op-1" && active.RequestKind == protocol.Message && active.Status == protocol.OperationStatusRunning && active.SettledAt == nil && active.Detail == nil && active.AgentType == "solo" && active.Model == "prov/m" && !active.AdmittedAt.IsZero(), "active projection = %+v, want the running message op-1 with no terminal members", active) close(gate) record := awaitOperation(t, r, session, "op-1", harness.OperationSuccess) settled := projectOperation(record) - if settled.Status != protocol.OperationStatusSuccess || settled.SettledAt == nil || settled.Detail != nil { - t.Fatalf("settled projection = %+v, want success with a settlement time and no detail", settled) - } - if !settled.AdmittedAt.Equal(record.Admission.AdmittedAt) { - t.Fatal("settled admitted_at drifted from the register admission") - } + testassert.Require(t, settled.Status == protocol.OperationStatusSuccess && settled.SettledAt != nil && settled.Detail == nil, "settled projection = %+v, want success with a settlement time and no detail", settled) + testassert.Require(t, settled.AdmittedAt.Equal(record.Admission.AdmittedAt), "settled admitted_at drifted from the register admission") // The interrupted terminal requires its diagnostic detail, on a fresh // Session: the terminal cleanup of the settled row does not imply the @@ -398,17 +335,9 @@ func TestProjectionOperationActiveAndTerminal(t *testing.T) { e.server.setHold(gate) submitThroughRuntime(t, r, interruptedSession, "op-2", "again") awaitModelArrival(t, e) - err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - return h.Interrupt(ctx, interruptedSession) - }) - if err != nil { - t.Fatalf("Interrupt: %v", err) - } + testassert.NoError(t, r.interruptSession(context.Background(), interruptedSession)) interrupted := projectOperation(awaitOperation(t, r, interruptedSession, "op-2", harness.OperationInterruption)) - if interrupted.Status != protocol.OperationStatusInterruption || interrupted.SettledAt == nil || - interrupted.Detail == nil || *interrupted.Detail == "" { - t.Fatalf("interrupted projection = %+v, want interruption with a required detail", interrupted) - } + testassert.Require(t, interrupted.Status == protocol.OperationStatusInterruption && interrupted.SettledAt != nil && interrupted.Detail != nil && *interrupted.Detail != "", "interrupted projection = %+v, want interruption with a required detail", interrupted) }) } @@ -426,9 +355,7 @@ func TestProjectionListFilters(t *testing.T) { _, err := h.ArchiveSession(ctx, archived) return err }) - if err != nil { - t.Fatalf("ArchiveSession: %v", err) - } + testassert.NoError(t, err) invalid := map[string]protocol.ListSessionsParams{ "absent workspace": {Lifecycle: "open"}, @@ -436,49 +363,30 @@ func TestProjectionListFilters(t *testing.T) { "unknown lifecycle": {Workspace: ws1, Lifecycle: "paused"}, } for name, params := range invalid { - if _, err := r.listSessions(context.Background(), params); !errors.Is(err, harness.ErrInvalid) { - t.Fatalf("%s: listSessions = %v, want harness.ErrInvalid", name, err) + { + _, err := r.listSessions(context.Background(), params) + testassert.Require(t, errors.Is(err, harness.ErrInvalid), "%s: listSessions = %v, want harness.ErrInvalid", name, err) } } openList, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: ws1, Lifecycle: "open"}) - if err != nil { - t.Fatalf("listSessions open: %v", err) - } - if len(openList) != 1 || openList[0].SessionId != open1 { - t.Fatalf("open list = %+v, want exactly %s", openList, open1) - } + testassert.NoError(t, err) + testassert.Require(t, len(openList) == 1 && openList[0].SessionId == open1, "open list = %+v, want exactly %s", openList, open1) archivedList, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: ws2, Lifecycle: "archived"}) - if err != nil { - t.Fatalf("listSessions archived: %v", err) - } - if len(archivedList) != 1 || archivedList[0].SessionId != archived || archivedList[0].Lifecycle != protocol.Archived || archivedList[0].ArchivedAt == nil { - t.Fatalf("archived list = %+v, want exactly the archived %s", archivedList, archived) - } + testassert.NoError(t, err) + testassert.Require(t, len(archivedList) == 1 && archivedList[0].SessionId == archived && archivedList[0].Lifecycle == protocol.Archived && archivedList[0].ArchivedAt != nil, "archived list = %+v, want exactly the archived %s", archivedList, archived) noMatch, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: ws2, Lifecycle: "open"}) - if err != nil { - t.Fatalf("listSessions no match: %v", err) - } - if len(noMatch) != 0 { - t.Fatalf("no-match list = %+v, want empty", noMatch) - } + testassert.NoError(t, err) + testassert.Require(t, len(noMatch) == 0, "no-match list = %+v, want empty", noMatch) // A nonempty relative root is normalized before filtering. cwd, err := os.Getwd() - if err != nil { - t.Fatalf("Getwd: %v", err) - } + testassert.NoError(t, err) relative, err := filepath.Rel(cwd, ws1) - if err != nil { - t.Fatalf("Rel: %v", err) - } + testassert.NoError(t, err) relativeList, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: relative, Lifecycle: "open"}) - if err != nil { - t.Fatalf("listSessions relative: %v", err) - } - if len(relativeList) != 1 || relativeList[0].SessionId != open1 || relativeList[0].Workspace != ws1 { - t.Fatalf("relative-root list = %+v, want exactly the normalized %s row", relativeList, ws1) - } + testassert.NoError(t, err) + testassert.Require(t, len(relativeList) == 1 && relativeList[0].SessionId == open1 && relativeList[0].Workspace == ws1, "relative-root list = %+v, want exactly the normalized %s row", relativeList, ws1) }) } @@ -500,32 +408,21 @@ func TestProjectionListAndReadsAroundUnavailableSessions(t *testing.T) { _, err := h.ArchiveSession(ctx, deleted) return err }) - if err != nil { - t.Fatalf("ArchiveSession: %v", err) - } - if err := r.deleteSession(context.Background(), deleted); err != nil { - t.Fatalf("DeleteSession: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.deleteSession(context.Background(), deleted)) list, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: ws, Lifecycle: "open"}) - if err != nil { - t.Fatalf("listSessions: %v", err) - } - if len(list) != 1 || list[0].SessionId != valid { - t.Fatalf("list = %+v, want exactly the valid %s", list, valid) - } + testassert.NoError(t, err) + testassert.Require(t, len(list) == 1 && list[0].SessionId == valid, "list = %+v, want exactly the valid %s", list, valid) - if err := headerErrorThroughRuntime(context.Background(), r, deleted); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("deleted read = %v, want harness.ErrNotFound", err) + { + err := headerErrorThroughRuntime(context.Background(), r, deleted) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "deleted read = %v, want harness.ErrNotFound", err) } err = headerErrorThroughRuntime(context.Background(), r, corruptID) - if !errors.Is(err, harness.ErrCorrupt) { - t.Fatalf("corrupt read = %v, want the harness.ErrCorrupt class", err) - } + testassert.Require(t, errors.Is(err, harness.ErrCorrupt), "corrupt read = %v, want the harness.ErrCorrupt class", err) var corruptErr *harness.CorruptionError - if !errors.As(err, &corruptErr) { - t.Fatal("corrupt read lost its typed CorruptionError identity") - } + testassert.Require(t, errors.As(err, &corruptErr), "corrupt read lost its typed CorruptionError identity") }) } @@ -538,9 +435,7 @@ func TestProjectionWorkspaceNavigation(t *testing.T) { // The empty navigation result is the non-nil [] shape before any // Session exists. initial, err := r.listWorkspaces(context.Background()) - if err != nil || initial == nil || len(initial) != 0 { - t.Fatalf("initial workspaces = (%+v, %v), want the non-nil empty list", initial, err) - } + testassert.Require(t, err == nil && initial != nil && len(initial) == 0, "initial workspaces = (%+v, %v), want the non-nil empty list", initial, err) wsA := filepath.Join(e.home, "alpha") wsB := filepath.Join(e.home, "beta") root := projectionSession(t, r, wsA, "solo").Identity.SessionID @@ -548,16 +443,12 @@ func TestProjectionWorkspaceNavigation(t *testing.T) { projectionSession(t, r, wsB, "solo") workspaces, err := r.listWorkspaces(context.Background()) - if err != nil { - t.Fatalf("listWorkspaces: %v", err) - } + testassert.NoError(t, err) want := []protocol.Workspace{ {Root: wsA, DisplayName: filepath.Base(wsA)}, {Root: wsB, DisplayName: filepath.Base(wsB)}, } - if !reflect.DeepEqual(workspaces, want) { - t.Fatalf("workspaces = %+v, want exactly %+v", workspaces, want) - } + testassert.Require(t, reflect.DeepEqual(workspaces, want), "workspaces = %+v, want exactly %+v", workspaces, want) }) } @@ -581,40 +472,26 @@ func TestProjectionCallerCopyOwnership(t *testing.T) { header.CreatedAt = time.Time{} list, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: ws, Lifecycle: "open"}) - if err != nil { - t.Fatalf("listSessions: %v", err) - } + testassert.NoError(t, err) wantList := append([]protocol.Session(nil), list...) list[0].SessionId = "mutated" list[0].SessionRevision.LocalRevision = "0" workspaces, err := r.listWorkspaces(context.Background()) - if err != nil { - t.Fatalf("listWorkspaces: %v", err) - } + testassert.NoError(t, err) wantWorkspaces := append([]protocol.Workspace(nil), workspaces...) if len(workspaces) == 1 { workspaces[0].Root = "mutated" } reread := projectSession(headerThroughRuntime(t, r, session)) - if reread != wantHeader { - t.Fatalf("re-read header = %+v, want the immutable pre-mutation %+v", reread, wantHeader) - } + testassert.Equal(t, reread, wantHeader) relisted, err := r.listSessions(context.Background(), protocol.ListSessionsParams{Workspace: ws, Lifecycle: "open"}) - if err != nil { - t.Fatalf("listSessions re-read: %v", err) - } - if !reflect.DeepEqual(relisted, wantList) { - t.Fatalf("re-read list = %+v, want the immutable pre-mutation %+v", relisted, wantList) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(relisted, wantList), "re-read list = %+v, want the immutable pre-mutation %+v", relisted, wantList) rews, err := r.listWorkspaces(context.Background()) - if err != nil { - t.Fatalf("listWorkspaces re-read: %v", err) - } - if !reflect.DeepEqual(rews, wantWorkspaces) { - t.Fatalf("re-read workspaces = %+v, want the immutable pre-mutation %+v", rews, wantWorkspaces) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(rews, wantWorkspaces), "re-read workspaces = %+v, want the immutable pre-mutation %+v", rews, wantWorkspaces) }) } @@ -632,16 +509,12 @@ func TestProjectionConcurrentReadDelete(t *testing.T) { _, err := h.ArchiveSession(ctx, session) return err }) - if err != nil { - t.Fatalf("ArchiveSession: %v", err) - } + testassert.NoError(t, err) // The complete old outcome: a read that finishes before the deletion // commit returns the full header. before := projectSession(headerThroughRuntime(t, r, session)) - if before.SessionId != session || before.Lifecycle != protocol.Archived { - t.Fatalf("pre-delete header = %+v, want the complete archived header", before) - } + testassert.Require(t, before.SessionId == session && before.Lifecycle == protocol.Archived, "pre-delete header = %+v, want the complete archived header", before) // The complete new outcome: a read parked behind the deletion // transaction converges to the typed not-found with no header. @@ -666,14 +539,10 @@ func TestProjectionConcurrentReadDelete(t *testing.T) { readDone <- headerErrorThroughRuntime(context.Background(), r, session) }() release() - if err := <-deleteDone; err != nil { - t.Fatalf("DeleteSession: %v", err) - } + testassert.NoError(t, <-deleteDone) select { case err := <-readDone: - if !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("read racing the delete = %v, want the typed not-found", err) - } + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "read racing the delete = %v, want the typed not-found", err) case <-time.After(10 * time.Second): t.Fatal("the racing read never converged") } @@ -697,20 +566,12 @@ func TestProjectionQueuedLocalRevisionWithoutDurableAdvance(t *testing.T) { submitQueuedThroughRuntime(t, r, session, "op-2", "queued") after := projectSession(headerThroughRuntime(t, r, session)) - if after.SessionRevision.DurableRevision != before.SessionRevision.DurableRevision { - t.Fatalf("durable revision = %s, want the unchanged %s", after.SessionRevision.DurableRevision, before.SessionRevision.DurableRevision) - } + testassert.Equal(t, after.SessionRevision.DurableRevision, before.SessionRevision.DurableRevision) beforeLocal, err := strconv.ParseUint(before.SessionRevision.LocalRevision, 10, 64) - if err != nil { - t.Fatalf("parse before local revision: %v", err) - } + testassert.NoError(t, err) afterLocal, err := strconv.ParseUint(after.SessionRevision.LocalRevision, 10, 64) - if err != nil { - t.Fatalf("parse after local revision: %v", err) - } - if afterLocal <= beforeLocal { - t.Fatalf("local revision %d did not advance past %d after the queued publication", afterLocal, beforeLocal) - } + testassert.NoError(t, err) + testassert.Require(t, afterLocal > beforeLocal, "local revision %d did not advance past %d after the queued publication", afterLocal, beforeLocal) close(gate) awaitOperation(t, r, session, "op-2", harness.OperationSuccess) @@ -723,9 +584,7 @@ func TestProjectionQueuedLocalRevisionWithoutDurableAdvance(t *testing.T) { func TestProjectionClosedAndCanceledCallers(t *testing.T) { r, _ := openProjectionRuntime(t, storage.NewMemory()) defer closeProjectionRuntime(r) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) closed := []func() error{ func() error { return headerErrorThroughRuntime(context.Background(), r, "0123456789abcdef0123456789abcdef") @@ -737,8 +596,9 @@ func TestProjectionClosedAndCanceledCallers(t *testing.T) { func() error { _, err := r.listWorkspaces(context.Background()); return err }, } for i, call := range closed { - if err := call(); !errors.Is(err, ErrClosed) { - t.Fatalf("closed caller %d = %v, want ErrClosed", i, err) + { + err := call() + testassert.Require(t, errors.Is(err, ErrClosed), "closed caller %d = %v, want ErrClosed", i, err) } } @@ -757,8 +617,9 @@ func TestProjectionClosedAndCanceledCallers(t *testing.T) { func() error { _, err := r2.listWorkspaces(canceled); return err }, } for i, call := range canceledCalls { - if err := call(); !errors.Is(err, context.Canceled) { - t.Fatalf("canceled caller %d = %v, want context.Canceled", i, err) + { + err := call() + testassert.Require(t, errors.Is(err, context.Canceled), "canceled caller %d = %v, want context.Canceled", i, err) } } } @@ -786,9 +647,7 @@ func TestProjectionHeaderCarriesNoInternalRepresentation(t *testing.T) { "admitted_at", "agent_type", "model", "operation_id", "request_kind", "settled_at", "status", "usage", "usage.by_model", }) - if !strings.Contains(operationJSON, `"by_model":[]`) { - t.Fatalf("empty usage = %s, want the non-nil [] array", operationJSON) - } + testassert.Require(t, strings.Contains(operationJSON, `"by_model":[]`), "empty usage = %s, want the non-nil [] array", operationJSON) // The canonical register totals project as decimal strings. usage := projectUsage(harness.UsageTotals{ByModel: []harness.ModelUsage{{ Model: model.ModelRef{Provider: "prov", Model: "m"}, @@ -798,9 +657,7 @@ func TestProjectionHeaderCarriesNoInternalRepresentation(t *testing.T) { Model: "prov/m", Usage: protocol.UsageCount{InputTokens: "11", CachedInputTokens: "3", OutputTokens: "5"}, }}} - if !reflect.DeepEqual(usage, wantUsage) { - t.Fatalf("usage projection = %+v, want %+v", usage, wantUsage) - } + testassert.Require(t, reflect.DeepEqual(usage, wantUsage), "usage projection = %+v, want %+v", usage, wantUsage) }) } @@ -810,14 +667,13 @@ func TestProjectionHeaderCarriesNoInternalRepresentation(t *testing.T) { func assertExactJSONKeys(t *testing.T, v any, want []string) string { t.Helper() data, err := json.Marshal(v) - if err != nil { - t.Fatalf("marshal %T: %v", v, err) - } + testassert.Require(t, err == nil, "marshal %T: %v", v, err) decoder := json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() var top map[string]any - if err := decoder.Decode(&top); err != nil { - t.Fatalf("unmarshal %T: %v", v, err) + { + err := decoder.Decode(&top) + testassert.Require(t, err == nil, "unmarshal %T: %v", v, err) } var got []string for key, value := range top { @@ -831,9 +687,7 @@ func assertExactJSONKeys(t *testing.T, v any, want []string) string { sort.Strings(got) wantSorted := append([]string(nil), want...) sort.Strings(wantSorted) - if !reflect.DeepEqual(got, wantSorted) { - t.Fatalf("wire keys = %v, want exactly %v", got, wantSorted) - } + testassert.Require(t, reflect.DeepEqual(got, wantSorted), "wire keys = %v, want exactly %v", got, wantSorted) return string(data) } @@ -912,9 +766,7 @@ func TestProjectionListCandidateDeletionDuringScan(t *testing.T) { // Warm the victim before arming: the scan reaches it only after the // parked survivor, so a missed gone check would return its cached // header instead of omitting it. - if err := headerErrorThroughRuntime(context.Background(), r, victim); err != nil { - t.Fatalf("warm ReadSessionHeader: %v", err) - } + testassert.NoError(t, headerErrorThroughRuntime(context.Background(), r, victim)) gated.arm() defer gated.releaseGate() // failure cleanup: runs before the owner close below @@ -932,23 +784,18 @@ func TestProjectionListCandidateDeletionDuringScan(t *testing.T) { } // While the survivor's read is parked, the already-copied victim is // deleted: the scan must observe it as absent, not fail. - if err := r.deleteSession(context.Background(), victim); err != nil { - t.Fatalf("delete during scan: %v", err) - } + testassert.NoError(t, r.deleteSession(context.Background(), victim)) gated.releaseGate() select { case err := <-listDone: - if err != nil { - t.Fatalf("listSessions: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("the scan never converged") } - if len(list) != 1 || list[0].SessionId != survivor || list[0].Lifecycle != protocol.Archived || list[0].ArchivedAt == nil { - t.Fatalf("list = %+v, want exactly the complete survivor %s header", list, survivor) - } - if err := headerErrorThroughRuntime(context.Background(), r, victim); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("victim read = %v, want harness.ErrNotFound", err) + testassert.Require(t, len(list) == 1 && list[0].SessionId == survivor && list[0].Lifecycle == protocol.Archived && list[0].ArchivedAt != nil, "list = %+v, want exactly the complete survivor %s header", list, survivor) + { + err := headerErrorThroughRuntime(context.Background(), r, victim) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "victim read = %v, want harness.ErrNotFound", err) } }) } diff --git a/runtime/protocol_server_test.go b/runtime/protocol_server_test.go index c5a3bb99..19318ff4 100644 --- a/runtime/protocol_server_test.go +++ b/runtime/protocol_server_test.go @@ -24,6 +24,7 @@ import ( "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -37,26 +38,28 @@ import ( func openProtocolServer(t *testing.T, r *Runtime) *ProtocolServer { t.Helper() ps, err := r.OpenProtocol(context.Background()) - if err != nil { - t.Fatalf("OpenProtocol: %v", err) - } + testassert.NoError(t, err) host, _, err := net.SplitHostPort(strings.TrimPrefix(ps.Endpoint(), "http://")) if err != nil { t.Fatalf("endpoint %q is not host:port: %v", ps.Endpoint(), err) } ip := net.ParseIP(host) - if ip == nil || !ip.IsLoopback() { - t.Fatalf("endpoint host %q is not a loopback address", host) - } - if len(ps.instance) != 32 || strings.Trim(ps.instance, "0123456789abcdef") != "" { - t.Fatalf("instance identity %q is not 32 lowercase hex", ps.instance) - } - if len(ps.credential) != 64 || strings.Trim(ps.credential, "0123456789abcdef") != "" { - t.Fatal("credential is not 64 lowercase hex") - } + testassert.Require(t, ip != nil && ip.IsLoopback(), "endpoint host %q is not a loopback address", host) + testassert.Require(t, len(ps.instance) == 32 && strings.Trim(ps.instance, "0123456789abcdef") == "", "instance identity %q is not 32 lowercase hex", ps.instance) + testassert.Require(t, len(ps.credential) == 64 && strings.Trim(ps.credential, "0123456789abcdef") == "", "credential is not 64 lowercase hex") return ps } +// openProtocolOwner opens one default-document owner over the given store for +// the protocol rows. +func openProtocolOwner(t *testing.T, store harness.Storage) (*Runtime, *ownerEnv) { + t.Helper() + e := newOwnerEnv(t) + r, err := e.open(context.Background(), e.storagePlugin(store)) + testassert.NoError(t, err) + return r, e +} + // protocolClient builds the generated Go client with the server's bearer // credential on every request. func protocolClient(t *testing.T, ps *ProtocolServer) *protocol.ClientWithResponses { @@ -65,9 +68,7 @@ func protocolClient(t *testing.T, ps *ProtocolServer) *protocol.ClientWithRespon req.Header.Set("Authorization", "Bearer "+ps.credential) return nil })) - if err != nil { - t.Fatalf("NewClientWithResponses: %v", err) - } + testassert.NoError(t, err) return client } @@ -81,9 +82,7 @@ func rawProtocol(t *testing.T, method, target, bearer, body string) *http.Respon reader = strings.NewReader(body) } req, err := http.NewRequest(method, target, reader) - if err != nil { - t.Fatalf("%s %s: %v", method, target, err) - } + testassert.Require(t, err == nil, "%s %s: %v", method, target, err) if bearer != "" { req.Header.Set("Authorization", "Bearer "+bearer) } @@ -95,9 +94,7 @@ func rawProtocol(t *testing.T, method, target, bearer, body string) *http.Respon Timeout: 30 * time.Second, } resp, err := client.Do(req) - if err != nil { - t.Fatalf("%s %s: %v", method, target, err) - } + testassert.Require(t, err == nil, "%s %s: %v", method, target, err) return resp } @@ -106,23 +103,21 @@ func decodeProtocolError(t *testing.T, resp *http.Response) protocol.Error { t.Helper() defer resp.Body.Close() data, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("read %s response body: %v", resp.Request.Method, err) - } + testassert.Require(t, err == nil, "read %s response body: %v", resp.Request.Method, err) body := string(data) decoder := json.NewDecoder(strings.NewReader(body)) decoder.DisallowUnknownFields() var typed protocol.Error - if err := decoder.Decode(&typed); err != nil { - t.Fatalf("decode error body %q: %v", body, err) + { + err := decoder.Decode(&typed) + testassert.Require(t, err == nil, "decode error body %q: %v", body, err) } var trailing json.RawMessage - if err := decoder.Decode(&trailing); err != io.EOF { - t.Fatalf("error body %q is not exactly one JSON document", body) - } - if !typed.Code.Valid() { - t.Fatalf("error body %q carries an unknown code", body) + { + err := decoder.Decode(&trailing) + testassert.Require(t, err == io.EOF, "error body %q is not exactly one JSON document", body) } + testassert.Require(t, typed.Code.Valid(), "error body %q carries an unknown code", body) return typed } @@ -140,9 +135,7 @@ func textContentPart(t *testing.T, text string, extra map[string]json.RawMessage if extra != nil { optional = &extra } - if err := part.FromTextPart(protocol.TextPart{Text: text, Extra: optional}); err != nil { - t.Fatalf("build text part: %v", err) - } + testassert.NoError(t, part.FromTextPart(protocol.TextPart{Text: text, Extra: optional})) return part } @@ -150,9 +143,7 @@ func textContentPart(t *testing.T, text string, extra map[string]json.RawMessage func imageURLContentPart(t *testing.T, url string) protocol.ContentPart { t.Helper() var part protocol.ContentPart - if err := part.FromImageURLPart(protocol.ImageURLPart{Url: url}); err != nil { - t.Fatalf("build image_url part: %v", err) - } + testassert.NoError(t, part.FromImageURLPart(protocol.ImageURLPart{Url: url})) return part } @@ -160,9 +151,7 @@ func imageURLContentPart(t *testing.T, url string) protocol.ContentPart { func opaqueContentPart(t *testing.T, wireType string) protocol.ContentPart { t.Helper() var part protocol.ContentPart - if err := part.FromOpaquePart(protocol.OpaquePart{OpaqueWireType: wireType}); err != nil { - t.Fatalf("build opaque part: %v", err) - } + testassert.NoError(t, part.FromOpaquePart(protocol.OpaquePart{OpaqueWireType: wireType})) return part } @@ -179,14 +168,10 @@ type sseStream struct { func openSSEStream(t *testing.T, ps *ProtocolServer) *sseStream { t.Helper() req, err := http.NewRequest(http.MethodGet, protocolTarget(ps, "/v1/events"), nil) - if err != nil { - t.Fatalf("build events request: %v", err) - } + testassert.NoError(t, err) req.Header.Set("Authorization", "Bearer "+ps.credential) resp, err := (&http.Client{}).Do(req) - if err != nil { - t.Fatalf("open events stream: %v", err) - } + testassert.NoError(t, err) if resp.StatusCode != http.StatusOK { resp.Body.Close() t.Fatalf("events stream status = %d, want 200", resp.StatusCode) @@ -236,21 +221,21 @@ func readUntilEvent(t *testing.T, stream *sseStream, kind string, bound int) map t.Helper() for i := 0; i < bound; i++ { frame, ok, err := stream.readSSEFrame() - if err != nil || !ok { - t.Fatalf("search for %s event: ok=%v err=%v after %d frames", kind, ok, err, i) - } + testassert.Require(t, err == nil && ok, "search for %s event: ok=%v err=%v after %d frames", kind, ok, err, i) var member struct { Kind string `json:"kind"` } - if err := json.Unmarshal(frame, &member); err != nil { - t.Fatalf("decode candidate frame %s: %v", frame, err) + { + err := json.Unmarshal(frame, &member) + testassert.Require(t, err == nil, "decode candidate frame %s: %v", frame, err) } if member.Kind != kind { continue } var object map[string]json.RawMessage - if err := json.Unmarshal(frame, &object); err != nil { - t.Fatalf("decode %s frame %s: %v", kind, frame, err) + { + err := json.Unmarshal(frame, &object) + testassert.Require(t, err == nil, "decode %s frame %s: %v", kind, frame, err) } return object } @@ -263,25 +248,20 @@ func readUntilEvent(t *testing.T, stream *sseStream, kind string, bound int) map func eventRevisionInstance(t *testing.T, revisionRaw json.RawMessage, member string) string { t.Helper() var revision map[string]json.RawMessage - if err := json.Unmarshal(revisionRaw, &revision); err != nil { - t.Fatalf("decode revision %s: %v", revisionRaw, err) + { + err := json.Unmarshal(revisionRaw, &revision) + testassert.Require(t, err == nil, "decode revision %s: %v", revisionRaw, err) } var instance string - if err := json.Unmarshal(revision["instance_id"], &instance); err != nil { - t.Fatalf("decode revision instance: %v", err) - } - if instance == "" { - t.Fatalf("revision %s carries an empty instance identity", member) - } + testassert.NoError(t, json.Unmarshal(revision["instance_id"], &instance)) + testassert.Require(t, instance != "", "revision %s carries an empty instance identity", member) return instance } // assertQualifiedInstance pins one revision object's instance identity. func assertQualifiedInstance(t *testing.T, which string, revision protocol.SessionRevision, instance string) { t.Helper() - if revision.InstanceId != instance { - t.Fatalf("%s session revision instance = %q, want the minted %q", which, revision.InstanceId, instance) - } + testassert.Require(t, revision.InstanceId == instance, "%s session revision instance = %q, want the minted %q", which, revision.InstanceId, instance) } // TestProtocolServerSessionFamily mounts the complete server and drives the @@ -299,59 +279,37 @@ func TestProtocolServerSessionFamily(t *testing.T) { ctx := context.Background() health, err := client.GetHealthWithResponse(ctx) - if err != nil { - t.Fatalf("GetHealth: %v", err) - } - if health.JSON200 == nil || health.HTTPResponse.StatusCode != http.StatusOK { - t.Fatalf("health = (%d, %v), want the 200 typed body", health.HTTPResponse.StatusCode, health.JSON200) - } - if health.JSON200.InstanceId != ps.instance || health.JSON200.ProtocolVersion != protocol.N2 { - t.Fatalf("health = %+v, want instance %q and protocol version %q", health.JSON200, ps.instance, protocol.N2) - } + testassert.NoError(t, err) + testassert.Require(t, health.JSON200 != nil && health.HTTPResponse.StatusCode == http.StatusOK, "health = (%d, %v), want the 200 typed body", health.HTTPResponse.StatusCode, health.JSON200) + testassert.Require(t, health.JSON200.InstanceId == ps.instance && health.JSON200.ProtocolVersion == protocol.N2, "health = %+v, want instance %q and protocol version %q", health.JSON200, ps.instance, protocol.N2) workspace := filepath.Join(e.home, "mounted") created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: workspace, AgentType: "solo"}) - if err != nil { - t.Fatalf("CreateSession: %v", err) - } - if created.HTTPResponse.StatusCode != http.StatusCreated || created.JSON201 == nil { - t.Fatalf("create = (%d, %v), want 201 with the typed header", created.HTTPResponse.StatusCode, created.JSON201) - } + testassert.NoError(t, err) + testassert.Require(t, created.HTTPResponse.StatusCode == http.StatusCreated && created.JSON201 != nil, "create = (%d, %v), want 201 with the typed header", created.HTTPResponse.StatusCode, created.JSON201) session := created.JSON201.SessionId assertQualifiedInstance(t, "created header", created.JSON201.SessionRevision, ps.instance) header, err := client.GetSessionHydrationWithResponse(ctx, session) - if err != nil { - t.Fatalf("GetSessionHydration: %v", err) - } - if header.JSON200 == nil || header.JSON200.Session.SessionId != session { - t.Fatalf("read header = %v, want the created session", header.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, header.JSON200 != nil && header.JSON200.Session.SessionId == session, "read header = %v, want the created session", header.JSON200) assertQualifiedInstance(t, "read header", header.JSON200.Session.SessionRevision, ps.instance) // The mounted list route normalizes one relative workspace root // before filtering; the literal compare is the forbidden sibling. t.Chdir(e.home) relative, err := filepath.Rel(e.home, workspace) - if err != nil { - t.Fatalf("relative workspace: %v", err) - } + testassert.NoError(t, err) listed, err := client.ListSessionsWithResponse(ctx, &protocol.ListSessionsParams{Workspace: relative, Lifecycle: "open"}) - if err != nil { - t.Fatalf("ListSessions: %v", err) - } + testassert.NoError(t, err) if listed.JSON200 == nil || len(*listed.JSON200) != 1 || (*listed.JSON200)[0].SessionId != session { body, _ := json.Marshal(listed.JSON200) t.Fatalf("relative-root list = %s, want exactly the created session", body) } - if (*listed.JSON200)[0].SessionRevision.InstanceId != ps.instance { - t.Fatalf("list row revision instance = %q, want %q", (*listed.JSON200)[0].SessionRevision.InstanceId, ps.instance) - } + testassert.Equal(t, (*listed.JSON200)[0].SessionRevision.InstanceId, ps.instance) // A valid filter matching no session is one 200 with an empty list. none, err := client.ListSessionsWithResponse(ctx, &protocol.ListSessionsParams{Workspace: filepath.Join(e.home, "absent"), Lifecycle: "archived"}) - if err != nil { - t.Fatalf("ListSessions no-match: %v", err) - } + testassert.NoError(t, err) if none.JSON200 == nil || *none.JSON200 == nil || len(*none.JSON200) != 0 { body, _ := json.Marshal(none.JSON200) t.Fatalf("no-match list = %s, want the empty JSON array", body) @@ -364,9 +322,7 @@ func TestProtocolServerSessionFamily(t *testing.T) { "unknown lifecycle": {Workspace: workspace, Lifecycle: "paused"}, } { refused, err := client.ListSessionsWithResponse(ctx, ¶ms) - if err != nil { - t.Fatalf("%s list: %v", name, err) - } + testassert.Require(t, err == nil, "%s list: %v", name, err) if refused.JSONDefault == nil || refused.JSONDefault.Code != protocol.Invalid { body, _ := json.Marshal(refused.JSONDefault) t.Fatalf("%s list = %s (status %d), want the typed invalid refusal", name, body, refused.HTTPResponse.StatusCode) @@ -387,8 +343,9 @@ func TestProtocolServerSessionFamily(t *testing.T) { omitted.Body.Close() t.Fatalf("%s list = %d, want the typed invalid refusal 400", name, omitted.StatusCode) } - if typed := decodeProtocolError(t, omitted); typed.Code != protocol.Invalid { - t.Fatalf("%s list error = %+v, want invalid", name, typed) + { + typed := decodeProtocolError(t, omitted) + testassert.Require(t, typed.Code == protocol.Invalid, "%s list error = %+v, want invalid", name, typed) } } @@ -407,9 +364,7 @@ func TestProtocolServerSessionFamily(t *testing.T) { Mode: protocol.SubmitRequestMode(mode), Content: []protocol.ContentPart{textContentPart(t, text, nil)}, }) - if err != nil { - t.Fatalf("SubmitSession(%s): %v", operationID, err) - } + testassert.Require(t, err == nil, "SubmitSession(%s): %v", operationID, err) return *response } admitted := submit("op-1", "regular", "first") @@ -436,54 +391,26 @@ func TestProtocolServerSessionFamily(t *testing.T) { } hydration, err := client.GetSessionHydrationWithResponse(ctx, session) - if err != nil { - t.Fatalf("GetSessionHydration: %v", err) - } - if hydration.JSON200 == nil { - t.Fatalf("hydration = %d with no typed body", hydration.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Require(t, hydration.JSON200 != nil, "hydration = %d with no typed body", hydration.HTTPResponse.StatusCode) if len(hydration.JSON200.Pending.Steering) != 1 || len(hydration.JSON200.Pending.Queued) != 1 { body, _ := json.Marshal(hydration.JSON200.Pending) t.Fatalf("pending queues = %s, want one steering and one queued item", body) } - if hydration.JSON200.Usage.Totals.ByModel == nil { - t.Fatalf("hydration usage totals = %+v, want the present by-model array", hydration.JSON200.Usage.Totals) - } - if hydration.JSON200.SelectedModel == nil || *hydration.JSON200.SelectedModel != "prov/m" { - t.Fatalf("hydration selected model = %v, want the configured prov/m", hydration.JSON200.SelectedModel) - } - if hydration.JSON200.Usage.Context.ContextWindow != 262144 { - t.Fatalf("hydration context window = %d, want the running Operation's captured catalog window", hydration.JSON200.Usage.Context.ContextWindow) - } + testassert.Require(t, hydration.JSON200.Usage.Totals.ByModel != nil && hydration.JSON200.SelectedModel != nil && *hydration.JSON200.SelectedModel == "prov/m" && hydration.JSON200.Usage.Context.ContextWindow == 262144, "hydration usage totals = %+v, want the present by-model array; hydration selected model = %v, want the configured prov/m; got %v, want %v", hydration.JSON200.Usage.Totals, hydration.JSON200.SelectedModel, hydration.JSON200.Usage.Context.ContextWindow, 262144) assertQualifiedInstance(t, "hydration", hydration.JSON200.SessionRevision, ps.instance) - if hydration.JSON200.Session.SessionId != session || hydration.JSON200.Session.SessionRevision.InstanceId != ps.instance { - t.Fatalf("hydration's nested session revision instance = %q, want %q", hydration.JSON200.Session.SessionRevision.InstanceId, ps.instance) - } - if hydration.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("hydration's configuration revision instance = %q", hydration.JSON200.ConfigurationRevision.InstanceId) - } - if hydration.JSON200.WarningsRevision.InstanceId != ps.instance { - t.Fatalf("hydration's warnings revision instance = %q", hydration.JSON200.WarningsRevision.InstanceId) - } + testassert.Require(t, hydration.JSON200.Session.SessionId == session && hydration.JSON200.Session.SessionRevision.InstanceId == ps.instance && hydration.JSON200.ConfigurationRevision.InstanceId == ps.instance && hydration.JSON200.WarningsRevision.InstanceId == ps.instance, "hydration's nested session revision instance = %q, want %q; got %v, want %v; got %v, want %v", hydration.JSON200.Session.SessionRevision.InstanceId, ps.instance, hydration.JSON200.ConfigurationRevision.InstanceId, ps.instance, hydration.JSON200.WarningsRevision.InstanceId, ps.instance) // An unresolvable Agent type yields the required null selection over // the mounted wire, never a fallback model. ghostSession, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: workspace, AgentType: "ghost"}) - if err != nil || ghostSession.JSON201 == nil { - t.Fatalf("create unresolved-type session: %v", err) - } + testassert.Require(t, err == nil && ghostSession.JSON201 != nil, "create unresolved-type session: %v", err) ghostHydration, err := client.GetSessionHydrationWithResponse(ctx, ghostSession.JSON201.SessionId) - if err != nil || ghostHydration.JSON200 == nil { - t.Fatalf("hydration of an unresolved type = (%d, %v), want a typed body", ghostHydration.HTTPResponse.StatusCode, err) - } - if ghostHydration.JSON200.SelectedModel != nil { - t.Fatalf("unresolved-type selected model = %v, want null", ghostHydration.JSON200.SelectedModel) - } + testassert.Require(t, err == nil && ghostHydration.JSON200 != nil, "hydration of an unresolved type = (%d, %v), want a typed body", ghostHydration.HTTPResponse.StatusCode, err) + testassert.Require(t, ghostHydration.JSON200.SelectedModel == nil, "unresolved-type selected model = %v, want null", ghostHydration.JSON200.SelectedModel) groups, err := client.GetSessionCodeSnapshotsWithResponse(ctx, session) - if err != nil { - t.Fatalf("GetSessionCodeSnapshots: %v", err) - } + testassert.NoError(t, err) if groups.JSON200 == nil || *groups.JSON200 == nil || len(*groups.JSON200) != 0 { body, _ := json.Marshal(groups.JSON200) t.Fatalf("empty code snapshot groups = %s, want the empty JSON array", body) @@ -502,35 +429,23 @@ func TestProtocolServerSessionFamily(t *testing.T) { // every Operation ends in clean success, and the delivery manufactures // no signal. steeringHistory, err := client.GetSessionHistoryWithResponse(ctx, session, &protocol.GetSessionHistoryParams{}) - if err != nil || steeringHistory.JSON200 == nil { - t.Fatalf("GetSessionHistory = (%v, %+v)", err, steeringHistory.JSON200) - } + testassert.Require(t, err == nil && steeringHistory.JSON200 != nil, "GetSessionHistory = (%v, %+v)", err, steeringHistory.JSON200) owned, ended := map[string]string{}, map[string]bool{} for _, item := range steeringHistory.JSON200.Items { switch itemKind(t, item) { case "input": input, err := item.AsInputItem() - if err != nil { - t.Fatalf("input item: %v", err) - } - if input.OperationId == nil || *input.OperationId == "" { - t.Fatalf("input item %s carries no owning Operation", input.ItemId) - } + testassert.NoError(t, err) + testassert.Require(t, input.OperationId != nil && *input.OperationId != "", "input item %s carries no owning Operation", input.ItemId) owned[input.ItemId] = *input.OperationId case "operation_end": end, err := item.AsOperationEndItem() - if err != nil { - t.Fatalf("end item: %v", err) - } - if end.Status != protocol.OperationEndItemStatusSuccess || end.Detail != "" || end.OperationId == nil { - t.Fatalf("end item = %+v, want clean success under its identity", end) - } + testassert.NoError(t, err) + testassert.Require(t, end.Status == protocol.OperationEndItemStatusSuccess && end.Detail == "" && end.OperationId != nil, "end item = %+v, want clean success under its identity", end) ended[*end.OperationId] = true case "signal": signal, err := item.AsSignalItem() - if err != nil { - t.Fatalf("signal item: %v", err) - } + testassert.NoError(t, err) t.Fatalf("artificial signal %q in the clean flow", signal.Subtype) } } @@ -541,12 +456,8 @@ func TestProtocolServerSessionFamily(t *testing.T) { found = true } } - if !found { - t.Fatalf("input %q has no item owned by %q (owners %v)", text, op, owned) - } - if !ended[op] { - t.Fatalf("operation %q has no clean-success end item (ended %v)", op, ended) - } + testassert.Require(t, found, "input %q has no item owned by %q (owners %v)", text, op, owned) + testassert.Require(t, ended[op], "operation %q has no clean-success end item (ended %v)", op, ended) } // The completed flow's model calls left their own arrival tokens; @@ -564,8 +475,9 @@ func TestProtocolServerSessionFamily(t *testing.T) { t.Fatalf("interrupted-run submit = %s, want the admitted op-int", body) } awaitModelArrival(t, e) - if response, err := client.InterruptSessionWithResponse(ctx, session); err != nil || response.HTTPResponse.StatusCode != http.StatusNoContent { - t.Fatalf("interrupt = (%d, %v), want 204", response.HTTPResponse.StatusCode, err) + { + response, err := client.InterruptSessionWithResponse(ctx, session) + testassert.Require(t, err == nil && response.HTTPResponse.StatusCode == http.StatusNoContent, "interrupt = (%d, %v), want 204", response.HTTPResponse.StatusCode, err) } releaseInt() awaitOperation(t, r, session, "op-int", harness.OperationInterruption) @@ -581,9 +493,7 @@ func TestProtocolServerSessionFamily(t *testing.T) { OperationId: "fork-1", Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, }) - if err != nil { - t.Fatalf("ForkSession: %v", err) - } + testassert.NoError(t, err) if forked.JSON200 == nil || forked.JSON200.Operation.OperationId != "fork-1" || forked.JSON200.Session.SessionId == session { body, _ := json.Marshal(forked.JSON200) t.Fatalf("fork = %s (status %d, %v), want the forked session and its admitted Operation", body, forked.HTTPResponse.StatusCode, forked.JSONDefault) @@ -599,9 +509,7 @@ func TestProtocolServerSessionFamily(t *testing.T) { } history, err := client.GetSessionHistoryWithResponse(ctx, session, &protocol.GetSessionHistoryParams{}) - if err != nil { - t.Fatalf("GetSessionHistory: %v", err) - } + testassert.NoError(t, err) if history.JSON200 == nil || len(history.JSON200.Items) == 0 { body, _ := json.Marshal(history.JSON200) t.Fatalf("history = %s, want the committed input items", body) @@ -612,13 +520,12 @@ func TestProtocolServerSessionFamily(t *testing.T) { // idle root Session, and the compact command admits its Operation on // the same idle discipline. other := projectionSession(t, r, filepath.Join(e.home, "other"), "solo").Identity.SessionID - if response, err := client.StopSessionWithResponse(ctx, other); err != nil || response.HTTPResponse.StatusCode != http.StatusNoContent { - t.Fatalf("stop = (%d, %v), want 204", response.HTTPResponse.StatusCode, err) + { + response, err := client.StopSessionWithResponse(ctx, other) + testassert.Require(t, err == nil && response.HTTPResponse.StatusCode == http.StatusNoContent, "stop = (%d, %v), want 204", response.HTTPResponse.StatusCode, err) } compacted, err := client.CompactSessionWithResponse(ctx, other, protocol.CompactRequest{OperationId: "compact-1"}) - if err != nil { - t.Fatalf("CompactSession: %v", err) - } + testassert.NoError(t, err) if compacted.JSON200 == nil || compacted.JSON200.OperationId != "compact-1" { body, _ := json.Marshal(compacted.JSON200) t.Fatalf("compact = %s, want the admitted compact Operation", body) @@ -626,47 +533,33 @@ func TestProtocolServerSessionFamily(t *testing.T) { awaitIdleSession(t, r, other) workspaces, err := client.ListWorkspacesWithResponse(ctx) - if err != nil { - t.Fatalf("ListWorkspaces: %v", err) - } + testassert.NoError(t, err) if workspaces.JSON200 == nil || len(*workspaces.JSON200) < 2 { body, _ := json.Marshal(workspaces.JSON200) t.Fatalf("workspaces = %s, want the distinct mounted and other roots", body) } view := filepath.Join(e.home, "view.txt") - if err := os.WriteFile(view, []byte("seen\n"), 0o600); err != nil { - t.Fatalf("write workspace file: %v", err) - } + testassert.NoError(t, os.WriteFile(view, []byte("seen\n"), 0o600)) read, err := client.ReadWorkspaceFileWithResponse(ctx, protocol.ReadFileRequest{Workspace: e.home, Path: "view.txt"}) - if err != nil { - t.Fatalf("ReadWorkspaceFile: %v", err) - } + testassert.NoError(t, err) if read.JSON200 == nil || read.JSON200.Content != "seen\n" || read.JSON200.Truncated { body, _ := json.Marshal(read.JSON200) t.Fatalf("file view = %s, want the bounded content", body) } warnings, err := client.GetWarningsWithResponse(ctx) - if err != nil { - t.Fatalf("GetWarnings: %v", err) - } - if warnings.JSON200 == nil || warnings.JSON200.WarningsRevision.InstanceId != ps.instance { - t.Fatalf("warnings revision instance = %+v", warnings.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, warnings.JSON200 != nil && warnings.JSON200.WarningsRevision.InstanceId == ps.instance, "warnings revision instance = %+v", warnings.JSON200) archived, err := client.ArchiveSessionWithResponse(ctx, other) - if err != nil { - t.Fatalf("ArchiveSession: %v", err) - } + testassert.NoError(t, err) if archived.JSON200 == nil || archived.JSON200.Lifecycle != "archived" { body, _ := json.Marshal(archived.JSON200) t.Fatalf("archive = %s, want the archived header", body) } reopened, err := client.ReopenSessionWithResponse(ctx, other) - if err != nil { - t.Fatalf("ReopenSession: %v", err) - } + testassert.NoError(t, err) if reopened.JSON200 == nil || reopened.JSON200.Lifecycle != "open" { body, _ := json.Marshal(reopened.JSON200) t.Fatalf("reopen = %s, want the reopened header", body) @@ -677,12 +570,8 @@ func TestProtocolServerSessionFamily(t *testing.T) { t.Fatalf("re-archive = %d", response.HTTPResponse.StatusCode) } deleted, err := client.DeleteSessionWithResponse(ctx, other) - if err != nil { - t.Fatalf("DeleteSession: %v", err) - } - if deleted.HTTPResponse.StatusCode != http.StatusNoContent { - t.Fatalf("delete = %d, want 204", deleted.HTTPResponse.StatusCode) - } + testassert.NoError(t, err) + testassert.Equal(t, deleted.HTTPResponse.StatusCode, http.StatusNoContent) if missing, err := client.GetSessionHydrationWithResponse(ctx, other); err != nil || missing.JSON200 != nil { t.Fatalf("deleted session read = (%d, %+v), want the typed not-found", missing.HTTPResponse.StatusCode, missing.JSON200) } else if missing.JSONDefault == nil || missing.JSONDefault.Code != protocol.NotFound { @@ -693,17 +582,16 @@ func TestProtocolServerSessionFamily(t *testing.T) { // publishes one live invalidation for this late stream. stream := openSSEStream(t, ps) defer stream.close() - if _, err := r.submitSession(ctx, session, protocol.SubmitRequest{ - OperationId: "op-4", - Mode: protocol.SubmitRequestModeQueued, - Content: []protocol.ContentPart{textContentPart(t, "stream", nil)}, - }); err != nil { - t.Fatalf("stream submit: %v", err) + { + _, err := r.submitSession(ctx, session, protocol.SubmitRequest{ + OperationId: "op-4", + Mode: protocol.SubmitRequestModeQueued, + Content: []protocol.ContentPart{textContentPart(t, "stream", nil)}, + }) + testassert.NoError(t, err) } frame := readUntilEvent(t, stream, "session_changed", 16) - if instance := eventRevisionInstance(t, frame["session_revision"], "session_changed"); instance != ps.instance { - t.Fatalf("stream session revision instance = %q, want %q", instance, ps.instance) - } + testassert.Equal(t, eventRevisionInstance(t, frame["session_revision"], "session_changed"), ps.instance) }) } @@ -723,9 +611,7 @@ func TestProtocolServerHistoryRoundTripsOpaqueNumbers(t *testing.T) { workspace := filepath.Join(e.home, "exact") created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: workspace, AgentType: "solo"}) - if err != nil || created.JSON201 == nil { - t.Fatalf("create: %v", err) - } + testassert.Require(t, err == nil && created.JSON201 != nil, "create: %v", err) session := created.JSON201.SessionId submitted, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ OperationId: "op-1", @@ -736,9 +622,7 @@ func TestProtocolServerHistoryRoundTripsOpaqueNumbers(t *testing.T) { "instance_id": json.RawMessage(`"opaque-identity"`), })}, }) - if err != nil { - t.Fatalf("submit: %v", err) - } + testassert.NoError(t, err) if submitted.JSON200 == nil || submitted.JSON200.Disposition != protocol.SubmitResultDispositionAdmitted { body, _ := json.Marshal(submitted.JSON200) t.Fatalf("submit = %s (status %d)", body, submitted.HTTPResponse.StatusCode) @@ -746,9 +630,7 @@ func TestProtocolServerHistoryRoundTripsOpaqueNumbers(t *testing.T) { awaitOperation(t, r, session, "op-1", harness.OperationSuccess) history, err := client.GetSessionHistoryWithResponse(ctx, session, &protocol.GetSessionHistoryParams{}) - if err != nil || history.JSON200 == nil { - t.Fatalf("history: %v", err) - } + testassert.Require(t, err == nil && history.JSON200 != nil, "history: %v", err) var input *protocol.InputItem for _, item := range history.JSON200.Items { if value, err := item.AsInputItem(); err == nil { @@ -756,31 +638,26 @@ func TestProtocolServerHistoryRoundTripsOpaqueNumbers(t *testing.T) { break } } - if input == nil || len(input.Content) == 0 { - t.Fatalf("history = %d items with no input, want the submitted input item", len(history.JSON200.Items)) - } + testassert.Require(t, input != nil && len(input.Content) != 0, "history = %d items with no input, want the submitted input item", len(history.JSON200.Items)) part, err := input.Content[0].AsTextPart() - if err != nil { - t.Fatalf("decode history part: %v", err) - } - if part.Extra == nil { - t.Fatal("history part lost its extra object") + testassert.NoError(t, err) + testassert.Require(t, part.Extra != nil, "history part lost its extra object") + { + n, ok := (*part.Extra)["n"] + testassert.Require(t, ok && string(bytes.TrimSpace(n)) == "9007199254740993", "extra n = %s, want the exact integer 9007199254740993", n) } - if n, ok := (*part.Extra)["n"]; !ok || string(bytes.TrimSpace(n)) != "9007199254740993" { - t.Fatalf("extra n = %s, want the exact integer 9007199254740993", n) - } - if nullable, ok := (*part.Extra)["nullable"]; !ok || !isJSONNull(nullable) { - t.Fatalf("extra nullable = %s, want the preserved opaque null", nullable) + { + nullable, ok := (*part.Extra)["nullable"] + testassert.Require(t, ok && isJSONNull(nullable), "extra nullable = %s, want the preserved opaque null", nullable) } // The opaque member named instance_id is never rewritten: the // envelope's revision carries the minted identity while the extra's // own member stays exactly as submitted. - if opaque, ok := (*part.Extra)["instance_id"]; !ok || string(bytes.TrimSpace(opaque)) != `"opaque-identity"` { - t.Fatalf("extra instance_id = %s, want the untouched opaque member", opaque) - } - if history.JSON200.SessionRevision.InstanceId != ps.instance { - t.Fatalf("history revision instance = %q, want the minted %q", history.JSON200.SessionRevision.InstanceId, ps.instance) + { + opaque, ok := (*part.Extra)["instance_id"] + testassert.Require(t, ok && string(bytes.TrimSpace(opaque)) == `"opaque-identity"`, "extra instance_id = %s, want the untouched opaque member", opaque) } + testassert.Equal(t, history.JSON200.SessionRevision.InstanceId, ps.instance) }) } @@ -789,9 +666,7 @@ func readRawBody(t *testing.T, resp *http.Response) string { t.Helper() defer resp.Body.Close() data, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("read raw response body: %v", err) - } + testassert.NoError(t, err) return string(data) } @@ -813,9 +688,7 @@ func TestProtocolServerCredentialFlowRawWireSecretFree(t *testing.T) { // the captured stderr sink is genuinely exercised before the scan. writeDotEnv(t, e.home, "MALFORMED LINE\n") r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) stream := openSSEStream(t, ps) @@ -832,17 +705,13 @@ func TestProtocolServerCredentialFlowRawWireSecretFree(t *testing.T) { if err != nil || !ok { break } - if strings.Contains(string(frame), secret) { - t.Fatalf("a raw %s notification frame carries the credential value: %s", what, frame) - } + testassert.Require(t, !strings.Contains(string(frame), secret), "a raw %s notification frame carries the credential value: %s", what, frame) if bytes.Contains(frame, []byte(`"configuration_changed"`)) { observed = true break } } - if !observed { - t.Fatalf("no raw configuration_changed frame was observed for %s", what) - } + testassert.Require(t, observed, "no raw configuration_changed frame was observed for %s", what) } createBody, err := json.Marshal(protocol.CreateProviderRequest{ Id: "rawscan", @@ -853,45 +722,29 @@ func TestProtocolServerCredentialFlowRawWireSecretFree(t *testing.T) { Models: map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, ApiKey: ptrTo(secret), }) - if err != nil { - t.Fatalf("encode create: %v", err) - } + testassert.NoError(t, err) // Successful credential publication: raw response body and witness. resp := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers"), ps.credential, string(createBody)) successBody := readRawBody(t, resp) - if resp.StatusCode != http.StatusOK || !strings.Contains(successBody, `"rawscan"`) { - t.Fatalf("raw create = %d: %s", resp.StatusCode, successBody) - } - if strings.Contains(successBody, secret) { - t.Fatalf("the raw create response carries the credential value: %s", successBody) - } + testassert.Require(t, resp.StatusCode == http.StatusOK && strings.Contains(successBody, `"rawscan"`), "raw create = %d: %s", resp.StatusCode, successBody) + testassert.Require(t, !strings.Contains(successBody, secret), "the raw create response carries the credential value: %s", successBody) scanFrames("create") // Real connect response and its raw witness over the same key. connectBody, err := json.Marshal(protocol.ConnectRequest{ApiKey: ptrTo(secret)}) - if err != nil { - t.Fatalf("encode connect: %v", err) - } + testassert.NoError(t, err) connectResp := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers/connect?provider_id=rawscan"), ps.credential, string(connectBody)) connectBytes := readRawBody(t, connectResp) - if connectResp.StatusCode != http.StatusOK || !strings.Contains(connectBytes, `"connected":true`) || !strings.Contains(connectBytes, `"key_source":"managed"`) { - t.Fatalf("raw connect = %d: %s", connectResp.StatusCode, connectBytes) - } - if strings.Contains(connectBytes, secret) { - t.Fatalf("the raw connect response carries the credential value: %s", connectBytes) - } + testassert.Require(t, connectResp.StatusCode == http.StatusOK && strings.Contains(connectBytes, `"connected":true`) && strings.Contains(connectBytes, `"key_source":"managed"`), "raw connect = %d: %s", connectResp.StatusCode, connectBytes) + testassert.Require(t, !strings.Contains(connectBytes, secret), "the raw connect response carries the credential value: %s", connectBytes) scanFrames("connect") // Real disconnect response and its raw witness over the same key. disconnectResp := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers/disconnect?provider_id=rawscan"), ps.credential, "") disconnectBytes := readRawBody(t, disconnectResp) - if disconnectResp.StatusCode != http.StatusOK || !strings.Contains(disconnectBytes, `"connected":false`) { - t.Fatalf("raw disconnect = %d: %s", disconnectResp.StatusCode, disconnectBytes) - } - if strings.Contains(disconnectBytes, secret) { - t.Fatalf("the raw disconnect response carries the credential value: %s", disconnectBytes) - } + testassert.Require(t, disconnectResp.StatusCode == http.StatusOK && strings.Contains(disconnectBytes, `"connected":false`), "raw disconnect = %d: %s", disconnectResp.StatusCode, disconnectBytes) + testassert.Require(t, !strings.Contains(disconnectBytes, secret), "the raw disconnect response carries the credential value: %s", disconnectBytes) scanFrames("disconnect") // Real credential-bearing refusal: the NUL value fails the native @@ -906,28 +759,17 @@ func TestProtocolServerCredentialFlowRawWireSecretFree(t *testing.T) { Models: map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, ApiKey: ptrTo(nulSecret), }) - if err != nil { - t.Fatalf("encode refusal: %v", err) - } + testassert.NoError(t, err) refused := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers"), ps.credential, string(refusalBody)) refusalBytes := readRawBody(t, refused) - if refused.StatusCode == http.StatusOK || !strings.Contains(refusalBytes, `"configuration"`) { - t.Fatalf("raw refusal = %d: %s", refused.StatusCode, refusalBytes) - } + testassert.Require(t, refused.StatusCode != http.StatusOK && strings.Contains(refusalBytes, `"configuration"`), "raw refusal = %d: %s", refused.StatusCode, refusalBytes) escaped := strings.ReplaceAll(nulSecret, "\x00", `\u0000`) - if strings.Contains(refusalBytes, nulSecret) || strings.Contains(refusalBytes, escaped) { - t.Fatalf("the raw refusal body carries the credential value: %s", refusalBytes) - } + testassert.Require(t, !strings.Contains(refusalBytes, nulSecret) && !strings.Contains(refusalBytes, escaped), "the raw refusal body carries the credential value: %s", refusalBytes) // The real process logging sink exercised by the flow: the startup // diagnostic ran, and no credential value reached the sink. logged := stderr() - if !strings.Contains(logged, "skipping malformed line") { - t.Fatalf("the startup logging path did not run; captured stderr = %q", logged) - } - if strings.Contains(logged, secret) || strings.Contains(logged, nulSecret) { - t.Fatalf("stderr carries a credential value: %q", logged) - } + testassert.Require(t, strings.Contains(logged, "skipping malformed line") && !strings.Contains(logged, secret) && !strings.Contains(logged, nulSecret), "the startup logging path did not run; captured stderr = %q; stderr carries a credential value: %q", logged, logged) }) } @@ -951,9 +793,7 @@ func TestProtocolServerDiscoverySuppliedKey(t *testing.T) { ctx := context.Background() before, err := client.GetConfigurationWithResponse(ctx) - if err != nil || before.JSON200 == nil { - t.Fatalf("GetConfiguration: %v", err) - } + testassert.Require(t, err == nil && before.JSON200 != nil, "GetConfiguration: %v", err) var mu sync.Mutex var auths []string @@ -993,8 +833,9 @@ func TestProtocolServerDiscoverySuppliedKey(t *testing.T) { body, _ := json.Marshal(fallback) t.Fatalf("fallback discovery = %v %s, want the empty answer's typed refusal", err, body) } - if refusal, merr := json.Marshal(fallback.JSONDefault); merr != nil || strings.Contains(string(refusal), pasted) || strings.Contains(string(refusal), external) { - t.Fatalf("the fallback refusal body carries a credential value: (%s, %v)", refusal, merr) + { + refusal, merr := json.Marshal(fallback.JSONDefault) + testassert.Require(t, merr == nil && !strings.Contains(string(refusal), pasted) && !strings.Contains(string(refusal), external), "the fallback refusal body carries a credential value: (%s, %v)", refusal, merr) } // The raw wire row: the strict body decode accepts the supplied @@ -1003,12 +844,8 @@ func TestProtocolServerDiscoverySuppliedKey(t *testing.T) { rawBody := `{"base_url":"` + discovery.URL + `/v1","api_key_env":"MOUNTED_DISCOVERY_KEY","api_key":"` + pasted + `"}` raw := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers/discover"), ps.credential, rawBody) rawBytes := readRawBody(t, raw) - if raw.StatusCode != http.StatusOK || !strings.Contains(rawBytes, `"stub/m"`) { - t.Fatalf("raw supplied-key discovery = %d: %s", raw.StatusCode, rawBytes) - } - if strings.Contains(rawBytes, pasted) || strings.Contains(rawBytes, external) { - t.Fatalf("the raw discovery response carries a credential value: %s", rawBytes) - } + testassert.Require(t, raw.StatusCode == http.StatusOK && strings.Contains(rawBytes, `"stub/m"`), "raw supplied-key discovery = %d: %s", raw.StatusCode, rawBytes) + testassert.Require(t, !strings.Contains(rawBytes, pasted) && !strings.Contains(rawBytes, external), "the raw discovery response carries a credential value: %s", rawBytes) // The exact wire credentials per row: the pasted key twice, with // the captured external value between them. @@ -1016,21 +853,15 @@ func TestProtocolServerDiscoverySuppliedKey(t *testing.T) { mu.Lock() wireAuths := append([]string(nil), auths...) mu.Unlock() - if !reflect.DeepEqual(wireAuths, wantAuths) { - t.Fatalf("discovery wire credentials = %v, want %v", wireAuths, wantAuths) - } + testassert.Require(t, reflect.DeepEqual(wireAuths, wantAuths), "discovery wire credentials = %v, want %v", wireAuths, wantAuths) // The reads published nothing: the configuration revision is // unchanged, no notification frame carries a credential value, no // discovery cache was written, and the externally owned reference // is untouched and unmanaged. after, err := client.GetConfigurationWithResponse(ctx) - if err != nil || after.JSON200 == nil { - t.Fatalf("GetConfiguration after the reads: %v", err) - } - if after.JSON200.ConfigurationRevision != before.JSON200.ConfigurationRevision { - t.Fatalf("the discovery reads advanced the revision from %+v to %+v", before.JSON200.ConfigurationRevision, after.JSON200.ConfigurationRevision) - } + testassert.Require(t, err == nil && after.JSON200 != nil, "GetConfiguration after the reads: %v", err) + testassert.Equal(t, after.JSON200.ConfigurationRevision, before.JSON200.ConfigurationRevision) frames := make(chan string, 1) go func() { frame, ok, rerr := stream.readSSEFrame() @@ -1044,8 +875,9 @@ func TestProtocolServerDiscoverySuppliedKey(t *testing.T) { t.Fatalf("the discovery reads published a notification frame: %s", frame) case <-time.After(2 * time.Second): } - if _, serr := os.Stat(filepath.Join(r.config.loader.Home(), ".lightcode", "cache", "discovery", "custom.json")); !os.IsNotExist(serr) { - t.Fatalf("the discovery reads wrote a cache file: %v", serr) + { + _, serr := os.Stat(filepath.Join(r.config.loader.Home(), ".lightcode", "cache", "discovery", "custom.json")) + testassert.Require(t, os.IsNotExist(serr), "the discovery reads wrote a cache file: %v", serr) } if os.Getenv("MOUNTED_DISCOVERY_KEY") != external || r.managedEnv.IsManaged("MOUNTED_DISCOVERY_KEY") { t.Fatalf("external reference state = (%q, %v), want the exact value intact and unmanaged", os.Getenv("MOUNTED_DISCOVERY_KEY"), r.managedEnv.IsManaged("MOUNTED_DISCOVERY_KEY")) @@ -1073,44 +905,26 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { ctx := context.Background() configuration, err := client.GetConfigurationWithResponse(ctx) - if err != nil { - t.Fatalf("GetConfiguration: %v", err) - } - if configuration.JSON200 == nil { - t.Fatalf("configuration = %d with no typed body", configuration.HTTPResponse.StatusCode) - } - if configuration.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("configuration revision instance = %q", configuration.JSON200.ConfigurationRevision.InstanceId) - } + testassert.NoError(t, err) + testassert.Require(t, configuration.JSON200 != nil, "configuration = %d with no typed body", configuration.HTTPResponse.StatusCode) + testassert.Equal(t, configuration.JSON200.ConfigurationRevision.InstanceId, ps.instance) models, err := client.ListModelsWithResponse(ctx, &protocol.ListModelsParams{All: true}) - if err != nil || models.JSON200 == nil { - t.Fatalf("ListModels: %v", err) - } - if models.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("model list revision instance = %q", models.JSON200.ConfigurationRevision.InstanceId) - } + testassert.Require(t, err == nil && models.JSON200 != nil, "ListModels: %v", err) + testassert.Equal(t, models.JSON200.ConfigurationRevision.InstanceId, ps.instance) providers, err := client.ListProvidersWithResponse(ctx) - if err != nil || providers.JSON200 == nil { - t.Fatalf("ListProviders: %v", err) - } - if providers.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("provider list revision instance = %q", providers.JSON200.ConfigurationRevision.InstanceId) - } + testassert.Require(t, err == nil && providers.JSON200 != nil, "ListProviders: %v", err) + testassert.Equal(t, providers.JSON200.ConfigurationRevision.InstanceId, ps.instance) detail, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "prov"}) - if err != nil || detail.JSON200 == nil || detail.JSON200.Provider.Id != "prov" { - t.Fatalf("GetProviderDetail: %v", err) - } + testassert.Require(t, err == nil && detail.JSON200 != nil && detail.JSON200.Provider.Id == "prov", "GetProviderDetail: %v", err) name := "Renamed" edited, err := client.UpdateProviderDetailWithResponse(ctx, &protocol.UpdateProviderDetailParams{ProviderId: "prov"}, protocol.UpdateProviderDetailRequest{ Provider: protocol.ProviderEdit{Name: &name}, }) - if err != nil { - t.Fatalf("UpdateProviderDetail: %v", err) - } + testassert.NoError(t, err) if edited.JSON200 == nil || edited.JSON200.Result.Name != "Renamed" || edited.JSON200.ConfigurationRevision.InstanceId != ps.instance { body, _ := json.Marshal(edited.JSON200) t.Fatalf("provider edit = %s, want the renamed post-state under the qualified mutation envelope", body) @@ -1126,9 +940,7 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { BaseUrl: discovery.URL + "/v1", ApiKeyEnv: "", }) - if err != nil { - t.Fatalf("DiscoverProviderCandidates: %v", err) - } + testassert.NoError(t, err) if candidates.JSON200 == nil || len(*candidates.JSON200) != 2 { body, _ := json.Marshal(candidates.JSON200) t.Fatalf("discovery candidates = %s, want both stub models", body) @@ -1145,9 +957,7 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { "stub/m": {Name: ptrTo("Stub"), ContextWindow: &window}, }, }) - if err != nil { - t.Fatalf("CreateProvider: %v", err) - } + testassert.NoError(t, err) if added.JSON200 == nil || added.JSON200.Result.Id != "stub" { body, _ := json.Marshal(added.JSON200) t.Fatalf("provider add = %s (status %d)", body, added.HTTPResponse.StatusCode) @@ -1156,9 +966,7 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { // The provider's own model discovery read answers the same stub, // excluding its already-included usable model. modelCandidates, err := client.DiscoverProviderModelCandidatesWithResponse(ctx, &protocol.DiscoverProviderModelCandidatesParams{ProviderId: "stub"}) - if err != nil { - t.Fatalf("DiscoverProviderModelCandidates: %v", err) - } + testassert.NoError(t, err) if modelCandidates.JSON200 == nil || len(*modelCandidates.JSON200) != 1 || (*modelCandidates.JSON200)[0].Id != "fresh/m" { body, _ := json.Marshal(modelCandidates.JSON200) t.Fatalf("provider model candidates = %s, want only the not-yet-included candidate", body) @@ -1191,41 +999,29 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { t.Fatalf("disconnect = %s (status %d, raw %s), want the disconnected provider", body, disconnected.HTTPResponse.StatusCode, disconnected.Body) } connected, err := client.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: "keyed"}, protocol.ConnectRequest{ApiKey: ptrTo("mounted-secret")}) - if err != nil { - t.Fatalf("ConnectProvider: %v", err) - } + testassert.NoError(t, err) if connected.JSON200 == nil || !connected.JSON200.Result.Connected || connected.JSON200.ConfigurationRevision.InstanceId != ps.instance { body, _ := json.Marshal(connected.JSON200) t.Fatalf("connect = %s (status %d), want the connected provider under the qualified envelope", body, connected.HTTPResponse.StatusCode) } listed, err := client.ListProviderModelsWithResponse(ctx, &protocol.ListProviderModelsParams{ProviderId: "stub"}) - if err != nil || listed.JSON200 == nil { - t.Fatalf("ListProviderModels: %v", err) - } - if listed.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("provider model list revision instance = %q", listed.JSON200.ConfigurationRevision.InstanceId) - } + testassert.Require(t, err == nil && listed.JSON200 != nil, "ListProviderModels: %v", err) + testassert.Equal(t, listed.JSON200.ConfigurationRevision.InstanceId, ps.instance) hidden := true saved, err := client.UpdateProviderModelWithResponse(ctx, &protocol.UpdateProviderModelParams{ProviderId: "stub", ModelId: "stub/m"}, protocol.UpdateProviderModelRequest{ Model: protocol.ModelEdit{Hidden: &hidden}, }) - if err != nil || saved.JSON200 == nil || !saved.JSON200.Result.Hidden { - t.Fatalf("UpdateProviderModel: %v", err) - } + testassert.Require(t, err == nil && saved.JSON200 != nil && saved.JSON200.Result.Hidden, "UpdateProviderModel: %v", err) // The absent-override field reset publishes under the shared // successful-edit rule: the unchanged provider view returns under the // next generation. beforeReset, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "stub"}) - if err != nil || beforeReset.JSON200 == nil { - t.Fatalf("read before reset: %v", err) - } + testassert.Require(t, err == nil && beforeReset.JSON200 != nil, "read before reset: %v", err) reset, err := client.ResetProviderFieldWithResponse(ctx, protocol.ProviderField("name"), &protocol.ResetProviderFieldParams{ProviderId: "stub"}) - if err != nil { - t.Fatalf("ResetProviderField: %v", err) - } + testassert.NoError(t, err) if reset.JSON200 == nil { body, _ := json.Marshal(reset.JSON200) t.Fatalf("provider field reset = %s (status %d, %v)", body, reset.HTTPResponse.StatusCode, reset.JSONDefault) @@ -1235,32 +1031,21 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { other, _ := json.Marshal(beforeReset.JSON200.Provider) t.Fatalf("absent-override reset = %s, want the unchanged view %s", body, other) } - if reset.JSON200.ConfigurationRevision.Generation == beforeReset.JSON200.ConfigurationRevision.Generation { - t.Fatalf("absent-override reset kept generation %s, want the shared successful-edit publication", - reset.JSON200.ConfigurationRevision.Generation) - } + testassert.Require(t, reset.JSON200.ConfigurationRevision.Generation != beforeReset.JSON200.ConfigurationRevision.Generation, "absent-override reset kept generation %s, want the shared successful-edit publication", reset.JSON200.ConfigurationRevision.Generation) modelReset, err := client.ResetProviderModelFieldWithResponse(ctx, protocol.ModelField("name"), &protocol.ResetProviderModelFieldParams{ProviderId: "stub", ModelId: "stub/m"}) - if err != nil || modelReset.JSON200 == nil { - t.Fatalf("ResetProviderModelField: %v", err) - } + testassert.Require(t, err == nil && modelReset.JSON200 != nil, "ResetProviderModelField: %v", err) removed, err := client.DeleteProviderModelWithResponse(ctx, &protocol.DeleteProviderModelParams{ProviderId: "stub", ModelId: "stub/m"}) - if err != nil { - t.Fatalf("DeleteProviderModel: %v", err) - } + testassert.NoError(t, err) if removed.JSON200 == nil || removed.JSON200.Result != nil { body, _ := json.Marshal(removed.JSON200) t.Fatalf("model deletion = %s, want the null post-state", body) } - if removed.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("deletion revision instance = %q", removed.JSON200.ConfigurationRevision.InstanceId) - } + testassert.Equal(t, removed.JSON200.ConfigurationRevision.InstanceId, ps.instance) deleted, err := client.DeleteProviderDetailWithResponse(ctx, &protocol.DeleteProviderDetailParams{ProviderId: "stub"}) - if err != nil { - t.Fatalf("DeleteProviderDetail: %v", err) - } + testassert.NoError(t, err) if deleted.JSON200 == nil || deleted.JSON200.Result != nil { body, _ := json.Marshal(deleted.JSON200) t.Fatalf("provider deletion = %s, want the null post-state", body) @@ -1277,16 +1062,12 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { }, Plugins: protocol.PluginsSettings{}, }}) - if err != nil { - t.Fatalf("UpdateConfigurationSettings: %v", err) - } + testassert.NoError(t, err) if settings.JSON200 == nil || !settings.JSON200.Result.Sessions.AutoArchive || settings.JSON200.Result.Sessions.ArchiveAfterDays != archiveDays { body, _ := json.Marshal(settings.JSON200) t.Fatalf("settings write = %s, want the published settings result", body) } - if settings.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("settings revision instance = %q", settings.JSON200.ConfigurationRevision.InstanceId) - } + testassert.Equal(t, settings.JSON200.ConfigurationRevision.InstanceId, ps.instance) // The settings boundary negatives over the mounted generated client: // an unknown compiled ID, a malformed document string, and a valid @@ -1297,9 +1078,7 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { // compiled "core" declaration, so only the document boundary is // exercised. refusalSub, err := r.Subscribe(64) - if err != nil { - t.Fatalf("subscribe for settings refusals: %v", err) - } + testassert.NoError(t, err) defer refusalSub.Close() refusalFile, refusalGeneration, refusalWarnRev := runtimeMutationBaseline(t, r) for _, row := range []struct { @@ -1314,9 +1093,7 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { Sessions: protocol.SessionsSettings{AutoArchive: true, ArchiveAfterDays: archiveDays, DeleteAfterArchiveDays: archiveDays}, Plugins: row.plugins, }}) - if err != nil { - t.Fatalf("%s: UpdateConfigurationSettings: %v", row.name, err) - } + testassert.Require(t, err == nil, "%s: UpdateConfigurationSettings: %v", row.name, err) if response.HTTPResponse.StatusCode != http.StatusUnprocessableEntity || response.JSONDefault == nil || response.JSONDefault.Code != protocol.Configuration { body, _ := json.Marshal(response) t.Fatalf("%s = %s (status %d), want the typed configuration 422", row.name, body, response.HTTPResponse.StatusCode) @@ -1331,80 +1108,52 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { // the advanced revision. A malformed candidate leaves the previous // revision. before, err := client.GetConfigurationWithResponse(ctx) - if err != nil || before.JSON200 == nil { - t.Fatalf("read before reload: %v", err) - } + testassert.Require(t, err == nil && before.JSON200 != nil, "read before reload: %v", err) configPath := filepath.Join(r.dataDir, "config.json") - writeServiceFile(t, configPath, `{"providers":{"prov":{"transport":{"base_url":"https://prov.test/v1","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":4096},"m2":{"name":"M2","context_window":8192}}}}}`) + writeServiceFile(t, configPath, `{"providers":{"prov":`+nominalProvMember("https://prov.test/v1", "", `{"m":{"name":"M","context_window":4096},"m2":{"name":"M2","context_window":8192}}`)+`}}`) writeServiceFile(t, agents.PathForConfig(configPath), `{"solo":{"model":"prov/m2","system_prompt":"simple"},"worker":{"model":"prov/m","system_prompt":"simple"}}`) reload, err := client.ReloadConfigurationWithResponse(ctx) - if err != nil { - t.Fatalf("ReloadConfiguration: %v", err) - } - if reload.JSON200 == nil || reload.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("reload revision = %+v", reload.JSON200) - } + testassert.NoError(t, err) + testassert.Require(t, reload.JSON200 != nil && reload.JSON200.ConfigurationRevision.InstanceId == ps.instance, "reload revision = %+v", reload.JSON200) after, err := client.GetConfigurationWithResponse(ctx) - if err != nil || after.JSON200 == nil { - t.Fatalf("read after reload: %v", err) - } - if after.JSON200.ConfigurationRevision.Generation == before.JSON200.ConfigurationRevision.Generation { - t.Fatal("the hand-edited publication did not advance the configuration revision") - } + testassert.Require(t, err == nil && after.JSON200 != nil, "read after reload: %v", err) + testassert.Require(t, after.JSON200.ConfigurationRevision.Generation != before.JSON200.ConfigurationRevision.Generation, "the hand-edited publication did not advance the configuration revision") selectionChanged := false for _, agent := range after.JSON200.Agents { if agent.Name == "solo" && agent.Model == "prov/m2" { selectionChanged = true } } - if !selectionChanged { - t.Fatal("the hand-edited agents selection is not current after reload") - } + testassert.Require(t, selectionChanged, "the hand-edited agents selection is not current after reload") session, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: filepath.Join(r.dataDir, "reload-ws"), AgentType: "solo"}) - if err != nil || session.JSON201 == nil { - t.Fatalf("create after reload: %v", err) - } + testassert.Require(t, err == nil && session.JSON201 != nil, "create after reload: %v", err) submitted, err := client.SubmitSessionWithResponse(ctx, session.JSON201.SessionId, protocol.SubmitRequest{ OperationId: "op-reload", Mode: protocol.SubmitRequestModeRegular, Content: []protocol.ContentPart{textContentPart(t, "reloaded", nil)}, }) - if err != nil { - t.Fatalf("submit after reload: %v", err) - } + testassert.NoError(t, err) if submitted.JSON200 == nil || submitted.JSON200.Operation == nil { body, _ := json.Marshal(submitted.JSON200) t.Fatalf("submit after reload = %s (status %d), want the admitted Operation", body, submitted.HTTPResponse.StatusCode) } - if submitted.JSON200.Operation.Model != "prov/m2" { - t.Fatalf("next admitted Operation model = %q, want the reloaded selection %q", submitted.JSON200.Operation.Model, "prov/m2") - } + testassert.Equal(t, submitted.JSON200.Operation.Model, "prov/m2") malformed := func(body string) { t.Helper() configPath := filepath.Join(r.dataDir, "config.json") original, err := os.ReadFile(configPath) - if err != nil { - t.Fatalf("read config: %v", err) - } + testassert.NoError(t, err) writeServiceFile(t, configPath, body) defer func() { writeServiceFile(t, configPath, string(original)) }() response, err := client.ReloadConfigurationWithResponse(ctx) - if err != nil { - t.Fatalf("malformed reload: %v", err) - } - if response.JSONDefault == nil || response.JSONDefault.Code != protocol.Configuration { - t.Fatalf("malformed reload error = %+v, want the typed configuration failure", response.JSONDefault) - } + testassert.NoError(t, err) + testassert.Require(t, response.JSONDefault != nil && response.JSONDefault.Code == protocol.Configuration, "malformed reload error = %+v, want the typed configuration failure", response.JSONDefault) kept, err := client.GetConfigurationWithResponse(ctx) - if err != nil || kept.JSON200 == nil { - t.Fatalf("read after malformed reload: %v", err) - } - if kept.JSON200.ConfigurationRevision.Generation != after.JSON200.ConfigurationRevision.Generation { - t.Fatal("a malformed candidate changed the published revision") - } + testassert.Require(t, err == nil && kept.JSON200 != nil, "read after malformed reload: %v", err) + testassert.Require(t, kept.JSON200.ConfigurationRevision.Generation == after.JSON200.ConfigurationRevision.Generation, "a malformed candidate changed the published revision") } malformed(`{"providers":`) }) @@ -1418,11 +1167,7 @@ func TestProtocolServerConfigurationFamily(t *testing.T) { // key value. func TestProtocolServerProviderPostStateAndKeyRules(t *testing.T) { reserveEnvKey(t, "LIGHTCODE_GENERATED_API_KEY") - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) client := protocolClient(t, ps) @@ -1464,9 +1209,7 @@ func TestProtocolServerProviderPostStateAndKeyRules(t *testing.T) { t.Fatalf("managed key state = %q, want the exact supplied value", os.Getenv("LIGHTCODE_GENERATED_API_KEY")) } body, _ := json.Marshal(created.JSON200) - if strings.Contains(string(body), key) { - t.Fatalf("the create response carries key bytes: %s", body) - } + testassert.Require(t, !strings.Contains(string(body), key), "the create response carries key bytes: %s", body) // A supplied key on an explicitly named external variable refuses before // any configuration or cache side effect, leaving the variable intact. @@ -1478,18 +1221,15 @@ func TestProtocolServerProviderPostStateAndKeyRules(t *testing.T) { Models: map[string]protocol.ModelEdit{"m": {ContextWindow: ptrTo(4096)}}, ApiKey: &supplied, }) - if err != nil { - t.Fatalf("external-binding create transport: %v", err) - } + testassert.NoError(t, err) if refused.JSONDefault == nil || refused.JSONDefault.Code != protocol.Configuration { body, _ := json.Marshal(refused.JSON200) t.Fatalf("external-binding create = %s (status %d), want the typed configuration refusal", body, refused.HTTPResponse.StatusCode) } - if r.managedEnv.IsManaged("PATH") { - t.Fatal("the refused create managed the external variable") - } - if detail, derr := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "external"}); derr != nil || detail.JSONDefault == nil || detail.JSONDefault.Code != protocol.NotFound { - t.Fatalf("the refused create registered the provider: (%v, %+v)", derr, detail.JSONDefault) + testassert.Require(t, !r.managedEnv.IsManaged("PATH"), "the refused create managed the external variable") + { + detail, derr := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "external"}) + testassert.Require(t, derr == nil && detail.JSONDefault != nil && detail.JSONDefault.Code == protocol.NotFound, "the refused create registered the provider: (%v, %+v)", derr, detail.JSONDefault) } // The same credential rule crosses the mounted connect route for both @@ -1520,13 +1260,9 @@ func TestProtocolServerProviderPostStateAndKeyRules(t *testing.T) { // their successful writes so the refused connects are provably silent. configPath := filepath.Join(r.dataDir, "config.json") refusalBaseline, rerr := os.ReadFile(configPath) - if rerr != nil { - t.Fatalf("read owning config: %v", rerr) - } + testassert.NoError(t, rerr) baselineView, err := client.GetConfigurationWithResponse(ctx) - if err != nil || baselineView.JSON200 == nil { - t.Fatalf("read revision baseline: %v", err) - } + testassert.Require(t, err == nil && baselineView.JSON200 != nil, "read revision baseline: %v", err) suppliedConnect := "mounted-connect-secret" keylessConnect, err := client.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: "keylessc"}, protocol.ConnectRequest{ApiKey: &suppliedConnect}) if err != nil || keylessConnect.JSONDefault == nil || keylessConnect.JSONDefault.Code != protocol.Configuration { @@ -1540,21 +1276,12 @@ func TestProtocolServerProviderPostStateAndKeyRules(t *testing.T) { body, _ := json.Marshal(externalConnect.JSON200) t.Fatalf("external connect with a supplied key = %s (status %d, %+v), want the typed configuration refusal", body, externalConnect.HTTPResponse.StatusCode, externalConnect.JSONDefault) } - if r.managedEnv.IsManaged("PATH") { - t.Fatal("a refused connect managed the external variable") - } + testassert.Require(t, !r.managedEnv.IsManaged("PATH"), "a refused connect managed the external variable") afterConfig, rerr := os.ReadFile(configPath) - if rerr != nil || string(afterConfig) != string(refusalBaseline) { - t.Fatalf("a refused connect changed the owning config (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(afterConfig) == string(refusalBaseline), "a refused connect changed the owning config (%v)", rerr) currentView, err := client.GetConfigurationWithResponse(ctx) - if err != nil || currentView.JSON200 == nil { - t.Fatalf("read after refusals: %v", err) - } - if currentView.JSON200.ConfigurationRevision.Generation != baselineView.JSON200.ConfigurationRevision.Generation { - t.Fatalf("a refused connect advanced the revision %s -> %s", - baselineView.JSON200.ConfigurationRevision.Generation, currentView.JSON200.ConfigurationRevision.Generation) - } + testassert.Require(t, err == nil && currentView.JSON200 != nil, "read after refusals: %v", err) + testassert.Equal(t, currentView.JSON200.ConfigurationRevision.Generation, baselineView.JSON200.ConfigurationRevision.Generation) // A field outside the shared enum refuses invalid at the mounted owner // through both reset routes, before any write. @@ -1564,17 +1291,14 @@ func TestProtocolServerProviderPostStateAndKeyRules(t *testing.T) { } { refusal := rawProtocol(t, http.MethodDelete, protocolTarget(ps, target), ps.credential, "") defer refusal.Body.Close() - if refusal.StatusCode != http.StatusBadRequest { - t.Fatalf("reset %q = %d, want the typed invalid rejection 400", target, refusal.StatusCode) - } - if typed := decodeProtocolError(t, refusal); typed.Code != protocol.Invalid { - t.Fatalf("reset %q error = %+v, want invalid", target, typed) + testassert.Require(t, refusal.StatusCode == http.StatusBadRequest, "reset %q = %d, want the typed invalid rejection 400", target, refusal.StatusCode) + { + typed := decodeProtocolError(t, refusal) + testassert.Require(t, typed.Code == protocol.Invalid, "reset %q error = %+v, want invalid", target, typed) } } afterConfig, rerr = os.ReadFile(configPath) - if rerr != nil || string(afterConfig) != string(refusalBaseline) { - t.Fatalf("a refused reset changed the owning config (%v)", rerr) - } + testassert.Require(t, rerr == nil && string(afterConfig) == string(refusalBaseline), "a refused reset changed the owning config (%v)", rerr) } // TestProtocolServerAgentModelEdit pins the agent-model operation and the @@ -1584,32 +1308,22 @@ func TestProtocolServerAgentModelEdit(t *testing.T) { e := newOwnerEnv(t) writeServiceFile(t, agents.PathForConfig(e.configPath), `{"solo": {"model": "prov/m", "system_prompt": "simple"}, "..": {"model": "prov/m", "system_prompt": "simple"}}`) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) client := protocolClient(t, ps) ctx := context.Background() mutation, err := client.SetAgentTypeModelWithResponse(ctx, &protocol.SetAgentTypeModelParams{AgentType: ".."}, protocol.SetAgentTypeModelRequest{Model: "prov/m"}) - if err != nil { - t.Fatalf("SetAgentTypeModel: %v", err) - } + testassert.NoError(t, err) if mutation.JSON200 == nil || mutation.JSON200.Result.Name != ".." || mutation.JSON200.Result.Model != "prov/m" { body, _ := json.Marshal(mutation.JSON200) t.Fatalf("agent model edit = %s, want the %q roster entry's new model", body, "..") } - if mutation.JSON200.ConfigurationRevision.InstanceId != ps.instance { - t.Fatalf("agent mutation revision instance = %q", mutation.JSON200.ConfigurationRevision.InstanceId) - } + testassert.Equal(t, mutation.JSON200.ConfigurationRevision.InstanceId, ps.instance) unknown, err := client.SetAgentTypeModelWithResponse(ctx, &protocol.SetAgentTypeModelParams{AgentType: "ghost"}, protocol.SetAgentTypeModelRequest{Model: "prov/m"}) - if err != nil { - t.Fatalf("SetAgentTypeModel(unknown): %v", err) - } - if unknown.JSONDefault == nil || unknown.JSONDefault.Code != protocol.Invalid { - t.Fatalf("unknown agent type edit = %+v, want the typed invalid refusal", unknown.JSONDefault) - } + testassert.NoError(t, err) + testassert.Require(t, unknown.JSONDefault != nil && unknown.JSONDefault.Code == protocol.Invalid, "unknown agent type edit = %+v, want the typed invalid refusal", unknown.JSONDefault) } // TestProtocolServerRetainedFamily drives the retained pre-cutover snapshot @@ -1626,21 +1340,15 @@ func TestProtocolServerRetainedFamily(t *testing.T) { ctx := context.Background() workspace := filepath.Join(e.home, "legacy-ws") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir legacy workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) legacyID := "feedface" sessionDir := seedRetainedSession(t, r.config.loader.Home(), workspace, legacyID) file := filepath.Join(workspace, "partial.txt") - if err := os.WriteFile(file, []byte("p0"), 0o600); err != nil { - t.Fatalf("write retained preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("p0"), 0o600)) recordRetainedMutation(t, sessionDir, 1, file, file, []byte("p1")) turns, err := client.GetRetainedCodeSnapshotsWithResponse(ctx, &protocol.GetRetainedCodeSnapshotsParams{Workspace: workspace, SessionId: legacyID}) - if err != nil { - t.Fatalf("GetRetainedCodeSnapshots: %v", err) - } + testassert.NoError(t, err) if turns.JSON200 == nil || len(turns.JSON200.Turns) != 1 { body, _ := json.Marshal(turns.JSON200) t.Fatalf("retained turns = %s (status %d, %v), want one recorded turn", body, turns.HTTPResponse.StatusCode, turns.JSONDefault) @@ -1652,24 +1360,21 @@ func TestProtocolServerRetainedFamily(t *testing.T) { } revert, err := client.RevertRetainedCodeWithResponse(ctx, protocol.RetainedRevertRequest{Workspace: workspace, SessionId: legacyID, AfterTurn: 0}) - if err != nil { - t.Fatalf("RevertRetainedCode: %v", err) - } + testassert.NoError(t, err) if revert.JSON200 == nil || !slices.Contains(revert.JSON200.Restored, file) || revert.JSON200.Error != nil { body, _ := json.Marshal(revert.JSON200) t.Fatalf("retained revert = %s (status %d), want the completed restore", body, revert.HTTPResponse.StatusCode) } - if restored, err := os.ReadFile(file); err != nil || string(restored) != "p0" { - t.Fatalf("restored retained file = (%q, %v), want its recorded preimage", restored, err) + { + restored, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(restored) == "p0", "restored retained file = (%q, %v), want its recorded preimage", restored, err) } // The identity boundary is one rule: a target 32-hex Session ID is // refused on the retained route before any snapshot read. target := strings.Repeat("a", 32) wrong, err := client.GetRetainedCodeSnapshotsWithResponse(ctx, &protocol.GetRetainedCodeSnapshotsParams{Workspace: workspace, SessionId: target}) - if err != nil { - t.Fatalf("retained read with a target identity: %v", err) - } + testassert.NoError(t, err) if wrong.JSONDefault == nil || wrong.JSONDefault.Code != protocol.Invalid { body, _ := json.Marshal(wrong.JSONDefault) t.Fatalf("target identity on the retained route = %s, want the typed invalid refusal", body) @@ -1689,11 +1394,7 @@ func TestProtocolServerIdentityQueryRoundTrips(t *testing.T) { // start from the same external state and any original shell value is // restored after the test. reserveEnvKey(t, "IDENTITY_QUERY_KEY") - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, _ := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) client := protocolClient(t, ps) @@ -1706,38 +1407,26 @@ func TestProtocolServerIdentityQueryRoundTrips(t *testing.T) { Provider: protocol.ProviderEdit{BaseUrl: ptrTo("https://identity.test/v1")}, Models: map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, }) - if err != nil { - t.Fatalf("create %q: %v", id, err) - } + testassert.Require(t, err == nil, "create %q: %v", id, err) if added.JSON200 == nil || added.JSON200.Result.Id != id { body, _ := json.Marshal(added.JSON200) t.Fatalf("create %q = %s (status %d), want the provider under its exact ID", id, body, added.HTTPResponse.StatusCode) } detail, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: id}) - if err != nil || detail.JSON200 == nil || detail.JSON200.Provider.Id != id { - t.Fatalf("detail %q = (%v, %+v), want the exact identity round trip", id, err, detail.JSON200) - } + testassert.Require(t, err == nil && detail.JSON200 != nil && detail.JSON200.Provider.Id == id, "detail %q = (%v, %+v), want the exact identity round trip", id, err, detail.JSON200) edited, err := client.UpdateProviderDetailWithResponse(ctx, &protocol.UpdateProviderDetailParams{ProviderId: id}, protocol.UpdateProviderDetailRequest{ Provider: protocol.ProviderEdit{Name: ptrTo("renamed")}, }) - if err != nil || edited.JSON200 == nil || edited.JSON200.Result.Name != "renamed" { - t.Fatalf("edit %q = (%v, %+v)", id, err, edited.JSON200) - } + testassert.Require(t, err == nil && edited.JSON200 != nil && edited.JSON200.Result.Name == "renamed", "edit %q = (%v, %+v)", id, err, edited.JSON200) models, err := client.ListProviderModelsWithResponse(ctx, &protocol.ListProviderModelsParams{ProviderId: id}) - if err != nil || models.JSON200 == nil || len(models.JSON200.Models) != 1 || models.JSON200.Models[0].Id != "m" { - t.Fatalf("models %q = (%v, %+v)", id, err, models.JSON200) - } + testassert.Require(t, err == nil && models.JSON200 != nil && len(models.JSON200.Models) == 1 && models.JSON200.Models[0].Id == "m", "models %q = (%v, %+v)", id, err, models.JSON200) hidden := true saved, err := client.UpdateProviderModelWithResponse(ctx, &protocol.UpdateProviderModelParams{ProviderId: id, ModelId: "m"}, protocol.UpdateProviderModelRequest{ Model: protocol.ModelEdit{Hidden: &hidden}, }) - if err != nil || saved.JSON200 == nil || !saved.JSON200.Result.Hidden { - t.Fatalf("model edit %q = (%v, %+v)", id, err, saved.JSON200) - } + testassert.Require(t, err == nil && saved.JSON200 != nil && saved.JSON200.Result.Hidden, "model edit %q = (%v, %+v)", id, err, saved.JSON200) reset, err := client.ResetProviderModelFieldWithResponse(ctx, protocol.ModelField("name"), &protocol.ResetProviderModelFieldParams{ProviderId: id, ModelId: "m"}) - if err != nil || reset.JSON200 == nil { - t.Fatalf("model reset %q = (%v, %+v)", id, err, reset.JSON200) - } + testassert.Require(t, err == nil && reset.JSON200 != nil, "model reset %q = (%v, %+v)", id, err, reset.JSON200) connected, err := client.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: id}, protocol.ConnectRequest{}) if err != nil || connected.JSON200 == nil || connected.JSON200.Result.Id != id { body, _ := json.Marshal(connected.JSON200) @@ -1765,16 +1454,13 @@ func TestProtocolServerIdentityQueryRoundTrips(t *testing.T) { Models: map[string]protocol.ModelEdit{"m": {ContextWindow: &window}}, ApiKey: ptrTo("identity-secret"), }) - if err != nil || keyed.JSON200 == nil { - t.Fatalf("keyed create: %v", err) - } - if disconnected, err := client.DisconnectProviderWithResponse(ctx, &protocol.DisconnectProviderParams{ProviderId: "?keyed"}); err != nil || disconnected.JSON200 == nil { - t.Fatalf("keyed disconnect: %v", err) + testassert.Require(t, err == nil && keyed.JSON200 != nil, "keyed create: %v", err) + { + disconnected, err := client.DisconnectProviderWithResponse(ctx, &protocol.DisconnectProviderParams{ProviderId: "?keyed"}) + testassert.Require(t, err == nil && disconnected.JSON200 != nil, "keyed disconnect: %v", err) } connected, err := client.ConnectProviderWithResponse(ctx, &protocol.ConnectProviderParams{ProviderId: "?keyed"}, protocol.ConnectRequest{ApiKey: ptrTo("identity-secret")}) - if err != nil || connected.JSON200 == nil || !connected.JSON200.Result.Connected { - t.Fatalf("keyed connect = (%v, %+v)", err, connected.JSON200) - } + testassert.Require(t, err == nil && connected.JSON200 != nil && connected.JSON200.Result.Connected, "keyed connect = (%v, %+v)", err, connected.JSON200) // A slash-containing model ID stays a valid query value. slashed, err := client.CreateProviderWithResponse(ctx, protocol.CreateProviderRequest{ @@ -1782,21 +1468,15 @@ func TestProtocolServerIdentityQueryRoundTrips(t *testing.T) { Provider: protocol.ProviderEdit{BaseUrl: ptrTo("https://identity.test/v1")}, Models: map[string]protocol.ModelEdit{"org/model": {ContextWindow: &window}}, }) - if err != nil || slashed.JSON200 == nil { - t.Fatalf("slash-model create: %v", err) - } + testassert.Require(t, err == nil && slashed.JSON200 != nil, "slash-model create: %v", err) slashSaved, err := client.UpdateProviderModelWithResponse(ctx, &protocol.UpdateProviderModelParams{ProviderId: "slashmodel", ModelId: "org/model"}, protocol.UpdateProviderModelRequest{ Model: protocol.ModelEdit{Name: ptrTo("Slashed")}, }) - if err != nil || slashSaved.JSON200 == nil || slashSaved.JSON200.Result.Id != "org/model" { - t.Fatalf("slash model edit = (%v, %+v)", err, slashSaved.JSON200) - } + testassert.Require(t, err == nil && slashSaved.JSON200 != nil && slashSaved.JSON200.Result.Id == "org/model", "slash model edit = (%v, %+v)", err, slashSaved.JSON200) // An empty required identity value is the one invalid-set rule. empty, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: ""}) - if err != nil { - t.Fatalf("empty identity read: %v", err) - } + testassert.NoError(t, err) if empty.JSONDefault == nil || empty.JSONDefault.Code != protocol.Invalid { body, _ := json.Marshal(empty.JSONDefault) t.Fatalf("empty identity = %s (status %d), want the typed invalid refusal", body, empty.HTTPResponse.StatusCode) @@ -1811,9 +1491,7 @@ func refusedAnswer(t *testing.T, resp interface { StatusCode() int }, err error) (int, protocol.Error) { t.Helper() - if err != nil { - t.Fatalf("generated client call: %v", err) - } + testassert.NoError(t, err) if typed := resp.GetJSONDefault(); typed != nil { return resp.StatusCode(), *typed } @@ -1852,9 +1530,7 @@ func TestProtocolServerMissingMetadata(t *testing.T) { t.Fatalf("existing model reset = %s (status %d, %v), want the published reset", body, spareReset.HTTPResponse.StatusCode, err) } detail, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "prov"}) - if err != nil || detail.JSON200 == nil || detail.JSON200.Provider.Id != "prov" { - t.Fatalf("existing provider read = (%v, %+v), want the provider view", err, detail.JSON200) - } + testassert.Require(t, err == nil && detail.JSON200 != nil && detail.JSON200.Provider.Id == "prov", "existing provider read = (%v, %+v), want the provider view", err, detail.JSON200) provReset, err := client.ResetProviderFieldWithResponse(ctx, protocol.ProviderFieldName, &protocol.ResetProviderFieldParams{ProviderId: "prov"}) if err != nil || provReset.JSON200 == nil { body, _ := json.Marshal(provReset.JSON200) @@ -1871,9 +1547,7 @@ func TestProtocolServerMissingMetadata(t *testing.T) { // latest owning bytes, the published generation, the warning revision // and the event stream through its wrapped expected class. subscription, err := r.Subscribe(64) - if err != nil { - t.Fatalf("subscribe for the refusal baseline: %v", err) - } + testassert.NoError(t, err) defer subscription.Close() refused := []struct { name string @@ -1919,9 +1593,7 @@ func TestProtocolServerMissingMetadata(t *testing.T) { t.Run(row.name, func(t *testing.T) { before, generation, warnRev := runtimeMutationBaseline(t, r) status, typed := row.call(t) - if typed.Code != row.code || status != row.status { - t.Fatalf("%s = (%d, %+v), want the typed (%d, %s)", row.name, status, typed, row.status, row.code) - } + testassert.Require(t, typed.Code == row.code && status == row.status, "%s = (%d, %+v), want the typed (%d, %s)", row.name, status, typed, row.status, row.code) assertRuntimeMutationRefused(t, r, subscription, before, generation, warnRev, fmt.Errorf("%s: %w", row.name, row.want), row.want) }) diff --git a/runtime/protocol_transport_test.go b/runtime/protocol_transport_test.go index cfab3ba3..2e722e66 100644 --- a/runtime/protocol_transport_test.go +++ b/runtime/protocol_transport_test.go @@ -18,6 +18,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/catalog" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/protocol" ) @@ -37,36 +38,25 @@ func TestProtocolServerAuthenticationOrder(t *testing.T) { ctx := context.Background() absent := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/health"), "", "") - if absent.StatusCode != http.StatusUnauthorized { - t.Fatalf("absent credential = %d, want 401", absent.StatusCode) - } + testassert.Equal(t, absent.StatusCode, http.StatusUnauthorized) typed := decodeProtocolError(t, absent) - if typed.Code != protocol.Unauthorized || strings.Contains(typed.Message, ps.credential) { - t.Fatalf("absent credential error = %+v, want unauthorized with no credential echo", typed) - } + testassert.Require(t, typed.Code == protocol.Unauthorized && !strings.Contains(typed.Message, ps.credential), "absent credential error = %+v, want unauthorized with no credential echo", typed) wrong := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/health"), "wrong-token", "") - if wrong.StatusCode != http.StatusUnauthorized { - t.Fatalf("wrong credential = %d, want 401", wrong.StatusCode) - } + testassert.Equal(t, wrong.StatusCode, http.StatusUnauthorized) scheme := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/health"), "Basic dXNlcjpwYXNz", "") - if scheme.StatusCode != http.StatusUnauthorized { - t.Fatalf("foreign scheme = %d, want 401", scheme.StatusCode) - } + testassert.Equal(t, scheme.StatusCode, http.StatusUnauthorized) // Authentication precedes parameter binding: a request missing its // required query member is answered unauthorized while the // credential is absent, and invalid once it is present. bindless := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/providers/detail"), "", "") - if bindless.StatusCode != http.StatusUnauthorized { - t.Fatalf("unauthenticated missing query = %d, want the 401 gate answer", bindless.StatusCode) - } + testassert.Equal(t, bindless.StatusCode, http.StatusUnauthorized) binding := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/providers/detail"), ps.credential, "") - if binding.StatusCode != http.StatusBadRequest { - t.Fatalf("missing query = %d, want the typed binding failure 400", binding.StatusCode) - } - if typed := decodeProtocolError(t, binding); typed.Code != protocol.Invalid { - t.Fatalf("missing query error = %+v, want invalid", typed) + testassert.Equal(t, binding.StatusCode, http.StatusBadRequest) + { + typed := decodeProtocolError(t, binding) + testassert.Require(t, typed.Code == protocol.Invalid, "missing query error = %+v, want invalid", typed) } // Authentication precedes every producer effect: a creation with a @@ -74,50 +64,35 @@ func TestProtocolServerAuthenticationOrder(t *testing.T) { workspace := filepath.Join(e.home, "auth") creation := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/sessions"), "bad", fmt.Sprintf(`{"workspace":%q,"agent_type":"solo"}`, workspace)) - if creation.StatusCode != http.StatusUnauthorized { - t.Fatalf("unauthenticated creation = %d, want 401", creation.StatusCode) - } + testassert.Equal(t, creation.StatusCode, http.StatusUnauthorized) sessions, err := r.listSessions(ctx, protocol.ListSessionsParams{Workspace: workspace, Lifecycle: "open"}) - if err != nil { - t.Fatalf("list after refused creation: %v", err) - } - if len(sessions) != 0 { - t.Fatalf("refused creation left %d sessions, want none", len(sessions)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(sessions), 0) // The root catch-all rides the same gate: an unknown route without a // credential is unauthorized, not a typed route miss. catchAll := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/nope"), "", "") - if catchAll.StatusCode != http.StatusUnauthorized { - t.Fatalf("unauthenticated unknown route = %d, want the 401 gate answer", catchAll.StatusCode) - } + testassert.Equal(t, catchAll.StatusCode, http.StatusUnauthorized) // The events stream authenticates before it subscribes: an // unauthenticated request answers the typed 401 and creates no // subscription. unauthenticatedEvents := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/events"), "", "") - if unauthenticatedEvents.StatusCode != http.StatusUnauthorized { - t.Fatalf("unauthenticated events = %d, want the typed 401", unauthenticatedEvents.StatusCode) - } - if typed := decodeProtocolError(t, unauthenticatedEvents); typed.Code != protocol.Unauthorized { - t.Fatalf("unauthenticated events error = %+v, want unauthorized", typed) - } - if subscribers := subscriberCount(r); subscribers != 0 { - t.Fatalf("the unauthenticated events request created %d subscriptions", subscribers) + testassert.Equal(t, unauthenticatedEvents.StatusCode, http.StatusUnauthorized) + { + typed := decodeProtocolError(t, unauthenticatedEvents) + testassert.Require(t, typed.Code == protocol.Unauthorized, "unauthenticated events error = %+v, want unauthorized", typed) } + testassert.Equal(t, subscriberCount(r), 0) // Version compatibility is connection-side: a bogus per-request // version header changes nothing on the authenticated health read. versioned, err := http.NewRequest(http.MethodGet, protocolTarget(ps, "/v1/health"), nil) - if err != nil { - t.Fatalf("build versioned health request: %v", err) - } + testassert.NoError(t, err) versioned.Header.Set("Authorization", "Bearer "+ps.credential) versioned.Header.Set("X-Lightcode-Version", "999") versionedResponse, err := (&http.Client{Timeout: 30 * time.Second}).Do(versioned) - if err != nil { - t.Fatalf("versioned health: %v", err) - } + testassert.NoError(t, err) if versionedResponse.StatusCode != http.StatusOK { versionedResponse.Body.Close() t.Fatalf("versioned health = %d, want 200", versionedResponse.StatusCode) @@ -128,9 +103,7 @@ func TestProtocolServerAuthenticationOrder(t *testing.T) { t.Fatalf("decode versioned health: %v", err) } versionedResponse.Body.Close() - if health.ProtocolVersion != protocol.N2 { - t.Fatalf("versioned health protocol = %q, want %q", health.ProtocolVersion, protocol.N2) - } + testassert.Equal(t, health.ProtocolVersion, protocol.N2) }) } @@ -155,19 +128,14 @@ func TestProtocolServerStrictBodies(t *testing.T) { t.Helper() response := create(body) defer response.Body.Close() - if response.StatusCode != http.StatusBadRequest { - t.Fatalf("%s = %d, want the typed invalid rejection 400", name, response.StatusCode) - } - if typed := decodeProtocolError(t, response); typed.Code != protocol.Invalid { - t.Fatalf("%s error = %+v, want invalid", name, typed) + testassert.Require(t, response.StatusCode == http.StatusBadRequest, "%s = %d, want the typed invalid rejection 400", name, response.StatusCode) + { + typed := decodeProtocolError(t, response) + testassert.Require(t, typed.Code == protocol.Invalid, "%s error = %+v, want invalid", name, typed) } sessions, err := r.listSessions(ctx, protocol.ListSessionsParams{Workspace: workspace, Lifecycle: "open"}) - if err != nil { - t.Fatalf("list after %s: %v", name, err) - } - if len(sessions) != 0 { - t.Fatalf("%s created a session, want no mutation before validation", name) - } + testassert.Require(t, err == nil, "list after %s: %v", name, err) + testassert.Require(t, len(sessions) == 0, "%s created a session, want no mutation before validation", name) } reject("unknown top member", fmt.Sprintf(`{"workspace":%q,"agent_type":"solo","note":"x"}`, workspace)) reject("missing required member", fmt.Sprintf(`{"workspace":%q}`, workspace)) @@ -184,18 +152,17 @@ func TestProtocolServerStrictBodies(t *testing.T) { // validator. A valid session exists for these rows. client := protocolClient(t, ps) created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: workspace, AgentType: "solo"}) - if err != nil || created.JSON201 == nil { - t.Fatalf("create: %v", err) - } + testassert.Require(t, err == nil && created.JSON201 != nil, "create: %v", err) session := created.JSON201.SessionId gate := make(chan struct{}) e.server.setHold(gate) defer close(gate) - if _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ - OperationId: "op-1", Mode: "regular", - Content: []protocol.ContentPart{textContentPart(t, "first", nil)}, - }); err != nil { - t.Fatalf("gated submit: %v", err) + { + _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ + OperationId: "op-1", Mode: "regular", + Content: []protocol.ContentPart{textContentPart(t, "first", nil)}, + }) + testassert.NoError(t, err) } awaitModelArrival(t, e) @@ -214,13 +181,12 @@ func TestProtocolServerStrictBodies(t *testing.T) { response.Body.Close() t.Fatalf("%s = %d, want the typed invalid rejection 400", name, response.StatusCode) } - if typed := decodeProtocolError(t, response); typed.Code != protocol.Invalid { - t.Fatalf("%s error = %+v, want the typed invalid body", name, typed) + { + typed := decodeProtocolError(t, response) + testassert.Require(t, typed.Code == protocol.Invalid, "%s error = %+v, want the typed invalid body", name, typed) } after := sessionViewThroughRuntime(t, r, session) - if !reflect.DeepEqual(before, after) { - t.Fatalf("%s changed the Session snapshot (pending queues, facts, or revisions)", name) - } + testassert.Require(t, reflect.DeepEqual(before, after), "%s changed the Session snapshot (pending queues, facts, or revisions)", name) } rejectSubmit("unknown union member", `{"operation_id":"op-2","mode":"queued","content":[{"kind":"text","text":"x","forged":1}]}`) rejectSubmit("unknown union discriminator", `{"operation_id":"op-2","mode":"queued","content":[{"kind":"pdf","text":"x"}]}`) @@ -244,9 +210,7 @@ func TestProtocolServerStrictBodies(t *testing.T) { opaqueContentPart(t, "vendor_wire"), }, }) - if err != nil { - t.Fatalf("valid image_url/opaque submit: %v", err) - } + testassert.NoError(t, err) if accepted.JSON200 == nil || accepted.JSON200.Disposition != protocol.SubmitResultDispositionQueued { body, _ := json.Marshal(accepted.JSON200) t.Fatalf("valid image_url/opaque submit = %s (status %d), want the buffered disposition", body, accepted.HTTPResponse.StatusCode) @@ -260,46 +224,40 @@ func TestProtocolServerStrictBodies(t *testing.T) { } nested := providerCreate(`{"id":"newp","provider":{"base_url":"https://x.test/v1"},"models":{"m":{"context_window":4096,"bogus":1}}}`) defer nested.Body.Close() - if nested.StatusCode != http.StatusBadRequest { - t.Fatalf("unknown nested model member = %d, want 400", nested.StatusCode) - } + testassert.Equal(t, nested.StatusCode, http.StatusBadRequest) nullModel := providerCreate(`{"id":"newp","provider":{"base_url":"https://x.test/v1"},"models":{"m":null}}`) defer nullModel.Body.Close() - if nullModel.StatusCode != http.StatusBadRequest { - t.Fatalf("null typed map value = %d, want 400", nullModel.StatusCode) - } + testassert.Equal(t, nullModel.StatusCode, http.StatusBadRequest) // The provider-create typed model map carries otherwise valid // members and an invalid generated SystemRole: the nested enum // validator refuses it before any write. invalidEnum := providerCreate(`{"id":"newp","provider":{"base_url":"https://x.test/v1"},"models":{"m":{"context_window":4096,"system_role":"boss"}}}`) defer invalidEnum.Body.Close() - if invalidEnum.StatusCode != http.StatusBadRequest { - t.Fatalf("invalid nested SystemRole = %d, want the typed invalid rejection 400", invalidEnum.StatusCode) - } - if typed := decodeProtocolError(t, invalidEnum); typed.Code != protocol.Invalid { - t.Fatalf("invalid nested SystemRole error = %+v, want invalid", typed) + testassert.Equal(t, invalidEnum.StatusCode, http.StatusBadRequest) + { + typed := decodeProtocolError(t, invalidEnum) + testassert.Require(t, typed.Code == protocol.Invalid, "invalid nested SystemRole error = %+v, want invalid", typed) } // The same typed map carries an invalid element inside the real // input_modalities slice; its valid sibling slice element is // accepted. invalidModalities := providerCreate(`{"id":"newp","provider":{"base_url":"https://x.test/v1"},"models":{"m":{"context_window":4096,"input_modalities":["text","hologram"]}}}`) defer invalidModalities.Body.Close() - if invalidModalities.StatusCode != http.StatusBadRequest { - t.Fatalf("invalid input_modalities element = %d, want the typed invalid rejection 400", invalidModalities.StatusCode) - } - if typed := decodeProtocolError(t, invalidModalities); typed.Code != protocol.Invalid { - t.Fatalf("invalid input_modalities element error = %+v, want invalid", typed) + testassert.Equal(t, invalidModalities.StatusCode, http.StatusBadRequest) + { + typed := decodeProtocolError(t, invalidModalities) + testassert.Require(t, typed.Code == protocol.Invalid, "invalid input_modalities element error = %+v, want invalid", typed) } validModalities := providerCreate(`{"id":"validmods","provider":{"base_url":"https://x.test/v1"},"models":{"m":{"context_window":4096,"input_modalities":["text"]}}}`) defer validModalities.Body.Close() - if validModalities.StatusCode != http.StatusOK { - t.Fatalf("valid input_modalities sibling = %d, want 200", validModalities.StatusCode) + testassert.Equal(t, validModalities.StatusCode, http.StatusOK) + { + _, err := r.getProvider(ctx, "validmods") + testassert.NoError(t, err) } - if _, err := r.getProvider(ctx, "validmods"); err != nil { - t.Fatalf("the valid input_modalities sibling was not written: %v", err) - } - if _, err := r.getProvider(ctx, "newp"); !errors.Is(err, catalog.ErrUnknownProvider) { - t.Fatalf("rejected creation wrote a provider: %v", err) + { + _, err := r.getProvider(ctx, "newp") + testassert.Require(t, errors.Is(err, catalog.ErrUnknownProvider), "rejected creation wrote a provider: %v", err) } // The retired provider controls follow the shared strict boundary: @@ -308,46 +266,36 @@ func TestProtocolServerStrictBodies(t *testing.T) { // mutation, leaving the addressed provider and its revision // untouched. beforeRetired, err := r.getProvider(ctx, "validmods") - if err != nil { - t.Fatalf("read provider before the retired-control rows: %v", err) - } + testassert.NoError(t, err) retiredEdit := func(name, body string) { t.Helper() response := rawProtocol(t, http.MethodPut, protocolTarget(ps, "/v1/providers/detail?provider_id=validmods"), ps.credential, body) defer response.Body.Close() - if response.StatusCode != http.StatusBadRequest { - t.Fatalf("%s = %d, want the typed invalid rejection 400", name, response.StatusCode) - } - if typed := decodeProtocolError(t, response); typed.Code != protocol.Invalid { - t.Fatalf("%s error = %+v, want invalid", name, typed) + testassert.Require(t, response.StatusCode == http.StatusBadRequest, "%s = %d, want the typed invalid rejection 400", name, response.StatusCode) + { + typed := decodeProtocolError(t, response) + testassert.Require(t, typed.Code == protocol.Invalid, "%s error = %+v, want invalid", name, typed) } } retiredEdit("retired edit max_tokens_field", `{"provider":{"max_tokens_field":"max_completion_tokens"}}`) retiredEdit("retired edit options", `{"provider":{"options":{"retries":3}}}`) retiredReset := rawProtocol(t, http.MethodDelete, protocolTarget(ps, "/v1/providers/fields/options?provider_id=validmods"), ps.credential, "") defer retiredReset.Body.Close() - if retiredReset.StatusCode != http.StatusBadRequest { - t.Fatalf("retired reset options = %d, want the typed invalid rejection 400", retiredReset.StatusCode) - } - if typed := decodeProtocolError(t, retiredReset); typed.Code != protocol.Invalid { - t.Fatalf("retired reset options error = %+v, want invalid", typed) + testassert.Equal(t, retiredReset.StatusCode, http.StatusBadRequest) + { + typed := decodeProtocolError(t, retiredReset) + testassert.Require(t, typed.Code == protocol.Invalid, "retired reset options error = %+v, want invalid", typed) } afterRetired, err := r.getProvider(ctx, "validmods") - if err != nil { - t.Fatalf("read provider after the retired-control rows: %v", err) - } - if !reflect.DeepEqual(beforeRetired, afterRetired) { - t.Fatalf("a retired-control refusal changed the provider:\nbefore %+v\nafter %+v", beforeRetired, afterRetired) - } + testassert.NoError(t, err) + testassert.Require(t, reflect.DeepEqual(beforeRetired, afterRetired), "a retired-control refusal changed the provider:\nbefore %+v\nafter %+v", beforeRetired, afterRetired) // A null header value inside a declared typed map is not an opaque // map member: the discovery route rejects it before any fetch. nullHeader := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers/discover"), ps.credential, `{"base_url":"https://x.test/v1","api_key_env":"","headers":{"h":null}}`) defer nullHeader.Body.Close() - if nullHeader.StatusCode != http.StatusBadRequest { - t.Fatalf("null header value = %d, want 400", nullHeader.StatusCode) - } + testassert.Equal(t, nullHeader.StatusCode, http.StatusBadRequest) // The controls are argument-free POSTs: no request-body schema // exists, so any body is ignored and the addressed Session decides. @@ -365,15 +313,11 @@ func TestProtocolServerStrictBodies(t *testing.T) { } { response := interrupt(session, body) response.Body.Close() - if response.StatusCode != http.StatusNoContent { - t.Fatalf("%s = %d, want the accepted 204", name, response.StatusCode) - } + testassert.Require(t, response.StatusCode == http.StatusNoContent, "%s = %d, want the accepted 204", name, response.StatusCode) } unknown := interrupt("0123456789abcdef0123456789abcdef", "") unknown.Body.Close() - if unknown.StatusCode != http.StatusNotFound { - t.Fatalf("unknown control target = %d, want the typed not-found 404", unknown.StatusCode) - } + testassert.Equal(t, unknown.StatusCode, http.StatusNotFound) }) } @@ -392,28 +336,25 @@ func TestProtocolServerRoutingDispositions(t *testing.T) { client := protocolClient(t, ps) ctx := context.Background() created, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{Workspace: filepath.Join(e.home, "unclean"), AgentType: "solo"}) - if err != nil || created.JSON201 == nil { - t.Fatalf("create session for the unclean-path row: %v", err) - } + testassert.Require(t, err == nil && created.JSON201 != nil, "create session for the unclean-path row: %v", err) session := created.JSON201.SessionId unknown := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/nope"), ps.credential, "") - if unknown.StatusCode != http.StatusNotFound { - t.Fatalf("unknown route = %d, want the typed 404", unknown.StatusCode) + testassert.Equal(t, unknown.StatusCode, http.StatusNotFound) + { + typed := decodeProtocolError(t, unknown) + testassert.Require(t, typed.Code == protocol.NotFound, "unknown route error = %+v, want not_found", typed) } - if typed := decodeProtocolError(t, unknown); typed.Code != protocol.NotFound { - t.Fatalf("unknown route error = %+v, want not_found", typed) - } - if media := unknown.Header.Get("Content-Type"); !strings.Contains(media, "json") { - t.Fatalf("unknown route content type = %q, want the typed JSON body", media) + { + media := unknown.Header.Get("Content-Type") + testassert.Require(t, strings.Contains(media, "json"), "unknown route content type = %q, want the typed JSON body", media) } wrongMethod := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/health"), ps.credential, "") - if wrongMethod.StatusCode != http.StatusNotFound { - t.Fatalf("wrong method = %d, want the typed 404", wrongMethod.StatusCode) - } - if typed := decodeProtocolError(t, wrongMethod); typed.Code != protocol.NotFound { - t.Fatalf("wrong method error = %+v, want not_found", typed) + testassert.Equal(t, wrongMethod.StatusCode, http.StatusNotFound) + { + typed := decodeProtocolError(t, wrongMethod) + testassert.Require(t, typed.Code == protocol.NotFound, "wrong method error = %+v, want not_found", typed) } // The removed standalone Session reads are unregistered routes: each @@ -424,11 +365,10 @@ func TestProtocolServerRoutingDispositions(t *testing.T) { "/v1/sessions/" + session + "/usage", } { response := rawProtocol(t, http.MethodGet, protocolTarget(ps, removed), ps.credential, "") - if response.StatusCode != http.StatusNotFound { - t.Fatalf("removed route %s = %d, want the typed 404", removed, response.StatusCode) - } - if typed := decodeProtocolError(t, response); typed.Code != protocol.NotFound { - t.Fatalf("removed route %s error = %+v, want not_found", removed, typed) + testassert.Require(t, response.StatusCode == http.StatusNotFound, "removed route %s = %d, want the typed 404", removed, response.StatusCode) + { + typed := decodeProtocolError(t, response) + testassert.Require(t, typed.Code == protocol.NotFound, "removed route %s error = %+v, want not_found", removed, typed) } } @@ -439,18 +379,12 @@ func TestProtocolServerRoutingDispositions(t *testing.T) { } unclean := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1//health"), ps.credential, "") - if unclean.StatusCode != http.StatusTemporaryRedirect { - t.Fatalf("unclean path = %d, want the transport-level 307", unclean.StatusCode) - } - if location := unclean.Header.Get("Location"); location != "/v1/health" { - t.Fatalf("unclean redirect location = %q, want the cleaned path", location) - } + testassert.Equal(t, unclean.StatusCode, http.StatusTemporaryRedirect) + testassert.Equal(t, unclean.Header.Get("Location"), "/v1/health") // The clean operation answers with a clean parameter, never an // untyped answer. healthy := rawProtocol(t, http.MethodGet, protocolTarget(ps, "/v1/health"), ps.credential, "") - if healthy.StatusCode != http.StatusOK { - t.Fatalf("clean health = %d, want 200", healthy.StatusCode) - } + testassert.Equal(t, healthy.StatusCode, http.StatusOK) healthy.Body.Close() // An unclean Session path is transport-redirected to its clean @@ -458,22 +392,14 @@ func TestProtocolServerRoutingDispositions(t *testing.T) { // hydration handler and reads exactly the addressed Session. uncleanSessionPath := "/v1//sessions/" + session + "/hydration" uncleanSession := rawProtocol(t, http.MethodGet, protocolTarget(ps, uncleanSessionPath), ps.credential, "") - if uncleanSession.StatusCode != http.StatusTemporaryRedirect { - t.Fatalf("unclean Session path = %d, want the transport-level 307", uncleanSession.StatusCode) - } - if location := uncleanSession.Header.Get("Location"); location != "/v1/sessions/"+session+"/hydration" { - t.Fatalf("unclean Session redirect location = %q, want the cleaned Session path", location) - } + testassert.Equal(t, uncleanSession.StatusCode, http.StatusTemporaryRedirect) + testassert.Equal(t, uncleanSession.Header.Get("Location"), "/v1/sessions/"+session+"/hydration") uncleanSession.Body.Close() redirectRequest, err := http.NewRequest(http.MethodGet, protocolTarget(ps, uncleanSessionPath), nil) - if err != nil { - t.Fatalf("build unclean Session request: %v", err) - } + testassert.NoError(t, err) redirectRequest.Header.Set("Authorization", "Bearer "+ps.credential) redirected, err := (&http.Client{Timeout: 30 * time.Second}).Do(redirectRequest) - if err != nil { - t.Fatalf("follow the unclean Session redirect: %v", err) - } + testassert.NoError(t, err) if redirected.StatusCode != http.StatusOK { redirected.Body.Close() t.Fatalf("followed unclean Session path = %d, want the clean 200", redirected.StatusCode) @@ -484,36 +410,28 @@ func TestProtocolServerRoutingDispositions(t *testing.T) { t.Fatalf("decode the followed Session: %v", err) } redirected.Body.Close() - if followedSession.Session.SessionId != session { - t.Fatalf("followed Session = %q, want the addressed %q", followedSession.Session.SessionId, session) - } + testassert.Equal(t, followedSession.Session.SessionId, session) // The asterisk-form OPTIONS request is answered by the server layer // before the routing tree, the authentication gate, and every // handler: it produces no typed protocol answer. conn, err := net.Dial("tcp", strings.TrimPrefix(ps.Endpoint(), "http://")) - if err != nil { - t.Fatalf("dial for OPTIONS: %v", err) - } + testassert.NoError(t, err) defer conn.Close() - if _, err := conn.Write([]byte("OPTIONS * HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n")); err != nil { - t.Fatalf("write OPTIONS: %v", err) + { + _, err := conn.Write([]byte("OPTIONS * HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n")) + testassert.NoError(t, err) } buffer := make([]byte, 512) read, err := conn.Read(buffer) - if err != nil && read == 0 { - t.Fatalf("read OPTIONS answer: %v", err) - } + testassert.Require(t, err == nil || read != 0, "read OPTIONS answer: %v", err) answer := string(buffer[:read]) statusLine, _, _ := strings.Cut(answer, "\r\n") - if !strings.Contains(statusLine, "200") { - t.Fatalf("OPTIONS * status = %q, want the stdlib transport-level 200", statusLine) - } - if !strings.Contains(answer, "Content-Length: 0") { - t.Fatalf("OPTIONS * headers = %q, want the empty transport answer", answer) - } - if _, body, terminated := strings.Cut(answer, "\r\n\r\n"); terminated && body != "" { - t.Fatalf("OPTIONS * body = %q, want the empty transport answer", body) + testassert.Require(t, strings.Contains(statusLine, "200"), "OPTIONS * status = %q, want the stdlib transport-level 200", statusLine) + testassert.Require(t, strings.Contains(answer, "Content-Length: 0"), "OPTIONS * headers = %q, want the empty transport answer", answer) + { + _, body, terminated := strings.Cut(answer, "\r\n\r\n") + testassert.Require(t, !terminated || body == "", "OPTIONS * body = %q, want the empty transport answer", body) } }) } @@ -532,21 +450,19 @@ func TestProtocolServerErrorClasses(t *testing.T) { // A well-formed but absent session identity answers not_found. unused := newLifecycleID(t) - if missing, err := client.GetSessionHydrationWithResponse(ctx, unused); err != nil || missing.JSONDefault == nil || - missing.JSONDefault.Code != protocol.NotFound || missing.HTTPResponse.StatusCode != http.StatusNotFound { - t.Fatalf("unknown session = (%d, %+v, %v), want the typed 404", missing.HTTPResponse.StatusCode, missing.JSONDefault, err) + { + missing, err := client.GetSessionHydrationWithResponse(ctx, unused) + testassert.Require(t, err == nil && missing.JSONDefault != nil && missing.JSONDefault.Code == protocol.NotFound && missing.HTTPResponse.StatusCode == http.StatusNotFound, "unknown session = (%d, %+v, %v), want the typed 404", missing.HTTPResponse.StatusCode, missing.JSONDefault, err) } // A target route never accepts a legacy 8-hex identity. legacy := rawProtocol(t, http.MethodDelete, protocolTarget(ps, "/v1/sessions/decafbad"), ps.credential, "") - if legacy.StatusCode != http.StatusBadRequest { - t.Fatalf("legacy identity on a target route = %d, want invalid 400", legacy.StatusCode) - } + testassert.Equal(t, legacy.StatusCode, http.StatusBadRequest) legacy.Body.Close() // A valid unknown provider answers the catalog's typed not-found. - if unknown, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "ghost"}); err != nil || - unknown.JSONDefault == nil || unknown.JSONDefault.Code != protocol.NotFound { - t.Fatalf("unknown provider = (%d, %+v, %v), want the typed 404", unknown.HTTPResponse.StatusCode, unknown.JSONDefault, err) + { + unknown, err := client.GetProviderDetailWithResponse(ctx, &protocol.GetProviderDetailParams{ProviderId: "ghost"}) + testassert.Require(t, err == nil && unknown.JSONDefault != nil && unknown.JSONDefault.Code == protocol.NotFound, "unknown provider = (%d, %+v, %v), want the typed 404", unknown.HTTPResponse.StatusCode, unknown.JSONDefault, err) } // A slash provider identity is refused at creation by the complete @@ -557,18 +473,15 @@ func TestProtocolServerErrorClasses(t *testing.T) { // refusal helpers; the mounted response's class is checked first // and then re-pinned through the helper with the same class. subscription, err := r.Subscribe(64) - if err != nil { - t.Fatalf("subscribe for the slash refusal baseline: %v", err) - } + testassert.NoError(t, err) defer subscription.Close() slashBefore, slashGeneration, slashWarnRev := runtimeMutationBaseline(t, r) slash := rawProtocol(t, http.MethodPost, protocolTarget(ps, "/v1/providers"), ps.credential, `{"id":"has/slash","provider":{"base_url":"https://x.test/v1"},"models":{"m":{"context_window":4096}}}`) - if slash.StatusCode != http.StatusUnprocessableEntity { - t.Fatalf("slash provider creation = %d, want the typed candidate failure 422", slash.StatusCode) - } - if typed := decodeProtocolError(t, slash); typed.Code != protocol.Configuration { - t.Fatalf("slash provider creation error = %+v, want configuration", typed) + testassert.Equal(t, slash.StatusCode, http.StatusUnprocessableEntity) + { + typed := decodeProtocolError(t, slash) + testassert.Require(t, typed.Code == protocol.Configuration, "slash provider creation error = %+v, want configuration", typed) } assertRuntimeMutationRefused(t, r, subscription, slashBefore, slashGeneration, slashWarnRev, fmt.Errorf("mounted slash refusal: %w", ErrConfiguration), ErrConfiguration) @@ -581,13 +494,8 @@ func TestProtocolServerErrorClasses(t *testing.T) { lifecycleInsertRegister(t, store, harness.RegisterKey{SessionID: corruptID, Kind: harness.RegisterSession}, `{"not":"a session register"}`) valid := projectionSession(t, r, workspace, "solo").Identity.SessionID corrupt, err := client.GetSessionHydrationWithResponse(ctx, corruptID) - if err != nil || corrupt.JSONDefault == nil || corrupt.JSONDefault.Code != protocol.Corrupt || - corrupt.HTTPResponse.StatusCode != http.StatusUnprocessableEntity { - t.Fatalf("corrupt session = (%d, %+v, %v), want the typed corrupt 422", corrupt.HTTPResponse.StatusCode, corrupt.JSONDefault, err) - } - if corrupt.JSONDefault.SessionId == nil || *corrupt.JSONDefault.SessionId != corruptID { - t.Fatalf("corrupt error session id = %v, want the corrupt session's identity", corrupt.JSONDefault.SessionId) - } + testassert.Require(t, err == nil && corrupt.JSONDefault != nil && corrupt.JSONDefault.Code == protocol.Corrupt && corrupt.HTTPResponse.StatusCode == http.StatusUnprocessableEntity, "corrupt session = (%d, %+v, %v), want the typed corrupt 422", corrupt.HTTPResponse.StatusCode, corrupt.JSONDefault, err) + testassert.Require(t, corrupt.JSONDefault.SessionId != nil && *corrupt.JSONDefault.SessionId == corruptID, "corrupt error session id = %v, want the corrupt session's identity", corrupt.JSONDefault.SessionId) listed, err := client.ListSessionsWithResponse(ctx, &protocol.ListSessionsParams{Workspace: workspace, Lifecycle: "open"}) if err != nil || listed.JSON200 == nil || len(*listed.JSON200) != 1 || (*listed.JSON200)[0].SessionId != valid { body, _ := json.Marshal(listed.JSON200) @@ -597,24 +505,17 @@ func TestProtocolServerErrorClasses(t *testing.T) { // An invalid settings write fails invalid before any candidate work, // with the previous revision intact. before, err := client.GetConfigurationWithResponse(ctx) - if err != nil || before.JSON200 == nil { - t.Fatalf("read before invalid settings: %v", err) - } + testassert.Require(t, err == nil && before.JSON200 != nil, "read before invalid settings: %v", err) invalidSettings := rawProtocol(t, http.MethodPut, protocolTarget(ps, "/v1/configuration/settings"), ps.credential, `{"settings":{"sessions":{"auto_archive":true,"archive_after_days":-5,"delete_after_archive_days":0},"plugins":{"unknown-plugin":{}}}}`) - if invalidSettings.StatusCode != http.StatusBadRequest { - t.Fatalf("invalid settings = %d, want the typed invalid 400", invalidSettings.StatusCode) - } - if typed := decodeProtocolError(t, invalidSettings); typed.Code != protocol.Invalid { - t.Fatalf("invalid settings error = %+v, want invalid", typed) + testassert.Equal(t, invalidSettings.StatusCode, http.StatusBadRequest) + { + typed := decodeProtocolError(t, invalidSettings) + testassert.Require(t, typed.Code == protocol.Invalid, "invalid settings error = %+v, want invalid", typed) } after, err := client.GetConfigurationWithResponse(ctx) - if err != nil || after.JSON200 == nil { - t.Fatalf("read after invalid settings: %v", err) - } - if after.JSON200.ConfigurationRevision.Generation != before.JSON200.ConfigurationRevision.Generation { - t.Fatal("an invalid candidate changed the published revision") - } + testassert.Require(t, err == nil && after.JSON200 != nil, "read after invalid settings: %v", err) + testassert.Require(t, after.JSON200.ConfigurationRevision.Generation == before.JSON200.ConfigurationRevision.Generation, "an invalid candidate changed the published revision") // The settings outer shape stays closed and its plugins member is a // required complete map: a missing or null member, a wrong-typed @@ -632,17 +533,14 @@ func TestProtocolServerErrorClasses(t *testing.T) { response.Body.Close() t.Fatalf("%s = %d, want the typed invalid 400", row.name, response.StatusCode) } - if typed := decodeProtocolError(t, response); typed.Code != protocol.Invalid { - t.Fatalf("%s error = %+v, want invalid", row.name, typed) + { + typed := decodeProtocolError(t, response) + testassert.Require(t, typed.Code == protocol.Invalid, "%s error = %+v, want invalid", row.name, typed) } } finalRead, err := client.GetConfigurationWithResponse(ctx) - if err != nil || finalRead.JSON200 == nil { - t.Fatalf("read after settings shape failures: %v", err) - } - if finalRead.JSON200.ConfigurationRevision.Generation != before.JSON200.ConfigurationRevision.Generation { - t.Fatal("a rejected settings shape changed the published revision") - } + testassert.Require(t, err == nil && finalRead.JSON200 != nil, "read after settings shape failures: %v", err) + testassert.Require(t, finalRead.JSON200.ConfigurationRevision.Generation == before.JSON200.ConfigurationRevision.Generation, "a rejected settings shape changed the published revision") // A restore attempt against a busy Session answers the conflict // class from the one idle gate. @@ -650,20 +548,17 @@ func TestProtocolServerErrorClasses(t *testing.T) { gate := make(chan struct{}) e.server.setHold(gate) defer close(gate) - if _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ - OperationId: "op-1", Mode: "regular", - Content: []protocol.ContentPart{textContentPart(t, "busy", nil)}, - }); err != nil { - t.Fatalf("busy submit: %v", err) + { + _, err := client.SubmitSessionWithResponse(ctx, session, protocol.SubmitRequest{ + OperationId: "op-1", Mode: "regular", + Content: []protocol.ContentPart{textContentPart(t, "busy", nil)}, + }) + testassert.NoError(t, err) } conflict, err := client.RevertSessionCodeWithResponse(ctx, session, protocol.RevertCodeRequest{BoundaryOperationId: "op-1"}) - if err != nil || conflict.JSONDefault == nil { - t.Fatalf("busy revert = (%v, %+v)", err, conflict.JSONDefault) - } + testassert.Require(t, err == nil && conflict.JSONDefault != nil, "busy revert = (%v, %+v)", err, conflict.JSONDefault) conflictError, err := conflict.JSONDefault.AsError() - if err != nil || conflictError.Code != protocol.Conflict || conflict.HTTPResponse.StatusCode != http.StatusConflict { - t.Fatalf("busy revert = (%d, %+v, %v), want the typed conflict 409", conflict.HTTPResponse.StatusCode, conflictError, err) - } + testassert.Require(t, err == nil && conflictError.Code == protocol.Conflict && conflict.HTTPResponse.StatusCode == http.StatusConflict, "busy revert = (%d, %+v, %v), want the typed conflict 409", conflict.HTTPResponse.StatusCode, conflictError, err) }) // The classifier table for the classes a wire request cannot reach @@ -683,9 +578,7 @@ func TestProtocolServerErrorClasses(t *testing.T) { } for _, row := range classes { code, status := classifyProtocolError(row.err) - if code != row.code || status != row.status { - t.Fatalf("classify(%v) = (%s, %d), want (%s, %d)", row.err, code, status, row.code, row.status) - } + testassert.Require(t, code == row.code && status == row.status, "classify(%v) = (%s, %d), want (%s, %d)", row.err, code, status, row.code, row.status) } } @@ -754,16 +647,10 @@ func TestClassifyProtocolMissingMetadata(t *testing.T) { for _, row := range rows { t.Run(row.name, func(t *testing.T) { err := row.run() - if err == nil { - t.Fatal("the producer refused nothing, want a typed refusal") - } - if !errors.Is(err, row.want) { - t.Fatalf("producer refusal = %v, want class %v", err, row.want) - } + testassert.Require(t, err != nil, "the producer refused nothing, want a typed refusal") + testassert.Require(t, errors.Is(err, row.want), "producer refusal = %v, want class %v", err, row.want) code, status := classifyProtocolError(fmt.Errorf("wrapped: %w", err)) - if code != row.code || status != row.status { - t.Fatalf("classify(%v) = (%s, %d), want (%s, %d)", err, code, status, row.code, row.status) - } + testassert.Require(t, code == row.code && status == row.status, "classify(%v) = (%s, %d), want (%s, %d)", err, code, status, row.code, row.status) }) } }) @@ -795,31 +682,26 @@ func TestProtocolServerForkAndHydrationRefuseWarmMarkedCorruptSession(t *testing source, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{ Workspace: filepath.Join(e.home, "marked-src"), AgentType: "solo", }) - if err != nil || source.JSON201 == nil { - t.Fatalf("create source over HTTP: %v (typed %+v)", err, source.JSONDefault) - } + testassert.Require(t, err == nil && source.JSON201 != nil, "create source over HTTP: %v (typed %+v)", err, source.JSONDefault) sourceID := source.JSON201.SessionId sibling, err := client.CreateSessionWithResponse(ctx, protocol.CreateSessionRequest{ Workspace: filepath.Join(e.home, "marked-sibling"), AgentType: "solo", }) - if err != nil || sibling.JSON201 == nil { - t.Fatalf("create sibling over HTTP: %v (typed %+v)", err, sibling.JSONDefault) - } + testassert.Require(t, err == nil && sibling.JSON201 != nil, "create sibling over HTTP: %v (typed %+v)", err, sibling.JSONDefault) siblingID := sibling.JSON201.SessionId - if _, err := client.SubmitSessionWithResponse(ctx, sourceID, protocol.SubmitRequest{ - OperationId: "op-src", Mode: "regular", - Content: []protocol.ContentPart{textContentPart(t, "boundary", nil)}, - }); err != nil { - t.Fatalf("source submit over HTTP: %v", err) + { + _, err := client.SubmitSessionWithResponse(ctx, sourceID, protocol.SubmitRequest{ + OperationId: "op-src", Mode: "regular", + Content: []protocol.ContentPart{textContentPart(t, "boundary", nil)}, + }) + testassert.NoError(t, err) } awaitOperation(t, r, sourceID, "op-src", harness.OperationSuccess) awaitIdleSession(t, r, sourceID) before, err := client.GetSessionHydrationWithResponse(ctx, sourceID) - if err != nil || before.JSON200 == nil { - t.Fatalf("hydration before the corruption: %v (typed %+v)", err, before.JSONDefault) - } + testassert.Require(t, err == nil && before.JSON200 != nil, "hydration before the corruption: %v (typed %+v)", err, before.JSONDefault) // The fork boundary is resolved from the mounted response itself: the // committed user-origin input item the server projected. boundaryItem := "" @@ -830,9 +712,7 @@ func TestProtocolServerForkAndHydrationRefuseWarmMarkedCorruptSession(t *testing } boundaryItem = input.ItemId } - if boundaryItem == "" { - t.Fatalf("the mounted hydration carries no user-origin input item: %+v", before.JSON200.Conversation.Items) - } + testassert.Require(t, boundaryItem != "", "the mounted hydration carries no user-origin input item: %+v", before.JSON200.Conversation.Items) // The durable register is corrupted while the mounted Runtime holds // the warm source coordinator, so the sticky marker the HTTP fork @@ -844,29 +724,15 @@ func TestProtocolServerForkAndHydrationRefuseWarmMarkedCorruptSession(t *testing OperationId: "op-fork-marked", Content: []protocol.ContentPart{textContentPart(t, "forked", nil)}, }) - if err != nil || refused.JSONDefault == nil { - t.Fatalf("fork over HTTP = (%d, %+v, %v), want the typed corrupt refusal", refused.HTTPResponse.StatusCode, refused.JSONDefault, err) - } - if refused.JSONDefault.Code != protocol.Corrupt || refused.HTTPResponse.StatusCode != http.StatusUnprocessableEntity { - t.Fatalf("fork over HTTP = (%d, %+v), want the typed corrupt 422", refused.HTTPResponse.StatusCode, refused.JSONDefault) - } - if refused.JSONDefault.SessionId == nil || *refused.JSONDefault.SessionId != sourceID { - t.Fatalf("fork corrupt error session id = %v, want the marked source identity", refused.JSONDefault.SessionId) - } + testassert.Require(t, err == nil && refused.JSONDefault != nil, "fork over HTTP = (%d, %+v, %v), want the typed corrupt refusal", refused.HTTPResponse.StatusCode, refused.JSONDefault, err) + testassert.Require(t, refused.JSONDefault.Code == protocol.Corrupt && refused.HTTPResponse.StatusCode == http.StatusUnprocessableEntity && refused.JSONDefault.SessionId != nil && *refused.JSONDefault.SessionId == sourceID, "fork over HTTP = (%d, %+v), want the typed corrupt 422; fork corrupt error session id = %v, want the marked source identity", refused.HTTPResponse.StatusCode, refused.JSONDefault, refused.JSONDefault.SessionId) after, err := client.GetSessionHydrationWithResponse(ctx, sourceID) - if err != nil || after.JSONDefault == nil || after.JSONDefault.Code != protocol.Corrupt || - after.HTTPResponse.StatusCode != http.StatusUnprocessableEntity { - t.Fatalf("hydration of the marked warm source = (%d, %+v, %v), want the typed corrupt 422", after.HTTPResponse.StatusCode, after.JSONDefault, err) - } - if after.JSONDefault.SessionId == nil || *after.JSONDefault.SessionId != sourceID { - t.Fatalf("hydration corrupt error session id = %v, want the marked source identity", after.JSONDefault.SessionId) - } + testassert.Require(t, err == nil && after.JSONDefault != nil && after.JSONDefault.Code == protocol.Corrupt && after.HTTPResponse.StatusCode == http.StatusUnprocessableEntity, "hydration of the marked warm source = (%d, %+v, %v), want the typed corrupt 422", after.HTTPResponse.StatusCode, after.JSONDefault, err) + testassert.Require(t, after.JSONDefault.SessionId != nil && *after.JSONDefault.SessionId == sourceID, "hydration corrupt error session id = %v, want the marked source identity", after.JSONDefault.SessionId) siblingHydration, err := client.GetSessionHydrationWithResponse(ctx, siblingID) - if err != nil || siblingHydration.JSON200 == nil || siblingHydration.JSON200.Session.SessionId != siblingID { - t.Fatalf("sibling hydration = (%d, %+v, %v), want the valid sibling read", siblingHydration.HTTPResponse.StatusCode, siblingHydration.JSON200, err) - } + testassert.Require(t, err == nil && siblingHydration.JSON200 != nil && siblingHydration.JSON200.Session.SessionId == siblingID, "sibling hydration = (%d, %+v, %v), want the valid sibling read", siblingHydration.HTTPResponse.StatusCode, siblingHydration.JSON200, err) listed, err := client.ListSessionsWithResponse(ctx, &protocol.ListSessionsParams{ Workspace: filepath.Join(e.home, "marked-sibling"), Lifecycle: "open", }) @@ -890,11 +756,7 @@ func TestProtocolServerForkAndHydrationRefuseWarmMarkedCorruptSession(t *testing // the mapped non-2xx status — never a replacement empty error, while a // pre-traversal refusal answers the plain typed error. func TestProtocolServerRevertPartialFailure(t *testing.T) { - e := newOwnerEnv(t) - r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + r, e := openProtocolOwner(t, storage.NewMemory()) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) client := protocolClient(t, ps) @@ -905,17 +767,13 @@ func TestProtocolServerRevertPartialFailure(t *testing.T) { // store directory becomes unreadable so the reverse traversal fails // after the newest group already changed files. workspace := filepath.Join(e.home, "revert") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID older := submitCodeOperation(t, r, session, "op-1", "older turn") newest := submitCodeOperation(t, r, session, "op-2", "newest turn") olderTarget := filepath.Join(workspace, "older.txt") newestTarget := filepath.Join(workspace, "newest.txt") - if err := os.WriteFile(newestTarget, []byte("v0"), 0o600); err != nil { - t.Fatalf("write the newest preimage: %v", err) - } + testassert.NoError(t, os.WriteFile(newestTarget, []byte("v0"), 0o600)) captureCodeMutation(t, codeGroupRoot(r, session, older.Admission.AdmittedEntry.EntryID), olderTarget, olderTarget, []byte("older")) captureCodeMutation(t, codeGroupRoot(r, session, newest.Admission.AdmittedEntry.EntryID), newestTarget, newestTarget, []byte("newest")) awaitRestorableSession(t, r, session) @@ -923,17 +781,11 @@ func TestProtocolServerRevertPartialFailure(t *testing.T) { // the newest group restores first, then the older group's turn errors. olderTurn := filepath.Join(codeGroupRoot(r, session, older.Admission.AdmittedEntry.EntryID), "snapshots", "1") entries, err := os.ReadDir(olderTurn) - if err != nil || len(entries) != 1 { - t.Fatalf("older group turn entries = (%d, %v), want one recorded entry", len(entries), err) - } - if err := os.WriteFile(filepath.Join(olderTurn, entries[0].Name(), "meta.json"), []byte("{"), 0o600); err != nil { - t.Fatalf("corrupt the older group's meta: %v", err) - } + testassert.Require(t, err == nil && len(entries) == 1, "older group turn entries = (%d, %v), want one recorded entry", len(entries), err) + testassert.NoError(t, os.WriteFile(filepath.Join(olderTurn, entries[0].Name(), "meta.json"), []byte("{"), 0o600)) response, err := client.RevertSessionCodeWithResponse(ctx, session, protocol.RevertCodeRequest{BoundaryOperationId: "op-1"}) - if err != nil { - t.Fatalf("revert: %v", err) - } + testassert.NoError(t, err) if response.HTTPResponse.StatusCode == http.StatusOK { t.Fatalf("partial restore answered 200: %s", string(response.Body)) } @@ -941,32 +793,22 @@ func TestProtocolServerRevertPartialFailure(t *testing.T) { if err := json.Unmarshal(response.Body, &result); err != nil { t.Fatalf("decode partial restore body %s: %v", string(response.Body), err) } - if result.Error == nil || result.Error.Code != protocol.Internal { - t.Fatalf("partial restore error member = %+v, want the accumulated internal error", result.Error) - } - if !slices.Contains(result.Restored, newestTarget) { - t.Fatalf("partial restore restored = %v, want the newest group's file before the failure", result.Restored) - } - if restored, err := os.ReadFile(newestTarget); err != nil || string(restored) != "v0" { - t.Fatalf("the newest group's file = (%q, %v), want its restored pre-turn content", restored, err) + testassert.Require(t, result.Error != nil && result.Error.Code == protocol.Internal && slices.Contains(result.Restored, newestTarget), "partial restore error member = %+v, want the accumulated internal error; partial restore restored = %v, want the newest group's file before the failure", result.Error, result.Restored) + { + restored, err := os.ReadFile(newestTarget) + testassert.Require(t, err == nil && string(restored) == "v0", "the newest group's file = (%q, %v), want its restored pre-turn content", restored, err) } // A pre-traversal refusal answers the plain typed error with no result // members: an unknown boundary never opens a store. refusal, err := client.RevertSessionCodeWithResponse(ctx, session, protocol.RevertCodeRequest{BoundaryOperationId: "ghost-op"}) - if err != nil { - t.Fatalf("unknown-boundary revert: %v", err) - } + testassert.NoError(t, err) if refusal.JSONDefault == nil { t.Fatalf("unknown-boundary revert = %s, want the typed error branch", string(refusal.Body)) } typed, err := refusal.JSONDefault.AsError() - if err != nil { - t.Fatalf("decode the refused revert's error branch: %v", err) - } - if typed.Code != protocol.Invalid || refusal.HTTPResponse.StatusCode != http.StatusBadRequest { - t.Fatalf("unknown-boundary revert = (%d, %+v), want the typed invalid 400", refusal.HTTPResponse.StatusCode, typed) - } + testassert.NoError(t, err) + testassert.Require(t, typed.Code == protocol.Invalid && refusal.HTTPResponse.StatusCode == http.StatusBadRequest, "unknown-boundary revert = (%d, %+v), want the typed invalid 400", refusal.HTTPResponse.StatusCode, typed) } // TestProtocolServerIntervalProtectionAndAvailability proves the artifact @@ -984,16 +826,12 @@ func TestProtocolServerIntervalProtectionAndAvailability(t *testing.T) { ctx := context.Background() workspace := filepath.Join(e.home, "interval-mounted") - if err := os.MkdirAll(workspace, 0o700); err != nil { - t.Fatalf("mkdir workspace: %v", err) - } + testassert.NoError(t, os.MkdirAll(workspace, 0o700)) session := projectionSession(t, r, workspace, "solo").Identity.SessionID op := submitCodeOperation(t, r, session, "op-1", "only") awaitRestorableSession(t, r, session) file := filepath.Join(workspace, "mounted.txt") - if err := os.WriteFile(file, []byte("v1"), 0o600); err != nil { - t.Fatalf("write v1: %v", err) - } + testassert.NoError(t, os.WriteFile(file, []byte("v1"), 0o600)) fifo := seedFIFOCodeEntry(t, codeGroupRoot(r, session, op.Admission.AdmittedEntry.EntryID), file, "v1") // The mounted restore parks inside its traversal: its HTTP handler is @@ -1018,8 +856,9 @@ func TestProtocolServerIntervalProtectionAndAvailability(t *testing.T) { body, _ := json.Marshal(del.JSONDefault) t.Fatalf("mounted delete during a parked restore = (%d, %s, %v), want the typed conflict 409", del.HTTPResponse.StatusCode, body, err) } - if hydration, err := client.GetSessionHydrationWithResponse(ctx, session); err != nil || hydration.JSON200 == nil { - t.Fatalf("hydration after the refused delete = (%v, %+v), want the surviving Session", err, hydration.JSON200) + { + hydration, err := client.GetSessionHydrationWithResponse(ctx, session) + testassert.Require(t, err == nil && hydration.JSON200 != nil, "hydration after the refused delete = (%v, %+v), want the surviving Session", err, hydration.JSON200) } // A second mounted rewind conflicts. @@ -1031,9 +870,7 @@ func TestProtocolServerIntervalProtectionAndAvailability(t *testing.T) { // The metadata-only transition stays available inside the interval. changed, err := client.SetSessionAgentTypeWithResponse(ctx, session, protocol.SetSessionAgentTypeRequest{AgentType: "worker"}) - if err != nil || changed.JSON200 == nil || changed.JSON200.AgentType != "worker" { - t.Fatalf("mounted agent-type change during a parked restore = (%v, %+v), want the changed selection", err, changed.JSON200) - } + testassert.Require(t, err == nil && changed.JSON200 != nil && changed.JSON200.AgentType == "worker", "mounted agent-type change during a parked restore = (%v, %+v), want the changed selection", err, changed.JSON200) // The parked mounted restore completes with its own result. park.release("restored") @@ -1043,14 +880,11 @@ func TestProtocolServerIntervalProtectionAndAvailability(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("the parked mounted restore never returned after its FIFO completed") } - if out.err != nil || out.response.JSON200 == nil { - t.Fatalf("mounted restore = (%+v, %v), want the restored result", out.response, out.err) - } - if len(out.response.JSON200.Restored) != 1 || out.response.JSON200.Restored[0] != file { - t.Fatalf("restored = %v, want the parked group's restore", out.response.JSON200.Restored) - } - if data, err := os.ReadFile(file); err != nil || string(data) != "restored" { - t.Fatalf("file = (%q, %v), want the FIFO content", data, err) + testassert.Require(t, out.err == nil && out.response.JSON200 != nil, "mounted restore = (%+v, %v), want the restored result", out.response, out.err) + testassert.Require(t, len(out.response.JSON200.Restored) == 1 && out.response.JSON200.Restored[0] == file, "restored = %v, want the parked group's restore", out.response.JSON200.Restored) + { + data, err := os.ReadFile(file) + testassert.Require(t, err == nil && string(data) == "restored", "file = (%q, %v), want the FIFO content", data, err) } }) } diff --git a/runtime/protocol_warnings_mounted_test.go b/runtime/protocol_warnings_mounted_test.go index cacd99be..4cb8dc71 100644 --- a/runtime/protocol_warnings_mounted_test.go +++ b/runtime/protocol_warnings_mounted_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" ) // The mounted warning-lifetime row: the generated Go client drives the real @@ -22,21 +23,15 @@ func TestMountedGeneratedGoClientWarningLifetime(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) ps := openProtocolServer(t, r) client := protocolClient(t, ps) victim, err := r.createSession(ctx, e.workspace("mounted-warn-victim"), "worker") - if err != nil { - t.Fatalf("createSession(victim): %v", err) - } + testassert.NoError(t, err) sibling, err := r.createSession(ctx, e.workspace("mounted-warn-sibling"), "worker") - if err != nil { - t.Fatalf("createSession(sibling): %v", err) - } + testassert.NoError(t, err) victimID, siblingID := victim.Identity.SessionID, sibling.Identity.SessionID submitThroughRuntime(t, r, victimID, "op-victim", "please write") awaitOperation(t, r, victimID, "op-victim", harness.OperationSuccess) @@ -46,12 +41,8 @@ func TestMountedGeneratedGoClientWarningLifetime(t *testing.T) { find := func(sessionID string) bool { t.Helper() response, err := client.GetWarningsWithResponse(ctx) - if err != nil { - t.Fatalf("GetWarnings: %v", err) - } - if response.JSON200 == nil { - t.Fatalf("GetWarnings body = %+v, want the typed snapshot", response) - } + testassert.NoError(t, err) + testassert.Require(t, response.JSON200 != nil, "GetWarnings body = %+v, want the typed snapshot", response) for _, warning := range response.JSON200.Warnings { if warning.Source == "runtime:prompt" && warning.SessionId != nil && *warning.SessionId == sessionID { return true @@ -59,27 +50,15 @@ func TestMountedGeneratedGoClientWarningLifetime(t *testing.T) { } return false } - if !find(victimID) || !find(siblingID) { - t.Fatalf("the mounted read misses a real submitted Session's prompt warning") - } + testassert.Require(t, find(victimID) && find(siblingID), "the mounted read misses a real submitted Session's prompt warning") archived, err := client.ArchiveSessionWithResponse(ctx, victimID) - if err != nil || archived.JSON200 == nil { - t.Fatalf("ArchiveSession = (%+v, %v), want the archived header", archived, err) - } - if !find(victimID) { - t.Fatalf("archive dropped the Session's warning over the mounted read") - } + testassert.Require(t, err == nil && archived.JSON200 != nil, "ArchiveSession = (%+v, %v), want the archived header", archived, err) + testassert.Require(t, find(victimID), "archive dropped the Session's warning over the mounted read") deleted, err := client.DeleteSessionWithResponse(ctx, victimID) - if err != nil || deleted.HTTPResponse.StatusCode != http.StatusNoContent { - t.Fatalf("DeleteSession = (%+v, %v), want the 204 deletion", deleted, err) - } - if find(victimID) { - t.Fatalf("the committed deletion kept the Session's warning over the mounted read") - } - if !find(siblingID) { - t.Fatalf("the committed deletion removed the sibling Session's warning") - } + testassert.Require(t, err == nil && deleted.HTTPResponse.StatusCode == http.StatusNoContent, "DeleteSession = (%+v, %v), want the 204 deletion", deleted, err) + testassert.Require(t, !find(victimID), "the committed deletion kept the Session's warning over the mounted read") + testassert.Require(t, find(siblingID), "the committed deletion removed the sibling Session's warning") }) } diff --git a/runtime/protocol_warnings_test.go b/runtime/protocol_warnings_test.go index 5da7fbd4..f4ff443f 100644 --- a/runtime/protocol_warnings_test.go +++ b/runtime/protocol_warnings_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" ) @@ -60,20 +61,14 @@ func TestWarningsPromptReplaceClearAndFailedPreparationNoChange(t *testing.T) { ctx := context.Background() hook := e.hookedHook() r, err := e.open(ctx, hook, &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) session, err := r.createSession(ctx, e.workspace("prompt-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID promptSub, err := r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(promptSub.Close) // Successful preparation: the assembled prompt warning replaces the @@ -85,9 +80,7 @@ func TestWarningsPromptReplaceClearAndFailedPreparationNoChange(t *testing.T) { progressBeforeHint := false for promptHint == nil { event, ok := nextEvent(t, promptSub) - if !ok { - t.Fatal("the prompt subscription closed before the preparation's warning hint") - } + testassert.Require(t, ok, "the prompt subscription closed before the preparation's warning hint") switch eventKind(t, event) { case "text_delta", "tool_started", "tool_finished": // The preparation's publication precedes every effect of the @@ -97,82 +90,56 @@ func TestWarningsPromptReplaceClearAndFailedPreparationNoChange(t *testing.T) { continue case "warning_changed": body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) == "session" { promptScope, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("session scope: %v", err) - } + testassert.NoError(t, err) if promptScope.SessionId == sessionID { promptHint = &body } } } } - if progressBeforeHint { - t.Fatal("the Session's first warning hint arrived after the Operation's progress, want the preparation's own publication") - } + testassert.Require(t, !progressBeforeHint, "the Session's first warning hint arrived after the Operation's progress, want the preparation's own publication") promptScope, err := promptHint.Scope.AsSessionScope() - if err != nil { - t.Fatalf("prompt warning hint scope: %v", err) - } - if promptScope.Workspace == nil || *promptScope.Workspace != e.workspace("prompt-ws") { - t.Fatalf("prompt warning hint scope = %+v, want the cached workspace identity", promptHint.Scope) - } - if revision, err := warnRevision(promptHint.WarningsRevision.Revision); err != nil || revision == 0 { - t.Fatalf("prompt warning hint revision = %q, want an advanced counter", promptHint.WarningsRevision.Revision) + testassert.NoError(t, err) + testassert.Require(t, promptScope.Workspace != nil && *promptScope.Workspace == e.workspace("prompt-ws"), "prompt warning hint scope = %+v, want the cached workspace identity", promptHint.Scope) + { + revision, err := warnRevision(promptHint.WarningsRevision.Revision) + testassert.Require(t, err == nil && revision != 0, "prompt warning hint revision = %q, want an advanced counter", promptHint.WarningsRevision.Revision) } awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) awaitSessionNotBusy(t, r, sessionID) first, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } + testassert.NoError(t, err) found := warningOf(first.Warnings, "runtime:prompt", "rules_not_found", sessionID) - if found == nil || found.Message != "No AGENTS.md found" { - t.Fatalf("prompt warning after successful preparation = %+v, want the assembled rules warning for the session", found) - } - if warningOf(first.Warnings, "runtime:setup", "setup_no_model", "") == nil { - t.Fatalf("global setup warning missing: %+v", first.Warnings) - } - if first.WarningsRevision.Revision == "0" { - t.Fatalf("warning revision = %q, want the advanced counter", first.WarningsRevision.Revision) - } + testassert.Require(t, found != nil && found.Message == "No AGENTS.md found", "prompt warning after successful preparation = %+v, want the assembled rules warning for the session", found) + testassert.Require(t, warningOf(first.Warnings, "runtime:setup", "setup_no_model", "") != nil && first.WarningsRevision.Revision != "0", "global setup warning missing: %+v; warning revision = %q, want the advanced counter", first.Warnings, first.WarningsRevision.Revision) // A warning-free successful preparation replaces the prior group with // nothing: the same Session's next preparation clears it. - if err := os.WriteFile(filepath.Join(e.workspace("prompt-ws"), "AGENTS.md"), []byte("# rules"), 0o600); err != nil { - t.Fatalf("write AGENTS.md: %v", err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(e.workspace("prompt-ws"), "AGENTS.md"), []byte("# rules"), 0o600)) submitThroughRuntime(t, r, sessionID, "op-2", "please write") awaitOperation(t, r, sessionID, "op-2", harness.OperationSuccess) awaitIdleSession(t, r, sessionID) cleared, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings cleared: %v", err) - } - if warningOf(cleared.Warnings, "runtime:prompt", "rules_not_found", sessionID) != nil { - t.Fatalf("the warning-free preparation kept the session's prompt warning: %+v", cleared.Warnings) - } - if revision, err := warnRevision(cleared.WarningsRevision.Revision); err != nil || revision == 0 { - t.Fatalf("warning revision after the clearing preparation = %q (%v), want an advanced counter", cleared.WarningsRevision.Revision, err) + testassert.NoError(t, err) + testassert.Require(t, warningOf(cleared.Warnings, "runtime:prompt", "rules_not_found", sessionID) == nil, "the warning-free preparation kept the session's prompt warning: %+v", cleared.Warnings) + { + revision, err := warnRevision(cleared.WarningsRevision.Revision) + testassert.Require(t, err == nil && revision != 0, "warning revision after the clearing preparation = %q (%v), want an advanced counter", cleared.WarningsRevision.Revision, err) } // A failed preparation changes nothing: the missing credential // rejects admission before any publication. before, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings before failed preparation: %v", err) - } + testassert.NoError(t, err) e.t.Setenv("PRODUCTION_TEST_KEY", "") failedSession, err := r.createSession(ctx, e.workspace("prompt-ws-3"), "worker") - if err != nil { - t.Fatalf("createSession(failed): %v", err) - } - if err := submitExpectFailure(r, failedSession.Identity.SessionID, "op-failed"); err == nil { - t.Fatal("the missing-credential submit succeeded, want the failed preparation") + testassert.NoError(t, err) + { + err := submitExpectFailure(r, failedSession.Identity.SessionID, "op-failed", "hello") + testassert.Require(t, err != nil, "the missing-credential submit succeeded, want the failed preparation") } e.t.Setenv("PRODUCTION_TEST_KEY", "production-secret-1") @@ -182,21 +149,16 @@ func TestWarningsPromptReplaceClearAndFailedPreparationNoChange(t *testing.T) { return c }) hookSession, err := r.createSession(ctx, e.workspace("prompt-ws-4"), "worker") - if err != nil { - t.Fatalf("createSession(hook): %v", err) - } - if err := submitExpectFailure(r, hookSession.Identity.SessionID, "op-hooked"); err == nil { - t.Fatal("the rejected-hook submit succeeded, want the failed preparation") + testassert.NoError(t, err) + { + err := submitExpectFailure(r, hookSession.Identity.SessionID, "op-hooked", "hello") + testassert.Require(t, err != nil, "the rejected-hook submit succeeded, want the failed preparation") } hook.set(nil, nil) after, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after failures: %v", err) - } - if after.WarningsRevision.Revision != before.WarningsRevision.Revision { - t.Fatalf("warning revision advanced on failed preparations: %s → %s", before.WarningsRevision.Revision, after.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, after.WarningsRevision.Revision, before.WarningsRevision.Revision) if !slices.Equal(warningBodies(before.Warnings), warningBodies(after.Warnings)) { t.Fatalf("warnings changed on failed preparations:\n%v\n%v", warningBodies(before.Warnings), warningBodies(after.Warnings)) } @@ -207,16 +169,12 @@ func TestWarningsPromptReplaceClearAndFailedPreparationNoChange(t *testing.T) { for { select { case event, ok := <-promptSub.Events(): - if !ok { - t.Fatal("the prompt subscription closed around the failed preparations") - } + testassert.Require(t, ok, "the prompt subscription closed around the failed preparations") if eventKind(t, event) != "warning_changed" { continue } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if hintSession, _ := scopeSessionIdentity(t, body.Scope); hintSession == sessionID { continue } @@ -236,23 +194,16 @@ func awaitSessionNotBusy(t *testing.T, r *Runtime, sessionID string) { t.Helper() deadline := time.Now().Add(10 * time.Second) for sessionViewThroughRuntime(t, r, sessionID).History.ExecutionBusy { - if time.Now().After(deadline) { - t.Fatalf("session %q never left its busy state", sessionID) - } + testassert.Require(t, !time.Now().After(deadline), "session %q never left its busy state", sessionID) time.Sleep(10 * time.Millisecond) } } // submitExpectFailure submits one regular message expecting the admission to // fail; the raw error is returned for the caller to ignore or inspect. -func submitExpectFailure(r *Runtime, sessionID, operationID string) error { - return r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - _, err := h.Submit(ctx, harness.SubmitRequest{ - SessionID: sessionID, OperationID: operationID, Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "hello"}}, Mode: harness.MessageModeRegular, - }) - return err - }) +func submitExpectFailure(r *Runtime, sessionID, operationID, text string) error { + _, err := submitRawThroughRuntime(context.Background(), r, sessionID, operationID, text, harness.MessageModeRegular) + return err } func warnRevision(rev string) (uint64, error) { @@ -285,134 +236,61 @@ func TestWarningsTwoSessionsHydrationFiltersOwnAndGlobals(t *testing.T) { writeServiceFile(t, e.configPath, strings.Replace( productionConfigDocument(e.server.URL, "T1"), `"models": {`, `"models": {"inc": {"name": "I"},`, 1)) r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) // Each Session prepares with a distinct prompt warning: an unreadable // rules file carries the path in its message. first := e.workspace("warn-a") second := e.workspace("warn-b") - if err := os.MkdirAll(filepath.Join(second, "AGENTS.md"), 0o700); err != nil { - t.Fatalf("mkdir AGENTS.md collision: %v", err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(second, "AGENTS.md"), 0o700)) sessionA, err := r.createSession(ctx, first, "worker") - if err != nil { - t.Fatalf("createSession A: %v", err) - } + testassert.NoError(t, err) sessionB, err := r.createSession(ctx, second, "worker") - if err != nil { - t.Fatalf("createSession B: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, sessionA.Identity.SessionID, "op-a", "please write") awaitOperation(t, r, sessionA.Identity.SessionID, "op-a", harness.OperationSuccess) submitThroughRuntime(t, r, sessionB.Identity.SessionID, "op-b", "please write") awaitOperation(t, r, sessionB.Identity.SessionID, "op-b", harness.OperationSuccess) all, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } - if warningOf(all.Warnings, "runtime:prompt", "rules_not_found", sessionA.Identity.SessionID) == nil { - t.Fatalf("unfiltered read misses session A's prompt warning: %+v", all.Warnings) - } - if warningOf(all.Warnings, "runtime:prompt", "rules_read_error", sessionB.Identity.SessionID) == nil { - t.Fatalf("unfiltered read misses session B's prompt warning: %+v", all.Warnings) - } - if warningOf(all.Warnings, "runtime:setup", "setup_no_model", "") == nil { - t.Fatalf("unfiltered read misses the global setup warning: %+v", all.Warnings) - } + testassert.NoError(t, err) + testassert.Require(t, warningOf(all.Warnings, "runtime:prompt", "rules_not_found", sessionA.Identity.SessionID) != nil && warningOf(all.Warnings, "runtime:prompt", "rules_read_error", sessionB.Identity.SessionID) != nil && warningOf(all.Warnings, "runtime:setup", "setup_no_model", "") != nil, "unfiltered read misses session A's prompt warning: %+v; unfiltered read misses session B's prompt warning: %+v; unfiltered read misses the global setup warning: %+v", all.Warnings, all.Warnings, all.Warnings) catalogWarning := warningOf(all.Warnings, "runtime:catalog", "incomplete_model", "") - if catalogWarning == nil || !strings.Contains(catalogWarning.Message, "prov/inc") { - t.Fatalf("unfiltered read misses the real catalog warning: %+v", all.Warnings) - } - if warningOf(all.Warnings, "runtime:agents", "invalid_agent_type", "") == nil { - t.Fatalf("unfiltered read misses the global agents warning: %+v", all.Warnings) - } + testassert.Require(t, catalogWarning != nil && strings.Contains(catalogWarning.Message, "prov/inc"), "unfiltered read misses the real catalog warning: %+v", all.Warnings) + testassert.Require(t, warningOf(all.Warnings, "runtime:agents", "invalid_agent_type", "") != nil, "unfiltered read misses the global agents warning: %+v", all.Warnings) hydrationA, err := r.buildHydration(ctx, sessionA.Identity.SessionID) - if err != nil { - t.Fatalf("hydration A: %v", err) - } + testassert.NoError(t, err) hydrationB, err := r.buildHydration(ctx, sessionB.Identity.SessionID) - if err != nil { - t.Fatalf("hydration B: %v", err) - } - if warningOf(hydrationA.Warnings, "runtime:prompt", "rules_not_found", sessionA.Identity.SessionID) == nil { - t.Fatalf("hydration A misses its own prompt warning: %+v", hydrationA.Warnings) - } - if warningOf(hydrationA.Warnings, "runtime:prompt", "rules_read_error", sessionB.Identity.SessionID) != nil { - t.Fatalf("hydration A contains the sibling's prompt warning: %+v", hydrationA.Warnings) - } - if warningOf(hydrationA.Warnings, "runtime:agents", "invalid_agent_type", "") == nil { - t.Fatalf("hydration A misses the global agents warning: %+v", hydrationA.Warnings) - } - if warningOf(hydrationB.Warnings, "runtime:prompt", "rules_read_error", sessionB.Identity.SessionID) == nil { - t.Fatalf("hydration B misses its own prompt warning: %+v", hydrationB.Warnings) - } - if warningOf(hydrationB.Warnings, "runtime:prompt", "rules_not_found", sessionA.Identity.SessionID) != nil { - t.Fatalf("hydration B contains the sibling's prompt warning: %+v", hydrationB.Warnings) - } + testassert.NoError(t, err) + testassert.Require(t, warningOf(hydrationA.Warnings, "runtime:prompt", "rules_not_found", sessionA.Identity.SessionID) != nil && warningOf(hydrationA.Warnings, "runtime:prompt", "rules_read_error", sessionB.Identity.SessionID) == nil && warningOf(hydrationA.Warnings, "runtime:agents", "invalid_agent_type", "") != nil, "hydration A misses its own prompt warning: %+v; hydration A contains the sibling's prompt warning: %+v; hydration A misses the global agents warning: %+v", hydrationA.Warnings, hydrationA.Warnings, hydrationA.Warnings) + testassert.Require(t, warningOf(hydrationB.Warnings, "runtime:prompt", "rules_read_error", sessionB.Identity.SessionID) != nil && warningOf(hydrationB.Warnings, "runtime:prompt", "rules_not_found", sessionA.Identity.SessionID) == nil, "hydration B misses its own prompt warning: %+v; hydration B contains the sibling's prompt warning: %+v", hydrationB.Warnings, hydrationB.Warnings) for _, hydration := range []protocol.Hydration{hydrationA, hydrationB} { - if warningOf(hydration.Warnings, "runtime:setup", "setup_no_model", "") == nil { - t.Fatalf("hydration misses the global setup warning: %+v", hydration.Warnings) - } - if warningOf(hydration.Warnings, "runtime:catalog", "incomplete_model", "") == nil { - t.Fatalf("hydration misses the global catalog warning: %+v", hydration.Warnings) - } - if warningOf(hydration.Warnings, "runtime:agents", "invalid_agent_type", "") == nil { - t.Fatalf("hydration misses the global agents warning: %+v", hydration.Warnings) - } - if hydration.WarningsRevision.Revision != all.WarningsRevision.Revision { - t.Fatalf("hydration warning revision %q != captured %q", hydration.WarningsRevision.Revision, all.WarningsRevision.Revision) - } - if hydration.ConfigurationRevision.Generation != "1" { - t.Fatalf("hydration configuration revision = %q, want the captured generation", hydration.ConfigurationRevision.Generation) - } + testassert.Require(t, warningOf(hydration.Warnings, "runtime:setup", "setup_no_model", "") != nil && warningOf(hydration.Warnings, "runtime:catalog", "incomplete_model", "") != nil && warningOf(hydration.Warnings, "runtime:agents", "invalid_agent_type", "") != nil && hydration.WarningsRevision.Revision == all.WarningsRevision.Revision && hydration.ConfigurationRevision.Generation == "1", "hydration misses the global setup warning: %+v; hydration misses the global catalog warning: %+v; hydration misses the global agents warning: %+v; got %v, want %v; got %v, want %v", hydration.Warnings, hydration.Warnings, hydration.Warnings, hydration.WarningsRevision.Revision, all.WarningsRevision.Revision, hydration.ConfigurationRevision.Generation, "1") } // The warning clock is independent of the Session and configuration // clocks: a later report from the OTHER Session advances this // Session's hydration warning revision while its session revision and // the configuration generation stay unchanged. next, err := r.createSession(ctx, e.workspace("warn-c"), "worker") - if err != nil { - t.Fatalf("createSession C: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, next.Identity.SessionID, "op-c", "please write") awaitOperation(t, r, next.Identity.SessionID, "op-c", harness.OperationSuccess) later, err := r.buildHydration(ctx, sessionA.Identity.SessionID) - if err != nil { - t.Fatalf("hydration A later: %v", err) - } + testassert.NoError(t, err) beforeRevision, revErr := warnRevision(hydrationA.WarningsRevision.Revision) afterRevision, afterErr := warnRevision(later.WarningsRevision.Revision) - if revErr != nil || afterErr != nil || afterRevision <= beforeRevision { - t.Fatalf("the sibling report did not advance the store-wide warning revision: %q → %q (%v, %v)", hydrationA.WarningsRevision.Revision, later.WarningsRevision.Revision, revErr, afterErr) - } - if later.SessionRevision.DurableRevision != hydrationA.SessionRevision.DurableRevision || later.SessionRevision.LocalRevision != hydrationA.SessionRevision.LocalRevision { - t.Fatalf("a warning advance changed the session revision: %+v → %+v", hydrationA.SessionRevision, later.SessionRevision) - } - if later.ConfigurationRevision.Generation != hydrationA.ConfigurationRevision.Generation { - t.Fatalf("a warning advance changed the configuration generation: %q → %q", hydrationA.ConfigurationRevision.Generation, later.ConfigurationRevision.Generation) - } + testassert.Require(t, revErr == nil && afterErr == nil && afterRevision > beforeRevision, "the sibling report did not advance the store-wide warning revision: %q → %q (%v, %v)", hydrationA.WarningsRevision.Revision, later.WarningsRevision.Revision, revErr, afterErr) + testassert.Require(t, later.SessionRevision.DurableRevision == hydrationA.SessionRevision.DurableRevision && later.SessionRevision.LocalRevision == hydrationA.SessionRevision.LocalRevision && later.ConfigurationRevision.Generation == hydrationA.ConfigurationRevision.Generation, "a warning advance changed the session revision: %+v → %+v; got %v, want %v", hydrationA.SessionRevision, later.SessionRevision, later.ConfigurationRevision.Generation, hydrationA.ConfigurationRevision.Generation) // The captured revision is coherent: repeated unfiltered reads with no // store change report the same revision and list. again, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings again: %v", err) - } + testassert.NoError(t, err) stable, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings stable: %v", err) - } - if stable.WarningsRevision.Revision != again.WarningsRevision.Revision || !slices.Equal(warningBodies(stable.Warnings), warningBodies(again.Warnings)) { - t.Fatalf("an unchanged store changed its read: %+v vs %+v", stable, again) - } - if stable.WarningsRevision.Revision != later.WarningsRevision.Revision { - t.Fatalf("the full read's revision %q diverged from the captured hydration's %q", stable.WarningsRevision.Revision, later.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Require(t, stable.WarningsRevision.Revision == again.WarningsRevision.Revision && slices.Equal(warningBodies(stable.Warnings), warningBodies(again.Warnings)) && stable.WarningsRevision.Revision == later.WarningsRevision.Revision, "an unchanged store changed its read: %+v vs %+v; got %v, want %v", stable, again, stable.WarningsRevision.Revision, later.WarningsRevision.Revision) }) } @@ -426,19 +304,13 @@ func TestWarningsModelClosureRecordsThroughRealWork(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) session, err := r.createSession(ctx, e.workspace("protocol-ws"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID protocolSub, err := r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(protocolSub.Close) // Turn 1: the tool call turn succeeds; the follow-up re-encodes the @@ -450,56 +322,38 @@ func TestWarningsModelClosureRecordsThroughRealWork(t *testing.T) { var protocolHint *protocol.WarningChangedEvent for protocolHint == nil { event, ok := nextEvent(t, protocolSub) - if !ok { - t.Fatal("the protocol subscription closed before the diagnostic hint") - } + testassert.Require(t, ok, "the protocol subscription closed before the diagnostic hint") if eventKind(t, event) != "warning_changed" { continue } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } + testassert.NoError(t, err) if scopeKind(t, body.Scope) == "session" { protocolScope, err := body.Scope.AsSessionScope() - if err != nil { - t.Fatalf("session scope: %v", err) - } + testassert.NoError(t, err) if protocolScope.SessionId == sessionID { protocolHint = &body } } } protocolScope, err := protocolHint.Scope.AsSessionScope() - if err != nil { - t.Fatalf("protocol diagnostic hint scope: %v", err) - } - if protocolScope.Workspace == nil || *protocolScope.Workspace != e.workspace("protocol-ws") { - t.Fatalf("protocol diagnostic hint scope = %+v, want the cached workspace identity", protocolHint.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, protocolScope.Workspace != nil && *protocolScope.Workspace == e.workspace("protocol-ws"), "protocol diagnostic hint scope = %+v, want the cached workspace identity", protocolHint.Scope) awaitOperation(t, r, sessionID, "op-1", harness.OperationSuccess) first, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings: %v", err) - } + testassert.NoError(t, err) found := warningOf(first.Warnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) - if found == nil || found.Message == "" { - t.Fatalf("protocol warning after the tool turn = %+v, want the MustPreserve diagnostic for the session", found) - } + testassert.Require(t, found != nil && found.Message != "", "protocol warning after the tool turn = %+v, want the MustPreserve diagnostic for the session", found) revision := first.WarningsRevision.Revision // An identical re-report dedupes: a second identical turn advances // nothing. next, err := r.createSession(ctx, e.workspace("protocol-ws"), "worker") - if err != nil { - t.Fatalf("createSession(next): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, next.Identity.SessionID, "op-2", "please write") awaitOperation(t, r, next.Identity.SessionID, "op-2", harness.OperationSuccess) second, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings second: %v", err) - } + testassert.NoError(t, err) // The second turn's follow-up request has one more message, so its // diagnostic text differs and DOES advance; the identical first // warning's (kind,message) is retained exactly once. @@ -509,13 +363,9 @@ func TestWarningsModelClosureRecordsThroughRealWork(t *testing.T) { firstReports++ } } - if firstReports != 1 { - t.Fatalf("session's identical protocol diagnostics = %d, want one deduped value", firstReports) - } + testassert.Equal(t, firstReports, 1) revisionAfterSecond := second.WarningsRevision.Revision - if revisionAfterSecond == revision { - t.Fatalf("warning revision unchanged after a distinct report: %q", revision) - } + testassert.Require(t, revisionAfterSecond != revision, "warning revision unchanged after a distinct report: %q", revision) // Post-encode transport failure: reload the published configuration // onto a counting endpoint whose first request serves the tool-call @@ -526,25 +376,17 @@ func TestWarningsModelClosureRecordsThroughRealWork(t *testing.T) { failingServer := httptest.NewServer(http.HandlerFunc(attempts.serve)) defer failingServer.Close() writeServiceFile(t, e.configPath, productionConfigDocument(failingServer.URL, "T2")) - if _, err := r.Reload(ctx); err != nil { - t.Fatalf("Reload to the failing endpoint: %v", err) + { + _, err := r.Reload(ctx) + testassert.NoError(t, err) } failing, err := r.createSession(ctx, e.workspace("protocol-ws"), "worker") - if err != nil { - t.Fatalf("createSession(failing): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, failing.Identity.SessionID, "op-3", "please write") awaitOperation(t, r, failing.Identity.SessionID, "op-3", harness.OperationFailure) third, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after failure: %v", err) - } - if warningOf(third.Warnings, "runtime:protocol", "protocol_must_preserve_missing", failing.Identity.SessionID) == nil { - t.Fatalf("the post-encode transport failure dropped its diagnostics: %+v", third.Warnings) - } - if third.WarningsRevision.Revision == revisionAfterSecond { - t.Fatalf("warning revision unchanged after the failed attempt's report: %q", third.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Require(t, warningOf(third.Warnings, "runtime:protocol", "protocol_must_preserve_missing", failing.Identity.SessionID) != nil && third.WarningsRevision.Revision != revisionAfterSecond, "the post-encode transport failure dropped its diagnostics: %+v; warning revision unchanged after the failed attempt's report: %q", third.Warnings, third.WarningsRevision.Revision) }) } @@ -557,7 +399,7 @@ func (e *countingFailingEndpoint) serve(w http.ResponseWriter, r *http.Request) body, _ := io.ReadAll(r.Body) e.count++ if e.count == 1 && lastMessageRole(string(body)) != "tool" { - writeToolCallTurn(w, "prod_write", productionWriteArgs) + writeToolCallTurn(w, "call-1", "prod_write", productionWriteArgs) return } http.Error(w, `{"error":{"message":"denied"}}`, http.StatusUnauthorized) @@ -575,18 +417,14 @@ func TestWarningsCompactClosureRecordsWithPostEncodeFailure(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) // One real existing subject: the direct compact opener records under // this admitted Session identity, and the new // existence-before-publication rule observes the same Harness. session, err := r.createSession(ctx, e.workspace("compact-warn"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sessionID := session.Identity.SessionID sh := newServiceHarness(t) @@ -595,7 +433,7 @@ func TestWarningsCompactClosureRecordsWithPostEncodeFailure(t *testing.T) { http.Error(w, `{"error":{"message":"compact endpoint down"}}`, http.StatusUnauthorized) })) t.Cleanup(server.Close) - writeServiceFile(t, sh.configPath, strings.ReplaceAll(warnCompactConfigDocument, "ENDPOINT", server.URL)) + writeServiceFile(t, sh.configPath, warnCompactConfigDocument(server.URL)) writeServiceFile(t, agents.PathForConfig(sh.configPath), agentsWithCompact(`{"model":"prov/m2"}`)) owner, cancel := context.WithCancel(context.Background()) defer cancel() @@ -603,8 +441,9 @@ func TestWarningsCompactClosureRecordsWithPostEncodeFailure(t *testing.T) { runtimeScope := mustOpenScope(t, comp, owner, ScopeInfo{Kind: ScopeRuntime, DataDir: sh.dataDir}, nil) svc := newConfigurationService(owner, comp, sh.loader, sh.configPath, r.obs) svc.attachWarnings(r.warnings) - if _, err := svc.publish(context.Background()); err != nil { - t.Fatalf("publish: %v", err) + { + _, err := svc.publish(context.Background()) + testassert.NoError(t, err) } ws := newWorkspaceScopes(owner, comp, []*scope{runtimeScope}, r.obs) adapter := newObservationAdapter(r.obs, r.warnings) @@ -614,21 +453,18 @@ func TestWarningsCompactClosureRecordsWithPostEncodeFailure(t *testing.T) { request := compactPrepRequest() request.Session.Identity.SessionID = sessionID prepared, err := p.bind()(context.Background(), request) - if err != nil { - t.Fatalf("bind: %v", err) - } + testassert.NoError(t, err) execution, err := prepared.Open(context.Background(), harness.OperationAdmission{ SessionID: sessionID, OperationID: "op-compact", RequestKind: harness.RequestKindMessage, AdmittedAt: time.Now(), }) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := execution.Close(); err != nil { - t.Errorf("execution close: %v", err) + { + err := execution.Close() + testassert.Check(t, err == nil, "execution close: %v", err) } }() @@ -640,79 +476,59 @@ func TestWarningsCompactClosureRecordsWithPostEncodeFailure(t *testing.T) { {Role: model.RoleAssistant, Source: model.ModelRef{Provider: "prov", Model: "m2"}, ToolCalls: []model.ToolCall{{ID: "call-1", Name: "echo", Arguments: json.RawMessage(`{}`)}}}, }} - if _, err := execution.CompactModel(context.Background(), diagnostic); err == nil { - t.Fatal("compact transport request over the failing endpoint succeeded, want the transport failure") + { + _, err := execution.CompactModel(context.Background(), diagnostic) + testassert.Require(t, err != nil, "compact transport request over the failing endpoint succeeded, want the transport failure") } revision, storeWarnings := r.warnings.hydrate(sessionID) - if warningOf(storeWarnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) == nil { - t.Fatalf("the compact closure dropped its post-encode diagnostics: %+v", storeWarnings) - } + testassert.Require(t, warningOf(storeWarnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) != nil, "the compact closure dropped its post-encode diagnostics: %+v", storeWarnings) // An identical attempt replaces with the same list: no advance. _, _ = execution.CompactModel(context.Background(), diagnostic) deduped, dedupedWarnings := r.warnings.hydrate(sessionID) - if deduped != revision || !slices.Equal(warningBodies(dedupedWarnings), warningBodies(storeWarnings)) { - t.Fatalf("an identical attempt advanced the store: revision %d → %d", revision, deduped) - } + testassert.Require(t, deduped == revision && slices.Equal(warningBodies(dedupedWarnings), warningBodies(storeWarnings)), "an identical attempt advanced the store: revision %d → %d", revision, deduped) // A diagnostic-free attempt is a complete computation: it replaces // the group with nothing and clears the obsolete value. clean := model.Request{Messages: []model.Message{ {Role: model.RoleUser, Content: []model.ContentPart{{Kind: model.PartText, Text: "summarize"}}}, }} - if _, err := execution.CompactModel(context.Background(), clean); err == nil { - t.Fatal("diagnostic-free compact request over the failing endpoint succeeded, want the transport failure") + { + _, err := execution.CompactModel(context.Background(), clean) + testassert.Require(t, err != nil, "diagnostic-free compact request over the failing endpoint succeeded, want the transport failure") } cleared, clearedWarnings := r.warnings.hydrate(sessionID) - if warningOf(clearedWarnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) != nil { - t.Fatalf("the diagnostic-free attempt kept the obsolete diagnostics: %+v", clearedWarnings) - } - if cleared == deduped { - t.Fatalf("clearing the group advanced nothing: revision %d", cleared) - } + testassert.Require(t, warningOf(clearedWarnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) == nil, "the diagnostic-free attempt kept the obsolete diagnostics: %+v", clearedWarnings) + testassert.Require(t, cleared != deduped, "clearing the group advanced nothing: revision %d", cleared) // The committed deletion removes the subject's groups; a late attempt // for the deleted subject cannot reintroduce them. - if _, err := r.archiveSession(ctx, sessionID); err != nil { - t.Fatalf("archive: %v", err) - } - if err := r.deleteSession(ctx, sessionID); err != nil { - t.Fatalf("delete: %v", err) + { + _, err := r.archiveSession(ctx, sessionID) + testassert.NoError(t, err) } + testassert.NoError(t, r.deleteSession(ctx, sessionID)) beforeLate, _ := r.warnings.snapshot() _, _ = execution.CompactModel(context.Background(), diagnostic) afterLate, lateWarnings := r.warnings.snapshot() - if afterLate != beforeLate { - t.Fatalf("a late attempt for the deleted subject advanced the store: %d → %d", beforeLate, afterLate) - } - if warningOf(lateWarnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) != nil { - t.Fatalf("a late attempt resurrected the deleted subject's diagnostics: %+v", lateWarnings) - } + testassert.Equal(t, afterLate, beforeLate) + testassert.Require(t, warningOf(lateWarnings, "runtime:protocol", "protocol_must_preserve_missing", sessionID) == nil, "a late attempt resurrected the deleted subject's diagnostics: %+v", lateWarnings) // Reports after the owner's warning-store closure are ignored. r.warnings.close() _, _ = execution.CompactModel(context.Background(), diagnostic) closed, closedWarnings := r.warnings.snapshot() - if closed != afterLate || !slices.Equal(warningBodies(closedWarnings), warningBodies(lateWarnings)) { - t.Fatalf("a report after closure changed the store") - } + testassert.Require(t, closed == afterLate && slices.Equal(warningBodies(closedWarnings), warningBodies(lateWarnings)), "a report after closure changed the store") }) } // warnCompactConfigDocument points both the conversation and the compact -// model at the same failing endpoint with must-preserve metadata. -const warnCompactConfigDocument = `{ - "providers": { - "prov": { - "transport": {"base_url": "ENDPOINT", "api_key_env": "PREP_COMPACT_KEY"}, - "discovery": false, - "models": { - "m": {"name": "M", "context_window": 4096, "protocol_metadata": {"must_preserve": ["proto_keep"]}}, - "m2": {"name": "M2", "context_window": 2048, "protocol_metadata": {"must_preserve": ["proto_keep"]}} - } - } - } -}` +// model at the given failing endpoint with must-preserve metadata. +func warnCompactConfigDocument(endpoint string) string { + return `{"providers":{"prov":` + nominalProvMember(endpoint, "PREP_COMPACT_KEY", + `{"m":{"name":"M","context_window":4096,"protocol_metadata":{"must_preserve":["proto_keep"]}},`+ + `"m2":{"name":"M2","context_window":2048,"protocol_metadata":{"must_preserve":["proto_keep"]}}}`) + `}}` +} // TestWarningStoreReadOwnsReturnedWarnings pins the shared read producer's // ownership: every returned warning — a global group's nil identity and both @@ -731,22 +547,14 @@ func TestWarningStoreReadOwnsReturnedWarnings(t *testing.T) { // The immutable serialized baseline, taken before any mutation. revision, warnings := store.snapshot() expected, err := json.Marshal(warnings) - if err != nil { - t.Fatalf("marshal baseline: %v", err) - } - if len(warnings) != 3 { - t.Fatalf("baseline warnings = %d (%+v), want the three seeded groups' members", len(warnings), warnings) - } + testassert.NoError(t, err) + testassert.Require(t, len(warnings) == 3, "baseline warnings = %d (%+v), want the three seeded groups' members", len(warnings), warnings) for _, warning := range warnings { switch warning.Source { case "runtime:setup": - if warning.SessionId != nil { - t.Fatalf("global warning carries an identity: %+v", warning) - } + testassert.Require(t, warning.SessionId == nil, "global warning carries an identity: %+v", warning) default: - if warning.SessionId == nil || *warning.SessionId != managedID { - t.Fatalf("%s warning identity = %v, want the owning session", warning.Source, warning.SessionId) - } + testassert.Require(t, warning.SessionId != nil && *warning.SessionId == managedID, "%s warning identity = %v, want the owning session", warning.Source, warning.SessionId) } } @@ -762,16 +570,10 @@ func TestWarningStoreReadOwnsReturnedWarnings(t *testing.T) { // The re-read is unchanged, byte-for-byte, and the revision stayed. again, againWarnings := store.snapshot() - if again != revision { - t.Fatalf("warning revision advanced without a report: %d → %d", revision, again) - } + testassert.Equal(t, again, revision) got, err := json.Marshal(againWarnings) - if err != nil { - t.Fatalf("marshal again: %v", err) - } - if string(got) != string(expected) { - t.Fatalf("a mutation of the returned warnings reached the store:\n%s\nwant\n%s", got, expected) - } + testassert.NoError(t, err) + testassert.Require(t, string(got) == string(expected), "a mutation of the returned warnings reached the store:\n%s\nwant\n%s", got, expected) // The filtered hydration read is owned the same way. _, hydrated := store.hydrate(managedID) @@ -782,9 +584,7 @@ func TestWarningStoreReadOwnsReturnedWarnings(t *testing.T) { } _, stable := store.hydrate(managedID) for _, warning := range stable { - if warning.SessionId != nil && *warning.SessionId != managedID { - t.Fatalf("a mutation of a returned hydration identity reached the store: %+v", warning) - } + testassert.Require(t, warning.SessionId == nil || *warning.SessionId == managedID, "a mutation of a returned hydration identity reached the store: %+v", warning) } // A next identical report still dedupes correctly against the untouched @@ -792,16 +592,10 @@ func TestWarningStoreReadOwnsReturnedWarnings(t *testing.T) { sameID := managedID store.setSessionPrompt(managedID, []protocol.Warning{{Source: "runtime:prompt", Kind: "prompt_k", Message: "prompt_m", SessionId: &sameID}}) final, finalWarnings := store.snapshot() - if final != revision { - t.Fatalf("an identical report advanced the revision: %d → %d", revision, final) - } + testassert.Equal(t, final, revision) finalGot, err := json.Marshal(finalWarnings) - if err != nil { - t.Fatalf("marshal final: %v", err) - } - if string(finalGot) != string(expected) { - t.Fatalf("the identical report changed the store's read:\n%s\nwant\n%s", finalGot, expected) - } + testassert.NoError(t, err) + testassert.Require(t, string(finalGot) == string(expected), "the identical report changed the store's read:\n%s\nwant\n%s", finalGot, expected) } // TestWarningStoreReadsDynamicDeterministicGroups pins the shared read @@ -820,17 +614,16 @@ func TestWarningStoreReadsDynamicDeterministicGroups(t *testing.T) { store.setGlobal("runtime:setup", []protocol.Warning{{Source: "runtime:setup", Kind: "s", Message: "s"}}) revision, warnings := store.snapshot() - if revision != 4 { - t.Fatalf("revision after four distinct groups = %d, want 4", revision) - } + testassert.Equal(t, revision, 4) want := []string{ "plugin:zeta/z/z/", "runtime:setup/s/s/", "runtime:prompt/a/a/" + sessionA, "runtime:prompt/b/b/" + sessionB, } - if got := warningBodies(warnings); !slices.Equal(got, want) { - t.Fatalf("dynamic read order = %v, want %v", got, want) + { + got := warningBodies(warnings) + testassert.Require(t, slices.Equal(got, want), "dynamic read order = %v, want %v", got, want) } // The Session filter carries every global group plus only that Session's @@ -841,16 +634,18 @@ func TestWarningStoreReadsDynamicDeterministicGroups(t *testing.T) { "runtime:setup/s/s/", "runtime:prompt/a/a/" + sessionA, } - if got := warningBodies(hydrated); !slices.Equal(got, wantHydrated) { - t.Fatalf("filtered read = %v, want %v", got, wantHydrated) + { + got := warningBodies(hydrated) + testassert.Require(t, slices.Equal(got, wantHydrated), "filtered read = %v, want %v", got, wantHydrated) } // Replacing with the identical list and clearing an absent group advance // nothing. store.setGlobal("runtime:setup", []protocol.Warning{{Source: "runtime:setup", Kind: "s", Message: "s"}}) store.setGlobal("plugin:absent", nil) - if after, _ := store.snapshot(); after != revision { - t.Fatalf("an identical or absent-clear report advanced the revision: %d → %d", revision, after) + { + after, _ := store.snapshot() + testassert.Equal(t, after, revision) } } @@ -865,42 +660,28 @@ func TestWarningStoreReplacementAlgebra(t *testing.T) { store.setSessionProtocol(id, []protocol.Warning{{Source: "runtime:protocol", Kind: "k1", Message: "m1", SessionId: &id}}) store.setSessionProtocol(id, []protocol.Warning{{Source: "runtime:protocol", Kind: "k2", Message: "m2", SessionId: &id}}) _, warnings := store.snapshot() - if warningOf(warnings, "runtime:protocol", "k1", id) != nil { - t.Fatalf("a new attempt kept the previous attempt's diagnostics: %+v", warnings) - } - if warningOf(warnings, "runtime:protocol", "k2", id) == nil { - t.Fatalf("the new attempt's diagnostics are missing: %+v", warnings) - } + testassert.Require(t, warningOf(warnings, "runtime:protocol", "k1", id) == nil && warningOf(warnings, "runtime:protocol", "k2", id) != nil, "a new attempt kept the previous attempt's diagnostics: %+v; the new attempt's diagnostics are missing: %+v", warnings, warnings) store.setSessionProtocol(id, nil) _, cleared := store.snapshot() - if warningOf(cleared, "runtime:protocol", "k2", id) != nil { - t.Fatalf("an empty attempt kept the obsolete diagnostics: %+v", cleared) - } + testassert.Require(t, warningOf(cleared, "runtime:protocol", "k2", id) == nil, "an empty attempt kept the obsolete diagnostics: %+v", cleared) store.appendPlugin("lsp", "notice", "one") store.appendPlugin("lsp", "notice", "one") store.appendPlugin("lsp", "other", "two") _, plugins := store.snapshot() - if warningOf(plugins, "plugin:lsp", "notice", "") == nil || warningOf(plugins, "plugin:lsp", "other", "") == nil { - t.Fatalf("plugin reports are missing from their own global group: %+v", plugins) - } + testassert.Require(t, warningOf(plugins, "plugin:lsp", "notice", "") != nil && warningOf(plugins, "plugin:lsp", "other", "") != nil, "plugin reports are missing from their own global group: %+v", plugins) count := 0 for _, warning := range plugins { if string(warning.Source) == "plugin:lsp" { count++ } } - if count != 2 { - t.Fatalf("plugin group members after the identical report = %d, want the deduped pair", count) - } + testassert.Equal(t, count, 2) // A nil store is inert for every mutation: the shared core's nil guard // covers the append path too. var absent *warningStore - if absent.appendPlugin("lsp", "k", "m") || absent.setGlobal("runtime:setup", nil) || - absent.setSessionProtocol(id, nil) || absent.removeSessions([]string{id}) { - t.Fatal("a nil store mutated") - } + testassert.Require(t, !absent.appendPlugin("lsp", "k", "m") && !absent.setGlobal("runtime:setup", nil) && !absent.setSessionProtocol(id, nil) && !absent.removeSessions([]string{id}), "a nil store mutated") } // TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup proves prompt @@ -914,17 +695,13 @@ func TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup(t *testing.T) { wrapped := newSweepStore(e.store) e.store = wrapped r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) // A real committed source with a valid boundary: the source's own // successful preparation already published its prompt group. source, err := r.createSession(ctx, e.workspace("orphan-src"), "worker") - if err != nil { - t.Fatalf("createSession: %v", err) - } + testassert.NoError(t, err) sourceID := source.Identity.SessionID submitThroughRuntime(t, r, sourceID, "op-src", "boundary") awaitOperation(t, r, sourceID, "op-src", harness.OperationSuccess) @@ -933,9 +710,7 @@ func TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup(t *testing.T) { boundaryItem := projectItemID(sourceID, commandUserInputEntry(t, snap, "op-src")) beforeFork, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings before fork: %v", err) - } + testassert.NoError(t, err) forkFailure := errors.New("test fork transaction failure") wrapped.armFailAfterRelease(forkFailure) forkDone := make(chan error, 1) @@ -953,16 +728,13 @@ func TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup(t *testing.T) { t.Fatal("the failed fork never reached its transaction") } wrapped.releaseBlock() - if err := <-forkDone; !errors.Is(err, forkFailure) { - t.Fatalf("failed fork = %v, want the injected transaction failure", err) + { + err := <-forkDone + testassert.Require(t, errors.Is(err, forkFailure), "failed fork = %v, want the injected transaction failure", err) } afterFork, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after fork: %v", err) - } - if afterFork.WarningsRevision.Revision != beforeFork.WarningsRevision.Revision { - t.Fatalf("failed fork advanced the warning revision: %s → %s", beforeFork.WarningsRevision.Revision, afterFork.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, afterFork.WarningsRevision.Revision, beforeFork.WarningsRevision.Revision) if !slices.Equal(warningBodies(beforeFork.Warnings), warningBodies(afterFork.Warnings)) { t.Fatalf("failed fork changed the warning store:\n%v\n%v", warningBodies(beforeFork.Warnings), warningBodies(afterFork.Warnings)) } @@ -971,18 +743,14 @@ func TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup(t *testing.T) { // transaction fails, and the preparation's prompt values stay // unpublished. victim, err := r.createSession(ctx, e.workspace("orphan-admit"), "worker") - if err != nil { - t.Fatalf("createSession(victim): %v", err) - } + testassert.NoError(t, err) beforeAdmit, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings before admission: %v", err) - } + testassert.NoError(t, err) admissionFailure := errors.New("test admission transaction failure") wrapped.armFailAfterRelease(admissionFailure) admitDone := make(chan error, 1) go func() { - admitDone <- submitExpectFailure(r, victim.Identity.SessionID, "op-orphan-admit") + admitDone <- submitExpectFailure(r, victim.Identity.SessionID, "op-orphan-admit", "hello") }() select { case <-wrapped.arrived: @@ -990,16 +758,13 @@ func TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup(t *testing.T) { t.Fatal("the failed admission never reached its transaction") } wrapped.releaseBlock() - if err := <-admitDone; !errors.Is(err, admissionFailure) { - t.Fatalf("failed admission = %v, want the injected transaction failure", err) + { + err := <-admitDone + testassert.Require(t, errors.Is(err, admissionFailure), "failed admission = %v, want the injected transaction failure", err) } afterAdmit, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after admission: %v", err) - } - if afterAdmit.WarningsRevision.Revision != beforeAdmit.WarningsRevision.Revision { - t.Fatalf("failed admission advanced the warning revision: %s → %s", beforeAdmit.WarningsRevision.Revision, afterAdmit.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, afterAdmit.WarningsRevision.Revision, beforeAdmit.WarningsRevision.Revision) if !slices.Equal(warningBodies(beforeAdmit.Warnings), warningBodies(afterAdmit.Warnings)) { t.Fatalf("failed admission changed the warning store:\n%v\n%v", warningBodies(beforeAdmit.Warnings), warningBodies(afterAdmit.Warnings)) } @@ -1007,21 +772,12 @@ func TestWarningsFailedForkAndAdmissionCreateNoOrphanGroup(t *testing.T) { // The oracle flips: an unarmed successful admission publishes its own // Session's prompt group and advances the revision. good, err := r.createSession(ctx, e.workspace("orphan-ok"), "worker") - if err != nil { - t.Fatalf("createSession(good): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, good.Identity.SessionID, "op-good", "please write") awaitOperation(t, r, good.Identity.SessionID, "op-good", harness.OperationSuccess) afterSuccess, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after success: %v", err) - } - if afterSuccess.WarningsRevision.Revision == afterAdmit.WarningsRevision.Revision { - t.Fatalf("a successful admission did not publish its prompt group: revision stayed %s", afterSuccess.WarningsRevision.Revision) - } - if warningOf(afterSuccess.Warnings, "runtime:prompt", "rules_not_found", good.Identity.SessionID) == nil { - t.Fatalf("the successful admission's prompt group is missing: %+v", afterSuccess.Warnings) - } + testassert.NoError(t, err) + testassert.Require(t, afterSuccess.WarningsRevision.Revision != afterAdmit.WarningsRevision.Revision && warningOf(afterSuccess.Warnings, "runtime:prompt", "rules_not_found", good.Identity.SessionID) != nil, "a successful admission did not publish its prompt group: revision stayed %s; the successful admission's prompt group is missing: %+v", afterSuccess.WarningsRevision.Revision, afterSuccess.Warnings) }) } @@ -1034,19 +790,13 @@ func TestWarningsDeleteArchiveAndLateReportLifetime(t *testing.T) { eachProductionStore(t, func(t *testing.T, e *productionEnv) { ctx := context.Background() r, err := e.open(ctx, e.hookedHook(), &parkingHook{}) - if err != nil { - t.Fatalf("Open: %v", err) - } + testassert.NoError(t, err) defer closeProjectionRuntime(r) victim, err := r.createSession(ctx, e.workspace("warn-delete"), "worker") - if err != nil { - t.Fatalf("createSession(victim): %v", err) - } + testassert.NoError(t, err) sibling, err := r.createSession(ctx, e.workspace("warn-keep"), "worker") - if err != nil { - t.Fatalf("createSession(sibling): %v", err) - } + testassert.NoError(t, err) victimID, siblingID := victim.Identity.SessionID, sibling.Identity.SessionID submitThroughRuntime(t, r, victimID, "op-victim", "please write") awaitOperation(t, r, victimID, "op-victim", harness.OperationSuccess) @@ -1055,71 +805,46 @@ func TestWarningsDeleteArchiveAndLateReportLifetime(t *testing.T) { // Archive retains the group; hydration of the archived subject still // carries it. - if _, err := r.archiveSession(ctx, victimID); err != nil { - t.Fatalf("archive: %v", err) + { + _, err := r.archiveSession(ctx, victimID) + testassert.NoError(t, err) } archived, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after archive: %v", err) - } - if !sessionWarningPresent(archived.Warnings, victimID) { - t.Fatalf("archive dropped the Session's warning group: %+v", archived.Warnings) - } + testassert.NoError(t, err) + testassert.Require(t, sessionWarningPresent(archived.Warnings, victimID), "archive dropped the Session's warning group: %+v", archived.Warnings) hydration, err := r.buildHydration(ctx, victimID) - if err != nil { - t.Fatalf("hydration of the archived subject: %v", err) - } - if !sessionWarningPresent(hydration.Warnings, victimID) { - t.Fatalf("the archived subject's hydration dropped its group: %+v", hydration.Warnings) - } + testassert.NoError(t, err) + testassert.Require(t, sessionWarningPresent(hydration.Warnings, victimID), "the archived subject's hydration dropped its group: %+v", hydration.Warnings) sub, err := r.Subscribe(256) - if err != nil { - t.Fatalf("Subscribe: %v", err) - } + testassert.NoError(t, err) t.Cleanup(sub.Close) baseRevision, _ := r.warnings.snapshot() // The committed deletion removes the group in one observation section // and publishes exactly one runtime-scoped hint. - if err := r.deleteSession(ctx, victimID); err != nil { - t.Fatalf("delete: %v", err) - } + testassert.NoError(t, r.deleteSession(ctx, victimID)) afterDelete, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after delete: %v", err) - } - if sessionWarningPresent(afterDelete.Warnings, victimID) { - t.Fatalf("the committed deletion kept the Session's group: %+v", afterDelete.Warnings) - } - if warningOf(afterDelete.Warnings, "runtime:prompt", "rules_not_found", siblingID) == nil { - t.Fatalf("the deletion removed the sibling's group: %+v", afterDelete.Warnings) - } - if _, err := r.buildHydration(ctx, victimID); !errors.Is(err, harness.ErrNotFound) { - t.Fatalf("hydration of the deleted subject = %v, want the typed absence", err) + testassert.NoError(t, err) + testassert.Require(t, !sessionWarningPresent(afterDelete.Warnings, victimID) && warningOf(afterDelete.Warnings, "runtime:prompt", "rules_not_found", siblingID) != nil, "the committed deletion kept the Session's group: %+v; the deletion removed the sibling's group: %+v", afterDelete.Warnings, afterDelete.Warnings) + { + _, err := r.buildHydration(ctx, victimID) + testassert.Require(t, errors.Is(err, harness.ErrNotFound), "hydration of the deleted subject = %v, want the typed absence", err) } var hint *protocol.WarningChangedEvent for hint == nil { event, ok := nextEvent(t, sub) - if !ok { - t.Fatal("the subscription closed before the deletion's warning hint") - } + testassert.Require(t, ok, "the subscription closed before the deletion's warning hint") if eventKind(t, event) != "warning_changed" { continue } body, err := event.AsWarningChangedEvent() - if err != nil { - t.Fatalf("warning event body: %v", err) - } - if scopeKind(t, body.Scope) != "runtime" { - t.Fatalf("deletion warning hint scope = %+v, want the runtime scope", body.Scope) - } + testassert.NoError(t, err) + testassert.Require(t, scopeKind(t, body.Scope) == "runtime", "deletion warning hint scope = %+v, want the runtime scope", body.Scope) hint = &body } - if hint.WarningsRevision.Revision != strconv.FormatUint(baseRevision+1, 10) { - t.Fatalf("deletion warning hint revision = %q, want the single advance from %d", hint.WarningsRevision.Revision, baseRevision) - } + testassert.Require(t, hint.WarningsRevision.Revision == strconv.FormatUint(baseRevision+1, 10), "deletion warning hint revision = %q, want the single advance from %d", hint.WarningsRevision.Revision, baseRevision) assertNoEvent(t, sub) // Late reports for the deleted subject through a real adapter bound to @@ -1132,12 +857,8 @@ func TestWarningsDeleteArchiveAndLateReportLifetime(t *testing.T) { late.replaceProtocolWarnings(lateID, []protocol.Warning{{Source: "runtime:protocol", Kind: "late_protocol", Message: "late", SessionId: &lateID}}) assertNoEvent(t, sub) lateRead, err := r.getWarnings(ctx) - if err != nil { - t.Fatalf("getWarnings after late reports: %v", err) - } - if lateRead.WarningsRevision.Revision != afterDelete.WarningsRevision.Revision { - t.Fatalf("a late report advanced the warning revision: %s → %s", afterDelete.WarningsRevision.Revision, lateRead.WarningsRevision.Revision) - } + testassert.NoError(t, err) + testassert.Equal(t, lateRead.WarningsRevision.Revision, afterDelete.WarningsRevision.Revision) if !slices.Equal(warningBodies(afterDelete.Warnings), warningBodies(lateRead.Warnings)) { t.Fatalf("a late report changed the store:\n%v\n%v", warningBodies(afterDelete.Warnings), warningBodies(lateRead.Warnings)) } @@ -1145,28 +866,22 @@ func TestWarningsDeleteArchiveAndLateReportLifetime(t *testing.T) { // A committed deletion with no owned groups is a store no-op: no // revision advance and no warning hint. bare, err := r.createSession(ctx, e.workspace("warn-bare"), "worker") - if err != nil { - t.Fatalf("createSession(bare): %v", err) - } - if _, err := r.archiveSession(ctx, bare.Identity.SessionID); err != nil { - t.Fatalf("archive(bare): %v", err) + testassert.NoError(t, err) + { + _, err := r.archiveSession(ctx, bare.Identity.SessionID) + testassert.NoError(t, err) } beforeBare, _ := r.warnings.snapshot() - if err := r.deleteSession(ctx, bare.Identity.SessionID); err != nil { - t.Fatalf("delete(bare): %v", err) - } - if afterBare, _ := r.warnings.snapshot(); afterBare != beforeBare { - t.Fatalf("a no-op deletion advanced the warning revision: %d → %d", beforeBare, afterBare) + testassert.NoError(t, r.deleteSession(ctx, bare.Identity.SessionID)) + { + afterBare, _ := r.warnings.snapshot() + testassert.Equal(t, afterBare, beforeBare) } for { select { case event, ok := <-sub.Events(): - if !ok { - t.Fatal("the subscription closed around the no-op deletion") - } - if eventKind(t, event) == "warning_changed" { - t.Fatalf("a no-op deletion published a warning hint: %+v", event) - } + testassert.Require(t, ok, "the subscription closed around the no-op deletion") + testassert.Require(t, eventKind(t, event) != "warning_changed", "a no-op deletion published a warning hint: %+v", event) continue default: } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index 4e71730c..323b6202 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -20,6 +20,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" ) @@ -33,7 +34,8 @@ import ( // server's keyless local endpoint with discovery disabled and a generous // window so ordinary rows never accidentally test compaction. func ownerConfigDocument(endpoint string) string { - return `{"providers":{"prov":{"transport":{"base_url":"` + endpoint + `","api_key_env":""},"discovery":false,"models":{"m":{"name":"M","context_window":262144,"max_output_tokens":4096}}}}}` + return `{"providers":{"prov":` + nominalProvMember(endpoint, "", + `{"m":{"name":"M","context_window":262144,"max_output_tokens":4096}}`) + `}}` } func ownerGatedConfigDocument(endpoint string) string { @@ -81,9 +83,7 @@ func newOwnerEnvIn(t *testing.T, home, dataDir string) *ownerEnv { func isolateBundledCredentials(t *testing.T) { t.Helper() entries, err := os.ReadDir(filepath.Join("..", "internal", "catalog", "builtin")) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue @@ -115,6 +115,18 @@ func (e *ownerEnv) open(ctx context.Context, plugins ...Plugin) (*Runtime, error return open(ctx, e.options(plugins...)) } +// openSwept opens one Runtime with a test-driven sweep clock: the returned +// channel is the only tick source, so the automatic lifecycle sweep never runs +// until a test sends. +func (e *ownerEnv) openSwept(ctx context.Context, plugins ...Plugin) (*Runtime, chan time.Time, error) { + e.t.Helper() + ticks := make(chan time.Time) + opts := e.options(plugins...) + opts.sweepTicks = ticks + r, err := open(ctx, opts) + return r, ticks, err +} + // corePlugin is one Runtime-scoped plugin whose single export is declared // exactly as harness.Storage: the private Core binding. Its Close records the // disposal event only; the test retains the store itself. @@ -275,35 +287,29 @@ type nilStorage struct{ harness.Storage } func wantFailedOpen(t *testing.T, e *ownerEnv, plugins []Plugin, wantIs error) { t.Helper() r, err := e.open(context.Background(), plugins...) - if r != nil { - t.Fatalf("failed construction returned a non-nil Runtime with error %v", err) - } - if !errors.Is(err, wantIs) { - t.Fatalf("open error = %v, want %v", err, wantIs) - } + testassert.Require(t, r == nil, "failed construction returned a non-nil Runtime with error %v", err) + testassert.Require(t, errors.Is(err, wantIs), "open error = %v, want %v", err, wantIs) } // assertLockReleased proves the failed attempt released the ownership lock. func (e *ownerEnv) assertLockReleased(t *testing.T) { t.Helper() r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("reopen after the failed attempt: %v (a resource or the ownership lock was left behind)", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(context.Background())) } // assertNoOwnershipSideEffects proves a pure declaration rejection touched // neither the filesystem nor any factory. func (e *ownerEnv) assertNoOwnershipSideEffects(t *testing.T) { t.Helper() - if _, err := os.Stat(filepath.Join(e.dataDir, "runtime.lock")); !os.IsNotExist(err) { - t.Fatalf("declaration validation reached the filesystem or ownership: stat err = %v", err) + { + _, err := os.Stat(filepath.Join(e.dataDir, "runtime.lock")) + testassert.Require(t, os.IsNotExist(err), "declaration validation reached the filesystem or ownership: stat err = %v", err) } - if events := e.events.all(); len(events) != 0 { - t.Fatalf("declaration validation invoked factories: %v", events) + { + events := e.events.all() + testassert.Require(t, len(events) == 0, "declaration validation invoked factories: %v", events) } } @@ -341,27 +347,30 @@ func eventsNamed(events []string, name string) int { return n } -func submitThroughRuntime(t *testing.T, r *Runtime, sessionID, operationID, text string) { - t.Helper() - err := r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { - res, err := h.Submit(ctx, harness.SubmitRequest{ +// submitRawThroughRuntime runs one user submission in the given mode through +// the Runtime's admission gate and returns the reported result and error +// unchanged, exactly as the Harness reports them. +func submitRawThroughRuntime(ctx context.Context, r *Runtime, sessionID, operationID, text string, mode harness.MessageMode) (harness.SubmitResult, error) { + var res harness.SubmitResult + err := r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + var err error + res, err = h.Submit(ctx, harness.SubmitRequest{ SessionID: sessionID, OperationID: operationID, Origin: harness.InputOriginUser, Content: []model.ContentPart{{Kind: model.PartText, Text: text}}, - Mode: harness.MessageModeRegular, + Mode: mode, }) - if err != nil { - return err - } - if res.Disposition != harness.DispositionAdmitted { - return fmt.Errorf("submit %q = %+v, want admitted", operationID, res.Disposition) - } - return nil + return err }) - if err != nil { - t.Fatalf("Submit(%q): %v", operationID, err) - } + return res, err +} + +func submitThroughRuntime(t *testing.T, r *Runtime, sessionID, operationID, text string) { + t.Helper() + res, err := submitRawThroughRuntime(context.Background(), r, sessionID, operationID, text, harness.MessageModeRegular) + testassert.Require(t, err == nil, "Submit(%q): %v", operationID, err) + testassert.Require(t, res.Disposition == harness.DispositionAdmitted, "submit %q = %+v, want admitted", operationID, res.Disposition) } func readOperation(t *testing.T, r *Runtime, sessionID, operationID string) harness.OperationRecord { @@ -372,12 +381,19 @@ func readOperation(t *testing.T, r *Runtime, sessionID, operationID string) harn rec, err = h.ReadOperation(ctx, sessionID, operationID) return err }) - if err != nil { - t.Fatalf("ReadOperation(%q): %v", operationID, err) - } + testassert.Require(t, err == nil, "ReadOperation(%q): %v", operationID, err) return rec } +// readOperationError reads one Operation through the Runtime's admission gate +// and returns its typed error unchanged. +func readOperationError(ctx context.Context, r *Runtime, sessionID, operationID string) error { + return r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { + _, err := h.ReadOperation(ctx, sessionID, operationID) + return err + }) +} + // committedAdmission renders one committed Operation's admission as the // agentType=model@revision triple its durable capture carries. func committedAdmission(t *testing.T, r *Runtime, sessionID, operationID string) string { @@ -400,7 +416,7 @@ func seedRunningOperation(t *testing.T, store harness.Storage, workspace string) Prepare: func(context.Context, harness.PreparationRequest) (harness.PreparedExecution, error) { return harness.PreparedExecution{ Capture: harness.ExecutionCapture{ConfigurationRevision: "1", Model: prepModelRef, ContextWindow: 4096, OutputReserve: 2048, SystemPrompt: "seeded", - Compact: harness.CompactCapture{Model: prepModelRef, ContextWindow: 2048, OutputReserve: 1024, SystemPrompt: "summarize"}}, + Compact: harness.CompactCapture{Model: prepModelRef, SystemPrompt: "summarize"}}, Open: func(context.Context, harness.OperationAdmission) (harness.Execution, error) { modelFn := func(context.Context, model.Request) (model.Stream, error) { select { @@ -422,18 +438,15 @@ func seedRunningOperation(t *testing.T, store harness.Storage, workspace string) }, nil }, }) - if err != nil { - t.Fatalf("seed harness.New: %v", err) - } + testassert.NoError(t, err) rec, err := h.CreateSession(context.Background(), harness.CreateSessionRequest{Workspace: workspace, AgentType: "seeded"}) - if err != nil { - t.Fatalf("seed CreateSession: %v", err) - } - if _, err := h.Submit(context.Background(), harness.SubmitRequest{ - SessionID: rec.Identity.SessionID, OperationID: "seed-op", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "running"}}, Mode: harness.MessageModeRegular, - }); err != nil { - t.Fatalf("seed Submit: %v", err) + testassert.NoError(t, err) + { + _, err := h.Submit(context.Background(), harness.SubmitRequest{ + SessionID: rec.Identity.SessionID, OperationID: "seed-op", Origin: harness.InputOriginUser, + Content: []model.ContentPart{{Kind: model.PartText, Text: "running"}}, Mode: harness.MessageModeRegular, + }) + testassert.NoError(t, err) } select { case <-arrived: @@ -457,9 +470,7 @@ func TestRuntimeOpenValidatesOptionsAndNormalizesPaths(t *testing.T) { } for _, tc := range cases { r, err := open(context.Background(), tc.opts) - if r != nil || err == nil { - t.Fatalf("open(%s) = (%v, %v), want a nil Runtime and an error", tc.name, r, err) - } + testassert.Require(t, r == nil && err != nil, "open(%s) = (%v, %v), want a nil Runtime and an error", tc.name, r, err) e.assertNoOwnershipSideEffects(t) } }) @@ -469,45 +480,36 @@ func TestRuntimeOpenValidatesOptionsAndNormalizesPaths(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() r, err := e.open(ctx, e.storagePlugin(storage.NewMemory())) - if r != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("open = (%v, %v), want (nil, context.Canceled)", r, err) - } + testassert.Require(t, r == nil && errors.Is(err, context.Canceled), "open = (%v, %v), want (nil, context.Canceled)", r, err) e.assertNoOwnershipSideEffects(t) }) t.Run("relative paths normalize through one filepath.Abs pass", func(t *testing.T) { e := newOwnerEnv(t) cwd, err := os.Getwd() - if err != nil { - t.Fatalf("Getwd: %v", err) - } + testassert.NoError(t, err) relData, err := filepath.Rel(cwd, e.dataDir) - if err != nil { - t.Fatalf("Rel: %v", err) - } + testassert.NoError(t, err) r, err := open(context.Background(), options{ DataDir: relData, ConfigPath: filepath.Join(relData, "config.json"), Plugins: []Plugin{e.sqliteDerivationPlugin()}, }) - if err != nil { - t.Fatalf("open(relative): %v", err) - } - if events := e.events.all(); !slices.Equal(eventNames(events), []string{"open:db"}) { - t.Fatalf("factory events = %v, want exactly the Core factory", events) + testassert.NoError(t, err) + { + events := e.events.all() + testassert.Require(t, slices.Equal(eventNames(events), []string{"open:db"}), "factory events = %v, want exactly the Core factory", events) } - if got := e.scopeDataDir.Load().(string); got != e.dataDir { - t.Fatalf("ScopeInfo DataDir = %q, want the same normalized owner root %q", got, e.dataDir) + testassert.Equal(t, e.scopeDataDir.Load().(string), e.dataDir) + { + _, err := os.Stat(filepath.Join(e.dataDir, "runtime.lock")) + testassert.NoError(t, err) } - if _, err := os.Stat(filepath.Join(e.dataDir, "runtime.lock")); err != nil { - t.Fatalf("lock under the normalized root: %v", err) - } - if _, err := os.Stat(filepath.Join(e.dataDir, "lightcode.db")); err != nil { - t.Fatalf("the storage backend derives its database from the same root: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + _, err := os.Stat(filepath.Join(e.dataDir, "lightcode.db")) + testassert.NoError(t, err) } + testassert.NoError(t, r.Close(context.Background())) }) t.Run("one data root with an independent config path", func(t *testing.T) { @@ -518,18 +520,16 @@ func TestRuntimeOpenValidatesOptionsAndNormalizesPaths(t *testing.T) { writeServiceFile(t, agents.PathForConfig(configPath), ownerAgentsDocument) e.configPath = configPath r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) + testassert.NoError(t, err) + { + _, err := os.Stat(filepath.Join(configDir, "agents.json")) + testassert.NoError(t, err) } - if _, err := os.Stat(filepath.Join(configDir, "agents.json")); err != nil { - t.Fatalf("agents.json does not stay beside the config path: %v", err) - } - if _, err := os.Stat(filepath.Join(e.home, ".lightcode", ".env")); err != nil { - t.Fatalf("the managed dotenv source is not home-based: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + _, err := os.Stat(filepath.Join(e.home, ".lightcode", ".env")) + testassert.NoError(t, err) } + testassert.NoError(t, r.Close(context.Background())) }) } @@ -538,9 +538,7 @@ func TestRuntimeOpenValidatesOptionsAndNormalizesPaths(t *testing.T) { func TestRuntimeOwnershipLockIsExclusive(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) // A contender with its own configuration override still targets the same // owner root: identity is the normalized data directory. rival := newOwnerEnvIn(t, e.home, e.dataDir) @@ -552,16 +550,10 @@ func TestRuntimeOwnershipLockIsExclusive(t *testing.T) { _ = r.Close(context.Background()) t.Fatalf("contender factory events = %v, want no factory invoked while losing ownership", rival.events.all()) } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) again, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("reopen after release: %v", err) - } - if err := again.Close(context.Background()); err != nil { - t.Fatalf("Close after reacquire: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, again.Close(context.Background())) } const lockChildEnv = "LIGHTCODE_RUNTIME_LOCK_CHILD" @@ -576,14 +568,10 @@ func TestRuntimeOwnershipLockSpansProcessExit(t *testing.T) { if dir := os.Getenv(lockChildEnv); dir != "" { e := newOwnerEnvIn(t, os.Getenv("HOME"), dir) r, err := e.open(context.Background(), e.sqliteDerivationPlugin()) - if err != nil { - t.Fatalf("child open: %v", err) - } + testassert.NoError(t, err) fmt.Println("acquired") _, _ = io.Copy(io.Discard, os.Stdin) - if err := r.Close(context.Background()); err != nil { - t.Fatalf("child Close: %v", err) - } + testassert.NoError(t, r.Close(context.Background())) fmt.Println("closed") return } @@ -602,16 +590,10 @@ func TestRuntimeOwnershipLockSpansProcessExit(t *testing.T) { cmd := exec.Command(os.Args[0], "-test.run=^"+t.Name()+"$", "-test.timeout=120s") cmd.Env = append(os.Environ(), lockChildEnv+"="+dataDir) stdin, err := cmd.StdinPipe() - if err != nil { - t.Fatalf("StdinPipe: %v", err) - } + testassert.NoError(t, err) stdout, err := cmd.StdoutPipe() - if err != nil { - t.Fatalf("StdoutPipe: %v", err) - } - if err := cmd.Start(); err != nil { - t.Fatalf("Start: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, cmd.Start()) c := &contender{cmd: cmd, env: parent, stdin: stdin, stdout: bufio.NewScanner(stdout)} for c.stdout.Scan() { if c.stdout.Text() == "acquired" { @@ -635,26 +617,19 @@ func TestRuntimeOwnershipLockSpansProcessExit(t *testing.T) { _ = c.cmd.Wait() t.Fatalf("contender factory events = %v, want none while another process owns the state", events) } - if err := c.cmd.Process.Kill(); err != nil { - t.Fatalf("Kill: %v", err) - } - if err := c.cmd.Wait(); err == nil { - t.Fatal("the killed child reported success") + testassert.NoError(t, c.cmd.Process.Kill()) + { + err := c.cmd.Wait() + testassert.Require(t, err != nil, "the killed child reported success") } r, err := c.env.open(context.Background(), c.env.sqliteDerivationPlugin()) - if err != nil { - t.Fatalf("open against the durable state left by the killed owner: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(context.Background())) }) t.Run("managed shutdown in another process releases ownership", func(t *testing.T) { c := start(t) - if err := c.stdin.Close(); err != nil { - t.Fatalf("close stdin: %v", err) - } + testassert.NoError(t, c.stdin.Close()) closed := false for c.stdout.Scan() { if c.stdout.Text() == "closed" { @@ -662,16 +637,13 @@ func TestRuntimeOwnershipLockSpansProcessExit(t *testing.T) { break } } - if err := c.cmd.Wait(); err != nil || !closed { - t.Fatalf("child managed shutdown: scanned-closed=%v wait=%v", closed, err) + { + err := c.cmd.Wait() + testassert.Require(t, err == nil && closed, "child managed shutdown: scanned-closed=%v wait=%v", closed, err) } r, err := c.env.open(context.Background(), c.env.sqliteDerivationPlugin()) - if err != nil { - t.Fatalf("open after the owner exited: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(context.Background())) }) } @@ -690,8 +662,9 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { e := newOwnerEnv(t) writeServiceFile(t, e.configPath, `{not json`) wantFailedOpen(t, e, []Plugin{e.storagePlugin(storage.NewMemory())}, ErrConfiguration) - if events := e.events.all(); len(events) != 0 { - t.Fatalf("factory events = %v, want the configuration rejection to precede any factory", events) + { + events := e.events.all() + testassert.Require(t, len(events) == 0, "factory events = %v, want the configuration rejection to precede any factory", events) } writeServiceFile(t, e.configPath, ownerConfigDocument(e.server.URL)) e.assertLockReleased(t) @@ -706,17 +679,16 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { } wantFailedOpen(t, e, []Plugin{e.storagePlugin(storage.NewMemory()), e.ordinaryPlugin("first", ScopeRuntime, "first.cap"), failing}, errFactory) events := eventNames(e.events.all()) - if !orderedSubset(events, "close:first") || slices.Contains(events, "close:failing") { - t.Fatalf("factory events = %v, want only the constructed prefix disposed", events) - } + testassert.Require(t, orderedSubset(events, "close:first") && !slices.Contains(events, "close:failing"), "factory events = %v, want only the constructed prefix disposed", events) e.assertLockReleased(t) }) t.Run("untyped-nil Core storage rolls back through its closer", func(t *testing.T) { e := newOwnerEnv(t) wantFailedOpen(t, e, []Plugin{e.storagePlugin(nil)}, ErrComposition) - if events := eventNames(e.events.all()); !slices.Contains(events, "close:core") { - t.Fatalf("factory events = %v, want the invalid instance disposed", events) + { + events := eventNames(e.events.all()) + testassert.Require(t, slices.Contains(events, "close:core"), "factory events = %v, want the invalid instance disposed", events) } e.assertLockReleased(t) }) @@ -724,8 +696,9 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { t.Run("typed-nil Core storage rolls back through its closer", func(t *testing.T) { e := newOwnerEnv(t) wantFailedOpen(t, e, []Plugin{e.storagePlugin((*nilStorage)(nil))}, ErrComposition) - if events := eventNames(e.events.all()); !slices.Contains(events, "close:core") { - t.Fatalf("factory events = %v, want the invalid instance disposed", events) + { + events := eventNames(e.events.all()) + testassert.Require(t, slices.Contains(events, "close:core"), "factory events = %v, want the invalid instance disposed", events) } e.assertLockReleased(t) }) @@ -735,8 +708,9 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { errRecover := errors.New("test recovery failure") store := &failingListStore{Storage: storage.NewMemory(), err: errRecover} wantFailedOpen(t, e, []Plugin{e.storagePlugin(store)}, errRecover) - if events := eventNames(e.events.all()); !slices.Contains(events, "close:core") { - t.Fatalf("factory events = %v, want the constructed Runtime scope disposed", events) + { + events := eventNames(e.events.all()) + testassert.Require(t, slices.Contains(events, "close:core"), "factory events = %v, want the constructed Runtime scope disposed", events) } e.assertLockReleased(t) }) @@ -774,8 +748,9 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { cancel() t.Fatalf("canceled construction error = %v, want the context error", err) } - if events := eventNames(e.events.all()); !orderedSubset(events, "close:signalled") { - t.Fatalf("factory events = %v, want the constructed prefix disposed on cancellation", events) + { + events := eventNames(e.events.all()) + testassert.Require(t, orderedSubset(events, "close:signalled"), "factory events = %v, want the constructed prefix disposed on cancellation", events) } e.assertLockReleased(t) }) @@ -807,14 +782,10 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("canceled construction never returned") } - if got.r != nil { - t.Fatalf("construction completed after cancellation (Runtime %v, error %v)", got.r, got.err) - } - if !errors.Is(got.err, context.Canceled) { - t.Fatalf("open error = %v, want the constructor context's own error, not the owner-lifecycle identity", got.err) - } - if events := e.events.all(); len(events) != 0 { - t.Fatalf("factory events = %v, want the publication rejection to precede any factory", events) + testassert.Require(t, got.r == nil && errors.Is(got.err, context.Canceled), "construction completed after cancellation (Runtime %v, error %v); open error = %v, want the constructor context's own error, not the owner-lifecycle identity", got.r, got.err, got.err) + { + events := e.events.all() + testassert.Require(t, len(events) == 0, "factory events = %v, want the publication rejection to precede any factory", events) } writeServiceFile(t, e.configPath, ownerConfigDocument(e.server.URL)) e.assertLockReleased(t) @@ -826,8 +797,9 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { gate := e.ordinaryPlugin("gate", ScopeRuntime, "gate.cap") gate.ValidateConfig = func(json.RawMessage) error { return ErrClosed } wantFailedOpen(t, e, []Plugin{e.storagePlugin(storage.NewMemory()), gate}, ErrClosed) - if events := e.events.all(); len(events) != 0 { - t.Fatalf("factory events = %v, want the validator rejection preserved before any factory", events) + { + events := e.events.all() + testassert.Require(t, len(events) == 0, "factory events = %v, want the validator rejection preserved before any factory", events) } writeServiceFile(t, e.configPath, ownerConfigDocument(e.server.URL)) e.assertLockReleased(t) @@ -861,15 +833,7 @@ func TestRuntimeStartupFailuresUnwindAcquiredResources(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("canceled construction never returned") } - if got.r != nil { - t.Fatalf("construction completed after the validator rejection (Runtime %v, error %v)", got.r, got.err) - } - if !errors.Is(got.err, errValidator) || !errors.Is(got.err, ErrConfiguration) { - t.Fatalf("open error = %v, want the wrapped validator and ErrConfiguration identities preserved, not replaced by the context error", got.err) - } - if errors.Is(got.err, context.Canceled) { - t.Fatalf("open error = %v, want the source identity rather than the constructor context error", got.err) - } + testassert.Require(t, got.r == nil && errors.Is(got.err, errValidator) && errors.Is(got.err, ErrConfiguration) && !errors.Is(got.err, context.Canceled), "construction completed after the validator rejection (Runtime %v, error %v); open error = %v, want the wrapped validator and ErrConfiguration identities preserved, not replaced by the context error; open error = %v, want the source identity rather than the constructor context error", got.r, got.err, got.err, got.err) writeServiceFile(t, e.configPath, ownerConfigDocument(e.server.URL)) e.assertLockReleased(t) }) @@ -908,14 +872,14 @@ func TestRuntimeCloseSharesOneCleanupAndReleasesTheLockLast(t *testing.T) { w := e.ordinaryPlugin("w", ScopeWorkspace, "w.cap") r, err := e.open(context.Background(), storagePlugin, y, x, w) - if err != nil { - t.Fatalf("open: %v", err) - } - if events := eventNames(e.events.all()); !orderedSubset(events, "open:core", "open:x", "open:y") { - t.Fatalf("factory events = %v, want dependencies constructed before consumers in stable topological order", events) + testassert.NoError(t, err) + { + events := eventNames(e.events.all()) + testassert.Require(t, orderedSubset(events, "open:core", "open:x", "open:y"), "factory events = %v, want dependencies constructed before consumers in stable topological order", events) } - if _, err := r.workspaces.get(context.Background(), ScopeInfo{Kind: ScopeWorkspace, DataDir: e.dataDir, Workspace: "/ws"}); err != nil { - t.Fatalf("workspace get: %v", err) + { + _, err := r.workspaces.get(context.Background(), ScopeInfo{Kind: ScopeWorkspace, DataDir: e.dataDir, Workspace: "/ws"}) + testassert.NoError(t, err) } firstDone := make(chan error, 1) @@ -930,8 +894,9 @@ func TestRuntimeCloseSharesOneCleanupAndReleasesTheLockLast(t *testing.T) { canceledCtx, cancelWait := context.WithCancel(context.Background()) cancelWait() - if err := r.Close(canceledCtx); !errors.Is(err, context.Canceled) { - t.Fatalf("canceled Close waiter = %v, want its own context error while the shared cleanup continues", err) + { + err := r.Close(canceledCtx) + testassert.Require(t, errors.Is(err, context.Canceled), "canceled Close waiter = %v, want its own context error while the shared cleanup continues", err) } if got, err := e.open(context.Background(), e.corePlugin("contender", "session_store", storage.NewMemory(), nil)); err == nil || !errors.Is(err, ErrOwned) { close(blockClose) @@ -945,23 +910,18 @@ func TestRuntimeCloseSharesOneCleanupAndReleasesTheLockLast(t *testing.T) { case <-time.After(10 * time.Second): t.Fatal("Close never converged after disposal was released") } - if !errors.Is(closeErr, errStorageClose) || !errors.Is(closeErr, errXClose) { - t.Fatalf("Close error = %v, want every attempted close joined", closeErr) - } + testassert.Require(t, errors.Is(closeErr, errStorageClose) && errors.Is(closeErr, errXClose), "Close error = %v, want every attempted close joined", closeErr) select { case joined := <-joinedDone: - if joined != closeErr { - t.Fatalf("concurrent Close = %v, want the same joined shared result %v", joined, closeErr) - } + testassert.Equal(t, joined, closeErr) case <-time.After(10 * time.Second): t.Fatal("the second Close caller never joined the shared cleanup") } - if again := r.Close(context.Background()); again != closeErr { - t.Fatalf("repeated Close = %v, want the same joined shared result", again) - } + testassert.Equal(t, r.Close(context.Background()), closeErr) wantClose := []string{"close:w", "close:y", "close:x", "close:core"} - if got := eventsNamedList(e.events.all(), "close"); !slices.Equal(got, wantClose) { - t.Fatalf("close events = %v, want Workspace scopes before the Runtime scope and reverse dependency order inside it", got) + { + got := eventsNamedList(e.events.all(), "close") + testassert.Require(t, slices.Equal(got, wantClose), "close events = %v, want Workspace scopes before the Runtime scope and reverse dependency order inside it", got) } }) } @@ -984,12 +944,11 @@ func TestRuntimeReloadSerializesAndShutdownJoinsTheBuild(t *testing.T) { t.Run("two reloads publish successive revisions", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() var revisions [2]string @@ -1007,9 +966,7 @@ func TestRuntimeReloadSerializesAndShutdownJoinsTheBuild(t *testing.T) { }(i) } wg.Wait() - if !((revisions[0] == "2" && revisions[1] == "3") || (revisions[0] == "3" && revisions[1] == "2")) { - t.Fatalf("concurrent Reload revisions = %v, want the serialized pair 2 and 3", revisions) - } + testassert.Require(t, ((revisions[0] == "2" && revisions[1] == "3") || (revisions[0] == "3" && revisions[1] == "2")), "concurrent Reload revisions = %v, want the serialized pair 2 and 3", revisions) }) t.Run("caller cancellation before publication consumes no revision", func(t *testing.T) { @@ -1019,12 +976,11 @@ func TestRuntimeReloadSerializesAndShutdownJoinsTheBuild(t *testing.T) { gate := e.ordinaryPlugin("gate", ScopeRuntime, "gate.cap") gate.ValidateConfig = validator.ValidateConfig r, err := e.open(context.Background(), e.storagePlugin(store), gate) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() ctx, cancel := context.WithCancel(context.Background()) @@ -1038,11 +994,13 @@ func TestRuntimeReloadSerializesAndShutdownJoinsTheBuild(t *testing.T) { <-arrive cancel() validator.release() - if err := <-reloadDone; !errors.Is(err, context.Canceled) { - t.Fatalf("canceled Reload = %v, want the caller's own context error", err) + { + err := <-reloadDone + testassert.Require(t, errors.Is(err, context.Canceled), "canceled Reload = %v, want the caller's own context error", err) } - if revision, err := r.Reload(context.Background()); err != nil || revision != "2" { - t.Fatalf("Reload after the canceled build = (%q, %v), want 2: a failed build consumes no generation", revision, err) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == "2", "Reload after the canceled build = (%q, %v), want 2: a failed build consumes no generation", revision, err) } }) @@ -1053,9 +1011,7 @@ func TestRuntimeReloadSerializesAndShutdownJoinsTheBuild(t *testing.T) { gate := e.ordinaryPlugin("gate", ScopeRuntime, "gate.cap") gate.ValidateConfig = validator.ValidateConfig r, err := e.open(context.Background(), e.storagePlugin(store), gate) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) arrive := validator.arm() reloadDone := make(chan error, 1) go func() { @@ -1073,17 +1029,13 @@ func TestRuntimeReloadSerializesAndShutdownJoinsTheBuild(t *testing.T) { validator.release() select { case err := <-closeDone: - if err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) case <-time.After(10 * time.Second): t.Fatal("Close never converged after the admitted build was released") } select { case err := <-reloadDone: - if !errors.Is(err, ErrClosed) { - t.Fatalf("admitted Reload under shutdown = %v, want ErrClosed: a live caller on a done owner still reports closure", err) - } + testassert.Require(t, errors.Is(err, ErrClosed), "admitted Reload under shutdown = %v, want ErrClosed: a live caller on a done owner still reports closure", err) default: t.Fatal("Close returned before the admitted Reload returned") } @@ -1098,11 +1050,10 @@ func TestRuntimeLifetimeBelongsToTheProcessNotItsClients(t *testing.T) { t.Run("a canceled request leaves the owner and other work intact", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } - if _, err := r.createSession(context.Background(), e.dataDir, "solo"); err != nil { - t.Fatalf("createSession: %v", err) + testassert.NoError(t, err) + { + _, err := r.createSession(context.Background(), e.dataDir, "solo") + testassert.NoError(t, err) } requestCtx, cancelRequest := context.WithCancel(context.Background()) held := make(chan struct{}) @@ -1115,27 +1066,26 @@ func TestRuntimeLifetimeBelongsToTheProcessNotItsClients(t *testing.T) { }) }() <-held - if _, err := r.createSession(context.Background(), e.dataDir, "solo"); err != nil { - t.Fatalf("createSession while one request was in flight: %v", err) + { + _, err := r.createSession(context.Background(), e.dataDir, "solo") + testassert.NoError(t, err) } cancelRequest() - if err := <-callDone; !errors.Is(err, context.Canceled) { - t.Fatalf("canceled request = %v, want the caller's own error", err) + { + err := <-callDone + testassert.Require(t, errors.Is(err, context.Canceled), "canceled request = %v, want the caller's own error", err) } - if revision, err := r.Reload(context.Background()); err != nil || revision != "2" { - t.Fatalf("Reload after a canceled request = (%q, %v), want the owner alive at revision 2", revision, err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + revision, err := r.Reload(context.Background()) + testassert.Require(t, err == nil && revision == "2", "Reload after a canceled request = (%q, %v), want the owner alive at revision 2", revision, err) } + testassert.NoError(t, r.Close(context.Background())) }) t.Run("a canceled Runtime context rejects entry before the cancellation watcher runs", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) // The exact post-cancellation pre-watcher state: the owned work // context is done while no closure has begun. originalWork, originalCancel := r.work, r.cancelWork @@ -1150,18 +1100,10 @@ func TestRuntimeLifetimeBelongsToTheProcessNotItsClients(t *testing.T) { }) _, sessionErr := r.createSession(context.Background(), e.dataDir, "solo") r.work, r.cancelWork = originalWork, originalCancel - if !errors.Is(reloadErr, ErrClosed) { - t.Fatalf("Reload after owner cancellation = %v, want ErrClosed", reloadErr) - } - if ranGuardedCall || !errors.Is(guardedErr, ErrClosed) { - t.Fatalf("withHarness after owner cancellation = %v (guarded call ran: %v), want ErrClosed with the call never run", guardedErr, ranGuardedCall) - } - if !errors.Is(sessionErr, ErrClosed) { - t.Fatalf("createSession after owner cancellation = %v, want ErrClosed", sessionErr) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close joining the watcher-started shutdown: %v", err) - } + testassert.Require(t, errors.Is(reloadErr, ErrClosed), "Reload after owner cancellation = %v, want ErrClosed", reloadErr) + testassert.Require(t, !ranGuardedCall && errors.Is(guardedErr, ErrClosed), "withHarness after owner cancellation = %v (guarded call ran: %v), want ErrClosed with the call never run", guardedErr, ranGuardedCall) + testassert.Require(t, errors.Is(sessionErr, ErrClosed), "createSession after owner cancellation = %v, want ErrClosed", sessionErr) + testassert.NoError(t, r.Close(context.Background())) e.assertLockReleased(t) }) @@ -1194,43 +1136,37 @@ func TestRuntimeLifetimeBelongsToTheProcessNotItsClients(t *testing.T) { t.Fatal("the watcher-started shutdown disposed no plugin") } again, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open after the watcher-started shutdown: %v", err) - } - if err := again.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, again.Close(context.Background())) }) t.Run("closed access rejects and repeated Close joins the same result", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(store)) - if err != nil { - t.Fatalf("open: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("first Close: %v", err) - } - if _, err := r.Reload(context.Background()); !errors.Is(err, ErrClosed) { - t.Fatalf("Reload after Close = %v, want ErrClosed", err) + testassert.NoError(t, err) + testassert.NoError(t, r.Close(context.Background())) + { + _, err := r.Reload(context.Background()) + testassert.Require(t, errors.Is(err, ErrClosed), "Reload after Close = %v, want ErrClosed", err) } callerCtx, cancelCaller := context.WithCancel(context.Background()) cancelCaller() - if _, err := r.Reload(callerCtx); !errors.Is(err, ErrClosed) { - t.Fatalf("Reload after Close with a canceled caller = %v, want ErrClosed: admission rejects before the caller check", err) - } - if _, err := r.createSession(context.Background(), e.dataDir, "solo"); !errors.Is(err, ErrClosed) { - t.Fatalf("createSession after Close = %v, want ErrClosed", err) - } - if err := r.withHarness(context.Background(), func(context.Context, *harness.Harness) error { - t.Error("the guarded call ran after Close") - return nil - }); !errors.Is(err, ErrClosed) { - t.Fatalf("withHarness after Close = %v, want ErrClosed", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("repeated Close = %v, want the same joined result", err) + { + _, err := r.Reload(callerCtx) + testassert.Require(t, errors.Is(err, ErrClosed), "Reload after Close with a canceled caller = %v, want ErrClosed: admission rejects before the caller check", err) + } + { + _, err := r.createSession(context.Background(), e.dataDir, "solo") + testassert.Require(t, errors.Is(err, ErrClosed), "createSession after Close = %v, want ErrClosed", err) + } + { + err := r.withHarness(context.Background(), func(context.Context, *harness.Harness) error { + t.Error("the guarded call ran after Close") + return nil + }) + testassert.Require(t, errors.Is(err, ErrClosed), "withHarness after Close = %v, want ErrClosed", err) } + testassert.NoError(t, r.Close(context.Background())) }) }) } @@ -1239,17 +1175,14 @@ func TestRuntimePublishOwnerBarrier(t *testing.T) { t.Run("observed cancellation unwinds and reports the context error", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) cancel() got, err := r.publishOwner(ctx) - if got != nil || !errors.Is(err, context.Canceled) { - t.Fatalf("publishOwner after observed cancellation = (%v, %v), want (nil, context error)", got, err) - } - if events := eventNames(e.events.all()); !slices.Contains(events, "close:core") { - t.Fatalf("factory events = %v, want the completed cleanup before the return", events) + testassert.Require(t, got == nil && errors.Is(err, context.Canceled), "publishOwner after observed cancellation = (%v, %v), want (nil, context error)", got, err) + { + events := eventNames(e.events.all()) + testassert.Require(t, slices.Contains(events, "close:core"), "factory events = %v, want the completed cleanup before the return", events) } e.assertLockReleased(t) }) @@ -1257,16 +1190,10 @@ func TestRuntimePublishOwnerBarrier(t *testing.T) { t.Run("publication wins while cancellation starts shutdown behind it", func(t *testing.T) { e := newOwnerEnv(t) r, err := e.open(context.Background(), e.storagePlugin(storage.NewMemory())) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) got, err := r.publishOwner(context.Background()) - if got != r || err != nil { - t.Fatalf("publishOwner = (%v, %v), want the same completed owner", got, err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.Require(t, got == r && err == nil, "publishOwner = (%v, %v), want the same completed owner", got, err) + testassert.NoError(t, r.Close(context.Background())) }) } @@ -1278,98 +1205,61 @@ func TestRuntimeWorkspaceIdentityIsLexicalNormalization(t *testing.T) { opens := make(chan int64, 16) ws := e.countedPlugin("ws", ScopeWorkspace, "ws.cap", opens) r, err := e.open(context.Background(), e.storagePlugin(store), ws) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) defer func() { - if err := r.Close(context.Background()); err != nil { - t.Errorf("Close: %v", err) + { + err := r.Close(context.Background()) + testassert.Check(t, err == nil, "Close: %v", err) } }() - if _, err := r.createSession(context.Background(), "", "solo"); err == nil { - t.Fatal("an empty workspace was accepted") - } - if err := r.withHarness(context.Background(), func(context.Context, *harness.Harness) error { return nil }); err != nil { - t.Fatalf("withHarness: %v", err) + { + _, err := r.createSession(context.Background(), "", "solo") + testassert.Require(t, err != nil, "an empty workspace was accepted") } + testassert.NoError(t, r.withHarness(context.Background(), func(context.Context, *harness.Harness) error { return nil })) t.Run("unclean and relative paths take one Abs", func(t *testing.T) { target := filepath.Join(e.home, "real", "project") - if err := os.MkdirAll(target, 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(target, 0o700)) unclean := filepath.Join(e.home, "real", "other", "..", "project") rec, err := r.createSession(context.Background(), unclean, "solo") - if err != nil { - t.Fatalf("createSession(unclean): %v", err) - } - if rec.Identity.Workspace != target { - t.Fatalf("workspace = %q, want the lexical %q", rec.Identity.Workspace, target) - } + testassert.NoError(t, err) + testassert.Equal(t, rec.Identity.Workspace, target) cwd, err := os.Getwd() - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) rel, err := filepath.Rel(cwd, target) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) relative, err := r.createSession(context.Background(), rel, "solo") - if err != nil { - t.Fatalf("createSession(relative): %v", err) - } - if relative.Identity.Workspace != target { - t.Fatalf("relative workspace = %q, want the same normalized %q", relative.Identity.Workspace, target) - } + testassert.NoError(t, err) + testassert.Equal(t, relative.Identity.Workspace, target) }) t.Run("symlink siblings keep distinct lexical identities", func(t *testing.T) { - if err := os.MkdirAll(filepath.Join(e.home, "real"), 0o700); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(e.home, "real"), 0o700)) link := filepath.Join(e.home, "link") - if err := os.Symlink(filepath.Join(e.home, "real"), link); err != nil { - t.Fatalf("Symlink: %v", err) - } + testassert.NoError(t, os.Symlink(filepath.Join(e.home, "real"), link)) byLink, err := r.createSession(context.Background(), filepath.Join(link, "project"), "solo") - if err != nil { - t.Fatalf("createSession(link): %v", err) - } + testassert.NoError(t, err) byReal, err := r.createSession(context.Background(), filepath.Join(e.home, "real", "project"), "solo") - if err != nil { - t.Fatalf("createSession(real): %v", err) - } - if byLink.Identity.Workspace != filepath.Join(link, "project") { - t.Fatalf("link workspace = %q, want the lexical link path, not %q", byLink.Identity.Workspace, byReal.Identity.Workspace) - } - if byLink.Identity.Workspace == byReal.Identity.Workspace { - t.Fatalf("both workspaces normalized to %q, want distinct lexical identities", byLink.Identity.Workspace) - } + testassert.NoError(t, err) + testassert.Require(t, byLink.Identity.Workspace == filepath.Join(link, "project") && byLink.Identity.Workspace != byReal.Identity.Workspace, "link workspace = %q, want the lexical link path, not %q; both workspaces normalized to %q, want distinct lexical identities", byLink.Identity.Workspace, byReal.Identity.Workspace, byLink.Identity.Workspace) }) t.Run("scope lookup and Fork inheritance use the normalized identity", func(t *testing.T) { shared := filepath.Join(e.home, "shared") left, err := r.createSession(context.Background(), filepath.Join(shared, "x", ".."), "solo") - if err != nil { - t.Fatalf("createSession(left): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, left.Identity.SessionID, "op-left", "first") right, err := r.createSession(context.Background(), shared, "solo") - if err != nil { - t.Fatalf("createSession(right): %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, right.Identity.SessionID, "op-right", "first") awaitOperation(t, r, left.Identity.SessionID, "op-left", harness.OperationSuccess) awaitOperation(t, r, right.Identity.SessionID, "op-right", harness.OperationSuccess) - if n := <-opens; n != 1 { - t.Fatalf("Workspace scope constructions = %d, want one shared scope for the same normalized identity", n) - } + testassert.Equal(t, <-opens, 1) entries, err := store.ReadEntries(context.Background(), left.Identity.SessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var boundary string for _, entry := range entries { if entry.Kind == harness.EntryInput && entry.OperationID == "op-left" { @@ -1377,9 +1267,7 @@ func TestRuntimeWorkspaceIdentityIsLexicalNormalization(t *testing.T) { break } } - if boundary == "" { - t.Fatal("no admitted input entry was found") - } + testassert.Require(t, boundary != "", "no admitted input entry was found") var forked harness.SessionRecord err = r.withHarness(context.Background(), func(ctx context.Context, h *harness.Harness) error { res, err := h.Fork(ctx, harness.ForkRequest{ @@ -1394,19 +1282,13 @@ func TestRuntimeWorkspaceIdentityIsLexicalNormalization(t *testing.T) { forked = res.Session return nil }) - if err != nil { - t.Fatalf("Fork: %v", err) - } - if forked.Identity.Workspace != shared { - t.Fatalf("Fork workspace = %q, want the inherited normalized %q", forked.Identity.Workspace, shared) - } + testassert.NoError(t, err) + testassert.Equal(t, forked.Identity.Workspace, shared) if e.scopeWorkspace.Load().(string) != shared { t.Fatalf("Workspace scope identity = %q, want the normalized %q", e.scopeWorkspace.Load().(string), shared) } awaitOperation(t, r, forked.Identity.SessionID, "op-fork", harness.OperationSuccess) - if n := eventsNamed(e.events.all(), "open:ws"); n != 1 { - t.Fatalf("Workspace scope constructions = %d, want exactly one across both Sessions and the Fork", n) - } + testassert.Equal(t, eventsNamed(e.events.all(), "open:ws"), 1) }) }) } @@ -1456,15 +1338,9 @@ func TestRuntimeCoreStorageBindingContract(t *testing.T) { named := e.ordinaryPlugin("storage", ScopeRuntime, "storage") plugin := e.corePlugin("state", "whatever", canonical, func() error { e.events.add("close:state"); return nil }) r, err := e.open(context.Background(), plugin, named) - if err != nil { - t.Fatalf("open: %v", err) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) - } - if canonical.listCount() == 0 { - t.Fatal("recovery never used the type-identified Core binding") - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(context.Background())) + testassert.Require(t, canonical.listCount() != 0, "recovery never used the type-identified Core binding") }) }) } @@ -1506,9 +1382,7 @@ func readSessionFacts(t *testing.T, r *Runtime, sessionID string) (harness.Sessi facts = historyFactsOf(view) return nil }) - if err != nil { - t.Fatalf("read session %q facts: %v", sessionID, err) - } + testassert.Require(t, err == nil, "read session %q facts: %v", sessionID, err) return header, facts } @@ -1521,43 +1395,30 @@ func TestRuntimeRecoveryRepairsBeforeExecution(t *testing.T) { e.storagePlugin(&countingStore{Storage: store}), shortScopeProbe("probe", ScopeOperation, &opOpens), shortScopeProbe("probe-agent", ScopeAgent, &agOpens)) - if err != nil { - t.Fatalf("open: %v", err) - } - if got := e.server.requests(); got != 0 { - t.Fatalf("HTTP requests during startup = %d, want recovery to reach no model request", got) - } - if ops, ags := opOpens.Load(), agOpens.Load(); ops != 0 || ags != 0 { - t.Fatalf("short-scope factory opens during startup = %d/%d, want recovery to open no execution scope", ops, ags) + testassert.NoError(t, err) + testassert.Equal(t, e.server.requests(), 0) + { + ops, ags := opOpens.Load(), agOpens.Load() + testassert.Require(t, ops == 0 && ags == 0, "short-scope factory opens during startup = %d/%d, want recovery to open no execution scope", ops, ags) } rec := readOperation(t, r, sessionID, operationID) - if rec.State.Status != harness.OperationInterruption || rec.State.Terminal == nil || - rec.State.Terminal.Detail != "Operation interrupted by Runtime loss." { - t.Fatalf("recovered Operation state = %+v, want the terminal interruption settlement", rec.State) - } + testassert.Require(t, rec.State.Status == harness.OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "Operation interrupted by Runtime loss.", "recovered Operation state = %+v, want the terminal interruption settlement", rec.State) header, facts := readSessionFacts(t, r, sessionID) - if header.CurrentOperationID != "" { - t.Fatalf("recovered Session current Operation = %q, want it cleared", header.CurrentOperationID) - } + testassert.Equal(t, header.CurrentOperationID, "") if len(facts) == 0 || facts[0].Kind != harness.EntryInput { t.Fatalf("recovered Session facts = %d, want the retained seeded input", len(facts)) } // Repair precedes execution: a later admission enters ordinary // admission on the repaired durable state. next, err := r.createSession(context.Background(), filepath.Join(e.home, "seeded"), "solo") - if err != nil { - t.Fatalf("createSession after recovery: %v", err) - } + testassert.NoError(t, err) submitThroughRuntime(t, r, next.Identity.SessionID, "op-after-repair", "continue") - if post := awaitOperation(t, r, next.Identity.SessionID, "op-after-repair", harness.OperationSuccess); post.State.Status != harness.OperationSuccess { - t.Fatalf("post-repair Operation = %+v, want success", post.State) - } - if got := e.server.requests(); got != 1 { - t.Fatalf("HTTP requests after the explicit submit = %d, want exactly the one admitted request", got) - } - if err := r.Close(context.Background()); err != nil { - t.Fatalf("Close: %v", err) + { + post := awaitOperation(t, r, next.Identity.SessionID, "op-after-repair", harness.OperationSuccess) + testassert.Require(t, post.State.Status == harness.OperationSuccess, "post-repair Operation = %+v, want success", post.State) } + testassert.Equal(t, e.server.requests(), 1) + testassert.NoError(t, r.Close(context.Background())) }) } @@ -1589,33 +1450,23 @@ func TestRuntimeRecoverySettlesQuiescentChildLossWithoutParentCompletion(t *test jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper), shortScopeProbe("probe", ScopeOperation, &opOpens), shortScopeProbe("probe-agent", ScopeAgent, &agOpens)) - if err != nil { - t.Fatalf("open: %v", err) - } + testassert.NoError(t, err) // Recovery invoked no model transport, opened no execution scope, and // stopped no job. - if got := e.server.requests(); got != 0 { - t.Fatalf("HTTP requests during startup = %d, want recovery to reach no model request", got) - } - if ops, ags := opOpens.Load(), agOpens.Load(); ops != 0 || ags != 0 { - t.Fatalf("short-scope factory opens during startup = %d/%d, want recovery to open no execution scope", ops, ags) - } - if n := eventsNamed(e.events.all(), "stopjob"); n != 0 { - t.Fatalf("job-stop invocations during startup = %d, want recovery to stop no job", n) + testassert.Equal(t, e.server.requests(), 0) + { + ops, ags := opOpens.Load(), agOpens.Load() + testassert.Require(t, ops == 0 && ags == 0, "short-scope factory opens during startup = %d/%d, want recovery to open no execution scope", ops, ags) } + testassert.Equal(t, eventsNamed(e.events.all(), "stopjob"), 0) // The child's running Operation settled as the runtime-loss // interruption with its committed input prefix retained. rec := readOperation(t, r, child, childOp) - if rec.State.Status != harness.OperationInterruption || rec.State.Terminal == nil || - rec.State.Terminal.Detail != "Operation interrupted by Runtime loss." { - t.Fatalf("recovered child operation state = %+v, want the terminal interruption settlement", rec.State) - } + testassert.Require(t, rec.State.Status == harness.OperationInterruption && rec.State.Terminal != nil && rec.State.Terminal.Detail == "Operation interrupted by Runtime loss.", "recovered child operation state = %+v, want the terminal interruption settlement", rec.State) childHeader, childFacts := readSessionFacts(t, r, child) - if childHeader.CurrentOperationID != "" { - t.Fatalf("recovered child current Operation = %q, want it cleared", childHeader.CurrentOperationID) - } + testassert.Equal(t, childHeader.CurrentOperationID, "") if len(childFacts) == 0 || childFacts[0].Kind != harness.EntryInput { t.Fatalf("recovered child facts = %d, want the retained seeded input prefix", len(childFacts)) } @@ -1623,64 +1474,48 @@ func TestRuntimeRecoverySettlesQuiescentChildLossWithoutParentCompletion(t *test // The loss stays parent-independent: the parent keeps no current // Operation and records no fabricated completion entry. parentHeader, parentFacts := readSessionFacts(t, r, parent) - if parentHeader.CurrentOperationID != "" { - t.Fatalf("recovered parent current Operation = %q, want none", parentHeader.CurrentOperationID) - } - if len(parentFacts) != 0 { - t.Fatalf("recovered parent facts = %+v, want no fabricated completion entry", parentFacts) - } + testassert.Equal(t, parentHeader.CurrentOperationID, "") + testassert.Require(t, len(parentFacts) == 0, "recovered parent facts = %+v, want no fabricated completion entry", parentFacts) // Only the subsequent explicit Submit may produce a request; the // parent admits normally and its lifecycle stays unblocked with no // reconstructed member. submitThroughRuntime(t, r, parent, "op-after-loss", "continue") - if post := awaitOperation(t, r, parent, "op-after-loss", harness.OperationSuccess); post.State.Status != harness.OperationSuccess { - t.Fatalf("post-recovery parent Operation = %+v, want success", post.State) - } - if got := e.server.requests(); got != 1 { - t.Fatalf("HTTP requests after the explicit submit = %d, want exactly the one admitted request", got) + { + post := awaitOperation(t, r, parent, "op-after-loss", harness.OperationSuccess) + testassert.Require(t, post.State.Status == harness.OperationSuccess, "post-recovery parent Operation = %+v, want success", post.State) } + testassert.Equal(t, e.server.requests(), 1) err = r.withHarness(ctx, func(ctx context.Context, h *harness.Harness) error { _, err := h.ArchiveSession(ctx, parent) return err }) - if err != nil { - t.Fatalf("ArchiveSession on the recovered parent = %v, want no reconstructed member blocking the lifecycle", err) - } + testassert.NoError(t, err) _, settledChildFacts := readSessionFacts(t, r, child) _, settledParentFacts := readSessionFacts(t, r, parent) - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) // A repeated recovery over the same durable state changes nothing. requestsBefore := e.server.requests() again, err := e.open(ctx, e.storagePlugin(store), jobStopperPlugin(e, "jobs", "job-stopper", ScopeRuntime, stopper)) - if err != nil { - t.Fatalf("reopen for the repeated recovery: %v", err) - } - if got := e.server.requests(); got != requestsBefore { - t.Fatalf("HTTP requests during the repeated recovery = %d, want none beyond the %d before it", got, requestsBefore) - } - if n := eventsNamed(e.events.all(), "stopjob"); n != 0 { - t.Fatalf("job-stop invocations across both recoveries = %d, want none", n) + testassert.NoError(t, err) + testassert.Equal(t, e.server.requests(), requestsBefore) + testassert.Equal(t, eventsNamed(e.events.all(), "stopjob"), 0) + { + got := readOperation(t, again, child, childOp) + testassert.Require(t, got.State.Status == rec.State.Status && got.State.Terminal != nil && got.State.Terminal.Detail == rec.State.Terminal.Detail, "repeated recovery changed the child settlement: %+v, want the unchanged %+v", got.State, rec.State) } - if got := readOperation(t, again, child, childOp); got.State.Status != rec.State.Status || - got.State.Terminal == nil || got.State.Terminal.Detail != rec.State.Terminal.Detail { - t.Fatalf("repeated recovery changed the child settlement: %+v, want the unchanged %+v", got.State, rec.State) - } - if _, facts := readSessionFacts(t, again, child); len(facts) != len(settledChildFacts) { - t.Fatalf("repeated recovery changed the child facts %d -> %d", len(settledChildFacts), len(facts)) + { + _, facts := readSessionFacts(t, again, child) + testassert.Equal(t, len(facts), len(settledChildFacts)) } if header, facts := readSessionFacts(t, again, parent); header.Lifecycle != harness.LifecycleArchived || len(facts) != len(settledParentFacts) { t.Fatalf("repeated recovery changed the parent: lifecycle %q facts %d -> %d, want the archived %d", header.Lifecycle, len(settledParentFacts), len(facts), len(settledParentFacts)) } - if err := again.Close(ctx); err != nil { - t.Fatalf("second Close: %v", err) - } + testassert.NoError(t, again.Close(ctx)) }) } diff --git a/runtime/sqlite_plugin_test.go b/runtime/sqlite_plugin_test.go index 4e223924..5c45fa6e 100644 --- a/runtime/sqlite_plugin_test.go +++ b/runtime/sqlite_plugin_test.go @@ -17,6 +17,7 @@ import ( "github.com/MMinasyan/lightcode/internal/plugins/builtin" "github.com/MMinasyan/lightcode/internal/plugins/sqlite" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/protocol" "github.com/MMinasyan/lightcode/runtime" @@ -45,9 +46,7 @@ func newComposeEnv(t *testing.T) *composeEnv { t.Setenv("HOME", home) isolateBundledCredentials(t) configPath := filepath.Join(configDir, "config.json") - if err := os.WriteFile(configPath, []byte(composedConfigDocument), 0o600); err != nil { - t.Fatalf("write config: %v", err) - } + testassert.NoError(t, os.WriteFile(configPath, []byte(composedConfigDocument), 0o600)) return &composeEnv{t: t, home: home, dataDir: dataDir, configPath: configPath} } @@ -56,9 +55,7 @@ func newComposeEnv(t *testing.T) *composeEnv { func isolateBundledCredentials(t *testing.T) { t.Helper() entries, err := os.ReadDir(filepath.Join("..", "internal", "catalog", "builtin")) - if err != nil { - t.Fatalf("read bundled catalog: %v", err) - } + testassert.NoError(t, err) for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue @@ -109,18 +106,14 @@ func treeFiles(t *testing.T, root string) map[string]string { files[filepath.ToSlash(rel)] = string(data) return nil }) - if err != nil { - t.Fatalf("walk %s: %v", root, err) - } + testassert.Require(t, err == nil, "walk %s: %v", root, err) return files } func sortedNames(t *testing.T, dir string) []string { t.Helper() entries, err := os.ReadDir(dir) - if err != nil { - t.Fatalf("read dir %s: %v", dir, err) - } + testassert.Require(t, err == nil, "read dir %s: %v", dir, err) names := make([]string, 0, len(entries)) for _, entry := range entries { names = append(names, entry.Name()) @@ -136,12 +129,8 @@ func assertSameTree(t *testing.T, before, after map[string]string, what string) } for name, want := range before { got, ok := after[name] - if !ok { - t.Fatalf("%s: file %q vanished", what, name) - } - if got != want { - t.Fatalf("%s: file %q was modified", what, name) - } + testassert.Require(t, ok, "%s: file %q vanished", what, name) + testassert.Require(t, got == want, "%s: file %q was modified", what, name) } } @@ -159,61 +148,31 @@ func keys(m map[string]string) []string { // implements. func TestSQLitePluginDeclaration(t *testing.T) { p := sqlite.Plugin() - if p.ID != "sqlite" { - t.Fatalf("plugin ID = %q, want %q", p.ID, "sqlite") - } - if p.Scope != runtime.ScopeRuntime { - t.Fatalf("plugin scope = %q, want the Runtime scope", p.Scope) - } - if len(p.Provides) != 1 { - t.Fatalf("plugin declares %d exports, want exactly one harness.Storage export", len(p.Provides)) - } - if len(p.Requires) != 0 { - t.Fatalf("plugin requires %d capabilities, want none", len(p.Requires)) - } - if p.ValidateConfig != nil { - t.Fatal("plugin declares a settings validator, want none") - } - if p.Open == nil { - t.Fatal("plugin declares no Open") - } + testassert.Require(t, p.ID == "sqlite" && p.Scope == runtime.ScopeRuntime && len(p.Provides) == 1 && len(p.Requires) == 0, "got %v, want %v; got %v, want %v; got %v, want %v; got %v, want %v", p.ID, "sqlite", p.Scope, runtime.ScopeRuntime, len(p.Provides), 1, len(p.Requires), 0) + testassert.Require(t, p.ValidateConfig == nil, "plugin declares a settings validator, want none") + testassert.Require(t, p.Open != nil, "plugin declares no Open") t.Run("open derives the exact database path and returns the declared store with its Close", func(t *testing.T) { dir := t.TempDir() inst, err := p.Open(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dir}, runtime.Bindings{}) - if err != nil { - t.Fatalf("Open: %v", err) - } - if len(inst.Values) != 1 { - t.Fatalf("instance values = %d exports, want exactly one declared export", len(inst.Values)) - } + testassert.NoError(t, err) + testassert.Equal(t, len(inst.Values), 1) var store harness.Storage for _, value := range inst.Values { storageValue, ok := value.(harness.Storage) - if !ok { - t.Fatalf("instance export supplies %T, not harness.Storage", value) - } + testassert.Require(t, ok, "instance export supplies %T, not harness.Storage", value) store = storageValue } - if store == nil { - t.Fatal("instance export supplies a nil harness.Storage") - } - if inst.Close == nil { - t.Fatal("instance declares no Close for the opened store") - } - if _, err := os.Stat(filepath.Join(dir, "lightcode.db")); err != nil { - t.Fatalf("the store was not opened at the exact derived path: %v", err) - } - if err := inst.Close(); err != nil { - t.Fatalf("Close: %v", err) + testassert.Require(t, store != nil, "instance export supplies a nil harness.Storage") + testassert.Require(t, inst.Close != nil, "instance declares no Close for the opened store") + { + _, err := os.Stat(filepath.Join(dir, "lightcode.db")) + testassert.NoError(t, err) } + testassert.NoError(t, inst.Close()) reopened, err := storage.OpenSQLite(filepath.Join(dir, "lightcode.db")) - if err != nil { - t.Fatalf("the closed database did not remain a valid canonical store: %v", err) - } - if err := reopened.Close(); err != nil { - t.Fatalf("Close of the reopened store: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, reopened.Close()) }) t.Run("open checks the context before touching the filesystem", func(t *testing.T) { @@ -221,14 +180,11 @@ func TestSQLitePluginDeclaration(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() inst, err := p.Open(ctx, runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: dir}, runtime.Bindings{}) - if inst.Values != nil || inst.Close != nil { - t.Fatalf("canceled Open returned the instance %+v", inst) - } - if !errors.Is(err, context.Canceled) { - t.Fatalf("canceled Open error = %v, want the context error", err) - } - if _, err := os.Stat(filepath.Join(dir, "lightcode.db")); !os.IsNotExist(err) { - t.Fatalf("a canceled Open touched the filesystem: stat err = %v", err) + testassert.Require(t, inst.Values == nil && inst.Close == nil, "canceled Open returned the instance %+v", inst) + testassert.Require(t, errors.Is(err, context.Canceled), "canceled Open error = %v, want the context error", err) + { + _, err := os.Stat(filepath.Join(dir, "lightcode.db")) + testassert.Require(t, os.IsNotExist(err), "a canceled Open touched the filesystem: stat err = %v", err) } }) } @@ -240,29 +196,18 @@ func TestComposedSQLiteRuntime(t *testing.T) { t.Run("composes the selected backend at the exact data root and preserves unrelated files", func(t *testing.T) { e := newComposeEnv(t) - if err := os.MkdirAll(filepath.Join(e.home, ".lightcode"), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(e.home, ".lightcode", ".env"), []byte("ISOLATED=1\n"), 0o600); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(e.home, ".lightcode", "unrelated.txt"), []byte("keep\n"), 0o600); err != nil { - t.Fatal(err) - } - if err := os.MkdirAll(filepath.Join(e.home, "project"), 0o700); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(e.home, "project", "notes.txt"), []byte("keep\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(e.home, ".lightcode"), 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(e.home, ".lightcode", ".env"), []byte("ISOLATED=1\n"), 0o600)) + testassert.NoError(t, os.WriteFile(filepath.Join(e.home, ".lightcode", "unrelated.txt"), []byte("keep\n"), 0o600)) + testassert.NoError(t, os.MkdirAll(filepath.Join(e.home, "project"), 0o700)) + testassert.NoError(t, os.WriteFile(filepath.Join(e.home, "project", "notes.txt"), []byte("keep\n"), 0o600)) homeBefore := treeFiles(t, e.home) r, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } - if _, err := os.Stat(filepath.Join(e.dataDir, "lightcode.db")); err != nil { - t.Fatalf("the composed backend did not open lightcode.db under the data root: %v", err) + testassert.NoError(t, err) + { + _, err := os.Stat(filepath.Join(e.dataDir, "lightcode.db")) + testassert.NoError(t, err) } for _, name := range sortedNames(t, e.dataDir) { switch name { @@ -271,11 +216,10 @@ func TestComposedSQLiteRuntime(t *testing.T) { t.Fatalf("unexpected file %q in the data root while open; it is the sole data-root input", name) } } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - if got := sortedNames(t, e.dataDir); !slices.Equal(got, []string{"lightcode.db", "runtime.lock"}) { - t.Fatalf("data root after Close = %v, want exactly the ownership lock and the composed database", got) + testassert.NoError(t, r.Close(ctx)) + { + got := sortedNames(t, e.dataDir) + testassert.Require(t, slices.Equal(got, []string{"lightcode.db", "runtime.lock"}), "data root after Close = %v, want exactly the ownership lock and the composed database", got) } assertSameTree(t, homeBefore, treeFiles(t, e.home), "the isolated home") }) @@ -283,9 +227,7 @@ func TestComposedSQLiteRuntime(t *testing.T) { t.Run("lock contention yields one owner and reacquisition follows release", func(t *testing.T) { e := newComposeEnv(t) r, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } + testassert.NoError(t, err) contender := newComposeEnv(t) rivalData := e.dataDir contender.dataDir = rivalData @@ -293,65 +235,42 @@ func TestComposedSQLiteRuntime(t *testing.T) { _ = r.Close(ctx) t.Fatalf("contender open = (%v, %v), want (nil, ErrOwned)", got, err) } - if err := r.Close(ctx); err != nil { - t.Fatalf("owner Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) again, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("reopen after release: %v", err) - } - if err := again.Close(ctx); err != nil { - t.Fatalf("Close after reacquire: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, again.Close(ctx)) }) t.Run("close and reopen compose the same database", func(t *testing.T) { e := newComposeEnv(t) first, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("first open: %v", err) - } - if err := first.Close(ctx); err != nil { - t.Fatalf("first Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, first.Close(ctx)) second, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("reopen: %v", err) - } - if err := second.Close(ctx); err != nil { - t.Fatalf("second Close: %v", err) - } - if got := sortedNames(t, e.dataDir); !slices.Equal(got, []string{"lightcode.db", "runtime.lock"}) { - t.Fatalf("data root after the second Close = %v, want exactly the lock and the database", got) + testassert.NoError(t, err) + testassert.NoError(t, second.Close(ctx)) + { + got := sortedNames(t, e.dataDir) + testassert.Require(t, slices.Equal(got, []string{"lightcode.db", "runtime.lock"}), "data root after the second Close = %v, want exactly the lock and the database", got) } }) t.Run("failed assembly releases ownership and creates no second owner", func(t *testing.T) { e := newComposeEnv(t) - if err := os.WriteFile(filepath.Join(e.dataDir, "lightcode.db"), []byte("not a database at all"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(e.dataDir, "lightcode.db"), []byte("not a database at all"), 0o600)) r, err := e.openComposed(ctx) - if r != nil { - t.Fatalf("failed assembly returned a Runtime (%v)", err) - } - if err == nil || errors.Is(err, runtime.ErrOwned) { - t.Fatalf("failed assembly error = %v, want the storage failure, not the ownership identity", err) - } + testassert.Require(t, r == nil, "failed assembly returned a Runtime (%v)", err) + testassert.Require(t, err != nil && !errors.Is(err, runtime.ErrOwned), "failed assembly error = %v, want the storage failure, not the ownership identity", err) // The lock was released: the next attempt fails at storage again rather // than reporting ownership contention, and no partial owner remains. again, err := e.openComposed(ctx) - if again != nil || err == nil || errors.Is(err, runtime.ErrOwned) { - t.Fatalf("open over the failed assembly = (%v, %v), want (nil, the same storage failure)", again, err) - } + testassert.Require(t, again == nil && err != nil && !errors.Is(err, runtime.ErrOwned), "open over the failed assembly = (%v, %v), want (nil, the same storage failure)", again, err) }) t.Run("startup recovery repairs the seeded running state before execution", func(t *testing.T) { e := newComposeEnv(t) store, err := storage.OpenSQLite(filepath.Join(e.dataDir, "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) // Registered before the seed's own cleanup, so the parked execution is // released before the store closes. t.Cleanup(func() { _ = store.Close() }) @@ -359,50 +278,30 @@ func TestComposedSQLiteRuntime(t *testing.T) { key := harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterOperation, OperationID: "seed-op"} reg, err := store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister before composition: %v", err) - } - if status := registerStatus(t, reg.Payload); status != harness.OperationRunning { - t.Fatalf("seeded Operation status before composition = %q, want running", status) - } + testassert.NoError(t, err) + testassert.Equal(t, registerStatus(t, reg.Payload), harness.OperationRunning) r, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } + testassert.NoError(t, err) // Repair completed during construction, before any execution: the // register is terminal and no execution callback ever ran again. reg, err = store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister after composition: %v", err) - } - if status := registerStatus(t, reg.Payload); status != harness.OperationInterruption { - t.Fatalf("recovered Operation status = %q, want the terminal interruption settlement", status) - } + testassert.NoError(t, err) + testassert.Equal(t, registerStatus(t, reg.Payload), harness.OperationInterruption) entries, err := store.ReadEntries(ctx, sessionID, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) var kinds []string for _, entry := range entries { kinds = append(kinds, string(entry.Kind)) } sort.Strings(kinds) - if !slices.Equal(kinds, []string{string(harness.EntryInput), string(harness.EntryOperationSettlement), string(harness.EntrySignal)}) { - t.Fatalf("repaired Session entries = %v, want the admitted input, the interruption signal, and the settlement", kinds) - } + testassert.Require(t, slices.Equal(kinds, []string{string(harness.EntryInput), string(harness.EntryOperationSettlement), string(harness.EntrySignal)}), "repaired Session entries = %v, want the admitted input, the interruption signal, and the settlement", kinds) session := readSessionRegister(t, store, sessionID) - if session != "" { - t.Fatalf("repaired Session still names current Operation %q, want the cleared current Operation", session) - } - if n := executions.Load(); n != 1 { - t.Fatalf("seeded model executions = %d, want only the original seeded one: recovery started no execution", n) - } + testassert.Equal(t, session, "") + testassert.Equal(t, executions.Load(), 1) - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) }) } @@ -427,10 +326,8 @@ func seedRunningOperation(t *testing.T, store harness.Storage, workspace string) OutputReserve: 2048, SystemPrompt: "seeded", Compact: harness.CompactCapture{ - Model: model.ModelRef{Provider: "prov", Model: "m"}, - ContextWindow: 2048, - OutputReserve: 1024, - SystemPrompt: "summarize", + Model: model.ModelRef{Provider: "prov", Model: "m"}, + SystemPrompt: "summarize", }, }, Open: func(context.Context, harness.OperationAdmission) (harness.Execution, error) { @@ -461,18 +358,15 @@ func seedRunningOperation(t *testing.T, store harness.Storage, workspace string) }, nil }, }) - if err != nil { - t.Fatalf("seed harness.New: %v", err) - } + testassert.NoError(t, err) rec, err := h.CreateSession(context.Background(), harness.CreateSessionRequest{Workspace: workspace, AgentType: "seeded"}) - if err != nil { - t.Fatalf("seed CreateSession: %v", err) - } - if _, err := h.Submit(context.Background(), harness.SubmitRequest{ - SessionID: rec.Identity.SessionID, OperationID: "seed-op", Origin: harness.InputOriginUser, - Content: []model.ContentPart{{Kind: model.PartText, Text: "running"}}, Mode: harness.MessageModeRegular, - }); err != nil { - t.Fatalf("seed Submit: %v", err) + testassert.NoError(t, err) + { + _, err := h.Submit(context.Background(), harness.SubmitRequest{ + SessionID: rec.Identity.SessionID, OperationID: "seed-op", Origin: harness.InputOriginUser, + Content: []model.ContentPart{{Kind: model.PartText, Text: "running"}}, Mode: harness.MessageModeRegular, + }) + testassert.NoError(t, err) } select { case <-arrived: @@ -490,9 +384,7 @@ func registerStatus(t *testing.T, payload json.RawMessage) harness.OperationStat Status harness.OperationState `json:"status"` } `json:"state"` } - if err := json.Unmarshal(payload, &wire); err != nil { - t.Fatalf("decode operation register payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(payload, &wire)) return wire.State.Status } @@ -501,17 +393,13 @@ func registerStatus(t *testing.T, payload json.RawMessage) harness.OperationStat func readSessionRegister(t *testing.T, store harness.Storage, sessionID string) string { t.Helper() reg, err := store.ReadRegister(context.Background(), harness.RegisterKey{SessionID: sessionID, Kind: harness.RegisterSession}) - if err != nil { - t.Fatalf("ReadRegister(session): %v", err) - } + testassert.NoError(t, err) var wire struct { State struct { CurrentOperationID string `json:"current_operation_id"` } `json:"state"` } - if err := json.Unmarshal(reg.Payload, &wire); err != nil { - t.Fatalf("decode session register payload: %v", err) - } + testassert.NoError(t, json.Unmarshal(reg.Payload, &wire)) return wire.State.CurrentOperationID } @@ -531,9 +419,7 @@ func nextComposedEvent(t *testing.T, sub *runtime.Subscription) (runtime.Event, func composedEventKind(t *testing.T, event runtime.Event) string { t.Helper() kind, err := event.Discriminator() - if err != nil { - t.Fatalf("event discriminator: %v", err) - } + testassert.NoError(t, err) return kind } @@ -542,9 +428,7 @@ func composedEventKind(t *testing.T, event runtime.Event) string { func composedEventGeneration(t *testing.T, event runtime.Event) string { t.Helper() body, err := event.AsConfigurationChangedEvent() - if err != nil { - t.Fatalf("configuration event body: %v", err) - } + testassert.NoError(t, err) return body.ConfigurationRevision.Generation } @@ -580,28 +464,24 @@ func TestComposedSQLiteRuntimeObservationShutdownAndOwnership(t *testing.T) { ctx := context.Background() e := newComposeEnv(t) r, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } + testassert.NoError(t, err) sat, err := r.Subscribe(1) - if err != nil { - t.Fatalf("Subscribe(saturated): %v", err) - } + testassert.NoError(t, err) healthy, err := r.Subscribe(8) - if err != nil { - t.Fatalf("Subscribe(healthy): %v", err) - } + testassert.NoError(t, err) for _, want := range []string{"2", "3"} { - if revision, err := r.Reload(ctx); err != nil || revision != want { - t.Fatalf("Reload = (%q, %v), want %s", revision, err, want) + { + revision, err := r.Reload(ctx) + testassert.Require(t, err == nil && revision == want, "Reload = (%q, %v), want %s", revision, err, want) } } first, ok := nextComposedEvent(t, sat) if !ok || composedEventGeneration(t, first) != "2" { t.Fatalf("saturated subscriber first event = %s (ok=%v), want revision 2", composedEventJSON(first), ok) } - if _, ok := nextComposedEvent(t, sat); ok { - t.Fatal("saturated subscriber still open at the second publication, want it removed and closed") + { + _, ok := nextComposedEvent(t, sat) + testassert.Require(t, !ok, "saturated subscriber still open at the second publication, want it removed and closed") } sat.Close() var observed []runtime.Event @@ -612,11 +492,10 @@ func TestComposedSQLiteRuntimeObservationShutdownAndOwnership(t *testing.T) { } observed = append(observed, event) } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - if got := sortedNames(t, e.dataDir); !slices.Equal(got, []string{"lightcode.db", "runtime.lock"}) { - t.Fatalf("data root after Close = %v, want exactly the ownership lock and the composed database", got) + testassert.NoError(t, r.Close(ctx)) + { + got := sortedNames(t, e.dataDir) + testassert.Require(t, slices.Equal(got, []string{"lightcode.db", "runtime.lock"}), "data root after Close = %v, want exactly the ownership lock and the composed database", got) } for { event, ok := nextComposedEvent(t, healthy) @@ -630,16 +509,10 @@ func TestComposedSQLiteRuntimeObservationShutdownAndOwnership(t *testing.T) { composedConfigurationEvent(3), composedRuntimeScopeClosed(), } - if !slices.EqualFunc(observed, want, runtime.EqualEventForTest) { - t.Fatalf("healthy subscriber events = %+v, want the observed revisions plus the Runtime closure in publication order %+v", observed, want) - } + testassert.Require(t, slices.EqualFunc(observed, want, runtime.EqualEventForTest), "healthy subscriber events = %+v, want the observed revisions plus the Runtime closure in publication order %+v", observed, want) again, err := e.openComposed(ctx) - if err != nil { - t.Fatalf("reopen after the shutdown released ownership: %v", err) - } - if err := again.Close(ctx); err != nil { - t.Fatalf("Close after reacquire: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, again.Close(ctx)) } // TestBuiltinRegistrationComposes proves the shipped registration set — the @@ -652,17 +525,15 @@ func TestBuiltinRegistrationComposes(t *testing.T) { ctx := context.Background() e := newComposeEnv(t) r, err := runtime.OpenForTest(ctx, e.dataDir, e.configPath, builtin.Plugins()) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } - if revision, err := r.Reload(ctx); err != nil || revision != "2" { - t.Fatalf("Reload = (%q, %v), want 2", revision, err) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } - if got := sortedNames(t, e.dataDir); !slices.Equal(got, []string{"lightcode.db", "runtime.lock"}) { - t.Fatalf("data root after shutdown = %v, want exactly the real SQLite plugin's files", got) + testassert.NoError(t, err) + { + revision, err := r.Reload(ctx) + testassert.Require(t, err == nil && revision == "2", "Reload = (%q, %v), want 2", revision, err) + } + testassert.NoError(t, r.Close(ctx)) + { + got := sortedNames(t, e.dataDir) + testassert.Require(t, slices.Equal(got, []string{"lightcode.db", "runtime.lock"}), "data root after shutdown = %v, want exactly the real SQLite plugin's files", got) } } diff --git a/runtime/tasks_plugin_test.go b/runtime/tasks_plugin_test.go index 70f6dcb4..3437ff62 100644 --- a/runtime/tasks_plugin_test.go +++ b/runtime/tasks_plugin_test.go @@ -11,6 +11,7 @@ import ( "github.com/MMinasyan/lightcode/harness" "github.com/MMinasyan/lightcode/internal/agents" "github.com/MMinasyan/lightcode/internal/plugins/tasks" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -37,18 +38,12 @@ func openTask(t *testing.T) runtime.Tool { Kind: runtime.ScopeRuntime, DataDir: t.TempDir(), }, []runtime.Plugin{tasks.Plugin()}) - if err != nil { - t.Fatalf("compose tasks plugin: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = closeScope() }) value, ok := values["task"] - if !ok { - t.Fatalf("composition is missing the tool export %q", "task") - } + testassert.Require(t, ok, "composition is missing the tool export %q", "task") toolValue, ok := value.(runtime.Tool) - if !ok { - t.Fatalf("export %q supplies %T, not a runtime.Tool", "task", value) - } + testassert.Require(t, ok, "export %q supplies %T, not a runtime.Tool", "task", value) return toolValue } @@ -57,9 +52,7 @@ func openTask(t *testing.T) runtime.Tool { func taskContext(t *testing.T, sections map[string]string, background runtime.BackgroundServices) runtime.ToolContext { t.Helper() invocation, err := runtime.ConfiguredInvocationForTest(sections) - if err != nil { - t.Fatalf("ConfiguredInvocationForTest: %v", err) - } + testassert.NoError(t, err) return runtime.ToolContext{ AdmittedEntry: harness.EntryRef{SessionID: testSessionID, EntryID: testEntryID}, Invocation: invocation, @@ -95,27 +88,16 @@ func (*taskBackground) DeliverCompletion(context.Context, string, string, string func TestTaskDescriptionComposesFromRoster(t *testing.T) { spec := tasks.Plugin().Provides[0] invocation, err := runtime.ConfiguredInvocationForTest(nil) - if err != nil { - t.Fatalf("ConfiguredInvocationForTest: %v", err) - } + testassert.NoError(t, err) root, err := runtime.DescribeToolForTest(spec, invocation, runtime.ToolConstraints{}, harness.SessionIdentity{SessionID: testSessionID}) - if err != nil { - t.Fatalf("root describe: %v", err) - } - if !root.Available { - t.Fatalf("root advertisement = %+v, want the tool available", root) - } - if root.Definition.Name != "task" { - t.Fatalf("definition name = %q, want %q", root.Definition.Name, "task") - } + testassert.NoError(t, err) + testassert.Require(t, root.Available && root.Definition.Name == "task", "root advertisement = %+v, want the tool available; got %v, want %v", root, root.Definition.Name, "task") // The expected text composes from the loader's own roster: builtins // first, customs alphabetically, one retained line per Subagent entry. cfg, err := agents.ParseWithCapabilities([]byte("{}"), nil, nil, nil) - if err != nil { - t.Fatalf("agents.ParseWithCapabilities: %v", err) - } + testassert.NoError(t, err) var want strings.Builder want.WriteString(taskDescriptionPrefix) want.WriteString("\n\nAvailable subagent types:\n") @@ -139,36 +121,24 @@ func TestTaskDescriptionComposesFromRoster(t *testing.T) { } `json:"properties"` Required []string `json:"required"` } - if err := json.Unmarshal(root.Definition.Parameters, &schema); err != nil { - t.Fatalf("schema: %v", err) - } - if schema.Type != "object" { - t.Errorf("schema type = %q, want object", schema.Type) + testassert.NoError(t, json.Unmarshal(root.Definition.Parameters, &schema)) + testassert.Check(t, schema.Type == "object", "schema type = %q, want object", schema.Type) + testassert.Check(t, len(schema.Required) == 2 && schema.Required[0] == "prompt" && schema.Required[1] == "subagent_type", "schema required = %q, want prompt and subagent_type", schema.Required) + { + p := schema.Properties["prompt"] + testassert.Check(t, p.Type == "string" && p.Description == "The task prompt for this subagent.", "prompt property = %+v, want the retained leaf description", p) } - if len(schema.Required) != 2 || schema.Required[0] != "prompt" || schema.Required[1] != "subagent_type" { - t.Errorf("schema required = %q, want prompt and subagent_type", schema.Required) - } - if p := schema.Properties["prompt"]; p.Type != "string" || p.Description != "The task prompt for this subagent." { - t.Errorf("prompt property = %+v, want the retained leaf description", p) - } - if p := schema.Properties["subagent_type"]; p.Type != "string" || p.Description != "The type of subagent to use." { - t.Errorf("subagent_type property = %+v, want the retained leaf description", p) + { + p := schema.Properties["subagent_type"] + testassert.Check(t, p.Type == "string" && p.Description == "The type of subagent to use.", "subagent_type property = %+v, want the retained leaf description", p) } child, err := runtime.DescribeToolForTest(spec, invocation, runtime.ToolConstraints{}, harness.SessionIdentity{ SessionID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ParentSessionID: testSessionID, }) - if err != nil { - t.Fatalf("child describe: %v", err) - } - if child.Available { - t.Fatalf("child advertisement = %+v, want the tool unavailable on child Sessions", child) - } - if child.Definition.Name != "task" || child.Definition.Description == "" || - string(child.Definition.Parameters) != string(root.Definition.Parameters) { - t.Fatalf("child definition = %+v, want a still-valid definition", child.Definition) - } + testassert.NoError(t, err) + testassert.Require(t, !child.Available && child.Definition.Name == "task" && child.Definition.Description != "" && string(child.Definition.Parameters) == string(root.Definition.Parameters), "child advertisement = %+v, want the tool unavailable on child Sessions; child definition = %+v, want a still-valid definition", child, child.Definition) } // TestTaskPrepareRejectsNonSubagentType proves the roster's Subagent filter @@ -180,21 +150,12 @@ func TestTaskPrepareRejectsNonSubagentType(t *testing.T) { tc := taskContext(t, nil, &taskBackground{}) denied := prepare(t, tool, tc, "task", `{"prompt":"do it","subagent_type":"primary"}`) - if denied.Immediate == nil { - t.Fatalf("non-subagent prepare = %+v, want an immediate validation error", denied) - } + testassert.Require(t, denied.Immediate != nil, "non-subagent prepare = %+v, want an immediate validation error", denied) want := `unknown subagent type "primary": agent type is not available as a subagent` - if denied.Immediate.Result.Status != model.ResultError || denied.Immediate.Result.Content != want { - t.Fatalf("non-subagent result = %+v, want %q", denied.Immediate.Result, want) - } + testassert.Require(t, denied.Immediate.Result.Status == model.ResultError && denied.Immediate.Result.Content == want, "non-subagent result = %+v, want %q", denied.Immediate.Result, want) allowed := prepare(t, tool, tc, "task", `{"prompt":"do it","subagent_type":"secondary"}`) - if allowed.Immediate != nil { - t.Fatalf("subagent prepare = %+v, want a prepared plan", allowed.Immediate) - } - if len(allowed.Permissions) != 1 || allowed.Permissions[0] != (harness.PermissionRequest{Permission: "child.launch", Target: "secondary"}) { - t.Fatalf("permissions = %+v, want the one child.launch pair on the type name", allowed.Permissions) - } + testassert.Require(t, allowed.Immediate == nil && len(allowed.Permissions) == 1 && allowed.Permissions[0] == (harness.PermissionRequest{Permission: "child.launch", Target: "secondary"}), "subagent prepare = %+v, want a prepared plan; permissions = %+v, want the one child.launch pair on the type name", allowed.Immediate, allowed.Permissions) } // TestTaskLaunchForwardsFields proves the launch request carries every field @@ -219,37 +180,19 @@ func TestTaskLaunchForwardsFields(t *testing.T) { ctx := taskContext(t, tc.sections, background) plan := prepare(t, tool, ctx, "task", `{"prompt":"do the thing","subagent_type":"secondary"}`) - if plan.Immediate != nil { - t.Fatalf("prepare = %+v, want a prepared plan", plan.Immediate) - } + testassert.Require(t, plan.Immediate == nil, "prepare = %+v, want a prepared plan", plan.Immediate) outcome := plan.Execute(context.Background()) wantContent := fmt.Sprintf("Task started in the background with session ID: `%s`. You will be notified when it completes. Continue with your next steps; the result will arrive as a message when it is ready.", taskChildID) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != wantContent { - t.Fatalf("outcome = %+v, want the retained immediate text with the child session id", outcome.Result) - } - if len(background.launched) != 1 { - t.Fatalf("launches = %d, want exactly one", len(background.launched)) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == wantContent, "outcome = %+v, want the retained immediate text with the child session id", outcome.Result) + testassert.Equal(t, len(background.launched), 1) req := background.launched[0] - if req.ParentSessionID != testSessionID { - t.Errorf("ParentSessionID = %q, want %q", req.ParentSessionID, testSessionID) - } - if req.AgentType != "secondary" { - t.Errorf("AgentType = %q, want secondary", req.AgentType) - } - if len(req.Content) != 1 || req.Content[0].Kind != model.PartText || req.Content[0].Text != "do the thing" { - t.Errorf("Content = %+v, want the prompt as one text part", req.Content) - } - if !isLowerHexID(req.OperationID) { - t.Errorf("OperationID = %q, want a fresh 32-lowercase-hex identity", req.OperationID) - } - if req.MaxConcurrent != tc.wantConcurrent { - t.Errorf("MaxConcurrent = %d, want %d", req.MaxConcurrent, tc.wantConcurrent) - } - if req.OutputLimit != tc.wantOutput { - t.Errorf("OutputLimit = %d, want %d", req.OutputLimit, tc.wantOutput) - } + testassert.Check(t, req.ParentSessionID == testSessionID, "ParentSessionID = %q, want %q", req.ParentSessionID, testSessionID) + testassert.Check(t, req.AgentType == "secondary", "AgentType = %q, want secondary", req.AgentType) + testassert.Check(t, len(req.Content) == 1 && req.Content[0].Kind == model.PartText && req.Content[0].Text == "do the thing", "Content = %+v, want the prompt as one text part", req.Content) + testassert.Check(t, isLowerHexID(req.OperationID), "OperationID = %q, want a fresh 32-lowercase-hex identity", req.OperationID) + testassert.Check(t, req.MaxConcurrent == tc.wantConcurrent, "MaxConcurrent = %d, want %d", req.MaxConcurrent, tc.wantConcurrent) + testassert.Check(t, req.OutputLimit == tc.wantOutput, "OutputLimit = %d, want %d", req.OutputLimit, tc.wantOutput) }) } } @@ -288,19 +231,13 @@ func TestTaskLaunchErrorsRenderAsToolErrors(t *testing.T) { ctx := taskContext(t, nil, background) plan := prepare(t, tool, ctx, "task", `{"prompt":"do the thing","subagent_type":"secondary"}`) - if plan.Immediate != nil { - t.Fatalf("prepare = %+v, want a prepared plan", plan.Immediate) - } + testassert.Require(t, plan.Immediate == nil, "prepare = %+v, want a prepared plan", plan.Immediate) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError { - t.Errorf("status = %q, want %q", outcome.Result.Status, model.ResultError) - } + testassert.Check(t, outcome.Result.Status == model.ResultError, "status = %q, want %q", outcome.Result.Status, model.ResultError) if outcome.Result.Content != tc.err.Error() { t.Errorf("content = %q, want the harness rejection text %q", outcome.Result.Content, tc.err.Error()) } - if len(background.launched) != 1 { - t.Fatalf("launches = %d, want the bridge reached exactly once", len(background.launched)) - } + testassert.Equal(t, len(background.launched), 1) }) } } @@ -315,9 +252,7 @@ func TestComposedTaskPermissionDenialSettlesOnlyTheCall(t *testing.T) { {"permission":"child.launch","target":"secondary","access":"deny"} ]}`), nil) invocation, err := runtime.ConfiguredInvocationForTest(nil) - if err != nil { - t.Fatalf("ConfiguredInvocationForTest: %v", err) - } + testassert.NoError(t, err) background := &taskBackground{result: harness.LaunchChildResult{ChildSessionID: taskChildID}} attempt := 0 var mu sync.Mutex @@ -346,26 +281,17 @@ func TestComposedTaskPermissionDenialSettlesOnlyTheCall(t *testing.T) { session := th.createSession() th.submit(session, "op-1", "exercise the denied and allowed launches") rec := th.awaitSettled(session, "op-1") - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) - } + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) results := th.readToolResults(session) - if len(results) != 2 { - t.Fatalf("committed tool results = %d, want exactly the two calls", len(results)) - } + testassert.Equal(t, len(results), 2) denied := results["call-denied"] - if denied.Status != model.ResultDenied || denied.Content != "Permission denied." { - t.Fatalf("call-denied = %+v, want the fixed denial", denied) - } + testassert.Require(t, denied.Status == model.ResultDenied && denied.Content == "Permission denied.", "call-denied = %+v, want the fixed denial", denied) allowed := results["call-allowed"] - if allowed.Status != model.ResultSuccess || !strings.HasPrefix(allowed.Content, "Task started in the background with session ID: `") { - t.Fatalf("call-allowed = %+v, want the retained immediate text", allowed) - } - if len(background.launched) != 1 { - t.Fatalf("launches = %d, want only the allowed call to reach the bridge", len(background.launched)) - } - if launch := background.launched[0]; launch.AgentType != "explore" { - t.Fatalf("launched AgentType = %q, want explore", launch.AgentType) + testassert.Require(t, allowed.Status == model.ResultSuccess && strings.HasPrefix(allowed.Content, "Task started in the background with session ID: `"), "call-allowed = %+v, want the retained immediate text", allowed) + testassert.Equal(t, len(background.launched), 1) + { + launch := background.launched[0] + testassert.Equal(t, launch.AgentType, "explore") } } diff --git a/runtime/tools_plugin_config_test.go b/runtime/tools_plugin_config_test.go index 5b3901e0..55d61852 100644 --- a/runtime/tools_plugin_config_test.go +++ b/runtime/tools_plugin_config_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/MMinasyan/lightcode/harness" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/runtime" ) @@ -24,40 +25,26 @@ func TestToolsPluginConsumesConfiguredValues(t *testing.T) { ctx := context.Background() byID, _ := openToolsComposed(t, t.TempDir(), fakeJobsPlugin(&fakeJobs{}), runtime.Plugin{}) value, ok := byID["read_file"] - if !ok { - t.Fatal("plugin instance exports no read_file") - } + testassert.Require(t, ok, "plugin instance exports no read_file") readTool := value call := func(t *testing.T, args string) model.ToolCall { t.Helper() completed, err := model.NewToolCall(model.ToolCall{ID: "call-1", Name: "read_file", Arguments: json.RawMessage(args)}) - if err != nil { - t.Fatalf("NewToolCall: %v", err) - } + testassert.NoError(t, err) return completed } configured, err := runtime.ConfiguredInvocationForTest(map[string]string{"tools": `{"read_max_lines":77}`}) - if err != nil { - t.Fatalf("ConfiguredInvocationForTest: %v", err) - } + testassert.NoError(t, err) raw, err := readTool.Normalize(runtime.ToolContext{Invocation: configured}, call(t, `{"path":"a.txt"}`)) - if err != nil { - t.Fatalf("Normalize: %v", err) - } - if string(raw) != `{"limit":77,"offset":1,"path":"a.txt"}` { - t.Fatalf("configured normalization = %s, want the configured default limit 77", raw) - } + testassert.NoError(t, err) + testassert.Require(t, string(raw) == `{"limit":77,"offset":1,"path":"a.txt"}`, "configured normalization = %s, want the configured default limit 77", raw) // The zero Invocation keeps the shipped default: the channel, not the // decoder, carries the configured value. raw, err = readTool.Normalize(runtime.ToolContext{}, call(t, `{"path":"a.txt"}`)) - if err != nil { - t.Fatalf("zero-Invocation Normalize: %v", err) - } - if string(raw) != `{"limit":500,"offset":1,"path":"a.txt"}` { - t.Fatalf("zero-Invocation normalization = %s, want the shipped default limit 500", raw) - } + testassert.NoError(t, err) + testassert.Require(t, string(raw) == `{"limit":500,"offset":1,"path":"a.txt"}`, "zero-Invocation normalization = %s, want the shipped default limit 500", raw) // End to end through the real pipeline: Normalize commits the // configured default limit, and Prepare consumes those committed bytes — @@ -67,30 +54,16 @@ func TestToolsPluginConsumesConfiguredValues(t *testing.T) { for i := range lines { lines[i] = strings.Repeat("x", 10) } - if err := os.WriteFile(filepath.Join(ws, "big.txt"), []byte(strings.Join(lines, "\n")), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "big.txt"), []byte(strings.Join(lines, "\n")), 0o644)) tc := runtime.ToolContext{ Workspace: ws, AdmittedEntry: harness.EntryRef{SessionID: "0123456789abcdef0123456789abcdef", EntryID: "fedcba9876543210fedcba9876543210"}, Invocation: configured, } committed, err := readTool.Normalize(tc, call(t, `{"path":"big.txt"}`)) - if err != nil { - t.Fatalf("Normalize: %v", err) - } + testassert.NoError(t, err) plan := readTool.Prepare(ctx, tc, call(t, string(committed))) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want one executor", plan) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil, "plan = %+v, want one executor", plan) outcome := plan.Execute(ctx) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want success", outcome.Result) - } - if !strings.HasPrefix(outcome.Result.Content, "1\txxxxxxxxxx\n") || !strings.Contains(outcome.Result.Content, "\n77\txxxxxxxxxx\n") { - t.Fatalf("content = %q, want the configured 77-line window", outcome.Result.Content) - } - if !strings.HasSuffix(outcome.Result.Content, "(Showing lines 1-77 of 100. Use offset=78 to continue.)") { - t.Fatalf("content = %q, want the configured-limit pagination footer", outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.HasPrefix(outcome.Result.Content, "1\txxxxxxxxxx\n") && strings.Contains(outcome.Result.Content, "\n77\txxxxxxxxxx\n") && strings.HasSuffix(outcome.Result.Content, "(Showing lines 1-77 of 100. Use offset=78 to continue.)"), "result = %+v, want success; content = %q, want the configured 77-line window; content = %q, want the configured-limit pagination footer", outcome.Result, outcome.Result.Content, outcome.Result.Content) } diff --git a/runtime/tools_plugin_test.go b/runtime/tools_plugin_test.go index 95a97f62..dd04225b 100644 --- a/runtime/tools_plugin_test.go +++ b/runtime/tools_plugin_test.go @@ -21,6 +21,7 @@ import ( "github.com/MMinasyan/lightcode/internal/plugins/sqlite" "github.com/MMinasyan/lightcode/internal/plugins/tools" "github.com/MMinasyan/lightcode/internal/storage" + "github.com/MMinasyan/lightcode/internal/testassert" "github.com/MMinasyan/lightcode/internal/tool" "github.com/MMinasyan/lightcode/model" "github.com/MMinasyan/lightcode/plugins/jobs" @@ -45,9 +46,7 @@ const toolsComposedNullDocument = `{"providers":{"prov":{"transport":{"base_url" func writeToolsConfig(t *testing.T, path, doc string) { t.Helper() - if err := os.WriteFile(path, []byte(doc), 0o600); err != nil { - t.Fatalf("write config: %v", err) - } + testassert.NoError(t, os.WriteFile(path, []byte(doc), 0o600)) } // TestComposedToolsPluginPublication composes the real tools plugin through @@ -60,9 +59,7 @@ func TestComposedToolsPluginPublication(t *testing.T) { e := newComposeEnv(t) writeToolsConfig(t, e.configPath, toolsComposedDocument) r, err := runtime.OpenForTest(ctx, e.dataDir, e.configPath, []runtime.Plugin{sqlite.Plugin(), fakeJobsPlugin(&fakeJobs{}), tools.Plugin()}) - if err != nil { - t.Fatalf("OpenForTest: %v", err) - } + testassert.NoError(t, err) if rev, err := r.Reload(ctx); err != nil || rev != "2" { _ = r.Close(ctx) t.Fatalf("first Reload = (%q, %v), want revision 2", rev, err) @@ -83,9 +80,7 @@ func TestComposedToolsPluginPublication(t *testing.T) { _ = r.Close(ctx) t.Fatalf("Reload after the failures = (%q, %v), want generation 3: failed publications published nothing", rev, err) } - if err := r.Close(ctx); err != nil { - t.Fatalf("Close: %v", err) - } + testassert.NoError(t, r.Close(ctx)) } // The admitted-input identity the direct preparation tests prepare with. @@ -171,20 +166,14 @@ func openToolsComposed(t *testing.T, dataDir string, jobsPlugin, extra runtime.P Kind: runtime.ScopeRuntime, DataDir: dataDir, }, plugins) - if err != nil { - t.Fatalf("compose tools plugin: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = closeScope() }) byID := make(map[string]runtime.Tool, len(toolExportIDs)) for _, id := range toolExportIDs { value, ok := values[id] - if !ok { - t.Fatalf("composition is missing the tool export %q", id) - } + testassert.Require(t, ok, "composition is missing the tool export %q", id) toolValue, ok := value.(runtime.Tool) - if !ok { - t.Fatalf("export %q supplies %T, not a runtime.Tool", id, value) - } + testassert.Require(t, ok, "export %q supplies %T, not a runtime.Tool", id, value) byID[id] = toolValue } return byID, values @@ -201,21 +190,15 @@ func openTools(t *testing.T, dataDir string) map[string]runtime.Tool { // export usable as a runtime.Tool once a jobs provider is registered. func TestComposedToolsPluginContract(t *testing.T) { p := tools.Plugin() - if p.ID != "tools" || p.Scope != runtime.ScopeRuntime || p.ValidateConfig == nil || p.Open == nil { - t.Fatalf("plugin declaration = %+v, want the Runtime-scoped tools plugin with a validator and an opener", p) - } - if len(p.Provides) != 7 { - t.Fatalf("plugin declares %d exports, want the four file tools plus run_command, process, and sleep", len(p.Provides)) - } + testassert.Require(t, p.ID == "tools" && p.Scope == runtime.ScopeRuntime && p.ValidateConfig != nil && p.Open != nil && len(p.Provides) == 7, "plugin declaration = %+v, want the Runtime-scoped tools plugin with a validator and an opener; got %v, want %v", p, len(p.Provides), 7) // Without a jobs provider the composed open fails, naming the required // capability: there is no degraded mode. - if _, _, err := runtime.ComposeScopeForTest(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, []runtime.Plugin{tools.Plugin()}); err == nil || !strings.Contains(err.Error(), `"jobs"`) { - t.Fatalf("open without a jobs provider = %v, want the unresolved jobs dependency error", err) + { + _, _, err := runtime.ComposeScopeForTest(context.Background(), runtime.ScopeInfo{Kind: runtime.ScopeRuntime, DataDir: t.TempDir()}, []runtime.Plugin{tools.Plugin()}) + testassert.Require(t, err != nil && strings.Contains(err.Error(), `"jobs"`), "open without a jobs provider = %v, want the unresolved jobs dependency error", err) } byID, _ := openToolsComposed(t, t.TempDir(), fakeJobsPlugin(&fakeJobs{}), runtime.Plugin{}) - if len(byID) != 7 { - t.Fatalf("composed open returned %d tools, want all seven", len(byID)) - } + testassert.Equal(t, len(byID), 7) } // callToolContext is the ToolContext the direct tests prepare with: a real @@ -251,9 +234,7 @@ func envCallContext(t *testing.T, workspace string, constraints runtime.ToolCons func makeCall(t *testing.T, name, args string) model.ToolCall { t.Helper() call, err := model.NewToolCall(model.ToolCall{ID: "call-1", Name: name, Arguments: json.RawMessage(args)}) - if err != nil { - t.Fatalf("NewToolCall: %v", err) - } + testassert.NoError(t, err) return call } @@ -333,9 +314,7 @@ var backgroundImmediatePattern = regexp.MustCompile("^Command running in the bac func assertBackgroundImmediate(t *testing.T, content string) string { t.Helper() m := backgroundImmediatePattern.FindStringSubmatch(content) - if m == nil { - t.Fatalf("result %q does not match the retained background template", content) - } + testassert.Require(t, m != nil, "result %q does not match the retained background template", content) return m[1] } @@ -391,10 +370,8 @@ func toolsCapture(names []string) harness.ExecutionCapture { SystemPrompt: "composed tools", Tools: definitions, Compact: harness.CompactCapture{ - Model: model.ModelRef{Provider: "prov", Model: "m"}, - ContextWindow: 2048, - OutputReserve: 1024, - SystemPrompt: "summarize", + Model: model.ModelRef{Provider: "prov", Model: "m"}, + SystemPrompt: "summarize", }, } } @@ -449,9 +426,7 @@ func newToolsHarnessWith(t *testing.T, modelFn func(context.Context, model.Reque store := opts.store if store == nil { opened, err := storage.OpenSQLite(filepath.Join(dataDir, "lightcode.db")) - if err != nil { - t.Fatalf("OpenSQLite: %v", err) - } + testassert.NoError(t, err) t.Cleanup(func() { _ = opened.Close() }) store = opened } @@ -463,13 +438,9 @@ func newToolsHarnessWith(t *testing.T, modelFn func(context.Context, model.Reque byID, values := openToolsComposed(t, dataDir, jobsPlugin, opts.extraPlugin) if opts.extraToolID != "" { value, ok := values[opts.extraToolID] - if !ok { - t.Fatalf("composition is missing the extra tool export %q", opts.extraToolID) - } + testassert.Require(t, ok, "composition is missing the extra tool export %q", opts.extraToolID) toolValue, ok := value.(runtime.Tool) - if !ok { - t.Fatalf("export %q supplies %T, not a runtime.Tool", opts.extraToolID, value) - } + testassert.Require(t, ok, "export %q supplies %T, not a runtime.Tool", opts.extraToolID, value) byID[opts.extraToolID] = toolValue } advertise := opts.advertise @@ -671,39 +642,23 @@ func TestComposedToolsHarnessAllowDenyEffect(t *testing.T) { } } th := newToolsHarness(t, modelFn) - if err := os.WriteFile(filepath.Join(th.workspace, "notes.txt"), []byte("hello\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(th.workspace, ".env"), []byte("SECRET=1\n"), 0o600); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(th.workspace, "notes.txt"), []byte("hello\n"), 0o644)) + testassert.NoError(t, os.WriteFile(filepath.Join(th.workspace, ".env"), []byte("SECRET=1\n"), 0o600)) session := th.createSession() th.submit(session, "op-1", "run the tools") rec := th.awaitSettled(session, "op-1") - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) - } + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) results := th.readToolResults(session) - if len(results) != 3 { - t.Fatalf("committed tool results = %d, want exactly the three calls", len(results)) - } + testassert.Equal(t, len(results), 3) allowed := results["call-allowed"] - if allowed.Status != model.ResultSuccess || !strings.Contains(allowed.Content, "1\thello") { - t.Fatalf("allowed read = %+v, want the bounded numbered content", allowed) - } + testassert.Require(t, allowed.Status == model.ResultSuccess && strings.Contains(allowed.Content, "1\thello"), "allowed read = %+v, want the bounded numbered content", allowed) denied := results["call-denied"] - if denied.Status != model.ResultDenied || denied.Content != "Permission denied." { - t.Fatalf("sensitive read = %+v, want the fixed denial", denied) - } + testassert.Require(t, denied.Status == model.ResultDenied && denied.Content == "Permission denied.", "sensitive read = %+v, want the fixed denial", denied) written := results["call-write"] - if written.Status != model.ResultSuccess || !strings.Contains(written.Content, "Wrote") { - t.Fatalf("write = %+v, want the write success", written) - } + testassert.Require(t, written.Status == model.ResultSuccess && strings.Contains(written.Content, "Wrote"), "write = %+v, want the write success", written) data, err := os.ReadFile(filepath.Join(th.workspace, "out", "new.txt")) - if err != nil || string(data) != "written" { - t.Fatalf("written file = (%q, %v), want the effect on disk", data, err) - } + testassert.Require(t, err == nil && string(data) == "written", "written file = (%q, %v), want the effect on disk", data, err) // The mutating call's code group lives at DataDir/code///snapshots/1/. th.mu.Lock() @@ -715,21 +670,19 @@ func TestComposedToolsHarnessAllowDenyEffect(t *testing.T) { t.Fatalf("code group %s = (%d entries, %v), want exactly one snapshot entry", group, len(entries), err) } metaData, err := os.ReadFile(filepath.Join(group, entries[0].Name(), "meta.json")) - if err != nil { - t.Fatalf("snapshot meta: %v", err) - } + testassert.NoError(t, err) var meta struct { OriginalPath string `json:"original_path"` Existed bool `json:"existed"` } - if err := json.Unmarshal(metaData, &meta); err != nil { - t.Fatalf("decode snapshot meta %s: %v", metaData, err) + { + err := json.Unmarshal(metaData, &meta) + testassert.Require(t, err == nil, "decode snapshot meta %s: %v", metaData, err) } - if meta.Existed || !strings.HasSuffix(meta.OriginalPath, "out/new.txt") { - t.Fatalf("snapshot meta = %s, want the new-file preimage of the declared target", metaData) - } - if _, err := os.Stat(filepath.Join(th.dataDir, "code", session, "missing")); !errors.Is(err, os.ErrNotExist) { - t.Fatal("an unexpected second group directory exists") + testassert.Require(t, !meta.Existed && strings.HasSuffix(meta.OriginalPath, "out/new.txt"), "snapshot meta = %s, want the new-file preimage of the declared target", metaData) + { + _, err := os.Stat(filepath.Join(th.dataDir, "code", session, "missing")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "an unexpected second group directory exists") } } @@ -765,31 +718,19 @@ func TestComposedToolsHarnessRecovery(t *testing.T) { // abandoned running state before any execution can start. e := newComposeEnv(t) r, err := runtime.OpenForTest(ctx, th.dataDir, e.configPath, []runtime.Plugin{sqlite.Plugin(), fakeJobsPlugin(&fakeJobs{}), tools.Plugin()}) - if err != nil { - t.Fatalf("OpenForTest recovery: %v", err) - } - if err := r.Close(ctx); err != nil { - t.Fatalf("recovery Close: %v", err) - } + testassert.NoError(t, err) + testassert.NoError(t, r.Close(ctx)) key := harness.RegisterKey{SessionID: session, Kind: harness.RegisterOperation, OperationID: "op-1"} reg, err := th.store.ReadRegister(ctx, key) - if err != nil { - t.Fatalf("ReadRegister after recovery: %v", err) - } - if status := registerStatus(t, reg.Payload); status != harness.OperationInterruption { - t.Fatalf("recovered operation status = %q, want the terminal interruption settlement", status) - } + testassert.NoError(t, err) + testassert.Equal(t, registerStatus(t, reg.Payload), harness.OperationInterruption) entries, err := th.store.ReadEntries(ctx, session, 0) - if err != nil { - t.Fatalf("ReadEntries after recovery: %v", err) - } + testassert.NoError(t, err) var kinds []string for _, entry := range entries { kinds = append(kinds, string(entry.Kind)) } - if !strings.Contains(strings.Join(kinds, ","), string(harness.EntryInput)) { - t.Fatalf("recovered entries = %v, want the admitted input intact", kinds) - } + testassert.Require(t, strings.Contains(strings.Join(kinds, ","), string(harness.EntryInput)), "recovered entries = %v, want the admitted input intact", kinds) } func TestRunCommandPrepareTargets(t *testing.T) { @@ -799,12 +740,7 @@ func TestRunCommandPrepareTargets(t *testing.T) { t.Run("conclusive simple commands declare one target per segment", func(t *testing.T) { plan := prepare(t, byID["run_command"], envCallContext(t, ws, runtime.ToolConstraints{}), "run_command", `{"command":"echo one; echo two && echo three"}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want an executor plan", plan) - } - if len(plan.Permissions) != 3 { - t.Fatalf("pairs = %+v, want one command.run pair per segment", plan.Permissions) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) == 3, "plan = %+v, want an executor plan; pairs = %+v, want one command.run pair per segment", plan, plan.Permissions) want := []string{"echo one", "echo two", "echo three"} for i, pair := range plan.Permissions { if pair.Permission != "command.run" || pair.Target != want[i] { @@ -825,81 +761,51 @@ func TestRunCommandPrepareTargets(t *testing.T) { {"{\"command\":\" \\ncat <= 2*time.Second, "elapsed %s, want the 1s override ignored in favor of the captured default", elapsed) } }) } @@ -941,25 +844,19 @@ func TestRunCommandExecuteOutcomes(t *testing.T) { t.Run("success carries the captured output", func(t *testing.T) { plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"printf ok"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != "ok" { - t.Fatalf("result = %+v, want the command output", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "ok", "result = %+v, want the command output", outcome.Result) }) t.Run("nonzero exit settles exactly as the legacy engine settles the ExitError", func(t *testing.T) { plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"printf fail && exit 7"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "Error: Exit code 7\nfail" { - t.Fatalf("result = %+v, want the engine's error settlement with the ExitError output", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "Error: Exit code 7\nfail", "result = %+v, want the engine's error settlement with the ExitError output", outcome.Result) }) t.Run("a configured timeout settles as an error result with the retained text", func(t *testing.T) { plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"sleep 5","timeout":1}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || !strings.HasPrefix(outcome.Result.Content, "Error: Exit code -1 (timeout)\n") { - t.Fatalf("result = %+v, want the retained timeout text as an error result", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.HasPrefix(outcome.Result.Content, "Error: Exit code -1 (timeout)\n"), "result = %+v, want the retained timeout text as an error result", outcome.Result) }) t.Run("cancellation settles as an interrupted result with the retained text", func(t *testing.T) { @@ -980,9 +877,7 @@ func TestRunCommandExecuteOutcomes(t *testing.T) { cancel() select { case outcome := <-outcomeCh: - if outcome.Status != model.ResultInterrupted || outcome.Body != "command cancelled" { - t.Fatalf("outcome = (%v, %q), want the retained cancellation text as interrupted", outcome.Status, outcome.Body) - } + testassert.Require(t, outcome.Status == model.ResultInterrupted && outcome.Body == "command cancelled", "outcome = (%v, %q), want the retained cancellation text as interrupted", outcome.Status, outcome.Body) case <-time.After(5 * time.Second): t.Fatal("cancelled command did not return") } @@ -991,13 +886,9 @@ func TestRunCommandExecuteOutcomes(t *testing.T) { t.Run("output spills under the calling session's output directory", func(t *testing.T) { plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"yes overflowing-output | head -c 20000"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want success with the spill marker", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "result = %+v, want success with the spill marker", outcome.Result) wantPrefix := filepath.Join(dataDir, "code", testSessionID, "output", "cmd_output_") - if !strings.Contains(outcome.Result.Content, "saved to: "+wantPrefix) { - t.Fatalf("result = %q, want the spill marker under %q", outcome.Result.Content, wantPrefix) - } + testassert.Require(t, strings.Contains(outcome.Result.Content, "saved to: "+wantPrefix), "result = %q, want the spill marker under %q", outcome.Result.Content, wantPrefix) marker := "saved to: " idx := strings.LastIndex(outcome.Result.Content, marker) path := outcome.Result.Content[idx+len(marker):] @@ -1005,43 +896,28 @@ func TestRunCommandExecuteOutcomes(t *testing.T) { path = path[:end] } data, err := os.ReadFile(strings.TrimSpace(path)) - if err != nil { - t.Fatalf("ReadFile(%q): %v", path, err) - } - if len(data) != 20000 { - t.Fatalf("spill size = %d, want the full 20000 output bytes", len(data)) - } + testassert.Require(t, err == nil, "ReadFile(%q): %v", path, err) + testassert.Equal(t, len(data), 20000) }) t.Run("the managed key connected after Open is scrubbed while an unlisted sibling and the workspace cwd remain", func(t *testing.T) { t.Setenv("LIGHTCODE_TOOLS_PLUGIN_TEST", "env-value") t.Setenv("LIGHTCODE_TOOLS_PLUGIN_MANAGED", "managed-secret") // cleanup reservation only - if err := os.Unsetenv("LIGHTCODE_TOOLS_PLUGIN_MANAGED"); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Unsetenv("LIGHTCODE_TOOLS_PLUGIN_MANAGED")) byID, _ := openToolsComposed(t, dataDir, fakeJobsPlugin(&fakeJobs{}), runtime.Plugin{}) runCommand := byID["run_command"] // The key connects AFTER the tools plugin opened: the call-time // subprocess-environment producer, not any Open-captured copy, must // scrub it from the child. managed := config.NewManagedEnvForTest(filepath.Join(t.TempDir(), ".env")) - if err := managed.TrySet("LIGHTCODE_TOOLS_PLUGIN_MANAGED", "managed-secret"); err != nil { - t.Fatalf("managed TrySet: %v", err) - } + testassert.NoError(t, managed.TrySet("LIGHTCODE_TOOLS_PLUGIN_MANAGED", "managed-secret")) callTC := tc callTC.SubprocessEnv = managed.SubprocessEnv plan := prepare(t, runCommand, callTC, "run_command", `{"command":"printf '%s|%s' \"$LIGHTCODE_TOOLS_PLUGIN_TEST\" \"$LIGHTCODE_TOOLS_PLUGIN_MANAGED\"; pwd"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "result = %+v, want success", outcome.Result) content := outcome.Result.Content - if !strings.Contains(content, "env-value|") || strings.Contains(content, "managed-secret") { - t.Fatalf("result = %q, want the unlisted key visible and the managed key scrubbed", content) - } - if !strings.Contains(content, ws) { - t.Fatalf("result = %q, want the workspace cwd", content) - } + testassert.Require(t, strings.Contains(content, "env-value|") && !strings.Contains(content, "managed-secret") && strings.Contains(content, ws), "result = %q, want the unlisted key visible and the managed key scrubbed; result = %q, want the workspace cwd", content, content) }) } @@ -1059,41 +935,27 @@ func TestSleepTool(t *testing.T) { `null`, // whole-null arguments `{"seconds":99999999999999999999}`, // too large } { - if _, err := normalize(t, sleepTool, tc, "sleep", args); err == nil { - t.Errorf("sleep normalized %s, want rejection", args) + { + _, err := normalize(t, sleepTool, tc, "sleep", args) + testassert.Check(t, err != nil, "sleep normalized %s, want rejection", args) } } raw, err := normalize(t, sleepTool, tc, "sleep", `{"seconds":2,"_lightcode_receipt":"x"}`) - if err != nil { - t.Fatalf("Normalize: %v", err) - } - if !strings.Contains(string(raw), `"seconds":2`) || strings.Contains(string(raw), "_lightcode_receipt") { - t.Fatalf("normalized = %s, want the clamped value with private fields stripped", raw) - } + testassert.NoError(t, err) + testassert.Require(t, strings.Contains(string(raw), `"seconds":2`) && !strings.Contains(string(raw), "_lightcode_receipt"), "normalized = %s, want the clamped value with private fields stripped", raw) raw, err = normalize(t, sleepTool, tc, "sleep", `{"seconds":5000}`) - if err != nil || !strings.Contains(string(raw), `"seconds":300`) { - t.Fatalf("normalized = (%s, %v), want the 300 clamp", raw, err) - } + testassert.Require(t, err == nil && strings.Contains(string(raw), `"seconds":300`), "normalized = (%s, %v), want the 300 clamp", raw, err) for _, args := range []string{`{}`, `{"seconds":0}`, `{"seconds":-5}`} { raw, err := normalize(t, sleepTool, tc, "sleep", args) - if err != nil || !strings.Contains(string(raw), `"seconds":1`) { - t.Fatalf("normalized = (%s, %v), want the retained default 1 for %s", raw, err, args) - } + testassert.Require(t, err == nil && strings.Contains(string(raw), `"seconds":1`), "normalized = (%s, %v), want the retained default 1 for %s", raw, err, args) } }) t.Run("prepare declares the fixed sleep target", func(t *testing.T) { plan := prepare(t, sleepTool, tc, "sleep", `{"seconds":1}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want an executor plan", plan) - } - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "sleep" || plan.Permissions[0].Target != "*" { - t.Fatalf("pairs = %+v, want the fixed sleep target *", plan.Permissions) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "sleep" && plan.Permissions[0].Target == "*", "plan = %+v, want an executor plan; pairs = %+v, want the fixed sleep target *", plan, plan.Permissions) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != "Slept for 1 seconds." { - t.Fatalf("result = %+v, want the retained sleep result", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "Slept for 1 seconds.", "result = %+v, want the retained sleep result", outcome.Result) }) t.Run("cancellation settles as an interrupted result", func(t *testing.T) { @@ -1112,9 +974,7 @@ func TestSleepTool(t *testing.T) { cancel() select { case got := <-outcomeCh: - if got.status != model.ResultInterrupted || got.body == "" { - t.Fatalf("outcome = (%v, %q), want a nonempty interrupted result", got.status, got.body) - } + testassert.Require(t, got.status == model.ResultInterrupted && got.body != "", "outcome = (%v, %q), want a nonempty interrupted result", got.status, got.body) case <-time.After(5 * time.Second): t.Fatal("cancelled sleep did not return") } @@ -1136,8 +996,9 @@ func TestReadOnlyRunCommandExecutesRewriteSuppressingGitContentHelpers(t *testin t.Helper() cmd := exec.Command("git", args...) cmd.Dir = repo - if output, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("git %v: %v\n%s", args, err, output) + { + output, err := cmd.CombinedOutput() + testassert.Require(t, err == nil, "git %v: %v\n%s", args, err, output) } } runGit("init") @@ -1149,31 +1010,21 @@ func TestReadOnlyRunCommandExecutesRewriteSuppressingGitContentHelpers(t *testin external := filepath.Join(repo, "external.sh") writeScript := func(path, content string) { t.Helper() - if err := os.WriteFile(path, []byte(content), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(path, []byte(content), 0o755)) } writeScript(textconv, fmt.Sprintf("#!/bin/sh\nprintf textconv >> %q\ncat \"$1\"\n", marker)) writeScript(external, fmt.Sprintf("#!/bin/sh\nprintf external >> %q\nexit 0\n", marker)) - if err := os.WriteFile(filepath.Join(repo, ".gitattributes"), []byte("*.secret diff=secret\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(repo, "file.secret"), []byte("one\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(repo, ".gitattributes"), []byte("*.secret diff=secret\n"), 0o644)) + testassert.NoError(t, os.WriteFile(filepath.Join(repo, "file.secret"), []byte("one\n"), 0o644)) runGit("add", ".") runGit("commit", "-m", "initial") runGit("config", "diff.secret.textconv", textconv) runGit("config", "diff.external", external) - if err := os.WriteFile(filepath.Join(repo, "file.secret"), []byte("two\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(repo, "file.secret"), []byte("two\n"), 0o644)) runGit("add", "file.secret") runGit("commit", "-m", "second") - if err := os.WriteFile(filepath.Join(repo, "file.secret"), []byte("three\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(repo, "file.secret"), []byte("three\n"), 0o644)) byID := openTools(t, t.TempDir()) for _, command := range []string{ @@ -1186,16 +1037,13 @@ func TestReadOnlyRunCommandExecutesRewriteSuppressingGitContentHelpers(t *testin t.Run(command, func(t *testing.T) { _ = os.Remove(marker) plan := prepare(t, byID["run_command"], envCallContext(t, repo, runtime.ToolConstraints{Readonly: true}), "run_command", `{"command":"`+command+`"}`) - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "command.run" { - t.Fatalf("pairs = %+v, want one command.run pair", plan.Permissions) - } - if target := plan.Permissions[0].Target; !strings.HasPrefix(target, "git --no-pager --no-optional-locks") { - t.Fatalf("target = %q, want the rewritten read-only git command", target) + testassert.Require(t, len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "command.run", "pairs = %+v, want one command.run pair", plan.Permissions) + { + target := plan.Permissions[0].Target + testassert.Require(t, strings.HasPrefix(target, "git --no-pager --no-optional-locks"), "target = %q, want the rewritten read-only git command", target) } outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want the rewritten command to succeed", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "result = %+v, want the rewritten command to succeed", outcome.Result) if _, err := os.Stat(marker); err == nil { data, _ := os.ReadFile(marker) t.Fatalf("executed the raw command instead of the rewrite: helper marker = %q", data) @@ -1211,76 +1059,43 @@ func TestPrepareShapesAndCanonicals(t *testing.T) { t.Run("read_file binds canonical targets with file.read pairs", func(t *testing.T) { ws := t.TempDir() - if err := os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("hello\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("hello\n"), 0o644)) plan := prepare(t, byID["read_file"], callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":"notes.txt"}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want one executor", plan) - } - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "file.read" { - t.Fatalf("permissions = %+v, want one file.read pair", plan.Permissions) - } - if plan.Permissions[0].Target != filepath.Join(ws, "notes.txt") { - t.Fatalf("target = %q, want the canonical file path", plan.Permissions[0].Target) - } - if plan.CanonicalWorkspace != ws || plan.CanonicalWriteDir != "" { - t.Fatalf("canonicals = %q/%q, want the workspace and an empty write dir", plan.CanonicalWorkspace, plan.CanonicalWriteDir) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "file.read", "plan = %+v, want one executor; permissions = %+v, want one file.read pair", plan, plan.Permissions) + testassert.Require(t, plan.Permissions[0].Target == filepath.Join(ws, "notes.txt") && plan.CanonicalWorkspace == ws && plan.CanonicalWriteDir == "", "target = %q, want the canonical file path; canonicals = %q/%q, want the workspace and an empty write dir", plan.Permissions[0].Target, plan.CanonicalWorkspace, plan.CanonicalWriteDir) }) t.Run("read_file of a missing leaf declares the parent directory too", func(t *testing.T) { ws := t.TempDir() plan := prepare(t, byID["read_file"], callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":"ghost.txt"}`) - if len(plan.Permissions) != 2 { - t.Fatalf("permissions = %+v, want the file and parent-directory file.read pairs", plan.Permissions) - } + testassert.Require(t, len(plan.Permissions) == 2, "permissions = %+v, want the file and parent-directory file.read pairs", plan.Permissions) for _, pair := range plan.Permissions { - if pair.Permission != "file.read" || pair.Target == "" { - t.Fatalf("pair = %+v, want a nonempty canonical file.read pair", pair) - } + testassert.Require(t, pair.Permission == "file.read" && pair.Target != "", "pair = %+v, want a nonempty canonical file.read pair", pair) } }) t.Run("canonical workspace resolves symlinks and stays lexically fixed", func(t *testing.T) { real := t.TempDir() link := filepath.Join(t.TempDir(), "link") - if err := os.Symlink(real, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(real, link)) plan := prepare(t, byID["read_file"], callToolContext(link, runtime.ToolConstraints{}), "read_file", `{"path":"a.txt"}`) - if plan.CanonicalWorkspace != real { - t.Fatalf("CanonicalWorkspace = %q, want the symlink-resolved %q", plan.CanonicalWorkspace, real) - } + testassert.Equal(t, plan.CanonicalWorkspace, real) }) t.Run("write_file declares file.write and the canonical write-dir boundary", func(t *testing.T) { ws := t.TempDir() - if err := os.MkdirAll(filepath.Join(ws, "wd"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(ws, "wd"), 0o755)) constraints := runtime.ToolConstraints{WriteDir: "wd"} plan := prepare(t, byID["write_file"], callToolContext(ws, constraints), "write_file", `{"path":"wd/out.txt","content":"x"}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want one executor", plan) - } - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "file.write" { - t.Fatalf("permissions = %+v, want one file.write pair", plan.Permissions) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "file.write", "plan = %+v, want one executor; permissions = %+v, want one file.write pair", plan, plan.Permissions) wantBoundary := filepath.Join(ws, "wd") - if plan.CanonicalWriteDir != wantBoundary || plan.CanonicalWorkspace != ws { - t.Fatalf("canonicals = %q/%q, want %q/%q", plan.CanonicalWorkspace, plan.CanonicalWriteDir, ws, wantBoundary) - } + testassert.Require(t, plan.CanonicalWriteDir == wantBoundary && plan.CanonicalWorkspace == ws, "canonicals = %q/%q, want %q/%q", plan.CanonicalWorkspace, plan.CanonicalWriteDir, ws, wantBoundary) }) t.Run("apply_patch declares every source and destination in declaration order", func(t *testing.T) { ws := t.TempDir() for name, content := range map[string]string{"b.txt": "old\n", "c.txt": "gone\n", "d.txt": "d\n"} { - if err := os.WriteFile(filepath.Join(ws, name), []byte(content), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, name), []byte(content), 0o644)) } patch := "*** Begin Patch\n*** Add File: a.txt\n+new\n*** Update File: b.txt\n@@\n-old\n+new\n*** Delete File: c.txt\n*** Update File: d.txt\n*** Move to: e.txt\n*** End Patch" input, err := json.Marshal(map[string]string{"input": patch}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) plan := prepare(t, byID["apply_patch"], callToolContext(ws, runtime.ToolConstraints{}), "apply_patch", string(input)) want := []string{"a.txt", "b.txt", "c.txt", "d.txt", "e.txt"} if len(plan.Permissions) != len(want) { @@ -1288,77 +1103,51 @@ func TestPrepareShapesAndCanonicals(t *testing.T) { } for i, name := range want { pair := plan.Permissions[i] - if pair.Permission != "file.write" || pair.Target != filepath.Join(ws, name) { - t.Fatalf("pair %d = %+v, want file.write on %s", i, pair, name) - } + testassert.Require(t, pair.Permission == "file.write" && pair.Target == filepath.Join(ws, name), "pair %d = %+v, want file.write on %s", i, pair, name) } }) t.Run("undecodable arguments in Prepare map to the immediate validation error", func(t *testing.T) { ws := t.TempDir() plan := prepare(t, byID["read_file"], callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":`) - if plan.Execute != nil || plan.Immediate == nil { - t.Fatalf("plan = %+v, want one immediate outcome", plan) - } + testassert.Require(t, plan.Execute == nil && plan.Immediate != nil, "plan = %+v, want one immediate outcome", plan) outcome := *plan.Immediate - if outcome.Result.Status != model.ResultError || outcome.Result.CallID != "call-1" || outcome.Result.Content == "" { - t.Fatalf("immediate = %+v, want the bounded validation error for the original call", outcome) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.CallID == "call-1" && outcome.Result.Content != "", "immediate = %+v, want the bounded validation error for the original call", outcome) }) t.Run("prepare normalizes once: it never re-runs argument normalization", func(t *testing.T) { ws := t.TempDir() - if err := os.WriteFile(filepath.Join(ws, "f.txt"), []byte("one\ntwo\nthree"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "f.txt"), []byte("one\ntwo\nthree"), 0o644)) tool := byID["read_file"] // The committed normalized arguments are authoritative: a strict // "1e0" spelling canonicalizes once at normalization, and // preparation consumes the byte-identical committed value and reads // exactly line 1 with the canonical integer window. committed, err := normalize(t, tool, callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":"f.txt","offset":1e0,"limit":1.0}`) - if err != nil { - t.Fatalf("Normalize: %v", err) - } - if string(committed) != `{"limit":1,"offset":1,"path":"f.txt"}` { - t.Fatalf("committed normalized arguments = %s, want the canonical integers once", committed) - } + testassert.NoError(t, err) + testassert.Require(t, string(committed) == `{"limit":1,"offset":1,"path":"f.txt"}`, "committed normalized arguments = %s, want the canonical integers once", committed) plan := prepare(t, tool, callToolContext(ws, runtime.ToolConstraints{}), "read_file", string(committed)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || !strings.HasPrefix(outcome.Result.Content, "1\tone\n") || !strings.Contains(outcome.Result.Content, "(Showing lines 1-1 of 3. Use offset=2 to continue.)") { - t.Fatalf("result = %+v, want the canonical 1/1 window through preparation", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.HasPrefix(outcome.Result.Content, "1\tone\n") && strings.Contains(outcome.Result.Content, "(Showing lines 1-1 of 3. Use offset=2 to continue.)"), "result = %+v, want the canonical 1/1 window through preparation", outcome.Result) // Preparation performs no validation or defaulting of its own: an // un-normalized fraction handed directly to Prepare is not a second // normalization error — preparation binds targets and parses the // canonical lexeme only at its point of use. plan = prepare(t, tool, callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":"f.txt","offset":2.5}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want one executor: Prepare runs no second normalization", plan) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil, "plan = %+v, want one executor: Prepare runs no second normalization", plan) }) t.Run("failed canonical preparation maps to the immediate denial", func(t *testing.T) { ws := t.TempDir() loop := filepath.Join(ws, "loop") - if err := os.Symlink("loop", loop); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink("loop", loop)) plan := prepare(t, byID["write_file"], callToolContext(ws, runtime.ToolConstraints{WriteDir: "loop"}), "write_file", `{"path":"out.txt","content":"x"}`) - if plan.Execute != nil || plan.Immediate == nil { - t.Fatalf("plan = %+v, want one immediate outcome", plan) - } + testassert.Require(t, plan.Execute == nil && plan.Immediate != nil, "plan = %+v, want one immediate outcome", plan) outcome := *plan.Immediate - if outcome.Result.Status != model.ResultDenied || outcome.Result.Content != "Permission denied." { - t.Fatalf("immediate = %+v, want the fixed denial", outcome) - } + testassert.Require(t, outcome.Result.Status == model.ResultDenied && outcome.Result.Content == "Permission denied.", "immediate = %+v, want the fixed denial", outcome) }) t.Run("a write target outside the write dir is denied at preparation", func(t *testing.T) { ws := t.TempDir() - if err := os.MkdirAll(filepath.Join(ws, "wd"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(ws, "wd"), 0o755)) plan := prepare(t, byID["edit_file"], callToolContext(ws, runtime.ToolConstraints{WriteDir: "wd"}), "edit_file", `{"path":"outside.txt","old_string":"a","new_string":"b"}`) - if plan.Execute != nil || plan.Immediate == nil || plan.Immediate.Result.Status != model.ResultDenied { - t.Fatalf("plan = %+v, want the preparation denial", plan) - } + testassert.Require(t, plan.Execute == nil && plan.Immediate != nil && plan.Immediate.Result.Status == model.ResultDenied, "plan = %+v, want the preparation denial", plan) }) } @@ -1368,31 +1157,17 @@ func TestExecuteFileEffects(t *testing.T) { t.Run("read_file returns bounded numbered content and no metadata", func(t *testing.T) { ws := t.TempDir() - if err := os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("one\ntwo\nthree\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("one\ntwo\nthree\n"), 0o644)) plan := prepare(t, byID["read_file"], callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":"notes.txt","offset":2,"limit":2}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.CallID != "call-1" { - t.Fatalf("result = %+v, want success for call-1", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, "2\ttwo") || !strings.Contains(outcome.Result.Content, "3\tthree") { - t.Fatalf("content = %q, want the numbered requested window", outcome.Result.Content) - } - if outcome.Metadata != nil { - t.Fatalf("metadata = %s, want none for read_file", outcome.Metadata) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.CallID == "call-1" && strings.Contains(outcome.Result.Content, "2\ttwo") && strings.Contains(outcome.Result.Content, "3\tthree") && outcome.Metadata == nil, "result = %+v, want success for call-1; content = %q, want the numbered requested window; metadata = %s, want none for read_file", outcome.Result, outcome.Result.Content, outcome.Metadata) }) t.Run("read_file of a missing file returns the suggestion outcome", func(t *testing.T) { ws := t.TempDir() - if err := os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("x\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("x\n"), 0o644)) plan := prepare(t, byID["read_file"], callToolContext(ws, runtime.ToolConstraints{}), "read_file", `{"path":"notes.tx"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || !strings.Contains(outcome.Result.Content, "not found") { - t.Fatalf("result = %+v, want the bounded not-found suggestions outcome", outcome) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "not found"), "result = %+v, want the bounded not-found suggestions outcome", outcome) }) t.Run("write_file creates parents, writes the file and captures the preimage group", func(t *testing.T) { dataDir := t.TempDir() @@ -1400,16 +1175,10 @@ func TestExecuteFileEffects(t *testing.T) { ws := t.TempDir() plan := prepare(t, byID["write_file"], callToolContext(ws, runtime.ToolConstraints{}), "write_file", `{"path":"sub/dir/new.txt","content":"body"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || !strings.Contains(outcome.Result.Content, "Wrote") { - t.Fatalf("result = %+v, want the write success", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "Wrote"), "result = %+v, want the write success", outcome.Result) data, err := os.ReadFile(filepath.Join(ws, "sub", "dir", "new.txt")) - if err != nil || string(data) != "body" { - t.Fatalf("written file = (%q, %v), want the new content", data, err) - } - if outcome.Metadata != nil { - t.Fatalf("metadata = %s, want none for write_file", outcome.Metadata) - } + testassert.Require(t, err == nil && string(data) == "body", "written file = (%q, %v), want the new content", data, err) + testassert.Require(t, outcome.Metadata == nil, "metadata = %s, want none for write_file", outcome.Metadata) // The preimage group lives at DataDir/code///snapshots/1/. turnDir := filepath.Join(groupDir(dataDir, testSessionID, testEntryID), "snapshots", "1") entries, err := os.ReadDir(turnDir) @@ -1417,37 +1186,23 @@ func TestExecuteFileEffects(t *testing.T) { t.Fatalf("group turn dir = (%d entries, %v), want one snapshot entry", len(entries), err) } meta, err := os.ReadFile(filepath.Join(turnDir, entries[0].Name(), "meta.json")) - if err != nil { - t.Fatalf("snapshot meta: %v", err) - } + testassert.NoError(t, err) var wire struct { OriginalPath string `json:"original_path"` Existed bool `json:"existed"` } - if err := json.Unmarshal(meta, &wire); err != nil { - t.Fatalf("decode meta: %v", err) - } - if wire.Existed || !strings.HasSuffix(wire.OriginalPath, "new.txt") { - t.Fatalf("meta = %s, want the new-file preimage of the declared target", meta) - } + testassert.NoError(t, json.Unmarshal(meta, &wire)) + testassert.Require(t, !wire.Existed && strings.HasSuffix(wire.OriginalPath, "new.txt"), "meta = %s, want the new-file preimage of the declared target", meta) }) t.Run("edit_file mutates the file and emits the edit_preview metadata shape", func(t *testing.T) { ws := t.TempDir() - if err := os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("alpha\nbeta\ngamma\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "notes.txt"), []byte("alpha\nbeta\ngamma\n"), 0o644)) plan := prepare(t, byID["edit_file"], callToolContext(ws, runtime.ToolConstraints{}), "edit_file", `{"path":"notes.txt","old_string":"beta","new_string":"BETA"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || !strings.Contains(outcome.Result.Content, "lines 2") { - t.Fatalf("result = %+v, want the edit summary", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "lines 2"), "result = %+v, want the edit summary", outcome.Result) data, err := os.ReadFile(filepath.Join(ws, "notes.txt")) - if err != nil || string(data) != "alpha\nBETA\ngamma\n" { - t.Fatalf("edited file = (%q, %v), want the replacement", data, err) - } - if outcome.Metadata == nil { - t.Fatal("edit_file emitted no metadata") - } + testassert.Require(t, err == nil && string(data) == "alpha\nBETA\ngamma\n", "edited file = (%q, %v), want the replacement", data, err) + testassert.Require(t, outcome.Metadata != nil, "edit_file emitted no metadata") var metadata struct { EditPreview *struct { Hunks []struct { @@ -1458,78 +1213,72 @@ func TestExecuteFileEffects(t *testing.T) { } `json:"hunks"` } `json:"edit_preview"` } - if err := json.Unmarshal(outcome.Metadata, &metadata); err != nil { - t.Fatalf("metadata %s is not the documented shape: %v", outcome.Metadata, err) - } - if metadata.EditPreview == nil || len(metadata.EditPreview.Hunks) != 1 || len(metadata.EditPreview.Hunks[0].Rows) != 2 { - t.Fatalf("metadata = %s, want one hunk with the remove/add diff rows", outcome.Metadata) + { + err := json.Unmarshal(outcome.Metadata, &metadata) + testassert.Require(t, err == nil, "metadata %s is not the documented shape: %v", outcome.Metadata, err) } + testassert.Require(t, metadata.EditPreview != nil && len(metadata.EditPreview.Hunks) == 1 && len(metadata.EditPreview.Hunks[0].Rows) == 2, "metadata = %s, want one hunk with the remove/add diff rows", outcome.Metadata) }) t.Run("apply_patch applies add update move delete and emits the per-file preview shape", func(t *testing.T) { ws := t.TempDir() for name, content := range map[string]string{"b.txt": "old\n", "c.txt": "gone\n", "d.txt": "d\n"} { - if err := os.WriteFile(filepath.Join(ws, name), []byte(content), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, name), []byte(content), 0o644)) } patch := "*** Begin Patch\n*** Add File: a.txt\n+new\n*** Update File: b.txt\n@@\n-old\n+new\n*** Delete File: c.txt\n*** Update File: d.txt\n*** Move to: e.txt\n*** End Patch" input, err := json.Marshal(map[string]string{"input": patch}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) plan := prepare(t, byID["apply_patch"], callToolContext(ws, runtime.ToolConstraints{}), "apply_patch", string(input)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || !strings.Contains(outcome.Result.Content, "Success. Updated the following files:") { - t.Fatalf("result = %+v, want the patch success summary", outcome.Result) - } - if _, err := os.Stat(filepath.Join(ws, "a.txt")); err != nil { - t.Fatalf("added file missing: %v", err) + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "Success. Updated the following files:"), "result = %+v, want the patch success summary", outcome.Result) + { + _, err := os.Stat(filepath.Join(ws, "a.txt")) + testassert.NoError(t, err) } data, err := os.ReadFile(filepath.Join(ws, "b.txt")) - if err != nil || string(data) != "new\n" { - t.Fatalf("updated file = (%q, %v), want the replacement", data, err) + testassert.Require(t, err == nil && string(data) == "new\n", "updated file = (%q, %v), want the replacement", data, err) + { + _, err := os.Stat(filepath.Join(ws, "c.txt")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "deleted file stat = %v, want gone", err) } - if _, err := os.Stat(filepath.Join(ws, "c.txt")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("deleted file stat = %v, want gone", err) + { + _, err := os.Stat(filepath.Join(ws, "d.txt")) + testassert.Require(t, errors.Is(err, os.ErrNotExist), "move source stat = %v, want gone", err) } - if _, err := os.Stat(filepath.Join(ws, "d.txt")); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("move source stat = %v, want gone", err) - } - if data, err := os.ReadFile(filepath.Join(ws, "e.txt")); err != nil || string(data) != "d\n" { - t.Fatalf("move destination = (%q, %v), want the renamed content", data, err) - } - if outcome.Metadata == nil { - t.Fatal("apply_patch emitted no metadata") + { + data, err := os.ReadFile(filepath.Join(ws, "e.txt")) + testassert.Require(t, err == nil && string(data) == "d\n", "move destination = (%q, %v), want the renamed content", data, err) } + testassert.Require(t, outcome.Metadata != nil, "apply_patch emitted no metadata") var metadata struct { Files []struct { Path string `json:"path"` Op string `json:"op"` } `json:"edit_preview_files"` } - if err := json.Unmarshal(outcome.Metadata, &metadata); err != nil { - t.Fatalf("metadata %s is not the documented shape: %v", outcome.Metadata, err) + { + err := json.Unmarshal(outcome.Metadata, &metadata) + testassert.Require(t, err == nil, "metadata %s is not the documented shape: %v", outcome.Metadata, err) } var ops []string for _, f := range metadata.Files { ops = append(ops, f.Path+"="+f.Op) } // The engine emits the move destination (M) before its source (D). - if strings.Join(ops, ",") != "a.txt=A,b.txt=M,c.txt=D,e.txt=M,d.txt=D" { - t.Fatalf("previews = %v, want the full A/M/D list with content retained", ops) - } + testassert.Require(t, strings.Join(ops, ",") == "a.txt=A,b.txt=M,c.txt=D,e.txt=M,d.txt=D", "previews = %v, want the full A/M/D list with content retained", ops) }) t.Run("successive calls of one operation share the disk group through fresh handles", func(t *testing.T) { dataDir := t.TempDir() byID := openTools(t, dataDir) ws := t.TempDir() first := prepare(t, byID["write_file"], callToolContext(ws, runtime.ToolConstraints{}), "write_file", `{"path":"one.txt","content":"1"}`) - if outcome := first.Execute(context.Background()); outcome.Result.Status != model.ResultSuccess { - t.Fatalf("first write = %+v", outcome.Result) + { + outcome := first.Execute(context.Background()) + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "first write = %+v", outcome.Result) } second := prepare(t, byID["write_file"], callToolContext(ws, runtime.ToolConstraints{}), "write_file", `{"path":"two.txt","content":"2"}`) - if outcome := second.Execute(context.Background()); outcome.Result.Status != model.ResultSuccess { - t.Fatalf("second write = %+v", outcome.Result) + { + outcome := second.Execute(context.Background()) + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "second write = %+v", outcome.Result) } turnDir := filepath.Join(groupDir(dataDir, testSessionID, testEntryID), "snapshots", "1") entries, err := os.ReadDir(turnDir) @@ -1544,8 +1293,9 @@ func TestExecuteFileEffects(t *testing.T) { tc := callToolContext(ws, runtime.ToolConstraints{}) tc.AdmittedEntry = harness.EntryRef{SessionID: testSessionID, EntryID: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} plan := prepare(t, byID["write_file"], tc, "write_file", `{"path":"other.txt","content":"x"}`) - if outcome := plan.Execute(context.Background()); outcome.Result.Status != model.ResultSuccess { - t.Fatalf("write = %+v", outcome.Result) + { + outcome := plan.Execute(context.Background()) + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "write = %+v", outcome.Result) } turnDir := filepath.Join(groupDir(dataDir, testSessionID, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), "snapshots", "1") if entries, err := os.ReadDir(turnDir); err != nil || len(entries) != 1 { @@ -1554,31 +1304,21 @@ func TestExecuteFileEffects(t *testing.T) { }) t.Run("a readonly agent with a write dir writes inside the boundary", func(t *testing.T) { ws := t.TempDir() - if err := os.MkdirAll(filepath.Join(ws, "wd"), 0o755); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.MkdirAll(filepath.Join(ws, "wd"), 0o755)) tc := callToolContext(ws, runtime.ToolConstraints{Readonly: true, WriteDir: "wd"}) plan := prepare(t, byID["write_file"], tc, "write_file", `{"path":"wd/allowed.txt","content":"x"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("write = %+v, want success inside the write dir", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "write = %+v, want success inside the write dir", outcome.Result) }) t.Run("executed patch failures surface as error outcomes", func(t *testing.T) { ws := t.TempDir() - if err := os.WriteFile(filepath.Join(ws, "b.txt"), []byte("different\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "b.txt"), []byte("different\n"), 0o644)) patch := "*** Begin Patch\n*** Update File: b.txt\n@@\n-old\n+new\n*** End Patch" input, err := json.Marshal(map[string]string{"input": patch}) - if err != nil { - t.Fatal(err) - } + testassert.NoError(t, err) plan := prepare(t, byID["apply_patch"], callToolContext(ws, runtime.ToolConstraints{}), "apply_patch", string(input)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content == "" { - t.Fatalf("result = %+v, want the failed-hunk error outcome", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content != "", "result = %+v, want the failed-hunk error outcome", outcome.Result) }) } @@ -1592,25 +1332,23 @@ func TestFailedCallRetainsEarlierSnapshot(t *testing.T) { byID := openTools(t, dataDir) ws := t.TempDir() tc := callToolContext(ws, runtime.ToolConstraints{}) - if err := os.WriteFile(filepath.Join(ws, "a.txt"), []byte("alpha\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(filepath.Join(ws, "b.txt"), []byte("beta\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "a.txt"), []byte("alpha\n"), 0o644)) + testassert.NoError(t, os.WriteFile(filepath.Join(ws, "b.txt"), []byte("beta\n"), 0o644)) // First call: a successful write retains its preimage in the group. first := prepare(t, byID["write_file"], tc, "write_file", `{"path":"a.txt","content":"ALPHA\n"}`) - if outcome := first.Execute(context.Background()); outcome.Result.Status != model.ResultSuccess { - t.Fatalf("first write = %+v, want success", outcome.Result) + { + outcome := first.Execute(context.Background()) + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "first write = %+v, want success", outcome.Result) } // Second call: a failing edit in the same group — the shared body // captures b.txt's preimage, fails before any mutation, and discards // its own claim through the same fresh-handle group. second := prepare(t, byID["edit_file"], tc, "edit_file", `{"path":"b.txt","old_string":"missing","new_string":"x"}`) - if outcome := second.Execute(context.Background()); outcome.Result.Status != model.ResultError { - t.Fatalf("failing edit = %+v, want the error outcome", outcome.Result) + { + outcome := second.Execute(context.Background()) + testassert.Require(t, outcome.Result.Status == model.ResultError, "failing edit = %+v, want the error outcome", outcome.Result) } // The first call's retained preimage survives on disk; the failing @@ -1622,24 +1360,17 @@ func TestFailedCallRetainsEarlierSnapshot(t *testing.T) { t.Fatalf("group turn dir = (%d entries, %v), want only the first call's retained preimage", len(entries), err) } metaData, err := os.ReadFile(filepath.Join(turnDir, entries[0].Name(), "meta.json")) - if err != nil { - t.Fatalf("snapshot meta: %v", err) - } + testassert.NoError(t, err) var meta struct { OriginalPath string `json:"original_path"` } - if err := json.Unmarshal(metaData, &meta); err != nil { - t.Fatalf("decode meta: %v", err) - } - if !strings.HasSuffix(meta.OriginalPath, "a.txt") { - t.Fatalf("retained entry = %q, want the first call's a.txt preimage", meta.OriginalPath) - } + testassert.NoError(t, json.Unmarshal(metaData, &meta)) + testassert.Require(t, strings.HasSuffix(meta.OriginalPath, "a.txt"), "retained entry = %q, want the first call's a.txt preimage", meta.OriginalPath) preimage, err := os.ReadFile(filepath.Join(turnDir, entries[0].Name(), "original")) - if err != nil || string(preimage) != "alpha\n" { - t.Fatalf("retained preimage = (%q, %v), want the first call's content", preimage, err) - } - if data, err := os.ReadFile(filepath.Join(ws, "a.txt")); err != nil || string(data) != "ALPHA\n" { - t.Fatalf("written file = (%q, %v), want the first call's mutation intact", data, err) + testassert.Require(t, err == nil && string(preimage) == "alpha\n", "retained preimage = (%q, %v), want the first call's content", preimage, err) + { + data, err := os.ReadFile(filepath.Join(ws, "a.txt")) + testassert.Require(t, err == nil && string(data) == "ALPHA\n", "written file = (%q, %v), want the first call's mutation intact", data, err) } } @@ -1653,33 +1384,19 @@ func TestExecutedPlanRejectsPostAuthorizationBindingChange(t *testing.T) { byID := openTools(t, dataDir) ws := t.TempDir() real := filepath.Join(ws, "real.txt") - if err := os.WriteFile(real, []byte("before\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(real, []byte("before\n"), 0o644)) link := filepath.Join(ws, "alias.txt") - if err := os.Symlink(real, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(real, link)) plan := prepare(t, byID["write_file"], callToolContext(ws, runtime.ToolConstraints{}), "write_file", `{"path":"alias.txt","content":"after"}`) // Repoint the alias at a different leaf after authorization. other := filepath.Join(ws, "other.txt") - if err := os.WriteFile(other, []byte("do not touch\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Remove(link); err != nil { - t.Fatal(err) - } - if err := os.Symlink(other, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(other, []byte("do not touch\n"), 0o644)) + testassert.NoError(t, os.Remove(link)) + testassert.NoError(t, os.Symlink(other, link)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || !strings.Contains(outcome.Result.Content, "resolve path") { - t.Fatalf("result = %+v, want the binding-change error", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, "resolve path"), "result = %+v, want the binding-change error", outcome.Result) data, err := os.ReadFile(other) - if err != nil || string(data) != "do not touch\n" { - t.Fatalf("repointed leaf = (%q, %v), want it unmutated", data, err) - } + testassert.Require(t, err == nil && string(data) == "do not touch\n", "repointed leaf = (%q, %v), want it unmutated", data, err) if _, err := os.Stat(filepath.Join(dataDir, "code")); !errors.Is(err, os.ErrNotExist) { t.Fatalf("failed pre-mutation call left %s: %v", filepath.Join(dataDir, "code"), err) } @@ -1692,28 +1409,16 @@ func TestExecutedPlanRejectsPostAuthorizationBindingChange(t *testing.T) { func TestExecutedReadFailsUnderWorkspaceRootChange(t *testing.T) { byID := openTools(t, t.TempDir()) real := t.TempDir() - if err := os.WriteFile(filepath.Join(real, "notes.txt"), []byte("real\n"), 0o644); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(real, "notes.txt"), []byte("real\n"), 0o644)) link := filepath.Join(t.TempDir(), "ws-link") - if err := os.Symlink(real, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Symlink(real, link)) plan := prepare(t, byID["read_file"], callToolContext(link, runtime.ToolConstraints{}), "read_file", `{"path":"notes.txt"}`) - if err := os.Remove(link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Remove(link)) other := t.TempDir() - if err := os.WriteFile(filepath.Join(other, "notes.txt"), []byte("substituted\n"), 0o644); err != nil { - t.Fatal(err) - } - if err := os.Symlink(other, link); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.WriteFile(filepath.Join(other, "notes.txt"), []byte("substituted\n"), 0o644)) + testassert.NoError(t, os.Symlink(other, link)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || strings.Contains(outcome.Result.Content, "substituted") { - t.Fatalf("result = %+v, want the changed-root failure without substituted content", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && !strings.Contains(outcome.Result.Content, "substituted"), "result = %+v, want the changed-root failure without substituted content", outcome.Result) } // TestBackgroundStartFailuresRenderOneWrapperAndAbortReservation pins the one @@ -1729,22 +1434,10 @@ func TestBackgroundStartFailuresRenderOneWrapperAndAbortReservation(t *testing.T tc.SubprocessEnv = stubManagedEnv(t).SubprocessEnv tc.Background = &scriptBackground{startErr: errors.New("background group is closed or stopping")} plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"true","background":true}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want an executor plan", plan) - } - if len(plan.Permissions) == 0 || plan.Permissions[0].Permission != "command.run" { - t.Fatalf("pairs = %+v, want command.run pairs on the background call", plan.Permissions) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) != 0 && plan.Permissions[0].Permission == "command.run", "plan = %+v, want an executor plan; pairs = %+v, want command.run pairs on the background call", plan, plan.Permissions) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "run_command: background start: background group is closed or stopping" { - t.Fatalf("result = %+v, want the one wrapper over the handoff rejection", outcome.Result) - } - if len(fake.aborted) != 1 || fake.aborted[0] != "b0b0b0b0" { - t.Fatalf("aborted = %v, want the reservation aborted once", fake.aborted) - } - if len(fake.started) != 0 { - t.Fatalf("started = %d, want the job never started", len(fake.started)) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "run_command: background start: background group is closed or stopping", "result = %+v, want the one wrapper over the handoff rejection", outcome.Result) + testassert.Require(t, len(fake.aborted) == 1 && fake.aborted[0] == "b0b0b0b0" && len(fake.started) == 0, "aborted = %v, want the reservation aborted once; got %v, want %v", fake.aborted, len(fake.started), 0) }) t.Run("spawn failure renders through the wrapper and aborts the reservation", func(t *testing.T) { @@ -1755,15 +1448,8 @@ func TestBackgroundStartFailuresRenderOneWrapperAndAbortReservation(t *testing.T tc.Background = &scriptBackground{} plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"true","background":true}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "run_command: background start: spawn boom" { - t.Fatalf("result = %+v, want the one wrapper over the spawn failure", outcome.Result) - } - if len(fake.aborted) != 1 || fake.aborted[0] != "b0b0b0b0" { - t.Fatalf("aborted = %v, want the reservation aborted once", fake.aborted) - } - if len(fake.started) != 1 { - t.Fatalf("started = %d, want the failed Start attempt recorded", len(fake.started)) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "run_command: background start: spawn boom", "result = %+v, want the one wrapper over the spawn failure", outcome.Result) + testassert.Require(t, len(fake.aborted) == 1 && fake.aborted[0] == "b0b0b0b0" && len(fake.started) == 1, "aborted = %v, want the reservation aborted once; got %v, want %v", fake.aborted, len(fake.started), 1) }) t.Run("reserve failure renders through the wrapper without an abort", func(t *testing.T) { @@ -1774,15 +1460,8 @@ func TestBackgroundStartFailuresRenderOneWrapperAndAbortReservation(t *testing.T tc.Background = &scriptBackground{} plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"true","background":true}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != "run_command: background start: process: manager is closed" { - t.Fatalf("result = %+v, want the one wrapper over the reserve failure", outcome.Result) - } - if len(fake.aborted) != 0 { - t.Fatalf("aborted = %v, want no abort when the reservation never existed", fake.aborted) - } - if fake.reserves != 1 { - t.Fatalf("reserves = %d, want exactly one attempt", fake.reserves) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == "run_command: background start: process: manager is closed", "result = %+v, want the one wrapper over the reserve failure", outcome.Result) + testassert.Require(t, len(fake.aborted) == 0 && fake.reserves == 1, "aborted = %v, want no abort when the reservation never existed; got %v, want %v", fake.aborted, fake.reserves, 1) }) } @@ -1799,38 +1478,21 @@ func TestReadOnlyBackgroundStartRewritesAndHonorsTimeout(t *testing.T) { tc.Background = &scriptBackground{} wantCommand, err := tool.ReadOnlyCommand("git status") - if err != nil { - t.Fatalf("ReadOnlyCommand: %v", err) - } + testassert.NoError(t, err) plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"git status","background":true,"timeout":5}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want an executor plan", plan) - } - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "command.run" || plan.Permissions[0].Target != wantCommand { - t.Fatalf("pairs = %+v, want the one command.run pair on the rewritten command", plan.Permissions) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "command.run" && plan.Permissions[0].Target == wantCommand, "plan = %+v, want an executor plan; pairs = %+v, want the one command.run pair on the rewritten command", plan, plan.Permissions) outcome := plan.Execute(context.Background()) wantContent := fmt.Sprintf("Command running in the background with ID: `%s`. You will be notified when it finishes. If your next steps do not depend on its output, continue with them; otherwise use sleep to wait and process to read the output.\nRunning in the background: `%s`.", "b0b0b0b0", strings.Join(fake.live, ", ")) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != wantContent { - t.Fatalf("result = %+v, want the retained immediate template %q", outcome.Result, wantContent) - } - if len(fake.started) != 1 { - t.Fatalf("started = %d, want exactly one background start", len(fake.started)) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == wantContent, "result = %+v, want the retained immediate template %q", outcome.Result, wantContent) + testassert.Equal(t, len(fake.started), 1) req := fake.started[0] - if req.Command != wantCommand || req.TimeoutSec != 5 || req.Workspace != ws || req.SessionID != testSessionID { - t.Fatalf("start request = %+v, want the rewritten command, timeout 5, the workspace, and the session", req) - } + testassert.Require(t, req.Command == wantCommand && req.TimeoutSec == 5 && req.Workspace == ws && req.SessionID == testSessionID, "start request = %+v, want the rewritten command, timeout 5, the workspace, and the session", req) // Absent or sub-one background timeout becomes 0. plan = prepare(t, byID["run_command"], tc, "run_command", `{"command":"pwd","background":true}`) outcome = plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want success", outcome.Result) - } - if len(fake.started) != 2 || fake.started[1].TimeoutSec != 0 || fake.started[1].Command != "pwd" { - t.Fatalf("started = %+v, want the second start with timeout 0 and the plain pwd command", fake.started) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "result = %+v, want success", outcome.Result) + testassert.Require(t, len(fake.started) == 2 && fake.started[1].TimeoutSec == 0 && fake.started[1].Command == "pwd", "started = %+v, want the second start with timeout 0 and the plain pwd command", fake.started) } // TestRunCommandConcurrentManagedWritesSpawnChildren drives real foreground @@ -1846,15 +1508,11 @@ func TestRunCommandConcurrentManagedWritesSpawnChildren(t *testing.T) { ) t.Setenv(externalKey, "sibling-value") t.Setenv(managedKey, "") // cleanup reservation only - if err := os.Unsetenv(managedKey); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Unsetenv(managedKey)) managed := stubManagedEnv(t) byID, values := openToolsComposed(t, t.TempDir(), jobs.Plugin(), runtime.Plugin{}) jobsInst, ok := values["jobs"].(jobs.Jobs) - if !ok { - t.Fatalf("values[\"jobs\"] supplies %T, not jobs.Jobs", values["jobs"]) - } + testassert.Require(t, ok, "values[\"jobs\"] supplies %T, not jobs.Jobs", values["jobs"]) ws := t.TempDir() tc := callToolContext(ws, runtime.ToolConstraints{}) tc.SubprocessEnv = managed.SubprocessEnv @@ -1862,9 +1520,7 @@ func TestRunCommandConcurrentManagedWritesSpawnChildren(t *testing.T) { // One synchronous managed write proves the writer path works before any // child spawns, so a failed or unscheduled writer cannot pass vacuously. - if err := managed.TrySet(managedKey, "managed-secret"); err != nil { - t.Fatalf("initial managed TrySet: %v", err) - } + testassert.NoError(t, managed.TrySet(managedKey, "managed-secret")) var wg sync.WaitGroup stop := make(chan struct{}) var stopOnce sync.Once @@ -1899,37 +1555,28 @@ func TestRunCommandConcurrentManagedWritesSpawnChildren(t *testing.T) { checkOutput := func(kind, output string) { t.Helper() output = strings.TrimSpace(output) - if !strings.HasPrefix(output, "sibling-value|") { - t.Fatalf("%s child lost the external sibling: %q", kind, output) - } - if rest := strings.TrimPrefix(output, "sibling-value|"); rest != "" { - t.Fatalf("%s child saw a managed value %q, want none", kind, rest) + testassert.Require(t, strings.HasPrefix(output, "sibling-value|"), "%s child lost the external sibling: %q", kind, output) + { + rest := strings.TrimPrefix(output, "sibling-value|") + testassert.Require(t, rest == "", "%s child saw a managed value %q, want none", kind, rest) } } command := `printf '%s|%s' "$LIGHTCODE_CONCURRENT_EXTERNAL" "$LIGHTCODE_CONCURRENT_MANAGED"` fgArgs, err := json.Marshal(map[string]any{"command": command}) - if err != nil { - t.Fatalf("marshal foreground args: %v", err) - } + testassert.NoError(t, err) bgArgs, err := json.Marshal(map[string]any{"command": command + "; sleep 30", "background": true}) - if err != nil { - t.Fatalf("marshal background args: %v", err) - } + testassert.NoError(t, err) for i := 0; i < 20; i++ { plan := prepare(t, byID["run_command"], tc, "run_command", string(fgArgs)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("foreground child %d = %+v, want success", i, outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "foreground child %d = %+v, want success", i, outcome.Result) checkOutput("foreground", outcome.Result.Content) } for i := 0; i < 5; i++ { plan := prepare(t, byID["run_command"], tc, "run_command", string(bgArgs)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("background child %d = %+v, want the immediate success", i, outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "background child %d = %+v, want the immediate success", i, outcome.Result) jobID := assertBackgroundImmediate(t, outcome.Result.Content) deadline := time.Now().Add(15 * time.Second) output := "" @@ -1941,12 +1588,11 @@ func TestRunCommandConcurrentManagedWritesSpawnChildren(t *testing.T) { } time.Sleep(20 * time.Millisecond) } - if output == "" { - t.Fatalf("the background child %d never produced its output", i) - } + testassert.Require(t, output != "", "the background child %d never produced its output", i) checkOutput("background", output) - if rerr := jobsInst.Kill(testSessionID, jobID); rerr != nil { - t.Fatalf("kill background child %d: %v", i, rerr) + { + rerr := jobsInst.Kill(testSessionID, jobID) + testassert.Require(t, rerr == nil, "kill background child %d: %v", i, rerr) } } stopWriter() @@ -1966,15 +1612,11 @@ func TestRunCommandConcurrentManagedWritesSpawnChildren(t *testing.T) { func TestBackgroundJobEnvironmentScrubsManagedKey(t *testing.T) { t.Setenv("LIGHTCODE_BG_UNLISTED", "early-value") t.Setenv("LIGHTCODE_BG_MANAGED", "managed-secret") // cleanup reservation only - if err := os.Unsetenv("LIGHTCODE_BG_MANAGED"); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Unsetenv("LIGHTCODE_BG_MANAGED")) dataDir := t.TempDir() byID, values := openToolsComposed(t, dataDir, jobs.Plugin(), runtime.Plugin{}) jobsInst, ok := values["jobs"].(jobs.Jobs) - if !ok { - t.Fatalf("values[\"jobs\"] supplies %T, not jobs.Jobs", values["jobs"]) - } + testassert.Require(t, ok, "values[\"jobs\"] supplies %T, not jobs.Jobs", values["jobs"]) managed := stubManagedEnv(t) ws := t.TempDir() tc := callToolContext(ws, runtime.ToolConstraints{}) @@ -1985,13 +1627,9 @@ func TestBackgroundJobEnvironmentScrubsManagedKey(t *testing.T) { // external sibling changes and the key connects. The job must see the // later sibling and no managed key. t.Setenv("LIGHTCODE_BG_UNLISTED", "visible-value") - if err := managed.TrySet("LIGHTCODE_BG_MANAGED", "managed-secret"); err != nil { - t.Fatalf("managed TrySet: %v", err) - } + testassert.NoError(t, managed.TrySet("LIGHTCODE_BG_MANAGED", "managed-secret")) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want the background start to succeed", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess, "result = %+v, want the background start to succeed", outcome.Result) jobID := assertBackgroundImmediate(t, outcome.Result.Content) deadline := time.Now().Add(15 * time.Second) var output string @@ -2003,12 +1641,8 @@ func TestBackgroundJobEnvironmentScrubsManagedKey(t *testing.T) { } time.Sleep(20 * time.Millisecond) } - if output == "" { - t.Fatal("the background job never produced its output") - } - if strings.Contains(output, "managed-secret") { - t.Fatalf("job output %q carries the managed key", output) - } + testassert.Require(t, output != "", "the background job never produced its output") + testassert.Require(t, !strings.Contains(output, "managed-secret"), "job output %q carries the managed key", output) } // completionTextIn returns the first completion text carried by a model @@ -2033,9 +1667,7 @@ func TestBackgroundStartRealProcessDeliversTruncatedSteeringCompletion(t *testin delivered := make(chan struct{}, 1) bg := &harnessBackground{delivered: delivered} invocation, err := runtime.ConfiguredInvocationForTest(map[string]string{"jobs": `{"max_output_bytes":1024}`}) - if err != nil { - t.Fatalf("ConfiguredInvocationForTest: %v", err) - } + testassert.NoError(t, err) var mu sync.Mutex attempt := 0 completion := "" @@ -2076,15 +1708,11 @@ func TestBackgroundStartRealProcessDeliversTruncatedSteeringCompletion(t *testin session := th.createSession() th.submit(session, "op-1", "run the background command") rec := th.awaitSettled(session, "op-1") - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) - } + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) results := th.readToolResults(session) immediate := results["call-bg"] - if immediate.Status != model.ResultSuccess { - t.Fatalf("immediate result = %+v, want success", immediate) - } + testassert.Require(t, immediate.Status == model.ResultSuccess, "immediate result = %+v, want success", immediate) jobID := assertBackgroundImmediate(t, immediate.Content) mu.Lock() @@ -2097,20 +1725,14 @@ func TestBackgroundStartRealProcessDeliversTruncatedSteeringCompletion(t *testin gotCompletion = completion mu.Unlock() } - if gotCompletion == "" { - t.Fatal("the completion never reached a model boundary as steering input") - } + testassert.Require(t, gotCompletion != "", "the completion never reached a model boundary as steering input") if len(gotCompletion) > 1024 || !strings.HasSuffix(gotCompletion, "\n[truncated]") || !utf8.ValidString(gotCompletion) { t.Fatalf("completion length = %d, want the text capped at 1024 bytes with the trailing marker and a complete UTF-8 prefix", len(gotCompletion)) } - if !strings.Contains(gotCompletion, "Background process "+jobID) { - t.Fatalf("completion = %q, want it to name the started job %q", gotCompletion, jobID) - } + testassert.Require(t, strings.Contains(gotCompletion, "Background process "+jobID), "completion = %q, want it to name the started job %q", gotCompletion, jobID) entries, err := th.store.ReadEntries(context.Background(), session, 0) - if err != nil { - t.Fatalf("ReadEntries: %v", err) - } + testassert.NoError(t, err) found := false for _, entry := range entries { if entry.Kind != harness.EntryInput || !strings.Contains(string(entry.Payload), "Background process ") { @@ -2119,35 +1741,25 @@ func TestBackgroundStartRealProcessDeliversTruncatedSteeringCompletion(t *testin var wire struct { Origin string `json:"origin"` } - if err := json.Unmarshal(entry.Payload, &wire); err != nil { - t.Fatalf("decode input payload: %v", err) - } - if wire.Origin != "runtime" { - t.Fatalf("completion input origin = %q, want runtime", wire.Origin) - } + testassert.NoError(t, json.Unmarshal(entry.Payload, &wire)) + testassert.Equal(t, wire.Origin, "runtime") found = true } - if !found { - t.Fatal("no runtime-origin input entry carries the delivered completion") - } + testassert.Require(t, found, "no runtime-origin input entry carries the delivered completion") } // reserveAndStart seeds one real background job on the composed capability. func reserveAndStart(t *testing.T, jobsInst jobs.Jobs, workspace, command string) string { t.Helper() id, err := jobsInst.Reserve() - if err != nil { - t.Fatalf("Reserve: %v", err) - } - if err := jobsInst.Start(jobs.StartRequest{ + testassert.NoError(t, err) + testassert.NoError(t, jobsInst.Start(jobs.StartRequest{ JobID: id, SessionID: testSessionID, Workspace: workspace, Command: command, Env: os.Environ(), - }); err != nil { - t.Fatalf("Start: %v", err) - } + })) return id } @@ -2158,25 +1770,16 @@ func reserveAndStart(t *testing.T, jobsInst jobs.Jobs, workspace, command string func TestProcessToolReturnsRetainedActionResults(t *testing.T) { byID, values := openToolsComposed(t, t.TempDir(), jobs.Plugin(), runtime.Plugin{}) jobsInst, ok := values["jobs"].(jobs.Jobs) - if !ok { - t.Fatalf("values[\"jobs\"] supplies %T, not jobs.Jobs", values["jobs"]) - } + testassert.Require(t, ok, "values[\"jobs\"] supplies %T, not jobs.Jobs", values["jobs"]) process := byID["process"] ws := t.TempDir() tc := callToolContext(ws, runtime.ToolConstraints{}) t.Run("empty list retains the empty text on the fixed target", func(t *testing.T) { plan := prepare(t, process, tc, "process", `{"action":"list"}`) - if plan.Immediate != nil || plan.Execute == nil { - t.Fatalf("plan = %+v, want an executor plan", plan) - } - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "process.own" || plan.Permissions[0].Target != "*" { - t.Fatalf("pairs = %+v, want process.own on *", plan.Permissions) - } + testassert.Require(t, plan.Immediate == nil && plan.Execute != nil && len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "process.own" && plan.Permissions[0].Target == "*", "plan = %+v, want an executor plan; pairs = %+v, want process.own on *", plan, plan.Permissions) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != "No background processes." { - t.Fatalf("result = %+v, want the retained empty list text", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "No background processes.", "result = %+v, want the retained empty list text", outcome.Result) }) runningID := reserveAndStart(t, jobsInst, ws, "sleep 30") @@ -2185,23 +1788,14 @@ func TestProcessToolReturnsRetainedActionResults(t *testing.T) { t.Run("list carries the retained line format", func(t *testing.T) { plan := prepare(t, process, tc, "process", `{"action":"list"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want success", outcome.Result) - } - if !strings.Contains(outcome.Result.Content, runningID) || !strings.Contains(outcome.Result.Content, "sleep 30") || !strings.Contains(outcome.Result.Content, "(running for") { - t.Fatalf("list = %q, want the retained running line for the seeded job", outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, runningID) && strings.Contains(outcome.Result.Content, "sleep 30") && strings.Contains(outcome.Result.Content, "(running for"), "result = %+v, want success; list = %q, want the retained running line for the seeded job", outcome.Result, outcome.Result.Content) }) t.Run("read of a silent running job retains the placeholder on the owner-scoped target", func(t *testing.T) { plan := prepare(t, process, tc, "process", fmt.Sprintf(`{"action":"read","id":"%s"}`, runningID)) - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "process.own" || plan.Permissions[0].Target != testSessionID+"/"+runningID { - t.Fatalf("pairs = %+v, want process.own on the owner-scoped canonical target", plan.Permissions) - } + testassert.Require(t, len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "process.own" && plan.Permissions[0].Target == testSessionID+"/"+runningID, "pairs = %+v, want process.own on the owner-scoped canonical target", plan.Permissions) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != "(No output yet)" { - t.Fatalf("result = %+v, want the retained empty-output placeholder", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == "(No output yet)", "result = %+v, want the retained empty-output placeholder", outcome.Result) }) t.Run("read carries the job's captured output", func(t *testing.T) { @@ -2215,52 +1809,36 @@ func TestProcessToolReturnsRetainedActionResults(t *testing.T) { } plan := prepare(t, process, tc, "process", fmt.Sprintf(`{"action":"read","id":"%s"}`, outID)) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess || !strings.Contains(outcome.Result.Content, "hello") { - t.Fatalf("result = %+v, want the captured job output", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.Contains(outcome.Result.Content, "hello"), "result = %+v, want the captured job output", outcome.Result) }) t.Run("a foreign session id reaches the capability and returns the same text", func(t *testing.T) { tcForeign := callToolContext(ws, runtime.ToolConstraints{}) tcForeign.AdmittedEntry.SessionID = "ffffffffffffffffffffffffffffffff" plan := prepare(t, process, tcForeign, "process", fmt.Sprintf(`{"action":"kill","id":"%s"}`, runningID)) - if len(plan.Permissions) != 1 || plan.Permissions[0].Target != "ffffffffffffffffffffffffffffffff/"+runningID { - t.Fatalf("pairs = %+v, want the foreign owner-scoped target", plan.Permissions) - } + testassert.Require(t, len(plan.Permissions) == 1 && plan.Permissions[0].Target == "ffffffffffffffffffffffffffffffff/"+runningID, "pairs = %+v, want the foreign owner-scoped target", plan.Permissions) outcome := plan.Execute(context.Background()) want := fmt.Sprintf("process: no process with ID %q", runningID) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != want { - t.Fatalf("result = %+v, want %q", outcome.Result, want) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == want, "result = %+v, want %q", outcome.Result, want) }) t.Run("kill retains the terminated text", func(t *testing.T) { plan := prepare(t, process, tc, "process", fmt.Sprintf(`{"action":"kill","id":"%s"}`, runningID)) - if len(plan.Permissions) != 1 || plan.Permissions[0].Permission != "process.own" || plan.Permissions[0].Target != testSessionID+"/"+runningID { - t.Fatalf("pairs = %+v, want process.own on the owner-scoped canonical target", plan.Permissions) - } + testassert.Require(t, len(plan.Permissions) == 1 && plan.Permissions[0].Permission == "process.own" && plan.Permissions[0].Target == testSessionID+"/"+runningID, "pairs = %+v, want process.own on the owner-scoped canonical target", plan.Permissions) outcome := plan.Execute(context.Background()) want := fmt.Sprintf("Process %s terminated.", runningID) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != want { - t.Fatalf("result = %+v, want %q", outcome.Result, want) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == want, "result = %+v, want %q", outcome.Result, want) plan = prepare(t, process, tc, "process", fmt.Sprintf(`{"action":"kill","id":"%s"}`, outID)) outcome = plan.Execute(context.Background()) want = fmt.Sprintf("Process %s terminated.", outID) - if outcome.Result.Status != model.ResultSuccess || outcome.Result.Content != want { - t.Fatalf("result = %+v, want %q", outcome.Result, want) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && outcome.Result.Content == want, "result = %+v, want %q", outcome.Result, want) }) t.Run("a well-shaped unknown id reaches the capability and returns the retained text", func(t *testing.T) { plan := prepare(t, process, tc, "process", `{"action":"read","id":"deadbeef"}`) - if len(plan.Permissions) != 1 || plan.Permissions[0].Target != testSessionID+"/deadbeef" { - t.Fatalf("pairs = %+v, want the owner-scoped target for the well-shaped unknown id", plan.Permissions) - } + testassert.Require(t, len(plan.Permissions) == 1 && plan.Permissions[0].Target == testSessionID+"/deadbeef", "pairs = %+v, want the owner-scoped target for the well-shaped unknown id", plan.Permissions) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || outcome.Result.Content != `process: no process with ID "deadbeef"` { - t.Fatalf("result = %+v, want the retained unknown-ID text", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && outcome.Result.Content == `process: no process with ID "deadbeef"`, "result = %+v, want the retained unknown-ID text", outcome.Result) }) } @@ -2289,19 +1867,10 @@ func TestProcessToolValidationIsImmediateError(t *testing.T) { } for _, tc := range cases { plan := prepare(t, process, callToolContext(ws, runtime.ToolConstraints{}), "process", tc.args) - if plan.Execute != nil || plan.Immediate == nil { - t.Fatalf("%s: plan = %+v, want one immediate outcome", tc.args, plan) - } - if plan.Immediate.Result.Status != model.ResultError || plan.Immediate.Result.Content != tc.want { - t.Fatalf("%s: immediate = %+v, want error %q", tc.args, plan.Immediate.Result, tc.want) - } - if len(plan.Permissions) != 0 { - t.Fatalf("%s: pairs = %+v, want no permission target", tc.args, plan.Permissions) - } - } - if fake.reserves != 0 || fake.reads != 0 || fake.kills != 0 || fake.lists != 0 { - t.Fatalf("capability calls = reserves %d reads %d kills %d lists %d, want none", fake.reserves, fake.reads, fake.kills, fake.lists) + testassert.Require(t, plan.Execute == nil && plan.Immediate != nil, "%s: plan = %+v, want one immediate outcome", tc.args, plan) + testassert.Require(t, plan.Immediate.Result.Status == model.ResultError && plan.Immediate.Result.Content == tc.want && len(plan.Permissions) == 0, "%s: immediate = %+v, want error %q; %s: pairs = %+v, want no permission target", tc.args, plan.Immediate.Result, tc.want, tc.args, plan.Permissions) } + testassert.Require(t, fake.reserves == 0 && fake.reads == 0 && fake.kills == 0 && fake.lists == 0, "capability calls = reserves %d reads %d kills %d lists %d, want none", fake.reserves, fake.reads, fake.kills, fake.lists) } // TestComposedPermissionDenialSettlesOnlyTheCall denies the background @@ -2340,25 +1909,18 @@ func TestComposedPermissionDenialSettlesOnlyTheCall(t *testing.T) { session := th.createSession() th.submit(session, "op-1", "exercise the denied calls") rec := th.awaitSettled(session, "op-1") - if rec.State.Status != harness.OperationSuccess { - t.Fatalf("operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) - } + testassert.Require(t, rec.State.Status == harness.OperationSuccess, "operation settled %q, want success: %+v", rec.State.Status, rec.State.Terminal) results := th.readToolResults(session) - if len(results) != 3 { - t.Fatalf("committed tool results = %d, want exactly the three calls", len(results)) - } + testassert.Equal(t, len(results), 3) for _, id := range []string{"call-bg", "call-list"} { got := results[id] - if got.Status != model.ResultDenied || got.Content != "Permission denied." { - t.Fatalf("%s = %+v, want the fixed denial", id, got) - } + testassert.Require(t, got.Status == model.ResultDenied && got.Content == "Permission denied.", "%s = %+v, want the fixed denial", id, got) } write := results["call-write"] - if write.Status != model.ResultSuccess || !strings.Contains(write.Content, "Wrote") { - t.Fatalf("call-write = %+v, want the later allowed call to run", write) - } - if data, err := os.ReadFile(filepath.Join(th.workspace, "out.txt")); err != nil || string(data) != "written" { - t.Fatalf("written file = (%q, %v), want the later write on disk", data, err) + testassert.Require(t, write.Status == model.ResultSuccess && strings.Contains(write.Content, "Wrote"), "call-write = %+v, want the later allowed call to run", write) + { + data, err := os.ReadFile(filepath.Join(th.workspace, "out.txt")) + testassert.Require(t, err == nil && string(data) == "written", "written file = (%q, %v), want the later write on disk", data, err) } if fake.reserves != 0 || len(fake.started) != 0 || fake.lists != 0 { t.Fatalf("capability calls = reserves %d started %d lists %d, want none behind the denials", fake.reserves, len(fake.started), fake.lists) @@ -2385,11 +1947,10 @@ func TestRunCommandNilSubprocessEnvNoStart(t *testing.T) { tc.SubprocessEnv = testCase.producer plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"touch `+marker+`"}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || !strings.Contains(outcome.Result.Content, testCase.want) { - t.Fatalf("result = %+v, want the %q refusal", outcome.Result, testCase.want) - } - if _, err := os.Stat(marker); !os.IsNotExist(err) { - t.Fatalf("the command started without an environment producer (%v)", err) + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, testCase.want), "result = %+v, want the %q refusal", outcome.Result, testCase.want) + { + _, err := os.Stat(marker) + testassert.Require(t, os.IsNotExist(err), "the command started without an environment producer (%v)", err) } }) } @@ -2406,9 +1967,7 @@ func TestBackgroundNilSubprocessEnvNoReservation(t *testing.T) { tc.Background = &scriptBackground{} plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"true","background":true}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || !strings.Contains(outcome.Result.Content, "no subprocess environment producer") { - t.Fatalf("result = %+v, want the missing-producer error", outcome.Result) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, "no subprocess environment producer"), "result = %+v, want the missing-producer error", outcome.Result) if fake.reserves != 0 || len(fake.started) != 0 { t.Fatalf("reserves = %d, started = %d, want no reservation and no start", fake.reserves, len(fake.started)) } @@ -2422,9 +1981,7 @@ func TestRunCommandEnvResolvedAtStartNotPrepare(t *testing.T) { key := "LIGHTCODE_SCRUB_LATE" external := "LIGHTCODE_TIMING_EXTERNAL" t.Setenv(key, "late-secret") // cleanup reservation only - if err := os.Unsetenv(key); err != nil { - t.Fatal(err) - } + testassert.NoError(t, os.Unsetenv(key)) t.Setenv(external, "early-value") runCommand := openTools(t, t.TempDir())["run_command"] ws := t.TempDir() @@ -2436,16 +1993,9 @@ func TestRunCommandEnvResolvedAtStartNotPrepare(t *testing.T) { // miss the late managed key; the start-time resolution sees the later // sibling and scrubs the key. t.Setenv(external, "later-value") - if err := managed.TrySet(key, "late-secret"); err != nil { - t.Fatalf("managed TrySet: %v", err) - } + testassert.NoError(t, managed.TrySet(key, "late-secret")) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultSuccess { - t.Fatalf("result = %+v, want success", outcome.Result) - } - if strings.TrimSpace(outcome.Result.Content) != "later-value|" { - t.Fatalf("result = %q, want the post-Prepare sibling and the scrubbed key", outcome.Result.Content) - } + testassert.Require(t, outcome.Result.Status == model.ResultSuccess && strings.TrimSpace(outcome.Result.Content) == "later-value|", "result = %+v, want success; result = %q, want the post-Prepare sibling and the scrubbed key", outcome.Result, outcome.Result.Content) } // TestBackgroundNilEnvSnapshotAbortsBeforeStart proves the forbidden-sibling @@ -2460,13 +2010,6 @@ func TestBackgroundNilEnvSnapshotAbortsBeforeStart(t *testing.T) { tc.Background = &scriptBackground{} plan := prepare(t, byID["run_command"], tc, "run_command", `{"command":"true","background":true}`) outcome := plan.Execute(context.Background()) - if outcome.Result.Status != model.ResultError || !strings.Contains(outcome.Result.Content, "returned no environment") { - t.Fatalf("result = %+v, want the nil-snapshot refusal", outcome.Result) - } - if len(fake.started) != 0 { - t.Fatalf("started = %d, want no process start", len(fake.started)) - } - if len(fake.aborted) != 1 { - t.Fatalf("aborted = %v, want the reservation aborted once", fake.aborted) - } + testassert.Require(t, outcome.Result.Status == model.ResultError && strings.Contains(outcome.Result.Content, "returned no environment"), "result = %+v, want the nil-snapshot refusal", outcome.Result) + testassert.Require(t, len(fake.started) == 0 && len(fake.aborted) == 1, "got %v, want %v; aborted = %v, want the reservation aborted once", len(fake.started), 0, fake.aborted) }